diff --git a/.gitignore b/.gitignore
index 5f9e0ca0..d68701e2 100644
--- a/.gitignore
+++ b/.gitignore
@@ -266,6 +266,8 @@
 /testing/location_tags.json
 /testing/rts/
 /testserver.log
+/tools/bisect/.bisect-builds-cache.json
+/tools/bisect/catapult_bisect_dep/
 # See third_party/.gitignore for entries covering src/third_party.
 /tools/.bisect-builds-cache.json
 /tools/cygprofile/*.wprgo
diff --git a/DEPS b/DEPS
index 801b3bd..3ddb40e 100644
--- a/DEPS
+++ b/DEPS
@@ -304,19 +304,19 @@
   # 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': 'a4ccbeaa41c7afd89c02c70fddd5dabaed66e29c',
+  'src_internal_revision': '596ba505f7df80c737fbeed3216f75fffe7c4763',
   # 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': '3bc48d128d4b081bb75a917c51c38f93d5a0e719',
+  'skia_revision': '01f5db01210ff282f1ccb7c6e3cf53a8bc2453d3',
   # 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': 'c61acc78d87d6796b8555c28ee5026281f543073',
+  'v8_revision': '5385579a7cf01f8fe63ce2b417fdee0a18be3351',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling ANGLE
   # and whatever else without interference from each other.
-  'angle_revision': 'b0875f124a2459318e974653517f87d8fcbb50c2',
+  'angle_revision': 'a951e0e0823408ce2a5a827ac135c9454f6fc230',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling SwiftShader
   # and whatever else without interference from each other.
@@ -359,7 +359,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling freetype
   # and whatever else without interference from each other.
-  'freetype_revision': '7bd887f177b765165ecf7c443b8d0bab88034df0',
+  'freetype_revision': 'd0e3239f32a0fe71c234cf2c1ce7a90cb11b64bb',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling freetype
   # 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': '9cc73b5e0315d6e6bc9b7d47458b076c94ed1cc0',
+  'devtools_frontend_revision': '674f95aa61785d51f0cbec824cf16d8cbf6f5a8a',
   # 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': 'd3bebe6cf996439ed197d1c83e323baf8a9a7135',
+  'dawn_revision': 'a87c5333bf91c8c1eec2518c01235f311d7ccb0c',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
@@ -539,6 +539,7 @@
   'webrtc.googlesource.com',
 
    # TODO(337061377): Move into a separate allowed gcs bucket list.
+  'chromium-ads-detection',
   'chromium-nodejs',
   'chrome-linux-sysroot',
   'chromium-fonts',
@@ -929,12 +930,12 @@
 
   'src/clank': {
     'url': Var('chrome_git') + '/clank/internal/apps.git' + '@' +
-    '86a403cb8a162ba2670eafa8f962ae894531a42b',
+    '53d4ad86f9f87e560f3ebd5feefa263a4aeae823',
     'condition': 'checkout_android and checkout_src_internal',
   },
 
   'src/docs/website': {
-    'url': Var('chromium_git') + '/website.git' + '@' + 'a91c462f4abff8bd8874a4b7cb5420647a0f0235',
+    'url': Var('chromium_git') + '/website.git' + '@' + 'fea62e99e8250d5596ecbb806298b2e2fec4a6de',
   },
 
   'src/ios/third_party/earl_grey2/src': {
@@ -1088,7 +1089,7 @@
     'packages': [
       {
           'package': 'chromium/third_party/androidx',
-          'version': 'DwQdkBicJUAwrRqlcXGhgR9YPbBfSPQDXV_lL6Lhe2oC',
+          'version': 'TvCXIrNB4pm9wP19HypfB8sqiAryWBDsBMYXvgAUVFIC',
       },
     ],
     'condition': 'checkout_android',
@@ -1304,7 +1305,7 @@
     Var('chromium_git') + '/devtools/devtools-frontend' + '@' + Var('devtools_frontend_revision'),
 
   'src/third_party/devtools-frontend-internal': {
-      'url': Var('chrome_git') + '/devtools/devtools-internal.git' + '@' + 'b905e0bc023bf72eaccbb36735adc6873ceff486',
+      'url': Var('chrome_git') + '/devtools/devtools-internal.git' + '@' + '6307112179b9bb77d936d8d433cca67d0bf64ba1',
     'condition': 'checkout_src_internal',
   },
 
@@ -1900,6 +1901,21 @@
       'dep_type': 'cipd',
   },
 
+  'src/third_party/subresource-filter-ruleset/data': {
+      'dep_type': 'gcs',
+      'condition': 'non_git_source',
+      'bucket': 'chromium-ads-detection',
+      'objects': [
+          {
+              'object_name': 'e4d1c702ca1b5497a3abcdd9495a5d0758f19ffc',
+              'sha256sum': 'ae2fd01d2908591e0f39343a5b4a78baa8e7d6cac9d78ba79c502fe0a15ce3ee',
+              'size_bytes': 70106,
+              'generation': 1695223938564350,
+              'output_file': 'UnindexedRules',
+          },
+      ],
+  },
+
   'src/third_party/swift-format': {
       'packages': [
           {
@@ -1993,10 +2009,10 @@
     Var('chromium_git') + '/external/khronosgroup/webgl.git' + '@' + '992583db238555e1b07106d2aeed93468dcbd7f6',
 
   'src/third_party/webgpu-cts/src':
-    Var('chromium_git') + '/external/github.com/gpuweb/cts.git' + '@' + '31fc42ca8a0518b64668cad45139ec9c3cac87ae',
+    Var('chromium_git') + '/external/github.com/gpuweb/cts.git' + '@' + 'c032a348d7aac5b5bfa9146fb0702c069306231c',
 
   'src/third_party/webrtc':
-    Var('webrtc_git') + '/src.git' + '@' + 'a2e33ed8808384ed46adb5ee3e0f8a2932645655',
+    Var('webrtc_git') + '/src.git' + '@' + '89679bfd02dad86938b401da3e61f699ddc6b13b',
 
   # 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.
@@ -2119,7 +2135,7 @@
     'packages': [
       {
         'package': 'chromeos_internal/apps/help_app/app',
-        'version': 'JKRM_ThHYxc-ebO3PzNXHKnl6rLb9-VbipefrQzt5KYC',
+        'version': 'WZJsGtEOvh_ui27Uy2klwcU0Vlu3TnfU8eKK1hojUCwC',
       },
     ],
     'condition': 'checkout_chromeos and checkout_src_internal',
@@ -4173,7 +4189,7 @@
 
   'src/components/optimization_guide/internal': {
       'url': Var('chrome_git') + '/chrome/components/optimization_guide.git' + '@' +
-        '9ab8d0e807bfc59c021093c7cb67a841c2347832',
+        '4aa461253ca6e5465faba2c96c60c269be1c17dc',
       'condition': 'checkout_src_internal',
   },
 
@@ -5150,17 +5166,6 @@
                 'src/third_party/arcore-android-sdk/test-apks/update.py',
     ],
   },
-  {
-    'name': 'subresource-filter-ruleset',
-    'pattern': '.',
-    'action': [ 'python3',
-                'src/third_party/depot_tools/download_from_google_storage.py',
-                '--no_resume',
-                '--no_auth',
-                '--bucket', 'chromium-ads-detection',
-                '-s', 'src/third_party/subresource-filter-ruleset/data/UnindexedRules.sha1',
-    ],
-  },
   # Download AFDO profiles for ChromeOS for each architecture.
   {
     'name': 'Fetch ChromeOS AFDO profiles (from Intel Atom cores)',
diff --git a/android_webview/expectations/trichrome_webview_32_64_bundle.arm64.libs_and_assets.expected b/android_webview/expectations/trichrome_webview_32_64_bundle.arm64.libs_and_assets.expected
index 5197e7f..2ebfa096f 100644
--- a/android_webview/expectations/trichrome_webview_32_64_bundle.arm64.libs_and_assets.expected
+++ b/android_webview/expectations/trichrome_webview_32_64_bundle.arm64.libs_and_assets.expected
@@ -84,3 +84,4 @@
 apk_path=assets/stored-locales/zh-HK.pak, compress=False, alignment=4
 apk_path=assets/stored-locales/zh-TW.pak, compress=False, alignment=4
 apk_path=assets/stored-locales/zu.pak, compress=False, alignment=4
+apk_path=assets/v8_context_snapshot_64.bin, compress=False, alignment=4
diff --git a/android_webview/expectations/trichrome_webview_64_32_bundle.arm64.libs_and_assets.expected b/android_webview/expectations/trichrome_webview_64_32_bundle.arm64.libs_and_assets.expected
index e9244d1..f9176f5 100644
--- a/android_webview/expectations/trichrome_webview_64_32_bundle.arm64.libs_and_assets.expected
+++ b/android_webview/expectations/trichrome_webview_64_32_bundle.arm64.libs_and_assets.expected
@@ -84,3 +84,4 @@
 apk_path=assets/stored-locales/zh-HK.pak, compress=False, alignment=4
 apk_path=assets/stored-locales/zh-TW.pak, compress=False, alignment=4
 apk_path=assets/stored-locales/zu.pak, compress=False, alignment=4
+apk_path=assets/v8_context_snapshot_32.bin, compress=False, alignment=4
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 a408200..f6d8cff 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
@@ -257,9 +257,6 @@
                 AutofillFeatures.AUTOFILL_NEW_FOCUS_EVENTS,
                 "Changes the semantics of Autofill's focus-change events"),
         Flag.baseFeature(
-                AutofillFeatures.AUTOFILL_PARSING_PATTERN_PROVIDER,
-                "Enables Autofill to use its new method to retrieve parsing patterns."),
-        Flag.baseFeature(
                 AutofillFeatures.AUTOFILL_PAGE_LANGUAGE_DETECTION,
                 "Enables Autofill to retrieve the page language for form parsing."),
         Flag.baseFeature(
diff --git a/ash/accessibility/accessibility_controller.cc b/ash/accessibility/accessibility_controller.cc
index cb11ccd9..1718f5e0 100644
--- a/ash/accessibility/accessibility_controller.cc
+++ b/ash/accessibility/accessibility_controller.cc
@@ -1707,15 +1707,11 @@
 
 bool AccessibilityController::IsLiveCaptionSettingVisibleInTray() {
   return captions::IsLiveCaptionFeatureSupported() &&
-         base::FeatureList::IsEnabled(
-             media::kLiveCaptionSystemWideOnChromeOS) &&
          live_caption().IsVisibleInTray();
 }
 
 bool AccessibilityController::IsEnterpriseIconVisibleForLiveCaption() {
   return captions::IsLiveCaptionFeatureSupported() &&
-         base::FeatureList::IsEnabled(
-             media::kLiveCaptionSystemWideOnChromeOS) &&
          live_caption().IsEnterpriseIconVisible();
 }
 
diff --git a/ash/accessibility/accessibility_controller_unittest.cc b/ash/accessibility/accessibility_controller_unittest.cc
index 22ff2434..17cb73cf 100644
--- a/ash/accessibility/accessibility_controller_unittest.cc
+++ b/ash/accessibility/accessibility_controller_unittest.cc
@@ -111,13 +111,12 @@
   void SetUp() override {
     scoped_feature_list_.InitWithFeatures(
         /*enabled_features=*/{media::kLiveCaption,
-                              media::kLiveCaptionSystemWideOnChromeOS,
                               ash::features::kOnDeviceSpeechRecognition,
                               ::features::kAccessibilityFaceGaze,
                               ::features::kAccessibilityMouseKeys,
                               ::features::
                                   kAccessibilityCaretBlinkIntervalSetting},
-        /*disabled_feaures=*/{});
+        /*disabled_features=*/{});
     AshTestBase::SetUp();
   }
 
diff --git a/ash/quick_pair/common/fast_pair/fast_pair_metrics.cc b/ash/quick_pair/common/fast_pair/fast_pair_metrics.cc
index a5d1896..d6e1645 100644
--- a/ash/quick_pair/common/fast_pair/fast_pair_metrics.cc
+++ b/ash/quick_pair/common/fast_pair/fast_pair_metrics.cc
@@ -13,7 +13,6 @@
 #include "components/cross_device/logging/logging.h"
 #include "components/metrics/structured/structured_events.h"
 #include "components/metrics/structured/structured_metrics_client.h"
-#include "components/metrics/structured/structured_metrics_features.h"
 
 namespace {
 
@@ -1588,10 +1587,6 @@
 void RecordStructuredDiscoveryNotificationShown(
     const Device& device,
     const device::BluetoothDevice* bt_device) {
-  if (!base::FeatureList::IsEnabled(metrics::structured::kFastPairMetrics)) {
-    return;
-  }
-
   CD_LOG(INFO, Feature::FP) << __func__;
   int model_id;
   if (!base::HexStringToInt(device.metadata_id(), &model_id)) {
@@ -1613,10 +1608,6 @@
 
 void RecordStructuredPairingStarted(const Device& device,
                                     const device::BluetoothDevice* bt_device) {
-  if (!base::FeatureList::IsEnabled(metrics::structured::kFastPairMetrics)) {
-    return;
-  }
-
   CD_LOG(INFO, Feature::FP) << __func__;
   int model_id;
   if (!base::HexStringToInt(device.metadata_id(), &model_id)) {
@@ -1638,10 +1629,6 @@
 
 void RecordStructuredPairingComplete(const Device& device,
                                      const device::BluetoothDevice* bt_device) {
-  if (!base::FeatureList::IsEnabled(metrics::structured::kFastPairMetrics)) {
-    return;
-  }
-
   CD_LOG(INFO, Feature::FP) << __func__;
   int model_id;
   if (!base::HexStringToInt(device.metadata_id(), &model_id)) {
@@ -1662,10 +1649,6 @@
 }
 
 void RecordStructuredPairFailure(const Device& device, PairFailure failure) {
-  if (!base::FeatureList::IsEnabled(metrics::structured::kFastPairMetrics)) {
-    return;
-  }
-
   CD_LOG(INFO, Feature::FP) << __func__;
   int model_id;
   if (!base::HexStringToInt(device.metadata_id(), &model_id)) {
diff --git a/ash/system/accessibility/accessibility_detailed_view_unittest.cc b/ash/system/accessibility/accessibility_detailed_view_unittest.cc
index ef5a0777..b814a88a 100644
--- a/ash/system/accessibility/accessibility_detailed_view_unittest.cc
+++ b/ash/system/accessibility/accessibility_detailed_view_unittest.cc
@@ -154,8 +154,7 @@
  public:
   AccessibilityDetailedViewTest() {
     scoped_feature_list_.InitWithFeatures(
-        {media::kLiveCaption, media::kLiveCaptionSystemWideOnChromeOS,
-         ash::features::kOnDeviceSpeechRecognition,
+        {media::kLiveCaption, ash::features::kOnDeviceSpeechRecognition,
          ::features::kAccessibilityFaceGaze},
         {});
   }
diff --git a/ash/system/audio/audio_detailed_view.cc b/ash/system/audio/audio_detailed_view.cc
index ac60db81..982aec9 100644
--- a/ash/system/audio/audio_detailed_view.cc
+++ b/ash/system/audio/audio_detailed_view.cc
@@ -248,7 +248,10 @@
 void AudioDetailedView::CreateItems() {
   CreateScrollableList();
   CreateTitleRow(IDS_ASH_STATUS_TRAY_AUDIO_TITLE);
-  CreateLiveCaptionView();
+
+  if (captions::IsLiveCaptionFeatureSupported()) {
+    CreateLiveCaptionView();
+  }
 
   mic_gain_controller_ = std::make_unique<MicGainSliderController>();
   unified_volume_slider_controller_ =
@@ -589,8 +592,10 @@
   views::View* container =
       scroll_content()->AddChildView(std::make_unique<RoundedContainer>());
 
-  // Adds the live caption toggle.
-  CreateLiveCaptionView();
+  if (captions::IsLiveCaptionFeatureSupported()) {
+    // Adds the live caption toggle.
+    CreateLiveCaptionView();
+  }
 
   // Adds audio output devices.
   const bool has_output_devices = output_devices_.size() > 0;
diff --git a/ash/system/audio/audio_detailed_view_pixeltest.cc b/ash/system/audio/audio_detailed_view_pixeltest.cc
index 838b03b..f1053e9 100644
--- a/ash/system/audio/audio_detailed_view_pixeltest.cc
+++ b/ash/system/audio/audio_detailed_view_pixeltest.cc
@@ -14,6 +14,7 @@
 #include "chromeos/ash/components/audio/cras_audio_handler.h"
 #include "chromeos/ash/components/dbus/audio/audio_node.h"
 #include "chromeos/ash/components/dbus/audio/fake_cras_audio_client.h"
+#include "media/base/media_switches.h"
 #include "ui/base/ui_base_features.h"
 #include "ui/views/view.h"
 
@@ -24,11 +25,13 @@
 // Pixel tests for the quick settings audio detailed view.
 class AudioDetailedViewPixelTest : public AshTestBase {
  public:
-  AudioDetailedViewPixelTest() : scoped_features_() {
-    scoped_features_.InitWithFeatures({::features::kChromeRefresh2023,
-                                       ::features::kChromeRefreshSecondary2023,
-                                       ::features::kChromeRefresh2023NTB},
-                                      {});
+  AudioDetailedViewPixelTest() {
+    scoped_features_.InitWithFeatures(
+        {::features::kChromeRefresh2023,
+         ::features::kChromeRefreshSecondary2023,
+         ::features::kChromeRefresh2023NTB, media::kLiveCaption,
+         features::kOnDeviceSpeechRecognition},
+        {});
   }
 
   // AshTestBase:
diff --git a/ash/system/audio/audio_effects_controller_unittest.cc b/ash/system/audio/audio_effects_controller_unittest.cc
index 85788630..530bb78 100644
--- a/ash/system/audio/audio_effects_controller_unittest.cc
+++ b/ash/system/audio/audio_effects_controller_unittest.cc
@@ -468,8 +468,7 @@
 TEST_F(AudioEffectsControllerTest, LiveCaptionSupported) {
   base::test::ScopedFeatureList scoped_feature_list;
   scoped_feature_list.InitWithFeatures(
-      {media::kLiveCaption, media::kLiveCaptionSystemWideOnChromeOS,
-       features::kOnDeviceSpeechRecognition,
+      {features::kOnDeviceSpeechRecognition,
        features::kShowLiveCaptionInVideoConferenceTray},
       {});
 
@@ -493,8 +492,7 @@
 TEST_F(AudioEffectsControllerTest, DoNotShowLiveCaptionInVcTray) {
   base::test::ScopedFeatureList scoped_feature_list;
   scoped_feature_list.InitWithFeatures(
-      {media::kLiveCaption, media::kLiveCaptionSystemWideOnChromeOS,
-       features::kOnDeviceSpeechRecognition},
+      {media::kLiveCaption, features::kOnDeviceSpeechRecognition},
       {features::kShowLiveCaptionInVideoConferenceTray});
 
   SimulateUserLogin("testuser1@gmail.com");
@@ -509,9 +507,7 @@
   // Ensure that live caption is supported.
   base::test::ScopedFeatureList scoped_feature_list;
   scoped_feature_list.InitWithFeatures(
-      {media::kLiveCaption, media::kLiveCaptionSystemWideOnChromeOS,
-       features::kOnDeviceSpeechRecognition},
-      {});
+      {media::kLiveCaption, features::kOnDeviceSpeechRecognition}, {});
 
   SimulateUserLogin("testuser1@gmail.com");
 
@@ -532,9 +528,7 @@
   // Ensure that live caption is supported.
   base::test::ScopedFeatureList scoped_feature_list;
   scoped_feature_list.InitWithFeatures(
-      {media::kLiveCaption, media::kLiveCaptionSystemWideOnChromeOS,
-       features::kOnDeviceSpeechRecognition},
-      {});
+      {media::kLiveCaption, features::kOnDeviceSpeechRecognition}, {});
 
   SimulateUserLogin("testuser1@gmail.com");
 
@@ -555,9 +549,7 @@
   // Ensure that live caption is supported.
   base::test::ScopedFeatureList scoped_feature_list;
   scoped_feature_list.InitWithFeatures(
-      {media::kLiveCaption, media::kLiveCaptionSystemWideOnChromeOS,
-       features::kOnDeviceSpeechRecognition},
-      {});
+      {media::kLiveCaption, features::kOnDeviceSpeechRecognition}, {});
 
   SimulateUserLogin("testuser1@gmail.com");
 
@@ -579,9 +571,7 @@
   // Ensure that live caption is supported.
   base::test::ScopedFeatureList scoped_feature_list;
   scoped_feature_list.InitWithFeatures(
-      {media::kLiveCaption, media::kLiveCaptionSystemWideOnChromeOS,
-       features::kOnDeviceSpeechRecognition},
-      {});
+      {media::kLiveCaption, features::kOnDeviceSpeechRecognition}, {});
 
   SimulateUserLogin("testuser1@gmail.com");
 
@@ -607,8 +597,7 @@
   // Ensure that live caption is supported.
   base::test::ScopedFeatureList scoped_feature_list;
   scoped_feature_list.InitWithFeatures(
-      {media::kLiveCaption, media::kLiveCaptionSystemWideOnChromeOS,
-       features::kOnDeviceSpeechRecognition,
+      {media::kLiveCaption, features::kOnDeviceSpeechRecognition,
        features::kShowLiveCaptionInVideoConferenceTray},
       {});
 
diff --git a/ash/system/audio/unified_audio_detailed_view_controller_unittest.cc b/ash/system/audio/unified_audio_detailed_view_controller_unittest.cc
index dbefc59f9b..7849eb9 100644
--- a/ash/system/audio/unified_audio_detailed_view_controller_unittest.cc
+++ b/ash/system/audio/unified_audio_detailed_view_controller_unittest.cc
@@ -138,7 +138,7 @@
 // Test param is the version of stable device id used by audio node.
 class UnifiedAudioDetailedViewControllerTest : public AshTestBase {
  public:
-  UnifiedAudioDetailedViewControllerTest() {}
+  UnifiedAudioDetailedViewControllerTest() = default;
   ~UnifiedAudioDetailedViewControllerTest() override = default;
 
   // AshTestBase:
@@ -580,9 +580,7 @@
 TEST_F(UnifiedAudioDetailedViewControllerTest, ToggleLiveCaption) {
   scoped_feature_list_.Reset();
   scoped_feature_list_.InitWithFeatures(
-      {media::kLiveCaption, media::kLiveCaptionSystemWideOnChromeOS,
-       ash::features::kOnDeviceSpeechRecognition},
-      {});
+      {media::kLiveCaption, ash::features::kOnDeviceSpeechRecognition}, {});
 
   EXPECT_TRUE(live_caption_view());
   EXPECT_FALSE(live_caption_enabled());
@@ -598,8 +596,8 @@
 
 TEST_F(UnifiedAudioDetailedViewControllerTest, LiveCaptionNotAvailable) {
   // If the Live Caption feature flags are not set, the Live Caption toggle will
-  // not be enabled in audio settings.
-  EXPECT_TRUE(live_caption_view());
+  // not be visible in audio settings.
+  EXPECT_FALSE(live_caption_view());
   EXPECT_FALSE(live_caption_enabled());
 }
 
@@ -667,8 +665,7 @@
     // calling speech::SodaInstaller::GetInstance() returns a valid instance.
     scoped_feature_list_.InitWithFeatures(
         {ash::features::kOnDeviceSpeechRecognition, media::kLiveCaption,
-         media::kLiveCaptionMultiLanguage,
-         media::kLiveCaptionSystemWideOnChromeOS},
+         media::kLiveCaptionMultiLanguage},
         {});
     soda_installer_impl_ =
         std::make_unique<speech::SodaInstallerImplChromeOS>();
diff --git a/ash/user_education/OWNERS b/ash/user_education/OWNERS
index 7dd5aaa..38086bd 100644
--- a/ash/user_education/OWNERS
+++ b/ash/user_education/OWNERS
@@ -1 +1,4 @@
 dmblack@google.com
+
+# For framework-related code only:
+dfried@chromium.org
diff --git a/ash/webui/common/resources/sea_pen/sea_pen_input_query_element.html b/ash/webui/common/resources/sea_pen/sea_pen_input_query_element.html
index bd4792d..0a5cec71 100644
--- a/ash/webui/common/resources/sea_pen/sea_pen_input_query_element.html
+++ b/ash/webui/common/resources/sea_pen/sea_pen_input_query_element.html
@@ -8,9 +8,10 @@
   }
 
   #queryInput {
+    --cr-input-error-display: none;
     padding: 32px 0 12px;
     text-align: center;
-    --cr-input-error-display: none;
+    width: 50%;
   }
 
   #searchButtons {
@@ -47,7 +48,7 @@
   </cr-input>
   <div id="searchButtons">
     <template is="dom-if" if="[[thumbnailsLoading_]]">
-      <span id="thumbnailsLoadingText" aria-description$="[[getTemplateAriaLabel_(templateTokens_)]]" aria-live="polite">
+      <span id="thumbnailsLoadingText" aria-description="{{textValue_}}" aria-live="polite">
         [[i18n('seaPenThumbnailsLoading')]]
       </span>
     </template>
diff --git a/ash/webui/common/resources/sea_pen/sea_pen_template_query_element.html b/ash/webui/common/resources/sea_pen/sea_pen_template_query_element.html
index a33509f..9b6b7b72 100644
--- a/ash/webui/common/resources/sea_pen/sea_pen_template_query_element.html
+++ b/ash/webui/common/resources/sea_pen/sea_pen_template_query_element.html
@@ -1,11 +1,30 @@
 <style include="common cros-button-style">
-  .main {
+  #container {
     display: flex;
     flex-direction: column;
     height: auto;
     justify-content: center;
     margin-block-start: 32px;
     padding-inline: 40px;
+    transition: height 300ms cubic-bezier(0, 0, 0, 1);
+  }
+
+  @keyframes fade-in {
+    from {
+      opacity: 0;
+    }
+
+    to {
+      opacity: 1;
+    }
+  }
+
+  sea-pen-options {
+    animation-direction: normal;
+    animation-duration: 200ms;
+    animation-iteration-count: 1;
+    animation-name: fade-in;
+    animation-timing-function: linear;
   }
 
   #placeholder {
@@ -113,8 +132,9 @@
     display: block;
   }
 </style>
-<div class="main">
-  <template is="dom-if" if="[[shouldShowOptions_(options_)]]">
+<div id="container" class="main">
+  <template is="dom-if" if="[[shouldShowOptions_(options_)]]"
+      on-dom-change="onSeaPenOptionsDomChanged_">
     <sea-pen-options
         options="[[options_]]"
         selected-chip="[[selectedChip_]]"
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 614805a..d5aa2f7 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
@@ -48,6 +48,12 @@
   return true;
 }
 
+export interface SeaPenTemplateQueryElement {
+  $: {
+    container: HTMLDivElement,
+  };
+}
+
 export class SeaPenTemplateQueryElement extends WithSeaPenStore {
   static get is() {
     return 'sea-pen-template-query';
@@ -140,6 +146,8 @@
   private searchButtonText_: string;
   private searchButtonIcon_: string;
   private isSelectingOptions: boolean;
+  private containerOriginalHeight_: number;
+  private resizeObserver_: ResizeObserver;
 
   static get observers() {
     return [
@@ -158,15 +166,45 @@
         'seaPenQuery_', state => state.currentSeaPenQuery);
     this.updateFromStore();
 
+    this.resizeObserver_ =
+        new ResizeObserver(() => this.animateContainerHeight());
+
     beforeNextRender(this, () => {
       this.inspireMeAnimation_ =
           this.shadowRoot?.querySelector<LottieRenderer>('#inspireMeAnimation');
       if (this.inspireMeAnimation_) {
         this.inspireMeAnimation_.autoplay = false;
       }
+
+      this.containerOriginalHeight_ = this.$.container.scrollHeight;
+      this.$.container.style.height = `${this.containerOriginalHeight_}px`;
     });
   }
 
+  override disconnectedCallback() {
+    super.disconnectedCallback();
+    this.resizeObserver_.disconnect();
+    this.removeEventListener('click', this.onClick_);
+  }
+
+  // Called when there is a custom dom-change event dispatched from
+  // `sea-pen-options` element.
+  private onSeaPenOptionsDomChanged_() {
+    const optionsContainer = this.shadowRoot!.querySelector('sea-pen-options');
+    if (optionsContainer) {
+      this.resizeObserver_.observe(optionsContainer);
+    }
+  }
+
+  // Updates main container's height and applies transition style.
+  private animateContainerHeight() {
+    const optionsContainer = this.shadowRoot!.querySelector('sea-pen-options');
+    const optionsContainerHeight =
+        optionsContainer ? optionsContainer.scrollHeight : 0;
+    this.$.container.style.height =
+        `${this.containerOriginalHeight_ + optionsContainerHeight}px`;
+  }
+
   // After exiting from the option selection (by "Esc" key or clicking on
   // anywhere), clear the selected chip state and set focus on the last selected
   // chip.
diff --git a/ash/webui/demo_mode_app_ui/resources/demo_mode_metrics_service.js b/ash/webui/demo_mode_app_ui/resources/demo_mode_metrics_service.js
index 3102492d..43ee8da 100644
--- a/ash/webui/demo_mode_app_ui/resources/demo_mode_metrics_service.js
+++ b/ash/webui/demo_mode_app_ui/resources/demo_mode_metrics_service.js
@@ -34,6 +34,7 @@
  * @enum {string}
  */
 export const DetailsPage = {
+  // 2023 CBX first released:
   ADOBE: 'Adobe',
   BATTERY: 'Battery',
   COMPARISON: 'Comparison',
@@ -65,6 +66,21 @@
   GEMINI_FOR_WORK_SPACE: 'GeminiForWorkSpace',
   AI_BACKGROUND: 'AIBackground',
   AI_PREMIUM_PLAN: 'AIPremiumPlan',
+
+  // CB, note that detail page for generic was not recorded before 2024 C1:
+  FAST_BOOT: 'FastBoot',
+  AUTO_UPDATE: 'AutoUpdate',
+  EASY_SETUP:'EasySetup',
+  LAUNCHER_SEARCH:'LauncherSearch',
+  GOOGLE_TOOLS_BUILT_IN:'GoogleToolsBuiltIn',
+  TITAN_C2:'TitanC2',
+  CREATIVITY:'Creativity',
+  ENTERTAINMENT:'Entertainment',
+  PRODUCTIVITY:'Productivity',
+  PLAY_STORE:'PlayStore',
+
+  // Enum shared between CB & CBX are: BATTERY, GOOGLE_APPS, NEARBY_SHARE,
+  // MESSAGING, BUILT_IN_SECURITY,MS_365_APPS, SWITCHING, COMPARISON
 };
 
 /**
diff --git a/ash/webui/diagnostics_ui/resources/routine_group.ts b/ash/webui/diagnostics_ui/resources/routine_group.ts
index a81e57b..10e803aa 100644
--- a/ash/webui/diagnostics_ui/resources/routine_group.ts
+++ b/ash/webui/diagnostics_ui/resources/routine_group.ts
@@ -25,7 +25,7 @@
  */
 export class RoutineGroup {
   routineProperties: RoutineProperties[];
-  private nonBlockingRoutines: Set<RoutineType>;
+  nonBlockingRoutines: Set<RoutineType>;
   routines: RoutineType[];
   groupName: string;
   progress: ExecutionProgress;
diff --git a/ash/wm/desks/desk_button/desk_button.cc b/ash/wm/desks/desk_button/desk_button.cc
index ab45877..1a209ac 100644
--- a/ash/wm/desks/desk_button/desk_button.cc
+++ b/ash/wm/desks/desk_button/desk_button.cc
@@ -62,6 +62,11 @@
 
 DeskButton::~DeskButton() {}
 
+void DeskButton::SetZeroState(bool zero_state) {
+  zero_state_ = zero_state;
+  UpdateBackground();
+}
+
 gfx::Size DeskButton::CalculatePreferredSize(
     const views::SizeBounds& available_size) const {
   if (zero_state_) {
@@ -191,8 +196,7 @@
   SetPaintToLayer();
   layer()->SetFillsBoundsOpaquely(false);
   SetFlipCanvasOnPaintForRTLUI(false);
-  SetBackground(views::CreateThemedRoundedRectBackground(
-      cros_tokens::kCrosSysSystemOnBase1, kDeskButtonCornerRadius));
+  UpdateBackground();
 
   SetInstallFocusRingOnFocus(true);
   SetFocusBehavior(views::View::FocusBehavior::ALWAYS);
@@ -237,10 +241,7 @@
 
   is_activated_ = is_activated;
 
-  SetBackground(views::CreateThemedRoundedRectBackground(
-      is_activated_ ? cros_tokens::kCrosSysSystemPrimaryContainer
-                    : cros_tokens::kCrosSysSystemOnBase1,
-      kDeskButtonCornerRadius));
+  UpdateBackground();
   desk_name_label_->SetEnabledColorId(
       is_activated_ ? cros_tokens::kCrosSysSystemOnPrimaryContainer
                     : cros_tokens::kCrosSysOnSurface);
@@ -374,6 +375,14 @@
   }
 }
 
+void DeskButton::UpdateBackground() {
+  SetBackground(views::CreateThemedRoundedRectBackground(
+      is_activated_ ? cros_tokens::kCrosSysSystemPrimaryContainer
+                    : (zero_state_ ? cros_tokens::kCrosSysSystemOnBase
+                                   : cros_tokens::kCrosSysSystemOnBase1),
+      kDeskButtonCornerRadius));
+}
+
 BEGIN_METADATA(DeskButton)
 END_METADATA
 
diff --git a/ash/wm/desks/desk_button/desk_button.h b/ash/wm/desks/desk_button/desk_button.h
index 12ce91c..57aa7729 100644
--- a/ash/wm/desks/desk_button/desk_button.h
+++ b/ash/wm/desks/desk_button/desk_button.h
@@ -43,7 +43,8 @@
   bool is_activated() const { return is_activated_; }
 
   bool zero_state() const { return zero_state_; }
-  void set_zero_state(bool zero_state) { zero_state_ = zero_state; }
+
+  void SetZeroState(bool zero_state);
 
   // views::Button:
   gfx::Size CalculatePreferredSize(
@@ -97,6 +98,8 @@
       std::optional<Shelf::ScopedDisableAutoHide>& disabler,
       bool should_enable_shelf_auto_hide);
 
+  void UpdateBackground();
+
   // A view that displays the profile avatar of the current desk.
   raw_ptr<views::ImageView> desk_avatar_view_;
 
diff --git a/ash/wm/desks/desk_button/desk_button_container.cc b/ash/wm/desks/desk_button/desk_button_container.cc
index 6301e017..c842702a 100644
--- a/ash/wm/desks/desk_button/desk_button_container.cc
+++ b/ash/wm/desks/desk_button/desk_button_container.cc
@@ -244,7 +244,7 @@
                             : views::CreateThemedRoundedRectBackground(
                                   cros_tokens::kCrosSysSystemOnBase,
                                   kDeskButtonContainerCornerRadius));
-  desk_button_->set_zero_state(zero_state_);
+  desk_button_->SetZeroState(zero_state_);
   desk_button_->UpdateUi(active_desk);
   prev_desk_button_->UpdateUi(active_desk);
   next_desk_button_->UpdateUi(active_desk);
diff --git a/ash/wm/desks/desks_unittests.cc b/ash/wm/desks/desks_unittests.cc
index 106c5d2..9bd6fb1 100644
--- a/ash/wm/desks/desks_unittests.cc
+++ b/ash/wm/desks/desks_unittests.cc
@@ -11252,7 +11252,7 @@
 }
 
 // Tests that switching the shelf alignment correctly repositions the desk
-// button.
+// button and updates their colors.
 TEST_P(DeskButtonTest, UpdateShelfAlignmentDuringTest) {
   // Desk button will be forced to be zero state for display that is narrower
   // than 1280.
@@ -11265,11 +11265,33 @@
   const bool bottom_at_start = GetParam().alignment == ShelfAlignment::kBottom;
   auto* desk_button = GetDeskButton();
   ASSERT_TRUE(desk_button);
+  // Verify desk names and color changes.
   ASSERT_EQ(bottom_at_start ? u"school" : u"s",
             desk_button->desk_name_label()->GetText());
+  auto* color_provider = desk_button->GetColorProvider();
+  ASSERT_EQ(color_provider->GetColor(bottom_at_start
+                                         ? cros_tokens::kCrosSysSystemOnBase1
+                                         : cros_tokens::kCrosSysSystemOnBase),
+            desk_button->GetBackground()->get_color());
 
+  // Activate/Deactivate the desk button and verify color changes.
+  ClickDeskButton();
+  ASSERT_EQ(
+      color_provider->GetColor(cros_tokens::kCrosSysSystemPrimaryContainer),
+      desk_button->GetBackground()->get_color());
+  ClickDeskButton();
+  ASSERT_EQ(color_provider->GetColor(bottom_at_start
+                                         ? cros_tokens::kCrosSysSystemOnBase1
+                                         : cros_tokens::kCrosSysSystemOnBase),
+            desk_button->GetBackground()->get_color());
+
+  // Update shelf alignment and verify desk names and color changes.
   GetPrimaryShelf()->SetAlignment(bottom_at_start ? ShelfAlignment::kLeft
                                                   : ShelfAlignment::kBottom);
+  ASSERT_EQ(color_provider->GetColor(bottom_at_start
+                                         ? cros_tokens::kCrosSysSystemOnBase
+                                         : cros_tokens::kCrosSysSystemOnBase1),
+            desk_button->GetBackground()->get_color());
   EXPECT_EQ(bottom_at_start ? u"s" : u"school",
             desk_button->desk_name_label()->GetText());
 }
diff --git a/base/allocator/partition_allocator/src/partition_alloc/BUILD.gn b/base/allocator/partition_allocator/src/partition_alloc/BUILD.gn
index 45bc6ecf04..48ff3ed 100644
--- a/base/allocator/partition_allocator/src/partition_alloc/BUILD.gn
+++ b/base/allocator/partition_allocator/src/partition_alloc/BUILD.gn
@@ -78,6 +78,15 @@
   }
 }
 
+# This will generate warnings when using Clang if code generates exit-time
+# destructors, which will slow down closing the program.
+# TODO(thakis): Make this a blocklist instead, http://crbug.com/101600
+config("wexit_time_destructors") {
+  if (is_clang) {
+    cflags = [ "-Wexit-time-destructors" ]
+  }
+}
+
 _remove_configs = []
 _add_configs = []
 if (!is_debug || partition_alloc_optimized_debug) {
@@ -112,7 +121,7 @@
   ]
   sources = [ "pointers/instance_tracer.cc" ]
   public_configs = [ ":public_includes" ]
-  configs += [ "//build/config/compiler:wexit_time_destructors" ]
+  configs += [ ":wexit_time_destructors" ]
 
   if (use_raw_ptr_backup_ref_impl) {
     sources += [
@@ -552,7 +561,7 @@
     configs += [
       ":partition_alloc_implementation",
       ":memory_tagging",
-      "//build/config/compiler:wexit_time_destructors",
+      ":wexit_time_destructors",
     ]
     deps = [ ":allocator_base" ]
     public_configs = []
@@ -777,7 +786,7 @@
     public_configs = [ ":public_includes" ]
     configs += [
       ":partition_alloc_base_implementation",
-      "//build/config/compiler:wexit_time_destructors",
+      ":wexit_time_destructors",
     ]
 
     deps = []
@@ -807,7 +816,7 @@
     public_configs = [ ":public_includes" ]
     configs += [
       ":allocator_shim_implementation",
-      "//build/config/compiler:wexit_time_destructors",
+      ":wexit_time_destructors",
     ]
     frameworks = []
 
diff --git a/base/android/java/src/org/chromium/base/cached_flags/CachedFlagUtils.java b/base/android/java/src/org/chromium/base/cached_flags/CachedFlagUtils.java
index 7ee79a5..453dbd4c 100644
--- a/base/android/java/src/org/chromium/base/cached_flags/CachedFlagUtils.java
+++ b/base/android/java/src/org/chromium/base/cached_flags/CachedFlagUtils.java
@@ -15,25 +15,30 @@
  */
 public class CachedFlagUtils {
     /** Caches flags that must take effect on startup but are set via native code. */
-    public static void cacheNativeFlags(List<CachedFlag> featuresToCache) {
+    public static void cacheNativeFlags(List<CachedFlag>... listsOfFeaturesToCache) {
         // Batch the updates into a single apply() call to avoid calling the expensive
         // SharedPreferencesImpl$EditorImpl.commitToMemory() method many times unnecessarily.
         final SharedPreferences.Editor editor =
                 CachedFlagsSharedPreferences.getInstance().getEditor();
-        for (CachedFlag feature : featuresToCache) {
-            feature.writeCacheValueToEditor(editor);
+        for (final List<CachedFlag> featuresToCache : listsOfFeaturesToCache) {
+            for (CachedFlag feature : featuresToCache) {
+                feature.writeCacheValueToEditor(editor);
+            }
         }
         editor.apply();
     }
 
     /** Caches flags that must take effect on startup but are set via native code. */
-    public static void cacheFieldTrialParameters(List<CachedFieldTrialParameter> parameters) {
+    public static void cacheFieldTrialParameters(
+            List<CachedFieldTrialParameter>... listsOfParameters) {
         // Batch the updates into a single apply() call to avoid calling the expensive
         // SharedPreferencesImpl$EditorImpl.commitToMemory() method many times unnecessarily.
         final SharedPreferences.Editor editor =
                 CachedFlagsSharedPreferences.getInstance().getEditor();
-        for (final CachedFieldTrialParameter parameter : parameters) {
-            parameter.writeCacheValueToEditor(editor);
+        for (final List<CachedFieldTrialParameter> parameters : listsOfParameters) {
+            for (final CachedFieldTrialParameter parameter : parameters) {
+                parameter.writeCacheValueToEditor(editor);
+            }
         }
         editor.apply();
     }
diff --git a/base/memory/discardable_shared_memory.cc b/base/memory/discardable_shared_memory.cc
index 8558c61..f5d96a5 100644
--- a/base/memory/discardable_shared_memory.cc
+++ b/base/memory/discardable_shared_memory.cc
@@ -443,36 +443,6 @@
   return true;
 }
 
-void DiscardableSharedMemory::ReleaseMemoryIfPossible(size_t offset,
-                                                      size_t length) {
-#if BUILDFLAG(IS_POSIX) && !BUILDFLAG(IS_NACL)
-// Linux and Android provide MADV_REMOVE which is preferred as it has a
-// behavior that can be verified in tests. Other POSIX flavors (MacOSX, BSDs),
-// provide MADV_FREE which has the same result but memory is purged lazily.
-#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_ANDROID)
-#define MADV_PURGE_ARGUMENT MADV_REMOVE
-#elif BUILDFLAG(IS_APPLE)
-// MADV_FREE_REUSABLE is similar to MADV_FREE, but also marks the pages with the
-// reusable bit, which allows both Activity Monitor and memory-infra to
-// correctly track the pages.
-#define MADV_PURGE_ARGUMENT MADV_FREE_REUSABLE
-#else  // BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_ANDROID)
-#define MADV_PURGE_ARGUMENT MADV_FREE
-#endif  // BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) ||
-        // BUILDFLAG(IS_ANDROID)
-  // Advise the kernel to remove resources associated with purged pages.
-  // Subsequent accesses of memory pages will succeed, but might result in
-  // zero-fill-on-demand pages.
-  base::span<uint8_t> span = memory().subspan(offset, length);
-  if (madvise(span.data(), span.size(), MADV_PURGE_ARGUMENT)) {
-    DPLOG(ERROR) << "madvise() failed";
-  }
-#else   // BUILDFLAG(IS_POSIX) && !BUILDFLAG(IS_NACL)
-  base::span<uint8_t> span = memory().subspan(offset, length);
-  partition_alloc::DiscardSystemPages(span.data(), span.size());
-#endif  // BUILDFLAG(IS_POSIX) && !BUILDFLAG(IS_NACL)
-}
-
 bool DiscardableSharedMemory::IsMemoryResident() const {
   DCHECK(shared_memory_mapping_.IsValid());
 
diff --git a/base/memory/discardable_shared_memory.h b/base/memory/discardable_shared_memory.h
index cbe20596..8006deb 100644
--- a/base/memory/discardable_shared_memory.h
+++ b/base/memory/discardable_shared_memory.h
@@ -131,13 +131,6 @@
   // different process. Returns NULL time if purged.
   Time last_known_usage() const { return last_known_usage_; }
 
-  // Releases any allocated pages in the specified range, if supported by the
-  // platform. Address space in the specified range continues to be reserved.
-  // The memory is not guaranteed to be released immediately.
-  // |offset| and |length| are both in bytes. |offset| and |length| must both be
-  // page aligned.
-  void ReleaseMemoryIfPossible(size_t offset, size_t length);
-
   // This returns true and sets |last_known_usage_| to 0 if
   // DiscardableSharedMemory object was successfully purged. Purging can fail
   // for two reasons; object might be locked or our last known usage timestamp
diff --git a/base/message_loop/message_pump_win.cc b/base/message_loop/message_pump_win.cc
index 5a48d5b..a51f1f6 100644
--- a/base/message_loop/message_pump_win.cc
+++ b/base/message_loop/message_pump_win.cc
@@ -14,6 +14,8 @@
 #include "base/auto_reset.h"
 #include "base/check.h"
 #include "base/debug/alias.h"
+#include "base/debug/dump_without_crashing.h"
+#include "base/debug/stack_trace.h"
 #include "base/feature_list.h"
 #include "base/functional/bind.h"
 #include "base/memory/raw_ptr.h"
@@ -387,6 +389,26 @@
 void MessagePumpForUI::HandleWorkMessage() {
   DCHECK_CALLED_ON_VALID_THREAD(bound_thread_);
 
+  if (!g_ui_pump_improvements_win && in_dispatch_message_ &&
+      !in_nested_native_loop_with_application_tasks_) {
+    // An undeclared nested native loop is spinning in DispatchMessage(), which
+    // will pump application tasks (task execution is not disabled since
+    // DoWork() is not on the stack). Since UIPumpImprovementsWin will break
+    // this use case (application tasks will no longer run because kMsgHaveWork
+    // is not pumped), investigation is being done into stacks where this occurs
+    // and whether they need to be remediated.
+    //
+    // A `StackTrace` is used to deduplicate stacks to ensure that all instances
+    // of undeclared nested runloops are detected during a session.
+    //
+    // TODO(crbug.com/335672561): Remove this once data has been analyzed.
+    uintptr_t id = 0;
+    for (const void* address : debug::StackTrace().addresses()) {
+      id ^= reinterpret_cast<uintptr_t>(address);
+    }
+    debug::DumpWithoutCrashingWithUniqueId(id);
+  }
+
   // If we are being called outside of the context of Run, then don't try to do
   // any work.  This could correspond to a MessageBox call or something of that
   // sort.
@@ -611,7 +633,10 @@
   for (Observer& observer : observers_)
     observer.WillDispatchMSG(msg);
   ::TranslateMessage(&msg);
-  ::DispatchMessage(&msg);
+  {
+    AutoReset<bool> reset(&in_dispatch_message_, true);
+    ::DispatchMessage(&msg);
+  }
   for (Observer& observer : observers_)
     observer.DidDispatchMSG(msg);
 
diff --git a/base/message_loop/message_pump_win.h b/base/message_loop/message_pump_win.h
index b5d1faf..bfe4bea 100644
--- a/base/message_loop/message_pump_win.h
+++ b/base/message_loop/message_pump_win.h
@@ -197,6 +197,13 @@
   //   - HandleNestedNativeLoopWithApplicationTasks(false) is called.
   bool in_nested_native_loop_with_application_tasks_ = false;
 
+  // This is set upon entering DispatchMessage() in ProcessNextWindowsMessage(),
+  // and unset upon exit. It is used to track nested runloops which pump during
+  // DispatchMessage(), a use case currently broken by UIPumpImprovementsWin
+  // (since, when they are undeclared, do not pump kMsgHaveWork, but do without
+  // the feature enabled).
+  bool in_dispatch_message_ = false;
+
   enum class WakeupState {
     kApplicationTask,
     kNative,
diff --git a/base/task/sequence_manager/thread_controller.cc b/base/task/sequence_manager/thread_controller.cc
index 5bf8ce7..dd2cc55 100644
--- a/base/task/sequence_manager/thread_controller.cc
+++ b/base/task/sequence_manager/thread_controller.cc
@@ -331,8 +331,8 @@
 void ThreadController::RunLevelTracker::RunLevel::LogPercentageMetric(
     const char* name,
     int percentage) {
-  UmaHistogramPercentage(
-      base::StrCat({name, base::StrCat({".", GetThreadName()})}), percentage);
+  UmaHistogramPercentage(base::StrCat({name, ".", GetThreadName()}),
+                         percentage);
 }
 
 void ThreadController::RunLevelTracker::RunLevel::LogPercentageMetric(
diff --git a/base/task/sequence_manager/thread_controller.h b/base/task/sequence_manager/thread_controller.h
index 6ee818e2..47bb7b9 100644
--- a/base/task/sequence_manager/thread_controller.h
+++ b/base/task/sequence_manager/thread_controller.h
@@ -279,7 +279,7 @@
 
     void EnableTimeKeeperMetrics(
         const char* thread_name,
-        bool wall_time_based_metrics_for_testing_enabled);
+        bool wall_time_based_metrics_enabled_for_testing);
 
     // Observes changes of state sent as trace-events so they can be tested.
     class TraceObserverForTesting {
diff --git a/base/test/android/javatests/src/org/chromium/base/test/transit/LogicalElement.java b/base/test/android/javatests/src/org/chromium/base/test/transit/LogicalElement.java
index b71b48d..652cb07 100644
--- a/base/test/android/javatests/src/org/chromium/base/test/transit/LogicalElement.java
+++ b/base/test/android/javatests/src/org/chromium/base/test/transit/LogicalElement.java
@@ -14,7 +14,7 @@
  *
  * <p>LogicalElements should be declared by calling {@link
  * Elements.Builder#declareLogicalElement(LogicalElement)} passing in an instance created by one of
- * the factory methods here such as {@link #sharedUiThreadLogicalElement(String, Callable)}.
+ * the factory methods here such as {@link #uiThreadLogicalElement(String, Callable)}.
  *
  * <p>Generates ENTER and EXIT Conditions for the ConditionalState to ensure the LogicalElement is
  * in the right state.
@@ -29,9 +29,27 @@
     @Nullable private Condition mExitCondition;
 
     /**
+     * Alias for {@link #unscopedUiThreadLogicalElement(String, Callable)} as the default way to
+     * declare LogicalElements.
+     */
+    public static LogicalElement uiThreadLogicalElement(
+            String description, Callable<Boolean> checkFunction) {
+        return unscopedUiThreadLogicalElement(description, checkFunction);
+    }
+
+    /**
+     * Alias for {@link #unscopedInstrumentationThreadLogicalElement(String, Callable)} as the
+     * default way to declare LogicalElements.
+     */
+    public static LogicalElement instrumentationThreadLogicalElement(
+            String description, Callable<Boolean> checkFunction) {
+        return unscopedInstrumentationThreadLogicalElement(description, checkFunction);
+    }
+
+    /**
      * Create a shared-scope LogicalElement that runs the check on the UI Thread.
      *
-     * <p>Unscoped LogicalElements wait for the function to be true as an ENTER Condition. It also
+     * <p>Shared LogicalElements wait for the function to be true as an ENTER Condition. It also
      * waits for the function to be false as an EXIT Condition when transitioning to a
      * ConditionalState that does not declare the LogicalElement too.
      */
diff --git a/chrome/android/chrome_java_resources.gni b/chrome/android/chrome_java_resources.gni
index 34d1a73..62a85aa 100644
--- a/chrome/android/chrome_java_resources.gni
+++ b/chrome/android/chrome_java_resources.gni
@@ -403,14 +403,12 @@
   "java/res/drawable/price_tracking_enabled_outline.xml",
   "java/res/drawable/qr_code.xml",
   "java/res/drawable/reading_list_empty_state_illustration.xml",
-  "java/res/drawable/safety_check.xml",
   "java/res/drawable/search_activity_bg.xml",
   "java/res/drawable/send_tab.xml",
   "java/res/drawable/shared_clipboard_zero_state_dark.xml",
   "java/res/drawable/shared_clipboard_zero_state_light.xml",
   "java/res/drawable/sharing_more.xml",
   "java/res/drawable/sharing_print.xml",
-  "java/res/drawable/shield.xml",
   "java/res/drawable/shopping_accessory_view_background.xml",
   "java/res/drawable/signin_header_animation.xml",
   "java/res/drawable/tab_close_button_bg.xml",
diff --git a/chrome/android/expectations/monochrome_64_32_public_bundle.arm64.libs_and_assets.expected b/chrome/android/expectations/monochrome_64_32_public_bundle.arm64.libs_and_assets.expected
index 7a785f5..2599828 100644
--- a/chrome/android/expectations/monochrome_64_32_public_bundle.arm64.libs_and_assets.expected
+++ b/chrome/android/expectations/monochrome_64_32_public_bundle.arm64.libs_and_assets.expected
@@ -170,5 +170,7 @@
 apk_path=assets/stored-locales/zh-HK.pak, compress=False, alignment=4
 apk_path=assets/stored-locales/zh-TW.pak, compress=False, alignment=4
 apk_path=assets/stored-locales/zu.pak, compress=False, alignment=4
+apk_path=assets/v8_context_snapshot_32.bin, compress=False, alignment=4
+apk_path=assets/v8_context_snapshot_64.bin, compress=False, alignment=4
 apk_path=assets/webapk8.dex, compress=True, alignment=0
 apk_path=assets/webapk_dex_version.txt, compress=True, alignment=0
diff --git a/chrome/android/expectations/monochrome_64_32_public_bundle.proguard_flags.expected b/chrome/android/expectations/monochrome_64_32_public_bundle.proguard_flags.expected
index 50be5a8..fc0ed83 100644
--- a/chrome/android/expectations/monochrome_64_32_public_bundle.proguard_flags.expected
+++ b/chrome/android/expectations/monochrome_64_32_public_bundle.proguard_flags.expected
@@ -618,7 +618,7 @@
     public boolean isLayoutSuppressed();
 }
 
-# File: obj/third_party/androidx/androidx_savedstate_savedstate_java/proguard.txt
+# File: obj/third_party/androidx/androidx_savedstate_savedstate_android_java/proguard.txt
 # Copyright (C) 2019 The Android Open Source Project
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
diff --git a/chrome/android/expectations/trichrome_library_32_64_apk.arm64.libs_and_assets.expected b/chrome/android/expectations/trichrome_library_32_64_apk.arm64.libs_and_assets.expected
index 45c1cbd9..23967eb 100644
--- a/chrome/android/expectations/trichrome_library_32_64_apk.arm64.libs_and_assets.expected
+++ b/chrome/android/expectations/trichrome_library_32_64_apk.arm64.libs_and_assets.expected
@@ -3,3 +3,4 @@
 apk_path=lib/armeabi-v7a/libmonochrome.so, compress=False, alignment=4096
 apk_path=assets/icudtl.dat, compress=False, alignment=4
 apk_path=assets/snapshot_blob_32.bin, compress=False, alignment=4
+apk_path=assets/v8_context_snapshot_32.bin, compress=False, alignment=4
diff --git a/chrome/android/expectations/trichrome_library_32_apk.arm64.libs_and_assets.expected b/chrome/android/expectations/trichrome_library_32_apk.arm64.libs_and_assets.expected
index 45c1cbd9..23967eb 100644
--- a/chrome/android/expectations/trichrome_library_32_apk.arm64.libs_and_assets.expected
+++ b/chrome/android/expectations/trichrome_library_32_apk.arm64.libs_and_assets.expected
@@ -3,3 +3,4 @@
 apk_path=lib/armeabi-v7a/libmonochrome.so, compress=False, alignment=4096
 apk_path=assets/icudtl.dat, compress=False, alignment=4
 apk_path=assets/snapshot_blob_32.bin, compress=False, alignment=4
+apk_path=assets/v8_context_snapshot_32.bin, compress=False, alignment=4
diff --git a/chrome/android/expectations/trichrome_library_64_32_apk.arm64.libs_and_assets.expected b/chrome/android/expectations/trichrome_library_64_32_apk.arm64.libs_and_assets.expected
index 0df38f2..bc2ba1a 100644
--- a/chrome/android/expectations/trichrome_library_64_32_apk.arm64.libs_and_assets.expected
+++ b/chrome/android/expectations/trichrome_library_64_32_apk.arm64.libs_and_assets.expected
@@ -3,3 +3,4 @@
 apk_path=lib/arm64-v8a/libmonochrome_64.so, compress=False, alignment=16384
 apk_path=assets/icudtl.dat, compress=False, alignment=4
 apk_path=assets/snapshot_blob_64.bin, compress=False, alignment=4
+apk_path=assets/v8_context_snapshot_64.bin, compress=False, alignment=4
diff --git a/chrome/android/expectations/trichrome_library_64_apk.arm64.libs_and_assets.expected b/chrome/android/expectations/trichrome_library_64_apk.arm64.libs_and_assets.expected
index 0df38f2..bc2ba1a 100644
--- a/chrome/android/expectations/trichrome_library_64_apk.arm64.libs_and_assets.expected
+++ b/chrome/android/expectations/trichrome_library_64_apk.arm64.libs_and_assets.expected
@@ -3,3 +3,4 @@
 apk_path=lib/arm64-v8a/libmonochrome_64.so, compress=False, alignment=16384
 apk_path=assets/icudtl.dat, compress=False, alignment=4
 apk_path=assets/snapshot_blob_64.bin, compress=False, alignment=4
+apk_path=assets/v8_context_snapshot_64.bin, compress=False, alignment=4
diff --git a/chrome/android/java/res/drawable/safety_check.xml b/chrome/android/java/res/drawable/safety_check.xml
deleted file mode 100644
index 3683a5f..0000000
--- a/chrome/android/java/res/drawable/safety_check.xml
+++ /dev/null
@@ -1,14 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-Copyright 2021 The Chromium Authors
-Use of this source code is governed by a BSD-style license that can be
-found in the LICENSE file.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
-        android:width="20dp"
-        android:height="20dp"
-        android:viewportWidth="20.0"
-        android:viewportHeight="20.0">
-    <path android:fillColor="@macro/default_icon_color"
-          android:pathData="M10 1l8 3.273v4.909c0 4.54-3.413 8.787-8 9.818-4.587-1.03-8-5.277-8-9.818v-4.91L10 1zm3.57 5.384l-5.126 5.391-2.014-2.11-1.097 1.153 3.111 3.273 6.223-6.546-1.097-1.161z"/>
-</vector>
diff --git a/chrome/android/java/res/drawable/shield.xml b/chrome/android/java/res/drawable/shield.xml
deleted file mode 100644
index 135625c7..0000000
--- a/chrome/android/java/res/drawable/shield.xml
+++ /dev/null
@@ -1,14 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-Copyright 2021 The Chromium Authors
-Use of this source code is governed by a BSD-style license that can be
-found in the LICENSE file.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
-        android:width="24dp"
-        android:height="24dp"
-        android:viewportWidth="24"
-        android:viewportHeight="24">
-<path android:fillColor="@color/baseline_neutral_variant_30"
-          android:pathData="M12.0 22.0Q8.525 21.125 6.2625 18.0125Q4.0 14.9 4.0 11.1V5.0L12.0 2.0L20.0 5.0V11.1Q20.0 14.9 17.7375 18.0125Q15.475 21.125 12.0 22.0ZM12.0 12.0Q12.0 12.0 12.0 12.0Q12.0 12.0 12.0 12.0Q12.0 12.0 12.0 12.0Q12.0 12.0 12.0 12.0Z"/>
-</vector>
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java
index b05233d..2fa34a1 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java
@@ -434,6 +434,8 @@
     // is supported. This can be explicitly set in the incoming Intent or internally assigned.
     private int mWindowId;
 
+    private @InstanceAllocationType int mInstanceAllocationType;
+
     // The URL of the last active Tab read from the Tab metadata file during cold startup.
     private String mLastActiveTabUrl;
 
@@ -1331,6 +1333,11 @@
         resetSavedInstanceState();
         BookmarkUtils.maybeExpireLastBookmarkLocationForReadLater(
                 mInactivityTracker.getTimeSinceLastBackgroundedMs());
+
+        MultiWindowUtils.maybeRecordDesktopWindowActivityCountHistogram(
+                mRootUiCoordinator.getDesktopWindowStateProvider(),
+                mInstanceAllocationType,
+                !mFromResumption);
     }
 
     @Override
@@ -2714,6 +2721,7 @@
     @Override
     protected boolean isStartedUpCorrectly(Intent intent) {
         mWindowId = 0;
+        mInstanceAllocationType = InstanceAllocationType.DEFAULT;
         Bundle savedInstanceState = getSavedInstanceState();
         int windowId = getExtraWindowIdFromIntent(intent);
         if (savedInstanceState != null && savedInstanceState.containsKey(WINDOW_INDEX)) {
@@ -2729,6 +2737,7 @@
                     mMultiInstanceManager.allocInstanceId(
                             windowId, ApplicationStatus.getTaskId(this), preferNew);
             mWindowId = instanceIdInfo.first;
+            mInstanceAllocationType = instanceIdInfo.second;
             logIntentInfo(intent);
             // If a new instance ID was allocated for the newly created activity, potentially
             // dispatch it to an existing activity under special circumstances. See
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/app/flags/ChromeCachedFlags.java b/chrome/android/java/src/org/chromium/chrome/browser/app/flags/ChromeCachedFlags.java
index cbc4884..51a6990 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/app/flags/ChromeCachedFlags.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/app/flags/ChromeCachedFlags.java
@@ -45,7 +45,6 @@
 import org.chromium.components.omnibox.OmniboxFeatures;
 
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.List;
 
 /** Caches the flags that Chrome might require before native is loaded in a later next run. */
@@ -80,96 +79,97 @@
         if (mIsFinishedCachingNativeFlags) return;
         FirstRunUtils.cacheFirstRunPrefs();
 
-        CachedFlagUtils.cacheNativeFlags(ChromeFeatureList.sFlagsCachedFullBrowser);
+        CachedFlagUtils.cacheNativeFlags(
+                ChromeFeatureList.sFlagsCachedFullBrowser, OmniboxFeatures.getFieldTrialsToCache());
         cacheAdditionalNativeFlags();
 
         List<CachedFieldTrialParameter> fieldTrialsToCache =
-                new ArrayList<>(
-                        Arrays.asList(
-                                BackPressManager.TAB_HISTORY_RECOVER,
-                                ChimeFeatures.ALWAYS_REGISTER,
-                                CustomTabIntentDataProvider.AUTO_TRANSLATE_ALLOW_ALL_FIRST_PARTIES,
-                                CustomTabIntentDataProvider.AUTO_TRANSLATE_PACKAGE_NAME_ALLOWLIST,
-                                CustomTabIntentDataProvider.THIRD_PARTIES_DEFAULT_POLICY,
-                                CustomTabIntentDataProvider.DENYLIST_ENTRIES,
-                                CustomTabIntentDataProvider.ALLOWLIST_ENTRIES,
-                                CustomTabIntentDataProvider.OMNIBOX_ALLOWED_PACKAGE_NAMES,
-                                DseNewTabUrlManager.EEA_COUNTRY_ONLY,
-                                DseNewTabUrlManager.SKIP_EEA_COUNTRY_CHECK,
-                                DseNewTabUrlManager.SWAP_OUT_NTP,
-                                GoogleBottomBarCoordinator.GOOGLE_BOTTOM_BAR_PARAM_BUTTON_LIST,
-                                HubFieldTrial.FLOATING_ACTION_BUTTON,
-                                HubFieldTrial.PANE_SWITCHER_USES_TEXT,
-                                HubFieldTrial.SUPPORTS_OTHER_TABS,
-                                HubFieldTrial.SUPPORTS_SEARCH,
-                                HubFieldTrial.SUPPORTS_BOOKMARKS,
-                                JankTrackerExperiment.JANK_TRACKER_DELAYED_START_MS,
-                                MinimizedFeatureUtils.ICON_VARIANT,
-                                MinimizedFeatureUtils.MANUFACTURER_EXCLUDE_LIST,
-                                MultiWindowUtils
-                                        .BACK_TO_BACK_CTA_CREATION_TIMESTAMP_DIFF_THRESHOLD_MS,
-                                OptimizationGuidePushNotificationManager.MAX_CACHE_SIZE,
-                                ShoppingPersistedTabDataService
-                                        .SKIP_SHOPPING_PERSISTED_TAB_DATA_DELAYED_INITIALIZATION,
-                                StartSurfaceConfiguration.IS_DOODLE_SUPPORTED,
-                                StartSurfaceConfiguration.START_SURFACE_RETURN_TIME_SECONDS,
-                                StartSurfaceConfiguration
-                                        .START_SURFACE_RETURN_TIME_ON_TABLET_SECONDS,
-                                StartSurfaceConfiguration.START_SURFACE_RETURN_TIME_USE_MODEL,
-                                StartSurfaceConfiguration.SIGNIN_PROMO_NTP_COUNT_LIMIT,
-                                StartSurfaceConfiguration
-                                        .SIGNIN_PROMO_NTP_SINCE_FIRST_TIME_SHOWN_LIMIT_HOURS,
-                                StartSurfaceConfiguration.SIGNIN_PROMO_NTP_RESET_AFTER_HOURS,
-                                StartSurfaceConfiguration
-                                        .START_SURFACE_HIDE_INCOGNITO_SWITCH_NO_TAB,
-                                StartSurfaceConfiguration.START_SURFACE_OPEN_NTP_INSTEAD_OF_START,
-                                StartSurfaceConfiguration.START_SURFACE_OPEN_START_AS_HOMEPAGE,
-                                StartSurfaceConfiguration.SURFACE_POLISH_OMNIBOX_COLOR,
-                                StartSurfaceConfiguration.SURFACE_POLISH_MOVE_DOWN_LOGO,
-                                StartSurfaceConfiguration.SURFACE_POLISH_LESS_BRAND_SPACE,
-                                StartSurfaceConfiguration.SURFACE_POLISH_SCROLLABLE_MVT,
-                                StartSurfaceConfiguration.LOGO_POLISH_LARGE_SIZE,
-                                StartSurfaceConfiguration.LOGO_POLISH_MEDIUM_SIZE,
-                                TabManagementFieldTrial.DELAY_TEMP_STRIP_TIMEOUT_MS,
-                                HomeModulesMetricsUtils.HOME_MODULES_SHOW_ALL_MODULES,
-                                HomeModulesMetricsUtils.HOME_MODULES_COMBINE_TABS,
-                                TabResumptionModuleUtils.TAB_RESUMPTION_MAX_TILES_NUMBER,
-                                TabResumptionModuleUtils.TAB_RESUMPTION_USE_SALIENT_IMAGE,
-                                TabResumptionModuleUtils.TAB_RESUMPTION_V2,
-                                TabStateFileManager.MIGRATE_STALE_TABS_CACHED_PARAM,
-                                TabUiFeatureUtilities.ANIMATION_START_TIMEOUT_MS,
-                                TabUiFeatureUtilities.ZOOMING_MIN_MEMORY,
-                                TabUiFeatureUtilities.SKIP_SLOW_ZOOMING,
-                                TabUiFeatureUtilities.DISABLE_STRIP_TO_CONTENT_DD,
-                                TabUiFeatureUtilities.DISABLE_STRIP_TO_STRIP_DD,
-                                TabUiFeatureUtilities.DISABLE_STRIP_TO_STRIP_DIFF_MODEL_DD,
-                                TabUiFeatureUtilities.DISABLE_DRAG_TO_NEW_INSTANCE_DD,
-                                TabUiFeatureUtilities
-                                        .ENABLE_NON_SPLIT_MODE_TAB_DRAG_MANUFACTURER_ALLOWLIST,
-                                ToolbarFeatures.DTC_TRANSITION_THRESHOLD_DP,
-                                ToolbarFeatures.USE_TOOLBAR_BG_COLOR_FOR_STRIP_TRANSITION_SCRIM,
-                                VersionNumberGetter.MIN_SDK_VERSION));
+                List.of(
+                        BackPressManager.TAB_HISTORY_RECOVER,
+                        ChimeFeatures.ALWAYS_REGISTER,
+                        CustomTabIntentDataProvider.AUTO_TRANSLATE_ALLOW_ALL_FIRST_PARTIES,
+                        CustomTabIntentDataProvider.AUTO_TRANSLATE_PACKAGE_NAME_ALLOWLIST,
+                        CustomTabIntentDataProvider.THIRD_PARTIES_DEFAULT_POLICY,
+                        CustomTabIntentDataProvider.DENYLIST_ENTRIES,
+                        CustomTabIntentDataProvider.ALLOWLIST_ENTRIES,
+                        CustomTabIntentDataProvider.OMNIBOX_ALLOWED_PACKAGE_NAMES,
+                        DseNewTabUrlManager.EEA_COUNTRY_ONLY,
+                        DseNewTabUrlManager.SKIP_EEA_COUNTRY_CHECK,
+                        DseNewTabUrlManager.SWAP_OUT_NTP,
+                        GoogleBottomBarCoordinator.GOOGLE_BOTTOM_BAR_PARAM_BUTTON_LIST,
+                        HubFieldTrial.FLOATING_ACTION_BUTTON,
+                        HubFieldTrial.PANE_SWITCHER_USES_TEXT,
+                        HubFieldTrial.SUPPORTS_OTHER_TABS,
+                        HubFieldTrial.SUPPORTS_SEARCH,
+                        HubFieldTrial.SUPPORTS_BOOKMARKS,
+                        JankTrackerExperiment.JANK_TRACKER_DELAYED_START_MS,
+                        MinimizedFeatureUtils.ICON_VARIANT,
+                        MinimizedFeatureUtils.MANUFACTURER_EXCLUDE_LIST,
+                        MultiWindowUtils.BACK_TO_BACK_CTA_CREATION_TIMESTAMP_DIFF_THRESHOLD_MS,
+                        OptimizationGuidePushNotificationManager.MAX_CACHE_SIZE,
+                        ShoppingPersistedTabDataService
+                                .SKIP_SHOPPING_PERSISTED_TAB_DATA_DELAYED_INITIALIZATION,
+                        StartSurfaceConfiguration.IS_DOODLE_SUPPORTED,
+                        StartSurfaceConfiguration.START_SURFACE_RETURN_TIME_SECONDS,
+                        StartSurfaceConfiguration.START_SURFACE_RETURN_TIME_ON_TABLET_SECONDS,
+                        StartSurfaceConfiguration.START_SURFACE_RETURN_TIME_USE_MODEL,
+                        StartSurfaceConfiguration.SIGNIN_PROMO_NTP_COUNT_LIMIT,
+                        StartSurfaceConfiguration
+                                .SIGNIN_PROMO_NTP_SINCE_FIRST_TIME_SHOWN_LIMIT_HOURS,
+                        StartSurfaceConfiguration.SIGNIN_PROMO_NTP_RESET_AFTER_HOURS,
+                        StartSurfaceConfiguration.START_SURFACE_HIDE_INCOGNITO_SWITCH_NO_TAB,
+                        StartSurfaceConfiguration.START_SURFACE_OPEN_NTP_INSTEAD_OF_START,
+                        StartSurfaceConfiguration.START_SURFACE_OPEN_START_AS_HOMEPAGE,
+                        StartSurfaceConfiguration.SURFACE_POLISH_OMNIBOX_COLOR,
+                        StartSurfaceConfiguration.SURFACE_POLISH_MOVE_DOWN_LOGO,
+                        StartSurfaceConfiguration.SURFACE_POLISH_LESS_BRAND_SPACE,
+                        StartSurfaceConfiguration.SURFACE_POLISH_SCROLLABLE_MVT,
+                        StartSurfaceConfiguration.LOGO_POLISH_LARGE_SIZE,
+                        StartSurfaceConfiguration.LOGO_POLISH_MEDIUM_SIZE,
+                        TabManagementFieldTrial.DELAY_TEMP_STRIP_TIMEOUT_MS,
+                        HomeModulesMetricsUtils.HOME_MODULES_SHOW_ALL_MODULES,
+                        HomeModulesMetricsUtils.HOME_MODULES_COMBINE_TABS,
+                        TabResumptionModuleUtils.TAB_RESUMPTION_MAX_TILES_NUMBER,
+                        TabResumptionModuleUtils.TAB_RESUMPTION_USE_SALIENT_IMAGE,
+                        TabResumptionModuleUtils.TAB_RESUMPTION_V2,
+                        TabStateFileManager.MIGRATE_STALE_TABS_CACHED_PARAM,
+                        TabUiFeatureUtilities.ANIMATION_START_TIMEOUT_MS,
+                        TabUiFeatureUtilities.ZOOMING_MIN_MEMORY,
+                        TabUiFeatureUtilities.SKIP_SLOW_ZOOMING,
+                        TabUiFeatureUtilities.DISABLE_STRIP_TO_CONTENT_DD,
+                        TabUiFeatureUtilities.DISABLE_STRIP_TO_STRIP_DD,
+                        TabUiFeatureUtilities.DISABLE_STRIP_TO_STRIP_DIFF_MODEL_DD,
+                        TabUiFeatureUtilities.DISABLE_DRAG_TO_NEW_INSTANCE_DD,
+                        TabUiFeatureUtilities.ENABLE_NON_SPLIT_MODE_TAB_DRAG_MANUFACTURER_ALLOWLIST,
+                        ToolbarFeatures.DTC_TRANSITION_THRESHOLD_DP,
+                        ToolbarFeatures.USE_TOOLBAR_BG_COLOR_FOR_STRIP_TRANSITION_SCRIM,
+                        VersionNumberGetter.MIN_SDK_VERSION);
 
-        fieldTrialsToCache.addAll(OmniboxFeatures.getFieldTrialParamsToCache());
+        tryToCatchMissingParameters(
+                fieldTrialsToCache, OmniboxFeatures.getFieldTrialParamsToCache());
+        CachedFlagUtils.cacheFieldTrialParameters(
+                fieldTrialsToCache, OmniboxFeatures.getFieldTrialParamsToCache());
 
-        tryToCatchMissingParameters(fieldTrialsToCache);
-        CachedFlagUtils.cacheFieldTrialParameters(fieldTrialsToCache);
-
-        OmniboxFeatures.cacheFeatureFlags();
         CachedFlagsSafeMode.getInstance().onEndCheckpoint();
         mIsFinishedCachingNativeFlags = true;
     }
 
-    private void tryToCatchMissingParameters(List<CachedFieldTrialParameter> listed) {
+    private void tryToCatchMissingParameters(
+            List<CachedFieldTrialParameter>... listsOfParamsToTest) {
         if (!BuildConfig.ENABLE_ASSERTS) return;
 
+        var paramsToTest = new ArrayList<CachedFieldTrialParameter>();
+        for (List<CachedFieldTrialParameter> list : listsOfParamsToTest) {
+            paramsToTest.addAll(list);
+        }
+
         // All instances of CachedFieldTrialParameter should be manually passed to
         // CachedFeatureFlags.cacheFieldTrialParameters(). The following checking is a best-effort
         // attempt to try to catch accidental omissions. It cannot replace the list because some
         // instances might not be instantiated if the classes they belong to are not accessed yet.
         List<String> omissions = new ArrayList<>();
         for (CachedFieldTrialParameter trial : CachedFieldTrialParameter.getAllInstances()) {
-            if (listed.contains(trial)) continue;
+            if (paramsToTest.contains(trial)) continue;
             if (MINIMAL_BROWSER_FIELD_TRIALS.contains(trial)) continue;
             omissions.add(trial.getFeatureName() + ":" + trial.getParameterName());
         }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadController.java b/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadController.java
index b0a0d199..55a9145 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadController.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadController.java
@@ -9,6 +9,7 @@
 import androidx.annotation.Nullable;
 
 import org.jni_zero.CalledByNative;
+import org.jni_zero.JniType;
 import org.jni_zero.NativeMethods;
 
 import org.chromium.chrome.browser.pdf.PdfPage;
@@ -27,6 +28,17 @@
 /** Java counterpart of android DownloadController. Owned by native. */
 public class DownloadController {
     /**
+     * Called to download the given URL triggered from a tab.
+     *
+     * @param url Url to download.
+     * @param tab Tab triggering the download.
+     */
+    public static void downloadUrl(String url, Tab tab) {
+        assert hasFileAccess(tab.getWindowAndroid());
+        DownloadControllerJni.get().downloadUrl(url, tab.getProfile());
+    }
+
+    /**
      * Notifies the download delegate that a download completed and passes along info about the
      * download. This can be either a POST download or a GET download with authentication.
      */
@@ -71,14 +83,17 @@
         if (windowAndroid == null) {
             DownloadControllerJni.get()
                     .onAcquirePermissionResult(
-                            callbackId, /* granted= */ false, /* permissionToUpdate= */ null);
+                            callbackId, /* granted= */ false, /* permissionToUpdate= */ "");
             return;
         }
         FileAccessPermissionHelper.requestFileAccessPermissionHelper(
                 windowAndroid,
                 result -> {
                     DownloadControllerJni.get()
-                            .onAcquirePermissionResult(callbackId, result.first, result.second);
+                            .onAcquirePermissionResult(
+                                    callbackId,
+                                    result.first,
+                                    result.second == null ? "" : result.second);
                 });
     }
 
@@ -148,8 +163,14 @@
 
     @NativeMethods
     interface Natives {
-        void onAcquirePermissionResult(long callbackId, boolean granted, String permissionToUpdate);
+        void onAcquirePermissionResult(
+                long callbackId,
+                boolean granted,
+                @JniType("std::string") String permissionToUpdate);
 
-        void cancelDownload(Profile profile, String downloadGuid);
+        void downloadUrl(@JniType("std::string") String url, @JniType("Profile*") Profile profile);
+
+        void cancelDownload(
+                @JniType("Profile*") Profile profile, @JniType("std::string") String downloadGuid);
     }
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadUtils.java b/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadUtils.java
index 6f1b5b9..446a0b14 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadUtils.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadUtils.java
@@ -256,9 +256,15 @@
 
     /**
      * Trigger the download of an Offline Page.
+     *
      * @param context Context to pull resources from.
+     * @param tab Tab triggering the download.
      */
     public static void downloadOfflinePage(Context context, Tab tab) {
+        if (tab.isNativePage() && tab.getNativePage().isPdf()) {
+            DownloadController.downloadUrl(tab.getUrl().getSpec(), tab);
+            return;
+        }
         OfflinePageOrigin origin = new OfflinePageOrigin(context, tab);
 
         if (tab.isShowingErrorPage()) {
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/multiwindow/MultiInstanceManagerApi31.java b/chrome/android/java/src/org/chromium/chrome/browser/multiwindow/MultiInstanceManagerApi31.java
index 3b54c05..3a70628e 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/multiwindow/MultiInstanceManagerApi31.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/multiwindow/MultiInstanceManagerApi31.java
@@ -600,13 +600,19 @@
     }
 
     private void recordActivityCountHistogram() {
+        RecordHistogram.recordExactLinearHistogram(
+                "Android.MultiInstance.NumActivities",
+                getRunningTabbedActivityCount(),
+                mMaxInstances + 1);
+    }
+
+    static int getRunningTabbedActivityCount() {
         int numActivities = 0;
         List<Activity> activities = getAllRunningActivities();
         for (Activity activity : activities) {
             if (activity instanceof ChromeTabbedActivity) numActivities++;
         }
-        RecordHistogram.recordExactLinearHistogram(
-                "Android.MultiInstance.NumActivities", numActivities, mMaxInstances + 1);
+        return numActivities;
     }
 
     private void recordInstanceCountHistogram() {
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/multiwindow/MultiWindowUtils.java b/chrome/android/java/src/org/chromium/chrome/browser/multiwindow/MultiWindowUtils.java
index c4399ee..a87504f0 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/multiwindow/MultiWindowUtils.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/multiwindow/MultiWindowUtils.java
@@ -47,6 +47,8 @@
 import org.chromium.chrome.browser.tab.Tab;
 import org.chromium.chrome.browser.tabmodel.TabModelSelector;
 import org.chromium.chrome.browser.tabmodel.TabWindowManager;
+import org.chromium.chrome.browser.ui.desktop_windowing.AppHeaderUtils;
+import org.chromium.chrome.browser.ui.desktop_windowing.DesktopWindowStateProvider;
 import org.chromium.chrome.browser.util.AndroidTaskUtils;
 import org.chromium.components.ukm.UkmRecorder;
 import org.chromium.ui.display.DisplayAndroidManager;
@@ -72,6 +74,13 @@
                             "activity_creation_timestamp_diff_threshold_ms",
                             1000);
 
+    static final String HISTOGRAM_NUM_ACTIVITIES_DESKTOP_WINDOW =
+            "Android.MultiInstance.NumActivities.DesktopWindow";
+    static final String HISTOGRAM_NUM_ACTIVITIES_DESKTOP_WINDOW_NEW_INSTANCE_SUFFIX =
+            ".NewInstance";
+    static final String HISTOGRAM_NUM_ACTIVITIES_DESKTOP_WINDOW_EXISTING_INSTANCE_SUFFIX =
+            ".ExistingInstance";
+
     private static MultiWindowUtils sInstance = new MultiWindowUtils();
 
     private static Integer sMaxInstancesForTesting;
@@ -838,6 +847,42 @@
         return windowId;
     }
 
+    /**
+     * Record the number of running ChromeTabbedActivity's when a new ChromeTabbedActivity is
+     * created in a desktop window.
+     *
+     * @param instanceAllocationType The {@link InstanceAllocationType} for the new activity.
+     * @param isColdStart Whether app startup is a cold start.
+     */
+    public static void maybeRecordDesktopWindowActivityCountHistogram(
+            @Nullable DesktopWindowStateProvider desktopWindowStateProvider,
+            @InstanceAllocationType int instanceAllocationType,
+            boolean isColdStart) {
+        // Emit the histogram only for an activity that starts in a desktop window.
+        if (!AppHeaderUtils.isAppInDesktopWindow(desktopWindowStateProvider)) return;
+
+        // Emit the histogram only for a newly created activity that is cold-started.
+        if (!isColdStart) return;
+
+        // Emit generic histogram, irrespective of instance allocation type.
+        RecordHistogram.recordExactLinearHistogram(
+                HISTOGRAM_NUM_ACTIVITIES_DESKTOP_WINDOW,
+                MultiInstanceManagerApi31.getRunningTabbedActivityCount(),
+                getMaxInstances() + 1);
+
+        // Emit histogram variant based on instance allocation type.
+        String histogramSuffix = HISTOGRAM_NUM_ACTIVITIES_DESKTOP_WINDOW_NEW_INSTANCE_SUFFIX;
+        if (instanceAllocationType != InstanceAllocationType.NEW_INSTANCE_NEW_TASK
+                && instanceAllocationType != InstanceAllocationType.PREFER_NEW_INSTANCE_NEW_TASK) {
+            histogramSuffix = HISTOGRAM_NUM_ACTIVITIES_DESKTOP_WINDOW_EXISTING_INSTANCE_SUFFIX;
+        }
+
+        RecordHistogram.recordExactLinearHistogram(
+                HISTOGRAM_NUM_ACTIVITIES_DESKTOP_WINDOW + histogramSuffix,
+                MultiInstanceManagerApi31.getRunningTabbedActivityCount(),
+                getMaxInstances() + 1);
+    }
+
     public static void setInstanceForTesting(MultiWindowUtils instance) {
         var oldValue = sInstance;
         sInstance = instance;
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/segmentation_platform/ContextualPageActionControllerUnitTest.java b/chrome/android/java/src/org/chromium/chrome/browser/segmentation_platform/ContextualPageActionControllerUnitTest.java
index 1d69933..c524f6f 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/segmentation_platform/ContextualPageActionControllerUnitTest.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/segmentation_platform/ContextualPageActionControllerUnitTest.java
@@ -45,10 +45,7 @@
 /** Unit tests for {@link ContextualPageActionController} */
 @RunWith(BaseRobolectricTestRunner.class)
 @Config(manifest = Config.NONE)
-@EnableFeatures({
-    ChromeFeatureList.CONTEXTUAL_PAGE_ACTIONS,
-    ChromeFeatureList.CONTEXTUAL_PAGE_ACTION_PRICE_TRACKING,
-})
+@EnableFeatures({ChromeFeatureList.CONTEXTUAL_PAGE_ACTIONS})
 public class ContextualPageActionControllerUnitTest {
     private ObservableSupplierImpl<Profile> mProfileSupplier;
     private ObservableSupplierImpl<Tab> mTabSupplier;
@@ -151,8 +148,6 @@
         testValues.addFeatureFlagOverride(ChromeFeatureList.CONTEXTUAL_PAGE_ACTIONS, true);
         testValues.addFieldTrialParamOverride(
                 ChromeFeatureList.CONTEXTUAL_PAGE_ACTIONS, "enable_ui", "false");
-        testValues.addFeatureFlagOverride(
-                ChromeFeatureList.CONTEXTUAL_PAGE_ACTION_PRICE_TRACKING, true);
         FeatureList.setTestValues(testValues);
 
         createContextualPageActionController();
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/segmentation_platform/PriceTrackingActionProvider.java b/chrome/android/java/src/org/chromium/chrome/browser/segmentation_platform/PriceTrackingActionProvider.java
index be160dfb..abc5f26c 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/segmentation_platform/PriceTrackingActionProvider.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/segmentation_platform/PriceTrackingActionProvider.java
@@ -11,6 +11,7 @@
 import org.chromium.chrome.browser.tab.Tab;
 import org.chromium.components.bookmarks.BookmarkId;
 import org.chromium.components.commerce.core.ShoppingService;
+import org.chromium.components.embedder_support.util.UrlUtilities;
 
 /** Provides price tracking signal for showing contextual page action for a given tab. */
 public class PriceTrackingActionProvider implements ContextualPageActionController.ActionProvider {
@@ -30,6 +31,13 @@
 
     @Override
     public void getAction(Tab tab, SignalAccumulator signalAccumulator) {
+
+        if (tab == null || tab.getUrl() == null || !UrlUtilities.isHttpOrHttps(tab.getUrl())) {
+            signalAccumulator.setHasPriceTracking(false);
+            signalAccumulator.notifySignalAvailable();
+            return;
+        }
+
         final BookmarkModel bookmarkModel = mBookmarkModelSupplier.get();
         bookmarkModel.finishLoadingBookmarkModel(
                 () -> {
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/segmentation_platform/PriceTrackingActionProviderTest.java b/chrome/android/java/src/org/chromium/chrome/browser/segmentation_platform/PriceTrackingActionProviderTest.java
index bb3609b..1e5d468 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/segmentation_platform/PriceTrackingActionProviderTest.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/segmentation_platform/PriceTrackingActionProviderTest.java
@@ -8,6 +8,8 @@
 import static org.mockito.ArgumentMatchers.anyLong;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
 
 import android.os.Handler;
 
@@ -29,13 +31,13 @@
 import org.chromium.chrome.browser.commerce.PriceTrackingUtils;
 import org.chromium.chrome.browser.commerce.PriceTrackingUtilsJni;
 import org.chromium.chrome.browser.profiles.Profile;
-import org.chromium.chrome.browser.profiles.ProfileManager;
 import org.chromium.chrome.browser.segmentation_platform.ContextualPageActionController.ActionProvider;
 import org.chromium.chrome.browser.tab.Tab;
 import org.chromium.components.bookmarks.BookmarkId;
 import org.chromium.components.commerce.core.ShoppingService;
 import org.chromium.components.commerce.core.ShoppingService.ProductInfo;
 import org.chromium.components.commerce.core.ShoppingService.ProductInfoCallback;
+import org.chromium.url.JUnitTestGURLs;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -114,6 +116,7 @@
 
     @Test
     public void priceTrackingActionShownSuccessfully() {
+        doReturn(JUnitTestGURLs.EXAMPLE_URL).when(mMockTab).getUrl();
         List<ActionProvider> providers = new ArrayList<>();
         PriceTrackingActionProvider provider =
                 new PriceTrackingActionProvider(
@@ -127,6 +130,7 @@
 
     @Test
     public void priceTrackingNotShownForAlreadyPriceTrackedPages() {
+        doReturn(JUnitTestGURLs.EXAMPLE_URL).when(mMockTab).getUrl();
         List<ActionProvider> providers = new ArrayList<>();
         PriceTrackingActionProvider provider =
                 new PriceTrackingActionProvider(
@@ -135,7 +139,6 @@
         SignalAccumulator accumulator = new SignalAccumulator(new Handler(), mMockTab, providers);
         // URL supports price tracking.
         setIsUrlPriceTrackableResult(true);
-        ProfileManager.setLastUsedProfileForTesting(mProfile);
         // URL is already bookmarked.
         doReturn(new BookmarkId(1L, 0)).when(mBookmarkModel).getUserBookmarkIdForTab(mMockTab);
         // Bookmark has price tracking information.
@@ -146,6 +149,7 @@
 
     @Test
     public void priceTrackingNotShownForNonTrackablePages() {
+        doReturn(JUnitTestGURLs.GOOGLE_URL).when(mMockTab).getUrl();
         List<ActionProvider> providers = new ArrayList<>();
         PriceTrackingActionProvider provider =
                 new PriceTrackingActionProvider(
@@ -154,7 +158,6 @@
         SignalAccumulator accumulator = new SignalAccumulator(new Handler(), mMockTab, providers);
         // URL does not support price tracking.
         setIsUrlPriceTrackableResult(false);
-        ProfileManager.setLastUsedProfileForTesting(mProfile);
         // URL is bookmarked.
         doReturn(new BookmarkId(1L, 0)).when(mBookmarkModel).getUserBookmarkIdForTab(mMockTab);
         // Bookmark has no price tracking information.
@@ -162,4 +165,20 @@
         provider.getAction(mMockTab, accumulator);
         Assert.assertFalse(accumulator.hasPriceTracking());
     }
+
+    @Test
+    public void priceTrackingNotUsedForNonHttpUrls() {
+        // Use a non-http(s) url (about:blank).
+        doReturn(JUnitTestGURLs.ABOUT_BLANK).when(mMockTab).getUrl();
+        List<ActionProvider> providers = new ArrayList<>();
+        PriceTrackingActionProvider provider =
+                new PriceTrackingActionProvider(
+                        () -> mShoppingService, () -> mBookmarkModel, () -> mProfile);
+        providers.add(provider);
+        SignalAccumulator accumulator = new SignalAccumulator(new Handler(), mMockTab, providers);
+        provider.getAction(mMockTab, accumulator);
+        Assert.assertFalse(accumulator.hasPriceTracking());
+        // Bookmark model shouldn't be loaded/queried.
+        verify(mBookmarkModel, never()).finishLoadingBookmarkModel(any());
+    }
 }
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/app/bookmarks/BookmarkTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/app/bookmarks/BookmarkTest.java
index 70d8d7f..da4ff7a 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/app/bookmarks/BookmarkTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/app/bookmarks/BookmarkTest.java
@@ -94,7 +94,6 @@
 import org.chromium.chrome.browser.bookmarks.TestingDelegate;
 import org.chromium.chrome.browser.commerce.ShoppingFeatures;
 import org.chromium.chrome.browser.commerce.ShoppingServiceFactory;
-import org.chromium.chrome.browser.flags.ChromeFeatureList;
 import org.chromium.chrome.browser.flags.ChromeSwitches;
 import org.chromium.chrome.browser.night_mode.ChromeNightModeTestUtils;
 import org.chromium.chrome.browser.preferences.ChromePreferenceKeys;
@@ -142,11 +141,7 @@
 /** Tests for the bookmark manager. */
 @RunWith(ChromeJUnit4ClassRunner.class)
 @CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE})
-// TODO(crbug.com/40252540): Disabling the shopping CPA should not be a requirement for these tests.
-@DisableFeatures({
-    ChromeFeatureList.CONTEXTUAL_PAGE_ACTION_PRICE_TRACKING,
-    SyncFeatureMap.ENABLE_BOOKMARK_FOLDERS_FOR_ACCOUNT_STORAGE
-})
+@DisableFeatures({SyncFeatureMap.ENABLE_BOOKMARK_FOLDERS_FOR_ACCOUNT_STORAGE})
 // TODO(crbug.com/40899175): Investigate batching.
 @DoNotBatch(reason = "BookmarkTest has behaviours and thus can't be batched.")
 public class BookmarkTest {
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/OmniboxTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/OmniboxTest.java
index 7cadcb8a..8e53a88 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/OmniboxTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/OmniboxTest.java
@@ -78,7 +78,7 @@
     }
 
     private static final OnSuggestionsReceivedListener sEmptySuggestionListener =
-            (result, autocompleteText, isFinal) -> {};
+            (result, isFinal) -> {};
 
     /**
      * Sanity check of Omnibox. The problem in http://b/5021723 would cause this to fail (hang or
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/suggestions/OmniboxActionsTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/suggestions/OmniboxActionsTest.java
index 3f8fac90..f753227 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/suggestions/OmniboxActionsTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/suggestions/OmniboxActionsTest.java
@@ -117,10 +117,9 @@
         mOmniboxUtils.requestFocus();
         // Ensure we start from empty suggestions list; don't carry over suggestions from previous
         // run.
-        mOmniboxUtils.setSuggestions(AutocompleteResult.fromCache(null, null), "");
+        mOmniboxUtils.setSuggestions(AutocompleteResult.fromCache(null, null));
 
-        mOmniboxUtils.setSuggestions(
-                AutocompleteResult.fromCache(Arrays.asList(matches), null), "");
+        mOmniboxUtils.setSuggestions(AutocompleteResult.fromCache(Arrays.asList(matches), null));
         mOmniboxUtils.checkSuggestionsShown();
         SuggestionInfo<BaseSuggestionView> info = mOmniboxUtils.findSuggestionWithActionChips();
         Assert.assertNotNull("No suggestions with actions", info);
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/suggestions/OmniboxPedalsRenderTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/suggestions/OmniboxPedalsRenderTest.java
index 954b3a67..e7545bf 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/suggestions/OmniboxPedalsRenderTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/suggestions/OmniboxPedalsRenderTest.java
@@ -121,8 +121,7 @@
         List<AutocompleteMatch> suggestionsList = new ArrayList<>();
         suggestionsList.add(
                 createDummyPedalSuggestion("pedal", OmniboxPedalId.RUN_CHROME_SAFETY_CHECK));
-        mOmniboxUtils.setSuggestions(
-                AutocompleteResult.fromCache(suggestionsList, null), "Run safety check");
+        mOmniboxUtils.setSuggestions(AutocompleteResult.fromCache(suggestionsList, null));
         mOmniboxUtils.checkSuggestionsShown();
 
         SuggestionInfo<BaseSuggestionView> info = mOmniboxUtils.findSuggestionWithActionChips();
@@ -136,8 +135,7 @@
         List<AutocompleteMatch> suggestionsList = new ArrayList<>();
         suggestionsList.add(
                 createDummyPedalSuggestion("pedal", OmniboxPedalId.PLAY_CHROME_DINO_GAME));
-        mOmniboxUtils.setSuggestions(
-                AutocompleteResult.fromCache(suggestionsList, null), "Dino game");
+        mOmniboxUtils.setSuggestions(AutocompleteResult.fromCache(suggestionsList, null));
         mOmniboxUtils.checkSuggestionsShown();
 
         SuggestionInfo<BaseSuggestionView> info = mOmniboxUtils.findSuggestionWithActionChips();
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/suggestions/OmniboxPedalsTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/suggestions/OmniboxPedalsTest.java
index 0e4f833a..9b7776f2d 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/suggestions/OmniboxPedalsTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/suggestions/OmniboxPedalsTest.java
@@ -146,10 +146,9 @@
         mOmniboxUtils.requestFocus();
         // Ensure we start from empty suggestions list; don't carry over suggestions from previous
         // run.
-        mOmniboxUtils.setSuggestions(AutocompleteResult.fromCache(null, null), "");
+        mOmniboxUtils.setSuggestions(AutocompleteResult.fromCache(null, null));
 
-        mOmniboxUtils.setSuggestions(
-                AutocompleteResult.fromCache(Arrays.asList(matches), null), "");
+        mOmniboxUtils.setSuggestions(AutocompleteResult.fromCache(Arrays.asList(matches), null));
         mOmniboxUtils.checkSuggestionsShown();
         SuggestionInfo<BaseSuggestionView> info = mOmniboxUtils.findSuggestionWithActionChips();
         Assert.assertNotNull("No suggestions with actions", info);
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/suggestions/mostvisited/MostVisitedTilesTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/suggestions/mostvisited/MostVisitedTilesTest.java
index a7ee589..de3ef6f 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/suggestions/mostvisited/MostVisitedTilesTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/suggestions/mostvisited/MostVisitedTilesTest.java
@@ -195,7 +195,7 @@
         mOmnibox.requestFocus();
         TestThreadUtils.runOnUiThreadBlocking(
                 () -> {
-                    mListener.getValue().onSuggestionsReceived(autocompleteResult, mStartUrl, true);
+                    mListener.getValue().onSuggestionsReceived(autocompleteResult, true);
                 });
         mOmnibox.checkSuggestionsShown();
     }
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/suggestions/querytiles/QueryTilesTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/suggestions/querytiles/QueryTilesTest.java
index f81717b..20a5761 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/suggestions/querytiles/QueryTilesTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/suggestions/querytiles/QueryTilesTest.java
@@ -120,7 +120,7 @@
         mOmnibox.requestFocus();
         verify(mController).startZeroSuggest(anyString(), any(), anyInt(), anyString());
         TestThreadUtils.runOnUiThreadBlocking(
-                () -> mListener.onSuggestionsReceived(acResult, "", true));
+                () -> mListener.onSuggestionsReceived(acResult, true));
         mOmnibox.checkSuggestionsShown();
     }
 
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/searchwidget/SearchActivityTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/searchwidget/SearchActivityTest.java
index ea78f9d..384c03d 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/searchwidget/SearchActivityTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/searchwidget/SearchActivityTest.java
@@ -256,7 +256,7 @@
         TestThreadUtils.runOnUiThreadBlocking(
                 () ->
                         mOnSuggestionsReceivedListener.onSuggestionsReceived(
-                                buildDummyAutocompleteResult(), "inline text", true));
+                                buildDummyAutocompleteResult(), true));
         mOmnibox.checkSuggestionsShown();
     }
 
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/multiwindow/MultiInstanceManagerApi31UnitTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/multiwindow/MultiInstanceManagerApi31UnitTest.java
index dade5e9..153728d 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/multiwindow/MultiInstanceManagerApi31UnitTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/multiwindow/MultiInstanceManagerApi31UnitTest.java
@@ -11,6 +11,7 @@
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
@@ -899,6 +900,26 @@
                 runningTabbedActivityIds.valueAt(0));
     }
 
+    @Test
+    @SmallTest
+    @Config(sdk = 31)
+    public void testGetRunningTabbedActivityCount() {
+        // Create 1 activity that is not a ChromeTabbedActivity and 2 ChromeTabbedActivity's.
+        assertEquals(0, allocInstanceIndex(PASSED_ID_INVALID, mActivityTask56));
+        assertEquals(1, allocInstanceIndex(PASSED_ID_INVALID, mTabbedActivityTask62));
+        assertEquals(2, allocInstanceIndex(PASSED_ID_INVALID, mTabbedActivityTask63));
+
+        // Remove ChromeTabbedActivity |mTabbedActivityTask62|, this will be considered a
+        // non-running activity subsequently.
+        removeTaskOnRecentsScreen(mTabbedActivityTask62);
+
+        int runningTabbedActivityCount = MultiInstanceManagerApi31.getRunningTabbedActivityCount();
+        assertEquals(
+                "There should be only 1 running ChromeTabbedActivity.",
+                1,
+                runningTabbedActivityCount);
+    }
+
     private void triggerSelectTab(TabModelObserver tabModelObserver, Tab tab) {
         // Set up the mocks to have |TabModelUtils.getCurrentTab(selector.getModel(false))|
         // return the last active normal tab.
@@ -1044,7 +1065,7 @@
         // Allocate and create two instances.
         assertEquals(0, allocInstanceIndex(PASSED_ID_INVALID, mTabbedActivityTask62, true));
         assertEquals(1, allocInstanceIndex(PASSED_ID_INVALID, mTabbedActivityTask63, true));
-        Mockito.doNothing()
+        doNothing()
                 .when(mMultiInstanceManager)
                 .moveAndReparentTabToNewWindow(
                         eq(mTab1), eq(INVALID_INSTANCE_ID), eq(true), eq(false), eq(true));
@@ -1068,7 +1089,7 @@
                 Mockito.spy(
                         createChromeInstance(
                                 INSTANCE_ID_1, TASK_ID_62, List.of(mTab1, mTab2, mTab3)));
-        Mockito.doNothing()
+        doNothing()
                 .when(multiInstanceManager1)
                 .moveAndReparentTabToNewWindow(
                         eq(mTab2), eq(INVALID_INSTANCE_ID), eq(true), eq(false), eq(true));
@@ -1101,7 +1122,7 @@
                 mMultiInstanceManager.mMaxInstances,
                 mMultiInstanceManager.getInstanceInfo().size());
 
-        Mockito.doNothing()
+        doNothing()
                 .when(mMultiInstanceManager)
                 .openNewWindow(eq("Android.WindowManager.NewWindow"));
 
@@ -1128,9 +1149,7 @@
         assertEquals(INSTANCE_ID_2, allocInstanceIndex(INSTANCE_ID_2, mTabbedActivityTask63, true));
         assertEquals(2, mMultiInstanceManager.getInstanceInfo().size());
 
-        Mockito.doNothing()
-                .when(mMultiInstanceManager)
-                .moveTabAction(any(), eq(mTab1), eq(tabAtIndex));
+        doNothing().when(mMultiInstanceManager).moveTabAction(any(), eq(mTab1), eq(tabAtIndex));
 
         // Action
         mMultiInstanceManager.moveTabToWindow(mTabbedActivityTask63, mTab1, tabAtIndex);
@@ -1151,9 +1170,7 @@
                 Mockito.spy(
                         createChromeInstance(
                                 INSTANCE_ID_1, TASK_ID_62, List.of(mTab1, mTab2, mTab3)));
-        Mockito.doNothing()
-                .when(multiInstanceManager)
-                .moveTabAction(any(), eq(mTab2), eq(tabAtIndex));
+        doNothing().when(multiInstanceManager).moveTabAction(any(), eq(mTab2), eq(tabAtIndex));
 
         // Action
         multiInstanceManager.moveTabToWindow(mTabbedActivityTask62, mTab2, tabAtIndex);
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/multiwindow/MultiWindowUtilsUnitTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/multiwindow/MultiWindowUtilsUnitTest.java
index 37ac766d..3d21b88 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/multiwindow/MultiWindowUtilsUnitTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/multiwindow/MultiWindowUtilsUnitTest.java
@@ -10,6 +10,10 @@
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
+import static org.chromium.chrome.browser.multiwindow.MultiWindowUtils.HISTOGRAM_NUM_ACTIVITIES_DESKTOP_WINDOW;
+import static org.chromium.chrome.browser.multiwindow.MultiWindowUtils.HISTOGRAM_NUM_ACTIVITIES_DESKTOP_WINDOW_EXISTING_INSTANCE_SUFFIX;
+import static org.chromium.chrome.browser.multiwindow.MultiWindowUtils.HISTOGRAM_NUM_ACTIVITIES_DESKTOP_WINDOW_NEW_INSTANCE_SUFFIX;
+
 import android.app.Activity;
 import android.content.Context;
 import android.os.Build.VERSION_CODES;
@@ -27,10 +31,14 @@
 import org.robolectric.annotation.Implements;
 
 import org.chromium.base.test.BaseRobolectricTestRunner;
+import org.chromium.base.test.util.HistogramWatcher;
 import org.chromium.chrome.browser.homepage.HomepageManager;
+import org.chromium.chrome.browser.multiwindow.MultiWindowUtils.InstanceAllocationType;
 import org.chromium.chrome.browser.multiwindow.MultiWindowUtilsUnitTest.ShadowMultiInstanceManagerApi31;
 import org.chromium.chrome.browser.tabmodel.TabModel;
 import org.chromium.chrome.browser.tabmodel.TabModelSelector;
+import org.chromium.chrome.browser.ui.desktop_windowing.AppHeaderState;
+import org.chromium.chrome.browser.ui.desktop_windowing.DesktopWindowStateProvider;
 import org.chromium.chrome.test.AutomotiveContextWrapperTestRule;
 import org.chromium.components.embedder_support.util.UrlConstants;
 import org.chromium.url.GURL;
@@ -45,6 +53,7 @@
     @Implements(MultiInstanceManagerApi31.class)
     public static class ShadowMultiInstanceManagerApi31 {
         private static SparseIntArray sWindowIdsOfRunningTabbedActivities;
+        private static int sRunningTabbedActivityCount;
 
         public static void updateWindowIdsOfRunningTabbedActivities(int windowId, boolean remove) {
             if (sWindowIdsOfRunningTabbedActivities == null) {
@@ -57,14 +66,24 @@
             }
         }
 
+        public static void updateRunningTabbedActivityCount(int count) {
+            sRunningTabbedActivityCount = count;
+        }
+
         public static void reset() {
             sWindowIdsOfRunningTabbedActivities = null;
+            sRunningTabbedActivityCount = 0;
         }
 
         @Implementation
         public static SparseIntArray getWindowIdsOfRunningTabbedActivities() {
             return sWindowIdsOfRunningTabbedActivities;
         }
+
+        @Implementation
+        public static int getRunningTabbedActivityCount() {
+            return sRunningTabbedActivityCount;
+        }
     }
 
     @Rule
@@ -427,6 +446,140 @@
                 instanceId);
     }
 
+    @Test
+    @Config(sdk = 31)
+    public void testRecordDesktopWindowNumActivities_OnlyOnColdStart() {
+        int runningActivityCount = 2;
+        var watcher =
+                HistogramWatcher.newSingleRecordWatcher(
+                        HISTOGRAM_NUM_ACTIVITIES_DESKTOP_WINDOW, runningActivityCount);
+        var desktopWindowStateProvider = mock(DesktopWindowStateProvider.class);
+        var appHeaderState = mock(AppHeaderState.class);
+        when(desktopWindowStateProvider.getAppHeaderState()).thenReturn(appHeaderState);
+        when(appHeaderState.isInDesktopWindow()).thenReturn(true);
+        ShadowMultiInstanceManagerApi31.updateRunningTabbedActivityCount(runningActivityCount);
+
+        // Assume that the histogram is attempted to be recorded on a cold start of the app.
+        MultiWindowUtils.maybeRecordDesktopWindowActivityCountHistogram(
+                desktopWindowStateProvider,
+                InstanceAllocationType.NEW_INSTANCE_NEW_TASK,
+                /* isColdStart= */ true);
+
+        // Assume that the histogram is attempted to be recorded on a subsequent warm start.
+        MultiWindowUtils.maybeRecordDesktopWindowActivityCountHistogram(
+                desktopWindowStateProvider,
+                InstanceAllocationType.NEW_INSTANCE_NEW_TASK,
+                /* isColdStart= */ false);
+
+        // Histogram should be emitted only once.
+        watcher.assertExpected();
+    }
+
+    @Test
+    @Config(sdk = 31)
+    public void testRecordDesktopWindowNumActivities_ColdStartOfExistingInstance() {
+        int runningActivityCount = 2;
+        var desktopWindowStateProvider = mock(DesktopWindowStateProvider.class);
+        var appHeaderState = mock(AppHeaderState.class);
+        when(desktopWindowStateProvider.getAppHeaderState()).thenReturn(appHeaderState);
+        when(appHeaderState.isInDesktopWindow()).thenReturn(true);
+        ShadowMultiInstanceManagerApi31.updateRunningTabbedActivityCount(runningActivityCount);
+
+        int[] instanceAllocationTypes =
+                new int[] {
+                    InstanceAllocationType.DEFAULT,
+                    InstanceAllocationType.EXISTING_INSTANCE_MAPPED_TASK,
+                    InstanceAllocationType.EXISTING_INSTANCE_UNMAPPED_TASK,
+                    InstanceAllocationType.EXISTING_INSTANCE_NEW_TASK
+                };
+
+        // Assume that the histogram is attempted to be recorded on a cold start of an existing
+        // instance, for different instance allocation types.
+        for (int type : instanceAllocationTypes) {
+            var watcher =
+                    HistogramWatcher.newBuilder()
+                            .expectIntRecord(
+                                    HISTOGRAM_NUM_ACTIVITIES_DESKTOP_WINDOW, runningActivityCount)
+                            .expectIntRecord(
+                                    HISTOGRAM_NUM_ACTIVITIES_DESKTOP_WINDOW
+                                            + HISTOGRAM_NUM_ACTIVITIES_DESKTOP_WINDOW_EXISTING_INSTANCE_SUFFIX,
+                                    runningActivityCount)
+                            .expectNoRecords(
+                                    HISTOGRAM_NUM_ACTIVITIES_DESKTOP_WINDOW
+                                            + HISTOGRAM_NUM_ACTIVITIES_DESKTOP_WINDOW_NEW_INSTANCE_SUFFIX)
+                            .build();
+            MultiWindowUtils.maybeRecordDesktopWindowActivityCountHistogram(
+                    desktopWindowStateProvider, type, /* isColdStart= */ true);
+            watcher.assertExpected();
+        }
+    }
+
+    @Test
+    @Config(sdk = 31)
+    public void testRecordDesktopWindowNumActivities_ColdStartOfNewInstance() {
+        int runningActivityCount = 2;
+        var desktopWindowStateProvider = mock(DesktopWindowStateProvider.class);
+        var appHeaderState = mock(AppHeaderState.class);
+        when(desktopWindowStateProvider.getAppHeaderState()).thenReturn(appHeaderState);
+        when(appHeaderState.isInDesktopWindow()).thenReturn(true);
+        ShadowMultiInstanceManagerApi31.updateRunningTabbedActivityCount(runningActivityCount);
+
+        int[] instanceAllocationTypes =
+                new int[] {
+                    InstanceAllocationType.NEW_INSTANCE_NEW_TASK,
+                    InstanceAllocationType.PREFER_NEW_INSTANCE_NEW_TASK
+                };
+
+        // Assume that the histogram is attempted to be recorded on a cold start of a new instance,
+        // for different instance allocation types.
+        for (int type : instanceAllocationTypes) {
+            var watcher =
+                    HistogramWatcher.newBuilder()
+                            .expectIntRecord(
+                                    HISTOGRAM_NUM_ACTIVITIES_DESKTOP_WINDOW, runningActivityCount)
+                            .expectIntRecord(
+                                    HISTOGRAM_NUM_ACTIVITIES_DESKTOP_WINDOW
+                                            + HISTOGRAM_NUM_ACTIVITIES_DESKTOP_WINDOW_NEW_INSTANCE_SUFFIX,
+                                    runningActivityCount)
+                            .expectNoRecords(
+                                    HISTOGRAM_NUM_ACTIVITIES_DESKTOP_WINDOW
+                                            + HISTOGRAM_NUM_ACTIVITIES_DESKTOP_WINDOW_EXISTING_INSTANCE_SUFFIX)
+                            .build();
+            MultiWindowUtils.maybeRecordDesktopWindowActivityCountHistogram(
+                    desktopWindowStateProvider, type, /* isColdStart= */ true);
+            watcher.assertExpected();
+        }
+    }
+
+    @Test
+    @Config(sdk = 31)
+    public void testRecordDesktopWindowNumActivities_NotInDesktopWindow() {
+        var watcher =
+                HistogramWatcher.newBuilder()
+                        .expectNoRecords(HISTOGRAM_NUM_ACTIVITIES_DESKTOP_WINDOW)
+                        .expectNoRecords(
+                                HISTOGRAM_NUM_ACTIVITIES_DESKTOP_WINDOW
+                                        + HISTOGRAM_NUM_ACTIVITIES_DESKTOP_WINDOW_EXISTING_INSTANCE_SUFFIX)
+                        .expectNoRecords(
+                                HISTOGRAM_NUM_ACTIVITIES_DESKTOP_WINDOW
+                                        + HISTOGRAM_NUM_ACTIVITIES_DESKTOP_WINDOW_NEW_INSTANCE_SUFFIX)
+                        .build();
+        var desktopWindowStateProvider = mock(DesktopWindowStateProvider.class);
+        var appHeaderState = mock(AppHeaderState.class);
+        when(desktopWindowStateProvider.getAppHeaderState()).thenReturn(appHeaderState);
+        when(appHeaderState.isInDesktopWindow()).thenReturn(false);
+
+        // Assume that the histogram is attempted to be recorded on a cold start of the app, not in
+        // a desktop window.
+        MultiWindowUtils.maybeRecordDesktopWindowActivityCountHistogram(
+                desktopWindowStateProvider,
+                InstanceAllocationType.NEW_INSTANCE_NEW_TASK,
+                /* isColdStart= */ true);
+
+        // Histogram should not be emitted.
+        watcher.assertExpected();
+    }
+
     private void writeInstanceInfo(
             int instanceId, String url, int tabCount, int incognitoTabCount, int taskId) {
         MultiInstanceManagerApi31.writeUrl(instanceId, url);
diff --git a/chrome/app/generated_resources.grd b/chrome/app/generated_resources.grd
index 2646d72..a4c7760 100644
--- a/chrome/app/generated_resources.grd
+++ b/chrome/app/generated_resources.grd
@@ -7704,9 +7704,6 @@
       </if>
 
       <!-- NTP -->
-      <message name="IDS_GOOGLE_SEARCH_BOX_EMPTY_HINT" desc="The text displayed in the fakebox (on the New Tab page) when it is empty, and Google is the default search engine.">
-        Search Google or type URL
-      </message>
       <message name="IDS_GOOGLE_SEARCH_BOX_EMPTY_HINT_MD" desc="The text displayed in the fakebox (on the New Tab page) when it is empty, Google is the default search engine, and the Material Design UI is enabled.">
         Search Google or type a URL
       </message>
@@ -8387,6 +8384,9 @@
       <message name="IDS_NTP_MODULES_TODAY_CALENDAR_HEADER" desc="Header of the Calendar module when showing today's calendar on the NTP." meaning="Header of the calendar module to indicate that the events shown are for today." translateable="false">
         Today's Calendar
       </message>
+      <message name="IDS_NTP_MODULES_TODAY_CALENDAR_DISABLE_BUTTON_TEXT" desc="Text shown on the disable button of a NTP Calendar card." translateable="false">
+        Don't show Today's Calendar
+      </message>
       <message name="IDS_NTP_MODULES_GOOGLE_CALENDAR_TITLE" desc="Title of the Google Calendar module shown in various UIs for Customize Chrome and the NTP." meaning="Title of feature for showing a glimpse of a user's Google Calendar.">
         Google Calendar
       </message>
@@ -16427,18 +16427,12 @@
       Enhanced Safe Browsing is on
     </message>
     <message name="IDS_TAILORED_SECURITY_CONSENTED_ENABLE_MESSAGE_DESCRIPTION" desc="Description shown in the Android message when the account tailored security setting changes for a consented primary account">
-      You&#x2019;re on Chrome&#x2019;s strongest security
-    </message>
-    <message name="IDS_TAILORED_SECURITY_CONSENTED_ENABLE_MESSAGE_DESCRIPTION_UPDATED" desc="Description shown in the Android message when the account tailored security setting changes for a consented primary account">
       You have Chrome&#x2019;s strongest security against harmful websites
     </message>
     <message name="IDS_TAILORED_SECURITY_CONSENTED_DISABLE_MESSAGE_TITLE" desc="Title shown in the Android message when the account tailored security setting changes for a consented primary account">
       Enhanced Safe Browsing is off
     </message>
     <message name="IDS_TAILORED_SECURITY_CONSENTED_DISABLE_MESSAGE_DESCRIPTION" desc="Description shown in the Android message when the account tailored security setting changes for a consented primary account">
-      You&#x2019;re getting standard protection
-    </message>
-    <message name="IDS_TAILORED_SECURITY_CONSENTED_DISABLE_MESSAGE_DESCRIPTION_UPDATED" desc="Description shown in the Android message when the account tailored security setting changes for a consented primary account">
       You&#x2019;re getting standard security protection on this device
     </message>
     <message name="IDS_TAILORED_SECURITY_CONSENTED_MESSAGE_OK_BUTTON" desc="The text on the ok button in the Android message when the account tailored security setting changes for a consented primary account">
@@ -16702,9 +16696,6 @@
     </if>
 
     <message name="IDS_TAILORED_SECURITY_UNCONSENTED_PROMOTION_NOTIFICATION_DESCRIPTION" desc="Description of notification prompting the user to enable Enhanced Safe Browsing after they have enabled account-level protections.">
-      Enhanced protection does more to block phishing and malware
-    </message>
-    <message name="IDS_TAILORED_SECURITY_UNCONSENTED_PROMOTION_NOTIFICATION_DESCRIPTION_UPDATED" desc="Description of notification prompting the user to enable Enhanced Safe Browsing after they have enabled account-level protections.">
       Enhanced Safe Browsing does more to protect you against dangerous websites, downloads, and extensions
     </message>
     <if expr="use_titlecase">
diff --git a/chrome/app/generated_resources_grd/IDS_TAILORED_SECURITY_CONSENTED_DISABLE_MESSAGE_DESCRIPTION.png.sha1 b/chrome/app/generated_resources_grd/IDS_TAILORED_SECURITY_CONSENTED_DISABLE_MESSAGE_DESCRIPTION.png.sha1
index c445166e..05990e0 100644
--- a/chrome/app/generated_resources_grd/IDS_TAILORED_SECURITY_CONSENTED_DISABLE_MESSAGE_DESCRIPTION.png.sha1
+++ b/chrome/app/generated_resources_grd/IDS_TAILORED_SECURITY_CONSENTED_DISABLE_MESSAGE_DESCRIPTION.png.sha1
@@ -1 +1 @@
-cae0cee1c314d11faf3ca5eca13ee1a0900227ca
\ No newline at end of file
+d19b4fb01370487612177a52f60b64f8d7b67285
\ No newline at end of file
diff --git a/chrome/app/generated_resources_grd/IDS_TAILORED_SECURITY_CONSENTED_DISABLE_MESSAGE_DESCRIPTION_UPDATED.png.sha1 b/chrome/app/generated_resources_grd/IDS_TAILORED_SECURITY_CONSENTED_DISABLE_MESSAGE_DESCRIPTION_UPDATED.png.sha1
deleted file mode 100644
index 05990e0..0000000
--- a/chrome/app/generated_resources_grd/IDS_TAILORED_SECURITY_CONSENTED_DISABLE_MESSAGE_DESCRIPTION_UPDATED.png.sha1
+++ /dev/null
@@ -1 +0,0 @@
-d19b4fb01370487612177a52f60b64f8d7b67285
\ No newline at end of file
diff --git a/chrome/app/generated_resources_grd/IDS_TAILORED_SECURITY_CONSENTED_ENABLE_MESSAGE_DESCRIPTION.png.sha1 b/chrome/app/generated_resources_grd/IDS_TAILORED_SECURITY_CONSENTED_ENABLE_MESSAGE_DESCRIPTION.png.sha1
index 6ad8cbf..3abc57d 100644
--- a/chrome/app/generated_resources_grd/IDS_TAILORED_SECURITY_CONSENTED_ENABLE_MESSAGE_DESCRIPTION.png.sha1
+++ b/chrome/app/generated_resources_grd/IDS_TAILORED_SECURITY_CONSENTED_ENABLE_MESSAGE_DESCRIPTION.png.sha1
@@ -1 +1 @@
-90f4324744dfbfcc4af24d269c5d75fcdf2d3804
\ No newline at end of file
+64fe53ea91c970f334f9bc9aa24d3e8035c6df32
\ No newline at end of file
diff --git a/chrome/app/generated_resources_grd/IDS_TAILORED_SECURITY_CONSENTED_ENABLE_MESSAGE_DESCRIPTION_UPDATED.png.sha1 b/chrome/app/generated_resources_grd/IDS_TAILORED_SECURITY_CONSENTED_ENABLE_MESSAGE_DESCRIPTION_UPDATED.png.sha1
deleted file mode 100644
index 3abc57d..0000000
--- a/chrome/app/generated_resources_grd/IDS_TAILORED_SECURITY_CONSENTED_ENABLE_MESSAGE_DESCRIPTION_UPDATED.png.sha1
+++ /dev/null
@@ -1 +0,0 @@
-64fe53ea91c970f334f9bc9aa24d3e8035c6df32
\ No newline at end of file
diff --git a/chrome/app/generated_resources_grd/IDS_TAILORED_SECURITY_UNCONSENTED_PROMOTION_NOTIFICATION_DESCRIPTION.png.sha1 b/chrome/app/generated_resources_grd/IDS_TAILORED_SECURITY_UNCONSENTED_PROMOTION_NOTIFICATION_DESCRIPTION.png.sha1
index faf962e..9c4913e 100644
--- a/chrome/app/generated_resources_grd/IDS_TAILORED_SECURITY_UNCONSENTED_PROMOTION_NOTIFICATION_DESCRIPTION.png.sha1
+++ b/chrome/app/generated_resources_grd/IDS_TAILORED_SECURITY_UNCONSENTED_PROMOTION_NOTIFICATION_DESCRIPTION.png.sha1
@@ -1 +1 @@
-a02d79936796ef839a4689570bcfdc9622e54755
\ No newline at end of file
+59de64f300936bc601818dc749ef9860d89a7a41
\ No newline at end of file
diff --git a/chrome/app/generated_resources_grd/IDS_TAILORED_SECURITY_UNCONSENTED_PROMOTION_NOTIFICATION_DESCRIPTION_UPDATED.png.sha1 b/chrome/app/generated_resources_grd/IDS_TAILORED_SECURITY_UNCONSENTED_PROMOTION_NOTIFICATION_DESCRIPTION_UPDATED.png.sha1
deleted file mode 100644
index 9c4913e..0000000
--- a/chrome/app/generated_resources_grd/IDS_TAILORED_SECURITY_UNCONSENTED_PROMOTION_NOTIFICATION_DESCRIPTION_UPDATED.png.sha1
+++ /dev/null
@@ -1 +0,0 @@
-59de64f300936bc601818dc749ef9860d89a7a41
\ No newline at end of file
diff --git a/chrome/app/os_settings_strings.grdp b/chrome/app/os_settings_strings.grdp
index 9a5989c..cfee852 100644
--- a/chrome/app/os_settings_strings.grdp
+++ b/chrome/app/os_settings_strings.grdp
@@ -6596,8 +6596,14 @@
   <message name="IDS_OS_SETTINGS_PRIVACY_HUB_GEOLOCATION_AREA_TITLE" desc="The title of the geolocation row under the Privacy controls (aka Privacy Hub) os-settings page.">
     Location access
   </message>
-  <message name="IDS_OS_SETTINGS_PRIVACY_HUB_GEOLOCATION_AREA_DESCRIPTION" desc="The description of the geolocation row under the Privacy controls (aka Privacy Hub) os-settings page. Displays the system overivew for geolocation usage.">
-    Manage location access for apps, websites and system services. Location may use sources like Wi-Fi, mobile networks, and sensors to help estimate your device's location. <ph name="LINK_BEGIN">&lt;a&gt;</ph>Learn more<ph name="LINK_END">&lt;/a&gt;</ph>
+  <message name="IDS_OS_SETTINGS_PRIVACY_HUB_GEOLOCATION_AREA_ALLOWED_SUBTEXT" desc="The subtext of the geolocation row under the Privacy controls (aka Privacy Hub) os-settings page. Shown when the system geolocation state is allowed, explaining how the system is affected.">
+    Apps and websites with the location permission, as well as system services, can use your location
+  </message>
+  <message name="IDS_OS_SETTINGS_PRIVACY_HUB_GEOLOCATION_AREA_ONLY_ALLOWED_FOR_SYSTEM_SUBTEXT" desc="The subtext of the geolocation row under the Privacy controls (aka Privacy Hub) os-settings page. Shown when the system geolocation state is only-allowed-for-system, explaining how the system is affected.">
+    Only system services can use your location
+  </message>
+  <message name="IDS_OS_SETTINGS_PRIVACY_HUB_GEOLOCATION_AREA_DISALLOWED_SUBTEXT" desc="The subtext of the geolocation row under the Privacy controls (aka Privacy Hub) os-settings page. Shown when the system geolocation state is blocked, explaining how the system is affected.">
+    Location access is blocked
   </message>
   <message name="IDS_OS_SETTINGS_PRIVACY_HUB_GEOLOCATION_ACCESS_LEVEL_ALLOWED" desc="In Geolocation setting subpage (under Privacy Controls), Dropdown list item to set system-wide geolocation access level. ">
     Allowed
diff --git a/chrome/app/os_settings_strings_grdp/IDS_OS_SETTINGS_PRIVACY_HUB_GEOLOCATION_AREA_ALLOWED_SUBTEXT.png.sha1 b/chrome/app/os_settings_strings_grdp/IDS_OS_SETTINGS_PRIVACY_HUB_GEOLOCATION_AREA_ALLOWED_SUBTEXT.png.sha1
new file mode 100644
index 0000000..fbfa997
--- /dev/null
+++ b/chrome/app/os_settings_strings_grdp/IDS_OS_SETTINGS_PRIVACY_HUB_GEOLOCATION_AREA_ALLOWED_SUBTEXT.png.sha1
@@ -0,0 +1 @@
+708359e5d57f900d5fe67b433867a1b7edecddff
\ No newline at end of file
diff --git a/chrome/app/os_settings_strings_grdp/IDS_OS_SETTINGS_PRIVACY_HUB_GEOLOCATION_AREA_DESCRIPTION.png.sha1 b/chrome/app/os_settings_strings_grdp/IDS_OS_SETTINGS_PRIVACY_HUB_GEOLOCATION_AREA_DESCRIPTION.png.sha1
deleted file mode 100644
index a5abec9..0000000
--- a/chrome/app/os_settings_strings_grdp/IDS_OS_SETTINGS_PRIVACY_HUB_GEOLOCATION_AREA_DESCRIPTION.png.sha1
+++ /dev/null
@@ -1 +0,0 @@
-b742354a8e48add7c1f550057e8b064f98849174
\ No newline at end of file
diff --git a/chrome/app/os_settings_strings_grdp/IDS_OS_SETTINGS_PRIVACY_HUB_GEOLOCATION_AREA_DISALLOWED_SUBTEXT.png.sha1 b/chrome/app/os_settings_strings_grdp/IDS_OS_SETTINGS_PRIVACY_HUB_GEOLOCATION_AREA_DISALLOWED_SUBTEXT.png.sha1
new file mode 100644
index 0000000..9d043f0
--- /dev/null
+++ b/chrome/app/os_settings_strings_grdp/IDS_OS_SETTINGS_PRIVACY_HUB_GEOLOCATION_AREA_DISALLOWED_SUBTEXT.png.sha1
@@ -0,0 +1 @@
+af6de1cedfd9414873c223432089db664bcecd44
\ No newline at end of file
diff --git a/chrome/app/os_settings_strings_grdp/IDS_OS_SETTINGS_PRIVACY_HUB_GEOLOCATION_AREA_ONLY_ALLOWED_FOR_SYSTEM_SUBTEXT.png.sha1 b/chrome/app/os_settings_strings_grdp/IDS_OS_SETTINGS_PRIVACY_HUB_GEOLOCATION_AREA_ONLY_ALLOWED_FOR_SYSTEM_SUBTEXT.png.sha1
new file mode 100644
index 0000000..fc7437b0
--- /dev/null
+++ b/chrome/app/os_settings_strings_grdp/IDS_OS_SETTINGS_PRIVACY_HUB_GEOLOCATION_AREA_ONLY_ALLOWED_FOR_SYSTEM_SUBTEXT.png.sha1
@@ -0,0 +1 @@
+dda2c65b9fd0b1d8579ba4066368589037adf74b
\ No newline at end of file
diff --git a/chrome/app/theme/default_100_percent/common/tailored_security_unconsented.png b/chrome/app/theme/default_100_percent/common/tailored_security_unconsented.png
deleted file mode 100644
index 2a3ad36..0000000
--- a/chrome/app/theme/default_100_percent/common/tailored_security_unconsented.png
+++ /dev/null
Binary files differ
diff --git a/chrome/app/theme/default_200_percent/common/tailored_security_unconsented.png b/chrome/app/theme/default_200_percent/common/tailored_security_unconsented.png
deleted file mode 100644
index a099bc6..0000000
--- a/chrome/app/theme/default_200_percent/common/tailored_security_unconsented.png
+++ /dev/null
Binary files differ
diff --git a/chrome/app/theme/theme_resources.grd b/chrome/app/theme/theme_resources.grd
index 10628c5c..884feb56 100644
--- a/chrome/app/theme/theme_resources.grd
+++ b/chrome/app/theme/theme_resources.grd
@@ -374,8 +374,7 @@
       <if expr="not is_android">
         <structure type="chrome_scaled_image" name="IDR_TAILORED_SECURITY_CONSENTED" file="common/safer_with_google_shield.png" />
         <structure type="chrome_scaled_image" name="IDR_TAILORED_SECURITY_CONSENTED_DARK" file="common/safer_with_google_shield_dark.png" />
-        <structure type="chrome_scaled_image" name="IDR_TAILORED_SECURITY_UNCONSENTED" file="common/tailored_security_unconsented.png" />
-        <structure type="chrome_scaled_image" name="IDR_TAILORED_SECURITY_UNCONSENTED_UPDATED" file="common/safer_with_google_shield.png" />
+        <structure type="chrome_scaled_image" name="IDR_TAILORED_SECURITY_UNCONSENTED" file="common/safer_with_google_shield.png" />
       </if>
       <if expr="_google_chrome">
         <if expr="not is_android">
diff --git a/chrome/app/vector_icons/BUILD.gn b/chrome/app/vector_icons/BUILD.gn
index 3cfa2de..c35ec58 100644
--- a/chrome/app/vector_icons/BUILD.gn
+++ b/chrome/app/vector_icons/BUILD.gn
@@ -56,7 +56,6 @@
     "click_to_call_illustration.icon",
     "click_to_call_illustration_dark.icon",
     "close_chrome_refresh.icon",
-    "close_group.icon",
     "close_group_refresh.icon",
     "close_tab_chrome_refresh.icon",
     "computer_with_circle_background.icon",
@@ -136,7 +135,6 @@
     "menu_book_chrome_refresh.icon",
     "mixed_content.icon",
     "more_tools_menu.icon",
-    "move_group_to_new_window.icon",
     "move_group_to_new_window_refresh.icon",
     "my_location.icon",
     "name_window.icon",
@@ -146,7 +144,6 @@
     "navigate_stop.icon",
     "navigate_stop_chrome_refresh.icon",
     "navigate_stop_touch.icon",
-    "new_tab_in_group.icon",
     "new_tab_in_group_refresh.icon",
     "new_tab_refresh.icon",
     "new_window.icon",
@@ -198,7 +195,6 @@
     "right_panel_close.icon",
     "sad_tab.icon",
     "safety_check.icon",
-    "save_group.icon",
     "save_group_refresh.icon",
     "save_page.icon",
     "saved_tab_group_bar_everything.icon",
@@ -257,7 +253,6 @@
     "trash_can_refresh.icon",
     "tv.icon",
     "unbranded_translate.icon",
-    "ungroup.icon",
     "ungroup_refresh.icon",
     "usb_cable.icon",
     "user_account_avatar.icon",
diff --git a/chrome/app/vector_icons/close_group.icon b/chrome/app/vector_icons/close_group.icon
deleted file mode 100644
index c4b19dc3..0000000
--- a/chrome/app/vector_icons/close_group.icon
+++ /dev/null
@@ -1,31 +0,0 @@
-// Copyright 2021 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-CANVAS_DIMENSIONS, 16,
-MOVE_TO, 8, 1.33f,
-CUBIC_TO, 4.31f, 1.33f, 1.33f, 4.31f, 1.33f, 8,
-CUBIC_TO, 1.33f, 11.69f, 4.31f, 14.67f, 8, 14.67f,
-CUBIC_TO, 11.69f, 14.67f, 14.67f, 11.69f, 14.67f, 8,
-CUBIC_TO, 14.67f, 4.31f, 11.69f, 1.33f, 8, 1.33f,
-CLOSE,
-MOVE_TO, 8, 13.33f,
-CUBIC_TO, 5.06f, 13.33f, 2.67f, 10.94f, 2.67f, 8,
-CUBIC_TO, 2.67f, 5.06f, 5.06f, 2.67f, 8, 2.67f,
-CUBIC_TO, 10.94f, 2.67f, 13.33f, 5.06f, 13.33f, 8,
-CUBIC_TO, 13.33f, 10.94f, 10.94f, 13.33f, 8, 13.33f,
-CLOSE,
-MOVE_TO, 8, 7.06f,
-LINE_TO, 10.39f, 4.67f,
-LINE_TO, 11.33f, 5.61f,
-LINE_TO, 8.94f, 8,
-LINE_TO, 11.33f, 10.39f,
-LINE_TO, 10.39f, 11.33f,
-LINE_TO, 8, 8.94f,
-LINE_TO, 5.61f, 11.33f,
-LINE_TO, 4.67f, 10.39f,
-LINE_TO, 7.06f, 8,
-LINE_TO, 4.67f, 5.61f,
-LINE_TO, 5.61f, 4.67f,
-LINE_TO, 8, 7.06f,
-CLOSE
diff --git a/chrome/app/vector_icons/close_group_refresh.icon b/chrome/app/vector_icons/close_group_refresh.icon
index 24e23a7f..50be38e 100644
--- a/chrome/app/vector_icons/close_group_refresh.icon
+++ b/chrome/app/vector_icons/close_group_refresh.icon
@@ -3,46 +3,93 @@
 // found in the LICENSE file.
 
 CANVAS_DIMENSIONS, 20,
-R_MOVE_TO, 9.68f, 11.55f,
-R_LINE_TO, 1.94f, -1.94f,
+FILL_RULE_NONZERO,
+MOVE_TO, 9.56f, 11.5f,
+LINE_TO, 11.5f, 9.56f,
 R_LINE_TO, 1.94f, 1.94f,
-R_LINE_TO, 1.23f, -1.23f,
-R_LINE_TO, -1.94f, -1.94f,
-R_LINE_TO, 1.94f, -1.94f,
-R_LINE_TO, -1.23f, -1.23f,
-R_LINE_TO, -1.94f, 1.94f,
-R_LINE_TO, -1.94f, -1.94f,
-R_LINE_TO, -1.23f, 1.23f,
-R_LINE_TO, 1.94f, 1.94f,
-R_LINE_TO, -1.94f, 1.94f,
+R_LINE_TO, 1.06f, -1.06f,
+LINE_TO, 12.56f, 8.5f,
+LINE_TO, 14.5f, 6.56f,
+LINE_TO, 13.44f, 5.5f,
+LINE_TO, 11.5f, 7.44f,
+LINE_TO, 9.56f, 5.5f,
+LINE_TO, 8.5f, 6.56f,
+LINE_TO, 10.44f, 8.5f,
+LINE_TO, 8.5f, 10.44f,
 CLOSE,
-R_MOVE_TO, -3, 3.51f,
-R_CUBIC_TO, -0.48f, 0, -0.89f, -0.17f, -1.23f, -0.5f,
-R_ARC_TO, 1.68f, 1.68f, 0, 0, 1, -0.5f, -1.23f,
-V_LINE_TO, 3.45f,
-R_CUBIC_TO, 0, -0.48f, 0.17f, -0.89f, 0.5f, -1.22f,
-R_CUBIC_TO, 0.34f, -0.34f, 0.75f, -0.51f, 1.23f, -0.51f,
-R_H_LINE_TO, 9.88f,
-R_CUBIC_TO, 0.48f, 0, 0.89f, 0.17f, 1.22f, 0.51f,
-R_CUBIC_TO, 0.34f, 0.34f, 0.51f, 0.74f, 0.51f, 1.22f,
-R_V_LINE_TO, 9.88f,
-R_CUBIC_TO, 0, 0.48f, -0.17f, 0.89f, -0.51f, 1.23f,
-R_ARC_TO, 1.66f, 1.66f, 0, 0, 1, -1.22f, 0.5f,
+MOVE_TO, 6.5f, 15,
+R_CUBIC_TO, -0.41f, 0, -0.77f, -0.15f, -1.06f, -0.44f,
+ARC_TO, 1.44f, 1.44f, 0, 0, 1, 5, 13.5f,
+R_V_LINE_TO, -10,
+R_CUBIC_TO, 0, -0.41f, 0.15f, -0.77f, 0.44f, -1.06f,
+ARC_TO, 1.44f, 1.44f, 0, 0, 1, 6.5f, 2,
+R_H_LINE_TO, 10,
+R_CUBIC_TO, 0.41f, 0, 0.77f, 0.15f, 1.06f, 0.44f,
+R_CUBIC_TO, 0.29f, 0.29f, 0.44f, 0.65f, 0.44f, 1.06f,
+R_V_LINE_TO, 10,
+R_CUBIC_TO, 0, 0.41f, -0.15f, 0.77f, -0.44f, 1.06f,
+ARC_TO, 1.44f, 1.44f, 0, 0, 1, 16.5f, 15,
 CLOSE,
-R_MOVE_TO, 0, -1.73f,
-R_H_LINE_TO, 9.88f,
-R_V_LINE_TO, -9.88f,
-R_H_LINE_TO, -9.88f,
+R_MOVE_TO, 0, -1.5f,
+R_H_LINE_TO, 10,
+R_V_LINE_TO, -10,
+R_H_LINE_TO, -10,
 CLOSE,
-R_MOVE_TO, -3.23f, 4.96f,
-R_CUBIC_TO, -0.48f, 0, -0.89f, -0.17f, -1.22f, -0.51f,
-R_ARC_TO, 1.65f, 1.65f, 0, 0, 1, -0.51f, -1.22f,
-V_LINE_TO, 4.95f,
-R_H_LINE_TO, 1.73f,
-R_V_LINE_TO, 11.61f,
-R_H_LINE_TO, 11.61f,
-R_V_LINE_TO, 1.73f,
+R_MOVE_TO, -3, 4.5f,
+R_CUBIC_TO, -0.41f, 0, -0.77f, -0.15f, -1.06f, -0.44f,
+ARC_TO, 1.44f, 1.44f, 0, 0, 1, 2, 16.5f,
+V_LINE_TO, 5,
+R_H_LINE_TO, 1.5f,
+R_V_LINE_TO, 11.5f,
+H_LINE_TO, 15,
+V_LINE_TO, 18,
 CLOSE,
-R_MOVE_TO, 3.23f, -14.84f,
-R_V_LINE_TO, 9.88f,
+R_MOVE_TO, 3, -14.5f,
+R_V_LINE_TO, 10,
+CLOSE
+
+CANVAS_DIMENSIONS, 16,
+FILL_RULE_NONZERO,
+R_MOVE_TO, 7.74f, 9.24f,
+R_LINE_TO, 1.55f, -1.55f,
+R_LINE_TO, 1.55f, 1.55f,
+R_LINE_TO, 0.98f, -0.98f,
+R_LINE_TO, -1.55f, -1.55f,
+R_LINE_TO, 1.55f, -1.55f,
+R_LINE_TO, -0.98f, -0.98f,
+R_LINE_TO, -1.55f, 1.55f,
+R_LINE_TO, -1.55f, -1.55f,
+R_LINE_TO, -0.98f, 0.98f,
+R_LINE_TO, 1.55f, 1.55f,
+R_LINE_TO, -1.55f, 1.55f,
+CLOSE,
+MOVE_TO, 5.34f, 12.04f,
+R_CUBIC_TO, -0.38f, 0, -0.71f, -0.13f, -0.98f, -0.4f,
+R_ARC_TO, 1.34f, 1.34f, 0, 0, 1, -0.4f, -0.98f,
+V_LINE_TO, 2.76f,
+R_CUBIC_TO, 0, -0.39f, 0.13f, -0.71f, 0.4f, -0.98f,
+R_CUBIC_TO, 0.27f, -0.27f, 0.6f, -0.41f, 0.98f, -0.41f,
+R_H_LINE_TO, 7.9f,
+R_CUBIC_TO, 0.39f, 0, 0.71f, 0.14f, 0.98f, 0.41f,
+R_CUBIC_TO, 0.27f, 0.27f, 0.41f, 0.59f, 0.41f, 0.98f,
+R_V_LINE_TO, 7.9f,
+R_CUBIC_TO, 0, 0.38f, -0.14f, 0.71f, -0.41f, 0.98f,
+R_ARC_TO, 1.33f, 1.33f, 0, 0, 1, -0.98f, 0.4f,
+CLOSE,
+R_MOVE_TO, 0, -1.38f,
+R_H_LINE_TO, 7.9f,
+V_LINE_TO, 2.76f,
+H_LINE_TO, 5.34f,
+CLOSE,
+MOVE_TO, 2.76f, 14.63f,
+R_CUBIC_TO, -0.39f, 0, -0.71f, -0.14f, -0.98f, -0.41f,
+R_ARC_TO, 1.33f, 1.33f, 0, 0, 1, -0.41f, -0.98f,
+V_LINE_TO, 3.96f,
+R_H_LINE_TO, 1.39f,
+R_V_LINE_TO, 9.29f,
+R_H_LINE_TO, 9.29f,
+R_V_LINE_TO, 1.39f,
+CLOSE,
+MOVE_TO, 5.34f, 2.76f,
+R_V_LINE_TO, 7.9f,
 CLOSE
diff --git a/chrome/app/vector_icons/move_group_to_new_window.icon b/chrome/app/vector_icons/move_group_to_new_window.icon
deleted file mode 100644
index 48dbb97..0000000
--- a/chrome/app/vector_icons/move_group_to_new_window.icon
+++ /dev/null
@@ -1,34 +0,0 @@
-// Copyright 2021 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-CANVAS_DIMENSIONS, 16,
-MOVE_TO, 2, 12.67f,
-LINE_TO, 2, 3.33f,
-CUBIC_TO, 2, 2.6f, 2.6f, 2, 3.33f, 2,
-LINE_TO, 12.67f, 2,
-CUBIC_TO, 13.4f, 2, 14, 2.6f, 14, 3.33f,
-LINE_TO, 14, 12.67f,
-CUBIC_TO, 14, 13.41f, 13.4f, 14, 12.67f, 14,
-LINE_TO, 10, 14,
-LINE_TO, 10, 12.67f,
-LINE_TO, 12.67f, 12.67f,
-LINE_TO, 12.67f, 3.33f,
-LINE_TO, 3.33f, 3.33f,
-LINE_TO, 3.33f, 12.67f,
-LINE_TO, 6, 12.67f,
-LINE_TO, 6, 14,
-LINE_TO, 3.33f, 14,
-CUBIC_TO, 2.6f, 14, 2, 13.41f, 2, 12.67f,
-CLOSE,
-MOVE_TO, 11.33f, 9,
-LINE_TO, 10.39f, 9.94f,
-LINE_TO, 8.67f, 8.22f,
-LINE_TO, 8.67f, 14,
-LINE_TO, 7.33f, 14,
-LINE_TO, 7.33f, 8.22f,
-LINE_TO, 5.61f, 9.94f,
-LINE_TO, 4.67f, 9,
-LINE_TO, 8, 5.67f,
-LINE_TO, 11.33f, 9,
-CLOSE
diff --git a/chrome/app/vector_icons/move_group_to_new_window_refresh.icon b/chrome/app/vector_icons/move_group_to_new_window_refresh.icon
index c0f77a5..7b9d616f 100644
--- a/chrome/app/vector_icons/move_group_to_new_window_refresh.icon
+++ b/chrome/app/vector_icons/move_group_to_new_window_refresh.icon
@@ -3,43 +3,87 @@
 // found in the LICENSE file.
 
 CANVAS_DIMENSIONS, 20,
-MOVE_TO, 6.68f, 15.06f,
-R_CUBIC_TO, -0.48f, 0, -0.89f, -0.17f, -1.23f, -0.5f,
-R_ARC_TO, 1.68f, 1.68f, 0, 0, 1, -0.5f, -1.23f,
-R_V_LINE_TO, -1.9f,
-R_H_LINE_TO, 1.73f,
-R_V_LINE_TO, 1.9f,
-R_H_LINE_TO, 9.88f,
-V_LINE_TO, 4.89f,
-R_H_LINE_TO, -9.88f,
-R_V_LINE_TO, 1.9f,
-R_H_LINE_TO, -1.73f,
-V_LINE_TO, 3.45f,
-R_CUBIC_TO, 0, -0.48f, 0.17f, -0.89f, 0.5f, -1.22f,
-R_CUBIC_TO, 0.34f, -0.34f, 0.75f, -0.51f, 1.23f, -0.51f,
-R_H_LINE_TO, 9.88f,
-R_CUBIC_TO, 0.48f, 0, 0.89f, 0.17f, 1.22f, 0.51f,
-R_CUBIC_TO, 0.34f, 0.34f, 0.51f, 0.74f, 0.51f, 1.22f,
-R_V_LINE_TO, 9.88f,
-R_CUBIC_TO, 0, 0.48f, -0.17f, 0.89f, -0.51f, 1.23f,
-R_ARC_TO, 1.66f, 1.66f, 0, 0, 1, -1.22f, 0.5f,
+FILL_RULE_NONZERO,
+MOVE_TO, 6.5f, 15,
+R_CUBIC_TO, -0.41f, 0, -0.77f, -0.15f, -1.06f, -0.44f,
+ARC_TO, 1.44f, 1.44f, 0, 0, 1, 5, 13.5f,
+R_V_LINE_TO, -2,
+R_H_LINE_TO, 1.5f,
+R_V_LINE_TO, 2,
+R_H_LINE_TO, 10,
+V_LINE_TO, 5,
+R_H_LINE_TO, -10,
+R_V_LINE_TO, 2,
+H_LINE_TO, 5,
+V_LINE_TO, 3.5f,
+R_CUBIC_TO, 0, -0.41f, 0.15f, -0.77f, 0.44f, -1.06f,
+ARC_TO, 1.44f, 1.44f, 0, 0, 1, 6.5f, 2,
+R_H_LINE_TO, 10,
+R_CUBIC_TO, 0.41f, 0, 0.77f, 0.15f, 1.06f, 0.44f,
+R_CUBIC_TO, 0.29f, 0.29f, 0.44f, 0.65f, 0.44f, 1.06f,
+R_V_LINE_TO, 10,
+R_CUBIC_TO, 0, 0.41f, -0.15f, 0.77f, -0.44f, 1.06f,
+ARC_TO, 1.44f, 1.44f, 0, 0, 1, 16.5f, 15,
 CLOSE,
-R_MOVE_TO, -3.23f, 3.23f,
-R_CUBIC_TO, -0.48f, 0, -0.89f, -0.17f, -1.22f, -0.51f,
-R_ARC_TO, 1.65f, 1.65f, 0, 0, 1, -0.51f, -1.22f,
-V_LINE_TO, 4.95f,
-R_H_LINE_TO, 1.73f,
-R_V_LINE_TO, 11.61f,
-R_H_LINE_TO, 11.61f,
-R_V_LINE_TO, 1.73f,
+R_MOVE_TO, -3, 3,
+R_CUBIC_TO, -0.41f, 0, -0.77f, -0.15f, -1.06f, -0.44f,
+ARC_TO, 1.44f, 1.44f, 0, 0, 1, 2, 16.5f,
+V_LINE_TO, 5,
+R_H_LINE_TO, 1.5f,
+R_V_LINE_TO, 11.5f,
+H_LINE_TO, 15,
+V_LINE_TO, 18,
 CLOSE,
-R_MOVE_TO, 7.5f, -5.84f,
-R_LINE_TO, -1.15f, -1.15f,
-R_LINE_TO, 1.37f, -1.37f,
-H_LINE_TO, 4.95f,
-V_LINE_TO, 8.29f,
-R_H_LINE_TO, 6.22f,
-LINE_TO, 9.8f, 6.92f,
-R_LINE_TO, 1.15f, -1.15f,
-R_LINE_TO, 3.34f, 3.34f,
+R_MOVE_TO, 7.25f, -5.5f,
+R_LINE_TO, -1.06f, -1.06f,
+LINE_TO, 11.13f, 10,
+H_LINE_TO, 5,
+V_LINE_TO, 8.5f,
+R_H_LINE_TO, 6.13f,
+LINE_TO, 9.69f, 7.06f,
+LINE_TO, 10.75f, 6,
+LINE_TO, 14, 9.25f,
+CLOSE
+
+CANVAS_DIMENSIONS, 16,
+FILL_RULE_NONZERO,
+MOVE_TO, 5.34f, 12.04f,
+R_CUBIC_TO, -0.38f, 0, -0.71f, -0.13f, -0.98f, -0.4f,
+R_ARC_TO, 1.34f, 1.34f, 0, 0, 1, -0.4f, -0.98f,
+V_LINE_TO, 9.14f,
+H_LINE_TO, 5.34f,
+R_V_LINE_TO, 1.52f,
+R_H_LINE_TO, 7.9f,
+V_LINE_TO, 3.91f,
+H_LINE_TO, 5.34f,
+R_V_LINE_TO, 1.52f,
+H_LINE_TO, 3.96f,
+V_LINE_TO, 2.76f,
+R_CUBIC_TO, 0, -0.39f, 0.13f, -0.71f, 0.4f, -0.98f,
+R_CUBIC_TO, 0.27f, -0.27f, 0.6f, -0.41f, 0.98f, -0.41f,
+R_H_LINE_TO, 7.9f,
+R_CUBIC_TO, 0.39f, 0, 0.71f, 0.14f, 0.98f, 0.41f,
+R_CUBIC_TO, 0.27f, 0.27f, 0.41f, 0.59f, 0.41f, 0.98f,
+R_V_LINE_TO, 7.9f,
+R_CUBIC_TO, 0, 0.38f, -0.14f, 0.71f, -0.41f, 0.98f,
+R_ARC_TO, 1.33f, 1.33f, 0, 0, 1, -0.98f, 0.4f,
+CLOSE,
+R_MOVE_TO, -2.58f, 2.59f,
+R_CUBIC_TO, -0.39f, 0, -0.71f, -0.14f, -0.98f, -0.41f,
+R_ARC_TO, 1.33f, 1.33f, 0, 0, 1, -0.41f, -0.98f,
+V_LINE_TO, 3.96f,
+R_H_LINE_TO, 1.39f,
+R_V_LINE_TO, 9.29f,
+R_H_LINE_TO, 9.29f,
+R_V_LINE_TO, 1.39f,
+CLOSE,
+R_MOVE_TO, 6, -4.67f,
+R_LINE_TO, -0.92f, -0.92f,
+R_LINE_TO, 1.1f, -1.1f,
+R_H_LINE_TO, -4.98f,
+V_LINE_TO, 6.63f,
+R_H_LINE_TO, 4.98f,
+LINE_TO, 7.84f, 5.54f,
+R_LINE_TO, 0.92f, -0.92f,
+R_LINE_TO, 2.67f, 2.67f,
 CLOSE
diff --git a/chrome/app/vector_icons/new_tab_in_group.icon b/chrome/app/vector_icons/new_tab_in_group.icon
deleted file mode 100644
index 6e7ec9a..0000000
--- a/chrome/app/vector_icons/new_tab_in_group.icon
+++ /dev/null
@@ -1,19 +0,0 @@
-// Copyright 2021 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-CANVAS_DIMENSIONS, 16,
-MOVE_TO, 13.33f, 8.67f,
-H_LINE_TO, 8.67f,
-V_LINE_TO, 13.33f,
-H_LINE_TO, 7.33f,
-V_LINE_TO, 8.67f,
-H_LINE_TO, 2.67f,
-V_LINE_TO, 7.33f,
-H_LINE_TO, 7.33f,
-V_LINE_TO, 2.67f,
-H_LINE_TO, 8.67f,
-V_LINE_TO, 7.33f,
-H_LINE_TO, 13.33f,
-V_LINE_TO, 8.67f,
-CLOSE
diff --git a/chrome/app/vector_icons/new_tab_in_group_refresh.icon b/chrome/app/vector_icons/new_tab_in_group_refresh.icon
index 8d9f4553..e739155 100644
--- a/chrome/app/vector_icons/new_tab_in_group_refresh.icon
+++ b/chrome/app/vector_icons/new_tab_in_group_refresh.icon
@@ -47,3 +47,49 @@
 R_MOVE_TO, 3, -14.5f,
 R_V_LINE_TO, 10,
 CLOSE
+
+CANVAS_DIMENSIONS, 16,
+FILL_RULE_NONZERO,
+MOVE_TO, 8.69f, 9.51f,
+R_H_LINE_TO, 1.2f,
+R_V_LINE_TO, -2.2f,
+R_H_LINE_TO, 2.2f,
+V_LINE_TO, 6.11f,
+R_H_LINE_TO, -2.2f,
+V_LINE_TO, 3.91f,
+H_LINE_TO, 8.69f,
+R_V_LINE_TO, 2.2f,
+H_LINE_TO, 6.49f,
+R_V_LINE_TO, 1.2f,
+R_H_LINE_TO, 2.2f,
+CLOSE,
+MOVE_TO, 5.34f, 12.04f,
+R_CUBIC_TO, -0.38f, 0, -0.71f, -0.13f, -0.98f, -0.4f,
+R_ARC_TO, 1.34f, 1.34f, 0, 0, 1, -0.4f, -0.98f,
+V_LINE_TO, 2.76f,
+R_CUBIC_TO, 0, -0.39f, 0.13f, -0.71f, 0.4f, -0.98f,
+R_CUBIC_TO, 0.27f, -0.27f, 0.6f, -0.41f, 0.98f, -0.41f,
+R_H_LINE_TO, 7.9f,
+R_CUBIC_TO, 0.39f, 0, 0.71f, 0.14f, 0.98f, 0.41f,
+R_CUBIC_TO, 0.27f, 0.27f, 0.41f, 0.59f, 0.41f, 0.98f,
+R_V_LINE_TO, 7.9f,
+R_CUBIC_TO, 0, 0.38f, -0.14f, 0.71f, -0.41f, 0.98f,
+R_ARC_TO, 1.33f, 1.33f, 0, 0, 1, -0.98f, 0.4f,
+CLOSE,
+R_MOVE_TO, 0, -1.38f,
+R_H_LINE_TO, 7.9f,
+V_LINE_TO, 2.76f,
+H_LINE_TO, 5.34f,
+CLOSE,
+MOVE_TO, 2.76f, 14.63f,
+R_CUBIC_TO, -0.39f, 0, -0.71f, -0.14f, -0.98f, -0.41f,
+R_ARC_TO, 1.33f, 1.33f, 0, 0, 1, -0.41f, -0.98f,
+V_LINE_TO, 3.96f,
+R_H_LINE_TO, 1.39f,
+R_V_LINE_TO, 9.29f,
+R_H_LINE_TO, 9.29f,
+R_V_LINE_TO, 1.39f,
+CLOSE,
+MOVE_TO, 5.34f, 2.76f,
+R_V_LINE_TO, 7.9f,
+CLOSE
diff --git a/chrome/app/vector_icons/save_group.icon b/chrome/app/vector_icons/save_group.icon
deleted file mode 100644
index a670aaff..0000000
--- a/chrome/app/vector_icons/save_group.icon
+++ /dev/null
@@ -1,31 +0,0 @@
-// Copyright 2021 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-CANVAS_DIMENSIONS, 16,
-MOVE_TO, 12.67f, 4,
-H_LINE_TO, 11.51f,
-CUBIC_TO, 12.62f, 4.97f, 13.33f, 6.4f, 13.33f, 8,
-CUBIC_TO, 13.33f, 10.72f, 11.3f, 12.96f, 8.67f, 13.29f,
-V_LINE_TO, 11.94f,
-CUBIC_TO, 10.55f, 11.62f, 12, 9.98f, 12, 8,
-CUBIC_TO, 12, 6.52f, 11.19f, 5.23f, 10, 4.54f,
-V_LINE_TO, 6.67f,
-H_LINE_TO, 8.67f,
-V_LINE_TO, 2.67f,
-H_LINE_TO, 12.67f,
-V_LINE_TO, 4,
-CLOSE,
-MOVE_TO, 4.49f, 12,
-CUBIC_TO, 3.38f, 11.03f, 2.67f, 9.6f, 2.67f, 8,
-CUBIC_TO, 2.67f, 5.28f, 4.7f, 3.04f, 7.33f, 2.71f,
-V_LINE_TO, 4.06f,
-CUBIC_TO, 5.45f, 4.38f, 4, 6.02f, 4, 8,
-CUBIC_TO, 4, 9.48f, 4.81f, 10.77f, 6, 11.46f,
-V_LINE_TO, 9.33f,
-H_LINE_TO, 7.33f,
-V_LINE_TO, 13.33f,
-H_LINE_TO, 3.33f,
-V_LINE_TO, 12,
-H_LINE_TO, 4.49f,
-CLOSE
diff --git a/chrome/app/vector_icons/saved_tab_group_bar_everything.icon b/chrome/app/vector_icons/saved_tab_group_bar_everything.icon
index 6ab18b9..97f6569 100644
--- a/chrome/app/vector_icons/saved_tab_group_bar_everything.icon
+++ b/chrome/app/vector_icons/saved_tab_group_bar_everything.icon
@@ -2,6 +2,57 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+CANVAS_DIMENSIONS, 20,
+FILL_RULE_NONZERO,
+MOVE_TO, 3, 9,
+V_LINE_TO, 3,
+R_H_LINE_TO, 6,
+R_V_LINE_TO, 6,
+CLOSE,
+R_MOVE_TO, 0, 8,
+R_V_LINE_TO, -6,
+R_H_LINE_TO, 6,
+R_V_LINE_TO, 6,
+CLOSE,
+R_MOVE_TO, 8, -8,
+V_LINE_TO, 3,
+R_H_LINE_TO, 6,
+R_V_LINE_TO, 6,
+CLOSE,
+R_MOVE_TO, 0, 8,
+R_V_LINE_TO, -6,
+R_H_LINE_TO, 6,
+R_V_LINE_TO, 6,
+CLOSE,
+MOVE_TO, 4.5f, 7.5f,
+R_H_LINE_TO, 3,
+R_V_LINE_TO, -3,
+R_H_LINE_TO, -3,
+CLOSE,
+R_MOVE_TO, 8, 0,
+R_H_LINE_TO, 3,
+R_V_LINE_TO, -3,
+R_H_LINE_TO, -3,
+CLOSE,
+R_MOVE_TO, 0, 8,
+R_H_LINE_TO, 3,
+R_V_LINE_TO, -3,
+R_H_LINE_TO, -3,
+CLOSE,
+R_MOVE_TO, -8, 0,
+R_H_LINE_TO, 3,
+R_V_LINE_TO, -3,
+R_H_LINE_TO, -3,
+CLOSE,
+R_MOVE_TO, 8, -8,
+CLOSE,
+R_MOVE_TO, 0, 5,
+CLOSE,
+R_MOVE_TO, -5, 0,
+CLOSE,
+R_MOVE_TO, 0, -5,
+CLOSE
+
 CANVAS_DIMENSIONS, 16,
 FILL_RULE_NONZERO,
 MOVE_TO, 2.27f, 7.2f,
diff --git a/chrome/app/vector_icons/tab_group.icon b/chrome/app/vector_icons/tab_group.icon
index 34f0bed..efae614 100644
--- a/chrome/app/vector_icons/tab_group.icon
+++ b/chrome/app/vector_icons/tab_group.icon
@@ -2,4 +2,25 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-CIRCLE, 24, 24, 24
\ No newline at end of file
+CANVAS_DIMENSIONS, 16,
+FILL_RULE_NONZERO,
+MOVE_TO, 8, 14.4f,
+CUBIC_TO, 7.13f, 14.4f, 6.29f, 14.23f, 5.52f, 13.9f,
+CUBIC_TO, 4.74f, 13.57f, 4.06f, 13.11f, 3.48f, 12.52f,
+CUBIC_TO, 2.89f, 11.94f, 2.43f, 11.26f, 2.1f, 10.48f,
+CUBIC_TO, 1.77f, 9.71f, 1.6f, 8.88f, 1.6f, 7.99f,
+CUBIC_TO, 1.6f, 7.11f, 1.77f, 6.28f, 2.1f, 5.51f,
+CUBIC_TO, 2.43f, 4.73f, 2.89f, 4.06f, 3.48f, 3.48f,
+CUBIC_TO, 4.06f, 2.89f, 4.74f, 2.43f, 5.52f, 2.1f,
+CUBIC_TO, 6.29f, 1.77f, 7.13f, 1.6f, 8.01f, 1.6f,
+CUBIC_TO, 8.89f, 1.6f, 9.72f, 1.77f, 10.49f, 2.1f,
+CUBIC_TO, 11.27f, 2.43f, 11.94f, 2.89f, 12.52f, 3.48f,
+CUBIC_TO, 13.11f, 4.06f, 13.57f, 4.74f, 13.9f, 5.51f,
+CUBIC_TO, 14.23f, 6.29f, 14.4f, 7.11f, 14.4f, 8,
+CUBIC_TO, 14.4f, 8.88f, 14.23f, 9.71f, 13.9f, 10.48f,
+CUBIC_TO, 13.57f, 11.26f, 13.11f, 11.94f, 12.52f, 12.52f,
+CUBIC_TO, 11.94f, 13.11f, 11.26f, 13.57f, 10.49f, 13.9f,
+CUBIC_TO, 9.71f, 14.23f, 8.89f, 14.4f, 8, 14.4f,
+CLOSE,
+MOVE_TO, 8, 14.4f,
+CLOSE
\ No newline at end of file
diff --git a/chrome/app/vector_icons/ungroup.icon b/chrome/app/vector_icons/ungroup.icon
deleted file mode 100644
index 4a1add66..0000000
--- a/chrome/app/vector_icons/ungroup.icon
+++ /dev/null
@@ -1,41 +0,0 @@
-// Copyright 2021 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-CANVAS_DIMENSIONS, 16,
-MOVE_TO, 2, 6,
-V_LINE_TO, 3.33f,
-CUBIC_TO, 2, 2.6f, 2.6f, 2, 3.33f, 2,
-H_LINE_TO, 6,
-V_LINE_TO, 3.33f,
-H_LINE_TO, 3.33f,
-V_LINE_TO, 6,
-H_LINE_TO, 2,
-CLOSE,
-MOVE_TO, 2, 10,
-H_LINE_TO, 3.33f,
-V_LINE_TO, 12.67f,
-H_LINE_TO, 6,
-V_LINE_TO, 14,
-H_LINE_TO, 3.33f,
-CUBIC_TO, 2.6f, 14, 2, 13.4f, 2, 12.67f,
-V_LINE_TO, 10,
-CLOSE,
-MOVE_TO, 12.67f, 12.67f,
-H_LINE_TO, 10,
-V_LINE_TO, 14,
-H_LINE_TO, 12.67f,
-CUBIC_TO, 13.4f, 14, 14, 13.4f, 14, 12.67f,
-V_LINE_TO, 10,
-H_LINE_TO, 12.67f,
-V_LINE_TO, 12.67f,
-CLOSE,
-MOVE_TO, 10, 2,
-H_LINE_TO, 12.67f,
-CUBIC_TO, 13.4f, 2, 14, 2.6f, 14, 3.33f,
-V_LINE_TO, 6,
-H_LINE_TO, 12.67f,
-V_LINE_TO, 3.33f,
-H_LINE_TO, 10,
-V_LINE_TO, 2,
-CLOSE
diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn
index ee7e860..7e5f8903 100644
--- a/chrome/browser/BUILD.gn
+++ b/chrome/browser/BUILD.gn
@@ -1890,6 +1890,8 @@
     "v8_compile_hints/v8_compile_hints_tab_helper.h",
     "visibility_timer_tab_helper.cc",
     "visibility_timer_tab_helper.h",
+    "visited_url_ranking/visited_url_ranking_service_factory.cc",
+    "visited_url_ranking/visited_url_ranking_service_factory.h",
     "vr/vr_tab_helper.cc",
     "vr/vr_tab_helper.h",
     "webapps/chrome_webapps_client.cc",
@@ -2531,6 +2533,7 @@
     "//components/variations/service:service",
     "//components/vector_icons",
     "//components/version_info",
+    "//components/visited_url_ranking/internal",
     "//components/visited_url_ranking/public",
     "//components/visitedlink/browser",
     "//components/visitedlink/common",
@@ -2658,6 +2661,7 @@
 
   if (!is_android) {
     deps += [
+      "//chrome/browser/ui/lens",
       "//chrome/browser/ui/webui/search_engine_choice:mojo_bindings",
       "//components/manta",
       "//components/manta/proto",
@@ -4752,6 +4756,7 @@
       "//components/user_notes/browser",
       "//components/user_notes/interfaces",
       "//components/user_notes/storage",
+      "//components/vector_icons",
       "//components/web_modal",
       "//components/webauthn/core/browser",
       "//components/webauthn/core/browser:passkey_model",
diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc
index 7b3a490..65d8f06 100644
--- a/chrome/browser/about_flags.cc
+++ b/chrome/browser/about_flags.cc
@@ -704,24 +704,6 @@
     {"Disable UI", kContextualPageActions_DisableUi},
 };
 
-const FeatureEntry::FeatureVariation
-    kContextualPageActionPriceTrackingVariations[] = {
-        {"Quiet", kContextualPageActionsUiParams_Quiet,
-         std::size(kContextualPageActionsUiParams_Quiet), nullptr},
-        {"Action Chip", kContextualPageActionsUiParams_ActionChip,
-         std::size(kContextualPageActionsUiParams_ActionChip), nullptr},
-        {"Action Chip - 6s", kContextualPageActionsUiParams_ActionChip_6s,
-         std::size(kContextualPageActionsUiParams_ActionChip_6s), nullptr},
-        {"Action Chip - Alternative Color",
-         kContextualPageActionsUiParams_ActionChip_AltColor,
-         std::size(kContextualPageActionsUiParams_ActionChip_AltColor),
-         nullptr},
-        {"Action Chip - Alternative Color - 6s",
-         kContextualPageActionsUiParams_ActionChip_AltColor_6s,
-         std::size(kContextualPageActionsUiParams_ActionChip_AltColor_6s),
-         nullptr},
-};
-
 const FeatureEntry::FeatureParam
     kContextualPageActionReaderMode_ActionChip_NotRateLimited[] = {
         {"action_chip", "true"},
@@ -4708,14 +4690,6 @@
          segmentation_platform::features::kContextualPageActions,
          kContextualPageActionsVariations,
          "ContextualPageActions")},
-    {"contextual-page-actions-with-price-tracking",
-     flag_descriptions::kContextualPageActionsPriceTrackingName,
-     flag_descriptions::kContextualPageActionsPriceTrackingDescription,
-     kOsAndroid,
-     FEATURE_WITH_PARAMS_VALUE_TYPE(
-         segmentation_platform::features::kContextualPageActionPriceTracking,
-         kContextualPageActionPriceTrackingVariations,
-         "ContextualPageActionPriceTracking")},
     {"contextual-page-actions-reader-mode",
      flag_descriptions::kContextualPageActionsReaderModeName,
      flag_descriptions::kContextualPageActionsReaderModeDescription, kOsAndroid,
@@ -7592,6 +7566,10 @@
      flag_descriptions::kAvifGainmapHdrImagesDescription, kOsAll,
      FEATURE_VALUE_TYPE(blink::features::kAvifGainmapHdrImages)},
 
+    {"crabbyavif", flag_descriptions::kCrabbyAvifName,
+     flag_descriptions::kCrabbyAvifDescription, kOsAll,
+     FEATURE_VALUE_TYPE(blink::features::kCrabbyAvif)},
+
     {"file-handling-icons", flag_descriptions::kFileHandlingIconsName,
      flag_descriptions::kFileHandlingIconsDescription, kOsDesktop,
      FEATURE_VALUE_TYPE(blink::features::kFileHandlingIcons)},
@@ -8658,12 +8636,6 @@
      flag_descriptions::kTailoredSecurityRetryForSyncUsersDescription, kOsAll,
      FEATURE_VALUE_TYPE(safe_browsing::kTailoredSecurityRetryForSyncUsers)},
 
-    {"enable-tailored-security-updated-messages",
-     flag_descriptions::kEnableTailoredSecurityUpdatedMessagesName,
-     flag_descriptions::kEnableTailoredSecurityUpdatedMessagesDescription,
-     kOsAll,
-     FEATURE_VALUE_TYPE(safe_browsing::kTailoredSecurityUpdatedMessages)},
-
 #if !BUILDFLAG(IS_ANDROID)
     {"sct-auditing", flag_descriptions::kSCTAuditingName,
      flag_descriptions::kSCTAuditingDescription, kOsDesktop,
diff --git a/chrome/browser/accessibility/embedded_a11y_extension_loader.cc b/chrome/browser/accessibility/embedded_a11y_extension_loader.cc
index 35e09843..5c3430e 100644
--- a/chrome/browser/accessibility/embedded_a11y_extension_loader.cc
+++ b/chrome/browser/accessibility/embedded_a11y_extension_loader.cc
@@ -65,8 +65,8 @@
 }  // namespace
 
 EmbeddedA11yExtensionLoader::ExtensionInfo::ExtensionInfo(
-    const std::string& extension_id,
-    const std::string& extension_path,
+    const std::string extension_id,
+    const std::string extension_path,
     const base::FilePath::CharType* extension_manifest_file,
     bool should_localize)
     : extension_id(extension_id),
@@ -110,23 +110,6 @@
   initialized_ = true;
 }
 
-void EmbeddedA11yExtensionLoader::InstallA11yHelperExtensionForReadingMode() {
-  // TODO(crbug.com/324143642): Install a11y helper extension for all platforms.
-#if BUILDFLAG(IS_CHROMEOS_LACROS)
-  InstallExtensionWithId(extension_misc::kEmbeddedA11yHelperExtensionId,
-                         extension_misc::kEmbeddedA11yHelperExtensionPath,
-                         extension_misc::kEmbeddedA11yHelperManifestFilename,
-                         /*should_localize=*/true);
-#endif
-}
-
-void EmbeddedA11yExtensionLoader::RemoveA11yHelperExtensionForReadingMode() {
-  // TODO(crbug.com/324143642): Remove a11y helper extension for all platforms.
-#if BUILDFLAG(IS_CHROMEOS_LACROS)
-  RemoveExtensionWithId(extension_misc::kEmbeddedA11yHelperExtensionId);
-#endif
-}
-
 void EmbeddedA11yExtensionLoader::InstallExtensionWithId(
     const std::string& extension_id,
     const std::string& extension_path,
@@ -234,9 +217,15 @@
   }
 
   base::FilePath resources_path;
+#if BUILDFLAG(IS_MAC)
+  base::FilePath root_path;
+  CHECK(base::PathService::Get(base::DIR_ASSETS, &root_path));
+  resources_path = root_path.Append("resources");
+#else
   if (!base::PathService::Get(chrome::DIR_RESOURCES, &resources_path)) {
     NOTREACHED();
   }
+#endif
 
   base::FilePath::StringType common_path;
 #if BUILDFLAG(IS_WIN)
@@ -279,3 +268,8 @@
     extension_installation_changed_callback_for_test_.Run();
   }
 }
+
+bool EmbeddedA11yExtensionLoader::IsExtensionInstalled(
+    const std::string& extension_id) {
+  return extension_map_.contains(extension_id);
+}
diff --git a/chrome/browser/accessibility/embedded_a11y_extension_loader.h b/chrome/browser/accessibility/embedded_a11y_extension_loader.h
index 7feb344..2504bac 100644
--- a/chrome/browser/accessibility/embedded_a11y_extension_loader.h
+++ b/chrome/browser/accessibility/embedded_a11y_extension_loader.h
@@ -34,8 +34,8 @@
   // Simple struct to hold information about each extension installed on all
   // profiles.
   struct ExtensionInfo {
-    ExtensionInfo(const std::string& extension_id,
-                  const std::string& extension_path,
+    ExtensionInfo(const std::string extension_id,
+                  const std::string extension_path,
                   const base::FilePath::CharType* extension_manifest_file,
                   bool should_localize);
     ExtensionInfo(const ExtensionInfo& other);
@@ -68,12 +68,6 @@
   // Should be called when the browser starts up.
   void Init();
 
-  // TODO(crbug.com/324143642): Observe the reading mode enabled/disabled state
-  // in this class instead of informing EmbeddedA11yManagerLacros to
-  // enable/disable reading mode.
-  virtual void InstallA11yHelperExtensionForReadingMode();
-  virtual void RemoveA11yHelperExtensionForReadingMode();
-
   // Install an extension.
   // `manifest_name` must live for the duration of the program. (e.g. be
   // statically allocated)
@@ -87,6 +81,9 @@
   // background page, and these extensions do not have background pages.
   void AddExtensionChangedCallbackForTest(base::RepeatingClosure callback);
 
+  // Check whether an extension is installed or not.
+  bool IsExtensionInstalled(const std::string& extension_id);
+
  private:
   // ProfileObserver:
   void OnProfileWillBeDestroyed(Profile* profile) override;
diff --git a/chrome/browser/accessibility/embedded_a11y_extension_loader_browsertest.cc b/chrome/browser/accessibility/embedded_a11y_extension_loader_browsertest.cc
index 5376dbed..95f0e1f 100644
--- a/chrome/browser/accessibility/embedded_a11y_extension_loader_browsertest.cc
+++ b/chrome/browser/accessibility/embedded_a11y_extension_loader_browsertest.cc
@@ -100,29 +100,7 @@
   std::unique_ptr<base::RunLoop> waiter_;
 };
 
-// TODO(b/324143642): test with non-Lacros extensions. Currently, the following
-// tests are tested with a Lacros extension embedded_a11y_helper_extension.
-// These tests should be tested with a cross-platform extension once the
-// extension is setup successfully.
-#if BUILDFLAG(IS_CHROMEOS_LACROS)
-IN_PROC_BROWSER_TEST_F(EmbeddedA11yExtensionLoaderTest,
-                       InstallsAndRemovesExtensionForReadingMode) {
-  ProfileManager* profile_manager = g_browser_process->profile_manager();
-  const auto& profiles = profile_manager->GetLoadedProfiles();
-  ASSERT_GT(profiles.size(), 0u);
-  Profile* profile = profiles[0];
-
-  auto* embedded_a11y_extension_loader =
-      EmbeddedA11yExtensionLoader::GetInstance();
-  embedded_a11y_extension_loader->InstallA11yHelperExtensionForReadingMode();
-  WaitForExtensionLoaded(profile,
-                         extension_misc::kEmbeddedA11yHelperExtensionId);
-
-  embedded_a11y_extension_loader->RemoveA11yHelperExtensionForReadingMode();
-  WaitForExtensionUnloaded(profile,
-                           extension_misc::kEmbeddedA11yHelperExtensionId);
-}
-
+#if !BUILDFLAG(IS_CHROMEOS_LACROS)
 IN_PROC_BROWSER_TEST_F(EmbeddedA11yExtensionLoaderTest,
                        InstallsRemovesAndReinstallsExtension) {
   ProfileManager* profile_manager = g_browser_process->profile_manager();
@@ -131,21 +109,22 @@
   Profile* profile = profiles[0];
 
   InstallAndWaitForExtensionLoaded(
-      profile, extension_misc::kEmbeddedA11yHelperExtensionId,
-      extension_misc::kEmbeddedA11yHelperExtensionPath,
-      extension_misc::kEmbeddedA11yHelperManifestFilename,
-      /*should_localize=*/true);
+      profile, extension_misc::kReadingModeGDocsHelperExtensionId,
+      extension_misc::kReadingModeGDocsHelperExtensionPath,
+      extension_misc::kReadingModeGDocsHelperManifestFilename,
+      /*should_localize=*/false);
   RemoveAndWaitForExtensionUnloaded(
-      profile, extension_misc::kEmbeddedA11yHelperExtensionId);
+      profile, extension_misc::kReadingModeGDocsHelperExtensionId);
   InstallAndWaitForExtensionLoaded(
-      profile, extension_misc::kEmbeddedA11yHelperExtensionId,
-      extension_misc::kEmbeddedA11yHelperExtensionPath,
-      extension_misc::kEmbeddedA11yHelperManifestFilename,
-      /*should_localize=*/true);
+      profile, extension_misc::kReadingModeGDocsHelperExtensionId,
+      extension_misc::kReadingModeGDocsHelperExtensionPath,
+      extension_misc::kReadingModeGDocsHelperManifestFilename,
+      /*should_localize=*/false);
   RemoveAndWaitForExtensionUnloaded(
-      profile, extension_misc::kEmbeddedA11yHelperExtensionId);
+      profile, extension_misc::kReadingModeGDocsHelperExtensionId);
 }
 
+#if !BUILDFLAG(IS_CHROMEOS_ASH)
 IN_PROC_BROWSER_TEST_F(EmbeddedA11yExtensionLoaderTest,
                        InstallsOnMultipleProfiles) {
   ProfileManager* profile_manager = g_browser_process->profile_manager();
@@ -167,47 +146,76 @@
   // Install extension for Reading Mode.
   auto* embedded_a11y_extension_loader =
       EmbeddedA11yExtensionLoader::GetInstance();
-  embedded_a11y_extension_loader->InstallA11yHelperExtensionForReadingMode();
+  embedded_a11y_extension_loader->InstallExtensionWithId(
+      extension_misc::kReadingModeGDocsHelperExtensionId,
+      extension_misc::kReadingModeGDocsHelperExtensionPath,
+      extension_misc::kReadingModeGDocsHelperManifestFilename,
+      /*should_localize=*/false);
   for (auto* const profile : profiles) {
     WaitForExtensionLoaded(profile,
-                           extension_misc::kEmbeddedA11yHelperExtensionId);
+                           extension_misc::kReadingModeGDocsHelperExtensionId);
   }
 
   // Remove the extension.
-  embedded_a11y_extension_loader->RemoveA11yHelperExtensionForReadingMode();
+  embedded_a11y_extension_loader->RemoveExtensionWithId(
+      extension_misc::kReadingModeGDocsHelperExtensionId);
   for (auto* const profile : profiles) {
-    WaitForExtensionUnloaded(profile,
-                             extension_misc::kEmbeddedA11yHelperExtensionId);
+    WaitForExtensionUnloaded(
+        profile, extension_misc::kReadingModeGDocsHelperExtensionId);
   }
 }
+#endif  // !BUILDFLAG(IS_CHROMEOS_ASH)
 
 IN_PROC_BROWSER_TEST_F(EmbeddedA11yExtensionLoaderTest,
                        InstallsOnIncognitoProfile) {
   ProfileManager* profile_manager = g_browser_process->profile_manager();
   Browser* incognito =
-      CreateIncognitoBrowser(profile_manager->GetPrimaryUserProfile());
+      CreateIncognitoBrowser(profile_manager->GetLastUsedProfile());
   content::RunAllTasksUntilIdle();
 
   InstallAndWaitForExtensionLoaded(
-      incognito->profile(), extension_misc::kEmbeddedA11yHelperExtensionId,
-      extension_misc::kEmbeddedA11yHelperExtensionPath,
-      extension_misc::kEmbeddedA11yHelperManifestFilename,
-      /*should_localize=*/true);
+      incognito->profile(), extension_misc::kReadingModeGDocsHelperExtensionId,
+      extension_misc::kReadingModeGDocsHelperExtensionPath,
+      extension_misc::kReadingModeGDocsHelperManifestFilename,
+      /*should_localize=*/false);
   RemoveAndWaitForExtensionUnloaded(
-      incognito->profile(), extension_misc::kEmbeddedA11yHelperExtensionId);
+      incognito->profile(), extension_misc::kReadingModeGDocsHelperExtensionId);
 }
 
+#if !BUILDFLAG(IS_CHROMEOS_ASH)
+// CreateGuestBrowser() is not supported for ChromeOS out of the box.
 IN_PROC_BROWSER_TEST_F(EmbeddedA11yExtensionLoaderTest,
                        InstallsOnGuestProfile) {
   Browser* guest_browser = CreateGuestBrowser();
   content::RunAllTasksUntilIdle();
 
   InstallAndWaitForExtensionLoaded(
-      guest_browser->profile(), extension_misc::kEmbeddedA11yHelperExtensionId,
+      guest_browser->profile(),
+      extension_misc::kReadingModeGDocsHelperExtensionId,
+      extension_misc::kReadingModeGDocsHelperExtensionPath,
+      extension_misc::kReadingModeGDocsHelperManifestFilename,
+      /*should_localize=*/false);
+  RemoveAndWaitForExtensionUnloaded(
+      guest_browser->profile(),
+      extension_misc::kReadingModeGDocsHelperExtensionId);
+}
+#endif  // !BUILDFLAG(IS_CHROMEOS_ASH)
+#endif  // !BUILDFLAG(IS_CHROMEOS_LACROS)
+
+#if BUILDFLAG(IS_CHROMEOS_LACROS)
+IN_PROC_BROWSER_TEST_F(EmbeddedA11yExtensionLoaderTest,
+                       InstallsExtensionOnLacros) {
+  ProfileManager* profile_manager = g_browser_process->profile_manager();
+  const auto& profiles = profile_manager->GetLoadedProfiles();
+  ASSERT_GT(profiles.size(), 0u);
+  Profile* profile = profiles[0];
+
+  InstallAndWaitForExtensionLoaded(
+      profile, extension_misc::kEmbeddedA11yHelperExtensionId,
       extension_misc::kEmbeddedA11yHelperExtensionPath,
       extension_misc::kEmbeddedA11yHelperManifestFilename,
       /*should_localize=*/true);
   RemoveAndWaitForExtensionUnloaded(
-      guest_browser->profile(), extension_misc::kEmbeddedA11yHelperExtensionId);
+      profile, extension_misc::kEmbeddedA11yHelperExtensionId);
 }
 #endif  // BUILDFLAG(IS_CHROMEOS_LACROS)
diff --git a/chrome/browser/android/omnibox/autocomplete_controller_android.cc b/chrome/browser/android/omnibox/autocomplete_controller_android.cc
index 82fb3f4..b36d1d1e 100644
--- a/chrome/browser/android/omnibox/autocomplete_controller_android.cc
+++ b/chrome/browser/android/omnibox/autocomplete_controller_android.cc
@@ -538,17 +538,9 @@
     const AutocompleteResult& autocomplete_result) {
   JNIEnv* env = AttachCurrentThread();
 
-  // Get the inline-autocomplete text.
-  std::u16string inline_autocompletion;
-  if (auto* default_match = autocomplete_result.default_match()) {
-    inline_autocompletion = default_match->inline_autocompletion;
-  }
-  ScopedJavaLocalRef<jstring> inline_text =
-      ConvertUTF16ToJavaString(env, inline_autocompletion);
-
   Java_AutocompleteController_onSuggestionsReceived(
       env, java_controller_, autocomplete_result.GetOrCreateJavaObject(env),
-      inline_text, autocomplete_controller_->done());
+      autocomplete_controller_->done());
 }
 
 void AutocompleteControllerAndroid::WarmUpRenderProcess() const {
diff --git a/chrome/browser/android/resource_id.h b/chrome/browser/android/resource_id.h
index 5f629b2..539e345 100644
--- a/chrome/browser/android/resource_id.h
+++ b/chrome/browser/android/resource_id.h
@@ -53,8 +53,6 @@
 DECLARE_RESOURCE_ID(IDR_ANDROID_MESSAGE_PERMISSION_XR,
                     R.drawable.gm_filled_cardboard_24)
 DECLARE_RESOURCE_ID(IDR_ANDROID_MESSAGE_SETTINGS, R.drawable.settings_cog)
-DECLARE_RESOURCE_ID(IDR_ANDROID_MESSAGE_SAFETY_CHECK, R.drawable.safety_check)
-DECLARE_RESOURCE_ID(IDR_ANDROID_MESSAGE_SHIELD, R.drawable.shield)
 DECLARE_RESOURCE_ID(IDR_ANDROID_MESSAGE_SHIELD_BLUE,
                     R.drawable.blue_google_shield)
 DECLARE_RESOURCE_ID(IDR_ANDROID_MESSAGE_SHIELD_GRAY, R.drawable.gray_shield)
diff --git a/chrome/browser/ash/BUILD.gn b/chrome/browser/ash/BUILD.gn
index ebca5d01..a52a753b 100644
--- a/chrome/browser/ash/BUILD.gn
+++ b/chrome/browser/ash/BUILD.gn
@@ -2616,8 +2616,8 @@
     "policy/enrollment/enrollment_state_fetcher.h",
     "policy/enrollment/enrollment_status.cc",
     "policy/enrollment/enrollment_status.h",
-    "policy/enrollment/flex_enrollment_token_provider.cc",
-    "policy/enrollment/flex_enrollment_token_provider.h",
+    "policy/enrollment/enrollment_token_provider.cc",
+    "policy/enrollment/enrollment_token_provider.h",
     "policy/enrollment/psm/construct_rlwe_id.cc",
     "policy/enrollment/psm/construct_rlwe_id.h",
     "policy/enrollment/psm/fake_rlwe_dmserver_client.cc",
@@ -6015,7 +6015,7 @@
     "policy/enrollment/enrollment_state_fetcher_unittest.cc",
     "policy/enrollment/enrollment_test_helper.cc",
     "policy/enrollment/enrollment_test_helper.h",
-    "policy/enrollment/flex_enrollment_token_provider_unittest.cc",
+    "policy/enrollment/enrollment_token_provider_unittest.cc",
     "policy/enrollment/psm/construct_rlwe_id_unittest.cc",
     "policy/enrollment/psm/rlwe_dmserver_client_impl_unittest.cc",
     "policy/enrollment/psm/rlwe_test_support_unittest.cc",
diff --git a/chrome/browser/ash/cert_provisioning/cert_provisioning_invalidator.cc b/chrome/browser/ash/cert_provisioning/cert_provisioning_invalidator.cc
index 895fef3..2996edad 100644
--- a/chrome/browser/ash/cert_provisioning/cert_provisioning_invalidator.cc
+++ b/chrome/browser/ash/cert_provisioning/cert_provisioning_invalidator.cc
@@ -146,8 +146,6 @@
       << "Incoming invalidation does not contain invalidation"
          " for certificate topic";
 
-  invalidation.Acknowledge();
-
   on_invalidation_event_callback_.Run(InvalidationEvent::kInvalidationReceived);
 }
 
diff --git a/chrome/browser/ash/cert_provisioning/cert_provisioning_invalidator_unittest.cc b/chrome/browser/ash/cert_provisioning/cert_provisioning_invalidator_unittest.cc
index d1bae4d..dfdd538 100644
--- a/chrome/browser/ash/cert_provisioning/cert_provisioning_invalidator_unittest.cc
+++ b/chrome/browser/ash/cert_provisioning/cert_provisioning_invalidator_unittest.cc
@@ -67,29 +67,24 @@
     return invalidation::Invalidation(topic, 42, "foo");
   }
 
+  // Will send invalidation to handler if `IsInvalidatorRegistered(topic) ==
+  // true`.
   invalidation::Invalidation FireInvalidation(
       const invalidation::Topic& topic) {
     const invalidation::Invalidation invalidation = CreateInvalidation(topic);
     invalidation_service_.EmitInvalidationForTest(invalidation);
-    base::RunLoop().RunUntilIdle();
     return invalidation;
   }
 
-  bool IsInvalidationSent(const invalidation::Invalidation& invalidation) {
-    return !invalidation_service_.GetFakeAckHandler()->IsUnsent(invalidation);
-  }
-
-  bool IsInvalidationAcknowledged(
-      const invalidation::Invalidation& invalidation) {
-    return invalidation_service_.GetFakeAckHandler()->IsAcknowledged(
-        invalidation);
-  }
-
   bool IsInvalidatorRegistered(
       CertProvisioningInvalidationHandler* invalidator) const {
-    return !invalidation_service_.invalidator_registrar()
-                .GetRegisteredTopics(invalidator)
-                .empty();
+    return invalidation_service_.HasObserver(invalidator);
+  }
+
+  bool IsInvalidatorRegistered(const invalidation::Topic& topic) {
+    return invalidation_service_.invalidator_registrar()
+        .GetRegisteredTopics(invalidation_handler_.get())
+        .contains(topic);
   }
 
   bool IsInvalidatorRegistered() const {
@@ -132,40 +127,20 @@
 
 TEST_P(CertProvisioningInvalidationHandlerTest,
        ShouldReceiveInvalidationForRegisteredTopic) {
-  EXPECT_TRUE(IsInvalidatorRegistered());
+  EXPECT_TRUE(IsInvalidatorRegistered(kInvalidatorTopic));
 
-  const auto invalidation = FireInvalidation(kInvalidatorTopic);
+  FireInvalidation(kInvalidatorTopic);
 
-  EXPECT_TRUE(IsInvalidationSent(invalidation));
-  EXPECT_TRUE(IsInvalidationAcknowledged(invalidation));
   EXPECT_EQ(invalidation_events_.Take(),
             InvalidationEvent::kInvalidationReceived);
 }
 
-TEST_P(CertProvisioningInvalidationHandlerTest,
-       ShouldNotReceiveInvalidationForDifferentTopic) {
-  EXPECT_TRUE(IsInvalidatorRegistered());
-
-  const auto invalidation = FireInvalidation(kSomeOtherTopic);
-
-  EXPECT_FALSE(IsInvalidationSent(invalidation));
-  EXPECT_FALSE(IsInvalidationAcknowledged(invalidation));
-  EXPECT_FALSE(invalidation_events_.IsReady());
-}
-
-TEST_P(CertProvisioningInvalidationHandlerTest,
-       ShouldNotReceiveInvalidationWhenUnregistered) {
-  EXPECT_TRUE(IsInvalidatorRegistered());
+TEST_P(CertProvisioningInvalidationHandlerTest, ShouldUnregister) {
+  EXPECT_TRUE(IsInvalidatorRegistered(kInvalidatorTopic));
 
   invalidation_handler_->Unregister();
 
   EXPECT_FALSE(IsInvalidatorRegistered());
-
-  const auto invalidation = FireInvalidation(kInvalidatorTopic);
-
-  EXPECT_FALSE(IsInvalidationSent(invalidation));
-  EXPECT_FALSE(IsInvalidationAcknowledged(invalidation));
-  EXPECT_FALSE(invalidation_events_.IsReady());
 }
 
 TEST_P(CertProvisioningInvalidationHandlerTest,
@@ -174,10 +149,8 @@
 
   invalidation_handler_.reset();
 
-  // Ensure that invalidator is unregistered and incoming invalidation does not
-  // cause undefined behaviour.
+  // Ensure that invalidator is unregistered.
   EXPECT_FALSE(IsInvalidatorRegistered());
-  FireInvalidation(kInvalidatorTopic);
   EXPECT_FALSE(invalidation_events_.IsReady());
 
   // Ensure that topic is still subscribed.
diff --git a/chrome/browser/ash/dbus/org.chromium.ChromeFeaturesService.conf b/chrome/browser/ash/dbus/org.chromium.ChromeFeaturesService.conf
index f6de0fc..2080f8ae 100644
--- a/chrome/browser/ash/dbus/org.chromium.ChromeFeaturesService.conf
+++ b/chrome/browser/ash/dbus/org.chromium.ChromeFeaturesService.conf
@@ -133,4 +133,11 @@
            send_member="IsFeatureEnabled"/>
   </policy>
 
+  <!-- limit regmond visibility to only IsFeatureEnabled. -->
+  <policy user="regmond">
+    <allow send_destination="org.chromium.ChromeFeaturesService"
+           send_interface="org.chromium.ChromeFeaturesServiceInterface"
+           send_member="IsFeatureEnabled"/>
+  </policy>
+
 </busconfig>
diff --git a/chrome/browser/ash/login/configuration_keys.cc b/chrome/browser/ash/login/configuration_keys.cc
index 0823dec0..7e24b43 100644
--- a/chrome/browser/ash/login/configuration_keys.cc
+++ b/chrome/browser/ash/login/configuration_keys.cc
@@ -91,8 +91,9 @@
 // values.
 const char kEnrollmentAutoAttributes[] = "enrollmentAutoAttributes";
 
-// String value, contains enrollment token used for Flex Auto Enrollment.
-const char kFlexToken[] = "flexToken";
+// String value, contains enrollment token (currently only used for Flex Auto
+// Enrollment).
+const char kEnrollmentToken[] = "enrollmentToken";
 
 using ValueType = base::Value::Type;
 
@@ -128,7 +129,8 @@
      ConfigurationHandlerSide::HANDLER_JS},
     {kArcTosAutoAccept, ValueType::BOOLEAN,
      ConfigurationHandlerSide::HANDLER_BOTH},
-    {kFlexToken, ValueType::STRING, ConfigurationHandlerSide::HANDLER_CPP},
+    {kEnrollmentToken, ValueType::STRING,
+     ConfigurationHandlerSide::HANDLER_CPP},
     {"desc", ValueType::STRING, ConfigurationHandlerSide::HANDLER_DOC},
     {"testValue", ValueType::STRING, ConfigurationHandlerSide::HANDLER_BOTH},
 };
diff --git a/chrome/browser/ash/login/configuration_keys.h b/chrome/browser/ash/login/configuration_keys.h
index 69f97c81..e221445 100644
--- a/chrome/browser/ash/login/configuration_keys.h
+++ b/chrome/browser/ash/login/configuration_keys.h
@@ -35,7 +35,7 @@
 extern const char kEnrollmentAssetId[];
 extern const char kEnrollmentLocation[];
 extern const char kEnrollmentAutoAttributes[];
-extern const char kFlexToken[];
+extern const char kEnrollmentToken[];
 
 enum class ConfigurationHandlerSide : unsigned int {
   HANDLER_JS,    // Handled by JS code
diff --git a/chrome/browser/ash/login/enrollment/enrollment_screen.cc b/chrome/browser/ash/login/enrollment/enrollment_screen.cc
index 5831319..1c1d627 100644
--- a/chrome/browser/ash/login/enrollment/enrollment_screen.cc
+++ b/chrome/browser/ash/login/enrollment/enrollment_screen.cc
@@ -35,7 +35,6 @@
 #include "chrome/browser/ash/policy/enrollment/enrollment_config.h"
 #include "chrome/browser/ash/policy/enrollment/enrollment_requisition_manager.h"
 #include "chrome/browser/ash/policy/enrollment/enrollment_status.h"
-#include "chrome/browser/ash/policy/enrollment/flex_enrollment_token_provider.h"
 #include "chrome/browser/ash/policy/handlers/tpm_auto_update_mode_policy_handler.h"
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/browser_process_platform_part.h"
diff --git a/chrome/browser/ash/login/enrollment/enrollment_screen_browsertest.cc b/chrome/browser/ash/login/enrollment/enrollment_screen_browsertest.cc
index b2a554e..10de3c6 100644
--- a/chrome/browser/ash/login/enrollment/enrollment_screen_browsertest.cc
+++ b/chrome/browser/ash/login/enrollment/enrollment_screen_browsertest.cc
@@ -11,6 +11,7 @@
 #include "base/command_line.h"
 #include "base/files/file_path.h"
 #include "base/files/file_util.h"
+#include "base/notreached.h"
 #include "base/path_service.h"
 #include "base/run_loop.h"
 #include "chrome/browser/ash/login/oobe_screen.h"
@@ -623,9 +624,7 @@
   bool IsManualEnrollmentMode(policy::EnrollmentConfig::Mode mode) const {
     switch (mode) {
       case policy::EnrollmentConfig::MODE_NONE:
-      case policy::EnrollmentConfig::DEPRECATED_MODE_ENROLLED_ROLLBACK:
-      case policy::EnrollmentConfig::DEPRECATED_MODE_OFFLINE_DEMO:
-        break;
+        NOTREACHED_NORETURN() << "Bad enrollment mode " << mode;
       case policy::EnrollmentConfig::MODE_MANUAL:
       case policy::EnrollmentConfig::MODE_MANUAL_REENROLLMENT:
       case policy::EnrollmentConfig::MODE_LOCAL_ADVERTISED:
@@ -649,9 +648,6 @@
           MODE_ENROLLMENT_TOKEN_INITIAL_MANUAL_FALLBACK:
         return false;
     }
-
-    NOTREACHED() << "Bad enrollment mode " << mode;
-    return true;
   }
 };
 
diff --git a/chrome/browser/ash/login/enrollment/enrollment_uma.cc b/chrome/browser/ash/login/enrollment/enrollment_uma.cc
index 48535f2..611a529 100644
--- a/chrome/browser/ash/login/enrollment/enrollment_uma.cc
+++ b/chrome/browser/ash/login/enrollment/enrollment_uma.cc
@@ -88,8 +88,6 @@
       base::UmaHistogramSparse(kMetricEnrollmentTokenBasedManualFallback,
                                sample);
       break;
-    case policy::EnrollmentConfig::DEPRECATED_MODE_ENROLLED_ROLLBACK:
-    case policy::EnrollmentConfig::DEPRECATED_MODE_OFFLINE_DEMO:
     case policy::EnrollmentConfig::MODE_NONE:
       NOTREACHED();
       break;
diff --git a/chrome/browser/ash/login/quickstart_controller.cc b/chrome/browser/ash/login/quickstart_controller.cc
index edbbd90..755a9ad 100644
--- a/chrome/browser/ash/login/quickstart_controller.cc
+++ b/chrome/browser/ash/login/quickstart_controller.cc
@@ -689,6 +689,7 @@
 
 void QuickStartController::StartAccountTransfer() {
   UpdateUiState(UiState::CONFIRM_GOOGLE_ACCOUNT);
+  QuickStartMetrics::RecordGaiaTransferStarted();
   bootstrap_controller_->RequestGoogleAccountInfo();
 }
 
diff --git a/chrome/browser/ash/login/startup_utils.cc b/chrome/browser/ash/login/startup_utils.cc
index 03633dd5..4ab45ed8 100644
--- a/chrome/browser/ash/login/startup_utils.cc
+++ b/chrome/browser/ash/login/startup_utils.cc
@@ -27,7 +27,7 @@
 #include "chrome/browser/ash/login/oobe_quick_start/oobe_quick_start_pref_names.h"
 #include "chrome/browser/ash/login/ui/login_display_host.h"
 #include "chrome/browser/ash/login/ui/login_display_host_common.h"
-#include "chrome/browser/ash/policy/enrollment/flex_enrollment_token_provider.h"
+#include "chrome/browser/ash/policy/enrollment/enrollment_token_provider.h"
 #include "chrome/browser/browser_process.h"
 #include "chrome/common/chrome_paths.h"
 #include "chrome/common/pref_names.h"
@@ -276,7 +276,7 @@
 
   ClearSpecificOobePrefs();
 
-  if (policy::GetFlexEnrollmentToken(OobeConfiguration::Get()).has_value()) {
+  if (policy::GetEnrollmentToken(OobeConfiguration::Get()).has_value()) {
     VLOG(0) << "Clearing Flex OOBE config after enrollment.";
     OobeConfigurationClient::Get()->DeleteFlexOobeConfig();
   }
diff --git a/chrome/browser/ash/login/wizard_controller.cc b/chrome/browser/ash/login/wizard_controller.cc
index c690552..6ba069b1 100644
--- a/chrome/browser/ash/login/wizard_controller.cc
+++ b/chrome/browser/ash/login/wizard_controller.cc
@@ -434,6 +434,12 @@
     obs.OnShutdown();
   }
 
+  if (GetOobeUI() && GetOobeUI()->GetOobeScreensHandlerFactory()) {
+      GetOobeUI()
+        ->GetOobeScreensHandlerFactory()
+        ->UnbindScreensHandlerFactory();
+  }
+
   previous_screens_.clear();
   screen_manager_.reset();
 }
diff --git a/chrome/browser/ash/login/wizard_controller_browsertest.cc b/chrome/browser/ash/login/wizard_controller_browsertest.cc
index b42fa312..5461f997 100644
--- a/chrome/browser/ash/login/wizard_controller_browsertest.cc
+++ b/chrome/browser/ash/login/wizard_controller_browsertest.cc
@@ -3133,7 +3133,7 @@
     command_line->AppendSwitch(ash::switches::kRevenBranding);
     base::FilePath configuration_file;
     ASSERT_TRUE(chromeos::test_utils::GetTestDataPath(
-        "oobe_configuration", "flex_enrollment_configuration.json",
+        "oobe_configuration", "enrollment_token_configuration.json",
         &configuration_file));
     command_line->AppendSwitchPath(chromeos::switches::kFakeOobeConfiguration,
                                    configuration_file);
diff --git a/chrome/browser/ash/policy/enrollment/auto_enrollment_client_impl.cc b/chrome/browser/ash/policy/enrollment/auto_enrollment_client_impl.cc
index 251cf9f3..ddf2a11e 100644
--- a/chrome/browser/ash/policy/enrollment/auto_enrollment_client_impl.cc
+++ b/chrome/browser/ash/policy/enrollment/auto_enrollment_client_impl.cc
@@ -24,7 +24,7 @@
 #include "base/values.h"
 #include "chrome/browser/ash/policy/enrollment/auto_enrollment_state.h"
 #include "chrome/browser/ash/policy/enrollment/auto_enrollment_state_message_processor.h"
-#include "chrome/browser/ash/policy/enrollment/flex_enrollment_token_provider.h"
+#include "chrome/browser/ash/policy/enrollment/enrollment_token_provider.h"
 #include "chrome/browser/ash/policy/enrollment/psm/rlwe_dmserver_client.h"
 #include "chrome/browser/ash/policy/server_backed_state/server_backed_device_state.h"
 #include "chrome/common/pref_names.h"
@@ -564,23 +564,23 @@
 
 // Stubbed out ServerStateAvailabilityRequester that always succeeds and
 // indicates that server state should be retrieved.
-class AutoEnrollmentClientImpl::FlexAutoEnrollmentStateAvailabilityRequester
+class AutoEnrollmentClientImpl::TokenBasedEnrollmentStateAvailabilityRequester
     : public ServerStateAvailabilityRequester {
  public:
-  explicit FlexAutoEnrollmentStateAvailabilityRequester(
-      std::optional<std::string> flex_enrollment_token,
+  explicit TokenBasedEnrollmentStateAvailabilityRequester(
+      std::optional<std::string> enrollment_token,
       PrefService* local_state)
-      : flex_enrollment_token_(std::move(flex_enrollment_token)),
+      : enrollment_token_(std::move(enrollment_token)),
         local_state_(local_state) {
     local_state_->SetInteger(
         prefs::kEnrollmentPsmResult,
         em::DeviceRegisterRequest::PSM_SKIPPED_FOR_FLEX_AUTO_ENROLLMENT);
     local_state_->SetBoolean(prefs::kShouldRetrieveDeviceState, true);
   }
-  FlexAutoEnrollmentStateAvailabilityRequester(
-      const FlexAutoEnrollmentStateAvailabilityRequester&) = delete;
-  FlexAutoEnrollmentStateAvailabilityRequester& operator=(
-      const FlexAutoEnrollmentStateAvailabilityRequester&) = delete;
+  TokenBasedEnrollmentStateAvailabilityRequester(
+      const TokenBasedEnrollmentStateAvailabilityRequester&) = delete;
+  TokenBasedEnrollmentStateAvailabilityRequester& operator=(
+      const TokenBasedEnrollmentStateAvailabilityRequester&) = delete;
 
   void Start(CompletionCallback callback) override {
     std::move(callback).Run(ServerStateAvailabilitySuccess::kSuccess);
@@ -588,14 +588,15 @@
 
   std::optional<bool> GetServerStateIfObtained() const override {
     // This should always return true (as this class _should_ only be
-    // instantiated after determining that a Flex token is present). Check the
-    // optional again anyways though for defensive programming purposes.
-    DCHECK(flex_enrollment_token_.has_value());
-    return flex_enrollment_token_.has_value();
+    // instantiated after determining that an enrollment token is present).
+    // Check the optional again anyways though for defensive programming
+    // purposes.
+    DCHECK(enrollment_token_.has_value());
+    return enrollment_token_.has_value();
   }
 
  private:
-  const std::optional<std::string> flex_enrollment_token_;
+  const std::optional<std::string> enrollment_token_;
   raw_ptr<PrefService> local_state_;
 };
 
@@ -796,12 +797,12 @@
     ash::OobeConfiguration* oobe_config) {
   std::unique_ptr<ServerStateAvailabilityRequester>
       server_state_availability_requester;
-  const std::optional<std::string> flex_enrollment_token =
-      GetFlexEnrollmentToken(oobe_config);
-  if (flex_enrollment_token.has_value()) {
+  const std::optional<std::string> enrollment_token =
+      GetEnrollmentToken(oobe_config);
+  if (enrollment_token.has_value()) {
     server_state_availability_requester =
-        std::make_unique<FlexAutoEnrollmentStateAvailabilityRequester>(
-            flex_enrollment_token, local_state);
+        std::make_unique<TokenBasedEnrollmentStateAvailabilityRequester>(
+            enrollment_token, local_state);
   } else {
     server_state_availability_requester =
         std::make_unique<InitialServerStateAvailabilityRequester>(
@@ -814,8 +815,7 @@
           /*device_id=*/base::Uuid::GenerateRandomV4().AsLowercaseString(),
           kUMASuffixInitialEnrollment,
           AutoEnrollmentStateMessageProcessor::CreateForInitialEnrollment(
-              device_serial_number, device_brand_code,
-              flex_enrollment_token))));
+              device_serial_number, device_brand_code, enrollment_token))));
 }
 
 // static
diff --git a/chrome/browser/ash/policy/enrollment/auto_enrollment_client_impl.h b/chrome/browser/ash/policy/enrollment/auto_enrollment_client_impl.h
index 7969a7d..33ec0ed 100644
--- a/chrome/browser/ash/policy/enrollment/auto_enrollment_client_impl.h
+++ b/chrome/browser/ash/policy/enrollment/auto_enrollment_client_impl.h
@@ -90,10 +90,10 @@
   // membership check requests for initial enrollment.
   class InitialServerStateAvailabilityRequester;
 
-  // Responsible for resolving availability status by checking Flex enrollment
+  // Responsible for resolving availability status by checking enrollment
   // token presence, to determine whether the device should retrieve server
-  // state for Flex Auto Enrollment.
-  class FlexAutoEnrollmentStateAvailabilityRequester;
+  // state for token-based initial enrollment.
+  class TokenBasedEnrollmentStateAvailabilityRequester;
 
   enum class ServerStateAvailabilitySuccess;
   using ServerStateAvailabilityResult =
diff --git a/chrome/browser/ash/policy/enrollment/auto_enrollment_type_checker.cc b/chrome/browser/ash/policy/enrollment/auto_enrollment_type_checker.cc
index 9f93f40..d6ff73e 100644
--- a/chrome/browser/ash/policy/enrollment/auto_enrollment_type_checker.cc
+++ b/chrome/browser/ash/policy/enrollment/auto_enrollment_type_checker.cc
@@ -466,10 +466,10 @@
   }
 
   if (IsOfficialGoogleFlex()) {
-    const std::string* flex_token =
+    const std::string* enrollment_token =
         ash::OobeConfiguration::Get()->configuration().FindString(
-            ash::configuration::kFlexToken);
-    if (!flex_token || flex_token->empty()) {
+            ash::configuration::kEnrollmentToken);
+    if (!enrollment_token || enrollment_token->empty()) {
       LOG(WARNING) << "Skipping Initial State Determination on Flex as no Flex "
                       "token was found.";
       return InitialStateDeterminationRequirement::kNotRequired;
diff --git a/chrome/browser/ash/policy/enrollment/auto_enrollment_type_checker_unittest.cc b/chrome/browser/ash/policy/enrollment/auto_enrollment_type_checker_unittest.cc
index 31bd4c39..5fb5c0cf 100644
--- a/chrome/browser/ash/policy/enrollment/auto_enrollment_type_checker_unittest.cc
+++ b/chrome/browser/ash/policy/enrollment/auto_enrollment_type_checker_unittest.cc
@@ -456,7 +456,7 @@
   // TODO(b/331285209): Change the JSON key to "enrollmentToken" along with the
   // key definition in configuration_keys.h.
   constexpr char kEmptyEnrollmentTokenOobeConfig[] = R"({
-    "flexToken": ""
+    "enrollmentToken": ""
   })";
   enrollment_test_helper_.SetUpEnrollmentTokenConfig(
       kEmptyEnrollmentTokenOobeConfig);
diff --git a/chrome/browser/ash/policy/enrollment/enrollment_config.cc b/chrome/browser/ash/policy/enrollment/enrollment_config.cc
index c35eb23..1b254d2 100644
--- a/chrome/browser/ash/policy/enrollment/enrollment_config.cc
+++ b/chrome/browser/ash/policy/enrollment/enrollment_config.cc
@@ -19,7 +19,7 @@
 #include "chrome/browser/ash/login/ui/login_display_host.h"
 #include "chrome/browser/ash/login/wizard_context.h"
 #include "chrome/browser/ash/policy/core/device_cloud_policy_manager_ash.h"
-#include "chrome/browser/ash/policy/enrollment/flex_enrollment_token_provider.h"
+#include "chrome/browser/ash/policy/enrollment/enrollment_token_provider.h"
 #include "chrome/browser/ash/policy/server_backed_state/server_backed_device_state.h"
 #include "chrome/browser/ash/settings/device_settings_service.h"
 #include "chrome/browser/browser_process.h"
@@ -96,8 +96,6 @@
     CASE(MODE_ATTESTATION_LOCAL_FORCED);
     CASE(MODE_ATTESTATION_SERVER_FORCED);
     CASE(MODE_ATTESTATION_MANUAL_FALLBACK);
-    CASE(DEPRECATED_MODE_OFFLINE_DEMO);
-    CASE(DEPRECATED_MODE_ENROLLED_ROLLBACK);
     CASE(MODE_INITIAL_SERVER_FORCED);
     CASE(MODE_ATTESTATION_INITIAL_SERVER_FORCED);
     CASE(MODE_ATTESTATION_INITIAL_MANUAL_FALLBACK);
@@ -262,15 +260,15 @@
   }
 
   if (device_state_mode == kDeviceStateInitialModeTokenEnrollment) {
-    std::optional<std::string> flex_enrollment_token =
-        GetFlexEnrollmentToken(oobe_configuration);
-    // TODO(b/329271128): CHECK to ensure flex_token always has value after this
-    // bug is fixed.
-    if (flex_enrollment_token.has_value()) {
+    std::optional<std::string> enrollment_token =
+        GetEnrollmentToken(oobe_configuration);
+    // TODO(b/329271128): CHECK to ensure enrollment_token always has value
+    // after this bug is fixed.
+    if (enrollment_token.has_value()) {
       return {
           .mode = EnrollmentConfig::MODE_ENROLLMENT_TOKEN_INITIAL_SERVER_FORCED,
           .auth_mechanism = EnrollmentConfig::AUTH_MECHANISM_TOKEN_PREFERRED,
-          .enrollment_token = std::move(flex_enrollment_token.value())};
+          .enrollment_token = std::move(enrollment_token.value())};
     } else {
       return {.mode = EnrollmentConfig::MODE_NONE,
               .auth_mechanism = GetPrescribedAuthMechanism(local_state)};
@@ -451,8 +449,6 @@
     case EnrollmentConfig::MODE_ATTESTATION:
     case EnrollmentConfig::MODE_ATTESTATION_LOCAL_FORCED:
     case EnrollmentConfig::MODE_ATTESTATION_MANUAL_FALLBACK:
-    case EnrollmentConfig::DEPRECATED_MODE_OFFLINE_DEMO:
-    case EnrollmentConfig::DEPRECATED_MODE_ENROLLED_ROLLBACK:
     case EnrollmentConfig::MODE_INITIAL_SERVER_FORCED:
     case EnrollmentConfig::MODE_ATTESTATION_INITIAL_MANUAL_FALLBACK:
     case EnrollmentConfig::MODE_ATTESTATION_ROLLBACK_MANUAL_FALLBACK:
diff --git a/chrome/browser/ash/policy/enrollment/enrollment_config.h b/chrome/browser/ash/policy/enrollment/enrollment_config.h
index 2f446db..9e78c990 100644
--- a/chrome/browser/ash/policy/enrollment/enrollment_config.h
+++ b/chrome/browser/ash/policy/enrollment/enrollment_config.h
@@ -66,15 +66,10 @@
     // Forced enrollment triggered as a fallback to attestation re-enrollment,
     // user can't skip.
     MODE_ATTESTATION_MANUAL_FALLBACK = 11,
-    // Deprecated. Demo mode does not support offline enrollment.
-    // Enrollment for offline demo mode with locally stored policy data.
-    DEPRECATED_MODE_OFFLINE_DEMO = 12,
-    // Deprecated. Flow that happens when already enrolled device undergoes
-    // version rollback. Enrollment information is preserved during rollback,
-    // but some steps have to be repeated as stateful partition was wiped.
-    DEPRECATED_MODE_ENROLLED_ROLLBACK = 13,
-    // Server-backed-state-triggered forced initial enrollment, user can't
-    // skip.
+
+    // DEPRECATED_MODE_OFFLINE_DEMO = 12,
+    // DEPRECATED_MODE_ENROLLED_ROLLBACK = 13,
+
     MODE_INITIAL_SERVER_FORCED = 14,
     // Server-backed-state-triggered attestation-based initial enrollment,
     // user can't skip.
diff --git a/chrome/browser/ash/policy/enrollment/enrollment_handler.cc b/chrome/browser/ash/policy/enrollment/enrollment_handler.cc
index 67274bcf3..bfdd2d8 100644
--- a/chrome/browser/ash/policy/enrollment/enrollment_handler.cc
+++ b/chrome/browser/ash/policy/enrollment/enrollment_handler.cc
@@ -13,6 +13,7 @@
 #include "base/functional/bind.h"
 #include "base/location.h"
 #include "base/logging.h"
+#include "base/notreached.h"
 #include "base/system/sys_info.h"
 #include "base/task/sequenced_task_runner.h"
 #include "base/task/single_thread_task_runner.h"
@@ -75,9 +76,7 @@
     EnrollmentConfig::Mode mode) {
   switch (mode) {
     case EnrollmentConfig::MODE_NONE:
-    case EnrollmentConfig::DEPRECATED_MODE_ENROLLED_ROLLBACK:
-    case EnrollmentConfig::DEPRECATED_MODE_OFFLINE_DEMO:
-      break;
+      NOTREACHED_NORETURN() << "Bad enrollment mode: " << mode;
     case EnrollmentConfig::MODE_MANUAL:
       return em::DeviceRegisterRequest::FLAVOR_ENROLLMENT_MANUAL;
     case EnrollmentConfig::MODE_MANUAL_REENROLLMENT:
@@ -124,9 +123,6 @@
       return em::DeviceRegisterRequest::
           FLAVOR_ENROLLMENT_TOKEN_INITIAL_MANUAL_FALLBACK;
   }
-
-  NOTREACHED() << "Bad enrollment mode: " << mode;
-  return em::DeviceRegisterRequest::FLAVOR_ENROLLMENT_MANUAL;
 }
 
 // Returns the PSM protocol execution result if prefs::kEnrollmentPsmResult is
diff --git a/chrome/browser/ash/policy/enrollment/enrollment_state_fetcher.cc b/chrome/browser/ash/policy/enrollment/enrollment_state_fetcher.cc
index 6795b2f..96777597 100644
--- a/chrome/browser/ash/policy/enrollment/enrollment_state_fetcher.cc
+++ b/chrome/browser/ash/policy/enrollment/enrollment_state_fetcher.cc
@@ -28,7 +28,7 @@
 #include "chrome/browser/ash/policy/core/browser_policy_connector_ash.h"
 #include "chrome/browser/ash/policy/enrollment/auto_enrollment_state.h"
 #include "chrome/browser/ash/policy/enrollment/auto_enrollment_type_checker.h"
-#include "chrome/browser/ash/policy/enrollment/flex_enrollment_token_provider.h"
+#include "chrome/browser/ash/policy/enrollment/enrollment_token_provider.h"
 #include "chrome/browser/ash/policy/server_backed_state/server_backed_device_state.h"
 #include "chrome/browser/ash/policy/server_backed_state/server_backed_state_keys_broker.h"
 #include "chrome/browser/ash/settings/device_settings_service.h"
@@ -897,7 +897,7 @@
                              device_management_service, url_loader_factory,
                              system_clock_client, state_key_broker,
                              device_settings_service,
-                             GetFlexEnrollmentToken(oobe_configuration)});
+                             GetEnrollmentToken(oobe_configuration)});
   }
 
   void Start() override;
diff --git a/chrome/browser/ash/policy/enrollment/enrollment_test_helper.cc b/chrome/browser/ash/policy/enrollment/enrollment_test_helper.cc
index 8996620..e3952e80 100644
--- a/chrome/browser/ash/policy/enrollment/enrollment_test_helper.cc
+++ b/chrome/browser/ash/policy/enrollment/enrollment_test_helper.cc
@@ -14,10 +14,8 @@
 namespace policy::test {
 
 const char kEnrollmentToken[] = "test_enrollment_token";
-// TODO(b/331285209): Change the JSON key to "enrollmentToken" along with the
-// key definition in configuration_keys.h.
 const char kEnrollmentTokenOobeConfig[] = R"({
-  "flexToken": "test_enrollment_token"
+  "enrollmentToken": "test_enrollment_token"
 })";
 
 EnrollmentTestHelper::EnrollmentTestHelper(
@@ -60,7 +58,7 @@
   // values from FakeOobeConfigurationClient.
   oobe_configuration_.CheckConfiguration();
   return oobe_configuration_.configuration().FindString(
-      ash::configuration::kFlexToken);
+      ash::configuration::kEnrollmentToken);
 }
 
 }  // namespace policy::test
diff --git a/chrome/browser/ash/policy/enrollment/flex_enrollment_token_provider.cc b/chrome/browser/ash/policy/enrollment/enrollment_token_provider.cc
similarity index 67%
rename from chrome/browser/ash/policy/enrollment/flex_enrollment_token_provider.cc
rename to chrome/browser/ash/policy/enrollment/enrollment_token_provider.cc
index d0bfdab..f30f6c8 100644
--- a/chrome/browser/ash/policy/enrollment/flex_enrollment_token_provider.cc
+++ b/chrome/browser/ash/policy/enrollment/enrollment_token_provider.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "chrome/browser/ash/policy/enrollment/flex_enrollment_token_provider.h"
+#include "chrome/browser/ash/policy/enrollment/enrollment_token_provider.h"
 
 #include "ash/constants/ash_switches.h"
 #include "base/logging.h"
@@ -12,7 +12,7 @@
 
 namespace policy {
 
-std::optional<std::string> GetFlexEnrollmentToken(
+std::optional<std::string> GetEnrollmentToken(
     const ash::OobeConfiguration* oobe_config) {
 #if BUILDFLAG(GOOGLE_CHROME_BRANDING)
   if (!ash::switches::IsRevenBranding()) {
@@ -24,10 +24,11 @@
     return std::nullopt;
   }
 
-  const std::string* flex_token =
-      oobe_config->configuration().FindString(ash::configuration::kFlexToken);
-  if (flex_token && !flex_token->empty()) {
-    return *flex_token;
+  const std::string* enrollment_token =
+      oobe_config->configuration().FindString(
+          ash::configuration::kEnrollmentToken);
+  if (enrollment_token && !enrollment_token->empty()) {
+    return *enrollment_token;
   }
 #endif
   return std::nullopt;
diff --git a/chrome/browser/ash/policy/enrollment/enrollment_token_provider.h b/chrome/browser/ash/policy/enrollment/enrollment_token_provider.h
new file mode 100644
index 0000000..0df1b37
--- /dev/null
+++ b/chrome/browser/ash/policy/enrollment/enrollment_token_provider.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_POLICY_ENROLLMENT_ENROLLMENT_TOKEN_PROVIDER_H_
+#define CHROME_BROWSER_ASH_POLICY_ENROLLMENT_ENROLLMENT_TOKEN_PROVIDER_H_
+
+#include <optional>
+#include <string>
+
+namespace ash {
+class OobeConfiguration;
+}  // namespace ash
+
+namespace policy {
+
+// Returns the enrollment token if the token is present in the OOBE config
+// and the device is a legitimate candidate for attempting token-based
+// enrollment on ChromeOS (as of writing only Flex devices use enrollment
+// tokens). Returns an empty optional otherwise.
+std::optional<std::string> GetEnrollmentToken(
+    const ash::OobeConfiguration* oobe_config);
+
+}  // namespace policy
+
+#endif  // CHROME_BROWSER_ASH_POLICY_ENROLLMENT_ENROLLMENT_TOKEN_PROVIDER_H_
diff --git a/chrome/browser/ash/policy/enrollment/flex_enrollment_token_provider_unittest.cc b/chrome/browser/ash/policy/enrollment/enrollment_token_provider_unittest.cc
similarity index 61%
rename from chrome/browser/ash/policy/enrollment/flex_enrollment_token_provider_unittest.cc
rename to chrome/browser/ash/policy/enrollment/enrollment_token_provider_unittest.cc
index cac908d..258fc62 100644
--- a/chrome/browser/ash/policy/enrollment/flex_enrollment_token_provider_unittest.cc
+++ b/chrome/browser/ash/policy/enrollment/enrollment_token_provider_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 "chrome/browser/ash/policy/enrollment/flex_enrollment_token_provider.h"
+#include "chrome/browser/ash/policy/enrollment/enrollment_token_provider.h"
 
 #include <optional>
 #include <string>
@@ -17,7 +17,7 @@
 
 namespace policy {
 
-class FlexEnrollmentTokenProviderTest : public testing::Test {
+class EnrollmentTokenProviderTest : public testing::Test {
  protected:
   base::test::ScopedCommandLine command_line_;
   ash::system::ScopedFakeStatisticsProvider statistics_provider_;
@@ -26,46 +26,46 @@
 };
 
 #if !BUILDFLAG(GOOGLE_CHROME_BRANDING)
-TEST_F(FlexEnrollmentTokenProviderTest, NotChromeBrandedReturnsEmptyOptional) {
+TEST_F(EnrollmentTokenProviderTest, NotChromeBrandedReturnsEmptyOptional) {
   enrollment_test_helper_.SetUpEnrollmentTokenConfig();
   enrollment_test_helper_.SetUpFlexDevice();
 
   ASSERT_FALSE(
-      GetFlexEnrollmentToken(enrollment_test_helper_.oobe_configuration())
+      GetEnrollmentToken(enrollment_test_helper_.oobe_configuration())
           .has_value());
 }
 #endif  // !BUILDFLAG(GOOGLE_CHROME_BRANDING)
 
 #if BUILDFLAG(GOOGLE_CHROME_BRANDING)
-TEST_F(FlexEnrollmentTokenProviderTest,
+TEST_F(EnrollmentTokenProviderTest,
        NoOobeConfigurationReturnsEmptyOptional) {
   enrollment_test_helper_.SetUpFlexDevice();
-  ASSERT_FALSE(GetFlexEnrollmentToken(nullptr).has_value());
+  ASSERT_FALSE(GetEnrollmentToken(nullptr).has_value());
 }
 
-TEST_F(FlexEnrollmentTokenProviderTest, NotOnFlexReturnsEmptyOptional) {
+TEST_F(EnrollmentTokenProviderTest, NotOnFlexReturnsEmptyOptional) {
   enrollment_test_helper_.SetUpEnrollmentTokenConfig();
   ASSERT_FALSE(
-      GetFlexEnrollmentToken(enrollment_test_helper_.oobe_configuration())
+      GetEnrollmentToken(enrollment_test_helper_.oobe_configuration())
           .has_value());
 }
 
-TEST_F(FlexEnrollmentTokenProviderTest, NoFlexTokenReturnsEmptyOptional) {
+TEST_F(EnrollmentTokenProviderTest, NoEnrollmentTokenReturnsEmptyOptional) {
   enrollment_test_helper_.SetUpFlexDevice();
   ASSERT_FALSE(
-      GetFlexEnrollmentToken(enrollment_test_helper_.oobe_configuration())
+      GetEnrollmentToken(enrollment_test_helper_.oobe_configuration())
           .has_value());
 }
 
-TEST_F(FlexEnrollmentTokenProviderTest, ReturnsTokenWhenSet) {
+TEST_F(EnrollmentTokenProviderTest, ReturnsTokenWhenSet) {
   enrollment_test_helper_.SetUpEnrollmentTokenConfig();
   enrollment_test_helper_.SetUpFlexDevice();
 
-  std::optional<std::string> flex_enrollment_token =
-      GetFlexEnrollmentToken(enrollment_test_helper_.oobe_configuration());
+  std::optional<std::string> enrollment_token =
+      GetEnrollmentToken(enrollment_test_helper_.oobe_configuration());
 
-  ASSERT_TRUE(flex_enrollment_token.has_value());
-  ASSERT_EQ(*flex_enrollment_token, test::kEnrollmentToken);
+  ASSERT_TRUE(enrollment_token.has_value());
+  ASSERT_EQ(*enrollment_token, test::kEnrollmentToken);
 }
 
 #endif  // BUILDFLAG(GOOGLE_CHROME_BRANDING)
diff --git a/chrome/browser/ash/policy/enrollment/flex_enrollment_token_provider.h b/chrome/browser/ash/policy/enrollment/flex_enrollment_token_provider.h
deleted file mode 100644
index 48dd4f7d..0000000
--- a/chrome/browser/ash/policy/enrollment/flex_enrollment_token_provider.h
+++ /dev/null
@@ -1,28 +0,0 @@
-// 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_POLICY_ENROLLMENT_FLEX_ENROLLMENT_TOKEN_PROVIDER_H_
-#define CHROME_BROWSER_ASH_POLICY_ENROLLMENT_FLEX_ENROLLMENT_TOKEN_PROVIDER_H_
-
-#include <optional>
-#include <string>
-
-namespace ash {
-class OobeConfiguration;
-}  // namespace ash
-
-namespace policy {
-
-// Returns the Flex enrollment token if the token is present in the OOBE config
-// and the device is a legitimate candidate for attempting Flex Auto Enrollment.
-// Returns an empty optional otherwise.
-//
-// TODO(b/331285209): Rename this method to GetEnrollmentToken as part of the
-// effort to dissociate "Flex" with enrollment tokens.
-std::optional<std::string> GetFlexEnrollmentToken(
-    const ash::OobeConfiguration* oobe_config);
-
-}  // namespace policy
-
-#endif  // CHROME_BROWSER_ASH_POLICY_ENROLLMENT_FLEX_ENROLLMENT_TOKEN_PROVIDER_H_
diff --git a/chrome/browser/ash/policy/networking/euicc_status_uploader.cc b/chrome/browser/ash/policy/networking/euicc_status_uploader.cc
index 043f011..3cab3ef3 100644
--- a/chrome/browser/ash/policy/networking/euicc_status_uploader.cc
+++ b/chrome/browser/ash/policy/networking/euicc_status_uploader.cc
@@ -32,8 +32,6 @@
 const char kLastUploadedEuiccStatusESimProfilesIccidKey[] = "iccid";
 const char kLastUploadedEuiccStatusESimProfilesNetworkNameKey[] =
     "network_name";
-const char kLastUploadedEuiccStatusESimProfilesSmdpAddressKey[] =
-    "smdp_address";
 const char kLastUploadedEuiccStatusESimProfilesSmdpActivationCodeKey[] =
     "smdp_activation_code";
 const char kLastUploadedEuiccStatusESimProfilesSmdsActivationCodeKey[] =
@@ -144,30 +142,25 @@
     esim_profile_info.set_iccid(*esim_profile_dict.FindString(
         kLastUploadedEuiccStatusESimProfilesIccidKey));
 
-    if (ash::features::IsSmdsSupportEnabled()) {
-      const std::string* network_name = esim_profile_dict.FindString(
-          kLastUploadedEuiccStatusESimProfilesNetworkNameKey);
-      if (network_name && !network_name->empty()) {
-        esim_profile_info.set_name(*network_name);
-      }
+    const std::string* network_name = esim_profile_dict.FindString(
+        kLastUploadedEuiccStatusESimProfilesNetworkNameKey);
+    if (network_name && !network_name->empty()) {
+      esim_profile_info.set_name(*network_name);
+    }
 
-      const std::string* smdp_activation_code = esim_profile_dict.FindString(
-          kLastUploadedEuiccStatusESimProfilesSmdpActivationCodeKey);
-      const std::string* smds_activation_code = esim_profile_dict.FindString(
-          kLastUploadedEuiccStatusESimProfilesSmdsActivationCodeKey);
+    const std::string* smdp_activation_code = esim_profile_dict.FindString(
+        kLastUploadedEuiccStatusESimProfilesSmdpActivationCodeKey);
+    const std::string* smds_activation_code = esim_profile_dict.FindString(
+        kLastUploadedEuiccStatusESimProfilesSmdsActivationCodeKey);
 
-      if (smdp_activation_code && !smdp_activation_code->empty()) {
-        esim_profile_info.set_smdp_address(*smdp_activation_code);
-      } else if (smds_activation_code && !smds_activation_code->empty()) {
-        esim_profile_info.set_smds_address(*smds_activation_code);
-      } else {
-        NET_LOG(ERROR) << "Failed to find an activation code when constructing "
-                          "EUICC upload request";
-        continue;
-      }
+    if (smdp_activation_code && !smdp_activation_code->empty()) {
+      esim_profile_info.set_smdp_address(*smdp_activation_code);
+    } else if (smds_activation_code && !smds_activation_code->empty()) {
+      esim_profile_info.set_smds_address(*smds_activation_code);
     } else {
-      esim_profile_info.set_smdp_address(*esim_profile_dict.FindString(
-          kLastUploadedEuiccStatusESimProfilesSmdpAddressKey));
+      NET_LOG(ERROR) << "Failed to find an activation code when constructing "
+                        "EUICC upload request";
+      continue;
     }
 
     mutable_esim_profiles->Add(std::move(esim_profile_info));
@@ -233,67 +226,48 @@
       continue;
     }
 
-    if (ash::features::IsSmdsSupportEnabled()) {
-      const base::Value::Dict* esim_metadata =
-          ash::NetworkHandler::Get()
-              ->managed_cellular_pref_handler()
-              ->GetESimMetadata(esim_profile.iccid());
+    const base::Value::Dict* esim_metadata =
+        ash::NetworkHandler::Get()
+            ->managed_cellular_pref_handler()
+            ->GetESimMetadata(esim_profile.iccid());
 
-      // Report only managed profiles that we have metadata for.
-      if (!esim_metadata) {
-        continue;
-      }
-
-      base::Value::Dict esim_profile_to_add;
-      esim_profile_to_add.Set(kLastUploadedEuiccStatusESimProfilesIccidKey,
-                              esim_profile.iccid());
-
-      const std::string* const smdp_activation_code =
-          esim_metadata->FindString(::onc::cellular::kSMDPAddress);
-      const std::string* const smds_activation_code =
-          esim_metadata->FindString(::onc::cellular::kSMDSAddress);
-
-      if (smdp_activation_code && !smdp_activation_code->empty()) {
-        esim_profile_to_add.Set(
-            kLastUploadedEuiccStatusESimProfilesSmdpActivationCodeKey,
-            *smdp_activation_code);
-      } else if (smds_activation_code && !smds_activation_code->empty()) {
-        esim_profile_to_add.Set(
-            kLastUploadedEuiccStatusESimProfilesSmdsActivationCodeKey,
-            *smds_activation_code);
-      } else {
-        // Report only managed profiles that we have an activation code for.
-        NET_LOG(ERROR) << "Failed to find an SM-DP+ or SM-DS activation code "
-                       << "in the eSIM metadata, skipping entry";
-        continue;
-      }
-
-      const std::string* network_name =
-          esim_metadata->FindString(::onc::network_config::kName);
-      if (network_name && !network_name->empty()) {
-        esim_profile_to_add.Set(
-            kLastUploadedEuiccStatusESimProfilesNetworkNameKey, *network_name);
-      }
-
-      esim_profiles.Append(std::move(esim_profile_to_add));
-    } else {
-      const std::string* smdp_address =
-          ash::NetworkHandler::Get()
-              ->managed_cellular_pref_handler()
-              ->GetSmdpAddressFromIccid(esim_profile.iccid());
-      // Report only managed profiles with a SMDP address.
-      if (!smdp_address) {
-        continue;
-      }
-
-      auto esim_profile_to_add =
-          base::Value::Dict()
-              .Set(kLastUploadedEuiccStatusESimProfilesIccidKey,
-                   esim_profile.iccid())
-              .Set(kLastUploadedEuiccStatusESimProfilesSmdpAddressKey,
-                   *smdp_address);
-      esim_profiles.Append(std::move(esim_profile_to_add));
+    // Report only managed profiles that we have metadata for.
+    if (!esim_metadata) {
+      continue;
     }
+
+    base::Value::Dict esim_profile_to_add;
+    esim_profile_to_add.Set(kLastUploadedEuiccStatusESimProfilesIccidKey,
+                            esim_profile.iccid());
+
+    const std::string* const smdp_activation_code =
+        esim_metadata->FindString(::onc::cellular::kSMDPAddress);
+    const std::string* const smds_activation_code =
+        esim_metadata->FindString(::onc::cellular::kSMDSAddress);
+
+    if (smdp_activation_code && !smdp_activation_code->empty()) {
+      esim_profile_to_add.Set(
+          kLastUploadedEuiccStatusESimProfilesSmdpActivationCodeKey,
+          *smdp_activation_code);
+    } else if (smds_activation_code && !smds_activation_code->empty()) {
+      esim_profile_to_add.Set(
+          kLastUploadedEuiccStatusESimProfilesSmdsActivationCodeKey,
+          *smds_activation_code);
+    } else {
+      // Report only managed profiles that we have an activation code for.
+      NET_LOG(ERROR) << "Failed to find an SM-DP+ or SM-DS activation code "
+                     << "in the eSIM metadata, skipping entry";
+      continue;
+    }
+
+    const std::string* network_name =
+        esim_metadata->FindString(::onc::network_config::kName);
+    if (network_name && !network_name->empty()) {
+      esim_profile_to_add.Set(
+          kLastUploadedEuiccStatusESimProfilesNetworkNameKey, *network_name);
+    }
+
+    esim_profiles.Append(std::move(esim_profile_to_add));
   }
 
   status.SetByDottedPath(kLastUploadedEuiccStatusESimProfilesKey,
diff --git a/chrome/browser/ash/policy/networking/euicc_status_uploader_unittest.cc b/chrome/browser/ash/policy/networking/euicc_status_uploader_unittest.cc
index 3e9ceaf..7cbf8e48 100644
--- a/chrome/browser/ash/policy/networking/euicc_status_uploader_unittest.cc
+++ b/chrome/browser/ash/policy/networking/euicc_status_uploader_unittest.cc
@@ -378,7 +378,6 @@
   }
 
   void SetLastUploadedValue(const std::string& last_value) {
-    DCHECK(ash::features::IsSmdsSupportEnabled());
     local_state_.Set(EuiccStatusUploader::kLastUploadedEuiccStatusPref,
                      base::test::ParseJson(last_value));
   }
diff --git a/chrome/browser/ash/printing/printers_sync_bridge.cc b/chrome/browser/ash/printing/printers_sync_bridge.cc
index 8c99911..15bce640 100644
--- a/chrome/browser/ash/printing/printers_sync_bridge.cc
+++ b/chrome/browser/ash/printing/printers_sync_bridge.cc
@@ -14,6 +14,7 @@
 #include "base/trace_event/trace_event.h"
 #include "chrome/browser/ash/printing/specifics_translation.h"
 #include "chromeos/printing/printer_configuration.h"
+#include "components/sync/base/deletion_origin.h"
 #include "components/sync/base/report_unrecoverable_error.h"
 #include "components/sync/model/client_tag_based_model_type_processor.h"
 #include "components/sync/model/model_type_change_processor.h"
@@ -397,7 +398,8 @@
   }
 
   if (change_processor()->IsTrackingMetadata()) {
-    change_processor()->Delete(id, batch->GetMetadataChangeList());
+    change_processor()->Delete(id, syncer::DeletionOrigin::Unspecified(),
+                               batch->GetMetadataChangeList());
   }
   store_delegate_->Commit(std::move(batch));
 
diff --git a/chrome/browser/ash/system/tray_accessibility_browsertest.cc b/chrome/browser/ash/system/tray_accessibility_browsertest.cc
index f8e31c8..aec2902c 100644
--- a/chrome/browser/ash/system/tray_accessibility_browsertest.cc
+++ b/chrome/browser/ash/system/tray_accessibility_browsertest.cc
@@ -152,8 +152,7 @@
   TrayAccessibilityTest()
       : disable_animations_(
             ui::ScopedAnimationDurationScaleMode::ZERO_DURATION) {
-    scoped_feature_list_.InitWithFeatures(
-        {media::kLiveCaption, media::kLiveCaptionSystemWideOnChromeOS}, {});
+    scoped_feature_list_.InitWithFeatures({media::kLiveCaption}, {});
   }
   ~TrayAccessibilityTest() override = default;
 
diff --git a/chrome/browser/chrome_browser_application_mac.mm b/chrome/browser/chrome_browser_application_mac.mm
index 169ed5e7..25972e1 100644
--- a/chrome/browser/chrome_browser_application_mac.mm
+++ b/chrome/browser/chrome_browser_application_mac.mm
@@ -148,6 +148,9 @@
 }
 
 + (void)initialize {
+  if (self != [BrowserCrApplication class]) {
+    return;
+  }
   // Turn all deallocated Objective-C objects into zombies, keeping
   // the most recent 10,000 of them on the treadmill.
   ObjcEvilDoers::ZombieEnable(true, 10000);
diff --git a/chrome/browser/chrome_content_browser_client.cc b/chrome/browser/chrome_content_browser_client.cc
index d4e5507..edcddff 100644
--- a/chrome/browser/chrome_content_browser_client.cc
+++ b/chrome/browser/chrome_content_browser_client.cc
@@ -3403,8 +3403,10 @@
   auto* privacy_sandbox_settings =
       PrivacySandboxSettingsFactory::GetForProfile(profile);
   DCHECK(privacy_sandbox_settings);
+  // TODO(335839125): Plumb in a non-null `out_block_is_site_setting_specific`.
   bool allowed = privacy_sandbox_settings->IsSharedStorageAllowed(
-      top_frame_origin, accessing_origin, out_debug_message, rfh);
+      top_frame_origin, accessing_origin, out_debug_message, rfh,
+      /*out_block_is_site_setting_specific=*/nullptr);
   if (rfh) {
     content_settings::PageSpecificContentSettings::BrowsingDataAccessed(
         rfh, blink::StorageKey::CreateFirstParty(accessing_origin),
@@ -3422,8 +3424,10 @@
   auto* privacy_sandbox_settings =
       PrivacySandboxSettingsFactory::GetForProfile(profile);
   DCHECK(privacy_sandbox_settings);
+  // TODO(335839125): Plumb in a non-null `out_block_is_site_setting_specific`.
   return privacy_sandbox_settings->IsSharedStorageSelectURLAllowed(
-      top_frame_origin, accessing_origin, out_debug_message);
+      top_frame_origin, accessing_origin, out_debug_message,
+      /*out_block_is_site_setting_specific=*/nullptr);
 }
 
 bool ChromeContentBrowserClient::IsPrivateAggregationAllowed(
@@ -3435,8 +3439,10 @@
       PrivacySandboxSettingsFactory::GetForProfile(profile);
   DCHECK(privacy_sandbox_settings);
 
+  // TODO(335839125): Plumb in a non-null `out_block_is_site_setting_specific`.
   return privacy_sandbox_settings->IsPrivateAggregationAllowed(
-      top_frame_origin, reporting_origin);
+      top_frame_origin, reporting_origin,
+      /*out_block_is_site_setting_specific=*/nullptr);
 }
 
 bool ChromeContentBrowserClient::IsPrivateAggregationDebugModeAllowed(
diff --git a/chrome/browser/commerce/price_tracking/android/javatests/src/org/chromium/chrome/browser/price_tracking/PriceTrackingButtonControllerUnitTest.java b/chrome/browser/commerce/price_tracking/android/javatests/src/org/chromium/chrome/browser/price_tracking/PriceTrackingButtonControllerUnitTest.java
index 3ad97b6..18efb8f7 100644
--- a/chrome/browser/commerce/price_tracking/android/javatests/src/org/chromium/chrome/browser/price_tracking/PriceTrackingButtonControllerUnitTest.java
+++ b/chrome/browser/commerce/price_tracking/android/javatests/src/org/chromium/chrome/browser/price_tracking/PriceTrackingButtonControllerUnitTest.java
@@ -46,10 +46,7 @@
 /** Unit test for {@link PriceTrackingButtonController}. */
 @RunWith(BaseRobolectricTestRunner.class)
 @Config(manifest = Config.NONE)
-@EnableFeatures({
-    ChromeFeatureList.CONTEXTUAL_PAGE_ACTION_PRICE_TRACKING,
-    ChromeFeatureList.ADAPTIVE_BUTTON_IN_TOP_TOOLBAR_CUSTOMIZATION_V2
-})
+@EnableFeatures({ChromeFeatureList.ADAPTIVE_BUTTON_IN_TOP_TOOLBAR_CUSTOMIZATION_V2})
 public class PriceTrackingButtonControllerUnitTest {
     @Rule public TestRule mFeaturesProcessor = new Features.JUnitProcessor();
 
diff --git a/chrome/browser/compose/chrome_compose_client.cc b/chrome/browser/compose/chrome_compose_client.cc
index 5a558ea..6410b47 100644
--- a/chrome/browser/compose/chrome_compose_client.cc
+++ b/chrome/browser/compose/chrome_compose_client.cc
@@ -295,8 +295,14 @@
   bool has_session = HasSession(active_compose_ids_.value().first);
   bool resume_current_session =
       has_session &&
-      (ui_entry_point == ChromeComposeClient::EntryPoint::kAutofillPopup ||
-       selected_text.empty());
+      (ui_entry_point == EntryPoint::kAutofillPopup || selected_text.empty());
+
+  if (!has_session && ui_entry_point == EntryPoint::kAutofillPopup) {
+    // If this is a new session from the popup then the proactive nudge was
+    // clicked. Record nudge ctr metric.
+    compose::LogComposeProactiveNudgeCtr(
+        compose::ComposeProactiveNudgeCtrEvent::kDialogOpened);
+  }
 
   if (resume_current_session) {
     auto it = sessions_.find(active_compose_ids_.value().first);
@@ -512,6 +518,11 @@
     page_ukm_tracker_->ComposeProactiveNudgeShouldShow();
   }
 
+  if (!ongoing_session && should_show_nudge.has_value()) {
+    compose::LogComposeProactiveNudgeCtr(
+        compose::ComposeProactiveNudgeCtrEvent::kNudgeDisplayed);
+  }
+
   return should_show_nudge.has_value();
 }
 
diff --git a/chrome/browser/compose/chrome_compose_client_unittest.cc b/chrome/browser/compose/chrome_compose_client_unittest.cc
index 94d68d42..c7e2fd9 100644
--- a/chrome/browser/compose/chrome_compose_client_unittest.cc
+++ b/chrome/browser/compose/chrome_compose_client_unittest.cc
@@ -814,6 +814,11 @@
           testing::Pair(
               ukm::builders::Compose_PageEvents::kProactiveNudgeShouldShowName,
               1)));
+
+  // Check Compose.ProactiveNudge.CTR metrics.
+  histograms().ExpectBucketCount(
+      compose::kComposeProactiveNudgeCtr,
+      compose::ComposeProactiveNudgeCtrEvent::kNudgeDisplayed, 1);
 }
 
 TEST_F(ChromeComposeClientTest,
@@ -1523,6 +1528,11 @@
   histograms().ExpectBucketCount(
       compose::kComposeSessionEventCounts,
       compose::ComposeSessionEventTypes::kInsertClicked, 1);
+
+  // Check Compose.ProactiveNudge.CTR metrics.
+  histograms().ExpectBucketCount(
+      compose::kComposeProactiveNudgeCtr,
+      compose::ComposeProactiveNudgeCtrEvent::kDialogOpened, 1);
 }
 
 // Test that opening the saved state dialog with selected text does not start
diff --git a/chrome/browser/compose/compose_enabling.cc b/chrome/browser/compose/compose_enabling.cc
index e90f38a7..7761b1f 100644
--- a/chrome/browser/compose/compose_enabling.cc
+++ b/chrome/browser/compose/compose_enabling.cc
@@ -159,7 +159,8 @@
   // Check that the feature flag is enabled.
   if (!base::FeatureList::IsEnabled(compose::features::kEnableCompose)) {
     DVLOG(2) << "feature not enabled ";
-    return base::unexpected(compose::ComposeShowStatus::kFeatureFlagDisabled);
+    return base::unexpected(
+        compose::ComposeShowStatus::kComposeFeatureFlagDisabled);
   }
 
   // Check signin status.
@@ -207,12 +208,38 @@
   if (ongoing_session) {
     return ShouldTriggerSavedStatePopup(trigger_source);
   }
-  return ShouldTriggerNoStatePopup(autocomplete_attribute, profile, prefs,
-                                   translate_manager, top_level_frame_origin,
-                                   element_frame_origin, url);
+
+  base::expected<void, compose::ComposeShowStatus> show_status =
+      ShouldTriggerNoStatePopup(autocomplete_attribute, profile, prefs,
+                                translate_manager, top_level_frame_origin,
+                                element_frame_origin, url);
+  if (show_status.has_value()) {
+    compose::LogComposeProactiveNudgeShowStatus(
+        compose::ComposeShowStatus::kShouldShow);
+    return base::ok();
+  }
+
+  compose::LogComposeProactiveNudgeShowStatus(show_status.error());
+  switch (show_status.error()) {
+    case compose::ComposeShowStatus::
+        kPractiveNudgeDisabledGloballyByUserPreference:
+    case compose::ComposeShowStatus::
+        kPractiveNudgeDisabledForSiteByUserPreference:
+    case compose::ComposeShowStatus::kProactiveNudgeFeatureDisabled:
+    case compose::ComposeShowStatus::kRandomlyBlocked:
+      // The disabled deny reason means the nudge would show if preference or
+      // configuration changed.
+      return base::unexpected(
+          compose::ComposeNudgeDenyReason::kProactiveNudgeDisabled);
+    default:
+      // The blocked deny reason means the nudge would never display even if
+      // preferences or configuration changed.
+      return base::unexpected(
+          compose::ComposeNudgeDenyReason::kProactiveNudgeBlocked);
+  }
 }
 
-base::expected<void, compose::ComposeNudgeDenyReason>
+base::expected<void, compose::ComposeShowStatus>
 ComposeEnabling::ShouldTriggerNoStatePopup(
     std::string_view autocomplete_attribute,
     Profile* profile,
@@ -224,29 +251,29 @@
   // TODO(b/319661274): Support fenced frame checks from the Autofill popup
   // entry point.
   bool is_in_fenced_frame = false;
-  if (!PageLevelChecks(translate_manager, url, top_level_frame_origin,
-                       element_frame_origin, is_in_fenced_frame)
-           .has_value()) {
-    return base::unexpected(compose::ComposeNudgeDenyReason::kPageLevelChecks);
+  if (auto page_checks =
+          PageLevelChecks(translate_manager, url, top_level_frame_origin,
+                          element_frame_origin, is_in_fenced_frame);
+      !page_checks.has_value()) {
+    return base::unexpected(page_checks.error());
   }
 
   // Check URL with Optimization guide.
   switch (GetOptimizationGuidanceForUrl(url, profile)) {
     case compose::ComposeHintDecision::COMPOSE_HINT_DECISION_COMPOSE_DISABLED:
-      return base::unexpected(
-          compose::ComposeNudgeDenyReason::kOptimizationGuideChecks);
+      return base::unexpected(compose::ComposeShowStatus::kPerUrlChecksFailed);
     case compose::ComposeHintDecision::COMPOSE_HINT_DECISION_DISABLE_NUDGE:
-      if (compose::GetComposeConfig()
-              .proactive_nudge_bypass_optimization_guide) {
-        break;
+      if (!compose::GetComposeConfig()
+               .proactive_nudge_bypass_optimization_guide) {
+        return base::unexpected(
+            compose::ComposeShowStatus::kPractiveNudgeDisabledByServerConfig);
       }
-      return base::unexpected(
-          compose::ComposeNudgeDenyReason::kOptimizationGuideChecks);
+      break;
     case compose::ComposeHintDecision::COMPOSE_HINT_DECISION_UNSPECIFIED:
       if (!base::FeatureList::IsEnabled(
               compose::features::kEnableNudgeForUnspecifiedHint)) {
         return base::unexpected(
-            compose::ComposeNudgeDenyReason::kOptimizationGuideChecks);
+            compose::ComposeShowStatus::kPractiveNudgeUnknownServerConfig);
       }
       break;
     case compose::ComposeHintDecision::COMPOSE_HINT_DECISION_ENABLED:
@@ -257,17 +284,17 @@
   // TODO(b/303288183): Decide if we should keep this check or not.
   if (!AutocompleteAllowed(autocomplete_attribute)) {
     DVLOG(2) << "autocomplete=off";
-    return base::unexpected(compose::ComposeNudgeDenyReason::kDOMLevelChecks);
+    return base::unexpected(compose::ComposeShowStatus::kAutocompleteOff);
   }
 
   if (!prefs->GetBoolean(prefs::kEnableProactiveNudge)) {
-    return base::unexpected(compose::ComposeNudgeDenyReason::
-                                kProactiveNudgeDisabledByGlobalPreference);
+    return base::unexpected(compose::ComposeShowStatus::
+                                kPractiveNudgeDisabledGloballyByUserPreference);
   }
 
   if (!compose::GetComposeConfig().proactive_nudge_enabled) {
     return base::unexpected(
-        compose::ComposeNudgeDenyReason::kProactiveNudgeDisabled);
+        compose::ComposeShowStatus::kProactiveNudgeFeatureDisabled);
   }
 
   if (base::RandDouble() <
@@ -275,7 +302,7 @@
     return base::ok();
   }
 
-  return base::unexpected(compose::ComposeNudgeDenyReason::kRandomlyBlocked);
+  return base::unexpected(compose::ComposeShowStatus::kRandomlyBlocked);
 }
 
 base::expected<void, compose::ComposeNudgeDenyReason>
diff --git a/chrome/browser/compose/compose_enabling.h b/chrome/browser/compose/compose_enabling.h
index 297a1186..d206137 100644
--- a/chrome/browser/compose/compose_enabling.h
+++ b/chrome/browser/compose/compose_enabling.h
@@ -27,12 +27,11 @@
 enum class ComposeNudgeDenyReason {
   kSavedStateNotificationDisabled = 0,
   kSavedStateNudgeDisabled = 1,
+  // The proactive nudge could have shown but was disabled by preference or
+  // config values.
   kProactiveNudgeDisabled = 2,
-  kOptimizationGuideChecks = 3,
-  kDOMLevelChecks = 4,
-  kPageLevelChecks = 5,
-  kProactiveNudgeDisabledByGlobalPreference = 6,
-  kRandomlyBlocked = 7,
+  // The proactive nudge can not be shown for this user, page, or field.
+  kProactiveNudgeBlocked = 3,
 };
 
 }  // namespace compose
@@ -94,14 +93,14 @@
       const url::Origin& element_frame_origin,
       bool is_newsted_within_fenced_frame);
 
-  base::expected<void, compose::ComposeNudgeDenyReason>
-  ShouldTriggerNoStatePopup(std::string_view autocomplete_attribute,
-                            Profile* profile,
-                            PrefService* prefs,
-                            translate::TranslateManager* translate_manager,
-                            const url::Origin& top_level_frame_origin,
-                            const url::Origin& element_frame_origin,
-                            GURL url);
+  base::expected<void, compose::ComposeShowStatus> ShouldTriggerNoStatePopup(
+      std::string_view autocomplete_attribute,
+      Profile* profile,
+      PrefService* prefs,
+      translate::TranslateManager* translate_manager,
+      const url::Origin& top_level_frame_origin,
+      const url::Origin& element_frame_origin,
+      GURL url);
   base::expected<void, compose::ComposeNudgeDenyReason>
   ShouldTriggerSavedStatePopup(
       autofill::AutofillSuggestionTriggerSource trigger_source);
diff --git a/chrome/browser/compose/compose_enabling_unittest.cc b/chrome/browser/compose/compose_enabling_unittest.cc
index b60988d4..425f024 100644
--- a/chrome/browser/compose/compose_enabling_unittest.cc
+++ b/chrome/browser/compose/compose_enabling_unittest.cc
@@ -262,13 +262,29 @@
   SignIn(signin::ConsentLevel::kSync);
 
   CheckIsEnabledError(compose_enabling_.get(),
-                      compose::ComposeShowStatus::kFeatureFlagDisabled);
+                      compose::ComposeShowStatus::kComposeFeatureFlagDisabled);
 }
 
 TEST_F(ComposeEnablingTest, NotSignedInTest) {
+  base::HistogramTester histogram_tester;
   // Intentionally skip the signin step.
   CheckIsEnabledError(compose_enabling_.get(),
                       compose::ComposeShowStatus::kSignedOut);
+
+  std::string autocomplete_attribute;
+  // Check that the proactive nudge does not show.
+  EXPECT_FALSE(
+      compose_enabling_
+          ->ShouldTriggerPopup(
+              autocomplete_attribute, GetProfile(), GetProfile()->GetPrefs(),
+              mock_translate_manager_.get(),
+              /*ongoing_session=*/false, GetOrigin(), GetOrigin(),
+              GURL(kExampleURL),
+              autofill::AutofillSuggestionTriggerSource::kTextFieldDidChange)
+          .has_value());
+
+  histogram_tester.ExpectBucketCount(compose::kComposeProactiveNudgeShowStatus,
+                                     compose::ComposeShowStatus::kSignedOut, 1);
 }
 
 TEST_F(ComposeEnablingTest, SignedInErrorTest) {
@@ -563,6 +579,7 @@
 
 TEST_F(ComposeEnablingTest, ShouldNotTriggerProactivePopupAutocompleteOffTest) {
   ResetFeaturesAndConfig({compose::features::kEnableComposeProactiveNudge}, {});
+  base::HistogramTester histogram_tester;
   // Enable everything.
   auto scoped_compose_enabled =
       ComposeEnabling::ScopedEnableComposeForTesting();
@@ -590,6 +607,10 @@
               GURL(kExampleURL),
               autofill::AutofillSuggestionTriggerSource::kTextFieldDidChange)
           .has_value());
+
+  histogram_tester.ExpectBucketCount(
+      compose::kComposeProactiveNudgeShowStatus,
+      compose::ComposeShowStatus::kAutocompleteOff, 1);
 }
 
 TEST_F(ComposeEnablingTest, ShouldTriggerPopupWithSavedStateTest) {
@@ -710,6 +731,7 @@
 }
 
 TEST_F(ComposeEnablingTest, ShouldTriggerPopupIncorrectSchemeTest) {
+  base::HistogramTester histogram_tester;
   // Enable everything.
   auto scoped_compose_enabled =
       ComposeEnabling::ScopedEnableComposeForTesting();
@@ -739,6 +761,10 @@
               GURL(kExampleBadURL),
               autofill::AutofillSuggestionTriggerSource::kTextFieldDidChange)
           .has_value());
+
+  histogram_tester.ExpectBucketCount(
+      compose::kComposeProactiveNudgeShowStatus,
+      compose::ComposeShowStatus::kIncorrectScheme, 1);
 }
 
 TEST_F(ComposeEnablingTest, ShouldTriggerPopupCrossOrigin) {
@@ -993,6 +1019,7 @@
 
 TEST_F(ComposeEnablingTest, ProactiveNudgePreferenceTest) {
   ResetFeaturesAndConfig({compose::features::kEnableComposeProactiveNudge}, {});
+  base::HistogramTester histogram_tester;
   // Enable the feature.
   auto scoped_compose_enabled =
       ComposeEnabling::ScopedEnableComposeForTesting();
@@ -1020,10 +1047,19 @@
               GURL(kExampleURL),
               autofill::AutofillSuggestionTriggerSource::kTextFieldDidChange)
           .has_value());
+  histogram_tester.ExpectBucketCount(compose::kComposeProactiveNudgeShowStatus,
+                                     compose::ComposeShowStatus::kShouldShow,
+                                     1);
+  histogram_tester.ExpectBucketCount(
+      compose::kComposeProactiveNudgeShowStatus,
+      compose::ComposeShowStatus::
+          kPractiveNudgeDisabledGloballyByUserPreference,
+      1);
 }
 
 TEST_F(ComposeEnablingTest, ProactiveNudgeDisabledByRandomness) {
   ResetFeaturesAndConfig({compose::features::kEnableComposeProactiveNudge}, {});
+  base::HistogramTester histogram_tester;
   // Enable the feature.
   auto scoped_compose_enabled =
       ComposeEnabling::ScopedEnableComposeForTesting();
@@ -1049,4 +1085,8 @@
               GURL(kExampleURL),
               autofill::AutofillSuggestionTriggerSource::kTextFieldDidChange)
           .has_value());
+
+  histogram_tester.ExpectBucketCount(
+      compose::kComposeProactiveNudgeShowStatus,
+      compose::ComposeShowStatus::kRandomlyBlocked, 1);
 }
diff --git a/chrome/browser/download/android/download_controller.cc b/chrome/browser/download/android/download_controller.cc
index a9dc5d6..658c112 100644
--- a/chrome/browser/download/android/download_controller.cc
+++ b/chrome/browser/download/android/download_controller.cc
@@ -170,7 +170,7 @@
     JNIEnv* env,
     jlong callback_id,
     jboolean granted,
-    const JavaParamRef<jstring>& jpermission_to_update) {
+    std::string& permission_to_update) {
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
   DCHECK(callback_id);
 
@@ -180,11 +180,6 @@
     return;
   }
 
-  std::string permission_to_update;
-  if (jpermission_to_update) {
-    permission_to_update =
-        base::android::ConvertJavaStringToUTF8(env, jpermission_to_update);
-  }
   // Convert java long long int to c++ pointer, take ownership.
   std::unique_ptr<DownloadController::AcquirePermissionCallback> cb(
       reinterpret_cast<DownloadController::AcquirePermissionCallback*>(
@@ -192,23 +187,36 @@
   std::move(*cb).Run(granted, permission_to_update);
 }
 
-static void JNI_DownloadController_CancelDownload(
-    JNIEnv* env,
-    const JavaParamRef<jobject>& jprofile,
-    const JavaParamRef<jstring>& jdownload_guid) {
+static void JNI_DownloadController_CancelDownload(JNIEnv* env,
+                                                  Profile* profile,
+                                                  std::string& download_guid) {
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
 
-  Profile* profile = ProfileAndroid::FromProfileAndroid(jprofile);
   DownloadManager* download_manager = profile->GetDownloadManager();
   if (download_manager) {
-    DownloadItem* download = download_manager->GetDownloadByGuid(
-        base::android::ConvertJavaStringToUTF8(env, jdownload_guid));
+    DownloadItem* download = download_manager->GetDownloadByGuid(download_guid);
     if (download) {
       download->Cancel(/*user_cancel=*/false);
     }
   }
 }
 
+static void JNI_DownloadController_DownloadUrl(JNIEnv* env,
+                                               std::string& url,
+                                               Profile* profile) {
+  DCHECK_CURRENTLY_ON(BrowserThread::UI);
+
+  DownloadManager* download_manager = profile->GetDownloadManager();
+  if (download_manager) {
+    auto dl_params = std::make_unique<download::DownloadUrlParameters>(
+        GURL(url),
+        TRAFFIC_ANNOTATION_WITHOUT_PROTO("Download via toolbar menu"));
+    dl_params->set_content_initiated(false);
+    dl_params->set_download_source(download::DownloadSource::TOOLBAR_MENU);
+    download_manager->DownloadUrl(std::move(dl_params));
+  }
+}
+
 // static
 DownloadControllerBase* DownloadControllerBase::Get() {
   base::AutoLock lock(g_download_controller_lock_.Get());
diff --git a/chrome/browser/enterprise/connectors/analysis/page_print_analysis_request.cc b/chrome/browser/enterprise/connectors/analysis/page_print_analysis_request.cc
index c9b252c..7b123e6 100644
--- a/chrome/browser/enterprise/connectors/analysis/page_print_analysis_request.cc
+++ b/chrome/browser/enterprise/connectors/analysis/page_print_analysis_request.cc
@@ -40,19 +40,14 @@
 void PagePrintAnalysisRequest::GetRequestData(DataCallback callback) {
   Data data;
   data.size = page_.GetSize();
-
-  // Only enforce a max size for cloud scans.
-  if (data.size >= kMaxPageSize
-      && cloud_or_local_settings().is_cloud_analysis()) {
-    std::move(callback).Run(
-        safe_browsing::BinaryUploadService::Result::FILE_TOO_LARGE,
-        std::move(data));
-    return;
-  }
-
   data.page = page_.Duplicate();
-  std::move(callback).Run(safe_browsing::BinaryUploadService::Result::SUCCESS,
-                          std::move(data));
+
+  std::move(callback).Run(
+      // Only enforce a max size for cloud scans.
+      data.size >= kMaxPageSize && cloud_or_local_settings().is_cloud_analysis()
+          ? safe_browsing::BinaryUploadService::Result::FILE_TOO_LARGE
+          : safe_browsing::BinaryUploadService::Result::SUCCESS,
+      std::move(data));
 }
 
 }  // namespace enterprise_connectors
diff --git a/chrome/browser/enterprise/connectors/analysis/page_print_analysis_request_unittest.cc b/chrome/browser/enterprise/connectors/analysis/page_print_analysis_request_unittest.cc
index a7bf91c..6a9fba0 100644
--- a/chrome/browser/enterprise/connectors/analysis/page_print_analysis_request_unittest.cc
+++ b/chrome/browser/enterprise/connectors/analysis/page_print_analysis_request_unittest.cc
@@ -63,11 +63,8 @@
 
         ASSERT_EQ(result, expected_result());
         ASSERT_EQ(data.size, page_size());
-        if (expected_result() ==
-            safe_browsing::BinaryUploadService::Result::SUCCESS) {
-          ASSERT_EQ(data.page.GetSize(), page_size());
-          ASSERT_TRUE(data.page.IsValid());
-        }
+        ASSERT_EQ(data.page.GetSize(), page_size());
+        ASSERT_TRUE(data.page.IsValid());
 
         run_loop.Quit();
       }));
diff --git a/chrome/browser/enterprise/data_protection/data_protection_navigation_observer.cc b/chrome/browser/enterprise/data_protection/data_protection_navigation_observer.cc
index 057bb09b..12585898 100644
--- a/chrome/browser/enterprise/data_protection/data_protection_navigation_observer.cc
+++ b/chrome/browser/enterprise/data_protection/data_protection_navigation_observer.cc
@@ -117,23 +117,6 @@
   std::move(callback).Run(std::move(rt_lookup_response));
 }
 
-void DoLookup(safe_browsing::RealTimeUrlLookupServiceBase* lookup_service,
-              const GURL& url,
-              const std::string& identifier,
-              LookupCallback callback,
-              content::WebContents* web_contents) {
-  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
-  DCHECK(web_contents);
-  DCHECK(!callback.is_null());
-
-  lookup_service->StartLookup(
-      url,
-      base::BindOnce(&OnRealTimeLookupComplete, std::move(callback),
-                     identifier),
-      base::SequencedTaskRunner::GetCurrentDefault(),
-      sessions::SessionTabHelper::IdForTab(web_contents));
-}
-
 bool IsEnterpriseLookupEnabled(Profile* profile) {
   // Some tests return a non-null pointer for the enterprise lookup service,
   // so we need to defensively check if enterprise lookup is enabled.
@@ -147,6 +130,29 @@
       profile->GetPrefs(), has_valid_dm_token, profile->IsOffTheRecord());
 }
 
+bool IsEnterpriseLookupEnabled(content::BrowserContext* context) {
+  DCHECK(context);
+  return IsEnterpriseLookupEnabled(Profile::FromBrowserContext(context));
+}
+
+void DoLookup(safe_browsing::RealTimeUrlLookupServiceBase* lookup_service,
+              const GURL& url,
+              const std::string& identifier,
+              LookupCallback callback,
+              content::WebContents* web_contents) {
+  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+  DCHECK(web_contents);
+  DCHECK(!callback.is_null());
+  DCHECK(IsEnterpriseLookupEnabled(web_contents->GetBrowserContext()));
+
+  lookup_service->StartLookup(
+      url,
+      base::BindOnce(&OnRealTimeLookupComplete, std::move(callback),
+                     identifier),
+      base::SequencedTaskRunner::GetCurrentDefault(),
+      sessions::SessionTabHelper::IdForTab(web_contents));
+}
+
 bool IsDesktopDataControlsEnabled() {
   return base::FeatureList::IsEnabled(
       data_controls::kEnableDesktopDataControls);
@@ -300,7 +306,8 @@
   // main frame still points to the existing page before the navigation, not the
   // ultimate destination page of the navigation.
   is_from_cache_ = navigation_handle.IsServedFromBackForwardCache();
-  if (!is_from_cache_ && lookup_service_) {
+  if (!is_from_cache_ &&
+      ShouldPerformRealTimeUrlCheck(web_contents->GetBrowserContext())) {
     DoLookup(lookup_service_, navigation_handle.GetURL(), identifier_,
              base::BindOnce(&DataProtectionNavigationObserver::OnLookupComplete,
                             weak_factory_.GetWeakPtr()),
@@ -318,6 +325,11 @@
   rt_lookup_response_ = std::move(rt_lookup_response);
 }
 
+bool DataProtectionNavigationObserver::ShouldPerformRealTimeUrlCheck(
+    content::BrowserContext* browser_context) const {
+  return lookup_service_ && IsEnterpriseLookupEnabled(browser_context);
+}
+
 void DataProtectionNavigationObserver::DidRedirectNavigation(
     content::NavigationHandle* navigation_handle) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
@@ -327,7 +339,8 @@
       navigation_handle->GetWebContents()->GetBrowserContext(),
       navigation_handle->GetURL());
 
-  if (lookup_service_) {
+  if (ShouldPerformRealTimeUrlCheck(
+          navigation_handle->GetWebContents()->GetBrowserContext())) {
     DoLookup(
         lookup_service_, navigation_handle->GetURL(),
         GetIdentifier(navigation_handle->GetWebContents()->GetBrowserContext()),
@@ -378,7 +391,8 @@
     OnDoLookupComplete(web_contents()->GetWeakPtr(),
                        std::move(pending_navigation_callback_), identifier_,
                        std::move(rt_lookup_response_));
-  } else if (lookup_service_) {
+  } else if (ShouldPerformRealTimeUrlCheck(
+                 web_contents()->GetBrowserContext())) {
     LogVerdictSource(URLVerdictSource::kPostNavigationLookup);
     DoLookup(
         lookup_service_, navigation_handle->GetURL(), identifier_,
diff --git a/chrome/browser/enterprise/data_protection/data_protection_navigation_observer.h b/chrome/browser/enterprise/data_protection/data_protection_navigation_observer.h
index 81dbd544..b7fa787 100644
--- a/chrome/browser/enterprise/data_protection/data_protection_navigation_observer.h
+++ b/chrome/browser/enterprise/data_protection/data_protection_navigation_observer.h
@@ -107,6 +107,12 @@
   void OnLookupComplete(
       std::unique_ptr<safe_browsing::RTLookupResponse> rt_lookup_response);
 
+  // Returns true when the "EnterpriseRealTimeUrlCheckMode" policy is enabled
+  // for `browser_context`, and when a `lookup_service_` is available to make
+  // URL filtering checks.
+  bool ShouldPerformRealTimeUrlCheck(
+      content::BrowserContext* browser_context) const;
+
   // content::WebContentsObserver:
   void DidRedirectNavigation(
       content::NavigationHandle* navigation_handle) override;
diff --git a/chrome/browser/enterprise/data_protection/data_protection_navigation_observer_unittest.cc b/chrome/browser/enterprise/data_protection/data_protection_navigation_observer_unittest.cc
index d0434f52..f7420dd 100644
--- a/chrome/browser/enterprise/data_protection/data_protection_navigation_observer_unittest.cc
+++ b/chrome/browser/enterprise/data_protection/data_protection_navigation_observer_unittest.cc
@@ -274,6 +274,49 @@
 }
 
 TEST_F(DataProtectionNavigationObserverTest,
+       TestWatermarkTextUpdated_NoUrlCheck) {
+  profile()->GetPrefs()->SetInteger(
+      prefs::kSafeBrowsingEnterpriseRealTimeUrlCheckMode,
+      safe_browsing::REAL_TIME_CHECK_DISABLED);
+
+  enterprise_connectors::test::EventReportValidator validator(client_.get());
+  validator.ExpectNoReport();
+
+  auto simulator = content::NavigationSimulator::CreateRendererInitiated(
+      GURL("https://test"), web_contents()->GetPrimaryMainFrame());
+
+  // DataProtectionNavigationObserver does not implement DidStartNavigation(),
+  // this is called by BrowserView. So we simply call Start() and manually
+  // construct the class using the navigation handle that is provided once
+  // Start() is called.
+  simulator->Start();
+  content::NavigationHandle* navigation_handle =
+      simulator->GetNavigationHandle();
+  base::test::TestFuture<const UrlSettings&> future;
+
+  // The DataProtectionNavigationObserver needs to be constructed using
+  // CreateForNavigationHandle to allow for proper lifetime management of the
+  // object, since we call DeleteForNavigationHandle() in our
+  // DidFinishNavigation() override.
+  enterprise_data_protection::DataProtectionNavigationObserver::
+      CreateForNavigationHandle(*navigation_handle, &lookup_service_,
+                                navigation_handle->GetWebContents(),
+                                future.GetCallback());
+
+  // Call DidFinishNavigation() navigation, which should invoke our callback.
+  simulator->Commit();
+
+  std::string watermark_text = future.Get().watermark_text;
+  EXPECT_TRUE(watermark_text.empty());
+
+  // Value should be cached.
+  auto* user_data = DataProtectionPageUserData::GetForPage(
+      GetPageFromWebContents(web_contents()));
+  ASSERT_TRUE(user_data);
+  EXPECT_TRUE(user_data->settings().watermark_text.empty());
+}
+
+TEST_F(DataProtectionNavigationObserverTest,
        TestScreenshotUpdated_DataControls) {
   enterprise_connectors::test::EventReportValidator validator(client_.get());
   validator.ExpectNoReport();
@@ -472,6 +515,36 @@
 }
 
 TEST_F(DataProtectionNavigationObserverTest,
+       GetDataProtectionSettings_NoUrlCheck) {
+  profile()->GetPrefs()->SetInteger(
+      prefs::kSafeBrowsingEnterpriseRealTimeUrlCheckMode,
+      safe_browsing::REAL_TIME_CHECK_DISABLED);
+
+  enterprise_connectors::test::EventReportValidator validator(client_.get());
+  validator.ExpectNoReport();
+  DataProtectionNavigationObserver::SetLookupServiceForTesting(
+      &lookup_service_);
+
+  SetContents(CreateTestWebContents());
+  NavigateAndCommit(GURL("https://example.com"));
+
+  base::test::TestFuture<const UrlSettings&> future;
+  DataProtectionNavigationObserver::GetDataProtectionSettings(
+      Profile::FromBrowserContext(browser_context()), web_contents(),
+      future.GetCallback());
+  EXPECT_TRUE(future.Get().watermark_text.empty());
+  EXPECT_TRUE(future.Get().allow_screenshots);  // Default is true.
+
+  // Value should be cached.
+  auto* user_data = DataProtectionPageUserData::GetForPage(
+      GetPageFromWebContents(web_contents()));
+  ASSERT_TRUE(user_data);
+  EXPECT_EQ(user_data->settings(), future.Get());
+  EXPECT_TRUE(user_data->settings().watermark_text.empty());
+  EXPECT_TRUE(user_data->settings().allow_screenshots);
+}
+
+TEST_F(DataProtectionNavigationObserverTest,
        GetDataProtectionSettings_DC_BlockScreenshot) {
   enterprise_connectors::test::EventReportValidator validator(client_.get());
   validator.ExpectNoReport();
diff --git a/chrome/browser/enterprise/watermark/watermark_browsertest.cc b/chrome/browser/enterprise/watermark/watermark_browsertest.cc
index 238896c..87fd6bec9 100644
--- a/chrome/browser/enterprise/watermark/watermark_browsertest.cc
+++ b/chrome/browser/enterprise/watermark/watermark_browsertest.cc
@@ -3,6 +3,7 @@
 // found in the LICENSE file.
 
 #include "base/test/scoped_feature_list.h"
+#include "chrome/browser/enterprise/watermark/watermark_view.h"
 #include "chrome/browser/ui/test/test_browser_ui.h"
 #include "chrome/browser/ui/views/frame/browser_view.h"
 #include "chrome/common/chrome_features.h"
diff --git a/chrome/browser/extensions/service_worker_lifetime_keepalive_browsertest.cc b/chrome/browser/extensions/service_worker_lifetime_keepalive_browsertest.cc
index 6ce2da5..1ce6e51 100644
--- a/chrome/browser/extensions/service_worker_lifetime_keepalive_browsertest.cc
+++ b/chrome/browser/extensions/service_worker_lifetime_keepalive_browsertest.cc
@@ -846,12 +846,19 @@
 
   Profile* incognito_profile =
       profile()->GetPrimaryOTRProfile(/*create_if_needed=*/true);
-  TestServiceWorkerContextObserver registration_observer(incognito_profile);
+  // TODO(crbug.com/335829868): Refactor to use
+  // ServiceWorkerTaskQueue::TestObserver::DidStartWorker() to ensure worker is
+  // ready to receive event in BackgroundScriptExecutor::ExecuteScript().
+  TestServiceWorkerContextObserver sw_observer_opener_extension(
+      incognito_profile, opener_extension->id());
+  TestServiceWorkerContextObserver sw_observer_listener_extension(
+      incognito_profile, listener_extension->id());
   // Open a new tab in incognito. This spawns the new process for the split mode
   // extensions.
   Browser* incognito_browser = OpenURLOffTheRecord(
       profile(), embedded_test_server()->GetURL("example.com", "/simple.html"));
-  registration_observer.WaitForWorkerActivated();
+  sw_observer_listener_extension.WaitForWorkerStarted();
+  sw_observer_opener_extension.WaitForWorkerStarted();
 
   // Send a message from one extension to the other, opening a message pipe.
   // Since the listener extension never responds, the message pipe will
@@ -992,10 +999,18 @@
       spanning_mode_dir.UnpackedPath(), {.allow_in_incognito = true});
   ASSERT_TRUE(spanning_mode_extension);
 
+  Profile* incognito_profile =
+      profile()->GetPrimaryOTRProfile(/*create_if_needed=*/true);
+  // Wait for the single worker from split_mode_extension.
+  // TODO(crbug.com/335829868): Refactor to use
+  // ServiceWorkerTaskQueue::TestObserver::DidStartWorker() to ensure worker is
+  // ready to receive event in BackgroundScriptExecutor::ExecuteScript().
+  TestServiceWorkerContextObserver sw_observer(incognito_profile);
   // Open a new tab in incognito. This spawns the new process for the split mode
   // extension.
   Browser* incognito_browser = OpenURLOffTheRecord(
       profile(), embedded_test_server()->GetURL("example.com", "/simple.html"));
+  sw_observer.WaitForWorkerStarted();
 
   // Send a message to the spanning mode extension from the incognito context of
   // the split mode extension.
@@ -1008,7 +1023,6 @@
            // Note: Pass a callback to signal a reply is expected.
            chrome.runtime.sendMessage('%s', 'hello', () => {});
          })();)";
-  Profile* incognito_profile = incognito_browser->profile();
   base::Value script_result = BackgroundScriptExecutor::ExecuteScript(
       incognito_profile, split_mode_extension->id(),
       base::StringPrintf(kOpenMessagePipe,
diff --git a/chrome/browser/flag-metadata.json b/chrome/browser/flag-metadata.json
index 7cd3e34..6f9e781 100644
--- a/chrome/browser/flag-metadata.json
+++ b/chrome/browser/flag-metadata.json
@@ -1538,6 +1538,11 @@
     "expiry_milestone": 130
   },
   {
+    "name": "crabbyavif",
+    "owners": [ "vigneshv@google.com", "image-codecs-eng@google.com" ],
+    "expiry_milestone": 130
+  },
+  {
     "name": "cras-processor-dedicated-thread",
     "owners": ["aaronyu@google.com", "chromeos-audio@google.com" ],
     "expiry_milestone": 130
@@ -3877,11 +3882,6 @@
     "expiry_milestone": 120
   },
   {
-    "name": "enable-tailored-security-updated-messages",
-    "owners": [ "awado@chromium.org", "jacastro@chromium.org", "chrome-counter-abuse-alerts@google.com"],
-    "expiry_milestone": 120
-  },
-  {
     "name": "enable-task-manager-end-process-disabled-for-extension",
     "owners": [
       "simonha@chromium.org"
diff --git a/chrome/browser/flag_descriptions.cc b/chrome/browser/flag_descriptions.cc
index db0eb1281..2970db2 100644
--- a/chrome/browser/flag_descriptions.cc
+++ b/chrome/browser/flag_descriptions.cc
@@ -263,6 +263,11 @@
     "Chrome uses the gainmap (if present) in AVIF images to render the HDR "
     "version on HDR displays and the SDR version on SDR displays.";
 
+const char kCrabbyAvifName[] = "CrabbyAvif for decoding AVIF images";
+const char kCrabbyAvifDescription[] =
+    "If enabled, CrabbyAvif will be used instead of libavif for decoding AVIF "
+    "images";
+
 const char kTangibleSyncName[] = "Tangible Sync";
 const char kTangibleSyncDescription[] =
     "Enables the tangible sync when a user starts the sync consent flow";
@@ -1340,12 +1345,6 @@
 const char kEnableSuspiciousSiteDetectionRTLookupsDescription[] =
     "Enables suspicious site detection for real time URL lookups.";
 
-const char kEnableTailoredSecurityUpdatedMessagesName[] =
-    "Enable tailored security updated messages";
-const char kEnableTailoredSecurityUpdatedMessagesDescription[] =
-    "Updates the tailored security dialog strings and icons for their "
-    "respective platforms.";
-
 const char kEnableNetworkLoggingToFileName[] = "Enable network logging to file";
 const char kEnableNetworkLoggingToFileDescription[] =
     "Enables network logging to a file named netlog.json in the user data "
@@ -1965,11 +1964,6 @@
 const char kContextualPageActionsDescription[] =
     "Enables contextual page action feature.";
 
-const char kContextualPageActionsPriceTrackingName[] =
-    "Contextual page actions - price tracking";
-const char kContextualPageActionsPriceTrackingDescription[] =
-    "Enables price tracking as a contextual page action.";
-
 const char kContextualPageActionsReaderModeName[] =
     "Contextual page actions - reader mode";
 const char kContextualPageActionsReaderModeDescription[] =
diff --git a/chrome/browser/flag_descriptions.h b/chrome/browser/flag_descriptions.h
index 6d9a34e..0f6ffea 100644
--- a/chrome/browser/flag_descriptions.h
+++ b/chrome/browser/flag_descriptions.h
@@ -215,6 +215,9 @@
 extern const char kAvifGainmapHdrImagesName[];
 extern const char kAvifGainmapHdrImagesDescription[];
 
+extern const char kCrabbyAvifName[];
+extern const char kCrabbyAvifDescription[];
+
 extern const char kTangibleSyncName[];
 extern const char kTangibleSyncDescription[];
 
@@ -789,9 +792,6 @@
 extern const char kEnableSuspiciousSiteDetectionRTLookupsName[];
 extern const char kEnableSuspiciousSiteDetectionRTLookupsDescription[];
 
-extern const char kEnableTailoredSecurityUpdatedMessagesName[];
-extern const char kEnableTailoredSecurityUpdatedMessagesDescription[];
-
 extern const char kEnableFencedFramesName[];
 extern const char kEnableFencedFramesDescription[];
 
diff --git a/chrome/browser/flags/android/chrome_feature_list.cc b/chrome/browser/flags/android/chrome_feature_list.cc
index cd942b5..abca6122 100644
--- a/chrome/browser/flags/android/chrome_feature_list.cc
+++ b/chrome/browser/flags/android/chrome_feature_list.cc
@@ -333,7 +333,6 @@
     &safe_browsing::kSafeBrowsingNewGmsApiForBrowseUrlDatabaseCheck,
     &safe_browsing::kSafeBrowsingNewGmsApiForSubresourceFilterCheck,
     &segmentation_platform::features::kContextualPageActions,
-    &segmentation_platform::features::kContextualPageActionPriceTracking,
     &segmentation_platform::features::kContextualPageActionReaderMode,
     &segmentation_platform::features::kContextualPageActionShareModel,
     &segmentation_platform::features::
diff --git a/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java b/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java
index cba390f..e47b87d 100644
--- a/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java
+++ b/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java
@@ -247,8 +247,6 @@
     public static final String COMMERCE_MERCHANT_VIEWER = "CommerceMerchantViewer";
     public static final String COMMERCE_PRICE_TRACKING = "CommercePriceTracking";
     public static final String CONTEXTUAL_PAGE_ACTIONS = "ContextualPageActions";
-    public static final String CONTEXTUAL_PAGE_ACTION_PRICE_TRACKING =
-            "ContextualPageActionPriceTracking";
     public static final String CONTEXTUAL_PAGE_ACTION_READER_MODE =
             "ContextualPageActionReaderMode";
     public static final String CONTEXTUAL_SEARCH_DISABLE_ONLINE_DETECTION =
diff --git a/chrome/browser/history/java/res/layout/appfilter_header.xml b/chrome/browser/history/java/res/layout/appfilter_header.xml
index 1c18495..4d3888c 100644
--- a/chrome/browser/history/java/res/layout/appfilter_header.xml
+++ b/chrome/browser/history/java/res/layout/appfilter_header.xml
@@ -9,7 +9,8 @@
     xmlns:app="http://schemas.android.com/apk/res-auto"
     xmlns:tools="http://schemas.android.com/tools"
     android:layout_width="match_parent"
-    android:layout_height="@dimen/min_touch_target_size"
+    android:layout_height="wrap_content"
+    android:minHeight="@dimen/min_touch_target_size"
     android:orientation="vertical"
     tools:ignore="UseCompoundDrawables">
     <ImageView
@@ -23,8 +24,9 @@
     <TextView
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
-        android:layout_marginStart="24dp"
-        android:layout_gravity="bottom"
+        android:minHeight="44dp"
+        android:layout_marginStart="@dimen/list_item_default_margin"
+        android:gravity="center_vertical"
         android:textDirection="locale"
         android:text="@string/history_filter_by_app"
         style="@style/TextAppearance.TextAccentMediumThick.Primary" />
diff --git a/chrome/browser/history/java/src/org/chromium/chrome/browser/history/AppFilterCoordinator.java b/chrome/browser/history/java/src/org/chromium/chrome/browser/history/AppFilterCoordinator.java
index 085d8c9..70a22fe 100644
--- a/chrome/browser/history/java/src/org/chromium/chrome/browser/history/AppFilterCoordinator.java
+++ b/chrome/browser/history/java/src/org/chromium/chrome/browser/history/AppFilterCoordinator.java
@@ -110,7 +110,9 @@
         var adapter = new SimpleRecyclerViewAdapter(listItems);
         adapter.registerType(
                 0,
-                (parent) -> layoutInflater.inflate(R.layout.modern_list_item_view, parent, false),
+                (parent) ->
+                        layoutInflater.inflate(
+                                R.layout.modern_list_item_small_icon_view, parent, false),
                 AppFilterViewBinder::bind);
         mItemListView.setAdapter(adapter);
 
@@ -169,7 +171,8 @@
             layoutParams = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 0);
         }
 
-        int rowHeight = mContext.getResources().getDimensionPixelSize(R.dimen.list_item_min_height);
+        int rowHeight =
+                mContext.getResources().getDimensionPixelSize(R.dimen.min_touch_target_size);
         layoutParams.height = calculateSheetHeight(rowHeight, mBaseView.getHeight(), mAppCount);
         mItemListView.setLayoutParams(layoutParams);
     }
diff --git a/chrome/browser/lacros/embedded_a11y_manager_lacros.cc b/chrome/browser/lacros/embedded_a11y_manager_lacros.cc
index 6337c081..16ca4b4 100644
--- a/chrome/browser/lacros/embedded_a11y_manager_lacros.cc
+++ b/chrome/browser/lacros/embedded_a11y_manager_lacros.cc
@@ -282,10 +282,22 @@
   }
 }
 
+void EmbeddedA11yManagerLacros::SetReadingModeEnabled(bool enabled) {
+  if (reading_mode_enabled_ != enabled) {
+    reading_mode_enabled_ = enabled;
+    UpdateEmbeddedA11yHelperExtension();
+  }
+}
+
+bool EmbeddedA11yManagerLacros::IsReadingModeEnabled() {
+  return reading_mode_enabled_;
+}
+
 void EmbeddedA11yManagerLacros::UpdateEmbeddedA11yHelperExtension() {
   // Switch Access and Select to Speak share a helper extension which has a
   // manifest content script to tell Google Docs to annotate the HTML canvas.
-  if (select_to_speak_enabled_ || switch_access_enabled_) {
+  if (select_to_speak_enabled_ || switch_access_enabled_ ||
+      reading_mode_enabled_) {
     EmbeddedA11yExtensionLoader::GetInstance()->InstallExtensionWithId(
         extension_misc::kEmbeddedA11yHelperExtensionId,
         extension_misc::kEmbeddedA11yHelperExtensionPath,
diff --git a/chrome/browser/lacros/embedded_a11y_manager_lacros.h b/chrome/browser/lacros/embedded_a11y_manager_lacros.h
index 9c2cd70..c540288 100644
--- a/chrome/browser/lacros/embedded_a11y_manager_lacros.h
+++ b/chrome/browser/lacros/embedded_a11y_manager_lacros.h
@@ -63,6 +63,10 @@
   void AddFocusChangedCallbackForTest(
       base::RepeatingCallback<void(gfx::Rect)> callback);
 
+  void SetReadingModeEnabled(bool enabled);
+
+  bool IsReadingModeEnabled();
+
  private:
   EmbeddedA11yManagerLacros();
   ~EmbeddedA11yManagerLacros() override;
@@ -108,6 +112,7 @@
   bool chromevox_enabled_ = false;
   bool select_to_speak_enabled_ = false;
   bool switch_access_enabled_ = false;
+  bool reading_mode_enabled_ = false;
   std::optional<bool> pdf_ocr_always_active_enabled_;
   std::optional<bool> overscroll_history_navigation_enabled_;
 
diff --git a/chrome/browser/lacros/embedded_a11y_manager_lacros_browsertest.cc b/chrome/browser/lacros/embedded_a11y_manager_lacros_browsertest.cc
index 291495f45c..29e8d4e8c 100644
--- a/chrome/browser/lacros/embedded_a11y_manager_lacros_browsertest.cc
+++ b/chrome/browser/lacros/embedded_a11y_manager_lacros_browsertest.cc
@@ -310,6 +310,23 @@
 }
 
 IN_PROC_BROWSER_TEST_F(EmbeddedA11yManagerLacrosTest,
+                       AddsAndRemovesHelperForReadingMode) {
+  ProfileManager* profile_manager = g_browser_process->profile_manager();
+  const auto& profiles = profile_manager->GetLoadedProfiles();
+  ASSERT_GT(profiles.size(), 0u);
+  Profile* profile = profiles[0];
+
+  auto* embedded_a11y_manager = EmbeddedA11yManagerLacros::GetInstance();
+  embedded_a11y_manager->SetReadingModeEnabled(true);
+  WaitForExtensionLoaded(profile,
+                         extension_misc::kEmbeddedA11yHelperExtensionId);
+
+  embedded_a11y_manager->SetReadingModeEnabled(false);
+  WaitForExtensionUnloaded(profile,
+                           extension_misc::kEmbeddedA11yHelperExtensionId);
+}
+
+IN_PROC_BROWSER_TEST_F(EmbeddedA11yManagerLacrosTest,
                        SwitchAccessAndSelectToSpeak) {
   ProfileManager* profile_manager = g_browser_process->profile_manager();
   const auto& profiles = profile_manager->GetLoadedProfiles();
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 e19175a..a2ea6f7 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
@@ -461,8 +461,8 @@
                    /*expected=*/true, /*entries=*/0);
 }
 
-// TODO(crbug.com/40866505): Flaky on lacros
-#if BUILDFLAG(IS_CHROMEOS_LACROS)
+// TODO(crbug.com/40866505): Flaky on lacros and Windows (crbug.com/336471594)
+#if BUILDFLAG(IS_CHROMEOS_LACROS) || BUILDFLAG(IS_WIN)
 #define MAYBE_MouseoverLCPTest DISABLED_MouseoverLCPTest
 #else
 #define MAYBE_MouseoverLCPTest MouseoverLCPTest
diff --git a/chrome/browser/prefs/browser_prefs.cc b/chrome/browser/prefs/browser_prefs.cc
index e0fd02f3b..37f0e9a 100644
--- a/chrome/browser/prefs/browser_prefs.cc
+++ b/chrome/browser/prefs/browser_prefs.cc
@@ -289,6 +289,7 @@
 #include "chrome/browser/serial/serial_policy_allowed_ports.h"
 #include "chrome/browser/signin/signin_promo.h"
 #include "chrome/browser/ui/commerce/commerce_ui_tab_helper.h"
+#include "chrome/browser/ui/lens/lens_overlay_permission_utils.h"
 #include "chrome/browser/ui/safety_hub/safety_hub_prefs.h"
 #include "chrome/browser/ui/startup/startup_browser_creator.h"
 #include "chrome/browser/ui/tabs/saved_tab_groups/saved_tab_group_utils.h"
@@ -2004,6 +2005,7 @@
   first_run::RegisterProfilePrefs(registry);
   gcm::RegisterProfilePrefs(registry);
   HatsServiceDesktop::RegisterProfilePrefs(registry);
+  lens::prefs::RegisterProfilePrefs(registry);
   NtpCustomBackgroundService::RegisterProfilePrefs(registry);
   media_router::RegisterAccessCodeProfilePrefs(registry);
   media_router::RegisterProfilePrefs(registry);
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 103b344..8bc154f3 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
@@ -447,6 +447,16 @@
                             notifyReadabilityMayHaveChanged();
                             if (tab != null && tab.getUrl() != null && tab.getUrl().isValid()) {
                                 maybeCheckReadability(tab.getUrl());
+                            }
+                        }
+
+                        @Override
+                        public void onLoadStarted(Tab tab, boolean toDifferentDocument) {
+                            Log.d(TAG, "onLoadStarted");
+                            if (tab != null
+                                    && tab.getUrl() != null
+                                    && tab.getUrl().isValid()
+                                    && toDifferentDocument) {
                                 maybeHandleTabReload(tab, tab.getUrl());
                                 maybeStopPlayback(tab);
                             }
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 9224cfdc..15b117d 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
@@ -326,6 +326,40 @@
     }
 
     @Test
+    public void testOnLoadStarted_differentDocument() {
+        // start a successful playback
+        mController.playTab(mTab, ReadAloudController.Entrypoint.MAGIC_TOOLBAR);
+        resolvePromises();
+        verify(mPlaybackHooks).createPlayback(Mockito.any(), mPlaybackCallbackCaptor.capture());
+        onPlaybackSuccess(mPlayback);
+        resolvePromises();
+
+        // Load new url
+        when(mTab.getUrl()).thenReturn(new GURL("https://en.wikipedia.org/wiki/Alphabet_Inc."));
+        mController.getTabModelTabObserverforTests().onLoadStarted(mTab, true);
+
+        verify(mHighlighter).handleTabReloaded(eq(mTab));
+        verify(mPlayerCoordinator).dismissPlayers();
+    }
+
+    @Test
+    public void testOnLoadStarted_sameDocument() {
+        // start a successful playback
+        mController.playTab(mTab, ReadAloudController.Entrypoint.MAGIC_TOOLBAR);
+        resolvePromises();
+        verify(mPlaybackHooks).createPlayback(Mockito.any(), mPlaybackCallbackCaptor.capture());
+        onPlaybackSuccess(mPlayback);
+        resolvePromises();
+
+        // Load the same document
+        mController.getTabModelTabObserverforTests().onLoadStarted(mTab, false);
+
+        // nothing should happen
+        verify(mHighlighter, never()).handleTabReloaded(eq(mTab));
+        verify(mPlayerCoordinator, never()).dismissPlayers();
+    }
+
+    @Test
     public void testReloadingPage() {
         // Reload tab before any playback starts - tests null checks
         mController.getTabModelTabObserverforTests().onPageLoadStarted(mTab, mTab.getUrl());
@@ -344,11 +378,11 @@
         verify(mPlayerCoordinator, never()).dismissPlayers();
         verify(mPlayback, never()).release();
 
-        // now reload the playing tab
+        // now reload the playing tab, playback should still keep going
         mController.getTabModelTabObserverforTests().onUrlUpdated(mTab);
 
-        verify(mPlayerCoordinator).dismissPlayers();
-        verify(mPlayback).release();
+        verify(mPlayerCoordinator, never()).dismissPlayers();
+        verify(mPlayback, never()).release();
     }
 
     @Test
@@ -462,23 +496,6 @@
     }
 
     @Test
-    public void testReloadPage_errorUiDismissed() {
-        // start a playback with an error
-        mController.playTab(mTab, ReadAloudController.Entrypoint.MAGIC_TOOLBAR);
-        resolvePromises();
-        verify(mPlaybackHooks, times(1))
-                .createPlayback(Mockito.any(), mPlaybackCallbackCaptor.capture());
-        mPlaybackCallbackCaptor.getValue().onFailure(new Exception("Very bad error"));
-        resolvePromises();
-
-        // Reload this url
-        mController.getTabModelTabObserverforTests().onUrlUpdated(mTab);
-
-        // No playback but error UI should get dismissed
-        verify(mPlayerCoordinator).dismissPlayers();
-    }
-
-    @Test
     public void testClosingTab() {
         // Close a  tab before any playback starts - tests null checks
         mController.getTabModelTabObserverforTests().willCloseTab(mTab);
@@ -1061,23 +1078,6 @@
     }
 
     @Test
-    public void testReloadingTab_highlightsCleared() {
-        // set up the highlighter
-        mController.setTimepointsSupportedForTest(mTab.getUrl().getSpec(), true);
-        mController.playTab(mTab, ReadAloudController.Entrypoint.MAGIC_TOOLBAR);
-        resolvePromises();
-        verify(mPlaybackHooks, times(1))
-                .createPlayback(Mockito.any(), mPlaybackCallbackCaptor.capture());
-        onPlaybackSuccess(mPlayback);
-        verify(mHighlighter).initializeJs(eq(mTab), eq(mMetadata), any(Highlighter.Config.class));
-
-        // Reload this url
-        mController.getTabModelTabObserverforTests().onUrlUpdated(mTab);
-
-        verify(mHighlighter).handleTabReloaded(eq(mTab));
-    }
-
-    @Test
     public void reloadingTab_highlightsNotCleared() {
         // set up the highlighter
         mController.setTimepointsSupportedForTest(mTab.getUrl().getSpec(), true);
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 7f3b349..dc50a60 100644
--- a/chrome/browser/renderer_context_menu/render_view_context_menu.cc
+++ b/chrome/browser/renderer_context_menu/render_view_context_menu.cc
@@ -176,6 +176,7 @@
 #include "components/url_formatter/url_formatter.h"
 #include "components/user_notes/user_notes_features.h"
 #include "components/user_prefs/user_prefs.h"
+#include "components/vector_icons/vector_icons.h"
 #include "components/web_modal/web_contents_modal_dialog_manager.h"
 #include "content/public/browser/browser_context.h"
 #include "content/public/browser/child_process_security_policy.h"
@@ -2677,9 +2678,15 @@
   if (provider) {
     const int region_search_idc = GetRegionSearchIdc();
     if (lens::features::IsLensOverlayEnabled()) {
-      menu_model_.AddItem(
-          region_search_idc,
-          l10n_util::GetStringUTF16(IDS_CONTENT_CONTEXT_LENS_OVERLAY));
+      const gfx::VectorIcon& icon =
+#if BUILDFLAG(GOOGLE_CHROME_BRANDING)
+          vector_icons::kGoogleLensMonochromeLogoIcon;
+#else
+          vector_icons::kSearchIcon;
+#endif
+      menu_model_.AddItemWithStringIdAndIcon(
+          region_search_idc, IDS_CONTENT_CONTEXT_LENS_OVERLAY,
+          ui::ImageModel::FromVectorIcon(icon));
     } else {
       int resource_id = IDS_CONTENT_CONTEXT_LENS_REGION_SEARCH;
       if (lens::features::IsLensFullscreenSearchEnabled()) {
diff --git a/chrome/browser/renderer_host/chrome_render_widget_host_view_mac_delegate.mm b/chrome/browser/renderer_host/chrome_render_widget_host_view_mac_delegate.mm
index 9880a97..2f4968aa 100644
--- a/chrome/browser/renderer_host/chrome_render_widget_host_view_mac_delegate.mm
+++ b/chrome/browser/renderer_host/chrome_render_widget_host_view_mac_delegate.mm
@@ -14,6 +14,7 @@
 #include "chrome/browser/ui/browser.h"
 #include "chrome/browser/ui/browser_commands.h"
 #include "chrome/browser/ui/browser_finder.h"
+#include "chrome/browser/ui/webui/top_chrome/webui_url_utils.h"
 #include "chrome/common/url_constants.h"
 #include "components/prefs/pref_service.h"
 #include "components/spellcheck/browser/pref_names.h"
@@ -409,4 +410,21 @@
   }
 }
 
+- (AcceptMouseEventsOption)acceptsMouseEventsOption {
+  content::WebContents* webContents = self.webContents;
+  if (!webContents) {
+    return kAcceptMouseEventsInActiveWindow;
+  }
+
+  // For Top Chrome WebUIs, allows inactive windows to accept
+  // mouse events as long as the application is active. This
+  // mimics the behavior of views UI.
+  if (IsTopChromeWebUIURL(webContents->GetVisibleURL()) ||
+      IsTopChromeUntrustedWebUIURL(webContents->GetVisibleURL())) {
+    return kAcceptMouseEventsInActiveApp;
+  }
+
+  return kAcceptMouseEventsInActiveWindow;
+}
+
 @end
diff --git a/chrome/browser/resources/ash/settings/os_privacy_page/privacy_hub_subpage.html b/chrome/browser/resources/ash/settings/os_privacy_page/privacy_hub_subpage.html
index 374f70b..0429be9 100644
--- a/chrome/browser/resources/ash/settings/os_privacy_page/privacy_hub_subpage.html
+++ b/chrome/browser/resources/ash/settings/os_privacy_page/privacy_hub_subpage.html
@@ -18,15 +18,11 @@
       id="geolocationAreaLinkRow"
       start-icon="app-management:location"
       on-click="onGeolocationAreaClick_"
+      label="$i18n{geolocationAreaTitle}"
+      sub-label="[[locationSubLabel_]]"
       role-description="$i18n{subpageArrowRoleDescription}"
       deep-link-focus-id$="[[Setting.kGeolocationOnOff]]"
       using-slotted-label>
-    <div slot="label">$i18n{geolocationAreaTitle}</div>
-    <div slot="sub-label">
-      <localized-link
-          localized-string="$i18n{geolocationAreaDescription}"
-          link-url="$i18n{geolocationAreaLearnMoreURL}">
-      </localized-link>
     </div>
   </cr-link-row>
 </template>
diff --git a/chrome/browser/resources/ash/settings/os_privacy_page/privacy_hub_subpage.ts b/chrome/browser/resources/ash/settings/os_privacy_page/privacy_hub_subpage.ts
index f9662505..6bf7c4b 100644
--- a/chrome/browser/resources/ash/settings/os_privacy_page/privacy_hub_subpage.ts
+++ b/chrome/browser/resources/ash/settings/os_privacy_page/privacy_hub_subpage.ts
@@ -28,6 +28,7 @@
 
 import {MediaDevicesProxy} from './media_devices_proxy.js';
 import {PrivacyHubBrowserProxy, PrivacyHubBrowserProxyImpl} from './privacy_hub_browser_proxy.js';
+import {GeolocationAccessLevel} from './privacy_hub_geolocation_subpage.js';
 import {PrivacyHubSensorSubpageUserAction} from './privacy_hub_metrics_util.js';
 import {getTemplate} from './privacy_hub_subpage.html.js';
 
@@ -67,6 +68,12 @@
         },
       },
 
+      locationSubLabel_: {
+        type: String,
+        computed: 'computeLocationRowSubtext_(' +
+            'prefs.ash.user.geolocation_access_level.value)',
+      },
+
       cameraSubLabel_: String,
 
       connectedCameraNames_: {
@@ -185,6 +192,8 @@
   }
 
   private browserProxy_: PrivacyHubBrowserProxy;
+  private showPrivacyHubLocationControl_: boolean;
+  private locationSublabel_: string;
   private cameraFallbackMechanismEnabled_: boolean;
   private cameraRowSubtext_: string;
   private cameraSubLabel_: string;
@@ -199,7 +208,6 @@
   private cameraSwitchForceDisabled_: boolean;
   private shouldDisableCameraToggle_: boolean;
   private showAppPermissions_: boolean;
-  private showPrivacyHubLocationControl_: boolean;
   private showSpeakOnMuteDetectionPage_: boolean;
 
   constructor() {
@@ -354,6 +362,26 @@
     Router.getInstance().navigateTo(routes.PRIVACY_HUB_GEOLOCATION);
   }
 
+  private computeLocationRowSubtext_(): string {
+    if (!this.prefs) {
+      return '';
+    }
+
+    const locationAccessLevel: GeolocationAccessLevel =
+        this.getPref<GeolocationAccessLevel>(
+                'ash.user.geolocation_access_level')
+            .value;
+
+    switch (locationAccessLevel) {
+      case GeolocationAccessLevel.ALLOWED:
+        return this.i18n('geolocationAreaAllowedSubtext');
+      case GeolocationAccessLevel.ONLY_ALLOWED_FOR_SYSTEM:
+        return this.i18n('geolocationAreaOnlyAllowedForSystemSubtext');
+      case GeolocationAccessLevel.DISALLOWED:
+        return this.i18n('geolocationAreaDisallowedSubtext');
+    }
+  }
+
   private computeCameraRowSubtext_(): string {
     // Note: `this.getPref()` will assert the queried pref exists, but the prefs
     // property may not be initialized yet when this element runs the first
diff --git a/chrome/browser/resources/chromeos/accessibility/.eslintrc.js b/chrome/browser/resources/chromeos/accessibility/.eslintrc.js
index c727c5e..8e7ecff 100644
--- a/chrome/browser/resources/chromeos/accessibility/.eslintrc.js
+++ b/chrome/browser/resources/chromeos/accessibility/.eslintrc.js
@@ -66,7 +66,9 @@
                       // Exclude initialisms such as JSON and IME
                       'toJSON|describeTextChangedByIME|' +
                       // Exclude the short name CVox
-                      'isCVoxModifierActive' +
+                      'isCVoxModifierActive|' +
+                      // Exclude the phrase OS.
+                      'addOSKeyboardShortcutsMenuItem' +
                       ')$',
                   match: false,
                 },
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/BUILD.gn b/chrome/browser/resources/chromeos/accessibility/chromevox/BUILD.gn
index d60d8f1..dfb82bf 100644
--- a/chrome/browser/resources/chromeos/accessibility/chromevox/BUILD.gn
+++ b/chrome/browser/resources/chromeos/accessibility/chromevox/BUILD.gn
@@ -85,6 +85,7 @@
   "log_page/log.ts",
   "log_page/log_loader.ts",
   "panel/i_search_ui.ts",
+  "panel/menu_manager.ts",
   "panel/panel.ts",
   "panel/panel_captions.ts",
   "panel/panel_interface.ts",
@@ -170,7 +171,6 @@
   "common/spannable.js",
   "common/settings_manager.js",
   "common/tree_dumper.js",
-  "panel/menu_manager.js",
 ]
 
 # Closure library modules needed by chromevox.
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/panel/menu_manager.js b/chrome/browser/resources/chromeos/accessibility/chromevox/panel/menu_manager.ts
similarity index 72%
rename from chrome/browser/resources/chromeos/accessibility/chromevox/panel/menu_manager.js
rename to chrome/browser/resources/chromeos/accessibility/chromevox/panel/menu_manager.ts
index e22ba0b..b76cfd05 100644
--- a/chrome/browser/resources/chromeos/accessibility/chromevox/panel/menu_manager.js
+++ b/chrome/browser/resources/chromeos/accessibility/chromevox/panel/menu_manager.ts
@@ -18,7 +18,7 @@
 import {EventSourceType} from '../common/event_source_type.js';
 import {GestureCommandData} from '../common/gesture_command_data.js';
 import {KeyMap} from '../common/key_map.js';
-import {KeySequence} from '../common/key_sequence.js';
+import {KeyBinding} from '../common/key_sequence.js';
 import {KeyUtil} from '../common/key_util.js';
 import {Msgs} from '../common/msgs.js';
 import {ALL_PANEL_MENU_NODE_DATA, PanelNodeMenuData, PanelNodeMenuId, PanelNodeMenuItemData} from '../common/panel_menu_data.js';
@@ -27,52 +27,30 @@
 import {PanelMenu, PanelNodeMenu, PanelSearchMenu} from './panel_menu.js';
 import {PanelMode} from './panel_mode.js';
 
-// TODO(anastasi): Import these types from key_sequence.js once this file is
-// converted to TypeScript.
+const $ = (id: string): HTMLElement | null => document.getElementById(id);
 
-/**
- * @typedef {{
-*   command: !Command,
-*   sequence: !KeySequence,
-*   keySeq: (string|undefined),
-*   title: (string|undefined),
-* }}
-*/
-let KeyBinding;
-
-const $ = (id) => document.getElementById(id);
+interface TouchMenuData {
+  titleText: string;
+  gestureText: string;
+  command: Command;
+}
 
 export class MenuManager {
-  constructor() {
-    /**
-     * The currently active menu, if any.
-     * @private {?PanelMenu}
-     */
-    this.activeMenu_ = null;
+  private activeMenu_: PanelMenu | null = null;
+  private lastMenu_ = '';
+  private menus_: PanelMenu[] = [];
+  private nodeMenuDictionary_: Record<PanelNodeMenuId, PanelNodeMenu> = {};
+  private searchMenu_: PanelSearchMenu | null = null;
 
-    /** @private {string} */
-    this.lastMenu_ = '';
-
-    /**
-     * The array of top-level menus.
-     * @private {!Array<!PanelMenu>}
-     */
-    this.menus_ = [];
-
-    /** @private {!Object<!PanelNodeMenuId, !PanelNodeMenu>} */
-    this.nodeMenuDictionary_ = {};
-
-    /** @private {?PanelSearchMenu} */
-    this.searchMenu_ = null;
-  }
+  static disableMissingMsgsErrorsForTesting = false;
 
   /**
    * Activate a menu, which implies hiding the previous active menu.
-   * @param {?PanelMenu} menu The new menu to activate.
-   * @param {boolean} activateFirstItem Whether or not we should activate the
+   * @param menu The new menu to activate.
+   * @param activateFirstItem Whether or not we should activate the
    *     menu's first item.
    */
-  activateMenu(menu, activateFirstItem) {
+  activateMenu(menu: PanelMenu | null, activateFirstItem: boolean): void {
     if (menu === this.activeMenu_) {
       return;
     }
@@ -83,18 +61,17 @@
     }
 
     this.activeMenu_ = menu;
-    PanelInterface.instance.setPendingCallback(null);
+    // TODO(b/314203187): Not null asserted, check that this is correct.
+    PanelInterface.instance!.setPendingCallback(null);
 
     if (this.activeMenu_) {
       this.activeMenu_.activate(activateFirstItem);
     }
   }
 
-  /**
-   * @param {!PanelMenu} actionsMenu
-   * @param {!Map<!Command, !KeyBinding>} bindingMap
-   */
-  async addActionsMenuItems(actionsMenu, bindingMap) {
+  async addActionsMenuItems(
+      actionsMenu: PanelMenu,
+      bindingMap: Map<Command, KeyBinding>): Promise<void> {
     const actions =
         await BackgroundBridge.PanelBackground.getActionsForCurrentNode();
     for (const standardAction of actions.standardActions) {
@@ -106,7 +83,7 @@
       let shortcutName = '';
       if (commandName) {
         const commandBinding = bindingMap.get(commandName);
-        shortcutName = commandBinding ? commandBinding.keySeq : '';
+        shortcutName = commandBinding ? commandBinding.keySeq as string : '';
       }
       const actionDesc = Msgs.getMsg(actionMsg);
       actionsMenu.addMenuItem(
@@ -127,33 +104,29 @@
 
   /**
    * Create a new menu with the given name and add it to the menu bar.
-   * @param {string} menuMsg The msg id of the new menu to add.
-   * @return {!PanelMenu} The menu just created.
+   * @param menuMsg The msg id of the new menu to add.
+   * @return The menu just created.
    */
-  addMenu(menuMsg) {
+  addMenu(menuMsg: string): PanelMenu {
     const menu = new PanelMenu(menuMsg);
-    $('menu-bar').appendChild(menu.menuBarItemElement);
+    $('menu-bar')!.appendChild(menu.menuBarItemElement);
     menu.menuBarItemElement.addEventListener(
         'mouseover',
         () => this.activateMenu(menu, true /* activateFirstItem */), false);
     menu.menuBarItemElement.addEventListener(
         'mouseup', event => this.onMouseUpOnMenuTitle(menu, event), false);
-    $('menus_background').appendChild(menu.menuContainerElement);
+    $('menus_background')!.appendChild(menu.menuContainerElement);
     this.menus_.push(menu);
     return menu;
   }
 
-  /**
-   * @param {!KeyBinding} binding
-   * @param {PanelMenu} menu
-   * @param {boolean} isTouchScreen
-   */
-  addMenuItemFromKeyBinding(binding, menu, isTouchScreen) {
+  addMenuItemFromKeyBinding(
+      binding: KeyBinding, menu: PanelMenu | null,
+      isTouchScreen: boolean): void {
     if (!binding.title || !menu) {
       return;
     }
 
-    const gestures = Object.keys(GestureCommandData.GESTURE_COMMAND_MAP);
     let keyText;
     let brailleText;
     let gestureText;
@@ -176,28 +149,26 @@
 
   /**
    * Create a new node menu with the given name and add it to the menu bar.
-   * @param {!PanelNodeMenuData} menuData The title/predicate for the new menu.
+   * @param menuData The title/predicate for the new menu.
    */
-  addNodeMenu(menuData) {
+  addNodeMenu(menuData: PanelNodeMenuData): void {
     const menu = new PanelNodeMenu(menuData.titleId);
-    $('menu-bar').appendChild(menu.menuBarItemElement);
+    $('menu-bar')!.appendChild(menu.menuBarItemElement);
     menu.menuBarItemElement.addEventListener(
         'mouseover',
         () => this.activateMenu(menu, true /* activateFirstItem */));
     menu.menuBarItemElement.addEventListener(
         'mouseup', event => this.onMouseUpOnMenuTitle(menu, event));
-    $('menus_background').appendChild(menu.menuContainerElement);
+    $('menus_background')!.appendChild(menu.menuContainerElement);
     this.menus_.push(menu);
     this.nodeMenuDictionary_[menuData.menuId] = menu;
   }
 
-  /** @param {!PanelNodeMenuItemData} itemData */
-  addNodeMenuItem(itemData) {
+  addNodeMenuItem(itemData: PanelNodeMenuItemData): void {
     this.nodeMenuDictionary_[itemData.menuId].addItemFromData(itemData);
   }
 
-  /** @param {!PanelMenu} menu */
-  async addOSKeyboardShortcutsMenuItem(menu) {
+  async addOSKeyboardShortcutsMenuItem(menu: PanelMenu): Promise<void> {
     let localizedSlash =
         await AsyncUtil.getLocalizedDomKeyStringForKeyCode(KeyCode.OEM_2);
     if (!localizedSlash) {
@@ -213,14 +184,15 @@
 
   /**
    * Create a new search menu with the given name and add it to the menu bar.
-   * @param {string} menuMsg The msg id of the new menu to add.
-   * @return {!PanelMenu} The menu just created.
+   * @param menuMsg The msg id of the new menu to add.
+   * @return The menu just created.
    */
-  addSearchMenu(menuMsg) {
+  addSearchMenu(menuMsg: string): PanelMenu {
     this.searchMenu_ = new PanelSearchMenu(menuMsg);
     // Add event listeners to search bar.
     this.searchMenu_.searchBar.addEventListener(
-        'input', event => this.onSearchBarQuery(event), false);
+        'input',
+        (event: Event) => this.onSearchBarQuery(event as InputEvent), false);
     this.searchMenu_.searchBar.addEventListener('mouseup', event => {
       // Clicking in the panel causes us to either activate an item or close the
       // menus altogether. Prevent that from happening if we click the search
@@ -229,23 +201,22 @@
       event.stopPropagation();
     }, false);
 
-    $('menu-bar').appendChild(this.searchMenu_.menuBarItemElement);
+    $('menu-bar')!.appendChild(this.searchMenu_.menuBarItemElement);
     this.searchMenu_.menuBarItemElement.addEventListener(
         'mouseover',
         () =>
             this.activateMenu(this.searchMenu_, false /* activateFirstItem */),
         false);
     this.searchMenu_.menuBarItemElement.addEventListener(
-        'mouseup', event => this.onMouseUpOnMenuTitle(this.searchMenu_, event),
+        'mouseup', event => this.onMouseUpOnMenuTitle(this.searchMenu_!, event),
         false);
-    $('menus_background').appendChild(this.searchMenu_.menuContainerElement);
+    $('menus_background')!.appendChild(this.searchMenu_.menuContainerElement);
     this.menus_.push(this.searchMenu_);
     return this.searchMenu_;
   }
 
-  /** @param {!PanelMenu} touchMenu */
-  addTouchGestureMenuItems(touchMenu) {
-    const touchGestureItems = [];
+  addTouchGestureMenuItems(touchMenu: PanelMenu): void {
+    const touchGestureItems: TouchMenuData[] = [];
     for (const data of Object.values(GestureCommandData.GESTURE_COMMAND_MAP)) {
       const command = data.command;
       if (!command) {
@@ -278,9 +249,9 @@
 
   /**
    * Advance the index of the current active menu by |delta|.
-   * @param {number} delta The number to add to the active menu index.
+   * @param delta The number to add to the active menu index.
    */
-  advanceActiveMenuBy(delta) {
+  advanceActiveMenuBy(delta: number): void {
     let activeIndex = this.menus_.findIndex(menu => menu === this.activeMenu_);
 
     if (activeIndex >= 0) {
@@ -306,24 +277,25 @@
    * Clear any previous menus. The menus are all regenerated each time the
    * menus are opened.
    */
-  clearMenus() {
+  clearMenus(): void {
     while (this.menus_.length) {
       const menu = this.menus_.pop();
-      $('menu-bar').removeChild(menu.menuBarItemElement);
-      $('menus_background').removeChild(menu.menuContainerElement);
+      $('menu-bar')!.removeChild(menu!.menuBarItemElement);
+      $('menus_background')!.removeChild(menu!.menuContainerElement);
+
+      if (this.activeMenu_) {
+        this.lastMenu_ = this.activeMenu_.menuMsg;
+      }
+      this.activeMenu_ = null;
     }
-    if (this.activeMenu_) {
-      this.lastMenu_ = this.activeMenu_.menuMsg;
-    }
-    this.activeMenu_ = null;
   }
 
   /** Disables menu items that are prohibited without a signed-in user. */
-  denySignedOut() {
+  denySignedOut(): void {
     for (const menu of this.menus_) {
       for (const item of menu.items) {
-        if (CommandStore.denySignedOut(
-                /** @type {!Command} */ (item.element.id))) {
+        // TODO(b/314203187): Not null asserted, check that this is correct.
+        if (CommandStore.denySignedOut(item.element!.id as Command)) {
           item.disable();
         }
       }
@@ -332,11 +304,9 @@
 
   /**
    * Starting at |startIndex|, looks for an enabled menu.
-   * @param {number} startIndex
-   * @param {number} delta
-   * @return {number} The index of the enabled menu. -1 if not found.
+   * @return The index of the enabled menu. -1 if not found.
    */
-  findEnabledMenuIndex(startIndex, delta) {
+  findEnabledMenuIndex(startIndex: number, delta: number): number {
     const endIndex = (delta > 0) ? this.menus_.length : -1;
     while (startIndex !== endIndex) {
       if (this.menus_[startIndex].enabled) {
@@ -349,29 +319,25 @@
 
   /**
    * Get the callback for whatever item is currently selected.
-   * @return {?Function} The callback for the current item.
+   * @return The callback for the current item.
+   *
+   * TODO(b/267329383): Specify this as Promise<void> once PanelMenu
+   * is converted to typescript.
    */
-  getCallbackForCurrentItem() {
+  getCallbackForCurrentItem(): (() => Promise<any>) | null{
     if (this.activeMenu_) {
       return this.activeMenu_.getCallbackForCurrentItem();
     }
     return null;
   }
 
-  /**
-   * @param {string|undefined} opt_menuTitle
-   * @return {!PanelMenu}
-   */
-  getSelectedMenu(opt_menuTitle) {
+  getSelectedMenu(menuTitle?: string): PanelMenu {
     const specifiedMenu =
-        this.menus_.find(menu => menu.menuMsg === opt_menuTitle);
+        this.menus_.find(menu => menu.menuMsg === menuTitle);
     return specifiedMenu || this.searchMenu_ || this.menus_[0];
   }
 
-  /**
-   * @return {!Promise<!Array<!KeyBinding>>}
-   */
-  async getSortedKeyBindings() {
+  async getSortedKeyBindings(): Promise<KeyBinding[]> {
     // TODO(accessibility): Commands should be based off of CommandStore and
     // not the keymap. There are commands that don't have a key binding (e.g.
     // commands for touch).
@@ -398,18 +364,13 @@
     }
     sortedBindings.sort(
         (binding1, binding2) =>
-            binding1.title.localeCompare(String(binding2.title)));
+            binding1.title!.localeCompare(String(binding2.title)));
     return sortedBindings;
   }
 
-  /**
-   * @param {!PanelMenu} actionsMenu
-   * @param {!PanelMenu} chromevoxMenu
-   * @param {!PanelMenu} jumpMenu
-   * @param {!PanelMenu} speechMenu
-   * @return {!Object<!CommandCategory, ?PanelMenu>}
-   */
-  makeCategoryMapping(actionsMenu, chromevoxMenu, jumpMenu, speechMenu) {
+  makeCategoryMapping(
+      actionsMenu: PanelMenu, chromevoxMenu: PanelMenu, jumpMenu: PanelMenu,
+      speechMenu: PanelMenu): Record<CommandCategory, PanelMenu | null> {
     return {
       [CommandCategory.ACTIONS]: actionsMenu,
       [CommandCategory.BRAILLE]: null,
@@ -425,11 +386,8 @@
       [CommandCategory.TABLES]: jumpMenu,
     };
   }
-  /**
-   * @param {!Array<!KeyBinding>} sortedBindings
-   * @return {!Map<!Command, !KeyBinding>}
-   */
-  makeBindingMap(sortedBindings) {
+
+  makeBindingMap(sortedBindings: KeyBinding[]): Map<Command, KeyBinding> {
     const bindingMap = new Map();
     for (const binding of sortedBindings) {
       bindingMap.set(binding.command, binding);
@@ -440,10 +398,10 @@
   /**
    * Activate a menu whose title has been clicked. Stop event propagation at
    * this point so we don't close the ChromeVox menus and restore focus.
-   * @param {PanelMenu} menu The menu we would like to activate.
-   * @param {Event} mouseUpEvent The mouseup event.
+   * @param menu The menu we would like to activate.
+   * @param mouseUpEvent The mouseup event.
    */
-  onMouseUpOnMenuTitle(menu, mouseUpEvent) {
+  onMouseUpOnMenuTitle(menu: PanelMenu, mouseUpEvent: MouseEvent): void {
     this.activateMenu(menu, true /* activateFirstItem */);
     mouseUpEvent.preventDefault();
     mouseUpEvent.stopPropagation();
@@ -451,25 +409,27 @@
 
   /**
    * Open / show the ChromeVox Menus.
-   * @param {Event=} opt_event An optional event that triggered this.
-   * @param {string=} opt_activateMenuTitle Title msg id of menu to open.
+   * @param {Event=} event An optional event that triggered this.
+   * @param {string=} activateMenuTitle?: string Title msg id of menu to open.
    */
-  async onOpenMenus(opt_event, opt_activateMenuTitle) {
+  async onOpenMenus(event?: Event, activateMenuTitle?: string): Promise<void> {
     // If the menu was already open, close it now and exit early.
-    if (PanelInterface.instance.mode !== PanelMode.COLLAPSED) {
-      PanelInterface.instance.setMode(PanelMode.COLLAPSED);
+    // TODO(b/314203187): Not null asserted, check that this is correct.
+    if (PanelInterface.instance!.mode !== PanelMode.COLLAPSED) {
+      PanelInterface.instance!.setMode(PanelMode.COLLAPSED);
       return;
     }
 
     // Eat the event so that a mousedown isn't turned into a drag, allowing
     // users to click-drag-release to select a menu item.
-    if (opt_event) {
-      opt_event.stopPropagation();
-      opt_event.preventDefault();
+    if (event) {
+      event.stopPropagation();
+      event.preventDefault();
     }
 
     await BackgroundBridge.PanelBackground.saveCurrentNode();
-    PanelInterface.instance.setMode(PanelMode.FULLSCREEN_MENUS);
+    // TODO(b/314203187): Not null asserted, check that this is correct.
+    PanelInterface.instance!.setMode(PanelMode.FULLSCREEN_MENUS);
 
     // The panel does not get focus immediately when we request to be full
     // screen (handled in ChromeVoxPanel natively on hash changed). Wait, if
@@ -482,7 +442,7 @@
     const touchScreen = (eventSource === EventSourceType.TOUCH_GESTURE);
 
     // Build the top-level menus.
-    const searchMenu = this.addSearchMenu('panel_search_menu');
+    this.addSearchMenu('panel_search_menu');
     const jumpMenu = this.addMenu('panel_menu_jump');
     const speechMenu = this.addMenu('panel_menu_speech');
     const touchMenu =
@@ -516,25 +476,27 @@
       this.addTouchGestureMenuItems(touchMenu);
     }
 
-    if (PanelInterface.instance.sessionState !== 'IN_SESSION') {
+    // TODO(b/314203187): Not null asserted, check that this is correct.
+    if (PanelInterface.instance!.sessionState !== 'IN_SESSION') {
       this.denySignedOut();
     }
 
     // Add a menu item that disables / closes ChromeVox.
+    // TODO(b/314203187): Not null asserted, check that this is correct.
     chromevoxMenu.addMenuItem(
         Msgs.getMsg('disable_chromevox'), 'Ctrl+Alt+Z', '', '',
-        async () => PanelInterface.instance.onClose());
+        async () => PanelInterface.instance!.onClose());
 
     for (const menuData of ALL_PANEL_MENU_NODE_DATA) {
       this.addNodeMenu(menuData);
     }
     await BackgroundBridge.PanelBackground.createAllNodeMenuBackgrounds(
-        opt_activateMenuTitle);
+        activateMenuTitle);
 
     await this.addActionsMenuItems(actionsMenu, bindingMap);
 
     // Activate either the specified menu or the search menu.
-    const selectedMenu = this.getSelectedMenu(opt_activateMenuTitle);
+    const selectedMenu = this.getSelectedMenu(activateMenuTitle);
 
     const activateFirstItem = (selectedMenu !== this.searchMenu);
     this.activateMenu(selectedMenu, activateFirstItem);
@@ -544,13 +506,13 @@
    * Listens to changes in the menu search bar. Populates the search menu
    * with items that match the search bar's contents.
    * Note: we ignore PanelNodeMenu items and items without shortcuts.
-   * @param {Event} event The input event.
+   * @param event The input event.
    */
-  onSearchBarQuery(event) {
+  onSearchBarQuery(event: InputEvent): void {
     if (!this.searchMenu_) {
       throw Error('MenuManager.searchMenu_ must be defined');
     }
-    const query = event.target.value.toLowerCase();
+    const query = (event.target as HTMLInputElement).value.toLowerCase();
     this.searchMenu_.clear();
     // Show the search results menu.
     this.activateMenu(this.searchMenu_, false /* activateFirstItem */);
@@ -579,7 +541,8 @@
 
     if (this.searchMenu_.items.length === 0) {
       this.searchMenu_.addMenuItem(
-          Msgs.getMsg('panel_menu_item_none'), '', '', '', function() {});
+          Msgs.getMsg(
+              'panel_menu_item_none'), '', '', '', () => Promise.resolve());
     }
     this.searchMenu_.activateItem(0);
   }
@@ -587,48 +550,36 @@
   // The following getters and setters are temporary during the migration from
   // panel.js.
 
-  /** @return {?PanelMenu} */
-  get activeMenu() {
+  get activeMenu(): PanelMenu | null {
     return this.activeMenu_;
   }
-  /** @param {?PanelMenu} menu */
-  set activeMenu(menu) {
+  set activeMenu(menu: PanelMenu | null) {
     this.activeMenu_ = menu;
   }
 
-  /** @return {string} */
-  get lastMenu() {
+  get lastMenu(): string {
     return this.lastMenu_;
   }
-  /** @param {string} menuMsg */
-  set lastMenu(menuMsg) {
+  set lastMenu(menuMsg: string) {
     this.lastMenu_ = menuMsg;
   }
 
-  /** @return {!Array<!PanelMenu>} */
-  get menus() {
+  get menus(): PanelMenu[] {
     return this.menus_;
   }
 
-  /** @return {!Object<!PanelNodeMenuId, !PanelNodeMenu>} */
-  get nodeMenuDictionary() {
+  get nodeMenuDictionary(): Record<PanelNodeMenuId, PanelNodeMenu> {
     return this.nodeMenuDictionary_;
   }
 
-  /** @return {?PanelSearchMenu} */
-  get searchMenu() {
+  get searchMenu(): PanelSearchMenu | null {
     return this.searchMenu_;
   }
-  /** @param {?PanelSearchMenu} menu */
-  set searchMenu(menu) {
+  set searchMenu(menu: PanelSearchMenu | null) {
     this.searchMenu_ = menu;
   }
 }
 
-
-/** @type {boolean} */
-MenuManager.disableMissingMsgsErrorsForTesting = false;
-
 // Local to module.
 
 const COMMANDS_WITH_NO_MSG_ID = [
@@ -646,7 +597,7 @@
   'copy',
 ];
 
-const ACTION_TO_MSG_ID = {
+const ACTION_TO_MSG_ID: Record<string, string> = {
   decrement: 'action_decrement_description',
   doDefault: 'perform_default_action',
   increment: 'action_increment_description',
@@ -656,7 +607,7 @@
   longClick: 'force_long_click_on_current_item',
 };
 
-async function waitForWindowFocus() {
+async function waitForWindowFocus(): Promise<any> {
   return new Promise(
       resolve => window.addEventListener('focus', resolve, {once: true}));
 }
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/panel/panel_menu.ts b/chrome/browser/resources/chromeos/accessibility/chromevox/panel/panel_menu.ts
index 888d97a..36d0e201 100644
--- a/chrome/browser/resources/chromeos/accessibility/chromevox/panel/panel_menu.ts
+++ b/chrome/browser/resources/chromeos/accessibility/chromevox/panel/panel_menu.ts
@@ -12,7 +12,7 @@
 
 import {PanelMenuItem} from './panel_menu_item.js';
 
-type MenuCallback = () => Promise<void>;
+type MenuCallback = () => Promise<any>;
 
 export class PanelMenu {
   menuBarItemElement: HTMLDivElement;
diff --git a/chrome/browser/resources/new_tab_page/lazy_load.ts b/chrome/browser/resources/new_tab_page/lazy_load.ts
index 1fbdb38..dbee2aa 100644
--- a/chrome/browser/resources/new_tab_page/lazy_load.ts
+++ b/chrome/browser/resources/new_tab_page/lazy_load.ts
@@ -46,7 +46,8 @@
 export {DisableModuleEvent, DismissModuleEvent, ModulesElement} from './modules/modules.js';
 export {photosDescriptor, PhotosModuleElement} from './modules/photos/module.js';
 export {PhotosProxy} from './modules/photos/photos_module_proxy.js';
-export {CalendarModuleElement, googleCalendarDescriptor, outlookCalendarDescriptor} from './modules/v2/calendar/module.js';
+export {googleCalendarDescriptor, GoogleCalendarModuleElement} from './modules/v2/calendar/google_calendar_module.js';
+export {outlookCalendarDescriptor, OutlookCalendarModuleElement} from './modules/v2/calendar/outlook_calendar_module.js';
 // <if expr="not is_official_build">
 export {FooProxy} from './modules/v2/dummy/foo_proxy.js';
 export {DummyModuleElement, dummyV2Descriptor} from './modules/v2/dummy/module.js';
diff --git a/chrome/browser/resources/new_tab_page/modules/module_descriptors.ts b/chrome/browser/resources/new_tab_page/modules/module_descriptors.ts
index e90786ca..463791e 100644
--- a/chrome/browser/resources/new_tab_page/modules/module_descriptors.ts
+++ b/chrome/browser/resources/new_tab_page/modules/module_descriptors.ts
@@ -17,7 +17,8 @@
 import type {ModuleDescriptor} from './module_descriptor.js';
 import {ModuleRegistry} from './module_registry.js';
 import {photosDescriptor} from './photos/module.js';
-import {googleCalendarDescriptor, outlookCalendarDescriptor} from './v2/calendar/module.js';
+import {googleCalendarDescriptor} from './v2/calendar/google_calendar_module.js';
+import {outlookCalendarDescriptor} from './v2/calendar/outlook_calendar_module.js';
 // <if expr="not is_official_build">
 import {dummyV2Descriptor} from './v2/dummy/module.js';
 // </if>
diff --git a/chrome/browser/resources/new_tab_page/modules/v2/calendar/calendar.gni b/chrome/browser/resources/new_tab_page/modules/v2/calendar/calendar.gni
index fe431c9..bd7f6cc2 100644
--- a/chrome/browser/resources/new_tab_page/modules/v2/calendar/calendar.gni
+++ b/chrome/browser/resources/new_tab_page/modules/v2/calendar/calendar.gni
@@ -3,4 +3,7 @@
 # found in the LICENSE file.
 
 # List of files that should be passed to html_to_wrapper().
-calendar_v2_web_component_files = [ "modules/v2/calendar/module.ts" ]
+calendar_v2_web_component_files = [
+  "modules/v2/calendar/google_calendar_module.ts",
+  "modules/v2/calendar/outlook_calendar_module.ts",
+]
diff --git a/chrome/browser/resources/new_tab_page/modules/v2/calendar/module.html b/chrome/browser/resources/new_tab_page/modules/v2/calendar/google_calendar_module.html
similarity index 61%
rename from chrome/browser/resources/new_tab_page/modules/v2/calendar/module.html
rename to chrome/browser/resources/new_tab_page/modules/v2/calendar/google_calendar_module.html
index d0dc56d6..ff40a776 100644
--- a/chrome/browser/resources/new_tab_page/modules/v2/calendar/module.html
+++ b/chrome/browser/resources/new_tab_page/modules/v2/calendar/google_calendar_module.html
@@ -1,10 +1,10 @@
 <ntp-module-header-v2
     id="moduleHeaderElementV2"
-    header-text="[[sourceTitle_]]"
-    menu-item-groups="[[getMenuItemGroups_(sourceDisableText_)]]"
+    header-text="[[i18n('modulesTodayCalendarHeader')]]"
+    menu-item-groups="[[getMenuItemGroups_()]]"
     more-actions-text="[[i18nRecursive('',
                           'modulesMoreActions',
-                          sourceTitle_)]]"
+                          'modulesTodayCalendarHeader')]]"
     on-disable-button-click="onDisableButtonClick_"
     on-menu-button-click="onMenuButtonClick_">
 </ntp-module-header-v2>
\ No newline at end of file
diff --git a/chrome/browser/resources/new_tab_page/modules/v2/calendar/google_calendar_module.ts b/chrome/browser/resources/new_tab_page/modules/v2/calendar/google_calendar_module.ts
new file mode 100644
index 0000000..182c708
--- /dev/null
+++ b/chrome/browser/resources/new_tab_page/modules/v2/calendar/google_calendar_module.ts
@@ -0,0 +1,85 @@
+// Copyright 2024 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import '../../module_header.js';
+
+import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+
+import {I18nMixin, loadTimeData} from '../../../i18n_setup.js';
+import {ModuleDescriptor} from '../../module_descriptor.js';
+import type {MenuItem, ModuleHeaderElementV2} from '../module_header.js';
+
+import {getTemplate} from './google_calendar_module.html.js';
+
+export interface GoogleCalendarModuleElement {
+  $: {
+    moduleHeaderElementV2: ModuleHeaderElementV2,
+  };
+}
+
+/**
+ * The Google Calendar module, which serves as an inside look in to upcoming
+ * events on a user's Google Calendar .
+ */
+export class GoogleCalendarModuleElement extends I18nMixin
+(PolymerElement) {
+  static get is() {
+    return 'ntp-google-calendar-module';
+  }
+
+  static get template() {
+    return getTemplate();
+  }
+
+  static get properties() {
+    return {};
+  }
+
+  private getMenuItemGroups_(): MenuItem[][] {
+    return [
+      [
+        {
+          action: 'disable',
+          icon: 'modules:block',
+          text: this.i18n('modulesTodayCalendarDisableButtonText'),
+        },
+      ],
+      [
+        {
+          action: 'customize-module',
+          icon: 'modules:tune',
+          text: this.i18n('modulesCustomizeButtonText'),
+        },
+      ],
+    ];
+  }
+
+  private onDisableButtonClick_() {
+    const disableEvent = new CustomEvent('disable-module', {
+      composed: true,
+      detail: {
+        message: loadTimeData.getStringF(
+            'disableModuleToastMessage',
+            loadTimeData.getString('modulesTodayCalendarHeader')),
+      },
+    });
+    this.dispatchEvent(disableEvent);
+  }
+
+  private onMenuButtonClick_(e: Event) {
+    this.$.moduleHeaderElementV2.showAt(e);
+  }
+}
+
+customElements.define(
+    GoogleCalendarModuleElement.is, GoogleCalendarModuleElement);
+
+async function createGoogleCalendarElement():
+    Promise<GoogleCalendarModuleElement|null> {
+  return new Promise<GoogleCalendarModuleElement>(
+      (resolve) => resolve(new GoogleCalendarModuleElement()));
+}
+
+export const googleCalendarDescriptor: ModuleDescriptor = new ModuleDescriptor(
+    /*id*/ 'google_calendar', createGoogleCalendarElement);
diff --git a/chrome/browser/resources/new_tab_page/modules/v2/calendar/module.ts b/chrome/browser/resources/new_tab_page/modules/v2/calendar/module.ts
deleted file mode 100644
index d697a07..0000000
--- a/chrome/browser/resources/new_tab_page/modules/v2/calendar/module.ts
+++ /dev/null
@@ -1,126 +0,0 @@
-// 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 '../../module_header.js';
-
-import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
-
-import {I18nMixin, loadTimeData} from '../../../i18n_setup.js';
-import {ModuleDescriptor} from '../../module_descriptor.js';
-import type {MenuItem, ModuleHeaderElementV2} from '../module_header.js';
-
-import {getTemplate} from './module.html.js';
-
-export enum CalendarSource {
-  GOOGLE,
-  OUTLOOK,
-}
-
-export interface CalendarModuleElement {
-  $: {
-    moduleHeaderElementV2: ModuleHeaderElementV2,
-  };
-}
-
-/**
- * The Calendar module, which serves as an inside look in to upcoming events on
- * a user's Google Calendar or Microsoft Outlook.
- */
-export class CalendarModuleElement extends I18nMixin
-(PolymerElement) {
-  static get is() {
-    return 'ntp-calendar-module';
-  }
-
-  static get template() {
-    return getTemplate();
-  }
-
-  static get properties() {
-    return {
-      calendarSource_: {
-        type: Object,
-        observer: 'setSourceText_',
-      },
-      sourceDisableText_: String,
-      sourceTitle_: String,
-    };
-  }
-
-  private calendarSource_: CalendarSource;
-  private sourceDisableText_: string = '';
-  private sourceTitle_: string = '';
-
-  constructor(calendarSource: CalendarSource) {
-    super();
-    this.calendarSource_ = calendarSource;
-    this.setSourceText_();
-  }
-
-  private setSourceText_() {
-    switch(this.calendarSource_) {
-      case CalendarSource.GOOGLE:
-        this.sourceTitle_ = this.i18n('modulesGoogleCalendarTitle');
-        this.sourceDisableText_ =
-            this.i18n('modulesGoogleCalendarDisableButtonText');
-        break;
-      case CalendarSource.OUTLOOK:
-        this.sourceTitle_ = this.i18n('modulesOutlookCalendarTitle');
-        this.sourceDisableText_ =
-            this.i18n('modulesOutlookCalendarDisableButtonText');
-        break;
-    }
-  }
-
-  private getMenuItemGroups_(): MenuItem[][] {
-    return [
-      [
-        {
-          action: 'disable',
-          icon: 'modules:block',
-          text: this.sourceDisableText_,
-        },
-      ],
-      [
-        {
-          action: 'customize-module',
-          icon: 'modules:tune',
-          text: this.i18n('modulesCustomizeButtonText'),
-        },
-      ],
-    ];
-  }
-
-  private onDisableButtonClick_() {
-    const disableEvent = new CustomEvent('disable-module', {
-      composed: true,
-      detail: {
-        message: loadTimeData.getStringF(
-            'disableModuleToastMessage',
-            this.sourceTitle_),
-      },
-    });
-    this.dispatchEvent(disableEvent);
-  }
-
-  private onMenuButtonClick_(e: Event) {
-    this.$.moduleHeaderElementV2.showAt(e);
-  }
-}
-
-customElements.define(CalendarModuleElement.is, CalendarModuleElement);
-
-async function createCalendarElement(calendarSource: CalendarSource):
-    Promise<CalendarModuleElement|null> {
-  return new Promise<CalendarModuleElement>(
-      (resolve) => resolve(new CalendarModuleElement(calendarSource)));
-}
-
-export const googleCalendarDescriptor: ModuleDescriptor = new ModuleDescriptor(
-    /*id*/ 'google_calendar',
-    () => createCalendarElement(CalendarSource.GOOGLE));
-
-export const outlookCalendarDescriptor: ModuleDescriptor = new ModuleDescriptor(
-    /*id*/ 'outlook_calendar',
-    () => createCalendarElement(CalendarSource.OUTLOOK));
diff --git a/chrome/browser/resources/new_tab_page/modules/v2/calendar/module.html b/chrome/browser/resources/new_tab_page/modules/v2/calendar/outlook_calendar_module.html
similarity index 61%
copy from chrome/browser/resources/new_tab_page/modules/v2/calendar/module.html
copy to chrome/browser/resources/new_tab_page/modules/v2/calendar/outlook_calendar_module.html
index d0dc56d6..947d1d9f 100644
--- a/chrome/browser/resources/new_tab_page/modules/v2/calendar/module.html
+++ b/chrome/browser/resources/new_tab_page/modules/v2/calendar/outlook_calendar_module.html
@@ -1,10 +1,10 @@
 <ntp-module-header-v2
     id="moduleHeaderElementV2"
-    header-text="[[sourceTitle_]]"
-    menu-item-groups="[[getMenuItemGroups_(sourceDisableText_)]]"
+    header-text="[[i18n('modulesOutlookCalendarTitle')]]"
+    menu-item-groups="[[getMenuItemGroups_()]]"
     more-actions-text="[[i18nRecursive('',
                           'modulesMoreActions',
-                          sourceTitle_)]]"
+                          'modulesOutlookCalendarTitle')]]"
     on-disable-button-click="onDisableButtonClick_"
     on-menu-button-click="onMenuButtonClick_">
 </ntp-module-header-v2>
\ No newline at end of file
diff --git a/chrome/browser/resources/new_tab_page/modules/v2/calendar/outlook_calendar_module.ts b/chrome/browser/resources/new_tab_page/modules/v2/calendar/outlook_calendar_module.ts
new file mode 100644
index 0000000..cddba42
--- /dev/null
+++ b/chrome/browser/resources/new_tab_page/modules/v2/calendar/outlook_calendar_module.ts
@@ -0,0 +1,85 @@
+// Copyright 2024 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import '../../module_header.js';
+
+import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+
+import {I18nMixin, loadTimeData} from '../../../i18n_setup.js';
+import {ModuleDescriptor} from '../../module_descriptor.js';
+import type {MenuItem, ModuleHeaderElementV2} from '../module_header.js';
+
+import {getTemplate} from './outlook_calendar_module.html.js';
+
+export interface OutlookCalendarModuleElement {
+  $: {
+    moduleHeaderElementV2: ModuleHeaderElementV2,
+  };
+}
+
+/**
+ * The Outlook Calendar module, which serves as an inside look in to upcoming
+ * events on a user's Microsoft Outlook calendar.
+ */
+export class OutlookCalendarModuleElement extends I18nMixin
+(PolymerElement) {
+  static get is() {
+    return 'ntp-outlook-calendar-module';
+  }
+
+  static get template() {
+    return getTemplate();
+  }
+
+  static get properties() {
+    return {};
+  }
+
+  private getMenuItemGroups_(): MenuItem[][] {
+    return [
+      [
+        {
+          action: 'disable',
+          icon: 'modules:block',
+          text: this.i18n('modulesOutlookCalendarDisableButtonText'),
+        },
+      ],
+      [
+        {
+          action: 'customize-module',
+          icon: 'modules:tune',
+          text: this.i18n('modulesCustomizeButtonText'),
+        },
+      ],
+    ];
+  }
+
+  private onDisableButtonClick_() {
+    const disableEvent = new CustomEvent('disable-module', {
+      composed: true,
+      detail: {
+        message: loadTimeData.getStringF(
+            'disableModuleToastMessage',
+            loadTimeData.getString('modulesOutlookCalendarTitle')),
+      },
+    });
+    this.dispatchEvent(disableEvent);
+  }
+
+  private onMenuButtonClick_(e: Event) {
+    this.$.moduleHeaderElementV2.showAt(e);
+  }
+}
+
+customElements.define(
+    OutlookCalendarModuleElement.is, OutlookCalendarModuleElement);
+
+async function createOutlookCalendarElement():
+    Promise<OutlookCalendarModuleElement|null> {
+  return new Promise<OutlookCalendarModuleElement>(
+      (resolve) => resolve(new OutlookCalendarModuleElement()));
+}
+
+export const outlookCalendarDescriptor: ModuleDescriptor = new ModuleDescriptor(
+    /*id*/ 'outlook_calendar', createOutlookCalendarElement);
diff --git a/chrome/browser/resources/side_panel/customize_chrome/app.ts b/chrome/browser/resources/side_panel/customize_chrome/app.ts
index 341d9af..225cf3c 100644
--- a/chrome/browser/resources/side_panel/customize_chrome/app.ts
+++ b/chrome/browser/resources/side_panel/customize_chrome/app.ts
@@ -25,6 +25,7 @@
 import {getTemplate} from './app.html.js';
 import type {AppearanceElement} from './appearance.js';
 import type {CategoriesElement} from './categories.js';
+import {CustomizeChromeImpression, recordCustomizeChromeImpression} from './common.js';
 import type {BackgroundCollection, CustomizeChromePageHandlerInterface} from './customize_chrome.mojom-webui.js';
 import {ChromeWebStoreCategory, ChromeWebStoreCollection, CustomizeChromeSection} from './customize_chrome.mojom-webui.js';
 import {CustomizeChromeApiProxy} from './customize_chrome_api_proxy.js';
@@ -137,6 +138,22 @@
     // laid out.
     window.addEventListener('load', () => {
       CustomizeChromeApiProxy.getInstance().handler.updateScrollToSection();
+      // Install observer to log extension cards impression.
+      const extensionsCardSectionObserver =
+          new IntersectionObserver(([{intersectionRatio}]) => {
+            if (intersectionRatio >= 0.8) {
+              extensionsCardSectionObserver.disconnect();
+              this.dispatchEvent(
+                  new Event('detect-extensions-card-section-impression'));
+              recordCustomizeChromeImpression(
+                  CustomizeChromeImpression.EXTENSIONS_CARD_SECTION_DISPLAYED);
+            }
+          }, {
+            threshold: 1.0,
+          });
+      // Start observing if extension cards are scroll into view.
+      extensionsCardSectionObserver.observe(
+          this.shadowRoot!.querySelector('#extensions')!);
     }, {once: true});
   }
 
diff --git a/chrome/browser/resources/side_panel/customize_chrome/common.ts b/chrome/browser/resources/side_panel/customize_chrome/common.ts
index c5c3888..7309b00 100644
--- a/chrome/browser/resources/side_panel/customize_chrome/common.ts
+++ b/chrome/browser/resources/side_panel/customize_chrome/common.ts
@@ -42,3 +42,23 @@
       'NewTabPage.CustomizeChromeSidePanelAction', action,
       CustomizeChromeAction.MAX_VALUE + 1);
 }
+
+/**
+ * Customize Chrome impressions. This enum must match the numbering for
+ * NTPCustomizeChromeSidePanelImpression in enums.xml. These values are
+ * persisted to logs. Entries should not be renumbered, removed or reused.
+ *
+ * MAX_VALUE should always be at the end to help get the current number of
+ * buckets.
+ */
+export enum CustomizeChromeImpression {
+  EXTENSIONS_CARD_SECTION_DISPLAYED,
+  MAX_VALUE = EXTENSIONS_CARD_SECTION_DISPLAYED,
+}
+
+export function recordCustomizeChromeImpression(
+    action: CustomizeChromeImpression) {
+  chrome.metricsPrivate.recordEnumerationValue(
+      'NewTabPage.CustomizeChromeSidePanelImpression', action,
+      CustomizeChromeImpression.MAX_VALUE + 1);
+}
diff --git a/chrome/browser/resources/side_panel/read_anything/read_anything_toolbar.html b/chrome/browser/resources/side_panel/read_anything/read_anything_toolbar.html
index a668fa4e..c4d567a6 100644
--- a/chrome/browser/resources/side_panel/read_anything/read_anything_toolbar.html
+++ b/chrome/browser/resources/side_panel/read_anything/read_anything_toolbar.html
@@ -139,7 +139,7 @@
     <cr-icon-button
       class="toolbar-button"
       id="rate"
-      tabindex="-1"
+      tabindex$="[[getRateTabIndex_(isReadAloudPlayable)]]"
       aria-label="$i18n{voiceSpeedLabel}"
       title="$i18n{voiceSpeedLabel}"
       aria-haspopup="menu"
@@ -248,7 +248,8 @@
 
   <cr-lazy-render id="moreOptionsMenu">
     <template>
-      <cr-action-menu on-keydown="onToolbarKeyDown_">
+      <cr-action-menu on-keydown="onToolbarKeyDown_"
+                      role-description="$i18n{menu}">
         <template is="dom-repeat"
                   items="[[moreOptionsButtons_]]"
                   inital-count="0">
@@ -267,7 +268,8 @@
   </cr-lazy-render>
   <cr-lazy-render id="rateMenu">
     <template>
-      <cr-action-menu accessibility-label="$i18n{voiceSpeedLabel}">
+      <cr-action-menu accessibility-label="$i18n{voiceSpeedLabel}"
+                      role-description="$i18n{menu}">
         <template is="dom-repeat" items="[[rateOptions_]]" index-as="index"
                   initial-count="8">
           <button class="dropdown-item" on-click="onRateClick_">
@@ -283,7 +285,8 @@
   <cr-lazy-render id="fontSizeMenu">
     <template>
       <cr-action-menu on-keydown="onFontSizeMenuKeyDown_"
-          accessibility-label="$i18n{fontSizeTitle}">
+          accessibility-label="$i18n{fontSizeTitle}"
+          role-description="$i18n{menu}">
         <cr-icon-button
             class="font-size"
             id="font-size-decrease"
@@ -311,7 +314,8 @@
   </cr-lazy-render>
   <cr-lazy-render id="colorMenu">
     <template>
-      <cr-action-menu accessibility-label="$i18n{themeTitle}">
+      <cr-action-menu accessibility-label="$i18n{themeTitle}"
+                      role-description="$i18n{menu}">
         <template is="dom-repeat" items="[[colorOptions_]]" index-as="index"
                   initial-count="5">
           <button class="dropdown-item" on-click="onColorClick_">
@@ -327,7 +331,8 @@
   </cr-lazy-render>
   <cr-lazy-render id="lineSpacingMenu">
     <template>
-      <cr-action-menu accessibility-label="$i18n{lineSpacingTitle}">
+      <cr-action-menu accessibility-label="$i18n{lineSpacingTitle}"
+                      role-description="$i18n{menu}">
         <template is="dom-repeat"
                   items="[[lineSpacingOptions_]]" index-as="index"
                   initial-count="3">
@@ -344,7 +349,8 @@
   </cr-lazy-render>
   <cr-lazy-render id="letterSpacingMenu">
     <template>
-      <cr-action-menu accessibility-label="$i18n{letterSpacingTitle}">
+      <cr-action-menu accessibility-label="$i18n{letterSpacingTitle}"
+                      role-description="$i18n{menu}">
         <template is="dom-repeat"
                   items="[[letterSpacingOptions_]]" index-as="index"
                   initial-count="3">
@@ -361,7 +367,8 @@
   </cr-lazy-render>
   <cr-lazy-render id="fontMenu">
     <template>
-      <cr-action-menu accessibility-label="$i18n{fontNameTitle}">
+      <cr-action-menu accessibility-label="$i18n{fontNameTitle}"
+                      role-description="$i18n{menu}">
         <template is="dom-repeat" items="[[fontOptions_]]" index-as="index"
                   initial-count="8">
           <button class="dropdown-item" on-click="onFontClick_"
diff --git a/chrome/browser/resources/side_panel/read_anything/read_anything_toolbar.ts b/chrome/browser/resources/side_panel/read_anything/read_anything_toolbar.ts
index 48e60c7..2652444 100644
--- a/chrome/browser/resources/side_panel/read_anything/read_anything_toolbar.ts
+++ b/chrome/browser/resources/side_panel/read_anything/read_anything_toolbar.ts
@@ -347,6 +347,7 @@
   private areFontsLoaded_: boolean = false;
   private colorSuffix_: string = '';
 
+  private currentFocusId_: string = '';
   private toolbarContainerObserver_: ResizeObserver|null;
   private windowResizeCallback_: () => void;
 
@@ -840,10 +841,7 @@
   }
 
   private onToolbarKeyDown_(e: KeyboardEvent) {
-    const shadowRoot = this.shadowRoot;
-    assert(shadowRoot);
-    const toolbar = shadowRoot.getElementById('toolbarContainer');
-    assert(toolbar);
+    const toolbar = this.$.toolbarContainer;
     const buttons = Array.from(toolbar.querySelectorAll('.toolbar-button')) as
         HTMLElement[];
     assert(buttons, 'no toolbar buttons');
@@ -858,8 +856,7 @@
 
     // Allow focusing the font selection if it's visible.
     if (!this.isReadAloudEnabled_) {
-      const select = this.$.toolbarContainer.querySelector<HTMLSelectElement>(
-          '#font-select');
+      const select = toolbar.querySelector<HTMLSelectElement>('#font-select');
       assert(select, 'no font select menu');
       focusableElements.unshift(select);
     }
@@ -945,6 +942,7 @@
       el.tabIndex = -1;
     });
     elementToFocus.tabIndex = 0;
+    this.currentFocusId_ = elementToFocus.id;
 
     // Wait for the next animation frame for the overflow menu to show or hide.
     requestAnimationFrame(() => {
@@ -952,6 +950,10 @@
     });
   }
 
+  private getRateTabIndex_(isReadAloudPlayable: boolean): number {
+    return (!isReadAloudPlayable || this.currentFocusId_ === 'rate') ? 0 : -1;
+  }
+
   private onFontSelectKeyDown_(e: KeyboardEvent) {
     // The default behavior goes to the next select option. However, we want
     // to instead go to the next toolbar button (handled in onToolbarKeyDown_).
diff --git a/chrome/browser/resources/side_panel/read_anything/voice_selection_menu.html b/chrome/browser/resources/side_panel/read_anything/voice_selection_menu.html
index 0f6890aa..c5b1eff 100644
--- a/chrome/browser/resources/side_panel/read_anything/voice_selection_menu.html
+++ b/chrome/browser/resources/side_panel/read_anything/voice_selection_menu.html
@@ -73,7 +73,8 @@
 <cr-lazy-render id="voiceSelectionMenu">
   <template>
     <cr-action-menu on-close="onClose_" on-keydown="onVoiceMenuKeyDown_"
-        accessibility-label="$i18n{voiceSelectionLabel}">
+        accessibility-label="$i18n{voiceSelectionLabel}"
+        role-description="$i18n{menu}">
       <template is="dom-repeat"
                 items="[[downloadingMessages_]]"
                 initial-count="0">
diff --git a/chrome/browser/resources/side_panel/read_anything/voice_selection_menu.ts b/chrome/browser/resources/side_panel/read_anything/voice_selection_menu.ts
index fae908214..82db959f 100644
--- a/chrome/browser/resources/side_panel/read_anything/voice_selection_menu.ts
+++ b/chrome/browser/resources/side_panel/read_anything/voice_selection_menu.ts
@@ -103,6 +103,13 @@
       minX: parseInt(spBodyPadding, 10),
       maxX: document.body.clientWidth - parseInt(spBodyPadding, 10),
     });
+
+    // Scroll to the selected voice.
+    requestAnimationFrame(() => {
+      const selectedItem =
+          menu.querySelector<HTMLElement>('.item-invisible-false');
+      selectedItem?.scrollIntoViewIfNeeded();
+    });
   }
 
   private computeEnabledVoices_(
diff --git a/chrome/browser/safe_browsing/tailored_security/consented_message_android.cc b/chrome/browser/safe_browsing/tailored_security/consented_message_android.cc
index 1f01019..af44b77 100644
--- a/chrome/browser/safe_browsing/tailored_security/consented_message_android.cc
+++ b/chrome/browser/safe_browsing/tailored_security/consented_message_android.cc
@@ -56,36 +56,20 @@
   if (is_enable_message_) {
     title = l10n_util::GetStringUTF16(
         IDS_TAILORED_SECURITY_CONSENTED_ENABLE_MESSAGE_TITLE);
-    if (base::FeatureList::IsEnabled(
-            safe_browsing::kTailoredSecurityUpdatedMessages)) {
-      description = l10n_util::GetStringUTF16(
-          IDS_TAILORED_SECURITY_CONSENTED_ENABLE_MESSAGE_DESCRIPTION_UPDATED);
-      icon_resource_id =
-          ResourceMapper::MapToJavaDrawableId(IDR_ANDROID_MESSAGE_SHIELD_BLUE);
-      // Need to disable tint here because it removes a shade of blue from the
-      // shield which distorts the image.
-      message_->DisableIconTint();
-    } else {
-      description = l10n_util::GetStringUTF16(
-          IDS_TAILORED_SECURITY_CONSENTED_ENABLE_MESSAGE_DESCRIPTION);
-      icon_resource_id =
-          ResourceMapper::MapToJavaDrawableId(IDR_ANDROID_MESSAGE_SAFETY_CHECK);
-    }
+    description = l10n_util::GetStringUTF16(
+        IDS_TAILORED_SECURITY_CONSENTED_ENABLE_MESSAGE_DESCRIPTION);
+    icon_resource_id =
+        ResourceMapper::MapToJavaDrawableId(IDR_ANDROID_MESSAGE_SHIELD_BLUE);
+    // Need to disable tint here because it removes a shade of blue from the
+    // shield which distorts the image.
+    message_->DisableIconTint();
   } else {
     title = l10n_util::GetStringUTF16(
         IDS_TAILORED_SECURITY_CONSENTED_DISABLE_MESSAGE_TITLE);
-    if (base::FeatureList::IsEnabled(
-            safe_browsing::kTailoredSecurityUpdatedMessages)) {
-      description = l10n_util::GetStringUTF16(
-          IDS_TAILORED_SECURITY_CONSENTED_DISABLE_MESSAGE_DESCRIPTION_UPDATED);
-      icon_resource_id =
-          ResourceMapper::MapToJavaDrawableId(IDR_ANDROID_MESSAGE_SHIELD_GRAY);
-    } else {
-      description = l10n_util::GetStringUTF16(
-          IDS_TAILORED_SECURITY_CONSENTED_DISABLE_MESSAGE_DESCRIPTION);
-      icon_resource_id =
-          ResourceMapper::MapToJavaDrawableId(IDR_ANDROID_MESSAGE_SHIELD);
-    }
+    description = l10n_util::GetStringUTF16(
+        IDS_TAILORED_SECURITY_CONSENTED_DISABLE_MESSAGE_DESCRIPTION);
+    icon_resource_id =
+        ResourceMapper::MapToJavaDrawableId(IDR_ANDROID_MESSAGE_SHIELD_GRAY);
     message_->DisableIconTint();
   }
   message_->SetTitle(title);
diff --git a/chrome/browser/safe_browsing/tailored_security/notification_handler_desktop.cc b/chrome/browser/safe_browsing/tailored_security/notification_handler_desktop.cc
index af3ed5d..76a3b9d 100644
--- a/chrome/browser/safe_browsing/tailored_security/notification_handler_desktop.cc
+++ b/chrome/browser/safe_browsing/tailored_security/notification_handler_desktop.cc
@@ -126,13 +126,8 @@
       kTailoredSecurityUnconsentedPromotionNotificationId;
   const std::u16string& title = l10n_util::GetStringUTF16(
       IDS_TAILORED_SECURITY_UNCONSENTED_PROMOTION_NOTIFICATION_TITLE);
-  const std::u16string& description =
-      (base::FeatureList::IsEnabled(
-          safe_browsing::kTailoredSecurityUpdatedMessages))
-          ? l10n_util::GetStringUTF16(
-                IDS_TAILORED_SECURITY_UNCONSENTED_PROMOTION_NOTIFICATION_DESCRIPTION_UPDATED)
-          : l10n_util::GetStringUTF16(
-                IDS_TAILORED_SECURITY_UNCONSENTED_PROMOTION_NOTIFICATION_DESCRIPTION);
+  const std::u16string& description = l10n_util::GetStringUTF16(
+      IDS_TAILORED_SECURITY_UNCONSENTED_PROMOTION_NOTIFICATION_DESCRIPTION);
   const std::u16string& primary_button = l10n_util::GetStringUTF16(
       IDS_TAILORED_SECURITY_UNCONSENTED_PROMOTION_NOTIFICATION_ACCEPT);
   const std::u16string& secondary_button =
@@ -142,12 +137,7 @@
 #else
   const message_center::NotifierId notifier_id = GetNotifierId();
 #endif
-  auto icon = (base::FeatureList::IsEnabled(
-                  safe_browsing::kTailoredSecurityUpdatedMessages))
-                  ? GetNotificationIcon()
-                  : ui::ImageModel::FromVectorIcon(
-                        kSafetyCheckIcon, ui::kColorAccent,
-                        message_center::kNotificationIconSize);
+  auto icon = GetNotificationIcon();
   LogUnconsentedOutcome(TailoredSecurityOutcome::kShown);
   message_center::Notification notification(
       message_center::NOTIFICATION_TYPE_SIMPLE, notification_id, title,
diff --git a/chrome/browser/safe_browsing/tailored_security/unconsented_message_android.cc b/chrome/browser/safe_browsing/tailored_security/unconsented_message_android.cc
index 70bc444..b901958 100644
--- a/chrome/browser/safe_browsing/tailored_security/unconsented_message_android.cc
+++ b/chrome/browser/safe_browsing/tailored_security/unconsented_message_android.cc
@@ -47,10 +47,6 @@
   }
 }
 
-const int kAvatarSize = 256;
-const int kAvatarWithBorderSize = 300;
-const int kBadgeSize = 100;
-
 class CircleImageSource : public gfx::CanvasImageSource {
  public:
   CircleImageSource(int size, SkColor color)
@@ -104,63 +100,15 @@
   message_->SetTitle(l10n_util::GetStringUTF16(message_title));
   message_->SetPrimaryButtonText(l10n_util::GetStringUTF16(primary_button));
   if (!is_in_flow_) {
-    if (base::FeatureList::IsEnabled(
-            safe_browsing::kTailoredSecurityUpdatedMessages)) {
-      message_->SetDescription(l10n_util::GetStringUTF16(
-          IDS_TAILORED_SECURITY_UNCONSENTED_PROMOTION_MESSAGE_DESCRIPTION_UPDATED));
-    } else {
-      message_->SetDescription(l10n_util::GetStringUTF16(
-          IDS_TAILORED_SECURITY_UNCONSENTED_PROMOTION_MESSAGE_DESCRIPTION));
-    }
+    message_->SetDescription(l10n_util::GetStringUTF16(
+        IDS_TAILORED_SECURITY_UNCONSENTED_PROMOTION_MESSAGE_DESCRIPTION));
   }
 
-  if (base::FeatureList::IsEnabled(
-          safe_browsing::kTailoredSecurityUpdatedMessages)) {
-    message_->SetIconResourceId(
-        ResourceMapper::MapToJavaDrawableId(IDR_ANDROID_MESSAGE_SHIELD_BLUE));
-    // Need to disable tint here because it removes a shade of blue from the
-    // shield which distorts the image.
-    message_->DisableIconTint();
-  } else {
-    if (is_in_flow_) {
-      signin::IdentityManager* identity_manager =
-          IdentityManagerFactory::GetForProfile(
-              Profile::FromBrowserContext(web_contents_->GetBrowserContext()));
-      if (identity_manager &&
-          identity_manager->HasPrimaryAccount(signin::ConsentLevel::kSignin)) {
-        gfx::ImageSkia avatar_image =
-            identity_manager
-                ->FindExtendedAccountInfoByAccountId(
-                    identity_manager->GetPrimaryAccountId(
-                        signin::ConsentLevel::kSignin))
-                .account_image.AsImageSkia();
-
-        gfx::ImageSkia sized_avatar_image =
-            gfx::ImageSkiaOperations::CreateResizedImage(
-                avatar_image, skia::ImageOperations::RESIZE_BEST,
-                gfx::Size(kAvatarSize, kAvatarSize));
-        gfx::ImageSkia cropped_avatar_image =
-            gfx::ImageSkiaOperations::CreateMaskedImage(
-                sized_avatar_image,
-                gfx::CanvasImageSource::MakeImageSkia<CircleImageSource>(
-                    sized_avatar_image.width(), SK_ColorWHITE));
-        gfx::ImageSkia final_avatar_image =
-            gfx::ImageSkiaOperations::CreateSuperimposedImage(
-                gfx::CanvasImageSource::MakeImageSkia<CircleImageSource>(
-                    kAvatarWithBorderSize, gfx::kGoogleBlue400),
-                cropped_avatar_image);
-        gfx::ImageSkia badge = gfx::CreateVectorIcon(
-            kSafetyCheckIcon, kBadgeSize, gfx::kGoogleBlue500);
-        icon_ = gfx::ImageSkiaOperations::CreateIconWithBadge(
-            final_avatar_image, badge);
-        message_->SetIcon(*icon_.bitmap());
-        message_->DisableIconTint();
-      }
-    } else {
-      message_->SetIconResourceId(ResourceMapper::MapToJavaDrawableId(
-          IDR_ANDROID_MESSAGE_SAFETY_CHECK));
-    }
-  }
+  message_->SetIconResourceId(
+      ResourceMapper::MapToJavaDrawableId(IDR_ANDROID_MESSAGE_SHIELD_BLUE));
+  // Need to disable tint here because it removes a shade of blue from the
+  // shield which distorts the image.
+  message_->DisableIconTint();
 
   LogMessageOutcome(TailoredSecurityOutcome::kShown, is_in_flow_);
   messages::MessageDispatcherBridge::Get()->EnqueueMessage(
diff --git a/chrome/browser/search_resumption/java/src/org/chromium/chrome/browser/search_resumption/SearchResumptionModuleMediator.java b/chrome/browser/search_resumption/java/src/org/chromium/chrome/browser/search_resumption/SearchResumptionModuleMediator.java
index b9364e21..e4ac615 100644
--- a/chrome/browser/search_resumption/java/src/org/chromium/chrome/browser/search_resumption/SearchResumptionModuleMediator.java
+++ b/chrome/browser/search_resumption/java/src/org/chromium/chrome/browser/search_resumption/SearchResumptionModuleMediator.java
@@ -90,8 +90,7 @@
     }
 
     @Override
-    public void onSuggestionsReceived(
-            AutocompleteResult autocompleteResult, String inlineAutocompleteText, boolean isFinal) {
+    public void onSuggestionsReceived(AutocompleteResult autocompleteResult, boolean isFinal) {
         if (!isFinal || mModel != null) return;
 
         if (!shouldShowSuggestionModule(autocompleteResult.getSuggestionsList())) {
diff --git a/chrome/browser/search_resumption/junit/src/org/chromium/chrome/browser/search_resumption/SearchResumptionModuleMediatorUnitTest.java b/chrome/browser/search_resumption/junit/src/org/chromium/chrome/browser/search_resumption/SearchResumptionModuleMediatorUnitTest.java
index dd20721..1448b00 100644
--- a/chrome/browser/search_resumption/junit/src/org/chromium/chrome/browser/search_resumption/SearchResumptionModuleMediatorUnitTest.java
+++ b/chrome/browser/search_resumption/junit/src/org/chromium/chrome/browser/search_resumption/SearchResumptionModuleMediatorUnitTest.java
@@ -147,7 +147,7 @@
         List<AutocompleteMatch> list = Arrays.asList(mNonSearchSuggest1, mNonSearchSuggest1);
         doReturn(list).when(mAutocompleteResult).getSuggestionsList();
 
-        mMediator.onSuggestionsReceived(mAutocompleteResult, "", true);
+        mMediator.onSuggestionsReceived(mAutocompleteResult, true);
         verify(mParent, times(0)).inflate();
         Assert.assertEquals(
                 0,
@@ -168,7 +168,7 @@
                 Arrays.asList(mNonSearchSuggest1, mSearchSuggest1, mSearchSuggest2);
         doReturn(list).when(mAutocompleteResult).getSuggestionsList();
 
-        mMediator.onSuggestionsReceived(mAutocompleteResult, "", true);
+        mMediator.onSuggestionsReceived(mAutocompleteResult, true);
         verify(mParent, times(1)).inflate();
         Assert.assertEquals(View.VISIBLE, mSuggestionTilesContainerView.getVisibility());
         Assert.assertEquals(
diff --git a/chrome/browser/segmentation_platform/segmentation_platform_config.cc b/chrome/browser/segmentation_platform/segmentation_platform_config.cc
index 3b26fb5..561fd1a0 100644
--- a/chrome/browser/segmentation_platform/segmentation_platform_config.cc
+++ b/chrome/browser/segmentation_platform/segmentation_platform_config.cc
@@ -97,16 +97,7 @@
 
 #if BUILDFLAG(IS_ANDROID)
 bool IsEnabledContextualPageActions() {
-  if (!base::FeatureList::IsEnabled(features::kContextualPageActions))
-    return false;
-
-  bool is_price_tracking_enabled = base::FeatureList::IsEnabled(
-      features::kContextualPageActionPriceTracking);
-
-  bool is_reader_mode_enabled =
-      base::FeatureList::IsEnabled(features::kContextualPageActionReaderMode);
-
-  return is_price_tracking_enabled || is_reader_mode_enabled;
+  return base::FeatureList::IsEnabled(features::kContextualPageActions);
 }
 
 std::unique_ptr<Config> GetConfigForContextualPageActions(
diff --git a/chrome/browser/tab_group_sync/BUILD.gn b/chrome/browser/tab_group_sync/BUILD.gn
index 7bc328f..399d5602 100644
--- a/chrome/browser/tab_group_sync/BUILD.gn
+++ b/chrome/browser/tab_group_sync/BUILD.gn
@@ -76,6 +76,7 @@
       "//chrome/browser/tab:java",
       "//chrome/browser/tab_group:java",
       "//chrome/browser/tabmodel:java",
+      "//components/embedder_support/android:util_java",
       "//components/prefs/android:java",
       "//components/saved_tab_groups:java",
       "//components/tab_groups:tab_groups_java",
@@ -124,6 +125,7 @@
       "//chrome/browser/tab_group_sync:java",
       "//chrome/browser/tabmodel:java",
       "//chrome/test/android:chrome_java_unit_test_support",
+      "//components/embedder_support/android:util_java",
       "//components/prefs/android:java",
       "//components/saved_tab_groups:java",
       "//components/tab_groups:tab_groups_java",
diff --git a/chrome/browser/tab_group_sync/android/java/src/org/chromium/chrome/browser/tab_group_sync/LocalTabGroupMutationHelper.java b/chrome/browser/tab_group_sync/android/java/src/org/chromium/chrome/browser/tab_group_sync/LocalTabGroupMutationHelper.java
index 773e973..fe09b67 100644
--- a/chrome/browser/tab_group_sync/android/java/src/org/chromium/chrome/browser/tab_group_sync/LocalTabGroupMutationHelper.java
+++ b/chrome/browser/tab_group_sync/android/java/src/org/chromium/chrome/browser/tab_group_sync/LocalTabGroupMutationHelper.java
@@ -148,6 +148,8 @@
 
         // Update the remaining tabs. If the tab is already there, ensure its URL is up-to-date.
         // If the tab doesn't exist yet, create a new one.
+        // Note, root ID might have changed due to the close operations. Query it again.
+        rootId = TabGroupSyncUtils.getRootId(mTabGroupModelFilter, tabGroup.localId);
         tabs = mTabGroupModelFilter.getRelatedTabListForRootId(rootId);
         int groupStartIndex = TabModelUtils.getTabIndexById(getTabModel(), tabs.get(0).getId());
         Tab parent = tabs.get(0);
diff --git a/chrome/browser/tab_group_sync/android/java/src/org/chromium/chrome/browser/tab_group_sync/NavigationObserver.java b/chrome/browser/tab_group_sync/android/java/src/org/chromium/chrome/browser/tab_group_sync/NavigationObserver.java
index 90128f7..53edd87 100644
--- a/chrome/browser/tab_group_sync/android/java/src/org/chromium/chrome/browser/tab_group_sync/NavigationObserver.java
+++ b/chrome/browser/tab_group_sync/android/java/src/org/chromium/chrome/browser/tab_group_sync/NavigationObserver.java
@@ -4,12 +4,15 @@
 
 package org.chromium.chrome.browser.tab_group_sync;
 
+import android.util.Pair;
+
 import org.chromium.chrome.browser.tab.Tab;
 import org.chromium.chrome.browser.tabmodel.TabModelSelector;
 import org.chromium.chrome.browser.tabmodel.TabModelSelectorTabObserver;
 import org.chromium.components.tab_group_sync.TabGroupSyncService;
 import org.chromium.content_public.browser.NavigationHandle;
 import org.chromium.ui.base.PageTransition;
+import org.chromium.url.GURL;
 
 /**
  * Observes navigations on every tab in the given tab model. Filters to navigations for tabs in tab
@@ -72,11 +75,13 @@
                 TAG,
                 "Navigation wasn't from sync, notify sync, url = "
                         + tab.getUrl().getValidSpecOrEmpty());
+        Pair<GURL, String> urlAndTitle =
+                TabGroupSyncUtils.getFilteredUrlAndTitle(tab.getUrl(), tab.getTitle());
         mTabGroupSyncService.updateTab(
                 TabGroupSyncUtils.getLocalTabGroupId(tab),
                 tab.getId(),
-                tab.getTitle(),
-                tab.getUrl(),
+                urlAndTitle.second,
+                urlAndTitle.first,
                 /* position= */ -1);
     }
 }
diff --git a/chrome/browser/tab_group_sync/android/java/src/org/chromium/chrome/browser/tab_group_sync/NavigationObserverUnitTest.java b/chrome/browser/tab_group_sync/android/java/src/org/chromium/chrome/browser/tab_group_sync/NavigationObserverUnitTest.java
index b495938..92e8a0f 100644
--- a/chrome/browser/tab_group_sync/android/java/src/org/chromium/chrome/browser/tab_group_sync/NavigationObserverUnitTest.java
+++ b/chrome/browser/tab_group_sync/android/java/src/org/chromium/chrome/browser/tab_group_sync/NavigationObserverUnitTest.java
@@ -24,6 +24,7 @@
 import org.chromium.chrome.browser.tab.Tab;
 import org.chromium.chrome.browser.tabmodel.TabModel;
 import org.chromium.chrome.browser.tabmodel.TabModelSelector;
+import org.chromium.components.embedder_support.util.UrlConstants;
 import org.chromium.components.tab_group_sync.LocalTabGroupId;
 import org.chromium.components.tab_group_sync.TabGroupSyncService;
 import org.chromium.content_public.browser.NavigationHandle;
@@ -42,6 +43,7 @@
     private static final Token TOKEN_1 = new Token(2, 3);
     private static final Token TOKEN_2 = new Token(4, 5);
     private static final LocalTabGroupId LOCAL_TAB_GROUP_ID_1 = new LocalTabGroupId(TOKEN_1);
+    private static final GURL CHROME_HISTORY_URL = new GURL("chrome://history");
 
     @Rule public MockitoRule mMockitoRule = MockitoJUnit.rule();
     @Mock private Tab mTab;
@@ -78,10 +80,10 @@
         when(mTab.getUrl()).thenReturn(url);
     }
 
-    private void simulateNavigation(GURL gurl, int transition) {
+    private void simulateNavigation(int transition) {
         NavigationHandle navigation =
                 NavigationHandle.createForTesting(
-                        gurl,
+                        new GURL("unused"),
                         /* isInPrimaryMainFrame= */ true,
                         /*isSameDocument*/ false,
                         /*isRendererInitiated*/ false,
@@ -101,7 +103,7 @@
                 mTestUrl,
                 /* isIncognito= */ false,
                 /* isGrouped= */ true);
-        simulateNavigation(mTestUrl, PageTransition.LINK);
+        simulateNavigation(PageTransition.LINK);
         verify(mTabGroupSyncService)
                 .updateTab(
                         eq(LOCAL_TAB_GROUP_ID_1),
@@ -121,7 +123,7 @@
                 mTestUrl,
                 /* isIncognito= */ false,
                 /* isGrouped= */ true);
-        simulateNavigation(mTestUrl, PageTransition.LINK);
+        simulateNavigation(PageTransition.LINK);
         verify(mTabGroupSyncService)
                 .updateTab(
                         eq(LOCAL_TAB_GROUP_ID_1),
@@ -137,7 +139,7 @@
                 mTestUrl2,
                 /* isIncognito= */ false,
                 /* isGrouped= */ true);
-        simulateNavigation(mTestUrl, PageTransition.LINK);
+        simulateNavigation(PageTransition.LINK);
         verify(mTabGroupSyncService)
                 .updateTab(
                         eq(new LocalTabGroupId(TOKEN_2)),
@@ -157,7 +159,7 @@
                 mTestUrl,
                 /* isIncognito= */ false,
                 /* isGrouped= */ true);
-        simulateNavigation(mTestUrl, PageTransition.LINK);
+        simulateNavigation(PageTransition.LINK);
         verifyNoInteractions(mTabGroupSyncService);
     }
 
@@ -171,7 +173,7 @@
                 mTestUrl,
                 /* isIncognito= */ true,
                 /* isGrouped= */ true);
-        simulateNavigation(mTestUrl, PageTransition.LINK);
+        simulateNavigation(PageTransition.LINK);
         verifyNoInteractions(mTabGroupSyncService);
     }
 
@@ -185,11 +187,51 @@
                 mTestUrl,
                 /* isIncognito= */ false,
                 /* isGrouped= */ true);
-        simulateNavigation(mTestUrl, PageTransition.SERVER_REDIRECT);
+        simulateNavigation(PageTransition.SERVER_REDIRECT);
         verifyNoInteractions(mTabGroupSyncService);
     }
 
     @Test
+    public void testChromeInternalUrl() {
+        mNavigationObserver.enableObservers(true);
+        mockTab(
+                TAB_ID_1,
+                TOKEN_1,
+                mTestTitle,
+                CHROME_HISTORY_URL,
+                /* isIncognito= */ false,
+                /* isGrouped= */ true);
+        simulateNavigation(PageTransition.LINK);
+        verify(mTabGroupSyncService)
+                .updateTab(
+                        eq(LOCAL_TAB_GROUP_ID_1),
+                        eq(TAB_ID_1),
+                        eq(TabGroupSyncUtils.UNSAVEABLE_TAB_TITLE),
+                        eq(new GURL(UrlConstants.NTP_URL)),
+                        eq(-1));
+    }
+
+    @Test
+    public void testNotHttpOrHttpsOrChromeUrl() {
+        mNavigationObserver.enableObservers(true);
+        mockTab(
+                TAB_ID_1,
+                TOKEN_1,
+                mTestTitle,
+                new GURL("ftp://someurl.com"),
+                /* isIncognito= */ false,
+                /* isGrouped= */ true);
+        simulateNavigation(PageTransition.LINK);
+        verify(mTabGroupSyncService)
+                .updateTab(
+                        eq(LOCAL_TAB_GROUP_ID_1),
+                        eq(TAB_ID_1),
+                        eq(TabGroupSyncUtils.UNSAVEABLE_TAB_TITLE),
+                        eq(new GURL(UrlConstants.NTP_URL)),
+                        eq(-1));
+    }
+
+    @Test
     public void testSyncInitiatedNavigation() {
         mNavigationObserver.enableObservers(true);
         mockTab(
diff --git a/chrome/browser/tab_group_sync/android/java/src/org/chromium/chrome/browser/tab_group_sync/RemoteTabGroupMutationHelper.java b/chrome/browser/tab_group_sync/android/java/src/org/chromium/chrome/browser/tab_group_sync/RemoteTabGroupMutationHelper.java
index 5f216d4a..a91789c 100644
--- a/chrome/browser/tab_group_sync/android/java/src/org/chromium/chrome/browser/tab_group_sync/RemoteTabGroupMutationHelper.java
+++ b/chrome/browser/tab_group_sync/android/java/src/org/chromium/chrome/browser/tab_group_sync/RemoteTabGroupMutationHelper.java
@@ -4,6 +4,8 @@
 
 package org.chromium.chrome.browser.tab_group_sync;
 
+import android.util.Pair;
+
 import org.chromium.base.Token;
 import org.chromium.base.supplier.LazyOneshotSupplier;
 import org.chromium.chrome.browser.tab.Tab;
@@ -14,6 +16,7 @@
 import org.chromium.components.tab_group_sync.SavedTabGroupTab;
 import org.chromium.components.tab_group_sync.TabGroupSyncService;
 import org.chromium.components.tab_groups.TabGroupColorId;
+import org.chromium.url.GURL;
 
 import java.util.List;
 import java.util.Set;
@@ -86,13 +89,17 @@
     }
 
     public void addTab(LocalTabGroupId tabGroupId, Tab tab, int position) {
+        Pair<GURL, String> urlAndTitle =
+                TabGroupSyncUtils.getFilteredUrlAndTitle(tab.getUrl(), tab.getTitle());
         mTabGroupSyncService.addTab(
-                tabGroupId, tab.getId(), tab.getTitle(), tab.getUrl(), position);
+                tabGroupId, tab.getId(), urlAndTitle.second, urlAndTitle.first, position);
     }
 
     public void updateTab(LocalTabGroupId tabGroupId, Tab tab, int position) {
+        Pair<GURL, String> urlAndTitle =
+                TabGroupSyncUtils.getFilteredUrlAndTitle(tab.getUrl(), tab.getTitle());
         mTabGroupSyncService.updateTab(
-                tabGroupId, tab.getId(), tab.getTitle(), tab.getUrl(), position);
+                tabGroupId, tab.getId(), urlAndTitle.second, urlAndTitle.first, position);
     }
 
     public void moveTab(LocalTabGroupId tabGroupId, int tabId, int newPosition) {
diff --git a/chrome/browser/tab_group_sync/android/java/src/org/chromium/chrome/browser/tab_group_sync/TabGroupSyncLocalObserverUnitTest.java b/chrome/browser/tab_group_sync/android/java/src/org/chromium/chrome/browser/tab_group_sync/TabGroupSyncLocalObserverUnitTest.java
index a723f0ab..e2c2f5e 100644
--- a/chrome/browser/tab_group_sync/android/java/src/org/chromium/chrome/browser/tab_group_sync/TabGroupSyncLocalObserverUnitTest.java
+++ b/chrome/browser/tab_group_sync/android/java/src/org/chromium/chrome/browser/tab_group_sync/TabGroupSyncLocalObserverUnitTest.java
@@ -43,6 +43,7 @@
 import org.chromium.chrome.browser.tasks.tab_groups.TabGroupModelFilterObserver;
 import org.chromium.chrome.browser.tasks.tab_groups.TabGroupModelFilterObserver.DidRemoveTabGroupReason;
 import org.chromium.chrome.test.util.browser.tabmodel.MockTabModel;
+import org.chromium.components.embedder_support.util.UrlConstants;
 import org.chromium.components.tab_group_sync.LocalTabGroupId;
 import org.chromium.components.tab_group_sync.TabGroupSyncService;
 import org.chromium.components.tab_groups.TabGroupColorId;
@@ -65,6 +66,8 @@
     private static final LocalTabGroupId LOCAL_TAB_GROUP_ID_1 = new LocalTabGroupId(TOKEN_1);
     private static final String TITLE_1 = "Group Title";
     private static final String TAB_GROUP_COLORS_FILE_NAME = "tab_group_colors";
+    private static final String TAB_TITLE_1 = "Tab Title";
+    private static final GURL TAB_URL_1 = new GURL("https://google.com");
 
     @Rule public MockitoRule mMockitoRule = MockitoJUnit.rule();
     private @Mock TabModelSelector mTabModelSelector;
@@ -86,7 +89,8 @@
         Tab tab = Mockito.mock(Tab.class);
         Mockito.doReturn(tabId).when(tab).getId();
         Mockito.doReturn(rootId).when(tab).getRootId();
-        Mockito.doReturn(GURL.emptyGURL()).when(tab).getUrl();
+        Mockito.doReturn(TAB_URL_1).when(tab).getUrl();
+        Mockito.doReturn(TAB_TITLE_1).when(tab).getTitle();
         return tab;
     }
 
@@ -142,7 +146,31 @@
                         TabCreationState.LIVE_IN_BACKGROUND,
                         false);
         verify(mTabGroupSyncService, times(1))
-                .addTab(eq(LOCAL_TAB_GROUP_ID_1), eq(TAB_ID_1), any(), any(), anyInt());
+                .addTab(
+                        eq(LOCAL_TAB_GROUP_ID_1),
+                        eq(TAB_ID_1),
+                        eq(TAB_TITLE_1),
+                        eq(TAB_URL_1),
+                        anyInt());
+    }
+
+    @Test
+    public void testTabAddedLocally_NonSaveableUrl() {
+        Mockito.doReturn(new GURL("ftp://someurl.com")).when(mTab1).getUrl();
+        mTabModelObserverCaptor
+                .getValue()
+                .didAddTab(
+                        mTab1,
+                        TabLaunchType.FROM_RESTORE,
+                        TabCreationState.LIVE_IN_BACKGROUND,
+                        false);
+        verify(mTabGroupSyncService, times(1))
+                .addTab(
+                        eq(LOCAL_TAB_GROUP_ID_1),
+                        eq(TAB_ID_1),
+                        eq(TabGroupSyncUtils.UNSAVEABLE_TAB_TITLE),
+                        eq(new GURL(UrlConstants.NTP_URL)),
+                        anyInt());
     }
 
     @Test
diff --git a/chrome/browser/tab_group_sync/android/java/src/org/chromium/chrome/browser/tab_group_sync/TabGroupSyncUtils.java b/chrome/browser/tab_group_sync/android/java/src/org/chromium/chrome/browser/tab_group_sync/TabGroupSyncUtils.java
index 0c5de53..295fe14b4 100644
--- a/chrome/browser/tab_group_sync/android/java/src/org/chromium/chrome/browser/tab_group_sync/TabGroupSyncUtils.java
+++ b/chrome/browser/tab_group_sync/android/java/src/org/chromium/chrome/browser/tab_group_sync/TabGroupSyncUtils.java
@@ -4,13 +4,19 @@
 
 package org.chromium.chrome.browser.tab_group_sync;
 
+import android.util.Pair;
+
 import org.chromium.base.Token;
 import org.chromium.chrome.browser.tab.Tab;
 import org.chromium.chrome.browser.tasks.tab_groups.TabGroupModelFilter;
+import org.chromium.components.embedder_support.util.UrlConstants;
+import org.chromium.components.embedder_support.util.UrlUtilities;
 import org.chromium.components.tab_group_sync.LocalTabGroupId;
+import org.chromium.url.GURL;
 
 /** Utility methods for tab group sync. */
 public final class TabGroupSyncUtils {
+    public static final String UNSAVEABLE_TAB_TITLE = "Unsavable tab";
 
     /**
      * Whether the given {@param localId} corresponds to a tab group in the current window
@@ -41,4 +47,13 @@
     public static LocalTabGroupId getLocalTabGroupId(Tab tab) {
         return new LocalTabGroupId(tab.getTabGroupId());
     }
+
+    /** Utility method to filter out URLs not suitable for tab group sync. */
+    public static Pair<GURL, String> getFilteredUrlAndTitle(GURL url, String title) {
+        assert url != null;
+        if (UrlUtilities.isHttpOrHttps(url) || UrlUtilities.isNtpUrl(url)) {
+            return new Pair<>(url, title);
+        }
+        return new Pair<>(new GURL(UrlConstants.NTP_URL), UNSAVEABLE_TAB_TITLE);
+    }
 }
diff --git a/chrome/browser/touch_to_fill/autofill/android/internal/java/src/org/chromium/chrome/browser/touch_to_fill/payments/TouchToFillPaymentMethodControllerRobolectricTest.java b/chrome/browser/touch_to_fill/autofill/android/internal/java/src/org/chromium/chrome/browser/touch_to_fill/payments/TouchToFillPaymentMethodControllerRobolectricTest.java
index 001d134..087422cc 100644
--- a/chrome/browser/touch_to_fill/autofill/android/internal/java/src/org/chromium/chrome/browser/touch_to_fill/payments/TouchToFillPaymentMethodControllerRobolectricTest.java
+++ b/chrome/browser/touch_to_fill/autofill/android/internal/java/src/org/chromium/chrome/browser/touch_to_fill/payments/TouchToFillPaymentMethodControllerRobolectricTest.java
@@ -445,14 +445,14 @@
         assertNotNull(mTouchToFillPaymentMethodModel.get(DISMISS_HANDLER));
         assertThat(mTouchToFillPaymentMethodModel.get(VISIBLE), is(false));
 
-        mCoordinator.showSheet(new Iban[] {LOCAL_IBAN});
+        mCoordinator.showSheet(List.of(LOCAL_IBAN));
 
         assertThat(mTouchToFillPaymentMethodModel.get(VISIBLE), is(true));
     }
 
     @Test
     public void testScanNewCardNotShownForIbans() {
-        mCoordinator.showSheet(new Iban[] {LOCAL_IBAN});
+        mCoordinator.showSheet(List.of(LOCAL_IBAN));
         int lastItemPos = mTouchToFillPaymentMethodModel.get(SHEET_ITEMS).size() - 1;
 
         assertNull(
@@ -465,7 +465,7 @@
 
     @Test
     public void testShowIbansWithOneEntry() throws TimeoutException {
-        mCoordinator.showSheet(new Iban[] {LOCAL_IBAN});
+        mCoordinator.showSheet(List.of(LOCAL_IBAN));
 
         ModelList itemList = mTouchToFillPaymentMethodModel.get(SHEET_ITEMS);
         assertThat(getModelsOfType(itemList, IBAN).size(), is(1));
@@ -480,7 +480,7 @@
 
     @Test
     public void testShowIbansWithTwoEntries() throws TimeoutException {
-        mCoordinator.showSheet(new Iban[] {LOCAL_IBAN, LOCAL_IBAN_NO_NICKNAME});
+        mCoordinator.showSheet(List.of(LOCAL_IBAN, LOCAL_IBAN_NO_NICKNAME));
 
         ModelList itemList = mTouchToFillPaymentMethodModel.get(SHEET_ITEMS);
         assertThat(getModelsOfType(itemList, IBAN).size(), is(2));
@@ -499,7 +499,7 @@
 
     @Test
     public void testShowPaymentMethodSettingsForIbans() {
-        mCoordinator.showSheet(new Iban[] {LOCAL_IBAN, LOCAL_IBAN_NO_NICKNAME});
+        mCoordinator.showSheet(List.of(LOCAL_IBAN, LOCAL_IBAN_NO_NICKNAME));
         int lastItemPos = mTouchToFillPaymentMethodModel.get(SHEET_ITEMS).size() - 1;
         mTouchToFillPaymentMethodModel
                 .get(SHEET_ITEMS)
@@ -512,7 +512,7 @@
 
     @Test
     public void testShowsContinueButtonWhenOneIban() {
-        mCoordinator.showSheet(new Iban[] {LOCAL_IBAN});
+        mCoordinator.showSheet(List.of(LOCAL_IBAN));
 
         ModelList itemList = mTouchToFillPaymentMethodModel.get(SHEET_ITEMS);
         assertEquals(getModelsOfType(itemList, FILL_BUTTON).size(), 1);
@@ -520,7 +520,7 @@
 
     @Test
     public void testNoContinueButtonWhenManyIbans() {
-        mCoordinator.showSheet(new Iban[] {LOCAL_IBAN, LOCAL_IBAN_NO_NICKNAME});
+        mCoordinator.showSheet(List.of(LOCAL_IBAN, LOCAL_IBAN_NO_NICKNAME));
 
         ModelList itemList = mTouchToFillPaymentMethodModel.get(SHEET_ITEMS);
         assertEquals(getModelsOfType(itemList, FILL_BUTTON).size(), 0);
diff --git a/chrome/browser/touch_to_fill/autofill/android/internal/java/src/org/chromium/chrome/browser/touch_to_fill/payments/TouchToFillPaymentMethodCoordinator.java b/chrome/browser/touch_to_fill/autofill/android/internal/java/src/org/chromium/chrome/browser/touch_to_fill/payments/TouchToFillPaymentMethodCoordinator.java
index 390fc9f4..23c4cbc 100644
--- a/chrome/browser/touch_to_fill/autofill/android/internal/java/src/org/chromium/chrome/browser/touch_to_fill/payments/TouchToFillPaymentMethodCoordinator.java
+++ b/chrome/browser/touch_to_fill/autofill/android/internal/java/src/org/chromium/chrome/browser/touch_to_fill/payments/TouchToFillPaymentMethodCoordinator.java
@@ -74,7 +74,7 @@
     }
 
     @Override
-    public void showSheet(Iban[] ibans) {
+    public void showSheet(List<Iban> ibans) {
         mMediator.showSheet(ibans);
     }
 
diff --git a/chrome/browser/touch_to_fill/autofill/android/internal/java/src/org/chromium/chrome/browser/touch_to_fill/payments/TouchToFillPaymentMethodMediator.java b/chrome/browser/touch_to_fill/autofill/android/internal/java/src/org/chromium/chrome/browser/touch_to_fill/payments/TouchToFillPaymentMethodMediator.java
index b51c6565..ffe0e8b6 100644
--- a/chrome/browser/touch_to_fill/autofill/android/internal/java/src/org/chromium/chrome/browser/touch_to_fill/payments/TouchToFillPaymentMethodMediator.java
+++ b/chrome/browser/touch_to_fill/autofill/android/internal/java/src/org/chromium/chrome/browser/touch_to_fill/payments/TouchToFillPaymentMethodMediator.java
@@ -42,7 +42,6 @@
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
-import java.util.Arrays;
 import java.util.List;
 import java.util.Locale;
 import java.util.function.Function;
@@ -147,22 +146,22 @@
         RecordHistogram.recordCount100Histogram(TOUCH_TO_FILL_NUMBER_OF_CARDS_SHOWN, mCards.size());
     }
 
-    public void showSheet(Iban[] ibans) {
+    public void showSheet(List<Iban> ibans) {
         mInputProtector.markShowTime();
 
         assert ibans != null;
-        mIbans = Arrays.asList(ibans);
+        mIbans = ibans;
 
         ModelList sheetItems = mModel.get(SHEET_ITEMS);
         sheetItems.clear();
 
-        for (int i = 0; i < ibans.length; ++i) {
-            Iban iban = ibans[i];
+        for (int i = 0; i < mIbans.size(); ++i) {
+            Iban iban = mIbans.get(i);
             final PropertyModel model = createIbanModel(iban);
             sheetItems.add(new ListItem(IBAN, model));
         }
 
-        if (ibans.length == 1) {
+        if (mIbans.size() == 1) {
             // Use the IBAN model as the property model for the fill button too.
             assert sheetItems.get(0).type == IBAN;
             sheetItems.add(new ListItem(FILL_BUTTON, sheetItems.get(0).model));
diff --git a/chrome/browser/touch_to_fill/autofill/android/internal/java/src/org/chromium/chrome/browser/touch_to_fill/payments/TouchToFillPaymentMethodRenderTest.java b/chrome/browser/touch_to_fill/autofill/android/internal/java/src/org/chromium/chrome/browser/touch_to_fill/payments/TouchToFillPaymentMethodRenderTest.java
index 8a891ed0d..970ba6e 100644
--- a/chrome/browser/touch_to_fill/autofill/android/internal/java/src/org/chromium/chrome/browser/touch_to_fill/payments/TouchToFillPaymentMethodRenderTest.java
+++ b/chrome/browser/touch_to_fill/autofill/android/internal/java/src/org/chromium/chrome/browser/touch_to_fill/payments/TouchToFillPaymentMethodRenderTest.java
@@ -370,7 +370,7 @@
     public void testShowsOneIban() throws IOException {
         runOnUiThreadBlocking(
                 () -> {
-                    mCoordinator.showSheet(new Iban[] {LOCAL_IBAN});
+                    mCoordinator.showSheet(List.of(LOCAL_IBAN));
                 });
         BottomSheetTestSupport.waitForOpen(mBottomSheetController);
 
@@ -384,7 +384,7 @@
     public void testShowsOneIbanHalfState() throws IOException {
         runOnUiThreadBlocking(
                 () -> {
-                    mCoordinator.showSheet(new Iban[] {LOCAL_IBAN});
+                    mCoordinator.showSheet(List.of(LOCAL_IBAN));
                 });
         BottomSheetTestSupport.waitForOpen(mBottomSheetController);
 
@@ -401,7 +401,7 @@
     public void testShowsTwoIbans() throws IOException {
         runOnUiThreadBlocking(
                 () -> {
-                    mCoordinator.showSheet(new Iban[] {LOCAL_IBAN, LOCAL_IBAN_NO_NICKNAME});
+                    mCoordinator.showSheet(List.of(LOCAL_IBAN, LOCAL_IBAN_NO_NICKNAME));
                 });
         BottomSheetTestSupport.waitForOpen(mBottomSheetController);
 
@@ -415,7 +415,7 @@
     public void testShowsTwoIbansHalfState() throws IOException {
         runOnUiThreadBlocking(
                 () -> {
-                    mCoordinator.showSheet(new Iban[] {LOCAL_IBAN, LOCAL_IBAN_NO_NICKNAME});
+                    mCoordinator.showSheet(List.of(LOCAL_IBAN, LOCAL_IBAN_NO_NICKNAME));
                 });
         BottomSheetTestSupport.waitForOpen(mBottomSheetController);
 
diff --git a/chrome/browser/touch_to_fill/autofill/android/internal/java/src/org/chromium/chrome/browser/touch_to_fill/payments/TouchToFillPaymentMethodViewBridge.java b/chrome/browser/touch_to_fill/autofill/android/internal/java/src/org/chromium/chrome/browser/touch_to_fill/payments/TouchToFillPaymentMethodViewBridge.java
index ff287710..361ed06a50 100644
--- a/chrome/browser/touch_to_fill/autofill/android/internal/java/src/org/chromium/chrome/browser/touch_to_fill/payments/TouchToFillPaymentMethodViewBridge.java
+++ b/chrome/browser/touch_to_fill/autofill/android/internal/java/src/org/chromium/chrome/browser/touch_to_fill/payments/TouchToFillPaymentMethodViewBridge.java
@@ -71,6 +71,11 @@
     }
 
     @CalledByNative
+    private void showSheet(@JniType("std::vector") Object[] ibans) {
+        mComponent.showSheet((List<PersonalDataManager.Iban>) (List<?>) Arrays.asList(ibans));
+    }
+
+    @CalledByNative
     private void hideSheet() {
         mComponent.hideSheet();
     }
diff --git a/chrome/browser/touch_to_fill/autofill/android/java/src/org/chromium/chrome/browser/touch_to_fill/payments/TouchToFillPaymentMethodComponent.java b/chrome/browser/touch_to_fill/autofill/android/java/src/org/chromium/chrome/browser/touch_to_fill/payments/TouchToFillPaymentMethodComponent.java
index 89cdf179..8b27d7c 100644
--- a/chrome/browser/touch_to_fill/autofill/android/java/src/org/chromium/chrome/browser/touch_to_fill/payments/TouchToFillPaymentMethodComponent.java
+++ b/chrome/browser/touch_to_fill/autofill/android/java/src/org/chromium/chrome/browser/touch_to_fill/payments/TouchToFillPaymentMethodComponent.java
@@ -66,7 +66,7 @@
     void showSheet(List<PersonalDataManager.CreditCard> cards, boolean shouldShowScanCreditCard);
 
     /** Displays a new IBAN bottom sheet. */
-    void showSheet(PersonalDataManager.Iban[] ibans);
+    void showSheet(List<PersonalDataManager.Iban> ibans);
 
     /** Hides the bottom sheet if shown. */
     void hideSheet();
diff --git a/chrome/browser/touch_to_fill/autofill/android/touch_to_fill_delegate_android_impl.cc b/chrome/browser/touch_to_fill/autofill/android/touch_to_fill_delegate_android_impl.cc
index 6d98f65..830e758 100644
--- a/chrome/browser/touch_to_fill/autofill/android/touch_to_fill_delegate_android_impl.cc
+++ b/chrome/browser/touch_to_fill/autofill/android/touch_to_fill_delegate_android_impl.cc
@@ -129,7 +129,8 @@
   CHECK(pdm);
   std::vector<Iban> ibans_to_suggest =
       pdm->payments_data_manager().GetOrderedIbansToSuggest();
-  return ibans_to_suggest.empty()
+  return ibans_to_suggest.empty() || !base::FeatureList::IsEnabled(
+                                         features::kAutofillEnableLocalIban)
              ? DryRunResult(TriggerOutcome::kNoValidPaymentMethods, {})
              : DryRunResult(TriggerOutcome::kShown,
                             std::move(ibans_to_suggest));
@@ -197,8 +198,10 @@
       dry_run.outcome = TriggerOutcome::kFailedToDisplayBottomSheet;
     } else if (std::vector<Iban>* ibans_to_suggest =
                    absl::get_if<std::vector<Iban>>(&dry_run.items_to_suggest);
-               ibans_to_suggest) {
-      // TODO(b/309163844): Handle dry_run.ibans_to_suggest case.
+               ibans_to_suggest &&
+               !manager_->client().ShowTouchToFillIban(
+                   GetWeakPtr(), std::move(*ibans_to_suggest))) {
+      dry_run.outcome = TriggerOutcome::kFailedToDisplayBottomSheet;
     }
   }
 
diff --git a/chrome/browser/touch_to_fill/autofill/android/touch_to_fill_delegate_android_impl_unittest.cc b/chrome/browser/touch_to_fill/autofill/android/touch_to_fill_delegate_android_impl_unittest.cc
index f6edabde..60f1dc99 100644
--- a/chrome/browser/touch_to_fill/autofill/android/touch_to_fill_delegate_android_impl_unittest.cc
+++ b/chrome/browser/touch_to_fill/autofill/android/touch_to_fill_delegate_android_impl_unittest.cc
@@ -28,6 +28,7 @@
 using ::testing::_;
 using ::testing::ElementsAre;
 using ::testing::ElementsAreArray;
+using ::testing::IsEmpty;
 using ::testing::NiceMock;
 using ::testing::Pointee;
 using ::testing::Ref;
@@ -50,13 +51,18 @@
               (base::WeakPtr<autofill::TouchToFillDelegate> delegate,
                base::span<const CreditCard> cards_to_suggest),
               (override));
+  MOCK_METHOD(bool,
+              ShowTouchToFillIban,
+              (base::WeakPtr<autofill::TouchToFillDelegate> delegate,
+               base::span<const Iban> ibanns_to_suggest),
+              (override));
   MOCK_METHOD(void, HideTouchToFillCreditCard, (), (override));
   MOCK_METHOD(void,
               HideAutofillSuggestions,
               (SuggestionHidingReason reason),
               (override));
 
-  void ExpectDelegateWeakPtrFromShowInvalidatedOnHide() {
+  void ExpectDelegateWeakPtrFromShowInvalidatedOnHideForCards() {
     EXPECT_CALL(*this, ShowTouchToFillCreditCard)
         .WillOnce([this](base::WeakPtr<autofill::TouchToFillDelegate> delegate,
                          base::span<const CreditCard> cards_to_suggest) {
@@ -68,6 +74,18 @@
     });
   }
 
+  void ExpectDelegateWeakPtrFromShowInvalidatedOnHideForIbans() {
+    EXPECT_CALL(*this, ShowTouchToFillIban)
+        .WillOnce([this](base::WeakPtr<autofill::TouchToFillDelegate> delegate,
+                         base::span<const Iban> ibans_to_suggest) {
+          captured_delegate_ = delegate;
+          return true;
+        });
+    EXPECT_CALL(*this, HideTouchToFillCreditCard).WillOnce([this]() {
+      EXPECT_FALSE(captured_delegate_);
+    });
+  }
+
  private:
   base::WeakPtr<autofill::TouchToFillDelegate> captured_delegate_;
 };
@@ -142,6 +160,7 @@
         .WillByDefault(Return(true));
     ON_CALL(autofill_client_, ShowTouchToFillCreditCard)
         .WillByDefault(Return(true));
+    ON_CALL(autofill_client_, ShowTouchToFillIban).WillByDefault(Return(true));
     // Calling HideTouchToFillCreditCard in production code leads to that
     // OnDismissed gets triggered (HideTouchToFillCreditCard calls view->Hide()
     // on java side, which in its turn triggers onDismissed). Here we mock this
@@ -216,6 +235,7 @@
   std::unique_ptr<TestAutofillDriver> autofill_driver_;
   std::unique_ptr<MockBrowserAutofillManager> browser_autofill_manager_;
   raw_ptr<TouchToFillDelegateAndroidImpl> touch_to_fill_delegate_;
+  base::test::ScopedFeatureList scoped_feature_list_;
   base::HistogramTester histogram_tester_;
 };
 
@@ -227,6 +247,8 @@
       public testing::WithParamInterface<bool> {
  protected:
   void SetUp() override {
+    scoped_feature_list_.InitAndEnableFeature(
+        features::kAutofillEnableLocalIban);
     TouchToFillDelegateAndroidImplUnitTest::SetUp();
     if (IsCreditCard()) {
       ConfigureForCreditCards(test::GetCreditCard());
@@ -253,6 +275,17 @@
 }
 
 TEST_P(TouchToFillDelegateAndroidImplPaymentMethodUnitTest,
+       TryToShowTouchToFillFailsIfNotSpecificField) {
+  form_.fields.insert(form_.fields.begin(),
+                      test::CreateTestFormField("Arbitrary", "arbitrary", "",
+                                                FormControlType::kInputText));
+
+  ASSERT_FALSE(touch_to_fill_delegate_->IsShowingTouchToFill());
+
+  TryToShowTouchToFill(/*expected_success=*/false);
+}
+
+TEST_P(TouchToFillDelegateAndroidImplPaymentMethodUnitTest,
        HideTouchToFillDoesNothingIfNotShown) {
   ASSERT_FALSE(touch_to_fill_delegate_->IsShowingTouchToFill());
 
@@ -297,6 +330,30 @@
   EXPECT_EQ(touch_to_fill_delegate_->IsShowingTouchToFill(), false);
 }
 
+TEST_P(TouchToFillDelegateAndroidImplPaymentMethodUnitTest,
+       TryToShowTouchToFillFailsIfShownBefore) {
+  TryToShowTouchToFill(/*expected_success=*/true);
+  touch_to_fill_delegate_->OnDismissed(/*dismissed_by_user=*/true);
+
+  EXPECT_CALL(autofill_client_,
+              HideAutofillSuggestions(
+                  SuggestionHidingReason::kOverlappingWithTouchToFillSurface))
+      .Times(0);
+  TryToShowTouchToFill(/*expected_success=*/false);
+}
+
+TEST_P(TouchToFillDelegateAndroidImplPaymentMethodUnitTest,
+       TryToShowTouchToFillFailsIfShownCurrently) {
+  TryToShowTouchToFill(/*expected_success=*/true);
+
+  EXPECT_CALL(autofill_client_,
+              HideAutofillSuggestions(
+                  SuggestionHidingReason::kOverlappingWithTouchToFillSurface))
+      .Times(0);
+  EXPECT_FALSE(
+      touch_to_fill_delegate_->TryToShowTouchToFill(form_, form_.fields[0]));
+}
+
 class TouchToFillDelegateAndroidImplCreditCardUnitTest
     : public TouchToFillDelegateAndroidImplUnitTest {
  public:
@@ -318,17 +375,6 @@
 };
 
 TEST_F(TouchToFillDelegateAndroidImplCreditCardUnitTest,
-       TryToShowTouchToFillFailsIfNotCreditCardField) {
-  form_.fields.insert(form_.fields.begin(),
-                      test::CreateTestFormField("Arbitrary", "arbitrary", "",
-                                                FormControlType::kInputText));
-
-  ASSERT_FALSE(touch_to_fill_delegate_->IsShowingTouchToFill());
-
-  TryToShowTouchToFill(/*expected_success=*/false);
-}
-
-TEST_F(TouchToFillDelegateAndroidImplCreditCardUnitTest,
        TryToShowTouchToFillPaymentMethodSucceeds) {
   ASSERT_FALSE(touch_to_fill_delegate_->IsShowingTouchToFill());
 
@@ -476,30 +522,6 @@
 }
 
 TEST_F(TouchToFillDelegateAndroidImplCreditCardUnitTest,
-       TryToShowTouchToFillFailsIfShownBefore) {
-  TryToShowTouchToFill(/*expected_success=*/true);
-  touch_to_fill_delegate_->OnDismissed(/*dismissed_by_user=*/true);
-
-  EXPECT_CALL(autofill_client_,
-              HideAutofillSuggestions(
-                  SuggestionHidingReason::kOverlappingWithTouchToFillSurface))
-      .Times(0);
-  TryToShowTouchToFill(/*expected_success=*/false);
-}
-
-TEST_F(TouchToFillDelegateAndroidImplCreditCardUnitTest,
-       TryToShowTouchToFillFailsIfShownCurrently) {
-  TryToShowTouchToFill(/*expected_success=*/true);
-
-  EXPECT_CALL(autofill_client_,
-              HideAutofillSuggestions(
-                  SuggestionHidingReason::kOverlappingWithTouchToFillSurface))
-      .Times(0);
-  EXPECT_FALSE(
-      touch_to_fill_delegate_->TryToShowTouchToFill(form_, form_.fields[0]));
-}
-
-TEST_F(TouchToFillDelegateAndroidImplCreditCardUnitTest,
        TryToShowTouchToFillToleratesFormattingCharacters) {
   form_.fields[0].set_value(u"____-____-____-____");
 
@@ -734,17 +756,24 @@
 
 TEST_F(TouchToFillDelegateAndroidImplCreditCardUnitTest,
        SafelyHideTouchToFillInDtor) {
-  autofill_client_.ExpectDelegateWeakPtrFromShowInvalidatedOnHide();
+  autofill_client_.ExpectDelegateWeakPtrFromShowInvalidatedOnHideForCards();
   TryToShowTouchToFill(/*expected_success=*/true);
 
   browser_autofill_manager_.reset();
 }
 
+// Add one IBAN to the PDM and verify that IBAN is not shown for the credit
+// card form.
 TEST_F(TouchToFillDelegateAndroidImplCreditCardUnitTest,
        PassTheCreditCardsToTheClient) {
   autofill_client_.GetPersonalDataManager()
       ->test_payments_data_manager()
       .ClearCreditCards();
+  Iban iban1;
+  iban1.set_value(base::UTF8ToUTF16(std::string(test::kIbanValue_1)));
+  autofill_client_.GetPersonalDataManager()
+      ->test_payments_data_manager()
+      .AddAsLocalIban(std::move(iban1));
   CreditCard credit_card1 = autofill::test::GetCreditCard();
   CreditCard credit_card2 = autofill::test::GetCreditCard2();
   autofill_client_.GetPersonalDataManager()
@@ -870,7 +899,72 @@
   IntendsToShowTouchToFill(/*expected_success=*/true);
 }
 
-TEST_F(TouchToFillDelegateAndroidImplUnitTest, IbanSelectionClosesTheSheet) {
+class TouchToFillDelegateAndroidImplIbanUnitTest
+    : public TouchToFillDelegateAndroidImplUnitTest {
+ protected:
+  void SetUp() override {
+    scoped_feature_list_.InitAndEnableFeature(
+        features::kAutofillEnableLocalIban);
+    TouchToFillDelegateAndroidImplUnitTest::SetUp();
+    ConfigureForIbans();
+  }
+};
+
+TEST_F(TouchToFillDelegateAndroidImplIbanUnitTest,
+       TryToShowTouchToFillFailsIfShowFails) {
+  ASSERT_FALSE(touch_to_fill_delegate_->IsShowingTouchToFill());
+  EXPECT_CALL(autofill_client_, ShowTouchToFillIban).WillOnce(Return(false));
+
+  TryToShowTouchToFill(/*expected_success=*/false);
+}
+
+// Add one valid credit card to PDM and verify that credit card is not shown in
+// IBAN form.
+TEST_F(TouchToFillDelegateAndroidImplIbanUnitTest, PassTheIbansToTheClient) {
+  autofill_client_.GetPersonalDataManager()->ClearAllLocalData();
+  autofill_client_.GetPersonalDataManager()
+      ->payments_data_manager()
+      .AddCreditCard(autofill::test::GetCreditCard());
+  Iban iban1;
+  iban1.set_value(base::UTF8ToUTF16(std::string(test::kIbanValue_1)));
+  autofill_client_.GetPersonalDataManager()
+      ->test_payments_data_manager()
+      .AddAsLocalIban(std::move(iban1));
+  Iban iban2;
+  iban2.set_value(base::UTF8ToUTF16(std::string(test::kIbanValue_2)));
+  autofill_client_.GetPersonalDataManager()
+      ->test_payments_data_manager()
+      .AddAsLocalIban(std::move(iban2));
+  std::vector<Iban> ibans = autofill_client_.GetPersonalDataManager()
+                                ->test_payments_data_manager()
+                                .GetOrderedIbansToSuggest();
+
+  EXPECT_CALL(autofill_client_,
+              ShowTouchToFillIban(_, ElementsAreArray(ibans)));
+
+  TryToShowTouchToFill(/*expected_success=*/true);
+
+  browser_autofill_manager_.reset();
+}
+
+TEST_F(TouchToFillDelegateAndroidImplIbanUnitTest,
+       TryToShowTouchToFillSucceeds) {
+  ASSERT_FALSE(touch_to_fill_delegate_->IsShowingTouchToFill());
+  EXPECT_CALL(autofill_client_, ShowTouchToFillIban).WillOnce(Return(true));
+
+  TryToShowTouchToFill(/*expected_success=*/true);
+}
+
+TEST_F(TouchToFillDelegateAndroidImplIbanUnitTest,
+       SafelyHideTouchToFillInDtor) {
+  autofill_client_.ExpectDelegateWeakPtrFromShowInvalidatedOnHideForIbans();
+  TryToShowTouchToFill(/*expected_success=*/true);
+
+  browser_autofill_manager_.reset();
+}
+
+TEST_F(TouchToFillDelegateAndroidImplIbanUnitTest,
+       IbanSelectionClosesTheSheet) {
   std::string guid = ConfigureForIbans();
   TryToShowTouchToFill(/*expected_success=*/true);
 
@@ -878,7 +972,7 @@
   touch_to_fill_delegate_->IbanSuggestionSelected(Iban::Guid(guid));
 }
 
-TEST_F(TouchToFillDelegateAndroidImplUnitTest, IbanSelectionFillsIbanForm) {
+TEST_F(TouchToFillDelegateAndroidImplIbanUnitTest, IbanSelectionFillsIbanForm) {
   std::string guid = ConfigureForIbans();
   TryToShowTouchToFill(/*expected_success=*/true);
 
diff --git a/chrome/browser/touch_to_fill/autofill/android/touch_to_fill_payment_method_controller.cc b/chrome/browser/touch_to_fill/autofill/android/touch_to_fill_payment_method_controller.cc
index 4dde383..0a877e45 100644
--- a/chrome/browser/touch_to_fill/autofill/android/touch_to_fill_payment_method_controller.cc
+++ b/chrome/browser/touch_to_fill/autofill/android/touch_to_fill_payment_method_controller.cc
@@ -109,6 +109,29 @@
   return true;
 }
 
+bool TouchToFillPaymentMethodController::Show(
+    std::unique_ptr<TouchToFillPaymentMethodView> view,
+    base::WeakPtr<TouchToFillDelegate> delegate,
+    base::span<const Iban> ibans_to_suggest) {
+  if (!keyboard_suppressor_.is_suppressing()) {
+    return false;
+  }
+
+  // Abort if TTF surface is already shown.
+  if (view_) {
+    return false;
+  }
+
+  if (!view->Show(this, ibans_to_suggest)) {
+    java_object_.Reset();
+    return false;
+  }
+
+  view_ = std::move(view);
+  delegate_ = std::move(delegate);
+  return true;
+}
+
 void TouchToFillPaymentMethodController::Hide() {
   if (view_)
     view_->Hide();
diff --git a/chrome/browser/touch_to_fill/autofill/android/touch_to_fill_payment_method_controller.h b/chrome/browser/touch_to_fill/autofill/android/touch_to_fill_payment_method_controller.h
index 979b2fc..72d986d 100644
--- a/chrome/browser/touch_to_fill/autofill/android/touch_to_fill_payment_method_controller.h
+++ b/chrome/browser/touch_to_fill/autofill/android/touch_to_fill_payment_method_controller.h
@@ -17,9 +17,10 @@
 namespace autofill {
 
 class ContentAutofillClient;
-class TouchToFillPaymentMethodView;
-class TouchToFillDelegate;
 class CreditCard;
+class Iban;
+class TouchToFillDelegate;
+class TouchToFillPaymentMethodView;
 
 // Controller of the bottom sheet surface for filling credit card or IBAN data on
 // Android. It is responsible for showing the view and handling user
@@ -56,6 +57,13 @@
             base::WeakPtr<TouchToFillDelegate> delegate,
             base::span<const CreditCard> cards_to_suggest);
 
+  // Shows the Touch To Fill `view`. `delegate` will provide the fillable IBANs
+  // and be notified of the user's decision. Returns whether the surface was
+  // successfully shown.
+  bool Show(std::unique_ptr<TouchToFillPaymentMethodView> view,
+            base::WeakPtr<TouchToFillDelegate> delegate,
+            base::span<const Iban> ibans_to_suggest);
+
   // Hides the surface if it is currently shown.
   void Hide();
 
diff --git a/chrome/browser/touch_to_fill/autofill/android/touch_to_fill_payment_method_controller_unittest.cc b/chrome/browser/touch_to_fill/autofill/android/touch_to_fill_payment_method_controller_unittest.cc
index 850773f54..984bb8b 100644
--- a/chrome/browser/touch_to_fill/autofill/android/touch_to_fill_payment_method_controller_unittest.cc
+++ b/chrome/browser/touch_to_fill/autofill/android/touch_to_fill_payment_method_controller_unittest.cc
@@ -21,6 +21,7 @@
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
+using ::testing::_;
 using ::testing::ElementsAreArray;
 using ::testing::Return;
 
@@ -31,7 +32,8 @@
 class MockTouchToFillPaymentMethodViewImpl : public TouchToFillPaymentMethodView {
  public:
   MockTouchToFillPaymentMethodViewImpl() {
-    ON_CALL(*this, Show).WillByDefault(Return(true));
+    ON_CALL(*this, Show(_, _, _)).WillByDefault(Return(true));
+    ON_CALL(*this, Show(_, _)).WillByDefault(Return(true));
   }
   ~MockTouchToFillPaymentMethodViewImpl() override = default;
 
@@ -40,6 +42,10 @@
               (TouchToFillPaymentMethodViewController * controller,
                base::span<const CreditCard> cards_to_suggest,
                bool should_show_scan_credit_card));
+  MOCK_METHOD(bool,
+              Show,
+              (TouchToFillPaymentMethodViewController * controller,
+               base::span<const Iban> ibans_to_suggest));
   MOCK_METHOD(void, Hide, ());
 };
 
@@ -109,6 +115,12 @@
     mock_view_ = std::make_unique<MockTouchToFillPaymentMethodViewImpl>();
   }
 
+  void SetUpIbanFormField() {
+    some_form_data_ = autofill::test::CreateTestIbanFormData();
+    some_form_ = some_form_data_.global_id();
+    some_field_ = test::MakeFieldGlobalId();
+  }
+
   void TearDown() override {
     mock_view_.reset();
     ChromeRenderViewHostTestHarness::TearDown();
@@ -134,6 +146,8 @@
 
   const std::vector<CreditCard> credit_cards_ = {test::GetCreditCard(),
                                                  test::GetCreditCard2()};
+  const std::vector<Iban> ibans_ = {test::GetLocalIban(),
+                                    test::GetServerIban()};
   std::unique_ptr<MockTouchToFillPaymentMethodViewImpl> mock_view_;
 
   void OnBeforeAskForValuesToFill() {
@@ -186,6 +200,17 @@
   OnAfterAskForValuesToFill();
 }
 
+TEST_F(TouchToFillPaymentMethodControllerTest, ShowPassesIbansToTheView) {
+  SetUpIbanFormField();
+  // Test that the IBANs have propagated to the view.
+  EXPECT_CALL(*mock_view_,
+              Show(&payment_method_controller(), ElementsAreArray(ibans_)));
+  OnBeforeAskForValuesToFill();
+  payment_method_controller().Show(std::move(mock_view_),
+                                   ttf_delegate().GetWeakPointer(), ibans_);
+  OnAfterAskForValuesToFill();
+}
+
 TEST_F(TouchToFillPaymentMethodControllerTest, ScanCreditCardIsCalled) {
   OnBeforeAskForValuesToFill();
   payment_method_controller().Show(std::move(mock_view_),
@@ -195,7 +220,8 @@
   payment_method_controller().ScanCreditCard(nullptr);
 }
 
-TEST_F(TouchToFillPaymentMethodControllerTest, ShowPaymentMethodSettingsIsCalled) {
+TEST_F(TouchToFillPaymentMethodControllerTest,
+       ShowPaymentMethodSettingsIsCalledForCards) {
   OnBeforeAskForValuesToFill();
   payment_method_controller().Show(std::move(mock_view_),
                                 ttf_delegate().GetWeakPointer(), credit_cards_);
@@ -204,6 +230,17 @@
   payment_method_controller().ShowPaymentMethodSettings(nullptr);
 }
 
+TEST_F(TouchToFillPaymentMethodControllerTest,
+       ShowPaymentMethodSettingsIsCalledForIbans) {
+  SetUpIbanFormField();
+  OnBeforeAskForValuesToFill();
+  payment_method_controller().Show(std::move(mock_view_),
+                                   ttf_delegate().GetWeakPointer(), ibans_);
+  OnAfterAskForValuesToFill();
+  EXPECT_CALL(ttf_delegate(), ShowPaymentMethodSettings);
+  payment_method_controller().ShowPaymentMethodSettings(nullptr);
+}
+
 TEST_F(TouchToFillPaymentMethodControllerTest, OnDismissedIsCalled) {
   OnBeforeAskForValuesToFill();
   payment_method_controller().Show(std::move(mock_view_),
diff --git a/chrome/browser/touch_to_fill/autofill/android/touch_to_fill_payment_method_view.h b/chrome/browser/touch_to_fill/autofill/android/touch_to_fill_payment_method_view.h
index 48a7404c..37d5aab 100644
--- a/chrome/browser/touch_to_fill/autofill/android/touch_to_fill_payment_method_view.h
+++ b/chrome/browser/touch_to_fill/autofill/android/touch_to_fill_payment_method_view.h
@@ -9,8 +9,9 @@
 
 namespace autofill {
 
-class TouchToFillPaymentMethodViewController;
 class CreditCard;
+class Iban;
+class TouchToFillPaymentMethodViewController;
 
 // The UI interface which prompts the user to select a credit card to fill
 // using Touch To Fill surface.
@@ -19,8 +20,10 @@
   virtual ~TouchToFillPaymentMethodView() = default;
 
   virtual bool Show(TouchToFillPaymentMethodViewController* controller,
-                    base::span<const autofill::CreditCard> cards_to_suggest,
+                    base::span<const CreditCard> cards_to_suggest,
                     bool should_show_scan_credit_card) = 0;
+  virtual bool Show(TouchToFillPaymentMethodViewController* controller,
+                    base::span<const Iban> ibans_to_suggest) = 0;
   virtual void Hide() = 0;
 };
 
diff --git a/chrome/browser/touch_to_fill/autofill/android/touch_to_fill_payment_method_view_impl.cc b/chrome/browser/touch_to_fill/autofill/android/touch_to_fill_payment_method_view_impl.cc
index fcccefa..28fff45 100644
--- a/chrome/browser/touch_to_fill/autofill/android/touch_to_fill_payment_method_view_impl.cc
+++ b/chrome/browser/touch_to_fill/autofill/android/touch_to_fill_payment_method_view_impl.cc
@@ -27,10 +27,9 @@
   Hide();
 }
 
-bool TouchToFillPaymentMethodViewImpl::Show(
+bool TouchToFillPaymentMethodViewImpl::IsReadyToShow(
     TouchToFillPaymentMethodViewController* controller,
-    base::span<const autofill::CreditCard> cards_to_suggest,
-    bool should_show_scan_credit_card) {
+    JNIEnv* env) {
   if (java_object_)
     return false;  // Already shown.
 
@@ -45,7 +44,6 @@
   if (!java_controller)
     return false;
 
-  JNIEnv* env = base::android::AttachCurrentThread();
   java_object_.Reset(Java_TouchToFillPaymentMethodViewBridge_create(
       env, java_controller,
       ProfileAndroid::FromProfile(
@@ -55,6 +53,18 @@
   if (!java_object_)
     return false;
 
+  return true;
+}
+
+bool TouchToFillPaymentMethodViewImpl::Show(
+    TouchToFillPaymentMethodViewController* controller,
+    base::span<const autofill::CreditCard> cards_to_suggest,
+    bool should_show_scan_credit_card) {
+  JNIEnv* env = base::android::AttachCurrentThread();
+  if (!IsReadyToShow(controller, env)) {
+    return false;
+  }
+
   std::vector<base::android::ScopedJavaLocalRef<jobject>> credit_cards_array;
   credit_cards_array.reserve(cards_to_suggest.size());
   for (const autofill::CreditCard& card : cards_to_suggest) {
@@ -67,6 +77,25 @@
   return true;
 }
 
+bool TouchToFillPaymentMethodViewImpl::Show(
+    TouchToFillPaymentMethodViewController* controller,
+    base::span<const autofill::Iban> ibans_to_suggest) {
+  JNIEnv* env = base::android::AttachCurrentThread();
+  if (!IsReadyToShow(controller, env)) {
+    return false;
+  }
+
+  std::vector<base::android::ScopedJavaLocalRef<jobject>> ibans_array;
+  ibans_array.reserve(ibans_to_suggest.size());
+  for (const autofill::Iban& iban : ibans_to_suggest) {
+    ibans_array.push_back(
+        PersonalDataManagerAndroid::CreateJavaIbanFromNative(env, iban));
+  }
+  Java_TouchToFillPaymentMethodViewBridge_showSheet(env, java_object_,
+                                                    std::move(ibans_array));
+  return true;
+}
+
 void TouchToFillPaymentMethodViewImpl::Hide() {
   if (java_object_) {
     Java_TouchToFillPaymentMethodViewBridge_hideSheet(
diff --git a/chrome/browser/touch_to_fill/autofill/android/touch_to_fill_payment_method_view_impl.h b/chrome/browser/touch_to_fill/autofill/android/touch_to_fill_payment_method_view_impl.h
index 83b2a2e..5479cc1 100644
--- a/chrome/browser/touch_to_fill/autofill/android/touch_to_fill_payment_method_view_impl.h
+++ b/chrome/browser/touch_to_fill/autofill/android/touch_to_fill_payment_method_view_impl.h
@@ -16,8 +16,9 @@
 
 namespace autofill {
 
-class TouchToFillPaymentMethodViewController;
 class CreditCard;
+class Iban;
+class TouchToFillPaymentMethodViewController;
 
 // Android implementation of the surface to select a credit card or IBAN to fill.
 // Uses Java TouchToFillPaymentMethodComponent to present a bottom sheet.
@@ -30,10 +31,16 @@
   ~TouchToFillPaymentMethodViewImpl() override;
 
  private:
+  // Returns true if `env`, `java_object_` and `controller` are in a state
+  // where TTF view can be rendered.
+  bool IsReadyToShow(TouchToFillPaymentMethodViewController* controller,
+                     JNIEnv* env);
   // TouchToFillPaymentMethodView:
   bool Show(TouchToFillPaymentMethodViewController* controller,
             base::span<const autofill::CreditCard> cards_to_suggest,
             bool should_show_scan_credit_card) override;
+  bool Show(TouchToFillPaymentMethodViewController* controller,
+            base::span<const autofill::Iban> ibans_to_suggest) override;
   void Hide() override;
 
   // The corresponding Java TouchToFillPaymentMethodViewBridge.
diff --git a/chrome/browser/tpcd/experiment/eligibility_service.cc b/chrome/browser/tpcd/experiment/eligibility_service.cc
index 3f5811e..aad7adc0 100644
--- a/chrome/browser/tpcd/experiment/eligibility_service.cc
+++ b/chrome/browser/tpcd/experiment/eligibility_service.cc
@@ -9,13 +9,12 @@
 #include "base/check.h"
 #include "base/functional/bind.h"
 #include "base/metrics/histogram_functions.h"
-#include "chrome/browser/privacy_sandbox/privacy_sandbox_settings_factory.h"
-#include "chrome/browser/privacy_sandbox/tracking_protection_onboarding_factory.h"
 #include "chrome/browser/tpcd/experiment/eligibility_service_factory.h"
 #include "chrome/browser/tpcd/experiment/experiment_manager.h"
 #include "chrome/browser/tpcd/experiment/tpcd_experiment_features.h"
 #include "components/privacy_sandbox/privacy_sandbox_settings.h"
 #include "components/privacy_sandbox/tpcd_experiment_eligibility.h"
+#include "components/privacy_sandbox/tracking_protection_onboarding.h"
 #include "content/public/browser/cookie_deprecation_label_manager.h"
 #include "content/public/browser/storage_partition.h"
 #include "content/public/common/content_features.h"
@@ -48,15 +47,20 @@
   }
 }
 
-EligibilityService::EligibilityService(Profile* profile,
-                                       ExperimentManager* experiment_manager)
+EligibilityService::EligibilityService(
+    Profile* profile,
+    privacy_sandbox::TrackingProtectionOnboarding*
+        tracking_protection_onboarding,
+    privacy_sandbox::PrivacySandboxSettings* privacy_sandbox_settings,
+    ExperimentManager* experiment_manager)
     : profile_(profile),
-      onboarding_service_(
-          TrackingProtectionOnboardingFactory::GetForProfile(profile_)),
+      onboarding_service_(tracking_protection_onboarding),
+      privacy_sandbox_settings_(privacy_sandbox_settings),
       experiment_manager_(experiment_manager) {
   CHECK(base::FeatureList::IsEnabled(
       features::kCookieDeprecationFacilitatedTesting));
   CHECK(experiment_manager_);
+  CHECK(privacy_sandbox_settings_);
 
   if (onboarding_service_) {
     onboarding_observation_.Observe(onboarding_service_);
@@ -78,7 +82,11 @@
 }
 
 void EligibilityService::Shutdown() {
-  onboarding_service_ = nullptr;
+  if (onboarding_service_) {
+    onboarding_observation_.Reset();
+    onboarding_service_ = nullptr;
+  }
+  privacy_sandbox_settings_ = nullptr;
 }
 
 void EligibilityService::BroadcastProfileEligibility() {
@@ -141,11 +149,8 @@
 
 privacy_sandbox::TpcdExperimentEligibility
 EligibilityService::ProfileEligibility() {
-  auto* privacy_sandbox_settings =
-      PrivacySandboxSettingsFactory::GetForProfile(profile_);
-  CHECK(privacy_sandbox_settings);
-
-  return privacy_sandbox_settings
+  CHECK(privacy_sandbox_settings_);
+  return privacy_sandbox_settings_
       ->GetCookieDeprecationExperimentCurrentEligibility();
 }
 
diff --git a/chrome/browser/tpcd/experiment/eligibility_service.h b/chrome/browser/tpcd/experiment/eligibility_service.h
index 2b24499..70e8639 100644
--- a/chrome/browser/tpcd/experiment/eligibility_service.h
+++ b/chrome/browser/tpcd/experiment/eligibility_service.h
@@ -16,6 +16,7 @@
 #include "components/privacy_sandbox/tracking_protection_onboarding.h"
 
 namespace privacy_sandbox {
+class PrivacySandboxSettings;
 class TrackingProtectionOnboarding;
 }
 
@@ -38,13 +39,19 @@
     : public privacy_sandbox::TrackingProtectionOnboarding::Observer,
       public KeyedService {
  public:
-  EligibilityService(Profile* profile, ExperimentManager* experiment_manager);
+  EligibilityService(
+      Profile* profile,
+      privacy_sandbox::TrackingProtectionOnboarding*
+          tracking_protection_onboarding,
+      privacy_sandbox::PrivacySandboxSettings* privacy_sandbox_settings,
+      ExperimentManager* experiment_manager);
   EligibilityService(const EligibilityService&) = delete;
   EligibilityService& operator=(const EligibilityService&) = delete;
   ~EligibilityService() override;
 
   static EligibilityService* Get(Profile* profile);
 
+  // KeyedService:
   void Shutdown() override;
 
  private:
@@ -75,9 +82,10 @@
           onboarding_status) override;
 
   raw_ptr<Profile> profile_;
-  // onboarding_service_ may be null for OTR and system profiles.
+  // `onboarding_service_` may be null for OTR and system profiles.
   raw_ptr<privacy_sandbox::TrackingProtectionOnboarding> onboarding_service_;
-  // `ExperimentManager` is a singleton and lives forever.
+  raw_ptr<privacy_sandbox::PrivacySandboxSettings> privacy_sandbox_settings_;
+  // `experiment_manager_` is a singleton and lives forever.
   raw_ptr<ExperimentManager> experiment_manager_;
 
   // Set in the constructor, it will always have a value past that point. An
diff --git a/chrome/browser/tpcd/experiment/eligibility_service_factory.cc b/chrome/browser/tpcd/experiment/eligibility_service_factory.cc
index de14e8c..1723240 100644
--- a/chrome/browser/tpcd/experiment/eligibility_service_factory.cc
+++ b/chrome/browser/tpcd/experiment/eligibility_service_factory.cc
@@ -6,6 +6,7 @@
 
 #include "base/feature_list.h"
 #include "chrome/browser/browser_process.h"
+#include "chrome/browser/privacy_sandbox/privacy_sandbox_settings_factory.h"
 #include "chrome/browser/privacy_sandbox/tracking_protection_onboarding_factory.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/tpcd/experiment/eligibility_service.h"
@@ -35,6 +36,7 @@
               .WithGuest(ProfileSelection::kOwnInstance)
               .WithAshInternals(ProfileSelection::kNone)
               .Build()) {
+  DependsOn(PrivacySandboxSettingsFactory::GetInstance());
   DependsOn(TrackingProtectionOnboardingFactory::GetInstance());
 }
 
@@ -46,9 +48,15 @@
 EligibilityServiceFactory::BuildServiceInstanceForBrowserContext(
     content::BrowserContext* context) const {
   Profile* profile = Profile::FromBrowserContext(context);
+  auto* onboarding_service =
+      TrackingProtectionOnboardingFactory::GetForProfile(profile);
+  auto* privacy_sandbox_settings =
+      PrivacySandboxSettingsFactory::GetForProfile(profile);
   if (auto* experiment_manager =
           ExperimentManagerImpl::GetForProfile(profile)) {
-    return std::make_unique<EligibilityService>(profile, experiment_manager);
+    return std::make_unique<EligibilityService>(profile, onboarding_service,
+                                                privacy_sandbox_settings,
+                                                experiment_manager);
   }
 
   if (base::FeatureList::IsEnabled(
@@ -56,8 +64,6 @@
     return nullptr;
   }
 
-  auto* onboarding_service =
-      TrackingProtectionOnboardingFactory::GetForProfile(profile);
   if (onboarding_service) {
     onboarding_service->MaybeResetOnboardingPrefs();
   }
diff --git a/chrome/browser/tpcd/experiment/eligibility_service_unittest.cc b/chrome/browser/tpcd/experiment/eligibility_service_unittest.cc
index 9e5bce4..3d97a55 100644
--- a/chrome/browser/tpcd/experiment/eligibility_service_unittest.cc
+++ b/chrome/browser/tpcd/experiment/eligibility_service_unittest.cc
@@ -65,7 +65,7 @@
     ON_CALL(*experiment_manager_, DidVersionChange)
         .WillByDefault(Return(false));
 
-    auto* privacy_sandbox_settings =
+    privacy_sandbox_settings_ =
         PrivacySandboxSettingsFactory::GetForProfile(&profile_);
     auto privacy_sandbox_delegate = std::make_unique<
         privacy_sandbox_test_util::MockPrivacySandboxSettingsDelegate>();
@@ -73,8 +73,11 @@
     privacy_sandbox_delegate_
         ->SetUpGetCookieDeprecationExperimentCurrentEligibility(
             TpcdExperimentEligibility::Reason::kEligible);
-    privacy_sandbox_settings->SetDelegateForTesting(
+    privacy_sandbox_settings_->SetDelegateForTesting(
         std::move(privacy_sandbox_delegate));
+
+    onboarding_service_ =
+        TrackingProtectionOnboardingFactory::GetForProfile(&profile_);
   }
 
  protected:
@@ -82,8 +85,10 @@
   ScopedTestingLocalState local_state_;
   TestingProfile profile_;
   std::unique_ptr<MockExperimentManager> experiment_manager_;
+  raw_ptr<privacy_sandbox::PrivacySandboxSettings> privacy_sandbox_settings_;
   raw_ptr<privacy_sandbox_test_util::MockPrivacySandboxSettingsDelegate>
       privacy_sandbox_delegate_;
+  raw_ptr<privacy_sandbox::TrackingProtectionOnboarding> onboarding_service_;
 };
 
 class EligibilityServiceTest : public EligibilityServiceTestBase {
@@ -94,10 +99,8 @@
   }
 
  protected:
-  void SetChannelVersion(Profile& profile, version_info::Channel channel) {
-    auto* onboarding_service =
-        TrackingProtectionOnboardingFactory::GetForProfile(&profile);
-    onboarding_service->channel_ = channel;
+  void SetChannelVersion(version_info::Channel channel) {
+    onboarding_service_->channel_ = channel;
   }
 
  private:
@@ -110,7 +113,9 @@
   EXPECT_CALL(*experiment_manager_, IsClientEligible).WillOnce(Return(false));
   EXPECT_CALL(*experiment_manager_, SetClientEligibility).Times(0);
 
-  EligibilityService eligibility_service(&profile_, experiment_manager_.get());
+  EligibilityService eligibility_service(&profile_, onboarding_service_,
+                                         privacy_sandbox_settings_,
+                                         experiment_manager_.get());
 
   histograms.ExpectTotalCount(kReasonForEligibilityStoredInPrefsHistogram, 0);
   histograms.ExpectUniqueSample(
@@ -134,7 +139,9 @@
   EXPECT_CALL(*experiment_manager_, SetClientEligibility(false, _))
       .WillOnce(base::test::RunOnceCallback<1>(false));
 
-  EligibilityService eligibility_service(&profile_, experiment_manager_.get());
+  EligibilityService eligibility_service(&profile_, onboarding_service_,
+                                         privacy_sandbox_settings_,
+                                         experiment_manager_.get());
 
   histograms.ExpectUniqueSample(
       kReasonForEligibilityStoredInPrefsHistogram,
@@ -161,7 +168,9 @@
   EXPECT_CALL(*experiment_manager_, SetClientEligibility(true, _))
       .WillOnce(base::test::RunOnceCallback<1>(true));
 
-  EligibilityService eligibility_service(&profile_, experiment_manager_.get());
+  EligibilityService eligibility_service(&profile_, onboarding_service_,
+                                         privacy_sandbox_settings_,
+                                         experiment_manager_.get());
 
   histograms.ExpectUniqueSample(
       kReasonForEligibilityStoredInPrefsHistogram,
@@ -176,23 +185,23 @@
 TEST_F(EligibilityServiceTest, VersionChange_OnboardingPrefsReset) {
   EXPECT_CALL(*experiment_manager_, DidVersionChange).WillOnce(Return(true));
 
-  SetChannelVersion(profile_, version_info::Channel::BETA);
+  SetChannelVersion(version_info::Channel::BETA);
 
-  auto* onboarding_service =
-      TrackingProtectionOnboardingFactory::GetForProfile(&profile_);
   // Simulate onboarding a profile.
-  onboarding_service->MaybeMarkEligible();
-  onboarding_service->OnboardingNoticeShown();
-  onboarding_service->NoticeActionTaken(
+  onboarding_service_->MaybeMarkEligible();
+  onboarding_service_->OnboardingNoticeShown();
+  onboarding_service_->NoticeActionTaken(
       privacy_sandbox::TrackingProtectionOnboarding::NoticeType::kOnboarding,
       privacy_sandbox::TrackingProtectionOnboarding::NoticeAction::kGotIt);
 
-  EXPECT_EQ(onboarding_service->GetOnboardingStatus(),
+  EXPECT_EQ(onboarding_service_->GetOnboardingStatus(),
             privacy_sandbox::TrackingProtectionOnboarding::OnboardingStatus::
                 kOnboarded);
 
-  EligibilityService eligibility_service(&profile_, experiment_manager_.get());
-  EXPECT_EQ(onboarding_service->GetOnboardingStatus(),
+  EligibilityService eligibility_service(&profile_, onboarding_service_,
+                                         privacy_sandbox_settings_,
+                                         experiment_manager_.get());
+  EXPECT_EQ(onboarding_service_->GetOnboardingStatus(),
             privacy_sandbox::TrackingProtectionOnboarding::OnboardingStatus::
                 kIneligible);
 }
@@ -286,7 +295,9 @@
               ? TpcdExperimentEligibility::Reason::kEligible
               : TpcdExperimentEligibility::Reason::kHasNotSeenNotice)));
 
-  EligibilityService eligibility_service(&profile_, experiment_manager_.get());
+  EligibilityService eligibility_service(&profile_, onboarding_service_,
+                                         privacy_sandbox_settings_,
+                                         experiment_manager_.get());
 
   // Expect mismatch value recorded in histogram.
   histograms().ExpectBucketCount(ProfileEligibilityMismatchHistogramName,
@@ -311,14 +322,14 @@
 
 TEST_F(EligibilityServiceDisable3PCsTest, Onboarded_NotifyManager) {
   EXPECT_CALL(*experiment_manager_, IsClientEligible).WillOnce(Return(true));
-  EligibilityService eligibility_service(&profile_, experiment_manager_.get());
+  EligibilityService eligibility_service(&profile_, onboarding_service_,
+                                         privacy_sandbox_settings_,
+                                         experiment_manager_.get());
 
   EXPECT_CALL(*experiment_manager_, NotifyProfileTrackingProtectionOnboarded);
 
-  auto* onboarding_service =
-      TrackingProtectionOnboardingFactory::GetForProfile(&profile_);
   // Simulate onboarding a profile.
-  onboarding_service->OnboardingNoticeShown();
+  onboarding_service_->OnboardingNoticeShown();
 }
 
 class EligibilityServiceSilentOnboardingTest
@@ -337,14 +348,14 @@
 
 TEST_F(EligibilityServiceSilentOnboardingTest, Onboarded_NotifyManager) {
   EXPECT_CALL(*experiment_manager_, IsClientEligible).WillOnce(Return(true));
-  EligibilityService eligibility_service(&profile_, experiment_manager_.get());
+  EligibilityService eligibility_service(&profile_, onboarding_service_,
+                                         privacy_sandbox_settings_,
+                                         experiment_manager_.get());
 
   EXPECT_CALL(*experiment_manager_, NotifyProfileTrackingProtectionOnboarded);
 
-  auto* onboarding_service =
-      TrackingProtectionOnboardingFactory::GetForProfile(&profile_);
   // Simulate onboarding a profile.
-  onboarding_service->SilentOnboardingNoticeShown();
+  onboarding_service_->SilentOnboardingNoticeShown();
 }
 
 }  // namespace tpcd::experiment
diff --git a/chrome/browser/ui/BUILD.gn b/chrome/browser/ui/BUILD.gn
index 18ec3a62..d738d33 100644
--- a/chrome/browser/ui/BUILD.gn
+++ b/chrome/browser/ui/BUILD.gn
@@ -1399,6 +1399,9 @@
       "performance_controls/performance_controls_hats_service_factory.h",
       "performance_controls/performance_controls_metrics.cc",
       "performance_controls/performance_controls_metrics.h",
+      "performance_controls/performance_intervention_button_controller.cc",
+      "performance_controls/performance_intervention_button_controller.h",
+      "performance_controls/performance_intervention_button_controller_delegate.h",
       "performance_controls/tab_resource_usage_collector.cc",
       "performance_controls/tab_resource_usage_collector.h",
       "performance_controls/tab_resource_usage_tab_helper.cc",
@@ -5840,6 +5843,8 @@
       "views/performance_controls/memory_saver_chip_view.h",
       "views/performance_controls/memory_saver_resource_view.cc",
       "views/performance_controls/memory_saver_resource_view.h",
+      "views/performance_controls/performance_intervention_button.cc",
+      "views/performance_controls/performance_intervention_button.h",
       "views/permissions/chip/multi_image_container.cc",
       "views/permissions/chip/multi_image_container.h",
       "views/permissions/chip/permission_chip_theme.h",
diff --git a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/AutocompleteController.java b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/AutocompleteController.java
index fadcb61e..5f295ec 100644
--- a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/AutocompleteController.java
+++ b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/AutocompleteController.java
@@ -58,14 +58,10 @@
          *
          * @param autocompleteResult The current set of autocomplete matches for previously supplied
          *     query.
-         * @param inlineAutocompleteText The text to offer as an inline autocompletion.
          * @param isFinal Whether this result is transitory (false) or final (true). Final result
          *     always comes in last, even if the query is canceled.
          */
-        void onSuggestionsReceived(
-                @NonNull AutocompleteResult autocompleteResult,
-                @NonNull String inlineAutocompleteText,
-                boolean isFinal);
+        void onSuggestionsReceived(@NonNull AutocompleteResult autocompleteResult, boolean isFinal);
     }
 
     /**
@@ -247,13 +243,12 @@
     @CalledByNative
     @VisibleForTesting
     public void onSuggestionsReceived(
-            @NonNull AutocompleteResult autocompleteResult,
-            @NonNull String inlineAutocompleteText,
-            boolean isFinal) {
+            @NonNull AutocompleteResult autocompleteResult, boolean isFinal) {
         mAutocompleteResult = autocompleteResult;
+
         // Notify callbacks of suggestions.
         for (OnSuggestionsReceivedListener listener : mListeners) {
-            listener.onSuggestionsReceived(autocompleteResult, inlineAutocompleteText, isFinal);
+            listener.onSuggestionsReceived(autocompleteResult, isFinal);
         }
     }
 
diff --git a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/AutocompleteMediator.java b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/AutocompleteMediator.java
index b3c1401..0827e6b 100644
--- a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/AutocompleteMediator.java
+++ b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/AutocompleteMediator.java
@@ -342,7 +342,7 @@
                         && pageClass != PageClassification.ANDROID_SHORTCUTS_WIDGET_VALUE)) {
             return;
         }
-        onSuggestionsReceived(CachedZeroSuggestionsManager.readFromCache(), "", true);
+        onSuggestionsReceived(CachedZeroSuggestionsManager.readFromCache(), true);
     }
 
     /** Notify the mediator that a item selection is pending and should be accepted. */
@@ -832,11 +832,13 @@
 
     @Override
     public void onSuggestionsReceived(
-            @NonNull AutocompleteResult autocompleteResult,
-            @NonNull String inlineAutocompleteText,
-            boolean isFinal) {
+            @NonNull AutocompleteResult autocompleteResult, boolean isFinal) {
         maybeCacheResult(autocompleteResult);
 
+        @Nullable AutocompleteMatch defaultMatch = autocompleteResult.getDefaultMatch();
+        String inlineAutocompleteText =
+                defaultMatch != null ? defaultMatch.getInlineAutocompletion() : "";
+
         final List<AutocompleteMatch> newSuggestions = autocompleteResult.getSuggestionsList();
         String userText = mUrlBarEditingTextProvider.getTextWithoutAutocomplete();
         mUrlTextAfterSuggestionsReceived = userText + inlineAutocompleteText;
diff --git a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/AutocompleteMediatorUnitTest.java b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/AutocompleteMediatorUnitTest.java
index f0730af..cfa2ff81 100644
--- a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/AutocompleteMediatorUnitTest.java
+++ b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/AutocompleteMediatorUnitTest.java
@@ -264,10 +264,14 @@
     private List<AutocompleteMatch> buildSampleSuggestionsList(int count, String prefix) {
         List<AutocompleteMatch> list = new ArrayList<>();
         for (int index = 0; index < count; ++index) {
-            list.add(
+            AutocompleteMatchBuilder builder =
                     AutocompleteMatchBuilder.searchWithType(OmniboxSuggestionType.SEARCH_SUGGEST)
-                            .setDisplayText(prefix + (index + 1))
-                            .build());
+                            .setDisplayText(prefix + (index + 1));
+            if (index == 0) {
+                builder.setInlineAutocompletion("inline_autocomplete")
+                        .setAllowedToBeDefaultMatch(true);
+            }
+            list.add(builder.build());
         }
 
         return list;
@@ -322,7 +326,7 @@
         final int maximumListHeight = SUGGESTION_MIN_HEIGHT * 7;
 
         mMediator.onSuggestionDropdownHeightChanged(maximumListHeight);
-        mMediator.onSuggestionsReceived(AutocompleteResult.EMPTY_RESULT, "", true);
+        mMediator.onSuggestionsReceived(AutocompleteResult.EMPTY_RESULT, /* isFinal= */ true);
 
         Assert.assertEquals(0, mSuggestionModels.size());
         Assert.assertFalse(mListModel.get(SuggestionListProperties.VISIBLE));
@@ -336,7 +340,7 @@
         final int maximumListHeight = SUGGESTION_MIN_HEIGHT * 7;
 
         mMediator.onSuggestionDropdownHeightChanged(maximumListHeight);
-        mMediator.onSuggestionsReceived(AutocompleteResult.EMPTY_RESULT, "", true);
+        mMediator.onSuggestionsReceived(AutocompleteResult.EMPTY_RESULT, /* isFinal= */ true);
 
         Assert.assertEquals(0, mSuggestionModels.size());
         Assert.assertFalse(mListModel.get(SuggestionListProperties.VISIBLE));
@@ -352,7 +356,7 @@
 
         mMediator.onSuggestionDropdownHeightChanged(heightWithOneConcealedItem);
         mMediator.onSuggestionsReceived(
-                AutocompleteResult.fromCache(mSuggestionsList, null), "", true);
+                AutocompleteResult.fromCache(mSuggestionsList, null), /* isFinal= */ true);
 
         // With fully concealed elements, scroll should trigger keyboard hide.
         reset(mAutocompleteDelegate);
@@ -394,7 +398,7 @@
         // fully concealed items on the suggestions list.
         mMediator.onSuggestionDropdownHeightChanged(heightOfOAllSuggestions);
         mMediator.onSuggestionsReceived(
-                AutocompleteResult.fromCache(mSuggestionsList, null), "", true);
+                AutocompleteResult.fromCache(mSuggestionsList, null), /* isFinal= */ true);
 
         // Build separate list of suggestions so that these are accepted as a new set.
         // We want to follow the same restrictions as the original list (specifically: have a
@@ -403,7 +407,8 @@
         List<AutocompleteMatch> newList =
                 buildSampleSuggestionsList(mSuggestionsList.size(), "SuggestionB");
         mMediator.onSuggestionDropdownHeightChanged(heightWithOneConcealedItem);
-        mMediator.onSuggestionsReceived(AutocompleteResult.fromCache(newList, null), "", true);
+        mMediator.onSuggestionsReceived(
+                AutocompleteResult.fromCache(newList, null), /* isFinal= */ true);
     }
 
     @Test
@@ -420,16 +425,14 @@
 
         // Report height change with keyboard visible
         mMediator.onSuggestionDropdownHeightChanged(heightWithOneConcealedItem);
-        mMediator.onSuggestionsReceived(
-                AutocompleteResult.fromCache(mSuggestionsList, null), "", true);
+        mMediator.onSuggestionsReceived(AutocompleteResult.fromCache(mSuggestionsList, null), true);
 
         // "Hide keyboard", report larger area and re-evaluate the results. We should see no
         // difference, as the logic should only evaluate presence of items concealed when keyboard
         // is active.
         when(mAutocompleteDelegate.isKeyboardActive()).thenReturn(false);
         mMediator.onSuggestionDropdownHeightChanged(heightOfOAllSuggestions);
-        mMediator.onSuggestionsReceived(
-                AutocompleteResult.fromCache(mSuggestionsList, null), "", true);
+        mMediator.onSuggestionsReceived(AutocompleteResult.fromCache(mSuggestionsList, null), true);
     }
 
     @Test
@@ -634,14 +637,20 @@
     public void onSuggestionsReceived_sendsOnSuggestionsChanged() {
         mMediator.onNativeInitialized();
         mMediator.onOmniboxSessionStateChange(true);
-        mMediator.onSuggestionsReceived(
-                AutocompleteResult.fromCache(mSuggestionsList, null), "inline_autocomplete", true);
+        mMediator.onSuggestionsReceived(AutocompleteResult.fromCache(mSuggestionsList, null), true);
         verify(mAutocompleteDelegate).onSuggestionsChanged("inline_autocomplete", true);
 
         // Ensure duplicate requests are not suppressed, to preserve the
         // relationship between Native and Java AutocompleteResult objects.
-        mMediator.onSuggestionsReceived(
-                AutocompleteResult.fromCache(mSuggestionsList, null), "inline_autocomplete2", true);
+        mSuggestionsList.remove(0);
+        mSuggestionsList.add(
+                0,
+                AutocompleteMatchBuilder.searchWithType(OmniboxSuggestionType.SEARCH_SUGGEST)
+                        .setDisplayText("Suggestion1")
+                        .setInlineAutocompletion("inline_autocomplete2")
+                        .setAllowedToBeDefaultMatch(true)
+                        .build());
+        mMediator.onSuggestionsReceived(AutocompleteResult.fromCache(mSuggestionsList, null), true);
         verify(mAutocompleteDelegate).onSuggestionsChanged("inline_autocomplete", true);
     }
 
@@ -744,7 +753,7 @@
         mMediator.setLayoutDirection(View.LAYOUT_DIRECTION_RTL);
         mMediator.onSuggestionDropdownHeightChanged(Integer.MAX_VALUE);
         mMediator.onSuggestionsReceived(
-                AutocompleteResult.fromCache(mSuggestionsList, null), "", true);
+                AutocompleteResult.fromCache(mSuggestionsList, null), /* isFinal= */ true);
         Assert.assertEquals(mSuggestionsList.size(), mSuggestionModels.size());
         for (int i = 0; i < mSuggestionModels.size(); i++) {
             Assert.assertEquals(
@@ -763,7 +772,7 @@
         mMediator.onNativeInitialized();
         mMediator.onSuggestionDropdownHeightChanged(Integer.MAX_VALUE);
         mMediator.onSuggestionsReceived(
-                AutocompleteResult.fromCache(mSuggestionsList, null), "", true);
+                AutocompleteResult.fromCache(mSuggestionsList, null), /* isFinal= */ true);
         Assert.assertEquals(mSuggestionsList.size(), mSuggestionModels.size());
 
         mMediator.setLayoutDirection(View.LAYOUT_DIRECTION_RTL);
@@ -1031,17 +1040,17 @@
 
         // Report first results. Observe first results histogram reported.
         ShadowPausedSystemClock.advanceBy(Duration.ofMillis(100));
-        mMediator.onSuggestionsReceived(mAutocompleteResult, "", /* isFinal= */ false);
+        mMediator.onSuggestionsReceived(mAutocompleteResult, /* isFinal= */ false);
         verifySuggestionRequestToUiModelHistograms(1, 100, 0, null);
 
         // Report next results. Observe first results histogram not reported.
         ShadowPausedSystemClock.advanceBy(Duration.ofMillis(300));
-        mMediator.onSuggestionsReceived(mAutocompleteResult, "", /* isFinal= */ false);
+        mMediator.onSuggestionsReceived(mAutocompleteResult, /* isFinal= */ false);
         verifySuggestionRequestToUiModelHistograms(1, 100, 0, null);
 
         // Report last results. Observe two histograms reported.
         ShadowPausedSystemClock.advanceBy(Duration.ofMillis(100));
-        mMediator.onSuggestionsReceived(mAutocompleteResult, "", /* isFinal= */ true);
+        mMediator.onSuggestionsReceived(mAutocompleteResult, /* isFinal= */ true);
         verifySuggestionRequestToUiModelHistograms(1, 100, 1, 500);
     }
 
@@ -1066,7 +1075,7 @@
 
         // Report first results. Observe first results histogram reported.
         ShadowPausedSystemClock.advanceBy(Duration.ofMillis(10));
-        mMediator.onSuggestionsReceived(mAutocompleteResult, "", /* isFinal= */ false);
+        mMediator.onSuggestionsReceived(mAutocompleteResult, /* isFinal= */ false);
         verifySuggestionRequestToUiModelHistograms(1, 10, 0, null);
 
         // Cancel the interaction.
@@ -1099,11 +1108,11 @@
         mMediator.onOmniboxSessionStateChange(false);
 
         // Report first results. Observe no report (no focus).
-        mMediator.onSuggestionsReceived(mAutocompleteResult, "", /* isFinal= */ false);
+        mMediator.onSuggestionsReceived(mAutocompleteResult, /* isFinal= */ false);
         verifySuggestionRequestToUiModelHistograms(0, null, 0, null);
 
         // Report last results. Observe no final report (no focus).
-        mMediator.onSuggestionsReceived(mAutocompleteResult, "", /* isFinal= */ true);
+        mMediator.onSuggestionsReceived(mAutocompleteResult, /* isFinal= */ true);
         verifySuggestionRequestToUiModelHistograms(0, null, 0, null);
     }
 
@@ -1128,7 +1137,7 @@
 
         // Report first result as final. Observe both metrics reported.
         ShadowPausedSystemClock.advanceBy(Duration.ofMillis(150));
-        mMediator.onSuggestionsReceived(mAutocompleteResult, "", /* isFinal= */ true);
+        mMediator.onSuggestionsReceived(mAutocompleteResult, /* isFinal= */ true);
         verifySuggestionRequestToUiModelHistograms(1, 150, 1, 150);
     }
 
@@ -1153,7 +1162,7 @@
 
         // Report first result as final. Observe both metrics reported.
         ShadowPausedSystemClock.advanceBy(Duration.ofMillis(150));
-        mMediator.onSuggestionsReceived(mAutocompleteResult, "", /* isFinal= */ false);
+        mMediator.onSuggestionsReceived(mAutocompleteResult, /* isFinal= */ false);
         verifySuggestionRequestToUiModelHistograms(1, 150, 0, null);
 
         // No change on key press. No unexpected recordings.
@@ -1164,7 +1173,7 @@
 
         // No change on key press. No unexpected recordings.
         ShadowPausedSystemClock.advanceBy(Duration.ofMillis(100));
-        mMediator.onSuggestionsReceived(mAutocompleteResult, "", /* isFinal= */ true);
+        mMediator.onSuggestionsReceived(mAutocompleteResult, /* isFinal= */ true);
         verifySuggestionRequestToUiModelHistograms(2, 100, 1, 100);
     }
 
diff --git a/chrome/browser/ui/android/signin/java/src/org/chromium/chrome/browser/ui/signin/fullscreen_signin/FullscreenSigninMediator.java b/chrome/browser/ui/android/signin/java/src/org/chromium/chrome/browser/ui/signin/fullscreen_signin/FullscreenSigninMediator.java
index ffe507f..bd879d8 100644
--- a/chrome/browser/ui/android/signin/java/src/org/chromium/chrome/browser/ui/signin/fullscreen_signin/FullscreenSigninMediator.java
+++ b/chrome/browser/ui/android/signin/java/src/org/chromium/chrome/browser/ui/signin/fullscreen_signin/FullscreenSigninMediator.java
@@ -30,7 +30,6 @@
 import org.chromium.chrome.browser.ui.signin.account_picker.AccountPickerCoordinator;
 import org.chromium.chrome.browser.ui.signin.account_picker.AccountPickerDialogCoordinator;
 import org.chromium.chrome.browser.ui.signin.fullscreen_signin.FullscreenSigninCoordinator.Delegate;
-import org.chromium.chrome.browser.ui.signin.fullscreen_signin.FullscreenSigninProperties.FrePolicy;
 import org.chromium.components.externalauth.ExternalAuthUtils;
 import org.chromium.components.signin.AccountManagerFacade;
 import org.chromium.components.signin.AccountManagerFacadeProvider;
@@ -229,10 +228,7 @@
                             + isSigninDisabledByPolicy);
             isMetricsReportingDisabledByPolicy =
                     !mPrivacyPreferencesManager.isUsageAndCrashReportingPermittedByPolicy();
-
-            final FrePolicy frePolicy = new FrePolicy();
-            frePolicy.metricsReportingDisabledByPolicy = isMetricsReportingDisabledByPolicy;
-            mModel.set(FullscreenSigninProperties.FRE_POLICY, frePolicy);
+            mModel.set(FullscreenSigninProperties.SHOW_ENTERPRISE_MANAGEMENT_NOTICE, true);
         }
 
         boolean isSigninSupported =
diff --git a/chrome/browser/ui/android/signin/java/src/org/chromium/chrome/browser/ui/signin/fullscreen_signin/FullscreenSigninProperties.java b/chrome/browser/ui/android/signin/java/src/org/chromium/chrome/browser/ui/signin/fullscreen_signin/FullscreenSigninProperties.java
index 3005e81..350d261 100644
--- a/chrome/browser/ui/android/signin/java/src/org/chromium/chrome/browser/ui/signin/fullscreen_signin/FullscreenSigninProperties.java
+++ b/chrome/browser/ui/android/signin/java/src/org/chromium/chrome/browser/ui/signin/fullscreen_signin/FullscreenSigninProperties.java
@@ -17,15 +17,6 @@
 import org.chromium.ui.modelutil.PropertyModel.WritableObjectPropertyKey;
 
 class FullscreenSigninProperties {
-    /**
-     * This class regroups the policies supported by FRE.
-     * When forced sign-in will be supported, we could use an enum field in this class
-     * instead of the boolean property.
-     */
-    static class FrePolicy {
-        public boolean metricsReportingDisabledByPolicy;
-    }
-
     static final ReadableObjectPropertyKey<OnClickListener> ON_SELECTED_ACCOUNT_CLICKED =
             new ReadableObjectPropertyKey<>("on_selected_account_clicked");
     static final WritableObjectPropertyKey<DisplayableProfileData> SELECTED_ACCOUNT_DATA =
@@ -54,8 +45,8 @@
     static final WritableBooleanPropertyKey SHOW_INITIAL_LOAD_PROGRESS_SPINNER =
             new WritableBooleanPropertyKey("show_initial_load_progress_spinner");
 
-    static final WritableObjectPropertyKey<FrePolicy> FRE_POLICY =
-            new WritableObjectPropertyKey<>("fre_policy");
+    static final WritableBooleanPropertyKey SHOW_ENTERPRISE_MANAGEMENT_NOTICE =
+            new WritableBooleanPropertyKey("show_enterprise_management_notice");
 
     static final WritableBooleanPropertyKey IS_SIGNIN_SUPPORTED =
             new WritableBooleanPropertyKey("is_signin_supported");
@@ -76,7 +67,7 @@
                 SHOW_SIGNIN_PROGRESS_SPINNER_WITH_TEXT,
                 SHOW_SIGNIN_PROGRESS_SPINNER,
                 SHOW_INITIAL_LOAD_PROGRESS_SPINNER,
-                FRE_POLICY,
+                SHOW_ENTERPRISE_MANAGEMENT_NOTICE,
                 IS_SIGNIN_SUPPORTED,
                 TITLE_STRING_ID,
                 FOOTER_STRING,
@@ -97,7 +88,7 @@
                 .with(ON_CONTINUE_AS_CLICKED, v -> onContinueAsClicked.run())
                 .with(ON_DISMISS_CLICKED, v -> onDismissClicked.run())
                 .with(SHOW_INITIAL_LOAD_PROGRESS_SPINNER, true)
-                .with(FRE_POLICY, null)
+                .with(SHOW_ENTERPRISE_MANAGEMENT_NOTICE, false)
                 .with(IS_SIGNIN_SUPPORTED, isSigninSupported)
                 .with(TITLE_STRING_ID, titleStringId)
                 .with(FOOTER_STRING, footerString)
diff --git a/chrome/browser/ui/android/signin/java/src/org/chromium/chrome/browser/ui/signin/fullscreen_signin/FullscreenSigninViewBinder.java b/chrome/browser/ui/android/signin/java/src/org/chromium/chrome/browser/ui/signin/fullscreen_signin/FullscreenSigninViewBinder.java
index fc89705..99b9d36 100644
--- a/chrome/browser/ui/android/signin/java/src/org/chromium/chrome/browser/ui/signin/fullscreen_signin/FullscreenSigninViewBinder.java
+++ b/chrome/browser/ui/android/signin/java/src/org/chromium/chrome/browser/ui/signin/fullscreen_signin/FullscreenSigninViewBinder.java
@@ -61,7 +61,7 @@
                 initialLoadProgressSpinner.setVisibility(View.GONE);
             }
             updateVisibility(view, model);
-        } else if (propertyKey == FullscreenSigninProperties.FRE_POLICY) {
+        } else if (propertyKey == FullscreenSigninProperties.SHOW_ENTERPRISE_MANAGEMENT_NOTICE) {
             updateBrowserManagedHeaderView(view, model);
         } else if (propertyKey == FullscreenSigninProperties.IS_SIGNIN_SUPPORTED) {
             if (!model.get(FullscreenSigninProperties.IS_SIGNIN_SUPPORTED)) {
@@ -86,23 +86,19 @@
 
     private static void updateBrowserManagedHeaderView(
             FullscreenSigninView view, PropertyModel model) {
-        // Supervised accounts do not have any enterprise policy set, but they set app
-        // restrictions which the policy load listener considers as policy. But if child
-        // accounts are loaded dynamically, policy load listener may say there are no
-        // policies on device. Because of the entangled nature of IS_SELECTED_ACCOUNT_SUPERVISED
-        // and FRE_POLICY they are both handled in this function as one of these properties
-        // will get updated before the other.
-        final boolean hasPolicy = model.get(FullscreenSigninProperties.FRE_POLICY) != null;
-        final boolean isAccountSupervised =
-                model.get(FullscreenSigninProperties.IS_SELECTED_ACCOUNT_SUPERVISED);
-
-        if (isAccountSupervised) {
+        // Supervised accounts do not have any enterprise policy set, but they set app restrictions
+        // which the policy load listener considers as policy. But if child accounts are loaded
+        // dynamically, policy load listener may say there are no policies on device. Because of the
+        // entangled nature of IS_SELECTED_ACCOUNT_SUPERVISED and SHOW_ENTERPRISE_MANAGEMENT_NOTICE
+        // they are both handled in this function as one of these properties will get updated before
+        // the other.
+        if (model.get(FullscreenSigninProperties.IS_SELECTED_ACCOUNT_SUPERVISED)) {
             view.getBrowserManagedHeaderView().setVisibility(View.VISIBLE);
             view.getPrivacyDisclaimer().setText(R.string.fre_browser_managed_by_parent);
             view.getPrivacyDisclaimer()
                     .setCompoundDrawablesRelativeWithIntrinsicBounds(
                             R.drawable.ic_account_child_20dp, 0, 0, 0);
-        } else if (hasPolicy) {
+        } else if (model.get(FullscreenSigninProperties.SHOW_ENTERPRISE_MANAGEMENT_NOTICE)) {
             view.getBrowserManagedHeaderView().setVisibility(View.VISIBLE);
             view.getPrivacyDisclaimer().setText(R.string.fre_browser_managed_by_organization);
             view.getPrivacyDisclaimer()
@@ -135,20 +131,21 @@
                 model.get(FullscreenSigninProperties.SHOW_INITIAL_LOAD_PROGRESS_SPINNER);
         final boolean isSelectedAccountSupervised =
                 model.get(FullscreenSigninProperties.IS_SELECTED_ACCOUNT_SUPERVISED);
-        final boolean hasPolicy = model.get(FullscreenSigninProperties.FRE_POLICY) != null;
+        final boolean showManagementNotice =
+                model.get(FullscreenSigninProperties.SHOW_ENTERPRISE_MANAGEMENT_NOTICE);
         view.getTitle().setVisibility(showInitialLoadProgressSpinner ? View.GONE : View.VISIBLE);
         view.getSubtitle()
                 .setVisibility(
                         !showInitialLoadProgressSpinner
                                         && !isSelectedAccountSupervised
-                                        && !hasPolicy
+                                        && !showManagementNotice
                                 ? View.VISIBLE
                                 : View.GONE);
 
         final int selectedAccountVisibility =
                 !showInitialLoadProgressSpinner
-                                && model
-                        .get(FullscreenSigninProperties.SELECTED_ACCOUNT_DATA) != null
+                                && model.get(FullscreenSigninProperties.SELECTED_ACCOUNT_DATA)
+                                        != null
                                 && model.get(FullscreenSigninProperties.IS_SIGNIN_SUPPORTED)
                         ? View.VISIBLE
                         : View.GONE;
diff --git a/chrome/browser/ui/android/toolbar/adaptive_toolbar_enums.h b/chrome/browser/ui/android/toolbar/adaptive_toolbar_enums.h
index 4ba2c42..c7ea874 100644
--- a/chrome/browser/ui/android/toolbar/adaptive_toolbar_enums.h
+++ b/chrome/browser/ui/android/toolbar/adaptive_toolbar_enums.h
@@ -35,7 +35,9 @@
   kAddToBookmarks = 9,
   // ReadAloud action.
   kReadAloud = 10,
-  kMaxValue = kReadAloud,
+  // Value for testing.
+  kTestButton = 11,
+  kMaxValue = kTestButton,
 };
 
 #endif  // CHROME_BROWSER_UI_ANDROID_TOOLBAR_ADAPTIVE_TOOLBAR_ENUMS_H_
diff --git a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/BaseButtonDataProviderTest.java b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/BaseButtonDataProviderTest.java
index 06a502a..951aaf0 100644
--- a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/BaseButtonDataProviderTest.java
+++ b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/BaseButtonDataProviderTest.java
@@ -42,10 +42,7 @@
 /** Unit test for {@link BaseButtonDataProvider}. */
 @RunWith(BaseRobolectricTestRunner.class)
 @Config(manifest = Config.NONE)
-@EnableFeatures({
-    ChromeFeatureList.CONTEXTUAL_PAGE_ACTION_PRICE_TRACKING,
-    ChromeFeatureList.ADAPTIVE_BUTTON_IN_TOP_TOOLBAR_CUSTOMIZATION_V2
-})
+@EnableFeatures({ChromeFeatureList.ADAPTIVE_BUTTON_IN_TOP_TOOLBAR_CUSTOMIZATION_V2})
 public class BaseButtonDataProviderTest {
     private class TestButtonDataProvider extends BaseButtonDataProvider {
         public TestButtonDataProvider(
diff --git a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/adaptive/AdaptiveToolbarFeatures.java b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/adaptive/AdaptiveToolbarFeatures.java
index e90d6d7..a7a235e 100644
--- a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/adaptive/AdaptiveToolbarFeatures.java
+++ b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/adaptive/AdaptiveToolbarFeatures.java
@@ -59,6 +59,10 @@
     /** Default action chip delay for reader mode. */
     public static final int DEFAULT_READER_MODE_ACTION_CHIP_DELAY_MS = 3000;
 
+    @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
+    public static final String CONTEXTUAL_PAGE_ACTION_TEST_FEATURE_NAME =
+            "CONTEXTUAL_PAGE_ACTION_TEST_FEATURE_NAME";
+
     @AdaptiveToolbarButtonVariant private static Integer sButtonVariant;
 
     /** For testing only. */
@@ -69,11 +73,17 @@
     private static Boolean sShowUiOnlyAfterReadyForTesting;
     private static HashMap<Integer, Boolean> sActionChipOverridesForTesting;
     private static HashMap<Integer, Boolean> sAlternativeColorOverridesForTesting;
+    private static HashMap<Integer, Boolean> sIsDynamicActionOverridesForTesting;
 
     /**
      * @return Whether the button variant is a dynamic action.
      */
     public static boolean isDynamicAction(@AdaptiveToolbarButtonVariant int variant) {
+        if (sIsDynamicActionOverridesForTesting != null
+                && sIsDynamicActionOverridesForTesting.containsKey(variant)) {
+            return Boolean.TRUE.equals(sIsDynamicActionOverridesForTesting.get(variant));
+        }
+
         switch (variant) {
             case AdaptiveToolbarButtonVariant.UNKNOWN:
             case AdaptiveToolbarButtonVariant.NONE:
@@ -92,10 +102,10 @@
     private static String getFeatureNameForButtonVariant(
             @AdaptiveToolbarButtonVariant int variant) {
         switch (variant) {
-            case AdaptiveToolbarButtonVariant.PRICE_TRACKING:
-                return ChromeFeatureList.CONTEXTUAL_PAGE_ACTION_PRICE_TRACKING;
             case AdaptiveToolbarButtonVariant.READER_MODE:
                 return ChromeFeatureList.CONTEXTUAL_PAGE_ACTION_READER_MODE;
+            case AdaptiveToolbarButtonVariant.TEST_BUTTON:
+                return CONTEXTUAL_PAGE_ACTION_TEST_FEATURE_NAME;
             default:
                 throw new IllegalArgumentException(
                         "Provided button variant not assigned to feature");
@@ -204,9 +214,8 @@
     }
 
     public static boolean isPriceTrackingPageActionEnabled() {
-        return ChromeFeatureList.isEnabled(ChromeFeatureList.CONTEXTUAL_PAGE_ACTIONS)
-                && ChromeFeatureList.isEnabled(
-                        ChromeFeatureList.CONTEXTUAL_PAGE_ACTION_PRICE_TRACKING);
+        // Price tracking is now default enabled, only depending on the global CPA flag.
+        return ChromeFeatureList.isEnabled(ChromeFeatureList.CONTEXTUAL_PAGE_ACTIONS);
     }
 
     public static boolean isReaderModePageActionEnabled() {
@@ -349,6 +358,15 @@
         ResettersForTesting.register(() -> sAlternativeColorOverridesForTesting = null);
     }
 
+    public static void setIsDynamicActionForTesting(
+            @AdaptiveToolbarButtonVariant int buttonVariant, Boolean isDynamicAction) {
+        if (sIsDynamicActionOverridesForTesting == null) {
+            sIsDynamicActionOverridesForTesting = new HashMap<>();
+        }
+        sIsDynamicActionOverridesForTesting.put(buttonVariant, isDynamicAction);
+        ResettersForTesting.register(() -> sIsDynamicActionOverridesForTesting = null);
+    }
+
     public static void clearParsedParamsForTesting() {
         sButtonVariant = null;
         sDefaultSegmentForTesting = null;
diff --git a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/optional_button/OptionalButtonCoordinatorTest.java b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/optional_button/OptionalButtonCoordinatorTest.java
index 62e55df..42ca7308 100644
--- a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/optional_button/OptionalButtonCoordinatorTest.java
+++ b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/optional_button/OptionalButtonCoordinatorTest.java
@@ -40,11 +40,11 @@
 import org.chromium.base.FeatureList;
 import org.chromium.base.FeatureList.TestValues;
 import org.chromium.base.test.BaseRobolectricTestRunner;
-import org.chromium.chrome.browser.flags.ChromeFeatureList;
 import org.chromium.chrome.browser.toolbar.ButtonData;
 import org.chromium.chrome.browser.toolbar.ButtonData.ButtonSpec;
 import org.chromium.chrome.browser.toolbar.ButtonDataImpl;
 import org.chromium.chrome.browser.toolbar.adaptive.AdaptiveToolbarButtonVariant;
+import org.chromium.chrome.browser.toolbar.adaptive.AdaptiveToolbarFeatures;
 import org.chromium.chrome.browser.toolbar.optional_button.OptionalButtonCoordinator.TransitionType;
 import org.chromium.chrome.browser.user_education.IPHCommandBuilder;
 import org.chromium.chrome.browser.user_education.UserEducationHelper;
@@ -301,9 +301,13 @@
 
     @Test
     public void testUpdateButton_actionChipResourceIdGetsRemovedWhenNotInVariant() {
+        AdaptiveToolbarFeatures.setIsDynamicActionForTesting(
+                AdaptiveToolbarButtonVariant.TEST_BUTTON, true);
         TestValues testValues = new TestValues();
         testValues.addFieldTrialParamOverride(
-                ChromeFeatureList.CONTEXTUAL_PAGE_ACTION_PRICE_TRACKING, "action_chip", "false");
+                AdaptiveToolbarFeatures.CONTEXTUAL_PAGE_ACTION_TEST_FEATURE_NAME,
+                "action_chip",
+                "false");
         FeatureList.setTestValues(testValues);
 
         Drawable iconDrawable = mock(Drawable.class);
@@ -322,7 +326,7 @@
                         /* supportsTinting= */ true,
                         mockIphCommandBuilder,
                         /* isEnabled= */ isEnabled,
-                        AdaptiveToolbarButtonVariant.PRICE_TRACKING,
+                        AdaptiveToolbarButtonVariant.TEST_BUTTON,
                         /* tooltipTextResId= */ Resources.ID_NULL,
                         /* showHoverHighlight= */ false);
 
@@ -335,9 +339,13 @@
 
     @Test
     public void testUpdateButton_actionChipResourceIdGetsRemovedByFeatureEngagement() {
+        AdaptiveToolbarFeatures.setIsDynamicActionForTesting(
+                AdaptiveToolbarButtonVariant.TEST_BUTTON, true);
         TestValues testValues = new TestValues();
         testValues.addFieldTrialParamOverride(
-                ChromeFeatureList.CONTEXTUAL_PAGE_ACTION_PRICE_TRACKING, "action_chip", "true");
+                AdaptiveToolbarFeatures.CONTEXTUAL_PAGE_ACTION_TEST_FEATURE_NAME,
+                "action_chip",
+                "true");
         FeatureList.setTestValues(testValues);
 
         doReturn(true).when(mMockTracker).isInitialized();
@@ -361,7 +369,7 @@
                         /* supportsTinting= */ true,
                         mockIphCommandBuilder,
                         /* isEnabled= */ isEnabled,
-                        AdaptiveToolbarButtonVariant.PRICE_TRACKING,
+                        AdaptiveToolbarButtonVariant.TEST_BUTTON,
                         /* tooltipTextResId= */ Resources.ID_NULL,
                         /* showHoverHighlight= */ false);
 
@@ -374,9 +382,13 @@
 
     @Test
     public void testUpdateButton_actionChipResourceIdGetsKeptByFeatureEngagement() {
+        AdaptiveToolbarFeatures.setIsDynamicActionForTesting(
+                AdaptiveToolbarButtonVariant.TEST_BUTTON, true);
         TestValues testValues = new TestValues();
         testValues.addFieldTrialParamOverride(
-                ChromeFeatureList.CONTEXTUAL_PAGE_ACTION_PRICE_TRACKING, "action_chip", "true");
+                AdaptiveToolbarFeatures.CONTEXTUAL_PAGE_ACTION_TEST_FEATURE_NAME,
+                "action_chip",
+                "true");
         FeatureList.setTestValues(testValues);
 
         doReturn(true).when(mMockTracker).isInitialized();
@@ -400,7 +412,7 @@
                         /* supportsTinting= */ true,
                         mockIphCommandBuilder,
                         /* isEnabled= */ isEnabled,
-                        AdaptiveToolbarButtonVariant.PRICE_TRACKING,
+                        AdaptiveToolbarButtonVariant.TEST_BUTTON,
                         /* tooltipTextResId= */ Resources.ID_NULL,
                         /* showHoverHighlight= */ false);
 
diff --git a/chrome/browser/ui/ash/download_status/holding_space_display_client.h b/chrome/browser/ui/ash/download_status/holding_space_display_client.h
index e5b9648..351452f3 100644
--- a/chrome/browser/ui/ash/download_status/holding_space_display_client.h
+++ b/chrome/browser/ui/ash/download_status/holding_space_display_client.h
@@ -30,7 +30,7 @@
 
  private:
   // The data used during download updates.
-  struct UpdateMetadata : public base::SupportsWeakPtr<UpdateMetadata> {
+  struct UpdateMetadata final {
     UpdateMetadata();
     UpdateMetadata(const UpdateMetadata&) = delete;
     UpdateMetadata& operator=(const UpdateMetadata&) = delete;
@@ -41,6 +41,13 @@
 
     // The nullable icons that override the default holding space icon.
     crosapi::mojom::DownloadStatusIconsPtr icons;
+
+    base::WeakPtr<UpdateMetadata> AsWeakPtr() {
+      return weak_ptr_factory_.GetWeakPtr();
+    }
+
+   private:
+    base::WeakPtrFactory<UpdateMetadata> weak_ptr_factory_{this};
   };
 
   // DisplayClient:
diff --git a/chrome/browser/ui/autofill/autofill_field_promo_controller.h b/chrome/browser/ui/autofill/autofill_field_promo_controller.h
index 5022825..f3056a8 100644
--- a/chrome/browser/ui/autofill/autofill_field_promo_controller.h
+++ b/chrome/browser/ui/autofill/autofill_field_promo_controller.h
@@ -15,6 +15,60 @@
 
 // This class is the controller for the `AutofillFieldPromoView`. Its main
 // function is to control the view's lifetime.
+//
+// This controller is used in order to display IPHs attached directly to the
+// DOM. Normally, this is not possible. IPHs can only be attached to views (for
+// example, an IPH can easily be attached to the autofill popup).
+//
+// One controller can display only one IPH. To display a new IPH, a new
+// controller instance is needed.
+//
+// The controller receives (in the constructor) details about which IPH should
+// be displayed. The `Show()` method additionally receives the bounds of the DOM
+// element. The IPH will be displayed under this DOM element.
+//
+// On show, the controller creates an `AutofillFieldPromoView`, an invisible
+// view placed at the bottom of where the DOM element is displayed.
+//
+// The role of the invisible view is to serve as an anchor to the IPH (as IPHs
+// can only be attached to views).
+//
+// An IPH is hidden automatically when its anchor view gets hidden/destroyed.
+// For example, an IPH attached to the autofill popup is hidden when the
+// autofill popup is hidden (on tab change, on scroll, etc.). In this case, the
+// AutofillFieldPromoController takes care of hiding the anchor view.
+//
+// Responsibilities when creating a new `AutofillFieldPromoController` instance:
+//
+// 1. An `AutofillFieldPromoController` already exists as a member variable of
+// `ChromeAutofillClient`. Creating another instance of the controller should
+// also happen in `ChromeAutofillClient` and follow the already existing
+// implementation.
+//
+// 2. The IPH displayed by this controller has to be hidden in various
+// scenarios, which are explained below. The hide calls to the new controller
+// should happen in the same spot as the hide calls of the existing controller.
+//
+// 3. Hiding becomes trickier when some hiding events cannot be captured by
+// the `AutofillPopupHideHelper`. Two such events are interesting for the IPH:
+//
+// 3.a. Scrolling. The IPH should be hidden in the same place where the autofill
+// popup is hidden (currently in `BrowserAutofillManager::OnHidePopupImpl()`).
+// This method is called from the renderer not only on scroll, but hiding the
+// IPH in extra scenarios is not a disadvantage.
+//
+// 3.b. Overlapping with password generation prompt. The IPH should be hidden in
+// the same place where the autofill popup is hidden (currently in
+// `password_manager_util.cc::UserTriggeredManualGenerationFromContextMenu()`).
+//
+// 4. The IPH has to be hidden right before the autofill popup is shown
+// (currently in `ChromeAutofillClient::ShowAutofillSuggestions()`), because the
+// autofill popup cannot be shown if it overlaps with another view. Hiding the
+// IPH is an asynchronous task, that's why immediately after the IPH is hidden,
+// the task of showing the autofill popup is posted to the task thread.
+//
+// 5. (Optional) For more information, see go/mullet-m3-iph "Closing the bubble
+// section".
 class AutofillFieldPromoController {
  public:
   virtual ~AutofillFieldPromoController() = default;
diff --git a/chrome/browser/ui/autofill/autofill_field_promo_view.h b/chrome/browser/ui/autofill/autofill_field_promo_view.h
index d56dd54..0311dd8 100644
--- a/chrome/browser/ui/autofill/autofill_field_promo_view.h
+++ b/chrome/browser/ui/autofill/autofill_field_promo_view.h
@@ -25,6 +25,9 @@
 // This is an invisible view which is placed at the bottom of an DOM element.
 // It serves as an anchor for IPH's which need to be attached directly to an
 // DOM element.
+//
+// This class should not be instantiated by itself. Please use an
+// `AutofillFieldPromoController`.
 class AutofillFieldPromoView {
  public:
   // Creates and displays the view, and also maybe displays the IPH (the IPH is
diff --git a/chrome/browser/ui/autofill/chrome_autofill_client.cc b/chrome/browser/ui/autofill/chrome_autofill_client.cc
index 9e86935b..82ab2b4 100644
--- a/chrome/browser/ui/autofill/chrome_autofill_client.cc
+++ b/chrome/browser/ui/autofill/chrome_autofill_client.cc
@@ -878,6 +878,7 @@
                                               std::move(callback));
 }
 
+// TODO(b/309163844): Add follow-up ManualFallback for showing IBANs.
 bool ChromeAutofillClient::ShowTouchToFillCreditCard(
     base::WeakPtr<TouchToFillDelegate> delegate,
     base::span<const autofill::CreditCard> cards_to_suggest) {
@@ -894,8 +895,20 @@
       delegate, std::move(cards_to_suggest));
 #else
   // Touch To Fill is not supported on Desktop.
-  NOTREACHED();
-  return false;
+  NOTREACHED_NORETURN();
+#endif
+}
+
+bool ChromeAutofillClient::ShowTouchToFillIban(
+    base::WeakPtr<TouchToFillDelegate> delegate,
+    base::span<const autofill::Iban> ibans_to_suggest) {
+#if BUILDFLAG(IS_ANDROID)
+  return touch_to_fill_payment_method_controller_.Show(
+      std::make_unique<TouchToFillPaymentMethodViewImpl>(web_contents()),
+      delegate, std::move(ibans_to_suggest));
+#else
+  // Touch To Fill is not supported on Desktop.
+  NOTREACHED_NORETURN();
 #endif
 }
 
diff --git a/chrome/browser/ui/autofill/chrome_autofill_client.h b/chrome/browser/ui/autofill/chrome_autofill_client.h
index 7614a9229..d23e95b6 100644
--- a/chrome/browser/ui/autofill/chrome_autofill_client.h
+++ b/chrome/browser/ui/autofill/chrome_autofill_client.h
@@ -207,6 +207,9 @@
   bool ShowTouchToFillCreditCard(
       base::WeakPtr<TouchToFillDelegate> delegate,
       base::span<const autofill::CreditCard> cards_to_suggest) override;
+  bool ShowTouchToFillIban(
+      base::WeakPtr<TouchToFillDelegate> delegate,
+      base::span<const autofill::Iban> ibans_to_suggest) override;
   void HideTouchToFillCreditCard() override;
   void ShowAutofillSuggestions(
       const PopupOpenArgs& open_args,
diff --git a/chrome/browser/ui/browser_element_identifiers.cc b/chrome/browser/ui/browser_element_identifiers.cc
index d87dd839..fdf1f24 100644
--- a/chrome/browser/ui/browser_element_identifiers.cc
+++ b/chrome/browser/ui/browser_element_identifiers.cc
@@ -95,6 +95,7 @@
 DEFINE_ELEMENT_IDENTIFIER_VALUE(kToolbarMediaButtonElementId);
 DEFINE_ELEMENT_IDENTIFIER_VALUE(kToolbarNewTabButtonElementId);
 DEFINE_ELEMENT_IDENTIFIER_VALUE(kToolbarOverflowButtonElementId);
+DEFINE_ELEMENT_IDENTIFIER_VALUE(kToolbarPerformanceInterventionButtonElementId);
 DEFINE_ELEMENT_IDENTIFIER_VALUE(kToolbarSidePanelButtonElementId);
 DEFINE_ELEMENT_IDENTIFIER_VALUE(kToolbarSidePanelContainerElementId);
 DEFINE_ELEMENT_IDENTIFIER_VALUE(kToolbarTabCounterButtonElementId);
diff --git a/chrome/browser/ui/browser_element_identifiers.h b/chrome/browser/ui/browser_element_identifiers.h
index 629d1972..9303f67 100644
--- a/chrome/browser/ui/browser_element_identifiers.h
+++ b/chrome/browser/ui/browser_element_identifiers.h
@@ -105,6 +105,8 @@
 DECLARE_ELEMENT_IDENTIFIER_VALUE(kToolbarMediaButtonElementId);
 DECLARE_ELEMENT_IDENTIFIER_VALUE(kToolbarNewTabButtonElementId);
 DECLARE_ELEMENT_IDENTIFIER_VALUE(kToolbarOverflowButtonElementId);
+DECLARE_ELEMENT_IDENTIFIER_VALUE(
+    kToolbarPerformanceInterventionButtonElementId);
 DECLARE_ELEMENT_IDENTIFIER_VALUE(kToolbarSidePanelButtonElementId);
 DECLARE_ELEMENT_IDENTIFIER_VALUE(kToolbarSidePanelContainerElementId);
 DECLARE_ELEMENT_IDENTIFIER_VALUE(kToolbarTabCounterButtonElementId);
diff --git a/chrome/browser/ui/lens/BUILD.gn b/chrome/browser/ui/lens/BUILD.gn
index 9bcd193..3a40a02 100644
--- a/chrome/browser/ui/lens/BUILD.gn
+++ b/chrome/browser/ui/lens/BUILD.gn
@@ -33,6 +33,7 @@
     "//components/prefs",
     "//components/signin/public/identity_manager",
     "//components/sync/service",
+    "//components/sync_preferences",
     "//components/unified_consent",
     "//components/variations",
     "//components/variations:variations_mojom",
diff --git a/chrome/browser/ui/lens/lens_overlay_permission_utils.cc b/chrome/browser/ui/lens/lens_overlay_permission_utils.cc
index 93c56b3a..c98f497 100644
--- a/chrome/browser/ui/lens/lens_overlay_permission_utils.cc
+++ b/chrome/browser/ui/lens/lens_overlay_permission_utils.cc
@@ -6,11 +6,24 @@
 
 #include "components/prefs/pref_service.h"
 #include "components/sync/service/sync_service.h"
+#include "components/sync_preferences/pref_service_syncable.h"
 #include "components/unified_consent/pref_names.h"
 #include "components/unified_consent/url_keyed_data_collection_consent_helper.h"
 
 namespace lens {
 
+namespace prefs {
+
+void RegisterProfilePrefs(user_prefs::PrefRegistrySyncable* registry) {
+  registry->RegisterBooleanPref(kLensSharingPageScreenshotEnabled, false);
+}
+
+}  // namespace prefs
+
+bool CanSharePageScreenshotWithLensOverlay(PrefService* pref_service) {
+  return pref_service->GetBoolean(prefs::kLensSharingPageScreenshotEnabled);
+}
+
 bool CanSharePageURLWithLensOverlay(PrefService* pref_service) {
   std::unique_ptr<unified_consent::UrlKeyedDataCollectionConsentHelper> helper =
       unified_consent::UrlKeyedDataCollectionConsentHelper::
diff --git a/chrome/browser/ui/lens/lens_overlay_permission_utils.h b/chrome/browser/ui/lens/lens_overlay_permission_utils.h
index 4c39dec..fe87a42 100644
--- a/chrome/browser/ui/lens/lens_overlay_permission_utils.h
+++ b/chrome/browser/ui/lens/lens_overlay_permission_utils.h
@@ -11,7 +11,26 @@
 class SyncService;
 }  // namespace syncer
 
+namespace user_prefs {
+class PrefRegistrySyncable;
+}  // namespace user_prefs
+
 namespace lens {
+namespace prefs {
+
+// A boolean indicating whether the whether the user has permitted sharing page
+// screenshot with the Lens Overlay server.
+inline constexpr char kLensSharingPageScreenshotEnabled[] =
+    "lens.sharing_page_screenshot.enabled";
+
+// Registers the prefs used by the Lens Overlay.
+void RegisterProfilePrefs(user_prefs::PrefRegistrySyncable* registry);
+
+}  // namespace prefs
+
+// Returns true if the user, i.e., the local, current profile, is permitted to
+// share the the page screenshot with the Lens Overlay server.
+bool CanSharePageScreenshotWithLensOverlay(PrefService* pref_service);
 
 // Returns true if the user, i.e., the local, current profile, is permitted to
 // share the the page URL with the the Lens Overlay server.
diff --git a/chrome/browser/ui/lens/lens_overlay_query_controller.cc b/chrome/browser/ui/lens/lens_overlay_query_controller.cc
index 86d61f4..7cc7845 100644
--- a/chrome/browser/ui/lens/lens_overlay_query_controller.cc
+++ b/chrome/browser/ui/lens/lens_overlay_query_controller.cc
@@ -26,6 +26,7 @@
 #include "components/variations/variations_ids_provider.h"
 #include "components/version_info/channel.h"
 #include "google_apis/common/api_error_codes.h"
+#include "google_apis/gaia/gaia_constants.h"
 #include "google_apis/gaia/gaia_urls.h"
 #include "google_apis/google_api_keys.h"
 #include "net/base/url_util.h"
@@ -48,7 +49,6 @@
 constexpr char kContentType[] = "application/x-protobuf";
 constexpr char kDeveloperKey[] = "X-Developer-Key";
 constexpr char kSessionIdQueryParameterKey[] = "gsessionid";
-constexpr char kLensOAuthScope[] = "https://www.googleapis.com/auth/lens";
 constexpr char kOAuthConsumerName[] = "LensOverlayQueryController";
 constexpr base::TimeDelta kServerRequestTimeout = base::Minutes(1);
 
@@ -533,7 +533,7 @@
                                  std::move(fetcher_created_callback),
                                  std::move(fetched_response_callback)));
     signin::ScopeSet oauth_scopes;
-    oauth_scopes.insert(kLensOAuthScope);
+    oauth_scopes.insert(GaiaConstants::kLensOAuth2Scope);
 
     // If an access token fetcher is already in flight, it is intentionally
     // replaced by this newer one.
diff --git a/chrome/browser/ui/performance_controls/performance_intervention_button_controller.cc b/chrome/browser/ui/performance_controls/performance_intervention_button_controller.cc
new file mode 100644
index 0000000..f757c78
--- /dev/null
+++ b/chrome/browser/ui/performance_controls/performance_intervention_button_controller.cc
@@ -0,0 +1,36 @@
+// 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/performance_controls/performance_intervention_button_controller.h"
+
+#include "chrome/browser/performance_manager/public/user_tuning/performance_detection_manager.h"
+
+PerformanceInterventionButtonController::
+    PerformanceInterventionButtonController(
+        PerformanceInterventionButtonControllerDelegate* delegate) {
+  CHECK(delegate);
+  delegate_ = delegate;
+  CHECK(PerformanceDetectionManager::HasInstance());
+  PerformanceDetectionManager* const detection_manager =
+      PerformanceDetectionManager::GetInstance();
+  const PerformanceDetectionManager::ResourceTypeSet resource_types = {
+      PerformanceDetectionManager::ResourceType::kCpu};
+  detection_manager->AddActionableTabsObserver(resource_types, this);
+}
+
+PerformanceInterventionButtonController::
+    ~PerformanceInterventionButtonController() {
+  if (PerformanceDetectionManager::HasInstance()) {
+    PerformanceDetectionManager* const detection_manager =
+        PerformanceDetectionManager::GetInstance();
+    detection_manager->RemoveActionableTabsObserver(this);
+  }
+}
+
+void PerformanceInterventionButtonController::OnActionableTabListChanged(
+    PerformanceDetectionManager::ResourceType type,
+    PerformanceDetectionManager::ActionableTabsResult result) {
+  // TODO(crbug.com/338072465): Implement show/hide toolbar button functionality
+  delegate_->Show();
+}
diff --git a/chrome/browser/ui/performance_controls/performance_intervention_button_controller.h b/chrome/browser/ui/performance_controls/performance_intervention_button_controller.h
new file mode 100644
index 0000000..d5372496
--- /dev/null
+++ b/chrome/browser/ui/performance_controls/performance_intervention_button_controller.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_UI_PERFORMANCE_CONTROLS_PERFORMANCE_INTERVENTION_BUTTON_CONTROLLER_H_
+#define CHROME_BROWSER_UI_PERFORMANCE_CONTROLS_PERFORMANCE_INTERVENTION_BUTTON_CONTROLLER_H_
+
+#include "chrome/browser/performance_manager/public/user_tuning/performance_detection_manager.h"
+#include "chrome/browser/ui/performance_controls/performance_intervention_button_controller_delegate.h"
+
+namespace {
+
+using performance_manager::user_tuning::PerformanceDetectionManager;
+
+}  // namespace
+
+class PerformanceInterventionButtonController
+    : public PerformanceDetectionManager::ActionableTabsObserver {
+ public:
+  explicit PerformanceInterventionButtonController(
+      PerformanceInterventionButtonControllerDelegate* delegate);
+  ~PerformanceInterventionButtonController() override;
+
+  PerformanceInterventionButtonController(
+      const PerformanceInterventionButtonController&) = delete;
+  PerformanceInterventionButtonController& operator=(
+      const PerformanceInterventionButtonController&) = delete;
+
+  // PerformanceDetectionManager::ActionableTabsObserver:
+  void OnActionableTabListChanged(
+      PerformanceDetectionManager::ResourceType type,
+      PerformanceDetectionManager::ActionableTabsResult result) override;
+
+ private:
+  raw_ptr<PerformanceInterventionButtonControllerDelegate> delegate_ = nullptr;
+};
+
+#endif  // CHROME_BROWSER_UI_PERFORMANCE_CONTROLS_PERFORMANCE_INTERVENTION_BUTTON_CONTROLLER_H_
diff --git a/chrome/browser/ui/performance_controls/performance_intervention_button_controller_delegate.h b/chrome/browser/ui/performance_controls/performance_intervention_button_controller_delegate.h
new file mode 100644
index 0000000..5ad57ad
--- /dev/null
+++ b/chrome/browser/ui/performance_controls/performance_intervention_button_controller_delegate.h
@@ -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.
+
+#ifndef CHROME_BROWSER_UI_PERFORMANCE_CONTROLS_PERFORMANCE_INTERVENTION_BUTTON_CONTROLLER_DELEGATE_H_
+#define CHROME_BROWSER_UI_PERFORMANCE_CONTROLS_PERFORMANCE_INTERVENTION_BUTTON_CONTROLLER_DELEGATE_H_
+
+// This delegate class is to control the performance intervention toolbar
+// button.
+class PerformanceInterventionButtonControllerDelegate {
+ public:
+  virtual ~PerformanceInterventionButtonControllerDelegate() = default;
+
+  // Show the performance intervention toolbar button.
+  virtual void Show() = 0;
+
+  // Hides the performance intervention toolbar button.
+  virtual void Hide() = 0;
+};
+
+#endif  // CHROME_BROWSER_UI_PERFORMANCE_CONTROLS_PERFORMANCE_INTERVENTION_BUTTON_CONTROLLER_DELEGATE_H_
diff --git a/chrome/browser/ui/prefs/pref_watcher.cc b/chrome/browser/ui/prefs/pref_watcher.cc
index 49cf9e5..3a3a92656 100644
--- a/chrome/browser/ui/prefs/pref_watcher.cc
+++ b/chrome/browser/ui/prefs/pref_watcher.cc
@@ -147,6 +147,8 @@
 }
 
 void PrefWatcher::Shutdown() {
+  tracking_protection_settings_ = nullptr;
+  tracking_protection_settings_observation_.Reset();
   profile_pref_change_registrar_.RemoveAll();
   local_state_pref_change_registrar_.RemoveAll();
 }
diff --git a/chrome/browser/ui/tabs/saved_tab_groups/saved_tab_group_utils.cc b/chrome/browser/ui/tabs/saved_tab_groups/saved_tab_group_utils.cc
index 9cde900..9da6e7b 100644
--- a/chrome/browser/ui/tabs/saved_tab_groups/saved_tab_group_utils.cc
+++ b/chrome/browser/ui/tabs/saved_tab_groups/saved_tab_group_utils.cc
@@ -32,6 +32,11 @@
 #include "ui/base/l10n/l10n_util.h"
 #include "url/gurl.h"
 
+namespace {
+static constexpr int kOldIconSize = 20;
+static constexpr int kUIUpdateIconSize = 16;
+}  // namespace
+
 namespace tab_groups {
 
 DEFINE_CLASS_ELEMENT_IDENTIFIER_VALUE(SavedTabGroupUtils, kDeleteGroupMenuItem);
@@ -151,8 +156,11 @@
                                         ->tab_count();
   }
 
+  bool is_ui_update = tab_groups::IsTabGroupsSaveUIUpdateEnabled();
   dialog_model.AddMenuItem(
-      ui::ImageModel::FromVectorIcon(kMoveGroupToNewWindowRefreshIcon),
+      ui::ImageModel::FromVectorIcon(
+          kMoveGroupToNewWindowRefreshIcon, ui::kColorMenuIcon,
+          is_ui_update ? kUIUpdateIconSize : kOldIconSize),
       move_or_open_group_text,
       base::BindRepeating(&SavedTabGroupUtils::OpenOrMoveSavedGroupToNewWindow,
                           browser, saved_group),
@@ -160,11 +168,13 @@
           .SetId(kMoveGroupToNewWindowMenuItem)
           .SetIsEnabled(should_enable_move_menu_item));
 
-  if (tab_groups::IsTabGroupsSaveUIUpdateEnabled() && show_pin_unpin_option) {
+  if (is_ui_update && show_pin_unpin_option) {
     dialog_model.AddMenuItem(
-        ui::ImageModel::FromVectorIcon(saved_group->is_pinned()
-                                           ? kKeepPinFilledChromeRefreshIcon
-                                           : kKeepPinChromeRefreshIcon),
+        ui::ImageModel::FromVectorIcon(
+            saved_group->is_pinned() ? kKeepPinFilledChromeRefreshIcon
+                                     : kKeepPinChromeRefreshIcon,
+            ui::kColorMenuIcon,
+            is_ui_update ? kUIUpdateIconSize : kOldIconSize),
         l10n_util::GetStringUTF16(saved_group->is_pinned()
                                       ? IDS_TAB_GROUP_HEADER_CXMENU_UNPIN_GROUP
                                       : IDS_TAB_GROUP_HEADER_CXMENU_PIN_GROUP),
@@ -175,14 +185,16 @@
 
   dialog_model
       .AddMenuItem(
-          ui::ImageModel::FromVectorIcon(kCloseGroupRefreshIcon),
+          ui::ImageModel::FromVectorIcon(
+              kCloseGroupRefreshIcon, ui::kColorMenuIcon,
+              is_ui_update ? kUIUpdateIconSize : kOldIconSize),
           l10n_util::GetStringUTF16(IDS_TAB_GROUP_HEADER_CXMENU_DELETE_GROUP),
           base::BindRepeating(&SavedTabGroupUtils::DeleteSavedTabGroup, browser,
                               saved_group),
           ui::DialogModelMenuItem::Params().SetId(kDeleteGroupMenuItem))
       .AddSeparator();
 
-  if (tab_groups::IsTabGroupsSaveUIUpdateEnabled()) {
+  if (is_ui_update) {
     dialog_model.AddTitleItem(l10n_util::GetStringUTF16(IDS_TABS_TITLE_CXMENU),
                               kTabsTitleItem);
   }
diff --git a/chrome/browser/ui/toolbar/app_menu_model.cc b/chrome/browser/ui/toolbar/app_menu_model.cc
index 68e13bf..ee8f71cd 100644
--- a/chrome/browser/ui/toolbar/app_menu_model.cc
+++ b/chrome/browser/ui/toolbar/app_menu_model.cc
@@ -1762,9 +1762,9 @@
 
 #if BUILDFLAG(GOOGLE_CHROME_BRANDING)
   if (lens::features::IsLensOverlayEnabled()) {
-    AddItemWithStringIdAndVectorIcon(this, IDC_CONTENT_CONTEXT_LENS_OVERLAY,
-                                     IDS_SHOW_LENS_OVERLAY,
-                                     vector_icons::kGoogleGLogoMonochromeIcon);
+    AddItemWithStringIdAndVectorIcon(
+        this, IDC_CONTENT_CONTEXT_LENS_OVERLAY, IDS_SHOW_LENS_OVERLAY,
+        vector_icons::kGoogleLensMonochromeLogoIcon);
     SetElementIdentifierAt(
         GetIndexOfCommandId(IDC_CONTENT_CONTEXT_LENS_OVERLAY).value(),
         kShowLensOverlay);
diff --git a/chrome/browser/ui/views/bookmarks/saved_tab_groups/saved_tab_group_everything_menu.cc b/chrome/browser/ui/views/bookmarks/saved_tab_groups/saved_tab_group_everything_menu.cc
index 458d270..2037a64f 100644
--- a/chrome/browser/ui/views/bookmarks/saved_tab_groups/saved_tab_group_everything_menu.cc
+++ b/chrome/browser/ui/views/bookmarks/saved_tab_groups/saved_tab_group_everything_menu.cc
@@ -20,6 +20,10 @@
 #include "ui/views/controls/menu/menu_model_adapter.h"
 #include "ui/views/widget/widget.h"
 
+namespace {
+static constexpr int kUIUpdateIconSize = 16;
+}
+
 namespace tab_groups {
 
 DEFINE_CLASS_ELEMENT_IDENTIFIER_VALUE(STGEverythingMenu, kCreateNewTabGroup);
@@ -78,7 +82,8 @@
   menu_model->AddItemWithIcon(
       IDC_CREATE_NEW_TAB_GROUP,
       l10n_util::GetStringUTF16(IDS_CREATE_NEW_TAB_GROUP),
-      ui::ImageModel::FromVectorIcon(kCreateNewTabGroupIcon));
+      ui::ImageModel::FromVectorIcon(kCreateNewTabGroupIcon, ui::kColorMenuIcon,
+                                     kUIUpdateIconSize));
   menu_model->SetElementIdentifierAt(
       menu_model->GetIndexOfCommandId(IDC_CREATE_NEW_TAB_GROUP).value(),
       kCreateNewTabGroup);
diff --git a/chrome/browser/ui/views/bookmarks/saved_tab_groups/saved_tab_group_overflow_button.cc b/chrome/browser/ui/views/bookmarks/saved_tab_groups/saved_tab_group_overflow_button.cc
index 2812765e..b034791 100644
--- a/chrome/browser/ui/views/bookmarks/saved_tab_groups/saved_tab_group_overflow_button.cc
+++ b/chrome/browser/ui/views/bookmarks/saved_tab_groups/saved_tab_group_overflow_button.cc
@@ -27,6 +27,11 @@
 #include "ui/views/controls/highlight_path_generator.h"
 #include "ui/views/view_class_properties.h"
 
+namespace {
+static constexpr int kDefaultIconSize = 16;
+static constexpr int kUIUpdateIconSize = 20;
+}  // namespace
+
 namespace tab_groups {
 
 SavedTabGroupOverflowButton::SavedTabGroupOverflowButton(
@@ -71,15 +76,18 @@
   views::MenuButton::OnThemeChanged();
 
   ui::ColorProvider* color_provider = GetColorProvider();
-  const gfx::VectorIcon& icon = IsTabGroupsSaveUIUpdateEnabled()
-                                    ? kSavedTabGroupBarEverythingIcon
-                                    : kBookmarkbarOverflowRefreshIcon;
-  SetImageModel(views::Button::STATE_NORMAL,
-                ui::ImageModel::FromVectorIcon(
-                    icon, color_provider->GetColor(kColorBookmarkButtonIcon)));
-  SetImageModel(views::Button::STATE_DISABLED,
-                ui::ImageModel::FromVectorIcon(
-                    icon, color_provider->GetColor(ui::kColorIconDisabled)));
+  bool is_ui_update = IsTabGroupsSaveUIUpdateEnabled();
+  const gfx::VectorIcon& icon = is_ui_update ? kSavedTabGroupBarEverythingIcon
+                                             : kBookmarkbarOverflowRefreshIcon;
+  const int icon_size = is_ui_update ? kUIUpdateIconSize : kDefaultIconSize;
+  SetImageModel(
+      views::Button::STATE_NORMAL,
+      ui::ImageModel::FromVectorIcon(
+          icon, color_provider->GetColor(kColorBookmarkButtonIcon), icon_size));
+  SetImageModel(
+      views::Button::STATE_DISABLED,
+      ui::ImageModel::FromVectorIcon(
+          icon, color_provider->GetColor(ui::kColorIconDisabled), icon_size));
   return;
 }
 
diff --git a/chrome/browser/ui/views/frame/browser_actions.cc b/chrome/browser/ui/views/frame/browser_actions.cc
index eda64fb9..a19ea2c 100644
--- a/chrome/browser/ui/views/frame/browser_actions.cc
+++ b/chrome/browser/ui/views/frame/browser_actions.cc
@@ -190,7 +190,7 @@
             &(browser_.get()));
     const gfx::VectorIcon& icon =
 #if BUILDFLAG(GOOGLE_CHROME_BRANDING)
-        vector_icons::kGoogleSearchCompanionMonochromeLogoChromeRefreshIcon;
+        vector_icons::kGoogleLensMonochromeLogoIcon;
 #else
         vector_icons::kSearchIcon;
 #endif
diff --git a/chrome/browser/ui/views/frame/browser_view.cc b/chrome/browser/ui/views/frame/browser_view.cc
index 099ddf7..ccc0e8b 100644
--- a/chrome/browser/ui/views/frame/browser_view.cc
+++ b/chrome/browser/ui/views/frame/browser_view.cc
@@ -341,6 +341,11 @@
 #include "chrome/browser/ui/views/frame/webui_tab_strip_container_view.h"
 #endif  // BUILDFLAG(ENABLE_WEBUI_TAB_STRIP)
 
+#if BUILDFLAG(ENTERPRISE_WATERMARK)
+#include "chrome/browser/enterprise/data_protection/data_protection_navigation_observer.h"
+#include "chrome/browser/enterprise/watermark/watermark_view.h"
+#endif
+
 using base::UserMetricsAction;
 using content::NativeWebKeyboardEvent;
 using content::WebContents;
diff --git a/chrome/browser/ui/views/frame/browser_view.h b/chrome/browser/ui/views/frame/browser_view.h
index cb1adb49..6ce7eda 100644
--- a/chrome/browser/ui/views/frame/browser_view.h
+++ b/chrome/browser/ui/views/frame/browser_view.h
@@ -63,11 +63,6 @@
 #include "ui/views/widget/widget_observer.h"
 #include "ui/views/window/client_view.h"
 
-#if BUILDFLAG(ENTERPRISE_WATERMARK)
-#include "chrome/browser/enterprise/data_protection/data_protection_navigation_observer.h"
-#include "chrome/browser/enterprise/watermark/watermark_view.h"
-#endif
-
 #if BUILDFLAG(IS_CHROMEOS_ASH)
 #include "ui/compositor/throughput_tracker.h"
 #endif
@@ -98,6 +93,10 @@
 class WebContentsCloseHandler;
 class WebUITabStripContainerView;
 
+namespace enterprise_data_protection {
+struct UrlSettings;
+}
+
 namespace ui {
 class NativeTheme;
 }  // namespace ui
@@ -116,6 +115,12 @@
 struct WebAppBannerData;
 }  // namespace webapps
 
+#if BUILDFLAG(ENTERPRISE_WATERMARK)
+namespace enterprise_watermark {
+class WatermarkView;
+}
+#endif
+
 ///////////////////////////////////////////////////////////////////////////////
 // BrowserView
 //
diff --git a/chrome/browser/ui/views/frame/browser_view_browsertest.cc b/chrome/browser/ui/views/frame/browser_view_browsertest.cc
index 6ea2c77..d1ac433 100644
--- a/chrome/browser/ui/views/frame/browser_view_browsertest.cc
+++ b/chrome/browser/ui/views/frame/browser_view_browsertest.cc
@@ -54,6 +54,7 @@
 #if BUILDFLAG(ENTERPRISE_WATERMARK)
 #include "base/callback_list.h"
 #include "base/functional/bind.h"
+#include "chrome/browser/enterprise/watermark/watermark_view.h"
 #include "chrome/browser/policy/dm_token_utils.h"
 #include "chrome/browser/safe_browsing/chrome_enterprise_url_lookup_service_factory.h"
 #include "chrome/browser/ui/browser_navigator.h"
diff --git a/chrome/browser/ui/views/performance_controls/performance_intervention_button.cc b/chrome/browser/ui/views/performance_controls/performance_intervention_button.cc
new file mode 100644
index 0000000..7fead62
--- /dev/null
+++ b/chrome/browser/ui/views/performance_controls/performance_intervention_button.cc
@@ -0,0 +1,60 @@
+// 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/views/performance_controls/performance_intervention_button.h"
+
+#include <memory>
+#include <string>
+
+#include "chrome/app/vector_icons/vector_icons.h"
+#include "chrome/browser/ui/browser_element_identifiers.h"
+#include "chrome/browser/ui/performance_controls/performance_intervention_button_controller.h"
+#include "chrome/browser/ui/views/frame/browser_view.h"
+#include "chrome/grit/generated_resources.h"
+#include "ui/accessibility/ax_enums.mojom-shared.h"
+#include "ui/base/l10n/l10n_util.h"
+#include "ui/base/metadata/metadata_impl_macros.h"
+#include "ui/base/models/image_model.h"
+#include "ui/views/accessibility/view_accessibility.h"
+#include "ui/views/controls/button/button_controller.h"
+#include "ui/views/view_class_properties.h"
+
+PerformanceInterventionButton::PerformanceInterventionButton()
+    : ToolbarButton(PressedCallback()) {
+  button_controller()->set_notify_action(
+      views::ButtonController::NotifyAction::kOnPress);
+  SetFlipCanvasOnPaintForRTLUI(false);
+  // TODO(crbug.com/338620692): Replace placeholder accessibility name when
+  // strings finalize.
+  SetAccessibleName(std::u16string(),
+                    ax::mojom::NameFrom::kAttributeExplicitlyEmpty);
+  SetProperty(views::kElementIdentifierKey,
+              kToolbarPerformanceInterventionButtonElementId);
+
+  controller_ = std::make_unique<PerformanceInterventionButtonController>(this);
+}
+
+PerformanceInterventionButton::~PerformanceInterventionButton() = default;
+
+void PerformanceInterventionButton::Show() {
+  SetVisible(true);
+  PreferredSizeChanged();
+}
+
+void PerformanceInterventionButton::Hide() {
+  SetVisible(false);
+  PreferredSizeChanged();
+}
+
+void PerformanceInterventionButton::OnThemeChanged() {
+  views::View::OnThemeChanged();
+  SetImageModel(
+      ButtonState::STATE_NORMAL,
+      ui::ImageModel::FromVectorIcon(
+          kMemorySaverChromeRefreshIcon,
+          GetColorProvider()->GetColor(kColorDownloadToolbarButtonActive)));
+}
+
+BEGIN_METADATA(PerformanceInterventionButton)
+END_METADATA
diff --git a/chrome/browser/ui/views/performance_controls/performance_intervention_button.h b/chrome/browser/ui/views/performance_controls/performance_intervention_button.h
new file mode 100644
index 0000000..b719c6a
--- /dev/null
+++ b/chrome/browser/ui/views/performance_controls/performance_intervention_button.h
@@ -0,0 +1,39 @@
+// 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_VIEWS_PERFORMANCE_CONTROLS_PERFORMANCE_INTERVENTION_BUTTON_H_
+#define CHROME_BROWSER_UI_VIEWS_PERFORMANCE_CONTROLS_PERFORMANCE_INTERVENTION_BUTTON_H_
+
+#include "base/memory/raw_ptr.h"
+#include "chrome/browser/ui/performance_controls/performance_intervention_button_controller_delegate.h"
+#include "chrome/browser/ui/views/toolbar/toolbar_button.h"
+#include "ui/base/metadata/metadata_header_macros.h"
+
+class PerformanceInterventionButtonController;
+
+class PerformanceInterventionButton
+    : public ToolbarButton,
+      public PerformanceInterventionButtonControllerDelegate {
+  METADATA_HEADER(PerformanceInterventionButton, ToolbarButton)
+
+ public:
+  PerformanceInterventionButton();
+  ~PerformanceInterventionButton() override;
+
+  PerformanceInterventionButton(const PerformanceInterventionButton&) = delete;
+  PerformanceInterventionButton& operator=(
+      const PerformanceInterventionButton&) = delete;
+
+  // PerformanceInterventionButtonControllerDelegate:
+  void Show() override;
+  void Hide() override;
+
+  // views::View:
+  void OnThemeChanged() override;
+
+ private:
+  std::unique_ptr<PerformanceInterventionButtonController> controller_;
+};
+
+#endif  // CHROME_BROWSER_UI_VIEWS_PERFORMANCE_CONTROLS_PERFORMANCE_INTERVENTION_BUTTON_H_
diff --git a/chrome/browser/ui/views/performance_controls/performance_intervention_interactive_ui_test.cc b/chrome/browser/ui/views/performance_controls/performance_intervention_interactive_ui_test.cc
new file mode 100644
index 0000000..6559a0be
--- /dev/null
+++ b/chrome/browser/ui/views/performance_controls/performance_intervention_interactive_ui_test.cc
@@ -0,0 +1,40 @@
+// Copyright 2024 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/test/scoped_feature_list.h"
+#include "chrome/browser/ui/browser_element_identifiers.h"
+#include "chrome/test/base/in_process_browser_test.h"
+#include "chrome/test/interaction/interactive_browser_test.h"
+#include "components/performance_manager/public/features.h"
+#include "content/public/test/browser_test.h"
+
+namespace {
+
+constexpr char kSkipPixelTestsReason[] = "Should only run in pixel_tests.";
+
+}  // namespace
+
+class PerformanceInterventionInteractiveTest : public InteractiveBrowserTest {
+ public:
+  void SetUp() override {
+    feature_list_.InitAndEnableFeature(
+        performance_manager::features::kPerformanceCPUIntervention);
+    InteractiveBrowserTest::SetUp();
+  }
+
+ private:
+  base::test::ScopedFeatureList feature_list_;
+};
+
+// Pixel test to verify that the performance intervention toolbar
+// button looks correct.
+IN_PROC_BROWSER_TEST_F(PerformanceInterventionInteractiveTest,
+                       InterventionToolbarButton) {
+  RunTestSequence(SetOnIncompatibleAction(OnIncompatibleAction::kSkipTest,
+                                          kSkipPixelTestsReason),
+                  WaitForShow(kToolbarPerformanceInterventionButtonElementId),
+                  Screenshot(kToolbarPerformanceInterventionButtonElementId,
+                             /*screenshot_name=*/"InterventionToolbarButton",
+                             /*baseline_cl=*/"5503223"));
+}
diff --git a/chrome/browser/ui/views/permissions/chip/permission_chip_view.cc b/chrome/browser/ui/views/permissions/chip/permission_chip_view.cc
index 60de092..996bb3e 100644
--- a/chrome/browser/ui/views/permissions/chip/permission_chip_view.cc
+++ b/chrome/browser/ui/views/permissions/chip/permission_chip_view.cc
@@ -47,9 +47,7 @@
   // Equalizing padding on the left, right and between icon and label.
   SetImageLabelSpacing(GetLayoutConstant(LOCATION_BAR_CHIP_PADDING));
   SetCustomPadding(GetPadding());
-  if (features::IsChromeRefresh2023()) {
-    label()->SetTextStyle(views::style::STYLE_BODY_4_EMPHASIS);
-  }
+  label()->SetTextStyle(views::style::STYLE_BODY_4_EMPHASIS);
   SetCornerRadius(GetCornerRadius());
   animation_ = std::make_unique<gfx::SlideAnimation>(this);
 
@@ -227,51 +225,44 @@
         kColorOmniboxChipOnSystemBlockedActivityIndicatorForeground);
   }
 
-  if (features::IsChromeRefresh2023()) {
-    // 1. Default to the system primary color.
-    SkColor text_and_icon_color = GetColorProvider()->GetColor(
-        kColorOmniboxChipForegroundNormalVisibility);
+  // 1. Default to the system primary color.
+  SkColor text_and_icon_color =
+      GetColorProvider()->GetColor(kColorOmniboxChipForegroundNormalVisibility);
 
-    // 2. Then update the color if the quiet chip is showing.
-    if (GetPermissionPromptStyle() == PermissionPromptStyle::kQuietChip) {
-      text_and_icon_color = GetColorProvider()->GetColor(
-          kColorOmniboxChipForegroundLowVisibility);
-    }
-
-    // 3. Then update the color based on the user decision.
-    // TODO(dljames): There is potentially a bug here if there exists a case
-    // where a quiet chip can be shown on a GRANTED_ONCE permission action.
-    // In that case the color should stay kColorOmniboxChipTextDefaultCR23.
-    switch (GetUserDecision()) {
-      case permissions::PermissionAction::GRANTED:
-      case permissions::PermissionAction::GRANTED_ONCE:
-        text_and_icon_color = GetColorProvider()->GetColor(
-            kColorOmniboxChipForegroundNormalVisibility);
-        break;
-      case permissions::PermissionAction::DENIED:
-      case permissions::PermissionAction::DISMISSED:
-      case permissions::PermissionAction::IGNORED:
-      case permissions::PermissionAction::REVOKED:
-        text_and_icon_color = GetColorProvider()->GetColor(
-            kColorOmniboxChipForegroundLowVisibility);
-        break;
-      case permissions::PermissionAction::NUM:
-        break;
-    }
-
-    // 4. Then update the color based on if the icon is blocked or not.
-    if (ShouldShowBlockedIcon()) {
-      text_and_icon_color = GetColorProvider()->GetColor(
-          kColorOmniboxChipForegroundLowVisibility);
-    }
-
-    return text_and_icon_color;
+  // 2. Then update the color if the quiet chip is showing.
+  if (GetPermissionPromptStyle() == PermissionPromptStyle::kQuietChip) {
+    text_and_icon_color =
+        GetColorProvider()->GetColor(kColorOmniboxChipForegroundLowVisibility);
   }
 
-  return GetColorProvider()->GetColor(
-      GetPermissionChipTheme() == PermissionChipTheme::kLowVisibility
-          ? kColorOmniboxChipForegroundLowVisibility
-          : kColorOmniboxChipForegroundNormalVisibility);
+  // 3. Then update the color based on the user decision.
+  // TODO(dljames): There is potentially a bug here if there exists a case
+  // where a quiet chip can be shown on a GRANTED_ONCE permission action.
+  // In that case the color should stay kColorOmniboxChipTextDefaultCR23.
+  switch (GetUserDecision()) {
+    case permissions::PermissionAction::GRANTED:
+    case permissions::PermissionAction::GRANTED_ONCE:
+      text_and_icon_color = GetColorProvider()->GetColor(
+          kColorOmniboxChipForegroundNormalVisibility);
+      break;
+    case permissions::PermissionAction::DENIED:
+    case permissions::PermissionAction::DISMISSED:
+    case permissions::PermissionAction::IGNORED:
+    case permissions::PermissionAction::REVOKED:
+      text_and_icon_color = GetColorProvider()->GetColor(
+          kColorOmniboxChipForegroundLowVisibility);
+      break;
+    case permissions::PermissionAction::NUM:
+      break;
+  }
+
+  // 4. Then update the color based on if the icon is blocked or not.
+  if (ShouldShowBlockedIcon()) {
+    text_and_icon_color =
+        GetColorProvider()->GetColor(kColorOmniboxChipForegroundLowVisibility);
+  }
+
+  return text_and_icon_color;
 }
 
 SkColor PermissionChipView::GetBackgroundColor() const {
@@ -302,10 +293,8 @@
   }
   SetEnabledTextColors(GetForegroundColor());
   SetImageModel(views::Button::STATE_NORMAL, GetIconImageModel());
-  if (features::IsChromeRefresh2023()) {
-    ConfigureInkDropForRefresh2023(this, kColorOmniboxChipInkDropHover,
-                                   kColorOmniboxChipInkDropRipple);
-  }
+  ConfigureInkDropForRefresh2023(this, kColorOmniboxChipInkDropHover,
+                                 kColorOmniboxChipInkDropRipple);
 }
 
 void PermissionChipView::ForceAnimateExpand() {
@@ -323,18 +312,11 @@
 }
 
 int PermissionChipView::GetIconSize() const {
-  if (features::IsChromeRefresh2023()) {
-    return GetLayoutConstant(LOCATION_BAR_CHIP_ICON_SIZE);
-  }
-
-  return GetLayoutConstant(LOCATION_BAR_ICON_SIZE);
+  return GetLayoutConstant(LOCATION_BAR_CHIP_ICON_SIZE);
 }
 
 int PermissionChipView::GetCornerRadius() const {
-  if (features::IsChromeRefresh2023()) {
-    return GetLayoutConstant(LOCATION_BAR_CHILD_CORNER_RADIUS);
-  }
-  return GetIconSize();
+  return GetLayoutConstant(LOCATION_BAR_CHILD_CORNER_RADIUS);
 }
 
 gfx::RoundedCornersF PermissionChipView::GetCornerRadii() const {
@@ -348,13 +330,7 @@
 }
 
 gfx::Insets PermissionChipView::GetPadding() const {
-  if (features::IsChromeRefresh2023()) {
-    return gfx::Insets(GetLayoutConstant(LOCATION_BAR_CHIP_PADDING));
-  } else {
-    return gfx::Insets::VH(
-        GetLayoutConstant(LOCATION_BAR_CHILD_INTERIOR_PADDING),
-        GetLayoutInsets(LOCATION_BAR_ICON_INTERIOR_PADDING).left());
-  }
+  return gfx::Insets(GetLayoutConstant(LOCATION_BAR_CHIP_PADDING));
 }
 
 void PermissionChipView::SetChipIcon(const gfx::VectorIcon& icon) {
diff --git a/chrome/browser/ui/views/permissions/embedded_permission_prompt_base_view.cc b/chrome/browser/ui/views/permissions/embedded_permission_prompt_base_view.cc
index 1cdf8fb8..1520735 100644
--- a/chrome/browser/ui/views/permissions/embedded_permission_prompt_base_view.cc
+++ b/chrome/browser/ui/views/permissions/embedded_permission_prompt_base_view.cc
@@ -70,7 +70,7 @@
 }
 
 int GetPermissionIconSize() {
-  return features::IsChromeRefresh2023() ? 20 : 18;
+  return 20;
 }
 
 }  // namespace
@@ -274,13 +274,11 @@
   label->SetMultiLine(true);
   AddElementIdentifierToLabel(*label, index);
 
-  if (features::IsChromeRefresh2023()) {
-    label->SetTextStyle(views::style::STYLE_BODY_3);
-    label->SetEnabledColorId(kColorPermissionPromptRequestText);
+  label->SetTextStyle(views::style::STYLE_BODY_3);
+  label->SetEnabledColorId(kColorPermissionPromptRequestText);
 
-    line_container->SetProperty(views::kMarginsKey,
-                                gfx::Insets().set_top(BODY_TOP_MARGIN));
-  }
+  line_container->SetProperty(views::kMarginsKey,
+                              gfx::Insets().set_top(BODY_TOP_MARGIN));
 }
 
 void EmbeddedPermissionPromptBaseView::AddButton(
diff --git a/chrome/browser/ui/views/permissions/midi_permissions_flow_interactive_uitest.cc b/chrome/browser/ui/views/permissions/midi_permissions_flow_interactive_uitest.cc
index 722a97a..30313fc4 100644
--- a/chrome/browser/ui/views/permissions/midi_permissions_flow_interactive_uitest.cc
+++ b/chrome/browser/ui/views/permissions/midi_permissions_flow_interactive_uitest.cc
@@ -170,13 +170,9 @@
           base::BindOnce([](ui::TrackedElement* element) {
             auto* element_view = AsView<ContentSettingImageView>(element);
             EXPECT_EQ(element_view->get_icon_for_testing(),
-                      base::FeatureList::IsEnabled(features::kChromeRefresh2023)
-                          ? &vector_icons::kMidiOffChromeRefreshIcon
-                          : &vector_icons::kMidiIcon);
+                      &vector_icons::kMidiOffChromeRefreshIcon);
             EXPECT_EQ(element_view->get_icon_badge_for_testing(),
-                      base::FeatureList::IsEnabled(features::kChromeRefresh2023)
-                          ? &gfx::kNoneIcon
-                          : &vector_icons::kBlockedBadgeIcon);
+                      &gfx::kNoneIcon);
             EXPECT_EQ(
                 element_view->get_tooltip_text_for_testing(),
                 l10n_util::GetStringUTF16(IDS_BLOCKED_MIDI_SYSEX_MESSAGE));
@@ -195,9 +191,7 @@
           base::BindOnce([](ui::TrackedElement* element) {
             auto* element_view = AsView<ContentSettingImageView>(element);
             EXPECT_EQ(element_view->get_icon_for_testing(),
-                      base::FeatureList::IsEnabled(features::kChromeRefresh2023)
-                          ? &vector_icons::kMidiChromeRefreshIcon
-                          : &vector_icons::kMidiIcon);
+                      &vector_icons::kMidiChromeRefreshIcon);
             EXPECT_EQ(element_view->get_icon_badge_for_testing(),
                       &gfx::kNoneIcon);
             EXPECT_EQ(
diff --git a/chrome/browser/ui/views/permissions/permission_indicators_interactive_uitest.cc b/chrome/browser/ui/views/permissions/permission_indicators_interactive_uitest.cc
index 47093cf..3ac963e 100644
--- a/chrome/browser/ui/views/permissions/permission_indicators_interactive_uitest.cc
+++ b/chrome/browser/ui/views/permissions/permission_indicators_interactive_uitest.cc
@@ -116,9 +116,7 @@
       CheckViewProperty(
           ContentSettingImageView::kMediaActivityIndicatorElementId,
           &ContentSettingImageView::get_icon_for_testing,
-          base::FeatureList::IsEnabled(features::kChromeRefresh2023)
-              ? &vector_icons::kVideocamChromeRefreshIcon
-              : &vector_icons::kVideocamIcon),
+          &vector_icons::kVideocamChromeRefreshIcon),
       // Permission is granted, there is no badge.
       CheckViewProperty(
           ContentSettingImageView::kMediaActivityIndicatorElementId,
@@ -144,9 +142,7 @@
       CheckViewProperty(
           ContentSettingImageView::kMediaActivityIndicatorElementId,
           &ContentSettingImageView::get_icon_for_testing,
-          base::FeatureList::IsEnabled(features::kChromeRefresh2023)
-              ? &vector_icons::kMicChromeRefreshIcon
-              : &vector_icons::kMicIcon),
+          &vector_icons::kMicChromeRefreshIcon),
       // Permission is granted, there is no badge.
       CheckViewProperty(
           ContentSettingImageView::kMediaActivityIndicatorElementId,
@@ -159,9 +155,7 @@
       CheckViewProperty(
           ContentSettingImageView::kMediaActivityIndicatorElementId,
           &ContentSettingImageView::get_icon_for_testing,
-          base::FeatureList::IsEnabled(features::kChromeRefresh2023)
-              ? &vector_icons::kVideocamChromeRefreshIcon
-              : &vector_icons::kVideocamIcon),
+          &vector_icons::kVideocamChromeRefreshIcon),
       // Permission is granted, there is no badge.
       CheckViewProperty(
           ContentSettingImageView::kMediaActivityIndicatorElementId,
diff --git a/chrome/browser/ui/views/permissions/permission_prompt_bubble_base_view.cc b/chrome/browser/ui/views/permissions/permission_prompt_bubble_base_view.cc
index 43403c6d..58ca0d0 100644
--- a/chrome/browser/ui/views/permissions/permission_prompt_bubble_base_view.cc
+++ b/chrome/browser/ui/views/permissions/permission_prompt_bubble_base_view.cc
@@ -121,11 +121,9 @@
                               kBlockButtonElementId);
     block_button->SetID(GetViewId(PermissionDialogButton::kDeny));
 
-    if (features::IsChromeRefresh2023()) {
-      allow_once_button->SetStyle(ui::ButtonStyle::kTonal);
-      allow_always_button->SetStyle(ui::ButtonStyle::kTonal);
-      block_button->SetStyle(ui::ButtonStyle::kTonal);
-    }
+    allow_once_button->SetStyle(ui::ButtonStyle::kTonal);
+    allow_always_button->SetStyle(ui::ButtonStyle::kTonal);
+    block_button->SetStyle(ui::ButtonStyle::kTonal);
 
     if (permissions::feature_params::kShowAllowAlwaysAsFirstButton.Get()) {
       buttons_container->AddChildView(std::move(allow_always_button));
@@ -157,10 +155,8 @@
         &PermissionPromptBubbleBaseView::RunButtonCallback,
         base::Unretained(this), GetViewId(PermissionDialogButton::kDeny)));
 
-    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);
   }
 }
 
@@ -173,10 +169,8 @@
                               .SetID(permissions::PermissionPromptViewID::
                                          VIEW_ID_PERMISSION_PROMPT_EXTRA_TEXT)
                               .Build();
-  if (features::IsChromeRefresh2023()) {
-    extra_text_label->SetTextStyle(views::style::STYLE_BODY_3);
-    extra_text_label->SetEnabledColorId(kColorPermissionPromptRequestText);
-  }
+  extra_text_label->SetTextStyle(views::style::STYLE_BODY_3);
+  extra_text_label->SetEnabledColorId(kColorPermissionPromptRequestText);
   AddChildView(std::move(extra_text_label));
 }
 
diff --git a/chrome/browser/ui/views/permissions/permission_prompt_bubble_one_origin_view.cc b/chrome/browser/ui/views/permissions/permission_prompt_bubble_one_origin_view.cc
index fd33fb75..9dccf8e 100644
--- a/chrome/browser/ui/views/permissions/permission_prompt_bubble_one_origin_view.cc
+++ b/chrome/browser/ui/views/permissions/permission_prompt_bubble_one_origin_view.cc
@@ -220,7 +220,7 @@
       provider->GetDistanceMetric(
           DISTANCE_PERMISSION_PROMPT_HORIZONTAL_ICON_LABEL_PADDING)));
 
-  const int kPermissionIconSize = features::IsChromeRefresh2023() ? 20 : 18;
+  constexpr int kPermissionIconSize = 20;
   auto* icon = line_container->AddChildView(
       std::make_unique<views::ImageView>(ui::ImageModel::FromVectorIcon(
           permissions::GetIconId(request->request_type()), ui::kColorIcon,
@@ -241,15 +241,13 @@
   }
 #endif
 
-  if (features::IsChromeRefresh2023()) {
-    label->SetTextStyle(views::style::STYLE_BODY_3);
-    label->SetEnabledColorId(kColorPermissionPromptRequestText);
+  label->SetTextStyle(views::style::STYLE_BODY_3);
+  label->SetEnabledColorId(kColorPermissionPromptRequestText);
 
-    if (index == 0u) {
-      constexpr int kPermissionBodyTopMargin = 10;
-      line_container->SetProperty(
-          views::kMarginsKey, gfx::Insets().set_top(kPermissionBodyTopMargin));
-    }
+  if (index == 0u) {
+    constexpr int kPermissionBodyTopMargin = 10;
+    line_container->SetProperty(
+        views::kMarginsKey, gfx::Insets().set_top(kPermissionBodyTopMargin));
   }
 }
 
diff --git a/chrome/browser/ui/views/permissions/permission_request_chip_browsertest.cc b/chrome/browser/ui/views/permissions/permission_request_chip_browsertest.cc
index 02ffc36b..04cad990 100644
--- a/chrome/browser/ui/views/permissions/permission_request_chip_browsertest.cc
+++ b/chrome/browser/ui/views/permissions/permission_request_chip_browsertest.cc
@@ -93,13 +93,6 @@
   // location icon isn't offset by the chip and the bubble is hidden.
   EXPECT_FALSE(lbv->GetChipController()->IsPermissionPromptChipVisible());
   EXPECT_FALSE(lbv->GetChipController()->IsBubbleShowing());
-  if (!features::IsChromeRefresh2023() &&
-      !OmniboxFieldTrial::IsCr23LayoutEnabled()) {
-    // CR2023 has a few experimental flavors of LocationIconView positioning.
-    // It does not make sense to test them here. See LocationBarView::Layout().
-    EXPECT_EQ(lbv->location_icon_view()->bounds().x(),
-              GetLayoutConstant(LOCATION_BAR_ELEMENT_PADDING));
-  }
 
   // Ensure no callbacks are pending.
   EXPECT_FALSE(
diff --git a/chrome/browser/ui/views/safe_browsing/tailored_security_unconsented_modal.cc b/chrome/browser/ui/views/safe_browsing/tailored_security_unconsented_modal.cc
index 268ccc4..3c9bbba 100644
--- a/chrome/browser/ui/views/safe_browsing/tailored_security_unconsented_modal.cc
+++ b/chrome/browser/ui/views/safe_browsing/tailored_security_unconsented_modal.cc
@@ -35,7 +35,6 @@
 
 namespace {
 
-constexpr int kAvatarSize = 40;
 constexpr int kAvatarOffset = 45;
 constexpr int kImageOffset = 5;
 
@@ -100,19 +99,15 @@
   SetModalType(ui::MODAL_TYPE_CHILD);
 
   SetTitle(IDS_TAILORED_SECURITY_UNCONSENTED_MODAL_TITLE);
-  if (base::FeatureList::IsEnabled(
-          safe_browsing::kTailoredSecurityUpdatedMessages)) {
-    auto* bodyLabel =
-        AddChildView(std::make_unique<views::Label>(l10n_util::GetStringUTF16(
-            IDS_TAILORED_SECURITY_UNCONSENTED_MODAL_BODY)));
-    bodyLabel->SetMultiLine(true);
-    bodyLabel->SetHorizontalAlignment(gfx::ALIGN_LEFT);
-    SetLayoutManager(std::make_unique<views::BoxLayout>(
-        views::BoxLayout::Orientation::kVertical,
-        ChromeLayoutProvider::Get()->GetInsetsMetric(views::INSETS_DIALOG)));
-    set_fixed_width(ChromeLayoutProvider::Get()->GetDistanceMetric(
-        views::DISTANCE_MODAL_DIALOG_PREFERRED_WIDTH));
-  }
+  auto* bodyLabel = AddChildView(std::make_unique<views::Label>(
+      l10n_util::GetStringUTF16(IDS_TAILORED_SECURITY_UNCONSENTED_MODAL_BODY)));
+  bodyLabel->SetMultiLine(true);
+  bodyLabel->SetHorizontalAlignment(gfx::ALIGN_LEFT);
+  SetLayoutManager(std::make_unique<views::BoxLayout>(
+      views::BoxLayout::Orientation::kVertical,
+      ChromeLayoutProvider::Get()->GetInsetsMetric(views::INSETS_DIALOG)));
+  set_fixed_width(ChromeLayoutProvider::Get()->GetDistanceMetric(
+      views::DISTANCE_MODAL_DIALOG_PREFERRED_WIDTH));
   SetButtonLabel(ui::DIALOG_BUTTON_OK,
                  l10n_util::GetStringUTF16(
                      IDS_TAILORED_SECURITY_UNCONSENTED_ACCEPT_BUTTON));
@@ -154,46 +149,12 @@
     return;
 
   ui::ResourceBundle& bundle = ui::ResourceBundle::GetSharedInstance();
-  if (base::FeatureList::IsEnabled(
-          safe_browsing::kTailoredSecurityUpdatedMessages)) {
-    gfx::ImageSkia header_image =
-        *bundle.GetImageSkiaNamed(IDR_TAILORED_SECURITY_UNCONSENTED_UPDATED);
-    auto image_view = std::make_unique<views::ImageView>(
-        ui::ImageModel::FromImageSkia(header_image));
-    image_view->SetVerticalAlignment(views::ImageView::Alignment::kLeading);
-    GetBubbleFrameView()->SetHeaderView(std::move(image_view));
-  } else {
-    gfx::ImageSkia avatar_image = identity_manager
-                                      ->FindExtendedAccountInfoByAccountId(
-                                          identity_manager->GetPrimaryAccountId(
-                                              signin::ConsentLevel::kSignin))
-                                      .account_image.AsImageSkia();
-
-    gfx::ImageSkia sized_avatar_image =
-        gfx::ImageSkiaOperations::CreateResizedImage(
-            avatar_image, skia::ImageOperations::RESIZE_BEST,
-            gfx::Size(kAvatarSize, kAvatarSize));
-    // The color used in `circle_mask` is irrelevant as long as it's opaque;
-    // only the alpha channel matters.
-    gfx::ImageSkia circle_mask =
-        gfx::ImageSkiaOperations::CreateImageWithCircleBackground(
-            kAvatarSize / 2, SK_ColorWHITE, gfx::ImageSkia());
-    gfx::ImageSkia cropped_avatar_image =
-        gfx::ImageSkiaOperations::CreateMaskedImage(sized_avatar_image,
-                                                    circle_mask);
-    gfx::ImageSkia header_image =
-        *bundle.GetImageSkiaNamed(IDR_TAILORED_SECURITY_UNCONSENTED);
-    gfx::ImageSkia header_and_avatar(
-        std::make_unique<SuperimposedOffsetImageSource>(header_image,
-                                                        cropped_avatar_image),
-        gfx::Size(header_image.size().width(),
-                  header_image.size().height() + kImageOffset));
-
-    auto image_view = std::make_unique<views::ImageView>(
-        ui::ImageModel::FromImageSkia(header_and_avatar));
-    image_view->SetVerticalAlignment(views::ImageView::Alignment::kLeading);
-    GetBubbleFrameView()->SetHeaderView(std::move(image_view));
-  }
+  gfx::ImageSkia header_image =
+      *bundle.GetImageSkiaNamed(IDR_TAILORED_SECURITY_UNCONSENTED);
+  auto image_view = std::make_unique<views::ImageView>(
+      ui::ImageModel::FromImageSkia(header_image));
+  image_view->SetVerticalAlignment(views::ImageView::Alignment::kLeading);
+  GetBubbleFrameView()->SetHeaderView(std::move(image_view));
 }
 
 BEGIN_METADATA(TailoredSecurityUnconsentedModal)
diff --git a/chrome/browser/ui/views/side_panel/read_anything/read_anything_coordinator.cc b/chrome/browser/ui/views/side_panel/read_anything/read_anything_coordinator.cc
index 492f133a..37b654b 100644
--- a/chrome/browser/ui/views/side_panel/read_anything/read_anything_coordinator.cc
+++ b/chrome/browser/ui/views/side_panel/read_anything/read_anything_coordinator.cc
@@ -12,6 +12,7 @@
 #include "base/metrics/field_trial_params.h"
 #include "base/time/time.h"
 #include "base/timer/timer.h"
+#include "build/chromeos_buildflags.h"
 #include "chrome/app/vector_icons/vector_icons.h"
 #include "chrome/browser/accessibility/embedded_a11y_extension_loader.h"
 #include "chrome/browser/language/language_model_manager_factory.h"
@@ -30,6 +31,7 @@
 #include "chrome/browser/ui/views/side_panel/side_panel_web_ui_view.h"
 #include "chrome/browser/ui/webui/side_panel/read_anything/read_anything_prefs.h"
 #include "chrome/browser/ui/webui/side_panel/read_anything/read_anything_untrusted_ui.h"
+#include "chrome/common/extensions/extension_constants.h"
 #include "chrome/grit/generated_resources.h"
 #include "components/feature_engagement/public/feature_constants.h"
 #include "components/language/core/browser/language_model.h"
@@ -38,6 +40,10 @@
 #include "ui/accessibility/accessibility_features.h"
 #include "ui/base/l10n/l10n_util.h"
 
+#if BUILDFLAG(IS_CHROMEOS_LACROS)
+#include "chrome/browser/lacros/embedded_a11y_manager_lacros.h"
+#endif  // BUILDFLAG(IS_CHROMEOS_LACROS)
+
 namespace {
 
 // Get the list of distillable URLs defined by the experiment parameter.
@@ -97,8 +103,7 @@
   }
 
   if (features::IsReadAnythingDocsIntegrationEnabled()) {
-    extension_loader_ = EmbeddedA11yExtensionLoader::GetInstance();
-    extension_loader_->Init();
+    EmbeddedA11yExtensionLoader::GetInstance()->Init();
   }
 }
 
@@ -155,10 +160,7 @@
 
 ReadAnythingCoordinator::~ReadAnythingCoordinator() {
   if (features::IsReadAnythingDocsIntegrationEnabled()) {
-    if (extension_loader_) {
-      extension_loader_->RemoveA11yHelperExtensionForReadingMode();
-      extension_loader_ = nullptr;
-    }
+    RemoveGDocsHelperExtension();
   }
 
   // Inform observers when |this| is destroyed so they can do their own cleanup.
@@ -268,7 +270,7 @@
   // for local side panels.
   if (features::IsReadAnythingDocsIntegrationEnabled() &&
       !features::IsReadAnythingLocalSidePanelEnabled()) {
-    extension_loader_->InstallA11yHelperExtensionForReadingMode();
+    InstallGDocsHelperExtension();
   }
 }
 
@@ -281,7 +283,7 @@
   // extension for local side panels.
   if (features::IsReadAnythingDocsIntegrationEnabled() &&
       !features::IsReadAnythingLocalSidePanelEnabled()) {
-    extension_loader_->RemoveA11yHelperExtensionForReadingMode();
+    RemoveGDocsHelperExtension();
   }
 }
 
@@ -419,6 +421,27 @@
   }
 }
 
+void ReadAnythingCoordinator::InstallGDocsHelperExtension() {
+#if BUILDFLAG(IS_CHROMEOS_LACROS)
+  EmbeddedA11yManagerLacros::GetInstance()->SetReadingModeEnabled(true);
+#else
+  EmbeddedA11yExtensionLoader::GetInstance()->InstallExtensionWithId(
+      extension_misc::kReadingModeGDocsHelperExtensionId,
+      extension_misc::kReadingModeGDocsHelperExtensionPath,
+      extension_misc::kReadingModeGDocsHelperManifestFilename,
+      /*should_localize=*/false);
+#endif  // BUILDFLAG(IS_CHROMEOS_LACROS)
+}
+
+void ReadAnythingCoordinator::RemoveGDocsHelperExtension() {
+#if BUILDFLAG(IS_CHROMEOS_LACROS)
+  EmbeddedA11yManagerLacros::GetInstance()->SetReadingModeEnabled(false);
+#else
+  EmbeddedA11yExtensionLoader::GetInstance()->RemoveExtensionWithId(
+      extension_misc::kReadingModeGDocsHelperExtensionId);
+#endif  // BUILDFLAG(IS_CHROMEOS_LACROS)
+}
+
 void ReadAnythingCoordinator::ActivePageNotDistillableForTesting() {
   ActivePageNotDistillable();
 }
diff --git a/chrome/browser/ui/views/side_panel/read_anything/read_anything_coordinator.h b/chrome/browser/ui/views/side_panel/read_anything/read_anything_coordinator.h
index 534bc14..06c2307 100644
--- a/chrome/browser/ui/views/side_panel/read_anything/read_anything_coordinator.h
+++ b/chrome/browser/ui/views/side_panel/read_anything/read_anything_coordinator.h
@@ -18,7 +18,6 @@
 #include "chrome/browser/ui/views/side_panel/side_panel_entry_observer.h"
 #include "content/public/browser/web_contents_observer.h"
 
-class EmbeddedA11yExtensionLoader;
 class Browser;
 class ReadAnythingController;
 class SidePanelRegistry;
@@ -112,6 +111,9 @@
   void ActivePageNotDistillable();
   bool IsActivePageDistillable() const;
 
+  void InstallGDocsHelperExtension();
+  void RemoveGDocsHelperExtension();
+
   std::string default_language_code_;
   std::unique_ptr<ReadAnythingModel> model_;
   std::unique_ptr<ReadAnythingController> controller_;
@@ -120,8 +122,6 @@
 
   base::ObserverList<Observer> observers_;
 
-  raw_ptr<EmbeddedA11yExtensionLoader> extension_loader_;
-
   bool post_tab_change_delay_complete_ = true;
   base::RetainingOneShotTimer delay_timer_;
 
diff --git a/chrome/browser/ui/views/side_panel/read_anything/read_anything_coordinator_unittest.cc b/chrome/browser/ui/views/side_panel/read_anything/read_anything_coordinator_unittest.cc
index 17c925d..9be0146 100644
--- a/chrome/browser/ui/views/side_panel/read_anything/read_anything_coordinator_unittest.cc
+++ b/chrome/browser/ui/views/side_panel/read_anything/read_anything_coordinator_unittest.cc
@@ -20,9 +20,14 @@
 #include "chrome/browser/ui/views/side_panel/side_panel_coordinator.h"
 #include "chrome/browser/ui/views/side_panel/side_panel_entry.h"
 #include "chrome/browser/ui/views/side_panel/side_panel_registry.h"
+#include "chrome/common/extensions/extension_constants.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "ui/accessibility/accessibility_features.h"
 
+#if BUILDFLAG(IS_CHROMEOS_LACROS)
+#include "chrome/browser/lacros/embedded_a11y_manager_lacros.h"
+#endif  // BUILDFLAG(IS_CHROMEOS_LACROS)
+
 using testing::_;
 
 class MockReadAnythingCoordinatorObserver
@@ -33,13 +38,6 @@
   MOCK_METHOD(void, OnCoordinatorDestroyed, (), (override));
 };
 
-class MockEmbeddedA11yExtensionLoader : public EmbeddedA11yExtensionLoader {
- public:
-  MockEmbeddedA11yExtensionLoader() : EmbeddedA11yExtensionLoader() {}
-  MOCK_METHOD(void, InstallA11yHelperExtensionForReadingMode, (), (override));
-  MOCK_METHOD(void, RemoveA11yHelperExtensionForReadingMode, (), (override));
-};
-
 class ReadAnythingCoordinatorTest : public TestWithBrowserView {
  public:
   void SetUp() override {
@@ -53,10 +51,6 @@
         SidePanelCoordinator::GetGlobalSidePanelRegistry(browser());
     read_anything_coordinator_ =
         ReadAnythingCoordinator::GetOrCreateForBrowser(browser());
-    mock_extension_loader_ =
-        std::make_unique<MockEmbeddedA11yExtensionLoader>();
-    read_anything_coordinator_->extension_loader_ =
-        mock_extension_loader_.get();
 
     AddTab(browser_view()->browser(), GURL("http://foo1.com"));
     browser_view()->browser()->tab_strip_model()->ActivateTabAt(0);
@@ -111,7 +105,6 @@
 
   MockReadAnythingCoordinatorObserver coordinator_observer_;
   base::test::ScopedFeatureList scoped_feature_list_;
-  std::unique_ptr<MockEmbeddedA11yExtensionLoader> mock_extension_loader_;
 };
 
 // TODO(crbug.com/40853217): Fix the memory leak on destruction observed on
@@ -158,6 +151,7 @@
   entry->OnEntryHidden();
 }
 
+#if !BUILDFLAG(IS_CHROMEOS_LACROS)
 TEST_F(
     ReadAnythingCoordinatorTest,
     // TODO(crbug.com/324143642): Re-enable this test when the docs integration
@@ -165,16 +159,39 @@
     DISABLED_SidePanelShowAndHide_NonLacros_CallEmbeddedA11yExtensionLoader) {
   SidePanelEntry* entry = side_panel_registry_->GetEntryForKey(
       SidePanelEntry::Key(SidePanelEntry::Id::kReadAnything));
+  EXPECT_FALSE(EmbeddedA11yExtensionLoader::GetInstance()->IsExtensionInstalled(
+      extension_misc::kReadingModeGDocsHelperExtensionId));
 
-  EXPECT_CALL(*mock_extension_loader_, InstallA11yHelperExtensionForReadingMode)
-      .Times(1);
   entry->OnEntryShown();
+  EXPECT_TRUE(EmbeddedA11yExtensionLoader::GetInstance()->IsExtensionInstalled(
+      extension_misc::kReadingModeGDocsHelperExtensionId));
 
   // Called once when calling OnEntryHidden and once on destruction.
-  EXPECT_CALL(*mock_extension_loader_, RemoveA11yHelperExtensionForReadingMode)
-      .Times(2);
   entry->OnEntryHidden();
+  EXPECT_FALSE(EmbeddedA11yExtensionLoader::GetInstance()->IsExtensionInstalled(
+      extension_misc::kReadingModeGDocsHelperExtensionId));
 }
+#endif  // !BUILDFLAG(IS_CHROMEOS_LACROS)
+
+#if BUILDFLAG(IS_CHROMEOS_LACROS)
+TEST_F(
+    ReadAnythingCoordinatorTest,
+    // TODO(crbug.com/324143642): Re-enable this test when the docs integration
+    // flag is enabled.
+    DISABLED_SidePanelShowAndHide_Lacros_EmbeddedA11yManagerLacrosUpdateReadingModeState) {
+  SidePanelEntry* entry = side_panel_registry_->GetEntryForKey(
+      SidePanelEntry::Key(SidePanelEntry::Id::kReadAnything));
+  EXPECT_FALSE(
+      EmbeddedA11yManagerLacros::GetInstance()->IsReadingModeEnabled());
+
+  entry->OnEntryShown();
+  EXPECT_TRUE(EmbeddedA11yManagerLacros::GetInstance()->IsReadingModeEnabled());
+
+  entry->OnEntryHidden();
+  EXPECT_FALSE(
+      EmbeddedA11yManagerLacros::GetInstance()->IsReadingModeEnabled());
+}
+#endif  // BUILDFLAG(IS_CHROMEOS_LACROS)
 
 TEST_F(ReadAnythingCoordinatorTest,
        OnBrowserSetLastActive_SidePanelIsNotVisible) {
diff --git a/chrome/browser/ui/views/tabs/tab_group_editor_bubble_view.cc b/chrome/browser/ui/views/tabs/tab_group_editor_bubble_view.cc
index 7d6ab522..75173155 100644
--- a/chrome/browser/ui/views/tabs/tab_group_editor_bubble_view.cc
+++ b/chrome/browser/ui/views/tabs/tab_group_editor_bubble_view.cc
@@ -96,6 +96,7 @@
 constexpr int kDialogWidth = 240;
 constexpr const char kLearnMoreURL[] =
     "https://support.google.com/chrome/answer/165139";
+static constexpr int kDefaultIconSize = 20;
 
 std::unique_ptr<views::LabelButton> CreateMenuItem(
     int button_id,
@@ -190,8 +191,8 @@
         old_image_model->IsVectorIcon()) {
       ui::VectorIconModel vector_icon_model = old_image_model->GetVectorIcon();
       const gfx::VectorIcon* icon = vector_icon_model.vector_icon();
-      const ui::ImageModel new_image_model =
-          ui::ImageModel::FromVectorIcon(*icon, icon_color);
+      const ui::ImageModel new_image_model = ui::ImageModel::FromVectorIcon(
+          *icon, icon_color, old_image_model->Size().width());
       menu_item->SetImageModel(button_state, new_image_model);
     }
   }
@@ -258,9 +259,8 @@
       base::BindRepeating(
           &TabGroupEditorBubbleView::MoveGroupToNewWindowPressed,
           base::Unretained(this)),
-      ui::ImageModel::FromVectorIcon(features::IsChromeRefresh2023()
-                                         ? kMoveGroupToNewWindowRefreshIcon
-                                         : kMoveGroupToNewWindowIcon));
+      ui::ImageModel::FromVectorIcon(kMoveGroupToNewWindowRefreshIcon,
+                                     ui::kColorMenuIcon, kDefaultIconSize));
 
   // Create view hierarchy.
   title_field_ =
@@ -293,9 +293,8 @@
     // utilizes a different view (view::Label) that does not have an option to
     // take in an image like the other line items do.
     save_group_icon_ = save_group_line_container->AddChildView(
-        std::make_unique<views::ImageView>(ui::ImageModel::FromVectorIcon(
-            features::IsChromeRefresh2023() ? kSaveGroupRefreshIcon
-                                            : kSaveGroupIcon)));
+        std::make_unique<views::ImageView>(
+            ui::ImageModel::FromVectorIcon(kSaveGroupRefreshIcon)));
 
     save_group_label_ =
         save_group_line_container->AddChildView(std::make_unique<views::Label>(
@@ -328,9 +327,8 @@
       l10n_util::GetStringUTF16(IDS_TAB_GROUP_HEADER_CXMENU_NEW_TAB_IN_GROUP),
       base::BindRepeating(&TabGroupEditorBubbleView::NewTabInGroupPressed,
                           base::Unretained(this)),
-      ui::ImageModel::FromVectorIcon(features::IsChromeRefresh2023()
-                                         ? kNewTabInGroupRefreshIcon
-                                         : kNewTabInGroupIcon)));
+      ui::ImageModel::FromVectorIcon(kNewTabInGroupRefreshIcon,
+                                     ui::kColorMenuIcon, kDefaultIconSize)));
   menu_items_.push_back(new_tab_menu_item);
 
   views::LabelButton* move_menu_item_ptr;
@@ -343,17 +341,14 @@
       l10n_util::GetStringUTF16(IDS_TAB_GROUP_HEADER_CXMENU_UNGROUP),
       base::BindRepeating(&TabGroupEditorBubbleView::UngroupPressed,
                           base::Unretained(this)),
-      ui::ImageModel::FromVectorIcon(features::IsChromeRefresh2023()
-                                         ? kUngroupRefreshIcon
-                                         : kUngroupIcon))));
+      ui::ImageModel::FromVectorIcon(kUngroupRefreshIcon))));
 
   views::LabelButton* close_group_menu_item = AddChildView(CreateMenuItem(
       TAB_GROUP_HEADER_CXMENU_CLOSE_GROUP, GetTextForCloseButton(),
       base::BindRepeating(&TabGroupEditorBubbleView::CloseGroupPressed,
                           base::Unretained(this)),
-      ui::ImageModel::FromVectorIcon(features::IsChromeRefresh2023()
-                                         ? kCloseGroupRefreshIcon
-                                         : kCloseGroupIcon)));
+      ui::ImageModel::FromVectorIcon(kCloseGroupRefreshIcon, ui::kColorMenuIcon,
+                                     kDefaultIconSize)));
   close_group_menu_item->SetProperty(views::kElementIdentifierKey,
                                      kTabGroupEditorBubbleCloseGroupButtonId);
   menu_items_.push_back(close_group_menu_item);
@@ -372,9 +367,7 @@
         l10n_util::GetStringUTF16(IDS_TAB_GROUP_HEADER_CXMENU_DELETE_GROUP),
         base::BindRepeating(&TabGroupEditorBubbleView::DeleteGroupPressed,
                             base::Unretained(this)),
-        ui::ImageModel::FromVectorIcon(features::IsChromeRefresh2023()
-                                           ? kTrashCanRefreshIcon
-                                           : kTrashCanIcon))));
+        ui::ImageModel::FromVectorIcon(kTrashCanRefreshIcon))));
     footer_ = AddChildView(std::make_unique<Footer>(browser_));
   }
 
diff --git a/chrome/browser/ui/views/toolbar/toolbar_view.cc b/chrome/browser/ui/views/toolbar/toolbar_view.cc
index 14fad46..55d3439 100644
--- a/chrome/browser/ui/views/toolbar/toolbar_view.cc
+++ b/chrome/browser/ui/views/toolbar/toolbar_view.cc
@@ -68,6 +68,7 @@
 #include "chrome/browser/ui/views/page_action/page_action_icon_container.h"
 #include "chrome/browser/ui/views/page_action/page_action_icon_controller.h"
 #include "chrome/browser/ui/views/performance_controls/battery_saver_button.h"
+#include "chrome/browser/ui/views/performance_controls/performance_intervention_button.h"
 #include "chrome/browser/ui/views/send_tab_to_self/send_tab_to_self_toolbar_icon_view.h"
 #include "chrome/browser/ui/views/side_panel/side_panel_toolbar_container.h"
 #include "chrome/browser/ui/views/tabs/tab_strip.h"
@@ -440,6 +441,12 @@
         std::make_unique<BatterySaverButton>(browser_view_));
   }
 
+  if (base::FeatureList::IsEnabled(
+          performance_manager::features::kPerformanceCPUIntervention)) {
+    performance_intervention_button_ = container_view_->AddChildView(
+        std::make_unique<PerformanceInterventionButton>());
+  }
+
   if (cast)
     cast_ = container_view_->AddChildView(std::move(cast));
 
diff --git a/chrome/browser/ui/views/toolbar/toolbar_view.h b/chrome/browser/ui/views/toolbar/toolbar_view.h
index e93c4dc..2437b17 100644
--- a/chrome/browser/ui/views/toolbar/toolbar_view.h
+++ b/chrome/browser/ui/views/toolbar/toolbar_view.h
@@ -59,6 +59,7 @@
 class ToolbarButton;
 class AvatarToolbarButtonBrowserTest;
 class ToolbarController;
+class PerformanceInterventionButton;
 
 namespace media_router {
 class CastToolbarButton;
@@ -295,6 +296,8 @@
   raw_ptr<views::View> toolbar_divider_ = nullptr;
   raw_ptr<ChromeLabsButton> chrome_labs_button_ = nullptr;
   raw_ptr<BatterySaverButton> battery_saver_button_ = nullptr;
+  raw_ptr<PerformanceInterventionButton> performance_intervention_button_ =
+      nullptr;
   raw_ptr<media_router::CastToolbarButton> cast_ = nullptr;
   raw_ptr<SidePanelToolbarContainer> side_panel_container_ = nullptr;
   raw_ptr<PinnedToolbarActionsContainer> pinned_toolbar_actions_container_ =
diff --git a/chrome/browser/ui/webauthn/authenticator_request_window.cc b/chrome/browser/ui/webauthn/authenticator_request_window.cc
index 7ea33ed..264ebbd 100644
--- a/chrome/browser/ui/webauthn/authenticator_request_window.cc
+++ b/chrome/browser/ui/webauthn/authenticator_request_window.cc
@@ -16,6 +16,7 @@
 #include "chrome/browser/webauthn/webauthn_switches.h"
 #include "content/public/browser/navigation_handle.h"
 #include "content/public/browser/web_contents_observer.h"
+#include "device/fido/enclave/metrics.h"
 #include "google_apis/gaia/gaia_urls.h"
 #include "net/base/url_util.h"
 #include "net/http/http_response_headers.h"
@@ -144,6 +145,7 @@
         // produce the "kdi" parameter's value.
         url = GaiaUrls::GetInstance()->gaia_url().Resolve(
             "/encryption/unlock/desktop?kdi=CAESDgoMaHdfcHJvdGVjdGVk");
+        device::enclave::RecordEvent(device::enclave::Event::kRecoveryShown);
         break;
 
       case AuthenticatorRequestDialogModel::Step::kGPMReauthAccount:
diff --git a/chrome/browser/ui/webauthn/sheet_models.cc b/chrome/browser/ui/webauthn/sheet_models.cc
index a4c3962..371c8f1 100644
--- a/chrome/browser/ui/webauthn/sheet_models.cc
+++ b/chrome/browser/ui/webauthn/sheet_models.cc
@@ -23,6 +23,7 @@
 #include "chrome/grit/generated_resources.h"
 #include "components/strings/grit/components_strings.h"
 #include "device/fido/discoverable_credential_metadata.h"
+#include "device/fido/enclave/metrics.h"
 #include "device/fido/features.h"
 #include "device/fido/fido_constants.h"
 #include "device/fido/fido_types.h"
@@ -2050,6 +2051,7 @@
                                   OtherMechanismButtonVisibility::kVisible) {
   lottie_illustrations_.emplace(IDR_WEBAUTHN_GPM_PASSKEY_LIGHT,
                                 IDR_WEBAUTHN_GPM_PASSKEY_DARK);
+  device::enclave::RecordEvent(device::enclave::Event::kOnboarding);
 }
 
 AuthenticatorGpmOnboardingSheetModel::~AuthenticatorGpmOnboardingSheetModel() =
@@ -2098,9 +2100,15 @@
 }
 
 void AuthenticatorGpmOnboardingSheetModel::OnAccept() {
+  device::enclave::RecordEvent(device::enclave::Event::kOnboardingAccepted);
   dialog_model()->OnGPMOnboardingAccepted();
 }
 
+void AuthenticatorGpmOnboardingSheetModel::OnBack() {
+  device::enclave::RecordEvent(device::enclave::Event::kOnboardingRejected);
+  AuthenticatorSheetModelBase::OnBack();
+}
+
 // AuthenticatorTrustThisComputerCreationSheetModel ---------------------
 
 AuthenticatorTrustThisComputerCreationSheetModel::
diff --git a/chrome/browser/ui/webauthn/sheet_models.h b/chrome/browser/ui/webauthn/sheet_models.h
index 6914793e..45bc8c22 100644
--- a/chrome/browser/ui/webauthn/sheet_models.h
+++ b/chrome/browser/ui/webauthn/sheet_models.h
@@ -874,6 +874,7 @@
   bool IsAcceptButtonVisible() const override;
   std::u16string GetAcceptButtonLabel() const override;
   void OnAccept() override;
+  void OnBack() override;
 };
 
 // The sheet shown for bootstrapping Google Password Manager passkeys during
diff --git a/chrome/browser/ui/webui/ash/cellular_setup/cellular_setup_localized_strings_provider.cc b/chrome/browser/ui/webui/ash/cellular_setup/cellular_setup_localized_strings_provider.cc
index 5b578155..95f68a23 100644
--- a/chrome/browser/ui/webui/ash/cellular_setup/cellular_setup_localized_strings_provider.cc
+++ b/chrome/browser/ui/webui/ash/cellular_setup/cellular_setup_localized_strings_provider.cc
@@ -122,8 +122,7 @@
 const std::vector<NamedBoolean>& GetBooleanValues() {
   static const base::NoDestructor<std::vector<NamedBoolean>> named_bools(
       {{"useSecondEuicc",
-        base::FeatureList::IsEnabled(features::kCellularUseSecondEuicc)},
-       {"isSmdsSupportEnabled", ash::features::IsSmdsSupportEnabled()}});
+        base::FeatureList::IsEnabled(features::kCellularUseSecondEuicc)}});
   return *named_bools;
 }
 
diff --git a/chrome/browser/ui/webui/ash/login/enrollment_screen_handler.cc b/chrome/browser/ui/webui/ash/login/enrollment_screen_handler.cc
index 98673fb..161778b5 100644
--- a/chrome/browser/ui/webui/ash/login/enrollment_screen_handler.cc
+++ b/chrome/browser/ui/webui/ash/login/enrollment_screen_handler.cc
@@ -12,6 +12,7 @@
 #include "base/functional/bind.h"
 #include "base/json/json_reader.h"
 #include "base/logging.h"
+#include "base/notreached.h"
 #include "base/system/sys_info.h"
 #include "base/uuid.h"
 #include "base/values.h"
@@ -63,9 +64,7 @@
 std::string EnrollmentModeToUIMode(policy::EnrollmentConfig::Mode mode) {
   switch (mode) {
     case policy::EnrollmentConfig::MODE_NONE:
-    case policy::EnrollmentConfig::DEPRECATED_MODE_ENROLLED_ROLLBACK:
-    case policy::EnrollmentConfig::DEPRECATED_MODE_OFFLINE_DEMO:
-      break;
+      NOTREACHED_NORETURN() << "Bad enrollment mode " << mode;
     case policy::EnrollmentConfig::MODE_MANUAL:
     case policy::EnrollmentConfig::MODE_MANUAL_REENROLLMENT:
     case policy::EnrollmentConfig::MODE_LOCAL_ADVERTISED:
@@ -89,9 +88,6 @@
     case policy::EnrollmentConfig::MODE_RECOVERY:
       return kEnrollmentModeUIRecovery;
   }
-
-  NOTREACHED() << "Bad enrollment mode " << mode;
-  return kEnrollmentModeUIManual;
 }
 
 std::string GetFlowString(EnrollmentScreenView::FlowType type) {
diff --git a/chrome/browser/ui/webui/ash/login/oobe_screens_handler_factory.cc b/chrome/browser/ui/webui/ash/login/oobe_screens_handler_factory.cc
index 4fb7965..b7e920d7 100644
--- a/chrome/browser/ui/webui/ash/login/oobe_screens_handler_factory.cc
+++ b/chrome/browser/ui/webui/ash/login/oobe_screens_handler_factory.cc
@@ -47,6 +47,10 @@
   }
 }
 
+void OobeScreensHandlerFactory::UnbindScreensHandlerFactory() {
+  page_factory_receiver_.reset();
+}
+
 void OobeScreensHandlerFactory::CreateDrivePinningScreenHandler(
     mojo::PendingRemote<screens_common::mojom::DrivePinningPage> page,
     mojo::PendingReceiver<screens_common::mojom::DrivePinningPageHandler>
diff --git a/chrome/browser/ui/webui/ash/login/oobe_screens_handler_factory.h b/chrome/browser/ui/webui/ash/login/oobe_screens_handler_factory.h
index 6895aa2..271fd5f 100644
--- a/chrome/browser/ui/webui/ash/login/oobe_screens_handler_factory.h
+++ b/chrome/browser/ui/webui/ash/login/oobe_screens_handler_factory.h
@@ -31,6 +31,7 @@
   ~OobeScreensHandlerFactory() override;
 
   void BindScreensHandlerFactory();
+  void UnbindScreensHandlerFactory();
 
  private:
   // screens_factory::mojom::ScreensFactory:
diff --git a/chrome/browser/ui/webui/ash/settings/pages/internet/internet_section.cc b/chrome/browser/ui/webui/ash/settings/pages/internet/internet_section.cc
index 696798a2..9ca0b02 100644
--- a/chrome/browser/ui/webui/ash/settings/pages/internet/internet_section.cc
+++ b/chrome/browser/ui/webui/ash/settings/pages/internet/internet_section.cc
@@ -1149,8 +1149,6 @@
   html_source->AddBoolean(
       "showHiddenToggle",
       base::FeatureList::IsEnabled(::features::kShowHiddenNetworkToggle));
-  html_source->AddBoolean("isSmdsSupportEnabled",
-                          ash::features::IsSmdsSupportEnabled());
   html_source->AddBoolean("isHotspotEnabled",
                           ash::features::IsHotspotEnabled());
   html_source->AddBoolean("isInstantHotspotRebrandEnabled",
diff --git a/chrome/browser/ui/webui/ash/settings/pages/privacy/privacy_section.cc b/chrome/browser/ui/webui/ash/settings/pages/privacy/privacy_section.cc
index 2646608..d3099b2 100644
--- a/chrome/browser/ui/webui/ash/settings/pages/privacy/privacy_section.cc
+++ b/chrome/browser/ui/webui/ash/settings/pages/privacy/privacy_section.cc
@@ -514,8 +514,12 @@
        IDS_OS_SETTINGS_PRIVACY_HUB_SPEAK_ON_MUTE_DETECTION_TOGGLE_SUBTEXT},
       {"geolocationAreaTitle",
        IDS_OS_SETTINGS_PRIVACY_HUB_GEOLOCATION_AREA_TITLE},
-      {"geolocationAreaDescription",
-       IDS_OS_SETTINGS_PRIVACY_HUB_GEOLOCATION_AREA_DESCRIPTION},
+      {"geolocationAreaAllowedSubtext",
+       IDS_OS_SETTINGS_PRIVACY_HUB_GEOLOCATION_AREA_ALLOWED_SUBTEXT},
+      {"geolocationAreaOnlyAllowedForSystemSubtext",
+       IDS_OS_SETTINGS_PRIVACY_HUB_GEOLOCATION_AREA_ONLY_ALLOWED_FOR_SYSTEM_SUBTEXT},
+      {"geolocationAreaDisallowedSubtext",
+       IDS_OS_SETTINGS_PRIVACY_HUB_GEOLOCATION_AREA_DISALLOWED_SUBTEXT},
       {"geolocationControlledByPrimaryUserText",
        IDS_OS_SETTINGS_PRIVACY_HUB_GEOLOCATION_PRIMARY_USER_CONTROLLED},
       {"geolocationAccessLevelAllowed",
diff --git a/chrome/browser/ui/webui/new_tab_page/new_tab_page_ui.cc b/chrome/browser/ui/webui/new_tab_page/new_tab_page_ui.cc
index 391cddce..2d6bc08 100644
--- a/chrome/browser/ui/webui/new_tab_page/new_tab_page_ui.cc
+++ b/chrome/browser/ui/webui/new_tab_page/new_tab_page_ui.cc
@@ -467,6 +467,8 @@
       {"modulesDummyTitle", IDS_NTP_MODULES_DUMMY_TITLE},
       {"modulesFeedTitle", IDS_NTP_MODULES_FEED_TITLE},
       {"modulesTodayCalendarHeader", IDS_NTP_MODULES_TODAY_CALENDAR_HEADER},
+      {"modulesTodayCalendarDisableButtonText",
+       IDS_NTP_MODULES_TODAY_CALENDAR_DISABLE_BUTTON_TEXT},
       {"modulesGoogleCalendarTitle", IDS_NTP_MODULES_GOOGLE_CALENDAR_TITLE},
       {"modulesGoogleCalendarDisableButtonText",
        IDS_NTP_MODULES_GOOGLE_CALENDAR_DISABLE_BUTTON_TEXT},
diff --git a/chrome/browser/ui/webui/side_panel/read_anything/read_anything_untrusted_ui.cc b/chrome/browser/ui/webui/side_panel/read_anything/read_anything_untrusted_ui.cc
index 58a8115..7205543 100644
--- a/chrome/browser/ui/webui/side_panel/read_anything/read_anything_untrusted_ui.cc
+++ b/chrome/browser/ui/webui/side_panel/read_anything/read_anything_untrusted_ui.cc
@@ -128,6 +128,7 @@
        IDS_READING_MODE_VOICE_DOWNLOADED_TITLE},
       {"readingModeVoiceDownloadedMessage",
        IDS_READING_MODE_VOICE_DOWNLOADED_MESSAGE},
+      {"menu", IDS_MENU},
   };
   for (const auto& str : kLocalizedStrings) {
     webui::AddLocalizedString(source, str.name, str.id);
diff --git a/chrome/browser/ui/webui/top_chrome/webui_url_utils.cc b/chrome/browser/ui/webui/top_chrome/webui_url_utils.cc
index fd2c6048..c622da8 100644
--- a/chrome/browser/ui/webui/top_chrome/webui_url_utils.cc
+++ b/chrome/browser/ui/webui/top_chrome/webui_url_utils.cc
@@ -4,6 +4,7 @@
 
 #include "chrome/browser/ui/webui/top_chrome/webui_url_utils.h"
 
+#include "build/build_config.h"
 #include "chrome/common/webui_url_constants.h"
 #include "content/public/common/url_constants.h"
 #include "url/gurl.h"
@@ -12,3 +13,16 @@
   return url.SchemeIs(content::kChromeUIScheme) &&
          url.DomainIs(chrome::kChromeUITopChromeDomain);
 }
+
+bool IsTopChromeUntrustedWebUIURL(const GURL& url) {
+  // TODO(b/339037968): Remove this exception once Lens uses ".top-chrome"
+  // suffix.
+#if !BUILDFLAG(IS_ANDROID)
+  if (url == GURL(chrome::kChromeUILensUntrustedURL)) {
+    return true;
+  }
+#endif
+
+  return url.SchemeIs(content::kChromeUIUntrustedScheme) &&
+         url.DomainIs(chrome::kChromeUITopChromeDomain);
+}
diff --git a/chrome/browser/ui/webui/top_chrome/webui_url_utils.h b/chrome/browser/ui/webui/top_chrome/webui_url_utils.h
index 5930c42e..054655d 100644
--- a/chrome/browser/ui/webui/top_chrome/webui_url_utils.h
+++ b/chrome/browser/ui/webui/top_chrome/webui_url_utils.h
@@ -7,8 +7,13 @@
 
 class GURL;
 
-// Returns true if `url` is the URL of a Top Chrome WebUI on
-// desktop browsers. Such a URL ends with the ".top-chrome" TLD.
+// Returns true if `url` is the URL of a trusted Top Chrome WebUI on desktop
+// browsers. These URLs always have the format "chrome://*.top-chrome".
 bool IsTopChromeWebUIURL(const GURL& url);
 
+// Returns true if `url` is the URL of an untrusted Top Chrome WebUI on desktop
+// browsers. These URLs always have the format
+// "chrome-untrusted://*.top-chrome".
+bool IsTopChromeUntrustedWebUIURL(const GURL& url);
+
 #endif  // CHROME_BROWSER_UI_WEBUI_TOP_CHROME_WEBUI_URL_UTILS_H_
diff --git a/chrome/browser/visited_url_ranking/DEPS b/chrome/browser/visited_url_ranking/DEPS
new file mode 100644
index 0000000..802323c
--- /dev/null
+++ b/chrome/browser/visited_url_ranking/DEPS
@@ -0,0 +1,3 @@
+include_rules = [
+  "+components/visited_url_ranking"
+]
diff --git a/chrome/browser/visited_url_ranking/OWNERS b/chrome/browser/visited_url_ranking/OWNERS
new file mode 100644
index 0000000..f01ad00
--- /dev/null
+++ b/chrome/browser/visited_url_ranking/OWNERS
@@ -0,0 +1 @@
+file://components/visited_url_ranking/OWNERS
diff --git a/chrome/browser/visited_url_ranking/visited_url_ranking_service_factory.cc b/chrome/browser/visited_url_ranking/visited_url_ranking_service_factory.cc
new file mode 100644
index 0000000..3fa09882
--- /dev/null
+++ b/chrome/browser/visited_url_ranking/visited_url_ranking_service_factory.cc
@@ -0,0 +1,87 @@
+// 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/visited_url_ranking/visited_url_ranking_service_factory.h"
+
+#include <map>
+#include <memory>
+
+#include "chrome/browser/bookmarks/bookmark_model_factory.h"
+#include "chrome/browser/commerce/shopping_service_factory.h"
+#include "chrome/browser/history/history_service_factory.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/profiles/profile_keyed_service_factory.h"
+#include "chrome/browser/sync/session_sync_service_factory.h"
+#include "chrome/common/channel_info.h"
+#include "components/bookmarks/browser/bookmark_model.h"
+#include "components/bookmarks/browser/core_bookmark_model.h"
+#include "components/keyed_service/core/keyed_service.h"
+#include "components/visited_url_ranking/internal/session_url_visit_data_fetcher.h"
+#include "components/visited_url_ranking/internal/visited_url_ranking_service_impl.h"
+#include "components/visited_url_ranking/public/url_visit_aggregates_transformer.h"
+#include "components/visited_url_ranking/public/url_visit_data_fetcher.h"
+#include "components/visited_url_ranking/public/visited_url_ranking_service.h"
+
+namespace visited_url_ranking {
+
+// static
+VisitedURLRankingService* VisitedURLRankingServiceFactory::GetForProfile(
+    Profile* profile) {
+  return static_cast<VisitedURLRankingService*>(
+      GetInstance()->GetServiceForBrowserContext(profile, true));
+}
+
+// static
+VisitedURLRankingServiceFactory*
+VisitedURLRankingServiceFactory::GetInstance() {
+  static base::NoDestructor<VisitedURLRankingServiceFactory> instance;
+  return instance.get();
+}
+
+VisitedURLRankingServiceFactory::VisitedURLRankingServiceFactory()
+    : ProfileKeyedServiceFactory(
+          "VisitedURLRankingService",
+          ProfileSelections::Builder()
+              .WithRegular(ProfileSelection::kOriginalOnly)
+              .WithGuest(ProfileSelection::kNone)
+              .WithAshInternals(ProfileSelection::kNone)
+              .Build()) {
+  DependsOn(SessionSyncServiceFactory::GetInstance());
+}
+
+VisitedURLRankingServiceFactory::~VisitedURLRankingServiceFactory() = default;
+
+std::unique_ptr<KeyedService>
+VisitedURLRankingServiceFactory::BuildServiceInstanceForBrowserContext(
+    content::BrowserContext* context) const {
+  auto* profile = Profile::FromBrowserContext(context);
+
+  std::map<Fetcher, std::unique_ptr<URLVisitDataFetcher>> data_fetchers;
+  sync_sessions::SessionSyncService* sss =
+      SessionSyncServiceFactory::GetInstance()->GetForProfile(profile);
+  if (sss) {
+    data_fetchers.emplace(Fetcher::kSession,
+                          std::make_unique<SessionURLVisitDataFetcher>(sss));
+  }
+
+  std::map<URLVisitAggregatesTransformType,
+           std::unique_ptr<URLVisitAggregatesTransformer>>
+      transformers = {};
+  // TODO(crbug.com/329242209): Add various aggregate transformers (e.g,
+  // bookmarks, shopping) to the service's map of supported transformers.
+
+  return std::make_unique<VisitedURLRankingServiceImpl>(
+      std::move(data_fetchers), std::move(transformers));
+}
+
+bool VisitedURLRankingServiceFactory::ServiceIsCreatedWithBrowserContext()
+    const {
+  return true;
+}
+
+bool VisitedURLRankingServiceFactory::ServiceIsNULLWhileTesting() const {
+  return false;
+}
+
+}  // namespace visited_url_ranking
diff --git a/chrome/browser/visited_url_ranking/visited_url_ranking_service_factory.h b/chrome/browser/visited_url_ranking/visited_url_ranking_service_factory.h
new file mode 100644
index 0000000..c8eecca
--- /dev/null
+++ b/chrome/browser/visited_url_ranking/visited_url_ranking_service_factory.h
@@ -0,0 +1,42 @@
+// 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_VISITED_URL_RANKING_VISITED_URL_RANKING_SERVICE_FACTORY_H_
+#define CHROME_BROWSER_VISITED_URL_RANKING_VISITED_URL_RANKING_SERVICE_FACTORY_H_
+
+#include "base/no_destructor.h"
+#include "chrome/browser/profiles/profile_keyed_service_factory.h"
+
+class Profile;
+
+namespace visited_url_ranking {
+
+class VisitedURLRankingService;
+
+// Singleton that owns all `VisitedURLRankingService` instances and associates
+// them with profiles.
+class VisitedURLRankingServiceFactory : public ProfileKeyedServiceFactory {
+ public:
+  static visited_url_ranking::VisitedURLRankingService* GetForProfile(
+      Profile* profile);
+  static VisitedURLRankingServiceFactory* GetInstance();
+  VisitedURLRankingServiceFactory(const VisitedURLRankingServiceFactory&) =
+      delete;
+
+ private:
+  friend base::NoDestructor<VisitedURLRankingServiceFactory>;
+
+  VisitedURLRankingServiceFactory();
+  ~VisitedURLRankingServiceFactory() override;
+
+  // ProfileKeyedServiceFactory::
+  std::unique_ptr<KeyedService> BuildServiceInstanceForBrowserContext(
+      content::BrowserContext* context) const override;
+  bool ServiceIsCreatedWithBrowserContext() const override;
+  bool ServiceIsNULLWhileTesting() const override;
+};
+
+}  // namespace visited_url_ranking
+
+#endif  // CHROME_BROWSER_VISITED_URL_RANKING_VISITED_URL_RANKING_SERVICE_FACTORY_H_
diff --git a/chrome/browser/webauthn/authenticator_request_dialog_model.cc b/chrome/browser/webauthn/authenticator_request_dialog_model.cc
index bbcd26d..77684a5a 100644
--- a/chrome/browser/webauthn/authenticator_request_dialog_model.cc
+++ b/chrome/browser/webauthn/authenticator_request_dialog_model.cc
@@ -1402,7 +1402,7 @@
 
 device::AuthenticatorType
 AuthenticatorRequestDialogController::OnAccountPreselected(
-    const std::vector<uint8_t>& credential_id) {
+    const std::vector<uint8_t> credential_id) {
   // User selected one of the platform authenticator credentials enumerated in
   // Conditional or regular modal UI prior to collecting user verification.
   // Run `account_preselected_callback_` to narrow the request to the selected
@@ -1430,7 +1430,7 @@
   if (!base::FeatureList::IsEnabled(device::kWebAuthnEnclaveAuthenticator)) {
     ContactPriorityPhone();
   } else {
-    model_->OnGPMPasskeySelected(credential_id);
+    model_->OnGPMPasskeySelected(std::move(credential_id));
   }
   return source;
 }
diff --git a/chrome/browser/webauthn/authenticator_request_dialog_model.h b/chrome/browser/webauthn/authenticator_request_dialog_model.h
index b0664ac..9c166c66 100644
--- a/chrome/browser/webauthn/authenticator_request_dialog_model.h
+++ b/chrome/browser/webauthn/authenticator_request_dialog_model.h
@@ -768,8 +768,12 @@
   // authentication. `crededential_id` must match one of the credentials in
   // `transport_availability_.recognized_credentials`. Returns the source of the
   // credential.
+  //
+  // Note: it's important not to pass a reference to `credential_id` here
+  // because this function clears `model_->creds`, which is where such a
+  // reference would often point.
   device::AuthenticatorType OnAccountPreselected(
-      const std::vector<uint8_t>& credential_id);
+      const std::vector<uint8_t> credential_id);
 
   void OnAccountPreselectedIndex(size_t index) override;
   void SetSelectedAuthenticatorForTesting(AuthenticatorReference authenticator);
diff --git a/chrome/browser/webauthn/enclave_authenticator_browsertest.cc b/chrome/browser/webauthn/enclave_authenticator_browsertest.cc
index 718a9e08..fc09e85 100644
--- a/chrome/browser/webauthn/enclave_authenticator_browsertest.cc
+++ b/chrome/browser/webauthn/enclave_authenticator_browsertest.cc
@@ -443,8 +443,7 @@
 #if BUILDFLAG(IS_WIN)
         webauthn_dll_override_(&fake_webauthn_dll_),
 #endif
-        recovery_key_store_(FakeRecoveryKeyStore::New())
-  {
+        recovery_key_store_(FakeRecoveryKeyStore::New()) {
 #if BUILDFLAG(IS_WIN)
     // Make webauthn.dll unavailable to ensure a consistent test environment on
     // Windows. Otherwise the version of webauthn.dll can differ between
diff --git a/chrome/browser/webauthn/gpm_enclave_controller.cc b/chrome/browser/webauthn/gpm_enclave_controller.cc
index 590b5f35..6d9f73ef 100644
--- a/chrome/browser/webauthn/gpm_enclave_controller.cc
+++ b/chrome/browser/webauthn/gpm_enclave_controller.cc
@@ -31,6 +31,7 @@
 #include "components/trusted_vault/trusted_vault_connection.h"
 #include "components/trusted_vault/trusted_vault_server_constants.h"
 #include "content/public/browser/render_frame_host.h"
+#include "device/fido/enclave/metrics.h"
 #include "device/fido/features.h"
 #include "device/fido/fido_constants.h"
 #include "device/fido/fido_discovery_factory.h"
@@ -584,6 +585,7 @@
   CHECK(enclave_manager_->has_pending_keys());
   CHECK(!enclave_manager_->is_ready());
 
+  device::enclave::RecordEvent(device::enclave::Event::kRecoverySuccessful);
   if (pin_metadata_.has_value() || *can_make_uv_keys_) {
     if (!enclave_manager_->AddDeviceToAccount(
             std::move(pin_metadata_),
diff --git a/chrome/build/android-arm32.pgo.txt b/chrome/build/android-arm32.pgo.txt
index fcc2f08..2a71df4d 100644
--- a/chrome/build/android-arm32.pgo.txt
+++ b/chrome/build/android-arm32.pgo.txt
@@ -1 +1 @@
-chrome-android32-main-1714996788-e3c2addce111ed86f8023654c33636b5746915fa-7c029bd763013d4311fe512635c7f1211ccf1917.profdata
+chrome-android32-main-1715018366-7ddae3bd5196c03d261bac72695acc37a056af7c-33a49ceccc02c48c60f84b2985a02dd114837f54.profdata
diff --git a/chrome/build/mac-arm.pgo.txt b/chrome/build/mac-arm.pgo.txt
index 6f80ba7..6fa5ba8 100644
--- a/chrome/build/mac-arm.pgo.txt
+++ b/chrome/build/mac-arm.pgo.txt
@@ -1 +1 @@
-chrome-mac-arm-main-1715003908-4b8d24c91952eb1d06cbd772b33aa213ab11447f-0e4f503cfb9fd0328f0d50bcd735b135524d6a5a.profdata
+chrome-mac-arm-main-1715018366-abef4bbe24bf87d87f2853d6e280ff7e9d3d99e3-33a49ceccc02c48c60f84b2985a02dd114837f54.profdata
diff --git a/chrome/build/win32.pgo.txt b/chrome/build/win32.pgo.txt
index 3bd8e904..41d6734 100644
--- a/chrome/build/win32.pgo.txt
+++ b/chrome/build/win32.pgo.txt
@@ -1 +1 @@
-chrome-win32-main-1714996788-441ab6b72f9b64c23ed5e2dd105a6e5f10a111bc-7c029bd763013d4311fe512635c7f1211ccf1917.profdata
+chrome-win32-main-1715007384-0559c592cdc1dfc87ca35343edf894071c1ea88e-0e2e4cfae0c76cb46c02f12917408786ebfd475d.profdata
diff --git a/chrome/build/win64.pgo.txt b/chrome/build/win64.pgo.txt
index 81db942..15e6dd15 100644
--- a/chrome/build/win64.pgo.txt
+++ b/chrome/build/win64.pgo.txt
@@ -1 +1 @@
-chrome-win64-main-1714996788-77cb83580f0f722722791763dd25f568c62af22a-7c029bd763013d4311fe512635c7f1211ccf1917.profdata
+chrome-win64-main-1715007384-cd4cbec0459701c00a6811e942d02e55269a1a5a-0e2e4cfae0c76cb46c02f12917408786ebfd475d.profdata
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn
index 5b624ad..9b78c35 100644
--- a/chrome/test/BUILD.gn
+++ b/chrome/test/BUILD.gn
@@ -2122,7 +2122,10 @@
     ]
 
     if (enterprise_watermark) {
-      deps += [ "//components/safe_browsing/core/browser/realtime:url_lookup_service_base" ]
+      deps += [
+        "//chrome/browser/enterprise/watermark:watermark_view_lib",
+        "//components/safe_browsing/core/browser/realtime:url_lookup_service_base",
+      ]
     }
 
     if (is_chromeos) {
@@ -2256,7 +2259,6 @@
       "../../apps/load_and_launch_browsertest.cc",
       "../browser/accessibility/accessibility_labels_service_browsertest.cc",
       "../browser/accessibility/browser_accessibility_state_browsertest.cc",
-      "../browser/accessibility/embedded_a11y_extension_loader_browsertest.cc",
       "../browser/accessibility/image_annotation_browsertest.cc",
       "../browser/accessibility/interstitial_accessibility_browsertest.cc",
       "../browser/accessibility/page_colors_browsertest.cc",
@@ -3657,6 +3659,7 @@
 
     if (enable_extensions) {
       sources += [
+        "../browser/accessibility/embedded_a11y_extension_loader_browsertest.cc",
         "../browser/apps/platform_apps/api/browser/browser_apitest.cc",
         "../browser/apps/platform_apps/api/media_galleries/media_galleries_watch_apitest.cc",
         "../browser/apps/platform_apps/api/sync_file_system/sync_file_system_apitest.cc",
@@ -4143,6 +4146,10 @@
           "../browser/content_settings/content_settings_browsertest.cc",
         ]
       }
+
+      if (!is_chromeos_lacros) {
+        data_deps += [ "//chrome/browser/resources/accessibility:reading_mode_gdocs_extension_resources" ]
+      }
     }
 
     if (use_aura) {
@@ -11329,6 +11336,7 @@
         "../browser/ui/views/passwords/manage_passwords_icon_view_interactive_uitest.cc",
         "../browser/ui/views/passwords/password_bubble_interactive_uitest.cc",
         "../browser/ui/views/performance_controls/memory_saver_interactive_ui_test.cc",
+        "../browser/ui/views/performance_controls/performance_intervention_interactive_ui_test.cc",
         "../browser/ui/views/permissions/chip/dashboard_kombucha_interactive_uitest.cc",
         "../browser/ui/views/permissions/chip/permission_chip_interactive_uitest.cc",
         "../browser/ui/views/permissions/chip/permission_chip_kombucha_interactive_uitest.cc",
diff --git a/chrome/test/android/javatests/src/org/chromium/chrome/test/util/OmniboxTestUtils.java b/chrome/test/android/javatests/src/org/chromium/chrome/test/util/OmniboxTestUtils.java
index ddb6580..2bccf5b 100644
--- a/chrome/test/android/javatests/src/org/chromium/chrome/test/util/OmniboxTestUtils.java
+++ b/chrome/test/android/javatests/src/org/chromium/chrome/test/util/OmniboxTestUtils.java
@@ -241,18 +241,15 @@
      * Set the suggestions to the Omnibox to display.
      *
      * @param autocompleteResult The set of suggestions will be displayed on the Omnibox dropdown
-     *         list.
-     * @param inlineAutocompleteText the inline-autocomplete text.
+     *     list.
      */
-    public void setSuggestions(
-            AutocompleteResult autocompleteResult, String inlineAutocompleteText) {
+    public void setSuggestions(AutocompleteResult autocompleteResult) {
         checkFocus(true);
         TestThreadUtils.runOnUiThreadBlocking(
                 () -> {
                     OnSuggestionsReceivedListener listener =
                             mAutocomplete.getSuggestionsReceivedListenerForTest();
-                    listener.onSuggestionsReceived(
-                            autocompleteResult, inlineAutocompleteText, true);
+                    listener.onSuggestionsReceived(autocompleteResult, true);
                 });
     }
 
diff --git a/chrome/test/data/webui/chromeos/diagnostics/BUILD.gn b/chrome/test/data/webui/chromeos/diagnostics/BUILD.gn
index 4dfd593..47757e4 100644
--- a/chrome/test/data/webui/chromeos/diagnostics/BUILD.gn
+++ b/chrome/test/data/webui/chromeos/diagnostics/BUILD.gn
@@ -46,7 +46,7 @@
     "overview_card_test.js",
     "percent_bar_chart_test.js",
     "realtime_cpu_chart_test.js",
-    "routine_group_test.js",
+    "routine_group_test.ts",
     "routine_list_executor_test.ts",
     "routine_result_entry_test.js",
     "routine_result_list_test.js",
diff --git a/chrome/test/data/webui/chromeos/diagnostics/routine_group_test.js b/chrome/test/data/webui/chromeos/diagnostics/routine_group_test.ts
similarity index 81%
rename from chrome/test/data/webui/chromeos/diagnostics/routine_group_test.js
rename to chrome/test/data/webui/chromeos/diagnostics/routine_group_test.ts
index 419784e..9efc8a69 100644
--- a/chrome/test/data/webui/chromeos/diagnostics/routine_group_test.js
+++ b/chrome/test/data/webui/chromeos/diagnostics/routine_group_test.ts
@@ -7,46 +7,38 @@
 import {createRoutine} from 'chrome://diagnostics/diagnostics_utils.js';
 import {RoutineGroup} from 'chrome://diagnostics/routine_group.js';
 import {ExecutionProgress, ResultStatusItem} from 'chrome://diagnostics/routine_list_executor.js';
-import {RoutineResult, RoutineType, StandardRoutineResult} from 'chrome://diagnostics/system_routine_controller.mojom-webui.js';
+import {RoutineType, StandardRoutineResult} from 'chrome://diagnostics/system_routine_controller.mojom-webui.js';
 import {assertEquals, assertFalse, assertTrue} from 'chrome://webui-test/chromeos/chai_assert.js';
 
-/**
- * @param {!RoutineType} routineType
- * @return {!ResultStatusItem}
- */
-function getRoutineRunningStatusItem(routineType) {
+function getRoutineRunningStatusItem(routineType: RoutineType):
+    ResultStatusItem {
   return new ResultStatusItem(routineType, ExecutionProgress.RUNNING);
 }
 
-/**
- * @param {!RoutineType} routineType
- * @return {!ResultStatusItem}
- */
-function getRoutinedPassedStatusItem(routineType) {
+function getRoutinedPassedStatusItem(routineType: RoutineType):
+    ResultStatusItem {
   const item = new ResultStatusItem(routineType, ExecutionProgress.COMPLETED);
-  item.result = /** @type {!RoutineResult} */ (
-      {simpleResult: StandardRoutineResult.kTestPassed});
+  item.result = {
+    simpleResult: StandardRoutineResult.kTestPassed,
+    powerResult: undefined,
+  };
   return item;
 }
 
-/**
- * @param {!RoutineType} routineType
- * @return {!ResultStatusItem}
- */
-function getRoutinedFailedStatusItem(routineType) {
+function getRoutinedFailedStatusItem(routineType: RoutineType):
+    ResultStatusItem {
   const item = new ResultStatusItem(routineType, ExecutionProgress.COMPLETED);
-  item.result = /** @type {!RoutineResult} */ (
-      {simpleResult: StandardRoutineResult.kTestFailed});
+  item.result = {
+    simpleResult: StandardRoutineResult.kTestFailed,
+    powerResult: undefined,
+  };
   return item;
 }
 
 /**
  * Get nonBlockingRoutines private member for testing.
- * @suppress {visibility} // access private member
- * @param {!RoutineGroup} routineGroup
- * @return {!Set<!RoutineType>}
  */
-function getNonBlockingRoutines(routineGroup) {
+function getNonBlockingRoutines(routineGroup: RoutineGroup): Set<RoutineType> {
   return routineGroup.nonBlockingRoutines;
 }
 
diff --git a/chrome/test/data/webui/chromeos/personalization_app/personalization_app_browsertest.cc b/chrome/test/data/webui/chromeos/personalization_app/personalization_app_browsertest.cc
index 6ac32591..e4a78cf 100644
--- a/chrome/test/data/webui/chromeos/personalization_app/personalization_app_browsertest.cc
+++ b/chrome/test/data/webui/chromeos/personalization_app/personalization_app_browsertest.cc
@@ -151,7 +151,6 @@
           "mocha.run()");
 }
 
-// TODO(b/322108635): Re-enable when the flakiness is resolved.
 IN_PROC_BROWSER_TEST_F(PersonalizationAppComponentTest, SeaPenTemplates) {
   RunTest("chromeos/personalization_app/sea_pen_templates_element_test.js",
           "mocha.run()");
@@ -187,9 +186,7 @@
           "mocha.run()");
 }
 
-// TODO(crbug.com/336418721): Re-enable this test flakily failing.
-IN_PROC_BROWSER_TEST_F(PersonalizationAppComponentTest,
-                       DISABLED_WallpaperImages) {
+IN_PROC_BROWSER_TEST_F(PersonalizationAppComponentTest, WallpaperImages) {
   RunTest("chromeos/personalization_app/wallpaper_images_element_test.js",
           "mocha.run()");
 }
diff --git a/chrome/test/data/webui/chromeos/settings/os_privacy_page/os_privacy_page_test.ts b/chrome/test/data/webui/chromeos/settings/os_privacy_page/os_privacy_page_test.ts
index 6811b69..096961f 100644
--- a/chrome/test/data/webui/chromeos/settings/os_privacy_page/os_privacy_page_test.ts
+++ b/chrome/test/data/webui/chromeos/settings/os_privacy_page/os_privacy_page_test.ts
@@ -3,7 +3,7 @@
 // found in the LICENSE file.
 
 import {PrivacyHubBrowserProxyImpl} from 'chrome://os-settings/lazy_load.js';
-import {CrDialogElement, createRouterForTesting, CrRadioGroupElement, OsSettingsPrivacyPageElement, PageStatus, PeripheralDataAccessBrowserProxyImpl, Router, routes, SecureDnsMode, settingMojom, SettingsToggleButtonElement, SyncBrowserProxy, SyncBrowserProxyImpl} from 'chrome://os-settings/os_settings.js';
+import {CrDialogElement, createRouterForTesting, CrRadioGroupElement, GeolocationAccessLevel, OsSettingsPrivacyPageElement, PageStatus, PeripheralDataAccessBrowserProxyImpl, Router, routes, SecureDnsMode, settingMojom, SettingsToggleButtonElement, SyncBrowserProxy, SyncBrowserProxyImpl} from 'chrome://os-settings/os_settings.js';
 import {assert} from 'chrome://resources/js/assert.js';
 import {webUIListenerCallback} from 'chrome://resources/js/cr.js';
 import {loadTimeData} from 'chrome://resources/js/load_time_data.js';
@@ -168,6 +168,11 @@
         'microphone_allowed': {
           value: true,
         },
+        'geolocation_access_level': {
+          key: 'ash.user.geolocation_access_level',
+          type: chrome.settingsPrivate.PrefType.NUMBER,
+          value: GeolocationAccessLevel.ALLOWED,
+        },
       },
     },
     'settings': {
diff --git a/chrome/test/data/webui/chromeos/settings/os_privacy_page/privacy_hub_subpage_test.ts b/chrome/test/data/webui/chromeos/settings/os_privacy_page/privacy_hub_subpage_test.ts
index c76474f..8cf3e70 100644
--- a/chrome/test/data/webui/chromeos/settings/os_privacy_page/privacy_hub_subpage_test.ts
+++ b/chrome/test/data/webui/chromeos/settings/os_privacy_page/privacy_hub_subpage_test.ts
@@ -5,7 +5,7 @@
 import 'chrome://os-settings/lazy_load.js';
 
 import {MediaDevicesProxy, PrivacyHubBrowserProxyImpl, SettingsPrivacyHubSubpage} from 'chrome://os-settings/lazy_load.js';
-import {CrLinkRowElement, CrToggleElement, MetricsConsentBrowserProxyImpl, OsSettingsPrivacyPageElement, PaperTooltipElement, PrivacyHubSensorSubpageUserAction, Router, routes, SecureDnsMode, settingMojom, SettingsToggleButtonElement} from 'chrome://os-settings/os_settings.js';
+import {CrLinkRowElement, CrToggleElement, GeolocationAccessLevel, MetricsConsentBrowserProxyImpl, OsSettingsPrivacyPageElement, PaperTooltipElement, PrivacyHubSensorSubpageUserAction, Router, routes, SecureDnsMode, settingMojom, SettingsToggleButtonElement} from 'chrome://os-settings/os_settings.js';
 import {assert} from 'chrome://resources/js/assert.js';
 import {webUIListenerCallback} from 'chrome://resources/js/cr.js';
 import {loadTimeData} from 'chrome://resources/js/load_time_data.js';
@@ -33,6 +33,11 @@
       'microphone_allowed': {
         value: true,
       },
+      'geolocation_access_level': {
+        key: 'ash.user.geolocation_access_level',
+        type: chrome.settingsPrivate.PrefType.NUMBER,
+        value: GeolocationAccessLevel.ALLOWED,
+      },
     },
   },
 };
@@ -977,6 +982,47 @@
         getCameraToggleAriaLabel());
     assertEquals(getCameraRowSubtext(), getCameraToggleAriaDescription());
   });
+
+  function setGeolocationAccessLevel(accessLevel: GeolocationAccessLevel) {
+    privacyHubSubpage.set(
+        'prefs.ash.user.geolocation_access_level.value', accessLevel);
+  }
+
+  function getGeolocationSubtext(): string {
+    return privacyHubSubpage.shadowRoot!
+        .querySelector<CrLinkRowElement>('#geolocationAreaLinkRow')!.shadowRoot!
+        .querySelector<HTMLElement>('#subLabel')!.innerText;
+  }
+
+  test('Geolocation row subtext', async () => {
+    // Location should be allowed by default
+    assertEquals(
+        privacyHubSubpage.prefs.ash.user.geolocation_access_level.value,
+        GeolocationAccessLevel.ALLOWED);
+    assertEquals(
+        privacyHubSubpage.i18n('geolocationAreaAllowedSubtext'),
+        getGeolocationSubtext());
+
+    // Set Location setting to system only
+    setGeolocationAccessLevel(GeolocationAccessLevel.ONLY_ALLOWED_FOR_SYSTEM);
+    await waitAfterNextRender(privacyHubSubpage);
+    assertEquals(
+        privacyHubSubpage.prefs.ash.user.geolocation_access_level.value,
+        GeolocationAccessLevel.ONLY_ALLOWED_FOR_SYSTEM);
+    assertEquals(
+        privacyHubSubpage.i18n('geolocationAreaOnlyAllowedForSystemSubtext'),
+        getGeolocationSubtext());
+
+    // Disable location
+    setGeolocationAccessLevel(GeolocationAccessLevel.DISALLOWED);
+    await waitAfterNextRender(privacyHubSubpage);
+    assertEquals(
+        privacyHubSubpage.prefs.ash.user.geolocation_access_level.value,
+        GeolocationAccessLevel.DISALLOWED);
+    assertEquals(
+        privacyHubSubpage.i18n('geolocationAreaDisallowedSubtext'),
+        getGeolocationSubtext());
+  });
 });
 
 
diff --git a/chrome/test/data/webui/new_tab_page/modules/v2/calendar/calendar.gni b/chrome/test/data/webui/new_tab_page/modules/v2/calendar/calendar.gni
index ed270af..0199fb9 100644
--- a/chrome/test/data/webui/new_tab_page/modules/v2/calendar/calendar.gni
+++ b/chrome/test/data/webui/new_tab_page/modules/v2/calendar/calendar.gni
@@ -2,4 +2,7 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
-calendar_v2_test_files = [ "modules/v2/calendar/module_test.ts" ]
+calendar_v2_test_files = [
+  "modules/v2/calendar/google_calendar_module_test.ts",
+  "modules/v2/calendar/outlook_calendar_module_test.ts",
+]
diff --git a/chrome/test/data/webui/new_tab_page/modules/v2/calendar/google_calendar_module_test.ts b/chrome/test/data/webui/new_tab_page/modules/v2/calendar/google_calendar_module_test.ts
new file mode 100644
index 0000000..00637b4
--- /dev/null
+++ b/chrome/test/data/webui/new_tab_page/modules/v2/calendar/google_calendar_module_test.ts
@@ -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.
+
+import type {GoogleCalendarModuleElement} from 'chrome://new-tab-page/lazy_load.js';
+import {googleCalendarDescriptor} from 'chrome://new-tab-page/lazy_load.js';
+import {loadTimeData} from 'chrome://resources/js/load_time_data.js';
+import {assertEquals, assertTrue} from 'chrome://webui-test/chai_assert.js';
+import {waitAfterNextRender} from 'chrome://webui-test/polymer_test_util.js';
+import {isVisible} from 'chrome://webui-test/test_util.js';
+
+suite('NewTabPageModulesGoogleCalendarModuleTest', () => {
+  const title = `Today's Calendar`;
+
+  setup(() => {
+    loadTimeData.overrideValues({
+      modulesTodayCalendarHeader: title,
+    });
+    document.body.innerHTML = window.trustedTypes!.emptyHTML;
+  });
+
+  test('creates module', async () => {
+    const module = await googleCalendarDescriptor.initialize(0) as
+        GoogleCalendarModuleElement;
+    assertTrue(!!module);
+    document.body.append(module);
+    await waitAfterNextRender(module);
+
+    // Assert.
+    assertTrue(isVisible(module.$.moduleHeaderElementV2));
+    assertEquals(module.$.moduleHeaderElementV2.headerText, title);
+  });
+});
diff --git a/chrome/test/data/webui/new_tab_page/modules/v2/calendar/module_test.ts b/chrome/test/data/webui/new_tab_page/modules/v2/calendar/module_test.ts
deleted file mode 100644
index fce544e..0000000
--- a/chrome/test/data/webui/new_tab_page/modules/v2/calendar/module_test.ts
+++ /dev/null
@@ -1,35 +0,0 @@
-// 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 type {CalendarModuleElement} from 'chrome://new-tab-page/lazy_load.js';
-import {googleCalendarDescriptor, outlookCalendarDescriptor} from 'chrome://new-tab-page/lazy_load.js';
-import {loadTimeData} from 'chrome://resources/js/load_time_data.js';
-import {assertEquals, assertTrue} from 'chrome://webui-test/chai_assert.js';
-import {waitAfterNextRender} from 'chrome://webui-test/polymer_test_util.js';
-import {isVisible} from 'chrome://webui-test/test_util.js';
-
-suite('NewTabPageModulesCalendarModuleTest', () => {
-  setup(() => {
-    document.body.innerHTML = window.trustedTypes!.emptyHTML;
-  });
-
-  [
-    {descriptor: googleCalendarDescriptor, title: 'Google Calendar'},
-    {descriptor: outlookCalendarDescriptor, title: 'Outlook Calendar'},
-  ].forEach(({descriptor, title}) => {
-    test(`creates ${title} module`, async () => {
-      loadTimeData.overrideValues({
-        modulesGoogleCalendarTitle: title,
-      });
-      const module = await descriptor.initialize(0) as CalendarModuleElement;
-      assertTrue(!!module);
-      document.body.append(module);
-      await waitAfterNextRender(module);
-
-      // Assert.
-      assertTrue(isVisible(module.$.moduleHeaderElementV2));
-      assertEquals(module.$.moduleHeaderElementV2.headerText, title);
-    });
-  });
-});
diff --git a/chrome/test/data/webui/new_tab_page/modules/v2/calendar/outlook_calendar_module_test.ts b/chrome/test/data/webui/new_tab_page/modules/v2/calendar/outlook_calendar_module_test.ts
new file mode 100644
index 0000000..2fde9e3
--- /dev/null
+++ b/chrome/test/data/webui/new_tab_page/modules/v2/calendar/outlook_calendar_module_test.ts
@@ -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.
+
+import type {OutlookCalendarModuleElement} from 'chrome://new-tab-page/lazy_load.js';
+import {outlookCalendarDescriptor} from 'chrome://new-tab-page/lazy_load.js';
+import {loadTimeData} from 'chrome://resources/js/load_time_data.js';
+import {assertEquals, assertTrue} from 'chrome://webui-test/chai_assert.js';
+import {waitAfterNextRender} from 'chrome://webui-test/polymer_test_util.js';
+import {isVisible} from 'chrome://webui-test/test_util.js';
+
+suite('NewTabPageModulesOutlookCalendarModuleTest', () => {
+  const title = 'Outlook Calendar';
+
+  setup(() => {
+    loadTimeData.overrideValues({
+      modulesOutlookCalendarTitle: title,
+    });
+    document.body.innerHTML = window.trustedTypes!.emptyHTML;
+  });
+
+  test(`creates module`, async () => {
+    const module = await outlookCalendarDescriptor.initialize(0) as
+        OutlookCalendarModuleElement;
+    assertTrue(!!module);
+    document.body.append(module);
+    await waitAfterNextRender(module);
+
+    // Assert.
+    assertTrue(isVisible(module.$.moduleHeaderElementV2));
+    assertEquals(module.$.moduleHeaderElementV2.headerText, title);
+  });
+});
diff --git a/chrome/test/data/webui/new_tab_page/new_tab_page_browsertest.cc b/chrome/test/data/webui/new_tab_page/new_tab_page_browsertest.cc
index 1e124bc8..329a76f 100644
--- a/chrome/test/data/webui/new_tab_page/new_tab_page_browsertest.cc
+++ b/chrome/test/data/webui/new_tab_page/new_tab_page_browsertest.cc
@@ -126,8 +126,14 @@
 }
 #endif  // !defined(OFFICIAL_BUILD)
 
-IN_PROC_BROWSER_TEST_F(NewTabPageModulesTest, CalendarModule) {
-  RunTest("new_tab_page/modules/v2/calendar/module_test.js", "mocha.run()");
+IN_PROC_BROWSER_TEST_F(NewTabPageModulesTest, GoogleCalendarModule) {
+  RunTest("new_tab_page/modules/v2/calendar/google_calendar_module_test.js",
+          "mocha.run()");
+}
+
+IN_PROC_BROWSER_TEST_F(NewTabPageModulesTest, OutlookCalendarModule) {
+  RunTest("new_tab_page/modules/v2/calendar/outlook_calendar_module_test.js",
+          "mocha.run()");
 }
 
 IN_PROC_BROWSER_TEST_F(NewTabPageModulesTest, DriveModule) {
diff --git a/chrome/test/data/webui/side_panel/customize_chrome/app_test.ts b/chrome/test/data/webui/side_panel/customize_chrome/app_test.ts
index 11c7094..a96c30c 100644
--- a/chrome/test/data/webui/side_panel/customize_chrome/app_test.ts
+++ b/chrome/test/data/webui/side_panel/customize_chrome/app_test.ts
@@ -5,12 +5,16 @@
 import 'chrome://customize-chrome-side-panel.top-chrome/app.js';
 
 import type {AppElement} from 'chrome://customize-chrome-side-panel.top-chrome/app.js';
+import {CustomizeChromeImpression} from 'chrome://customize-chrome-side-panel.top-chrome/common.js';
 import type {BackgroundCollection, CustomizeChromePageRemote} from 'chrome://customize-chrome-side-panel.top-chrome/customize_chrome.mojom-webui.js';
 import {CustomizeChromePageCallbackRouter, CustomizeChromePageHandlerRemote, CustomizeChromeSection} from 'chrome://customize-chrome-side-panel.top-chrome/customize_chrome.mojom-webui.js';
 import {CustomizeChromeApiProxy} from 'chrome://customize-chrome-side-panel.top-chrome/customize_chrome_api_proxy.js';
 import {loadTimeData} from 'chrome://resources/js/load_time_data.js';
 import {assertEquals, assertGE, assertTrue} from 'chrome://webui-test/chai_assert.js';
+import type {MetricsTracker} from 'chrome://webui-test/metrics_test_support.js';
+import {fakeMetricsPrivate} from 'chrome://webui-test/metrics_test_support.js';
 import type {TestMock} from 'chrome://webui-test/test_mock.js';
+import {eventToPromise} from 'chrome://webui-test/test_util.js';
 
 import {installMock} from './test_support.js';
 
@@ -18,6 +22,7 @@
   let customizeChromeApp: AppElement;
   let handler: TestMock<CustomizeChromePageHandlerRemote>;
   let callbackRouter: CustomizeChromePageRemote;
+  let metrics: MetricsTracker;
 
   setup(async () => {
     document.body.innerHTML = window.trustedTypes!.emptyHTML;
@@ -32,6 +37,42 @@
                          .callbackRouter.$.bindNewPipeAndPassRemote();
     customizeChromeApp = document.createElement('customize-chrome-app');
     document.body.appendChild(customizeChromeApp);
+    metrics = fakeMetricsPrivate();
+  });
+
+  suite('Metrics', () => {
+    suiteSetup(() => {
+      document.body.innerHTML = window.trustedTypes!.emptyHTML;
+      customizeChromeApp = document.createElement('customize-chrome-app');
+      document.body.appendChild(customizeChromeApp);
+      loadTimeData.overrideValues({
+        'extensionsCardEnabled': true,
+      });
+    });
+    test('Rendering extensions card section sets metric', async () => {
+      window.dispatchEvent(new Event('load'));
+      const eventPromise = eventToPromise(
+          'detect-extensions-card-section-impression', customizeChromeApp);
+      assertEquals(
+          0, metrics.count('NewTabPage.CustomizeChromeSidePanelImpression'));
+      assertEquals(
+          0,
+          metrics.count(
+              'NewTabPage.CustomizeChromeSidePanelImpression',
+              CustomizeChromeImpression.EXTENSIONS_CARD_SECTION_DISPLAYED));
+
+      customizeChromeApp.shadowRoot!.querySelector('#extensions')!
+          .scrollIntoView({'behavior': 'instant'});
+      await eventPromise;
+
+      assertEquals(
+          1, metrics.count('NewTabPage.CustomizeChromeSidePanelImpression'));
+      assertEquals(
+          1,
+          metrics.count(
+              'NewTabPage.CustomizeChromeSidePanelImpression',
+              CustomizeChromeImpression.EXTENSIONS_CARD_SECTION_DISPLAYED));
+    });
   });
 
   test('app changes pages', async () => {
diff --git a/chromeos/ash/components/audio/audio_device_metrics_handler.cc b/chromeos/ash/components/audio/audio_device_metrics_handler.cc
index eb2bfdc6..bd9648d 100644
--- a/chromeos/ash/components/audio/audio_device_metrics_handler.cc
+++ b/chromeos/ash/components/audio/audio_device_metrics_handler.cc
@@ -39,15 +39,34 @@
       return;
     }
 
+    AudioDeviceList input_devices =
+        CrasAudioHandler::GetSimpleUsageAudioDevices(audio_devices_,
+                                                     /*is_input=*/true);
+    uint32_t input_devices_bits = EncodeAudioDeviceSet(input_devices);
+    AudioDeviceList previous_input_devices =
+        CrasAudioHandler::GetSimpleUsageAudioDevices(previous_audio_devices_,
+                                                     /*is_input=*/true);
+    uint32_t previous_input_devices_bits =
+        EncodeAudioDeviceSet(previous_input_devices);
+
+    // Do not record system decision metrics since the device set doesn't
+    // change. No interested system selection decision in this case. This could
+    // happen when cras lost the active device, or cras fires extra node change
+    // signal.
+    if (input_devices_bits == previous_input_devices_bits) {
+      // Reset timestamp since no interested system selection decision is made
+      // and to prevent previous system decision from being used to record the
+      // user override.
+      ResetSystemSwitchTimestamp(is_input);
+      return;
+    }
+
     base::UmaHistogramBoolean(kSystemSwitchInputAudio, is_switched);
     base::UmaHistogramEnumeration(
         kAudioSelectionPerformance,
         is_switched ? AudioSelectionEvents::kSystemSwitchInput
                     : AudioSelectionEvents::kSystemNotSwitchInput);
 
-    AudioDeviceList input_devices =
-        CrasAudioHandler::GetSimpleUsageAudioDevices(audio_devices_,
-                                                     /*is_input=*/true);
     // Record the number of audio devices at the moment.
     base::UmaHistogramExactLinear(is_switched
                                       ? kSystemSwitchInputAudioDeviceCount
@@ -57,11 +76,8 @@
     // Record the encoded device set.
     base::UmaHistogramSparse(is_switched ? kSystemSwitchInputAudioDeviceSet
                                          : kSystemNotSwitchInputAudioDeviceSet,
-                             EncodeAudioDeviceSet(input_devices));
+                             input_devices_bits);
 
-    AudioDeviceList previous_input_devices =
-        CrasAudioHandler::GetSimpleUsageAudioDevices(previous_audio_devices_,
-                                                     /*is_input=*/true);
     // Record the before and after encoded device sets.
     base::UmaHistogramSparse(
         is_switched ? kSystemSwitchInputBeforeAndAfterAudioDeviceSet
@@ -90,15 +106,34 @@
       return;
     }
 
+    AudioDeviceList output_devices =
+        CrasAudioHandler::GetSimpleUsageAudioDevices(audio_devices_,
+                                                     /*is_input=*/false);
+    uint32_t output_devices_bits = EncodeAudioDeviceSet(output_devices);
+    AudioDeviceList previous_output_devices =
+        CrasAudioHandler::GetSimpleUsageAudioDevices(previous_audio_devices_,
+                                                     /*is_input=*/false);
+    uint32_t previous_output_devices_bits =
+        EncodeAudioDeviceSet(previous_output_devices);
+
+    // Do not record system decision metrics since the device set doesn't
+    // change. No interested system selection decision in this case. This could
+    // happen when cras lost the active device, or cras fires extra node change
+    // signal.
+    if (output_devices_bits == previous_output_devices_bits) {
+      // Reset timestamp since no interested system selection decision is made
+      // and to prevent previous system decision from being used to record the
+      // user override.
+      ResetSystemSwitchTimestamp(is_input);
+      return;
+    }
+
     base::UmaHistogramBoolean(kSystemSwitchOutputAudio, is_switched);
     base::UmaHistogramEnumeration(
         kAudioSelectionPerformance,
         is_switched ? AudioSelectionEvents::kSystemSwitchOutput
                     : AudioSelectionEvents::kSystemNotSwitchOutput);
 
-    AudioDeviceList output_devices =
-        CrasAudioHandler::GetSimpleUsageAudioDevices(audio_devices_,
-                                                     /*is_input=*/false);
     // Record the number of audio devices at the moment.
     base::UmaHistogramExactLinear(is_switched
                                       ? kSystemSwitchOutputAudioDeviceCount
@@ -108,10 +143,7 @@
     // Record the encoded device set.
     base::UmaHistogramSparse(is_switched ? kSystemSwitchOutputAudioDeviceSet
                                          : kSystemNotSwitchOutputAudioDeviceSet,
-                             EncodeAudioDeviceSet(output_devices));
-    AudioDeviceList previous_output_devices =
-        CrasAudioHandler::GetSimpleUsageAudioDevices(previous_audio_devices_,
-                                                     /*is_input=*/false);
+                             output_devices_bits);
 
     // Record the before and after encoded device sets.
     base::UmaHistogramSparse(
@@ -188,8 +220,9 @@
     AudioSelectionEvents audio_selection_event =
         is_input ? AudioSelectionEvents::kUserOverrideSystemSwitchInput
                  : AudioSelectionEvents::kUserOverrideSystemSwitchOutput;
-    RecordUserOverrideMetrics(histogram_name_switched, audio_selection_event,
-                              time_delta_since_system_decision);
+    RecordUserOverrideMetricsHelper(histogram_name_switched,
+                                    audio_selection_event,
+                                    time_delta_since_system_decision);
 
     // Record user override metrics separated by chrome restarts.
 
@@ -215,9 +248,9 @@
     AudioSelectionEvents audio_selection_event =
         is_input ? AudioSelectionEvents::kUserOverrideSystemNotSwitchInput
                  : AudioSelectionEvents::kUserOverrideSystemNotSwitchOutput;
-    RecordUserOverrideMetrics(histogram_name_not_switched,
-                              audio_selection_event,
-                              time_delta_since_system_decision);
+    RecordUserOverrideMetricsHelper(histogram_name_not_switched,
+                                    audio_selection_event,
+                                    time_delta_since_system_decision);
 
     // Record user override metrics separated by chrome restarts.
 
@@ -338,7 +371,7 @@
                                previous_device_list, current_device_list));
 }
 
-void AudioDeviceMetricsHandler::RecordUserOverrideMetrics(
+void AudioDeviceMetricsHandler::RecordUserOverrideMetricsHelper(
     const std::string_view histogram_name,
     AudioSelectionEvents audio_selection_event,
     int time_delta_since_system_decision) const {
@@ -412,8 +445,9 @@
     }
   }
 
-  RecordUserOverrideMetrics(user_override_histogram_name, audio_selection_event,
-                            time_delta_since_system_decision);
+  RecordUserOverrideMetricsHelper(user_override_histogram_name,
+                                  audio_selection_event,
+                                  time_delta_since_system_decision);
 }
 
 void AudioDeviceMetricsHandler::RecordConsecutiveAudioDevicsChangeTimeElapsed(
diff --git a/chromeos/ash/components/audio/audio_device_metrics_handler.h b/chromeos/ash/components/audio/audio_device_metrics_handler.h
index 822953ff..66645f2c 100644
--- a/chromeos/ash/components/audio/audio_device_metrics_handler.h
+++ b/chromeos/ash/components/audio/audio_device_metrics_handler.h
@@ -370,11 +370,6 @@
       const AudioDeviceList& previous_device_list,
       const AudioDeviceList& current_device_list) const;
 
-  // Record user overrides system decision metrics.
-  void RecordUserOverrideMetrics(const std::string_view histogram_name,
-                                 AudioSelectionEvents audio_selection_event,
-                                 int time_delta_since_system_decision) const;
-
   // Record user overrides system decision metrics in the case of chrome
   // restarts, including system boots and users sign out, as well as the case of
   // normal user hotplug or unplug.
@@ -405,6 +400,13 @@
   }
 
  private:
+  // A helper function to record user overrides system decision metrics and
+  // directly calls uma histogram function.
+  void RecordUserOverrideMetricsHelper(
+      const std::string_view histogram_name,
+      AudioSelectionEvents audio_selection_event,
+      int time_delta_since_system_decision) const;
+
   // Clear the timer of system switch/not switch decision.
   void ResetSystemSwitchTimestamp(bool is_input);
 
diff --git a/chromeos/ash/components/audio/audio_device_metrics_handler_unittest.cc b/chromeos/ash/components/audio/audio_device_metrics_handler_unittest.cc
index 1afe8e2..4aaba575 100644
--- a/chromeos/ash/components/audio/audio_device_metrics_handler_unittest.cc
+++ b/chromeos/ash/components/audio/audio_device_metrics_handler_unittest.cc
@@ -68,7 +68,7 @@
       }
       histogram_tester().ExpectBucketCount(device_count_histogram_name,
                                            current_devices.size(),
-                                           /*bucket_count=*/1);
+                                           /*expected_count=*/1);
 
       std::string device_set_histogram_name;
       if (is_switched) {
@@ -88,7 +88,7 @@
 
       histogram_tester().ExpectBucketCount(
           device_set_histogram_name, EncodeAudioDeviceSet(current_devices),
-          /*bucket_count=*/1);
+          /*expected_count=*/1);
 
       std::string before_and_after_device_set_histogram_name;
       if (is_switched) {
@@ -110,13 +110,13 @@
           before_and_after_device_set_histogram_name,
           EncodeBeforeAndAfterAudioDeviceSets(previous_devices,
                                               current_devices),
-          /*bucket_count=*/1);
+          /*expected_count=*/1);
 
       // Test user override metrics.
       audio_device_metrics_handler()
           .RecordUserOverrideMetricsSeparatedByChromeRestarts(
               is_input, is_switched, /*is_chrome_restarts=*/false,
-              /*time_delta=*/kTimeDeltaInMinute);
+              /*time_delta_since_system_decision=*/kTimeDeltaInMinute);
 
       std::string user_override_histogram_name;
       if (is_switched) {
@@ -137,7 +137,7 @@
       }
 
       histogram_tester().ExpectTotalCount(user_override_histogram_name,
-                                          /*bucket_count=*/1);
+                                          /*expected_count=*/1);
       histogram_tester().ExpectTimeBucketCount(
           user_override_histogram_name,
           base::Minutes(kTimeDeltaInMinute) / base::Minutes(1).InMilliseconds(),
@@ -184,7 +184,7 @@
       }
       histogram_tester().ExpectBucketCount(device_count_histogram_name,
                                            current_devices.size(),
-                                           /*bucket_count=*/1);
+                                           /*expected_count=*/1);
 
       std::string device_set_histogram_name;
       if (is_switched) {
@@ -202,7 +202,7 @@
       }
       histogram_tester().ExpectBucketCount(
           device_set_histogram_name, EncodeAudioDeviceSet(current_devices),
-          /*bucket_count=*/1);
+          /*expected_count=*/1);
 
       std::string before_and_after_device_set_histogram_name;
       if (is_switched) {
@@ -224,13 +224,13 @@
           before_and_after_device_set_histogram_name,
           EncodeBeforeAndAfterAudioDeviceSets(previous_devices,
                                               current_devices),
-          /*bucket_count=*/1);
+          /*expected_count=*/1);
 
       // Test user override metrics.
       audio_device_metrics_handler()
           .RecordUserOverrideMetricsSeparatedByChromeRestarts(
               is_input, is_switched, /*is_chrome_restarts=*/true,
-              /*time_delta=*/kTimeDeltaInMinute);
+              /*time_delta_since_system_decision=*/kTimeDeltaInMinute);
 
       std::string user_override_histogram_name;
       if (is_switched) {
@@ -250,7 +250,7 @@
       }
 
       histogram_tester().ExpectTotalCount(user_override_histogram_name,
-                                          /*bucket_count=*/1);
+                                          /*expected_count=*/1);
       histogram_tester().ExpectTimeBucketCount(
           user_override_histogram_name,
           base::Minutes(kTimeDeltaInMinute) / base::Minutes(1).InMilliseconds(),
@@ -259,6 +259,87 @@
   }
 }
 
+// Tests that no audio selection metrics are fired when device set hasn't
+// changed.
+TEST_F(AudioDeviceMetricsHandlerTest,
+       NoAudioSelectionMetricsRecordedWhenNoDeviceChanges) {
+  AudioDevice input_USB = AudioDevice(NewInputNode("USB"));
+  AudioDevice input_BLUETOOTH = AudioDevice(NewInputNode("BLUETOOTH"));
+  AudioDeviceMap previous_devices_map;
+  previous_devices_map[input_USB.id] = input_USB;
+  previous_devices_map[input_BLUETOOTH.id] = input_BLUETOOTH;
+  AudioDeviceMap current_devices_map;
+  current_devices_map[input_USB.id] = input_USB;
+  current_devices_map[input_BLUETOOTH.id] = input_BLUETOOTH;
+  AudioDeviceList previous_devices = {input_USB, input_BLUETOOTH};
+  AudioDeviceList current_devices = {input_USB, input_BLUETOOTH};
+
+  for (const bool is_input : {true, false}) {
+    for (const bool is_switched : {true, false}) {
+      audio_device_metrics_handler().MaybeRecordSystemSwitchDecisionAndContext(
+          is_input, /*has_alternative_device=*/true, is_switched,
+          current_devices_map, previous_devices_map);
+
+      std::string system_switch_histogram_name =
+          is_input ? AudioDeviceMetricsHandler::kSystemSwitchInputAudio
+                   : AudioDeviceMetricsHandler::kSystemSwitchOutputAudio;
+      histogram_tester().ExpectTotalCount(system_switch_histogram_name, 0);
+
+      std::string device_count_histogram_name;
+      if (is_switched) {
+        device_count_histogram_name =
+            is_input
+                ? AudioDeviceMetricsHandler::kSystemSwitchInputAudioDeviceCount
+                : AudioDeviceMetricsHandler::
+                      kSystemSwitchOutputAudioDeviceCount;
+      } else {
+        device_count_histogram_name =
+            is_input ? AudioDeviceMetricsHandler::
+                           kSystemNotSwitchInputAudioDeviceCount
+                     : AudioDeviceMetricsHandler::
+                           kSystemNotSwitchOutputAudioDeviceCount;
+      }
+      histogram_tester().ExpectTotalCount(device_count_histogram_name,
+                                          /*expected_count=*/0);
+
+      std::string device_set_histogram_name;
+      if (is_switched) {
+        device_set_histogram_name =
+            is_input
+                ? AudioDeviceMetricsHandler::kSystemSwitchInputAudioDeviceSet
+                : AudioDeviceMetricsHandler::kSystemSwitchOutputAudioDeviceSet;
+      } else {
+        device_set_histogram_name =
+            is_input
+                ? AudioDeviceMetricsHandler::kSystemNotSwitchInputAudioDeviceSet
+                : AudioDeviceMetricsHandler::
+                      kSystemNotSwitchOutputAudioDeviceSet;
+      }
+
+      histogram_tester().ExpectTotalCount(device_set_histogram_name,
+                                          /*expected_count=*/0);
+
+      std::string before_and_after_device_set_histogram_name;
+      if (is_switched) {
+        before_and_after_device_set_histogram_name =
+            is_input ? AudioDeviceMetricsHandler::
+                           kSystemSwitchInputBeforeAndAfterAudioDeviceSet
+                     : AudioDeviceMetricsHandler::
+                           kSystemSwitchOutputBeforeAndAfterAudioDeviceSet;
+      } else {
+        before_and_after_device_set_histogram_name =
+            is_input ? AudioDeviceMetricsHandler::
+                           kSystemNotSwitchInputBeforeAndAfterAudioDeviceSet
+                     : AudioDeviceMetricsHandler::
+                           kSystemNotSwitchOutputBeforeAndAfterAudioDeviceSet;
+      }
+      histogram_tester().ExpectTotalCount(
+          before_and_after_device_set_histogram_name,
+          /*expected_count=*/0);
+    }
+  }
+}
+
 TEST_F(AudioDeviceMetricsHandlerTest, RecordConsecutiveAudioDevicsChange) {
   uint16_t expected_input_devices_changed_count = 0;
   uint16_t expected_output_devices_changed_count = 0;
@@ -312,8 +393,8 @@
         is_input ? AudioDeviceMetricsHandler::kConsecutiveInputDevicsChanged
                  : AudioDeviceMetricsHandler::kConsecutiveOutputDevicsChanged;
     histogram_tester().ExpectBucketCount(devices_changed_histogram_name,
-                                         /*bucket=*/kTimeDeltaInSecondA,
-                                         /*count=*/1);
+                                         /*sample=*/kTimeDeltaInSecondA,
+                                         /*expected_count=*/1);
 
     // Test consecutive devices addition.
     audio_device_metrics_handler()
@@ -329,8 +410,8 @@
         is_input ? AudioDeviceMetricsHandler::kConsecutiveInputDevicsAdded
                  : AudioDeviceMetricsHandler::kConsecutiveOutputDevicsAdded;
     histogram_tester().ExpectBucketCount(devices_added_histogram_name,
-                                         /*bucket=*/kTimeDeltaInSecondB,
-                                         /*count=*/1);
+                                         /*sample=*/kTimeDeltaInSecondB,
+                                         /*expected_count=*/1);
   }
 }
 
@@ -350,19 +431,19 @@
     // No histogram is recorded before firing.
     histogram_tester().ExpectBucketCount(
         AudioDeviceMetricsHandler::kAudioSelectionExceptionRuleMetrics,
-        /*bucket=*/rule,
-        /*count=*/0);
+        /*sample=*/rule,
+        /*expected_count=*/0);
 
     audio_device_metrics_handler().RecordExceptionRulesMet(rule);
 
     // Histogram is recorded after firing.
     histogram_tester().ExpectBucketCount(
         AudioDeviceMetricsHandler::kAudioSelectionExceptionRuleMetrics,
-        /*bucket=*/rule,
-        /*count=*/1);
+        /*sample=*/rule,
+        /*expected_count=*/1);
     histogram_tester().ExpectTotalCount(
         AudioDeviceMetricsHandler::kAudioSelectionExceptionRuleMetrics,
-        /*count=*/ruleInt + 1);
+        /*expected_count=*/ruleInt + 1);
   }
 }
 
diff --git a/chromeos/ash/components/dbus/hermes/hermes_profile_client.cc b/chromeos/ash/components/dbus/hermes/hermes_profile_client.cc
index a2ccb2b..fc9af3f9 100644
--- a/chromeos/ash/components/dbus/hermes/hermes_profile_client.cc
+++ b/chromeos/ash/components/dbus/hermes/hermes_profile_client.cc
@@ -23,9 +23,7 @@
 // hermes::profile::State enum.
 template <>
 Property<hermes::profile::State>::Property()
-    : value_(ash::features::IsSmdsSupportEnabled()
-                 ? hermes::profile::State::kPending
-                 : hermes::profile::State::kInactive) {}
+    : value_(hermes::profile::State::kPending) {}
 
 template <>
 bool Property<hermes::profile::State>::PopValueFromReader(
diff --git a/chromeos/ash/components/dbus/oobe_config/fake_oobe_configuration_client.cc b/chromeos/ash/components/dbus/oobe_config/fake_oobe_configuration_client.cc
index 495ddbb..307d5894 100644
--- a/chromeos/ash/components/dbus/oobe_config/fake_oobe_configuration_client.cc
+++ b/chromeos/ash/components/dbus/oobe_config/fake_oobe_configuration_client.cc
@@ -19,7 +19,7 @@
 
 namespace {
 
-const char kFlexToken[] = "flexToken";
+const char kEnrollmentToken[] = "enrollmentToken";
 
 std::string LoadConfigurationFile(base::FilePath path) {
   std::string configuration_data;
@@ -78,7 +78,7 @@
   if (!dict.has_value()) {
     return;
   }
-  dict->Remove(kFlexToken);
+  dict->Remove(kEnrollmentToken);
 
   std::optional<std::string> new_configuration = base::WriteJson(*dict);
   if (!new_configuration.has_value()) {
diff --git a/chromeos/ash/components/network/cellular_connection_handler.h b/chromeos/ash/components/network/cellular_connection_handler.h
index 8f0b4f4..1078cd3a 100644
--- a/chromeos/ash/components/network/cellular_connection_handler.h
+++ b/chromeos/ash/components/network/cellular_connection_handler.h
@@ -116,8 +116,6 @@
 
  private:
   friend class CellularESimInstallerTest;
-  friend class CellularESimInstallerLegacyTest;
-  friend class CellularPolicyHandlerLegacyTest;
   friend class CellularPolicyHandlerTest;
   friend class ManagedNetworkConfigurationHandlerTest;
   friend class cellular_setup::ESimTestBase;
diff --git a/chromeos/ash/components/network/cellular_esim_installer.cc b/chromeos/ash/components/network/cellular_esim_installer.cc
index 0bae5ab..9942d6d4 100644
--- a/chromeos/ash/components/network/cellular_esim_installer.cc
+++ b/chromeos/ash/components/network/cellular_esim_installer.cc
@@ -108,53 +108,11 @@
 }  // namespace
 
 // static
-void CellularESimInstaller::RecordInstallESimProfileResultLegacy(
-    InstallESimProfileResult result,
-    bool is_managed,
-    bool is_initial_install,
-    bool is_install_via_qr_code) {
-  // Log all installation results.
-  base::UmaHistogramEnumeration("Network.Cellular.ESim.InstallationResult",
-                                result);
-
-  // Log eSIM installation via policy.
-  if (is_managed) {
-    base::UmaHistogramEnumeration(
-        "Network.Cellular.ESim.Policy.ESimInstall.OperationResult", result);
-    if (is_initial_install) {
-      base::UmaHistogramEnumeration(
-          "Network.Cellular.ESim.Policy.ESimInstall.OperationResult."
-          "InitialAttempt",
-          result);
-      return;
-    }
-    base::UmaHistogramEnumeration(
-        "Network.Cellular.ESim.Policy.ESimInstall.OperationResult.Retry",
-        result);
-    return;
-  }
-
-  // Log eSIM installation by user.
-  base::UmaHistogramEnumeration(
-      "Network.Cellular.ESim.UserInstall.OperationResult.All", result);
-  if (is_install_via_qr_code) {
-    base::UmaHistogramEnumeration(
-        "Network.Cellular.ESim.UserInstall.OperationResult.ViaQrCode", result);
-  } else {
-    base::UmaHistogramEnumeration(
-        "Network.Cellular.ESim.UserInstall.OperationResult.ViaCodeInput",
-        result);
-  }
-}
-
-// static
 void CellularESimInstaller::RecordInstallESimProfileResult(
     std::optional<HermesResponseStatus> status,
     bool is_managed,
     bool is_initial_install,
     ProfileInstallMethod install_method) {
-  DCHECK(ash::features::IsSmdsSupportEnabled());
-
   const bool is_user_error =
       status.has_value() &&
       CellularNetworkMetricsLogger::HermesResponseStatusIsUserError(*status);
@@ -217,19 +175,9 @@
   if (!inhibit_lock) {
     NET_LOG(ERROR) << "Error inhibiting cellular device";
 
-    const bool is_managed = IsManagedNetwork(new_shill_properties);
-    const bool is_install_via_qr_code =
-        install_method == ProfileInstallMethod::kViaQrCodeAfterSmds ||
-        install_method == ProfileInstallMethod::kViaQrCodeSkippedSmds;
-
-    RecordInstallESimProfileResultLegacy(
-        InstallESimProfileResult::kInhibitFailed, is_managed,
-        is_initial_install, is_install_via_qr_code);
-    if (ash::features::IsSmdsSupportEnabled()) {
-      RecordInstallESimProfileResult(
-          /*status=*/std::nullopt, is_managed, is_initial_install,
-          install_method);
-    }
+    RecordInstallESimProfileResult(
+        /*status=*/std::nullopt, IsManagedNetwork(new_shill_properties),
+        is_initial_install, install_method);
 
     std::move(callback).Run(HermesResponseStatus::kErrorWrongState,
                             /*profile_path=*/std::nullopt,
@@ -264,22 +212,11 @@
   hermes_metrics::LogInstallViaQrCodeResult(status, dbusResult,
                                             is_initial_install);
 
-  bool is_managed = IsManagedNetwork(new_shill_properties);
-  const bool is_install_via_qr_code =
-      install_method == ProfileInstallMethod::kViaQrCodeAfterSmds ||
-      install_method == ProfileInstallMethod::kViaQrCodeSkippedSmds;
+  RecordInstallESimProfileResult(status, IsManagedNetwork(new_shill_properties),
+                                 is_initial_install, install_method);
 
   if (status != HermesResponseStatus::kSuccess) {
     NET_LOG(ERROR) << "Error Installing profile status=" << status;
-
-    RecordInstallESimProfileResultLegacy(
-        InstallESimProfileResult::kHermesInstallFailed, is_managed,
-        is_initial_install, is_install_via_qr_code);
-    if (ash::features::IsSmdsSupportEnabled()) {
-      RecordInstallESimProfileResult(status, is_managed, is_initial_install,
-                                     install_method);
-    }
-
     std::move(callback).Run(status, /*profile_path=*/std::nullopt,
                             /*service_path=*/std::nullopt);
     return;
@@ -289,14 +226,6 @@
       "Network.Cellular.ESim.ProfileDownload.ActivationCode.Latency",
       base::Time::Now() - installation_start_time);
 
-  RecordInstallESimProfileResultLegacy(InstallESimProfileResult::kSuccess,
-                                       is_managed, is_initial_install,
-                                       is_install_via_qr_code);
-  if (ash::features::IsSmdsSupportEnabled()) {
-    RecordInstallESimProfileResult(status, is_managed, is_initial_install,
-                                   install_method);
-  }
-
   pending_inhibit_locks_.emplace(*profile_path, std::move(inhibit_lock));
   ConfigureESimService(
       new_shill_properties, euicc_path, *profile_path,
@@ -427,19 +356,13 @@
   NET_LOG(ERROR) << "Error enabling newly created profile path="
                  << profile_path.value() << ", service path=" << service_path
                  << ", error_name=" << error_name;
-  if (ash::features::IsSmdsSupportEnabled()) {
-    // Propagate |profile_path| and |service_path| so that the code that
-    // initiated the installation can handle the case where the profile was
-    // successfully installed, but the installation process failed for some
-    // other reason e.g. failed to enable the profile.
-    std::move(callback).Run(HermesResponseStatus::kErrorWrongState,
-                            /*profile_path=*/profile_path,
-                            /*service_path=*/service_path);
-  } else {
-    std::move(callback).Run(HermesResponseStatus::kErrorWrongState,
-                            /*profile_path=*/std::nullopt,
-                            /*service_path=*/std::nullopt);
-  }
+  // Propagate |profile_path| and |service_path| so that the code that
+  // initiated the installation can handle the case where the profile was
+  // successfully installed, but the installation process failed for some
+  // other reason e.g. failed to enable the profile.
+  std::move(callback).Run(HermesResponseStatus::kErrorWrongState,
+                          /*profile_path=*/profile_path,
+                          /*service_path=*/service_path);
 }
 
 }  // namespace ash
diff --git a/chromeos/ash/components/network/cellular_esim_installer.h b/chromeos/ash/components/network/cellular_esim_installer.h
index abb11ae..6c67966 100644
--- a/chromeos/ash/components/network/cellular_esim_installer.h
+++ b/chromeos/ash/components/network/cellular_esim_installer.h
@@ -113,19 +113,6 @@
     kMaxValue = kHermesInstallFailed
   };
 
-  // TODO(b/281904820): Remove once SM-DS Support has fully launched and we have
-  // enough data to confirm that the new metrics are correct.
-  // Record the result of an attempt to install an eSIM profile either via a
-  // QR code or policy configuration. It also records to
-  // ESim.Policy.ESimInstall.Initial.OperationResult
-  // or ESim.Policy.ESimInstall.Retry.OperationResult histogram to indicate
-  // whether the policy eSIM profile installation is an initial attempt or not.
-  static void RecordInstallESimProfileResultLegacy(
-      InstallESimProfileResult result,
-      bool is_managed,
-      bool is_initial_install,
-      bool is_install_via_qr_code);
-
   // Record the result of an attempt to install an eSIM profile. This function
   // will emit to histograms that capture the method used and whether this is
   // the first installation attempt or not. When |status| is not provided this
diff --git a/chromeos/ash/components/network/cellular_esim_installer_unittest.cc b/chromeos/ash/components/network/cellular_esim_installer_unittest.cc
index 1d016b9e5..3c7b06c0 100644
--- a/chromeos/ash/components/network/cellular_esim_installer_unittest.cc
+++ b/chromeos/ash/components/network/cellular_esim_installer_unittest.cc
@@ -56,21 +56,6 @@
 const char kESimInstallNonUserErrorSuccessRate[] =
     "Network.Cellular.ESim.Installation.NonUserErrorSuccessRate";
 
-const char kUserInstallOperationHistogram[] =
-    "Network.Cellular.ESim.UserInstall.OperationResult.All";
-const char kUserInstallViaQrCodeOperationHistogram[] =
-    "Network.Cellular.ESim.UserInstall.OperationResult.ViaQrCode";
-const char kUserInstallViaCodeInputOperationHistogram[] =
-    "Network.Cellular.ESim.UserInstall.OperationResult.ViaCodeInput";
-
-const char kInstallViaPolicyOperationHistogram[] =
-    "Network.Cellular.ESim.Policy.ESimInstall.OperationResult";
-const char kInstallViaPolicyInitialOperationHistogram[] =
-    "Network.Cellular.ESim.Policy.ESimInstall.OperationResult.InitialAttempt";
-const char kInstallViaPolicyRetryOperationHistogram[] =
-    "Network.Cellular.ESim.Policy.ESimInstall.OperationResult.Retry";
-const char kInstallESimResultHistogram[] =
-    "Network.Cellular.ESim.InstallationResult";
 const char kESimProfileDownloadLatencyHistogram[] =
     "Network.Cellular.ESim.ProfileDownload.ActivationCode.Latency";
 
@@ -243,8 +228,6 @@
       CellularESimInstaller::InstallESimProfileResult expected_install_result) {
     histogram_tester()->ExpectBucketCount(
         kInstallViaQrCodeHistogram, expected_hermes_status, expected_count);
-    histogram_tester()->ExpectBucketCount(
-        kInstallESimResultHistogram, expected_install_result, expected_count);
 
     if (expected_hermes_status == HermesResponseStatus::kSuccess ||
         !base::Contains(kHermesUserErrorCodes, expected_hermes_status)) {
@@ -261,41 +244,6 @@
     }
   }
 
-  void CheckDetailedESimInstallHistograms(
-      CellularESimInstaller::InstallESimProfileResult expected_result,
-      bool is_managed = false,
-      bool is_retry = false,
-      bool is_install_via_qr_code = false) {
-    histogram_tester()->ExpectBucketCount(kUserInstallOperationHistogram,
-                                          expected_result,
-                                          /*expected_count=*/1);
-    histogram_tester()->ExpectBucketCount(
-        is_install_via_qr_code ? kUserInstallViaQrCodeOperationHistogram
-                               : kUserInstallViaCodeInputOperationHistogram,
-        expected_result,
-        /*expected_count=*/1);
-
-    int expected_policy_histogram_counts = 0;
-    int expected_policy_initial_counts = 0;
-    int expected_policy_retry_counts = 0;
-    if (is_managed) {
-      expected_policy_histogram_counts = 1;
-      if (is_retry) {
-        expected_policy_retry_counts = 1;
-      } else {
-        expected_policy_initial_counts = 1;
-      }
-    }
-    histogram_tester()->ExpectBucketCount(kInstallViaPolicyOperationHistogram,
-                                          expected_result,
-                                          expected_policy_histogram_counts);
-    histogram_tester()->ExpectBucketCount(
-        kInstallViaPolicyInitialOperationHistogram, expected_result,
-        expected_policy_initial_counts);
-    histogram_tester()->ExpectBucketCount(
-        kInstallViaPolicyRetryOperationHistogram, expected_result,
-        expected_policy_retry_counts);
-  }
 
   void FastForwardProfileRefreshDelay() {
     const base::TimeDelta kProfileRefreshCallbackDelay =
@@ -347,8 +295,6 @@
   CheckESimInstallHistograms(
       /*expected_count=*/1, HermesResponseStatus::kErrorInvalidActivationCode,
       CellularESimInstaller::InstallESimProfileResult::kHermesInstallFailed);
-  CheckDetailedESimInstallHistograms(
-      CellularESimInstaller::InstallESimProfileResult::kHermesInstallFailed);
 
   state.user_install_user_errors_included_all.hermes_failed_count++;
   state.user_install_user_errors_included_via_activation_code_after_smds
@@ -368,11 +314,6 @@
   CheckESimInstallHistograms(
       /*expected_count=*/2, HermesResponseStatus::kErrorInvalidActivationCode,
       CellularESimInstaller::InstallESimProfileResult::kHermesInstallFailed);
-  CheckDetailedESimInstallHistograms(
-      CellularESimInstaller::InstallESimProfileResult::kHermesInstallFailed,
-      /*is_managed=*/true);
-  histogram_tester()->ExpectTotalCount(kInstallViaPolicyRetryOperationHistogram,
-                                       0);
 
   state.policy_install_user_errors_included_all.hermes_failed_count++;
   state.policy_install_user_errors_included_smdp_initial.hermes_failed_count++;
@@ -425,8 +366,6 @@
   CheckESimInstallHistograms(
       /*expected_count=*/1, HermesResponseStatus::kSuccess,
       CellularESimInstaller::InstallESimProfileResult::kSuccess);
-  CheckDetailedESimInstallHistograms(
-      CellularESimInstaller::InstallESimProfileResult::kSuccess);
 
   state.user_install_user_errors_filtered_all.success_count++;
   state.user_install_user_errors_filtered_via_activation_code_after_smds
@@ -449,9 +388,6 @@
   CheckESimInstallHistograms(
       /*expected_count=*/2, HermesResponseStatus::kSuccess,
       CellularESimInstaller::InstallESimProfileResult::kSuccess);
-  CheckDetailedESimInstallHistograms(
-      CellularESimInstaller::InstallESimProfileResult::kSuccess,
-      /*is_managed=*/true);
 
   state.policy_install_user_errors_filtered_all.success_count++;
   state.policy_install_user_errors_filtered_smdp_initial.success_count++;
@@ -479,8 +415,6 @@
   CheckESimInstallHistograms(
       /*expected_count=*/1, HermesResponseStatus::kSuccess,
       CellularESimInstaller::InstallESimProfileResult::kSuccess);
-  CheckDetailedESimInstallHistograms(
-      CellularESimInstaller::InstallESimProfileResult::kSuccess);
 
   state.user_install_user_errors_filtered_all.success_count++;
   state.user_install_user_errors_filtered_via_activation_code_after_smds
@@ -506,9 +440,6 @@
   CheckESimInstallHistograms(
       /*expected_count=*/2, HermesResponseStatus::kSuccess,
       CellularESimInstaller::InstallESimProfileResult::kSuccess);
-  CheckDetailedESimInstallHistograms(
-      CellularESimInstaller::InstallESimProfileResult::kSuccess,
-      /*is_managed=*/true, /*is_retry=*/true);
 
   state.policy_install_user_errors_filtered_all.success_count++;
   state.policy_install_user_errors_filtered_smds_retry.success_count++;
@@ -538,10 +469,6 @@
   CheckESimInstallHistograms(
       /*expected_count=*/1, HermesResponseStatus::kSuccess,
       CellularESimInstaller::InstallESimProfileResult::kSuccess);
-  CheckDetailedESimInstallHistograms(
-      CellularESimInstaller::InstallESimProfileResult::kSuccess,
-      /*is_managed=*/false, /*is_retry=*/false,
-      /*is_install_via_qr_code=*/true);
 
   state.user_install_user_errors_filtered_all.success_count++;
   state.user_install_user_errors_filtered_via_qr_code_after_smds
@@ -574,10 +501,6 @@
   CheckESimInstallHistograms(
       /*expected_count=*/1, HermesResponseStatus::kSuccess,
       CellularESimInstaller::InstallESimProfileResult::kSuccess);
-  CheckDetailedESimInstallHistograms(
-      CellularESimInstaller::InstallESimProfileResult::kSuccess,
-      /*is_managed=*/false, /*is_retry=*/false,
-      /*is_install_via_qr_code=*/true);
 
   state.user_install_user_errors_filtered_all.success_count++;
   state.user_install_user_errors_filtered_via_qr_code_skipped_smds
diff --git a/chromeos/ash/components/network/cellular_esim_profile_handler.cc b/chromeos/ash/components/network/cellular_esim_profile_handler.cc
index c25d91d..2747413 100644
--- a/chromeos/ash/components/network/cellular_esim_profile_handler.cc
+++ b/chromeos/ash/components/network/cellular_esim_profile_handler.cc
@@ -74,8 +74,6 @@
 void CellularESimProfileHandler::RequestAvailableProfiles(
     const dbus::ObjectPath& euicc_path,
     RequestAvailableProfilesCallback callback) {
-  DCHECK(ash::features::IsSmdsSupportEnabled());
-
   std::unique_ptr<RequestAvailableProfilesInfo> info =
       std::make_unique<RequestAvailableProfilesInfo>();
   info->smds_activation_codes = cellular_utils::GetSmdsActivationCodes();
diff --git a/chromeos/ash/components/network/cellular_esim_profile_handler_impl.cc b/chromeos/ash/components/network/cellular_esim_profile_handler_impl.cc
index 90b1302f..d799751 100644
--- a/chromeos/ash/components/network/cellular_esim_profile_handler_impl.cc
+++ b/chromeos/ash/components/network/cellular_esim_profile_handler_impl.cc
@@ -249,17 +249,15 @@
   // Don't include pending profiles in the list to cache since we do not provide
   // a mechanism for installing a pending profile except through the dedicated
   // dialog which performs a fresh SM-DS scan each time it is opened.
-  if (ash::features::IsSmdsSupportEnabled()) {
-    std::erase_if(profiles_from_hermes, [](const CellularESimProfile& profile) {
-      if (profile.state() == CellularESimProfile::State::kPending) {
-        NET_LOG(DEBUG) << "Removing eSIM profile {iccid: " << profile.iccid()
-                       << ", eid: " << profile.eid()
-                       << "} from list to cache since it is pending";
-        return true;
-      }
-      return false;
-    });
-  }
+  std::erase_if(profiles_from_hermes, [](const CellularESimProfile& profile) {
+    if (profile.state() == CellularESimProfile::State::kPending) {
+      NET_LOG(DEBUG) << "Removing eSIM profile {iccid: " << profile.iccid()
+                     << ", eid: " << profile.eid()
+                     << "} from list to cache since it is pending";
+      return true;
+    }
+    return false;
+  });
 
   // Skip updating if there are profiles that haven't received ICCID updates
   // yet. This is required because property updates to eSIM profile objects
diff --git a/chromeos/ash/components/network/cellular_esim_uninstall_handler.cc b/chromeos/ash/components/network/cellular_esim_uninstall_handler.cc
index 5f0187b1..353768c 100644
--- a/chromeos/ash/components/network/cellular_esim_uninstall_handler.cc
+++ b/chromeos/ash/components/network/cellular_esim_uninstall_handler.cc
@@ -344,11 +344,7 @@
 
   if (managed_cellular_pref_handler_) {
     for (const auto& iccid : removed_iccids) {
-      if (ash::features::IsSmdsSupportEnabled()) {
-        managed_cellular_pref_handler_->RemoveESimMetadata(iccid);
-      } else {
-        managed_cellular_pref_handler_->RemovePairWithIccid(iccid);
-      }
+      managed_cellular_pref_handler_->RemoveESimMetadata(iccid);
     }
   }
   TransitionToUninstallState(UninstallState::kRemovingShillService);
diff --git a/chromeos/ash/components/network/cellular_metrics_logger.cc b/chromeos/ash/components/network/cellular_metrics_logger.cc
index 4ba08aa4..4b7fd7c 100644
--- a/chromeos/ash/components/network/cellular_metrics_logger.cc
+++ b/chromeos/ash/components/network/cellular_metrics_logger.cc
@@ -648,8 +648,7 @@
 
   esim_feature_usage_metrics_ =
       std::make_unique<ESimFeatureUsageMetrics>(network_state_handler_);
-  if (ash::features::IsSmdsSupportEnabled() &&
-      InstallAttributes::IsInitialized() &&
+  if (InstallAttributes::IsInitialized() &&
       InstallAttributes::Get()->IsEnterpriseManaged()) {
     enterprise_esim_feature_usage_metrics_ =
         std::make_unique<EnterpriseESimFeatureUsageMetrics>(
diff --git a/chromeos/ash/components/network/cellular_policy_handler.cc b/chromeos/ash/components/network/cellular_policy_handler.cc
index 6ae252f..0ec2255d 100644
--- a/chromeos/ash/components/network/cellular_policy_handler.cc
+++ b/chromeos/ash/components/network/cellular_policy_handler.cc
@@ -131,18 +131,7 @@
   network_state_handler_observer_.Observe(network_state_handler_.get());
 }
 
-void CellularPolicyHandler::InstallESim(const std::string& smdp_address,
-                                        const base::Value::Dict& onc_config) {
-  DCHECK(!ash::features::IsSmdsSupportEnabled());
-  PushRequestAndProcess(std::make_unique<InstallPolicyESimRequest>(
-      policy_util::SmdxActivationCode(
-          policy_util::SmdxActivationCode::Type::SMDP, smdp_address),
-      onc_config));
-}
-
 void CellularPolicyHandler::InstallESim(const base::Value::Dict& onc_config) {
-  DCHECK(ash::features::IsSmdsSupportEnabled());
-
   std::optional<policy_util::SmdxActivationCode> activation_code =
       policy_util::GetSmdxActivationCodeFromONC(onc_config);
 
@@ -339,40 +328,17 @@
     return;
   }
 
-  NET_LOG(EVENT) << "Installing policy eSIM profile: "
-                 << GetCurrentActivationCode().ToString();
+  NET_LOG(EVENT) << "Installing policy eSIM profile ("
+                 << GetCurrentActivationCode().ToString() << ") and inhibiting "
+                 << "cellular device to request available profiles for SM-DX "
+                    "activation code.";
 
-  if (ash::features::IsSmdsSupportEnabled()) {
-    NET_LOG(EVENT)
-        << "Inhibiting the cellular device to request available profiles for "
-        << "the policy eSIM profile SM-DX activation code";
-
-    // Confirmation codes are not required when installing policy eSIM profiles.
-    cellular_inhibitor_->InhibitCellularScanning(
-        CellularInhibitor::InhibitReason::kRequestingAvailableProfiles,
-        base::BindOnce(
-            &CellularPolicyHandler::OnInhibitedForRefreshSmdxProfiles,
-            weak_ptr_factory_.GetWeakPtr(), euicc_path,
-            std::move(new_shill_properties)));
-  } else {
-    const bool is_initial_install =
-        remaining_install_requests_.front()->retry_backoff.failure_count() == 0;
-    const bool is_smds =
-        remaining_install_requests_.front()->activation_code.type() ==
-        policy_util::SmdxActivationCode::Type::SMDS;
-
-    // Remote provisioning of eSIM profiles via SM-DP+ activation code in policy
-    // does not require confirmation code.
-    cellular_esim_installer_->InstallProfileFromActivationCode(
-        GetCurrentActivationCode().value(), /*confirmation_code=*/std::string(),
-        euicc_path, std::move(new_shill_properties),
-        base::BindOnce(
-            &CellularPolicyHandler::OnESimProfileInstallAttemptComplete,
-            weak_ptr_factory_.GetWeakPtr()),
-        is_initial_install,
-        is_smds ? ProfileInstallMethod::kViaSmds
-                : ProfileInstallMethod::kViaActivationCodeAfterSmds);
-  }
+  // Confirmation codes are not required when installing policy eSIM profiles.
+  cellular_inhibitor_->InhibitCellularScanning(
+      CellularInhibitor::InhibitReason::kRequestingAvailableProfiles,
+      base::BindOnce(&CellularPolicyHandler::OnInhibitedForRefreshSmdxProfiles,
+                     weak_ptr_factory_.GetWeakPtr(), euicc_path,
+                     std::move(new_shill_properties)));
 }
 
 void CellularPolicyHandler::OnConfigureESimService(
@@ -395,16 +361,11 @@
       policy_util::GetIccidFromONC(current_request->onc_config);
   DCHECK(iccid);
 
-  if (ash::features::IsSmdsSupportEnabled()) {
-    const std::string* name =
-        current_request->onc_config.FindString(::onc::network_config::kName);
-    DCHECK(name);
-    managed_cellular_pref_handler_->AddESimMetadata(
-        *iccid, *name, current_request->activation_code);
-  } else {
-    managed_cellular_pref_handler_->AddIccidSmdpPair(
-        *iccid, current_request->activation_code.value());
-  }
+  const std::string* name =
+      current_request->onc_config.FindString(::onc::network_config::kName);
+  DCHECK(name);
+  managed_cellular_pref_handler_->AddESimMetadata(
+      *iccid, *name, current_request->activation_code);
   ProcessRequests();
 }
 
@@ -565,8 +526,7 @@
   PopRequest();
 
   const bool has_error = status != HermesResponseStatus::kSuccess;
-  const bool was_installed =
-      profile_path.has_value() && ash::features::IsSmdsSupportEnabled();
+  const bool was_installed = profile_path.has_value();
 
   if (has_error && !was_installed) {
     if (!base::Contains(kHermesUserErrorCodes, status)) {
@@ -603,20 +563,13 @@
   HermesProfileClient::Properties* profile_properties =
       HermesProfileClient::Get()->GetProperties(*profile_path);
 
-  if (ash::features::IsSmdsSupportEnabled()) {
-    const std::string* name =
-        current_request->onc_config.FindString(::onc::network_config::kName);
-    DCHECK(name);
-    managed_cellular_pref_handler_->AddESimMetadata(
-        profile_properties->iccid().value(), *name,
-        current_request->activation_code,
-        /*sync_stub_networks=*/false);
-  } else {
-    managed_cellular_pref_handler_->AddIccidSmdpPair(
-        profile_properties->iccid().value(),
-        current_request->activation_code.value(),
-        /*sync_stub_networks=*/false);
-  }
+  const std::string* name =
+      current_request->onc_config.FindString(::onc::network_config::kName);
+  DCHECK(name);
+  managed_cellular_pref_handler_->AddESimMetadata(
+      profile_properties->iccid().value(), *name,
+      current_request->activation_code,
+      /*sync_stub_networks=*/false);
 
   managed_network_configuration_handler_->NotifyPolicyAppliedToNetwork(
       *service_path);
diff --git a/chromeos/ash/components/network/cellular_policy_handler.h b/chromeos/ash/components/network/cellular_policy_handler.h
index 4d4bd29..1335c6d 100644
--- a/chromeos/ash/components/network/cellular_policy_handler.h
+++ b/chromeos/ash/components/network/cellular_policy_handler.h
@@ -35,15 +35,6 @@
 class ManagedNetworkConfigurationHandler;
 enum class HermesResponseStatus;
 
-// TODO(b/281904820): Remove this old description.
-// Handles provisioning eSIM profiles via policy.
-//
-// When installing policy eSIM profiles, the activation code is constructed from
-// the SM-DP+ address in the policy configuration. Install requests are queued
-// and installation is performed one by one. Install attempts are retried for
-// fixed number of tries and the request queue doesn't get blocked by the
-// requests that are waiting for retry attempt.
-
 // This class encapsulates the logic for installing eSIM profiles configured by
 // policy. Installation requests are added to a queue, and each request will be
 // retried a fixed number of times with a retry delay between each attempt.
@@ -66,16 +57,6 @@
             ManagedNetworkConfigurationHandler*
                 managed_network_configuration_handler);
 
-  // TODO(b/281904820): Remove this function once SM-DS Support is launched.
-  // Installs an eSIM profile and connects to its network from policy with
-  // given |smdp_address|. The Shill service configuration will also be updated
-  // to the policy guid and the new ICCID after installation completes. If
-  // another eSIM profile is already under installation process, the current
-  // request will wait until the previous one is completed. Each installation
-  // will be retried for a fixed number of tries.
-  void InstallESim(const std::string& smdp_address,
-                   const base::Value::Dict& onc_config);
-
   // Installs the policy eSIM profile defined in |onc_config|. The Shill service
   // configuration will be updated to match the GUID provided by |onc_config|
   // and to include the ICCID of the installed profile. Installations are
diff --git a/chromeos/ash/components/network/cellular_policy_handler_unittest.cc b/chromeos/ash/components/network/cellular_policy_handler_unittest.cc
index c758be6..ae67c97 100644
--- a/chromeos/ash/components/network/cellular_policy_handler_unittest.cc
+++ b/chromeos/ash/components/network/cellular_policy_handler_unittest.cc
@@ -76,12 +76,6 @@
       "ICCID": "%s"
     })";
 
-const char kInstallViaPolicyOperationHistogram[] =
-    "Network.Cellular.ESim.Policy.ESimInstall.OperationResult";
-const char kInstallViaPolicyInitialOperationHistogram[] =
-    "Network.Cellular.ESim.Policy.ESimInstall.OperationResult.InitialAttempt";
-const char kInstallViaPolicyRetryOperationHistogram[] =
-    "Network.Cellular.ESim.Policy.ESimInstall.OperationResult.Retry";
 
 std::string GenerateCellularPolicy(
     const policy_util::SmdxActivationCode& activation_code,
@@ -329,24 +323,6 @@
   }
 
   void CheckHistogramState(const ExpectedHistogramState& state) {
-    CheckHistogram(
-        kInstallViaPolicyOperationHistogram,
-        /*success_count=*/state.success_initial_count +
-            state.success_retry_count,
-        /*inhibit_failed_count=*/state.inhibit_failed_initial_count +
-            state.inhibit_failed_retry_count,
-        /*hermes_install_failed=*/state.hermes_install_failed_initial_count +
-            state.hermes_install_failed_retry_count);
-    CheckHistogram(
-        kInstallViaPolicyInitialOperationHistogram,
-        /*success_count=*/state.success_initial_count,
-        /*inhibit_failed_count=*/state.inhibit_failed_initial_count,
-        /*hermes_install_failed=*/state.hermes_install_failed_initial_count);
-    CheckHistogram(
-        kInstallViaPolicyRetryOperationHistogram,
-        /*success_count=*/state.success_retry_count,
-        /*inhibit_failed_count=*/state.inhibit_failed_retry_count,
-        /*hermes_install_failed=*/state.hermes_install_failed_retry_count);
     histogram_tester_.ExpectTotalCount(
         CellularNetworkMetricsLogger::kSmdsScanProfileCount,
         /*expected_count=*/state.smds_scan_profile_total_count);
diff --git a/chromeos/ash/components/network/cellular_utils.cc b/chromeos/ash/components/network/cellular_utils.cc
index ebb5893..cd9a2f7 100644
--- a/chromeos/ash/components/network/cellular_utils.cc
+++ b/chromeos/ash/components/network/cellular_utils.cc
@@ -209,16 +209,16 @@
 std::vector<std::string> GetSmdsActivationCodes() {
   std::vector<std::string> activation_codes;
   if (features::ShouldUseStorkSmds()) {
-    activation_codes.push_back(kSmdsStork);
+    activation_codes.emplace_back(kSmdsStork);
   }
   if (features::ShouldUseAndroidStagingSmds()) {
-    activation_codes.push_back(kSmdsAndroidStaging);
+    activation_codes.emplace_back(kSmdsAndroidStaging);
   }
   if (activation_codes.empty()) {
-    if (features::IsSmdsSupportEnabled()) {
-      activation_codes.push_back(kSmdsAndroidProduction);
-    }
-    activation_codes.push_back(kSmdsGsma);
+    activation_codes = {
+        kSmdsAndroidProduction,
+        kSmdsGsma,
+    };
   }
   return activation_codes;
 }
diff --git a/chromeos/ash/components/network/managed_cellular_pref_handler.cc b/chromeos/ash/components/network/managed_cellular_pref_handler.cc
index 9d72813..1f385ba 100644
--- a/chromeos/ash/components/network/managed_cellular_pref_handler.cc
+++ b/chromeos/ash/components/network/managed_cellular_pref_handler.cc
@@ -36,21 +36,12 @@
 void ManagedCellularPrefHandler::SetDevicePrefs(PrefService* device_prefs) {
   device_prefs_ = device_prefs;
 
-  if (!device_prefs_) {
+  if (!device_prefs_ ||
+      device_prefs_->HasPrefPath(prefs::kManagedCellularESimMetadata)) {
     return;
   }
 
-  const bool hasPref =
-      device_prefs_->HasPrefPath(prefs::kManagedCellularESimMetadata);
-  if (!ash::features::IsSmdsSupportEnabled()) {
-    if (hasPref) {
-      device_prefs_->ClearPref(prefs::kManagedCellularESimMetadata);
-    }
-    return;
-  }
-  if (!hasPref) {
-    MigrateExistingPrefs();
-  }
+  MigrateExistingPrefs();
 }
 
 void ManagedCellularPrefHandler::AddObserver(Observer* observer) {
@@ -70,71 +61,11 @@
     observer.OnManagedCellularPrefChanged();
 }
 
-void ManagedCellularPrefHandler::AddIccidSmdpPair(
-    const std::string& iccid,
-    const std::string& smdp_address,
-    bool sync_stub_networks) {
-  DCHECK(!ash::features::IsSmdsSupportEnabled());
-
-  if (!device_prefs_) {
-    NET_LOG(ERROR) << "Device pref not available yet.";
-    return;
-  }
-  const std::string* existed_smdp_address = GetSmdpAddressFromIccid(iccid);
-  if (existed_smdp_address && *existed_smdp_address == smdp_address)
-    return;
-
-  NET_LOG(EVENT) << "Adding iccid smdp pair to device pref, iccid: " << iccid
-                 << ", smdp: " << smdp_address;
-  ScopedDictPrefUpdate update(device_prefs_,
-                              prefs::kManagedCellularIccidSmdpPair);
-  update->SetByDottedPath(iccid, smdp_address);
-  if (sync_stub_networks) {
-    network_state_handler_->SyncStubCellularNetworks();
-  }
-
-  NotifyManagedCellularPrefChanged();
-}
-
-void ManagedCellularPrefHandler::RemovePairWithIccid(const std::string& iccid) {
-  DCHECK(!ash::features::IsSmdsSupportEnabled());
-
-  if (!device_prefs_) {
-    NET_LOG(ERROR) << "Device pref not available yet.";
-    return;
-  }
-  const std::string* existed_smdp_address = GetSmdpAddressFromIccid(iccid);
-  if (!existed_smdp_address)
-    return;
-
-  NET_LOG(EVENT) << "Removing iccid smdp pair from device pref, iccid: "
-                 << iccid;
-  ScopedDictPrefUpdate update(device_prefs_,
-                              prefs::kManagedCellularIccidSmdpPair);
-  update->RemoveByDottedPath(iccid);
-  network_state_handler_->SyncStubCellularNetworks();
-  NotifyManagedCellularPrefChanged();
-}
-
-const std::string* ManagedCellularPrefHandler::GetSmdpAddressFromIccid(
-    const std::string& iccid) const {
-  DCHECK(!ash::features::IsSmdsSupportEnabled());
-
-  if (!device_prefs_) {
-    NET_LOG(ERROR) << "Device pref not available yet.";
-    return nullptr;
-  }
-  const base::Value::Dict& iccid_smdp_pairs =
-      device_prefs_->GetDict(prefs::kManagedCellularIccidSmdpPair);
-  return iccid_smdp_pairs.FindString(iccid);
-}
-
 void ManagedCellularPrefHandler::AddESimMetadata(
     const std::string& iccid,
     const std::string& name,
     const policy_util::SmdxActivationCode& activation_code,
     bool sync_stub_networks) {
-  DCHECK(ash::features::IsSmdsSupportEnabled());
   DCHECK(!name.empty());
   DCHECK(!activation_code.value().empty());
 
@@ -172,7 +103,6 @@
 
 const base::Value::Dict* ManagedCellularPrefHandler::GetESimMetadata(
     const std::string& iccid) {
-  DCHECK(ash::features::IsSmdsSupportEnabled());
 
   if (!device_prefs_) {
     NET_LOG(ERROR) << "Device pref not available yet";
@@ -184,8 +114,6 @@
 }
 
 void ManagedCellularPrefHandler::RemoveESimMetadata(const std::string& iccid) {
-  DCHECK(ash::features::IsSmdsSupportEnabled());
-
   if (!device_prefs_) {
     NET_LOG(ERROR) << "Device pref not available yet";
     return;
@@ -237,7 +165,6 @@
 }
 
 void ManagedCellularPrefHandler::MigrateExistingPrefs() {
-  DCHECK(ash::features::IsSmdsSupportEnabled());
   DCHECK(device_prefs_);
 
   NET_LOG(EVENT) << "Starting migration of existing ICCID and SM-DP+ pairs";
diff --git a/chromeos/ash/components/network/managed_cellular_pref_handler.h b/chromeos/ash/components/network/managed_cellular_pref_handler.h
index fd58a7b8..fcb46a2 100644
--- a/chromeos/ash/components/network/managed_cellular_pref_handler.h
+++ b/chromeos/ash/components/network/managed_cellular_pref_handler.h
@@ -42,17 +42,6 @@
   void Init(NetworkStateHandler* network_state_handler);
   void SetDevicePrefs(PrefService* device_prefs);
 
-  // Add a new ICCID and SMDP+ address pair to device pref for a managed
-  // cellular network. If |sync_stub_networks| is set true,
-  // NetworkStateHandler::SyncStubCellularNetworks() will be called.
-  void AddIccidSmdpPair(const std::string& iccid,
-                        const std::string& smdp_address,
-                        bool sync_stub_networks = true);
-
-  // Remove the ICCID and SMDP+ address pair from the device pref with given
-  // |iccid|.
-  void RemovePairWithIccid(const std::string& iccid);
-
   // Persistes the eSIM metadata for a managed cellular network to device prefs.
   // If |sync_stub_networks| is set true,
   // NetworkStateHandler::SyncStubCellularNetworks() will be called.
@@ -75,10 +64,6 @@
   // Return true if the |iccid| has been migrated to the APN Revamp feature.
   virtual bool ContainsApnMigratedIccid(const std::string& iccid) const;
 
-  // Returns the corresponding SMDP+ address for the given |iccid|. Returns
-  // nullptr if no such |iccid| is found.
-  const std::string* GetSmdpAddressFromIccid(const std::string& iccid) const;
-
   void AddObserver(Observer* observer);
   void RemoveObserver(Observer* observer);
   bool HasObserver(Observer* observer) const;
diff --git a/chromeos/ash/components/network/managed_cellular_pref_handler_unittest.cc b/chromeos/ash/components/network/managed_cellular_pref_handler_unittest.cc
index de0aac3..acf44bdb 100644
--- a/chromeos/ash/components/network/managed_cellular_pref_handler_unittest.cc
+++ b/chromeos/ash/components/network/managed_cellular_pref_handler_unittest.cc
@@ -73,19 +73,6 @@
         set_to_null ? nullptr : &device_prefs_);
   }
 
-  void AddIccidSmdpPair(const std::string& iccid,
-                        const std::string& smdp_address) {
-    managed_cellular_pref_handler_->AddIccidSmdpPair(iccid, smdp_address);
-  }
-
-  void RemovePairForIccid(const std::string& iccid) {
-    managed_cellular_pref_handler_->RemovePairWithIccid(iccid);
-  }
-
-  const std::string* GetSmdpAddressFromIccid(const std::string& iccid) {
-    return managed_cellular_pref_handler_->GetSmdpAddressFromIccid(iccid);
-  }
-
   void AddApnMigratedIccid(const std::string& iccid) {
     managed_cellular_pref_handler_->AddApnMigratedIccid(iccid);
   }
diff --git a/chromeos/ash/components/network/managed_network_configuration_handler_impl.cc b/chromeos/ash/components/network/managed_network_configuration_handler_impl.cc
index 24c1272..28b735e 100644
--- a/chromeos/ash/components/network/managed_network_configuration_handler_impl.cc
+++ b/chromeos/ash/components/network/managed_network_configuration_handler_impl.cc
@@ -794,23 +794,7 @@
     const base::Value::Dict* network_policy = policies->GetPolicyByGuid(guid);
     DCHECK(network_policy);
 
-    if (ash::features::IsSmdsSupportEnabled()) {
-      cellular_policy_handler_->InstallESim(*network_policy);
-    } else {
-      const std::string* smdp_address =
-          policy_util::GetSMDPAddressFromONC(*network_policy);
-      if (smdp_address) {
-        NET_LOG(EVENT)
-            << "Found ONC configuration with SMDP: " << *smdp_address << ". "
-            << "Start installing policy eSim profile with ONC config: "
-            << *network_policy;
-        cellular_policy_handler_->InstallESim(*smdp_address, *network_policy);
-      } else {
-        NET_LOG(EVENT) << "Skip installing policy eSIM either because the eSIM "
-                       << "policy feature is not enabled or the SMDP address "
-                       << "is missing from ONC.";
-      }
-    }
+    cellular_policy_handler_->InstallESim(*network_policy);
   }
 }
 
diff --git a/chromeos/ash/components/network/policy_applicator.cc b/chromeos/ash/components/network/policy_applicator.cc
index d4c4e3a1..5ee3153 100644
--- a/chromeos/ash/components/network/policy_applicator.cc
+++ b/chromeos/ash/components/network/policy_applicator.cc
@@ -265,23 +265,14 @@
     // the policy being applied we update the preferences that are used to track
     // eSIM profiles that have been installed for managed networks to match this
     // more recent policy application.
-    if (ash::features::IsSmdsSupportEnabled()) {
-      const std::string* name =
-          new_policy->FindString(::onc::network_config::kName);
-      std::optional<policy_util::SmdxActivationCode> activation_code =
-          policy_util::GetSmdxActivationCodeFromONC(*new_policy);
-      if (managed_cellular_pref_handler_ && iccid && name &&
-          activation_code.has_value()) {
-        managed_cellular_pref_handler_->AddESimMetadata(*iccid, *name,
-                                                        *activation_code);
-      }
-    } else {
-      const std::string* smdp_address =
-          policy_util::GetSMDPAddressFromONC(*new_policy);
-      if (was_managed && managed_cellular_pref_handler_ && iccid &&
-          smdp_address) {
-        managed_cellular_pref_handler_->AddIccidSmdpPair(*iccid, *smdp_address);
-      }
+    const std::string* name =
+        new_policy->FindString(::onc::network_config::kName);
+    std::optional<policy_util::SmdxActivationCode> activation_code =
+        policy_util::GetSmdxActivationCodeFromONC(*new_policy);
+    if (managed_cellular_pref_handler_ && iccid && name &&
+        activation_code.has_value()) {
+      managed_cellular_pref_handler_->AddESimMetadata(*iccid, *name,
+                                                      *activation_code);
     }
     return;
   }
@@ -297,11 +288,7 @@
 
     const std::string* iccid = policy_util::GetIccidFromONC(onc_part);
     if (managed_cellular_pref_handler_ && iccid) {
-      if (ash::features::IsSmdsSupportEnabled()) {
-        managed_cellular_pref_handler_->RemoveESimMetadata(*iccid);
-      } else {
-        managed_cellular_pref_handler_->RemovePairWithIccid(*iccid);
-      }
+      managed_cellular_pref_handler_->RemoveESimMetadata(*iccid);
     }
     return;
   }
diff --git a/chromeos/ash/components/network/stub_cellular_networks_provider.cc b/chromeos/ash/components/network/stub_cellular_networks_provider.cc
index 683232c..a7a9eda 100644
--- a/chromeos/ash/components/network/stub_cellular_networks_provider.cc
+++ b/chromeos/ash/components/network/stub_cellular_networks_provider.cc
@@ -172,13 +172,8 @@
 
     bool is_managed = false;
     if (managed_cellular_pref_handler_) {
-      if (ash::features::IsSmdsSupportEnabled()) {
-        is_managed = managed_cellular_pref_handler_->GetESimMetadata(
-                         iccid_eid_pair.first) != nullptr;
-      } else {
-        is_managed = managed_cellular_pref_handler_->GetSmdpAddressFromIccid(
-            iccid_eid_pair.first);
-      }
+      is_managed = managed_cellular_pref_handler_->GetESimMetadata(
+                       iccid_eid_pair.first) != nullptr;
     }
     NET_LOG(EVENT) << "Adding stub cellular network for ICCID="
                    << iccid_eid_pair.first << " EID=" << iccid_eid_pair.second
diff --git a/chromeos/ash/components/osauth/impl/BUILD.gn b/chromeos/ash/components/osauth/impl/BUILD.gn
index 1446f1d..ec88d9c5 100644
--- a/chromeos/ash/components/osauth/impl/BUILD.gn
+++ b/chromeos/ash/components/osauth/impl/BUILD.gn
@@ -80,5 +80,6 @@
     "auth_hub_mode_lifecycle_unittest.cc",
     "auth_hub_vector_lifecycle_unittest.cc",
     "auth_session_storage_impl_unittest.cc",
+    "cryptohome_core_impl_unittest.cc",
   ]
 }
diff --git a/chromeos/ash/components/osauth/impl/auth_session_storage_impl.cc b/chromeos/ash/components/osauth/impl/auth_session_storage_impl.cc
index dfb49ec..4131a1a8 100644
--- a/chromeos/ash/components/osauth/impl/auth_session_storage_impl.cc
+++ b/chromeos/ash/components/osauth/impl/auth_session_storage_impl.cc
@@ -104,7 +104,7 @@
 
 void AuthSessionStorageImpl::BorrowAsync(const base::Location& location,
                                          const AuthProofToken& token,
-                                         BorrowCallback callback) {
+                                         BorrowContextCallback callback) {
   auto data_it = tokens_.find(token);
   if (data_it == std::end(tokens_)) {
     LOG(ERROR) << "Accessing expired token";
@@ -190,7 +190,7 @@
   }
 
   if (!data_it->second->borrow_queue.empty()) {
-    std::pair<base::Location, BorrowCallback> pending_borrow =
+    std::pair<base::Location, BorrowContextCallback> pending_borrow =
         std::move(data_it->second->borrow_queue.front());
     data_it->second->borrow_queue.pop();
     BorrowAsync(pending_borrow.first, token, std::move(pending_borrow.second));
@@ -198,7 +198,7 @@
 }
 
 void AuthSessionStorageImpl::Withdraw(const AuthProofToken& token,
-                                      BorrowCallback callback) {
+                                      BorrowContextCallback callback) {
   auto data_it = tokens_.find(token);
   if (data_it == std::end(tokens_)) {
     LOG(ERROR) << "Accessing expired token";
@@ -228,7 +228,7 @@
   CHECK_EQ(data_it->second->state, TokenState::kBorrowed);
   // Drain borrow queue.
   while (!data_it->second->borrow_queue.empty()) {
-    std::pair<base::Location, BorrowCallback> pending_borrow =
+    std::pair<base::Location, BorrowContextCallback> pending_borrow =
         std::move(data_it->second->borrow_queue.front());
     data_it->second->borrow_queue.pop();
     std::move(pending_borrow.second).Run(nullptr);
@@ -255,7 +255,7 @@
   }
   // Drain borrow queue.
   while (!data_it->second->borrow_queue.empty()) {
-    std::pair<base::Location, BorrowCallback> pending_borrow =
+    std::pair<base::Location, BorrowContextCallback> pending_borrow =
         std::move(data_it->second->borrow_queue.front());
     data_it->second->borrow_queue.pop();
     std::move(pending_borrow.second).Run(nullptr);
diff --git a/chromeos/ash/components/osauth/impl/auth_session_storage_impl.h b/chromeos/ash/components/osauth/impl/auth_session_storage_impl.h
index edc76e26..8aa7332 100644
--- a/chromeos/ash/components/osauth/impl/auth_session_storage_impl.h
+++ b/chromeos/ash/components/osauth/impl/auth_session_storage_impl.h
@@ -58,11 +58,12 @@
       const AuthProofToken& token) override;
   void BorrowAsync(const base::Location& location,
                    const AuthProofToken& token,
-                   BorrowCallback callback) override;
+                   BorrowContextCallback callback) override;
   const UserContext* Peek(const AuthProofToken& token) override;
   void Return(const AuthProofToken& token,
               std::unique_ptr<UserContext> context) override;
-  void Withdraw(const AuthProofToken& token, BorrowCallback callback) override;
+  void Withdraw(const AuthProofToken& token,
+                BorrowContextCallback callback) override;
   void Invalidate(const AuthProofToken& token,
                   std::optional<InvalidationCallback> on_invalidated) override;
   std::unique_ptr<ScopedSessionRefresher> KeepAlive(
@@ -92,8 +93,8 @@
     bool invalidate_on_return = false;
 
     std::queue<InvalidationCallback> invalidation_queue;
-    std::optional<BorrowCallback> withdraw_callback;
-    std::queue<std::pair<base::Location, BorrowCallback>> borrow_queue;
+    std::optional<BorrowContextCallback> withdraw_callback;
+    std::queue<std::pair<base::Location, BorrowContextCallback>> borrow_queue;
 
     // Timer to perform next action (extending or invalidating session).
     std::unique_ptr<base::OneShotTimer> next_action_timer_;
diff --git a/chromeos/ash/components/osauth/impl/cryptohome_core_impl.cc b/chromeos/ash/components/osauth/impl/cryptohome_core_impl.cc
index 28002b7a..f43675b 100644
--- a/chromeos/ash/components/osauth/impl/cryptohome_core_impl.cc
+++ b/chromeos/ash/components/osauth/impl/cryptohome_core_impl.cc
@@ -12,6 +12,7 @@
 #include "base/check_op.h"
 #include "base/functional/bind.h"
 #include "base/logging.h"
+#include "base/task/sequenced_task_runner.h"
 #include "chromeos/ash/components/dbus/userdataauth/userdataauth_client.h"
 #include "chromeos/ash/components/login/auth/auth_performer.h"
 #include "chromeos/ash/components/login/auth/public/auth_session_intent.h"
@@ -208,14 +209,30 @@
   return AuthSessionStorage::Get()->Store(std::move(context_));
 }
 
-std::unique_ptr<UserContext> CryptohomeCoreImpl::BorrowContext() {
-  CHECK(context_);
-  return std::move(context_);
+void CryptohomeCoreImpl::BorrowContext(BorrowContextCallback callback) {
+  if (!context_) {
+    borrow_callback_queue_.emplace(std::move(callback));
+    return;
+  }
+  BorrowContextAndRun(std::move(callback));
+  return;
 }
 
 void CryptohomeCoreImpl::ReturnContext(std::unique_ptr<UserContext> context) {
   CHECK(!context_);
   context_ = std::move(context);
+  if (!borrow_callback_queue_.empty()) {
+    auto callback = std::move(borrow_callback_queue_.front());
+    borrow_callback_queue_.pop();
+    BorrowContextAndRun(std::move(callback));
+    return;
+  }
+}
+
+void CryptohomeCoreImpl::BorrowContextAndRun(BorrowContextCallback callback) {
+  CHECK(context_);
+  base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
+      FROM_HERE, base::BindOnce(std::move(callback), std::move(context_)));
 }
 
 }  // namespace ash
diff --git a/chromeos/ash/components/osauth/impl/cryptohome_core_impl.h b/chromeos/ash/components/osauth/impl/cryptohome_core_impl.h
index fdb7a91f..90c29bf 100644
--- a/chromeos/ash/components/osauth/impl/cryptohome_core_impl.h
+++ b/chromeos/ash/components/osauth/impl/cryptohome_core_impl.h
@@ -7,6 +7,7 @@
 
 #include <memory>
 #include <optional>
+#include <queue>
 
 #include "base/containers/flat_set.h"
 #include "base/memory/raw_ptr.h"
@@ -20,7 +21,8 @@
 class UserDataAuthClient;
 class UserContext;
 
-class CryptohomeCoreImpl : public CryptohomeCore {
+class COMPONENT_EXPORT(CHROMEOS_ASH_COMPONENTS_OSAUTH) CryptohomeCoreImpl
+    : public CryptohomeCore {
  public:
   explicit CryptohomeCoreImpl(UserDataAuthClient* client);
   ~CryptohomeCoreImpl() override;
@@ -30,8 +32,8 @@
                         Client* client) override;
   void EndAuthSession(Client* client) override;
   UserContext* GetCurrentContext() const override;
+  void BorrowContext(BorrowContextCallback callback) override;
   AuthPerformer* GetAuthPerformer() const override;
-  std::unique_ptr<UserContext> BorrowContext() override;
   void ReturnContext(std::unique_ptr<UserContext> context) override;
   AuthProofToken StoreAuthenticationContext() override;
 
@@ -50,10 +52,12 @@
   void OnInvalidateAuthSession(std::unique_ptr<UserContext> context,
                                std::optional<AuthenticationError> error);
   void EndAuthSessionImpl();
+  void BorrowContextAndRun(BorrowContextCallback callback);
 
   std::optional<AuthAttemptVector> current_attempt_;
   base::flat_set<raw_ptr<Client>> clients_;
   base::flat_set<raw_ptr<Client>> clients_being_removed_;
+  std::queue<BorrowContextCallback> borrow_callback_queue_;
 
   Stage current_stage_ = Stage::kIdle;
   bool auth_session_started_ = false;
diff --git a/chromeos/ash/components/osauth/impl/cryptohome_core_impl_unittest.cc b/chromeos/ash/components/osauth/impl/cryptohome_core_impl_unittest.cc
new file mode 100644
index 0000000..3514bba
--- /dev/null
+++ b/chromeos/ash/components/osauth/impl/cryptohome_core_impl_unittest.cc
@@ -0,0 +1,97 @@
+// Copyright 2024 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chromeos/ash/components/osauth/impl/cryptohome_core_impl.h"
+
+#include <memory>
+#include <utility>
+
+#include "base/functional/callback_helpers.h"
+#include "base/location.h"
+#include "base/run_loop.h"
+#include "base/test/task_environment.h"
+#include "base/test/test_future.h"
+#include "base/time/default_clock.h"
+#include "base/time/time.h"
+#include "chromeos/ash/components/dbus/userdataauth/mock_userdataauth_client.h"
+#include "chromeos/ash/components/login/auth/public/user_context.h"
+#include "chromeos/ash/components/osauth/public/common_types.h"
+#include "cryptohome_core_impl.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace ash {
+
+class CryptohomeCoreImplTest : public ::testing::Test {
+ protected:
+  CryptohomeCoreImplTest() {
+    core_ = std::make_unique<CryptohomeCoreImpl>(&mock_udac_);
+  }
+
+  std::unique_ptr<UserContext> CreateUserContext() {
+    std::unique_ptr<UserContext> context = std::make_unique<UserContext>();
+    context->SetAuthSessionIds("some-id", "broadcast");
+    context->SetSessionLifetime(base::Time::Now() + base::Seconds(60));
+    return context;
+  }
+
+  base::test::SingleThreadTaskEnvironment task_environment{
+      base::test::TaskEnvironment::TimeSource::MOCK_TIME};
+  ash::MockUserDataAuthClient mock_udac_;
+  std::unique_ptr<CryptohomeCoreImpl> core_;
+};
+
+TEST_F(CryptohomeCoreImplTest, GetCurrentContext) {
+  core_->ReturnContext(CreateUserContext());
+  ASSERT_NE(core_->GetCurrentContext(), nullptr);
+}
+
+TEST_F(CryptohomeCoreImplTest, BorrowAndReturn) {
+  core_->ReturnContext(CreateUserContext());
+
+  // Simple paired borrow and return.
+  base::test::TestFuture<std::unique_ptr<UserContext>> borrow_future;
+  core_->BorrowContext(borrow_future.GetCallback());
+  base::RunLoop().RunUntilIdle();
+  ASSERT_TRUE(borrow_future.IsReady());
+  ASSERT_NE(borrow_future.Get().get(), nullptr);
+  core_->ReturnContext(borrow_future.Take());
+
+  // Three borrows in a row.
+  // The first borrow will get the context.
+  base::test::TestFuture<std::unique_ptr<UserContext>> borrow_future_1;
+  core_->BorrowContext(borrow_future_1.GetCallback());
+
+  base::RunLoop().RunUntilIdle();
+  ASSERT_TRUE(borrow_future_1.IsReady());
+  ASSERT_NE(borrow_future_1.Get().get(), nullptr);
+
+  // Two more borrows.
+  base::test::TestFuture<std::unique_ptr<UserContext>> borrow_future_2;
+  base::test::TestFuture<std::unique_ptr<UserContext>> borrow_future_3;
+  core_->BorrowContext(borrow_future_2.GetCallback());
+  core_->BorrowContext(borrow_future_3.GetCallback());
+  base::RunLoop().RunUntilIdle();
+  ASSERT_FALSE(borrow_future_2.IsReady());
+  ASSERT_FALSE(borrow_future_3.IsReady());
+
+  // Return context, first in queue should get it
+  core_->ReturnContext(borrow_future_1.Take());
+  base::RunLoop().RunUntilIdle();
+  ASSERT_TRUE(borrow_future_2.IsReady());
+  ASSERT_NE(borrow_future_2.Get().get(), nullptr);
+  ASSERT_FALSE(borrow_future_3.IsReady());
+
+  // Return another context, next in queue should get it
+  core_->ReturnContext(borrow_future_2.Take());
+  base::RunLoop().RunUntilIdle();
+  ASSERT_TRUE(borrow_future_3.IsReady());
+  ASSERT_NE(borrow_future_3.Get().get(), nullptr);
+
+  // Return and check the context is restored.
+  core_->ReturnContext(borrow_future_3.Take());
+  ASSERT_NE(core_->GetCurrentContext(), nullptr);
+}
+
+}  // namespace ash
diff --git a/chromeos/ash/components/osauth/impl/engines/cryptohome_password_engine.cc b/chromeos/ash/components/osauth/impl/engines/cryptohome_password_engine.cc
index d1e56f9..084bf77 100644
--- a/chromeos/ash/components/osauth/impl/engines/cryptohome_password_engine.cc
+++ b/chromeos/ash/components/osauth/impl/engines/cryptohome_password_engine.cc
@@ -59,10 +59,9 @@
   }
   CHECK(get_ref().has_value());
   get_observer()->OnFactorAttempt(GetFactor());
-  get_core()->GetAuthPerformer()->AuthenticateWithPassword(
-      get_ref()->label().value(), raw_password, get_core()->BorrowContext(),
-      base::BindOnce(&CryptohomePasswordEngine::OnAuthAttempt,
-                     weak_factory_.GetWeakPtr()));
+  get_core()->BorrowContext(
+      base::BindOnce(&CryptohomePasswordEngine::PerformAuthenticationAttempt,
+                     weak_factory_.GetWeakPtr(), raw_password));
 }
 
 void CryptohomePasswordEngine::OnAuthAttempt(
@@ -73,6 +72,15 @@
                                         /* success= */ !error.has_value());
 }
 
+void CryptohomePasswordEngine::PerformAuthenticationAttempt(
+    const std::string& raw_password,
+    std::unique_ptr<UserContext> context) {
+  get_core()->GetAuthPerformer()->AuthenticateWithPassword(
+      get_ref()->label().value(), raw_password, std::move(context),
+      base::BindOnce(&CryptohomePasswordEngine::OnAuthAttempt,
+                     weak_factory_.GetWeakPtr()));
+}
+
 CryptohomePasswordEngineFactory::CryptohomePasswordEngineFactory() = default;
 
 CryptohomePasswordEngineFactory::~CryptohomePasswordEngineFactory() = default;
diff --git a/chromeos/ash/components/osauth/impl/engines/cryptohome_password_engine.h b/chromeos/ash/components/osauth/impl/engines/cryptohome_password_engine.h
index 97cfce6..affe0ce 100644
--- a/chromeos/ash/components/osauth/impl/engines/cryptohome_password_engine.h
+++ b/chromeos/ash/components/osauth/impl/engines/cryptohome_password_engine.h
@@ -38,6 +38,8 @@
  private:
   void OnAuthAttempt(std::unique_ptr<UserContext>,
                      std::optional<AuthenticationError>);
+  void PerformAuthenticationAttempt(const std::string& raw_password,
+                                    std::unique_ptr<UserContext> context);
 
   base::WeakPtrFactory<CryptohomePasswordEngine> weak_factory_{this};
 };
diff --git a/chromeos/ash/components/osauth/impl/engines/cryptohome_pin_engine.cc b/chromeos/ash/components/osauth/impl/engines/cryptohome_pin_engine.cc
index b25a04e..0476c794a 100644
--- a/chromeos/ash/components/osauth/impl/engines/cryptohome_pin_engine.cc
+++ b/chromeos/ash/components/osauth/impl/engines/cryptohome_pin_engine.cc
@@ -63,13 +63,10 @@
     return;
   }
   CHECK(get_ref().has_value());
-  const AccountId& account_id = get_core()->GetCurrentContext()->GetAccountId();
   get_observer()->OnFactorAttempt(GetFactor());
-  get_core()->GetAuthPerformer()->AuthenticateWithPin(
-      raw_pin, GetUserSalt(account_id, local_state_),
-      get_core()->BorrowContext(),
-      base::BindOnce(&CryptohomePinEngine::OnAuthAttempt,
-                     weak_factory_.GetWeakPtr()));
+  get_core()->BorrowContext(
+      base::BindOnce(&CryptohomePinEngine::PerformAuthenticationAttempt,
+                     weak_factory_.GetWeakPtr(), raw_pin));
 }
 
 void CryptohomePinEngine::OnAuthAttempt(
@@ -80,6 +77,16 @@
                                         /* success= */ !error.has_value());
 }
 
+void CryptohomePinEngine::PerformAuthenticationAttempt(
+    const std::string& raw_pin,
+    std::unique_ptr<UserContext> context) {
+  const AccountId& account_id = context->GetAccountId();
+  get_core()->GetAuthPerformer()->AuthenticateWithPin(
+      raw_pin, GetUserSalt(account_id, local_state_), std::move(context),
+      base::BindOnce(&CryptohomePinEngine::OnAuthAttempt,
+                     weak_factory_.GetWeakPtr()));
+}
+
 std::string CryptohomePinEngine::GetUserSalt(const AccountId& account_id,
                                              PrefService* local_state) const {
   user_manager::KnownUser known_user(local_state);
diff --git a/chromeos/ash/components/osauth/impl/engines/cryptohome_pin_engine.h b/chromeos/ash/components/osauth/impl/engines/cryptohome_pin_engine.h
index 7a67707..85c3581 100644
--- a/chromeos/ash/components/osauth/impl/engines/cryptohome_pin_engine.h
+++ b/chromeos/ash/components/osauth/impl/engines/cryptohome_pin_engine.h
@@ -42,6 +42,8 @@
  private:
   void OnAuthAttempt(std::unique_ptr<UserContext>,
                      std::optional<AuthenticationError>);
+  void PerformAuthenticationAttempt(const std::string& raw_pin,
+                                    std::unique_ptr<UserContext> context);
 
   std::string GetUserSalt(const AccountId& account_id,
                           PrefService* local_state) const;
diff --git a/chromeos/ash/components/osauth/public/auth_session_storage.h b/chromeos/ash/components/osauth/public/auth_session_storage.h
index 09ea1d9f..7356b94f 100644
--- a/chromeos/ash/components/osauth/public/auth_session_storage.h
+++ b/chromeos/ash/components/osauth/public/auth_session_storage.h
@@ -48,7 +48,6 @@
 // returned to storage as soon as operation completes.
 class COMPONENT_EXPORT(CHROMEOS_ASH_COMPONENTS_OSAUTH) AuthSessionStorage {
  public:
-  using BorrowCallback = base::OnceCallback<void(std::unique_ptr<UserContext>)>;
   using InvalidationCallback = base::OnceCallback<void(void)>;
 
   // TODO (b/271249180): Define an observer for notifications about token
@@ -81,7 +80,7 @@
   // the context would become invalid before it is returned.
   virtual void BorrowAsync(const base::Location& location,
                            const AuthProofToken& token,
-                           BorrowCallback callback) = 0;
+                           BorrowContextCallback callback) = 0;
 
   // Allows client to obtain UserContext without intent to return it back.
   // Takes precedence over Borrow requests, but not over invalidate
@@ -96,7 +95,7 @@
   // There can be only one Withdraw request at one time, requesting parallel
   // Withdraw request would result in crash.
   virtual void Withdraw(const AuthProofToken& token,
-                        BorrowCallback callback) = 0;
+                        BorrowContextCallback callback) = 0;
 
   // Allows to inspect stored UserContext. The reference is only valid within
   // same UI event, and should not be stored by caller.
diff --git a/chromeos/ash/components/osauth/public/common_types.h b/chromeos/ash/components/osauth/public/common_types.h
index 9e72c34..39a969d 100644
--- a/chromeos/ash/components/osauth/public/common_types.h
+++ b/chromeos/ash/components/osauth/public/common_types.h
@@ -9,10 +9,13 @@
 
 #include "base/component_export.h"
 #include "base/containers/enum_set.h"
+#include "base/functional/callback.h"
 #include "components/account_id/account_id.h"
 
 namespace ash {
 
+class UserContext;
+
 // This token represents authentication proof. It can be safely passed
 // between components, and can be used to obtain authenticated
 // 'UserContext' from `AuthSessionStorage` to perform authenticated
@@ -77,6 +80,9 @@
   bool operator==(const AuthAttemptVector&) const = default;
 };
 
+using BorrowContextCallback =
+    base::OnceCallback<void(std::unique_ptr<UserContext>)>;
+
 }  // namespace ash
 
 #endif  // CHROMEOS_ASH_COMPONENTS_OSAUTH_PUBLIC_COMMON_TYPES_H_
diff --git a/chromeos/ash/components/osauth/public/cryptohome_core.h b/chromeos/ash/components/osauth/public/cryptohome_core.h
index cca62bd..e242e7f 100644
--- a/chromeos/ash/components/osauth/public/cryptohome_core.h
+++ b/chromeos/ash/components/osauth/public/cryptohome_core.h
@@ -50,7 +50,15 @@
   virtual void EndAuthSession(Client* client) = 0;
   virtual AuthPerformer* GetAuthPerformer() const = 0;
   virtual UserContext* GetCurrentContext() const = 0;
-  virtual std::unique_ptr<UserContext> BorrowContext() = 0;
+
+  // Borrows the UserContext to perform Cryptohome actions in |callback|.
+  // If the UserContext has been borrowed, the callback will be queued
+  // to be called when the UserContext is returned.
+  virtual void BorrowContext(BorrowContextCallback callback) = 0;
+
+  // Returns the UserContext and if there are queued borrow callbacks,
+  // the first queued callback will take the returned context and
+  // get called.
   virtual void ReturnContext(std::unique_ptr<UserContext> context) = 0;
   virtual AuthProofToken StoreAuthenticationContext() = 0;
 };
diff --git a/chromeos/ash/components/quick_start/quick_start_metrics.cc b/chromeos/ash/components/quick_start/quick_start_metrics.cc
index 6d5425b..b593797 100644
--- a/chromeos/ash/components/quick_start/quick_start_metrics.cc
+++ b/chromeos/ash/components/quick_start/quick_start_metrics.cc
@@ -284,6 +284,8 @@
                                   failure_reason.value());
   }
   base::UmaHistogramBoolean(kWifiTransferResultHistogramName, succeeded);
+  metrics::structured::StructuredMetricsClient::Record(
+      cros_events::QuickStart_GetWifiCredentials().SetSuccess(succeeded));
 }
 
 // static
@@ -295,6 +297,12 @@
 }
 
 // static
+void QuickStartMetrics::RecordGaiaTransferStarted() {
+  metrics::structured::StructuredMetricsClient::Record(
+      cros_events::QuickStart_AccountTransferStarted());
+}
+
+// static
 void QuickStartMetrics::RecordGaiaTransferResult(
     bool succeeded,
     std::optional<GaiaTransferResultFailureReason> failure_reason) {
@@ -306,6 +314,8 @@
                                   failure_reason.value());
   }
   base::UmaHistogramBoolean(kGaiaTransferResultName, succeeded);
+  metrics::structured::StructuredMetricsClient::Record(
+      cros_events::QuickStart_AccountTransferComplete().SetSuccess(succeeded));
 }
 
 // static
@@ -327,14 +337,20 @@
 void QuickStartMetrics::RecordUpdateStarted(bool is_forced) {
   if (is_forced) {
     base::UmaHistogramBoolean(kForcedUpdateStartedHistogramName, true);
+    metrics::structured::StructuredMetricsClient::Record(
+        cros_events::QuickStart_InstallForcedUpdate());
   } else {
     base::UmaHistogramBoolean(kConsumerUpdateStartedHistogramName, true);
+    metrics::structured::StructuredMetricsClient::Record(
+        cros_events::QuickStart_InstallConsumerUpdate());
   }
 }
 
 // static
 void QuickStartMetrics::RecordConsumerUpdateCancelled() {
   base::UmaHistogramBoolean(kConsumerUpdateCancelledHistogramName, true);
+  metrics::structured::StructuredMetricsClient::Record(
+      cros_events::QuickStart_ConsumerUpdateCancelled());
 }
 
 // static
diff --git a/chromeos/ash/components/quick_start/quick_start_metrics.h b/chromeos/ash/components/quick_start/quick_start_metrics.h
index f9ff316..1b7a4bce 100644
--- a/chromeos/ash/components/quick_start/quick_start_metrics.h
+++ b/chromeos/ash/components/quick_start/quick_start_metrics.h
@@ -268,7 +268,7 @@
       bool succeeded,
       std::optional<WifiTransferResultFailureReason> failure_reason);
 
-  static void RecordGaiaTransferAttempted(bool attempted);
+  static void RecordGaiaTransferStarted();
 
   static void RecordCapturePortalEncountered(int32_t session_id);
 
diff --git a/chromeos/ash/components/sync_wifi/wifi_configuration_bridge.cc b/chromeos/ash/components/sync_wifi/wifi_configuration_bridge.cc
index 1074bf3..b6ad848 100644
--- a/chromeos/ash/components/sync_wifi/wifi_configuration_bridge.cc
+++ b/chromeos/ash/components/sync_wifi/wifi_configuration_bridge.cc
@@ -30,6 +30,7 @@
 #include "components/device_event_log/device_event_log.h"
 #include "components/prefs/pref_registry_simple.h"
 #include "components/prefs/pref_service.h"
+#include "components/sync/base/deletion_origin.h"
 #include "components/sync/model/entity_change.h"
 #include "components/sync/model/metadata_batch.h"
 #include "components/sync/model/metadata_change_list.h"
@@ -605,7 +606,8 @@
   std::unique_ptr<syncer::ModelTypeStore::WriteBatch> batch =
       store_->CreateWriteBatch();
   batch->DeleteData(storage_key);
-  change_processor()->Delete(storage_key, batch->GetMetadataChangeList());
+  change_processor()->Delete(storage_key, syncer::DeletionOrigin::Unspecified(),
+                             batch->GetMetadataChangeList());
   entries_.erase(storage_key);
   Commit(std::move(batch));
   NET_LOG(EVENT) << "Removed network from sync.";
diff --git a/chromeos/ash/components/tether/BUILD.gn b/chromeos/ash/components/tether/BUILD.gn
index 0913df06..59d7673 100644
--- a/chromeos/ash/components/tether/BUILD.gn
+++ b/chromeos/ash/components/tether/BUILD.gn
@@ -166,6 +166,8 @@
     "fake_disconnect_tethering_request_sender.h",
     "fake_gms_core_notifications_state_tracker.cc",
     "fake_gms_core_notifications_state_tracker.h",
+    "fake_host_connection.cc",
+    "fake_host_connection.h",
     "fake_host_scan_cache.cc",
     "fake_host_scan_cache.h",
     "fake_host_scan_scheduler.cc",
diff --git a/chromeos/ash/components/tether/asynchronous_shutdown_object_container.h b/chromeos/ash/components/tether/asynchronous_shutdown_object_container.h
index 6c94cb4a..b18a0b2 100644
--- a/chromeos/ash/components/tether/asynchronous_shutdown_object_container.h
+++ b/chromeos/ash/components/tether/asynchronous_shutdown_object_container.h
@@ -6,10 +6,9 @@
 #define CHROMEOS_ASH_COMPONENTS_TETHER_ASYNCHRONOUS_SHUTDOWN_OBJECT_CONTAINER_H_
 
 #include "base/functional/callback_forward.h"
+#include "chromeos/ash/components/tether/host_connection.h"
 
-namespace ash {
-
-namespace tether {
+namespace ash::tether {
 
 class TetherHostFetcher;
 class DisconnectTetheringRequestSender;
@@ -41,10 +40,9 @@
   disconnect_tethering_request_sender() = 0;
   virtual NetworkConfigurationRemover* network_configuration_remover() = 0;
   virtual WifiHotspotDisconnector* wifi_hotspot_disconnector() = 0;
+  virtual HostConnection::Factory* host_connection_factory() = 0;
 };
 
-}  // namespace tether
-
-}  // namespace ash
+}  // namespace ash::tether
 
 #endif  // CHROMEOS_ASH_COMPONENTS_TETHER_ASYNCHRONOUS_SHUTDOWN_OBJECT_CONTAINER_H_
diff --git a/chromeos/ash/components/tether/asynchronous_shutdown_object_container_impl.cc b/chromeos/ash/components/tether/asynchronous_shutdown_object_container_impl.cc
index 13140b08..495acef 100644
--- a/chromeos/ash/components/tether/asynchronous_shutdown_object_container_impl.cc
+++ b/chromeos/ash/components/tether/asynchronous_shutdown_object_container_impl.cc
@@ -7,13 +7,12 @@
 #include "base/memory/ptr_util.h"
 #include "chromeos/ash/components/tether/disconnect_tethering_request_sender_impl.h"
 #include "chromeos/ash/components/tether/network_configuration_remover.h"
+#include "chromeos/ash/components/tether/secure_channel_host_connection.h"
 #include "chromeos/ash/components/tether/wifi_hotspot_disconnector_impl.h"
 #include "chromeos/ash/services/device_sync/public/cpp/device_sync_client.h"
 #include "chromeos/ash/services/secure_channel/public/cpp/client/secure_channel_client.h"
 
-namespace ash {
-
-namespace tether {
+namespace ash::tether {
 
 // static
 AsynchronousShutdownObjectContainerImpl::Factory*
@@ -62,10 +61,13 @@
         NetworkConnectionHandler* network_connection_handler,
         PrefService* pref_service)
     : tether_host_fetcher_(tether_host_fetcher),
+      host_connection_factory_(base::WrapUnique<HostConnection::Factory>(
+          new SecureChannelHostConnection::Factory(device_sync_client,
+                                                   secure_channel_client,
+                                                   tether_host_fetcher))),
       disconnect_tethering_request_sender_(
           DisconnectTetheringRequestSenderImpl::Factory::Create(
-              device_sync_client,
-              secure_channel_client,
+              host_connection_factory_.get(),
               tether_host_fetcher_)),
       network_configuration_remover_(
           std::make_unique<NetworkConfigurationRemover>(
@@ -97,6 +99,11 @@
   return tether_host_fetcher_;
 }
 
+HostConnection::Factory*
+AsynchronousShutdownObjectContainerImpl::host_connection_factory() {
+  return host_connection_factory_.get();
+}
+
 DisconnectTetheringRequestSender*
 AsynchronousShutdownObjectContainerImpl::disconnect_tethering_request_sender() {
   return disconnect_tethering_request_sender_.get();
@@ -120,8 +127,9 @@
 void AsynchronousShutdownObjectContainerImpl::ShutdownIfPossible() {
   DCHECK(!shutdown_complete_callback_.is_null());
 
-  if (AreAsynchronousOperationsActive())
+  if (AreAsynchronousOperationsActive()) {
     return;
+  }
 
   disconnect_tethering_request_sender_->RemoveObserver(this);
 
@@ -146,6 +154,4 @@
       std::move(disconnect_tethering_request_sender);
 }
 
-}  // namespace tether
-
-}  // namespace ash
+}  // namespace ash::tether
diff --git a/chromeos/ash/components/tether/asynchronous_shutdown_object_container_impl.h b/chromeos/ash/components/tether/asynchronous_shutdown_object_container_impl.h
index 4d97664..4992ddc1 100644
--- a/chromeos/ash/components/tether/asynchronous_shutdown_object_container_impl.h
+++ b/chromeos/ash/components/tether/asynchronous_shutdown_object_container_impl.h
@@ -82,6 +82,7 @@
       override;
   NetworkConfigurationRemover* network_configuration_remover() override;
   WifiHotspotDisconnector* wifi_hotspot_disconnector() override;
+  HostConnection::Factory* host_connection_factory() override;
 
  protected:
   AsynchronousShutdownObjectContainerImpl(
@@ -106,6 +107,7 @@
                           disconnect_tethering_request_sender);
 
   raw_ptr<TetherHostFetcher> tether_host_fetcher_;
+  std::unique_ptr<HostConnection::Factory> host_connection_factory_;
   std::unique_ptr<DisconnectTetheringRequestSender>
       disconnect_tethering_request_sender_;
   std::unique_ptr<NetworkConfigurationRemover> network_configuration_remover_;
diff --git a/chromeos/ash/components/tether/connect_tethering_operation.cc b/chromeos/ash/components/tether/connect_tethering_operation.cc
index a0f9983..0acf08f 100644
--- a/chromeos/ash/components/tether/connect_tethering_operation.cc
+++ b/chromeos/ash/components/tether/connect_tethering_operation.cc
@@ -10,7 +10,6 @@
 #include "chromeos/ash/components/multidevice/logging/logging.h"
 #include "chromeos/ash/components/tether/message_wrapper.h"
 #include "chromeos/ash/components/tether/proto/tether.pb.h"
-#include "chromeos/ash/services/secure_channel/public/cpp/client/secure_channel_client.h"
 
 namespace ash::tether {
 
@@ -36,16 +35,15 @@
 std::unique_ptr<ConnectTetheringOperation>
 ConnectTetheringOperation::Factory::Create(
     const TetherHost& tether_host,
-    device_sync::DeviceSyncClient* device_sync_client,
-    secure_channel::SecureChannelClient* secure_channel_client,
+    raw_ptr<HostConnection::Factory> host_connection_factory,
     bool setup_required) {
   if (factory_instance_) {
     return factory_instance_->CreateInstance(
-        tether_host, device_sync_client, secure_channel_client, setup_required);
+        tether_host, host_connection_factory, setup_required);
   }
 
   return base::WrapUnique(new ConnectTetheringOperation(
-      tether_host, device_sync_client, secure_channel_client, setup_required));
+      tether_host, host_connection_factory, setup_required));
 }
 
 // static
@@ -58,13 +56,12 @@
 
 ConnectTetheringOperation::ConnectTetheringOperation(
     const TetherHost& tether_host,
-    device_sync::DeviceSyncClient* device_sync_client,
-    secure_channel::SecureChannelClient* secure_channel_client,
+    raw_ptr<HostConnection::Factory> host_connection_factory,
     bool setup_required)
-    : MessageTransferOperation(tether_host,
-                               secure_channel::ConnectionPriority::kHigh,
-                               device_sync_client,
-                               secure_channel_client),
+    : MessageTransferOperation(
+          tether_host,
+          HostConnection::Factory::ConnectionPriority::kHigh,
+          host_connection_factory),
       clock_(base::DefaultClock::GetInstance()),
       setup_required_(setup_required),
       error_code_to_return_(HostResponseErrorCode::NO_RESPONSE) {}
@@ -81,8 +78,10 @@
 
 void ConnectTetheringOperation::OnDeviceAuthenticated() {
   connect_tethering_request_start_time_ = clock_->Now();
-  connect_message_sequence_number_ = SendMessageToDevice(
-      std::make_unique<MessageWrapper>(ConnectTetheringRequest()));
+  SendMessage(std::make_unique<MessageWrapper>(ConnectTetheringRequest()),
+              base::BindOnce(
+                  &ConnectTetheringOperation::NotifyConnectTetheringRequestSent,
+                  weak_ptr_factory_.GetWeakPtr()));
 }
 
 void ConnectTetheringOperation::OnMessageReceived(
@@ -135,7 +134,7 @@
       "InstantTethering.Performance.ConnectTetheringResponseDuration",
       clock_->Now() - connect_tethering_request_start_time_);
 
-  // Now that a response has been received, the operation may finish.
+  // Now that a response has been received, the device can be unregistered.
   StopOperation();
 }
 
@@ -155,14 +154,6 @@
   return MessageType::CONNECT_TETHERING_REQUEST;
 }
 
-void ConnectTetheringOperation::OnMessageSent(int sequence_number) {
-  if (sequence_number != connect_message_sequence_number_) {
-    return;
-  }
-
-  NotifyConnectTetheringRequestSent();
-}
-
 void ConnectTetheringOperation::NotifyConnectTetheringRequestSent() {
   for (auto& observer : observer_list_) {
     observer.OnConnectTetheringRequestSent();
diff --git a/chromeos/ash/components/tether/connect_tethering_operation.h b/chromeos/ash/components/tether/connect_tethering_operation.h
index 5e95c6aa..f0008cff 100644
--- a/chromeos/ash/components/tether/connect_tethering_operation.h
+++ b/chromeos/ash/components/tether/connect_tethering_operation.h
@@ -17,14 +17,6 @@
 #include "base/time/time.h"
 #include "chromeos/ash/components/tether/message_transfer_operation.h"
 
-namespace ash::device_sync {
-class DeviceSyncClient;
-}
-
-namespace ash::secure_channel {
-class SecureChannelClient;
-}
-
 namespace ash::tether {
 
 class MessageWrapper;
@@ -57,8 +49,7 @@
    public:
     static std::unique_ptr<ConnectTetheringOperation> Create(
         const TetherHost& tether_host,
-        device_sync::DeviceSyncClient* device_sync_client,
-        secure_channel::SecureChannelClient* secure_channel_client,
+        raw_ptr<HostConnection::Factory> host_connection_factory,
         bool setup_required);
 
     static void SetFactoryForTesting(Factory* factory);
@@ -67,8 +58,7 @@
     virtual ~Factory();
     virtual std::unique_ptr<ConnectTetheringOperation> CreateInstance(
         const TetherHost& tether_host,
-        device_sync::DeviceSyncClient* device_sync_client,
-        secure_channel::SecureChannelClient* secure_channel_client,
+        raw_ptr<HostConnection::Factory> host_connection_factory,
         bool setup_required) = 0;
 
    private:
@@ -97,8 +87,7 @@
  protected:
   ConnectTetheringOperation(
       const TetherHost& tether_host,
-      device_sync::DeviceSyncClient* device_sync_client,
-      secure_channel::SecureChannelClient* secure_channel_client,
+      raw_ptr<HostConnection::Factory> host_connection_factory,
       bool setup_required);
 
   // MessageTransferOperation:
@@ -107,7 +96,6 @@
       std::unique_ptr<MessageWrapper> message_wrapper) override;
   void OnOperationFinished() override;
   MessageType GetMessageTypeForConnection() override;
-  void OnMessageSent(int sequence_number) override;
   uint32_t GetMessageTimeoutSeconds() override;
 
   void NotifyConnectTetheringRequestSent();
@@ -146,7 +134,6 @@
   static const uint32_t kSetupRequiredResponseTimeoutSeconds;
 
   raw_ptr<base::Clock> clock_;
-  int connect_message_sequence_number_ = -1;
   bool setup_required_;
 
   // These values are saved in OnMessageReceived() and returned in
@@ -157,6 +144,8 @@
   base::Time connect_tethering_request_start_time_;
 
   base::ObserverList<Observer>::Unchecked observer_list_;
+
+  base::WeakPtrFactory<ConnectTetheringOperation> weak_ptr_factory_{this};
 };
 
 }  // namespace ash::tether
diff --git a/chromeos/ash/components/tether/connect_tethering_operation_unittest.cc b/chromeos/ash/components/tether/connect_tethering_operation_unittest.cc
index 94158eb3..5384ed06 100644
--- a/chromeos/ash/components/tether/connect_tethering_operation_unittest.cc
+++ b/chromeos/ash/components/tether/connect_tethering_operation_unittest.cc
@@ -16,13 +16,10 @@
 #include "base/time/time.h"
 #include "base/timer/mock_timer.h"
 #include "chromeos/ash/components/multidevice/remote_device_test_util.h"
+#include "chromeos/ash/components/tether/fake_host_connection.h"
 #include "chromeos/ash/components/tether/message_wrapper.h"
 #include "chromeos/ash/components/tether/proto/tether.pb.h"
 #include "chromeos/ash/components/tether/proto_test_util.h"
-#include "chromeos/ash/services/device_sync/public/cpp/fake_device_sync_client.h"
-#include "chromeos/ash/services/secure_channel/public/cpp/client/fake_client_channel.h"
-#include "chromeos/ash/services/secure_channel/public/cpp/client/fake_connection_attempt.h"
-#include "chromeos/ash/services/secure_channel/public/cpp/client/fake_secure_channel_client.h"
 #include "components/cross_device/timer_factory/fake_timer_factory.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
@@ -60,69 +57,29 @@
 class ConnectTetheringOperationTest : public testing::Test {
  protected:
   ConnectTetheringOperationTest()
-      : test_local_device_(multidevice::RemoteDeviceRefBuilder()
-                               .SetPublicKey("local device")
-                               .Build()),
-        remote_device_(multidevice::CreateRemoteDeviceRefForTest()) {}
+      : tether_host_(TetherHost(multidevice::CreateRemoteDeviceRefForTest())) {}
 
   void SetUp() override {
-    fake_device_sync_client_ =
-        std::make_unique<device_sync::FakeDeviceSyncClient>();
-    fake_device_sync_client_->set_local_device_metadata(test_local_device_);
-    fake_secure_channel_client_ =
-        std::make_unique<secure_channel::FakeSecureChannelClient>();
+    fake_host_connection_factory_ =
+        std::make_unique<FakeHostConnection::Factory>();
 
-    operation_ = ConstructOperation();
-    operation_->Initialize();
+    operation_ = base::WrapUnique(new ConnectTetheringOperation(
+        tether_host_, fake_host_connection_factory_.get(),
+        /*setup_required=*/false));
 
-    ConnectAuthenticatedChannelForDevice(remote_device_);
-  }
-
-  std::unique_ptr<ConnectTetheringOperation> ConstructOperation() {
-    std::unique_ptr<ConnectTetheringOperation> operation;
-    auto fake_connection_attempt =
-        std::make_unique<secure_channel::FakeConnectionAttempt>();
-    remote_device_to_fake_connection_attempt_map_[remote_device_] =
-        fake_connection_attempt.get();
-    fake_secure_channel_client_->set_next_listen_connection_attempt(
-        remote_device_, test_local_device_, std::move(fake_connection_attempt));
-
-    operation = base::WrapUnique(new ConnectTetheringOperation(
-        TetherHost(remote_device_), fake_device_sync_client_.get(),
-        fake_secure_channel_client_.get(), false /* setup_required */));
-    operation->SetTimerFactoryForTest(
+    operation_->SetTimerFactoryForTest(
         std::make_unique<cross_device::FakeTimerFactory>());
-    operation->AddObserver(&mock_observer_);
+    operation_->AddObserver(&mock_observer_);
 
     test_clock_.SetNow(base::Time::UnixEpoch());
-    operation->SetClockForTest(&test_clock_);
+    operation_->SetClockForTest(&test_clock_);
 
-    return operation;
+    fake_host_connection_factory_->SetupConnectionAttempt(tether_host_);
   }
 
-  void ConnectAuthenticatedChannelForDevice(
-      multidevice::RemoteDeviceRef remote_device) {
-    auto fake_client_channel =
-        std::make_unique<secure_channel::FakeClientChannel>();
-    remote_device_to_fake_client_channel_map_[remote_device] =
-        fake_client_channel.get();
-    remote_device_to_fake_connection_attempt_map_[remote_device]
-        ->NotifyConnection(std::move(fake_client_channel));
-  }
+  const TetherHost tether_host_;
 
-  const multidevice::RemoteDeviceRef test_local_device_;
-  const multidevice::RemoteDeviceRef remote_device_;
-
-  base::flat_map<multidevice::RemoteDeviceRef,
-                 secure_channel::FakeConnectionAttempt*>
-      remote_device_to_fake_connection_attempt_map_;
-  base::flat_map<multidevice::RemoteDeviceRef,
-                 secure_channel::FakeClientChannel*>
-      remote_device_to_fake_client_channel_map_;
-
-  std::unique_ptr<device_sync::FakeDeviceSyncClient> fake_device_sync_client_;
-  std::unique_ptr<secure_channel::FakeSecureChannelClient>
-      fake_secure_channel_client_;
+  std::unique_ptr<FakeHostConnection::Factory> fake_host_connection_factory_;
   base::SimpleTestClock test_clock_;
   MockOperationObserver mock_observer_;
   base::HistogramTester histogram_tester_;
@@ -137,6 +94,8 @@
   EXPECT_CALL(mock_observer_,
               OnSuccessfulConnectTetheringResponse(kTestSsid, kTestPassword));
 
+  operation_->Initialize();
+
   // Advance the clock in order to verify a non-zero response duration is
   // recorded and verified (below).
   test_clock_.Advance(kConnectTetheringResponseTimeSeconds);
@@ -169,6 +128,7 @@
                   ConnectTetheringOperation::HostResponseErrorCode::
                       INVALID_HOTSPOT_CREDENTIALS));
 
+  operation_->Initialize();
   // The ConnectTetheringResponse message does not contain the required SSID and
   // password fields.
   ConnectTetheringResponse response;
@@ -187,6 +147,7 @@
       OnConnectTetheringFailure(
           ConnectTetheringOperation::HostResponseErrorCode::UNKNOWN_ERROR));
 
+  operation_->Initialize();
   ConnectTetheringResponse response;
   response.set_response_code(
       ConnectTetheringResponse_ResponseCode::
@@ -204,6 +165,7 @@
                   ConnectTetheringOperation::HostResponseErrorCode::
                       PROVISIONING_FAILED));
 
+  operation_->Initialize();
   ConnectTetheringResponse response;
   response.set_response_code(
       ConnectTetheringResponse_ResponseCode::
@@ -221,6 +183,7 @@
                   ConnectTetheringOperation::HostResponseErrorCode::
                       INVALID_WIFI_AP_CONFIG));
 
+  operation_->Initialize();
   ConnectTetheringResponse response;
   response.set_response_code(
       ConnectTetheringResponse_ResponseCode::
@@ -238,6 +201,7 @@
                   ConnectTetheringOperation::HostResponseErrorCode::
                       INVALID_ACTIVE_EXISTING_SOFT_AP_CONFIG));
 
+  operation_->Initialize();
   ConnectTetheringResponse response;
   response.set_response_code(
       ConnectTetheringResponse_ResponseCode::
@@ -255,6 +219,7 @@
                   ConnectTetheringOperation::HostResponseErrorCode::
                       INVALID_NEW_SOFT_AP_CONFIG));
 
+  operation_->Initialize();
   ConnectTetheringResponse response;
   response.set_response_code(
       ConnectTetheringResponse_ResponseCode::
@@ -264,29 +229,22 @@
   operation_->OnMessageReceived(std::move(message));
 }
 
-// Tests that observers are notified when the connection request is sent.
-TEST_F(ConnectTetheringOperationTest, NotifyConnectTetheringRequest) {
-  EXPECT_CALL(mock_observer_, OnConnectTetheringRequestSent());
-
-  operation_->OnMessageSent(0 /* sequence_number */);
-}
-
 // Tests that the message timeout value varies based on whether setup is
 // required or not.
 TEST_F(ConnectTetheringOperationTest, GetMessageTimeoutSeconds) {
   // Setup required case.
   std::unique_ptr<ConnectTetheringOperation> operation(
-      new ConnectTetheringOperation(
-          TetherHost(remote_device_), fake_device_sync_client_.get(),
-          fake_secure_channel_client_.get(), true /* setup_required */));
+      new ConnectTetheringOperation(tether_host_,
+                                    fake_host_connection_factory_.get(),
+                                    true /* setup_required */));
 
   EXPECT_EQ(ConnectTetheringOperation::kSetupRequiredResponseTimeoutSeconds,
             operation->GetMessageTimeoutSeconds());
 
   // Setup not required case.
   operation.reset(new ConnectTetheringOperation(
-      TetherHost(remote_device_), fake_device_sync_client_.get(),
-      fake_secure_channel_client_.get(), false /* setup_required */));
+      tether_host_, fake_host_connection_factory_.get(),
+      false /* setup_required */));
 
   EXPECT_EQ(ConnectTetheringOperation::kSetupNotRequiredResponseTimeoutSeconds,
             operation->GetMessageTimeoutSeconds());
@@ -295,29 +253,19 @@
 // Tests that the ConnectTetheringRequest message is sent to the remote device
 // once the communication channel is connected and authenticated.
 TEST_F(ConnectTetheringOperationTest, ConnectRequestSentOnceAuthenticated) {
-  std::unique_ptr<ConnectTetheringOperation> operation = ConstructOperation();
-  operation->Initialize();
-
-  // Create the client channel to the remote device.
-  auto fake_client_channel =
-      std::make_unique<secure_channel::FakeClientChannel>();
-  remote_device_to_fake_client_channel_map_[remote_device_] =
-      fake_client_channel.get();
-
-  // No requests as a result of creating the client channel.
-  auto& sent_messages = fake_client_channel->sent_messages();
-  EXPECT_EQ(0u, sent_messages.size());
+  auto* fake_host_connection =
+      fake_host_connection_factory_->SetupConnectionAttempt(tether_host_);
 
   // Connect and authenticate the client channel.
-  remote_device_to_fake_connection_attempt_map_[remote_device_]
-      ->NotifyConnection(std::move(fake_client_channel));
+  operation_->Initialize();
 
   // Verify the ConnectTetheringRequest message is sent.
   auto message_wrapper =
       std::make_unique<MessageWrapper>(ConnectTetheringRequest());
+  auto& sent_messages = fake_host_connection->sent_messages();
   std::string expected_payload = message_wrapper->ToRawMessage();
   EXPECT_EQ(1u, sent_messages.size());
-  EXPECT_EQ(expected_payload, sent_messages[0].first);
+  EXPECT_EQ(expected_payload, sent_messages[0].first->ToRawMessage());
 }
 
 }  // namespace ash::tether
diff --git a/chromeos/ash/components/tether/disconnect_tethering_operation.cc b/chromeos/ash/components/tether/disconnect_tethering_operation.cc
index 69a4959d..d9ace1b 100644
--- a/chromeos/ash/components/tether/disconnect_tethering_operation.cc
+++ b/chromeos/ash/components/tether/disconnect_tethering_operation.cc
@@ -12,7 +12,6 @@
 #include "chromeos/ash/components/multidevice/logging/logging.h"
 #include "chromeos/ash/components/tether/message_wrapper.h"
 #include "chromeos/ash/components/tether/proto/tether.pb.h"
-#include "chromeos/ash/services/secure_channel/public/cpp/client/secure_channel_client.h"
 
 namespace ash::tether {
 
@@ -24,15 +23,14 @@
 std::unique_ptr<DisconnectTetheringOperation>
 DisconnectTetheringOperation::Factory::Create(
     const TetherHost& tether_host,
-    device_sync::DeviceSyncClient* device_sync_client,
-    secure_channel::SecureChannelClient* secure_channel_client) {
+    raw_ptr<HostConnection::Factory> host_connection_factory) {
   if (factory_instance_) {
-    return factory_instance_->CreateInstance(tether_host, device_sync_client,
-                                             secure_channel_client);
+    return factory_instance_->CreateInstance(tether_host,
+                                             host_connection_factory);
   }
 
-  return base::WrapUnique(new DisconnectTetheringOperation(
-      tether_host, device_sync_client, secure_channel_client));
+  return base::WrapUnique(
+      new DisconnectTetheringOperation(tether_host, host_connection_factory));
 }
 
 // static
@@ -45,12 +43,11 @@
 
 DisconnectTetheringOperation::DisconnectTetheringOperation(
     const TetherHost& tether_host,
-    device_sync::DeviceSyncClient* device_sync_client,
-    secure_channel::SecureChannelClient* secure_channel_client)
-    : MessageTransferOperation(tether_host,
-                               secure_channel::ConnectionPriority::kHigh,
-                               device_sync_client,
-                               secure_channel_client),
+    raw_ptr<HostConnection::Factory> host_connection_factory)
+    : MessageTransferOperation(
+          tether_host,
+          HostConnection::Factory::ConnectionPriority::kHigh,
+          host_connection_factory),
       has_sent_message_(false),
       clock_(base::DefaultClock::GetInstance()) {}
 
@@ -73,8 +70,9 @@
 }
 
 void DisconnectTetheringOperation::OnDeviceAuthenticated() {
-  disconnect_message_sequence_number_ = SendMessageToDevice(
-      std::make_unique<MessageWrapper>(DisconnectTetheringRequest()));
+  SendMessage(std::make_unique<MessageWrapper>(DisconnectTetheringRequest()),
+              base::BindOnce(&DisconnectTetheringOperation::OnMessageSent,
+                             weak_ptr_factory_.GetWeakPtr()));
   disconnect_start_time_ = clock_->Now();
 }
 
@@ -86,11 +84,7 @@
   return MessageType::DISCONNECT_TETHERING_REQUEST;
 }
 
-void DisconnectTetheringOperation::OnMessageSent(int sequence_number) {
-  if (sequence_number != disconnect_message_sequence_number_) {
-    return;
-  }
-
+void DisconnectTetheringOperation::OnMessageSent() {
   has_sent_message_ = true;
 
   DCHECK(!disconnect_start_time_.is_null());
diff --git a/chromeos/ash/components/tether/disconnect_tethering_operation.h b/chromeos/ash/components/tether/disconnect_tethering_operation.h
index 97636fb..f0df7c00 100644
--- a/chromeos/ash/components/tether/disconnect_tethering_operation.h
+++ b/chromeos/ash/components/tether/disconnect_tethering_operation.h
@@ -12,14 +12,6 @@
 #include "base/time/time.h"
 #include "chromeos/ash/components/tether/message_transfer_operation.h"
 
-namespace ash::device_sync {
-class DeviceSyncClient;
-}
-
-namespace ash::secure_channel {
-class SecureChannelClient;
-}
-
 namespace ash::tether {
 
 // Operation which sends a disconnect message to a tether host.
@@ -29,8 +21,7 @@
    public:
     static std::unique_ptr<DisconnectTetheringOperation> Create(
         const TetherHost& tether_host,
-        device_sync::DeviceSyncClient* device_sync_client,
-        secure_channel::SecureChannelClient* secure_channel_client);
+        raw_ptr<HostConnection::Factory> host_connection_factory);
 
     static void SetFactoryForTesting(Factory* factory);
 
@@ -38,8 +29,7 @@
     virtual ~Factory();
     virtual std::unique_ptr<DisconnectTetheringOperation> CreateInstance(
         const TetherHost& tether_host,
-        device_sync::DeviceSyncClient* device_sync_client,
-        secure_channel::SecureChannelClient* secure_channel_client) = 0;
+        raw_ptr<HostConnection::Factory> host_connection_factory) = 0;
 
    private:
     static Factory* factory_instance_;
@@ -66,8 +56,7 @@
  protected:
   DisconnectTetheringOperation(
       const TetherHost& tether_host,
-      device_sync::DeviceSyncClient* device_sync_client,
-      secure_channel::SecureChannelClient* secure_channel_client);
+      raw_ptr<HostConnection::Factory> host_connection_factory);
 
   void NotifyObserversOperationFinished(bool success);
 
@@ -75,7 +64,8 @@
   void OnDeviceAuthenticated() override;
   void OnOperationFinished() override;
   MessageType GetMessageTypeForConnection() override;
-  void OnMessageSent(int sequence_number) override;
+
+  void OnMessageSent();
 
  private:
   friend class DisconnectTetheringOperationTest;
@@ -87,11 +77,11 @@
   void SetClockForTest(base::Clock* clock_for_test);
 
   base::ObserverList<Observer>::Unchecked observer_list_;
-  int disconnect_message_sequence_number_ = -1;
   bool has_sent_message_;
 
   raw_ptr<base::Clock> clock_;
   base::Time disconnect_start_time_;
+  base::WeakPtrFactory<DisconnectTetheringOperation> weak_ptr_factory_{this};
 };
 
 }  // namespace ash::tether
diff --git a/chromeos/ash/components/tether/disconnect_tethering_operation_unittest.cc b/chromeos/ash/components/tether/disconnect_tethering_operation_unittest.cc
index d5e1ae46..da37265 100644
--- a/chromeos/ash/components/tether/disconnect_tethering_operation_unittest.cc
+++ b/chromeos/ash/components/tether/disconnect_tethering_operation_unittest.cc
@@ -15,12 +15,9 @@
 #include "base/time/time.h"
 #include "base/timer/mock_timer.h"
 #include "chromeos/ash/components/multidevice/remote_device_test_util.h"
+#include "chromeos/ash/components/tether/fake_host_connection.h"
 #include "chromeos/ash/components/tether/message_wrapper.h"
 #include "chromeos/ash/components/tether/proto/tether.pb.h"
-#include "chromeos/ash/services/device_sync/public/cpp/fake_device_sync_client.h"
-#include "chromeos/ash/services/secure_channel/public/cpp/client/fake_client_channel.h"
-#include "chromeos/ash/services/secure_channel/public/cpp/client/fake_connection_attempt.h"
-#include "chromeos/ash/services/secure_channel/public/cpp/client/fake_secure_channel_client.h"
 #include "components/cross_device/timer_factory/fake_timer_factory.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
@@ -56,62 +53,26 @@
 
  protected:
   DisconnectTetheringOperationTest()
-      : local_device_(multidevice::RemoteDeviceRefBuilder()
-                          .SetPublicKey("local device")
-                          .Build()),
-        remote_device_(multidevice::CreateRemoteDeviceRefListForTest(1)[0]) {}
+      : tether_host_(TetherHost(multidevice::CreateRemoteDeviceRefForTest())) {}
 
   void SetUp() override {
-    fake_device_sync_client_ =
-        std::make_unique<device_sync::FakeDeviceSyncClient>();
-    fake_device_sync_client_->set_local_device_metadata(local_device_);
-    fake_secure_channel_client_ =
-        std::make_unique<secure_channel::FakeSecureChannelClient>();
+    fake_host_connection_factory_ =
+        std::make_unique<FakeHostConnection::Factory>();
 
-    operation_ = ConstructOperation();
-    operation_->Initialize();
+    operation_ = base::WrapUnique(new DisconnectTetheringOperation(
+        tether_host_, fake_host_connection_factory_.get()));
+    operation_->AddObserver(&mock_observer_);
 
-    ConnectAuthenticatedChannelForDevice(remote_device_);
-  }
-
-  std::unique_ptr<DisconnectTetheringOperation> ConstructOperation() {
-    auto connection_attempt =
-        std::make_unique<secure_channel::FakeConnectionAttempt>();
-    connection_attempt_ = connection_attempt.get();
-    // remote_device_to_fake_connection_attempt_map_[remote_device_] =
-    //     fake_connection_attempt.get();
-    fake_secure_channel_client_->set_next_listen_connection_attempt(
-        remote_device_, local_device_, std::move(connection_attempt));
-
-    auto operation = base::WrapUnique(new DisconnectTetheringOperation(
-        TetherHost(remote_device_), fake_device_sync_client_.get(),
-        fake_secure_channel_client_.get()));
-    operation->AddObserver(&mock_observer_);
-
-    operation->SetTimerFactoryForTest(
+    operation_->SetTimerFactoryForTest(
         std::make_unique<cross_device::FakeTimerFactory>());
 
     test_clock_.SetNow(base::Time::UnixEpoch());
-    operation->SetClockForTest(&test_clock_);
-
-    return operation;
+    operation_->SetClockForTest(&test_clock_);
   }
 
-  void ConnectAuthenticatedChannelForDevice(
-      multidevice::RemoteDeviceRef remote_device) {
-    auto fake_client_channel =
-        std::make_unique<secure_channel::FakeClientChannel>();
-    connection_attempt_->NotifyConnection(std::move(fake_client_channel));
-  }
+  const TetherHost tether_host_;
 
-  const multidevice::RemoteDeviceRef local_device_;
-  const multidevice::RemoteDeviceRef remote_device_;
-
-  raw_ptr<secure_channel::FakeConnectionAttempt, DanglingUntriaged>
-      connection_attempt_;
-  std::unique_ptr<device_sync::FakeDeviceSyncClient> fake_device_sync_client_;
-  std::unique_ptr<secure_channel::FakeSecureChannelClient>
-      fake_secure_channel_client_;
+  std::unique_ptr<FakeHostConnection::Factory> fake_host_connection_factory_;
 
   base::SimpleTestClock test_clock_;
   MockOperationObserver mock_observer_;
@@ -121,16 +82,23 @@
 };
 
 TEST_F(DisconnectTetheringOperationTest, TestSuccess) {
-  // Verify that the Observer is called with success and the correct parameters.
-  EXPECT_CALL(mock_observer_, OnOperationFinished(remote_device_.GetDeviceId(),
+  // Setup the connection.
+  auto* host_connection =
+      fake_host_connection_factory_->SetupConnectionAttempt(tether_host_);
+
+  // Verify that the Observer is called with success and the correct
+  // parameters.
+  EXPECT_CALL(mock_observer_, OnOperationFinished(tether_host_.GetDeviceId(),
                                                   true /* successful */));
 
+  // Initialize the operation.
+  operation_->Initialize();
+
   // Advance the clock in order to verify a non-zero request duration is
   // recorded and verified (below).
   test_clock_.Advance(kDisconnectTetheringRequestTime);
 
-  // Execute the operation.
-  operation_->OnMessageSent(0 /* sequence_number */);
+  host_connection->FinishSendingMessages();
 
   // Verify the request duration metric is recorded.
   histogram_tester_.ExpectTimeBucketCount(
@@ -140,12 +108,18 @@
 
 TEST_F(DisconnectTetheringOperationTest, TestFailure) {
   // Verify that the observer is called with failure and the correct parameters.
-  EXPECT_CALL(mock_observer_, OnOperationFinished(remote_device_.GetDeviceId(),
+  EXPECT_CALL(mock_observer_, OnOperationFinished(tether_host_.GetDeviceId(),
                                                   false /* successful */));
 
-  // Finalize the operation; no message has been sent so this represents a
-  // failure case.
-  operation_->StopOperation();
+  // Setup a connection to be returned.
+  FakeHostConnection* fake_host_connection =
+      fake_host_connection_factory_->SetupConnectionAttempt(tether_host_);
+
+  // Start the operation.
+  operation_->Initialize();
+
+  // Disconnect before allowing messages to send.
+  fake_host_connection->Close();
 
   histogram_tester_.ExpectTotalCount(
       "InstantTethering.Performance.DisconnectTetheringRequestDuration", 0);
@@ -155,27 +129,20 @@
 // once the communication channel is connected and authenticated.
 TEST_F(DisconnectTetheringOperationTest,
        DisconnectRequestSentOnceAuthenticated) {
-  std::unique_ptr<DisconnectTetheringOperation> operation =
-      ConstructOperation();
-  operation->Initialize();
+  // Setup the connection.
+  auto* fake_host_connection =
+      fake_host_connection_factory_->SetupConnectionAttempt(tether_host_);
 
-  // Create the client channel for the remote device.
-  auto fake_client_channel =
-      std::make_unique<secure_channel::FakeClientChannel>();
-
-  // No requests as a result of creating the client channel.
-  auto& sent_messages = fake_client_channel->sent_messages();
-  EXPECT_EQ(0u, sent_messages.size());
-
-  // Connect and authenticate the client channel.
-  connection_attempt_->NotifyConnection(std::move(fake_client_channel));
+  // Initialize the operation.
+  operation_->Initialize();
 
   // Verify the DisconnectTetheringRequest message is sent.
   auto message_wrapper =
       std::make_unique<MessageWrapper>(DisconnectTetheringRequest());
   std::string expected_payload = message_wrapper->ToRawMessage();
+  auto& sent_messages = fake_host_connection->sent_messages();
   EXPECT_EQ(1u, sent_messages.size());
-  EXPECT_EQ(expected_payload, sent_messages[0].first);
+  EXPECT_EQ(expected_payload, sent_messages[0].first->ToRawMessage());
 }
 
 }  // namespace ash::tether
diff --git a/chromeos/ash/components/tether/disconnect_tethering_request_sender_impl.cc b/chromeos/ash/components/tether/disconnect_tethering_request_sender_impl.cc
index e4795f8..4cbb891 100644
--- a/chromeos/ash/components/tether/disconnect_tethering_request_sender_impl.cc
+++ b/chromeos/ash/components/tether/disconnect_tethering_request_sender_impl.cc
@@ -11,7 +11,6 @@
 #include "base/memory/ptr_util.h"
 #include "chromeos/ash/components/multidevice/logging/logging.h"
 #include "chromeos/ash/components/tether/tether_host_fetcher.h"
-#include "chromeos/ash/services/secure_channel/public/cpp/client/secure_channel_client.h"
 
 namespace ash::tether {
 
@@ -22,16 +21,15 @@
 // static
 std::unique_ptr<DisconnectTetheringRequestSender>
 DisconnectTetheringRequestSenderImpl::Factory::Create(
-    device_sync::DeviceSyncClient* device_sync_client,
-    secure_channel::SecureChannelClient* secure_channel_client,
+    raw_ptr<HostConnection::Factory> host_connection_factory,
     TetherHostFetcher* tether_host_fetcher) {
   if (factory_instance_) {
-    return factory_instance_->CreateInstance(
-        device_sync_client, secure_channel_client, tether_host_fetcher);
+    return factory_instance_->CreateInstance(host_connection_factory,
+                                             tether_host_fetcher);
   }
 
   return base::WrapUnique(new DisconnectTetheringRequestSenderImpl(
-      device_sync_client, secure_channel_client, tether_host_fetcher));
+      host_connection_factory, tether_host_fetcher));
 }
 
 // static
@@ -43,11 +41,9 @@
 DisconnectTetheringRequestSenderImpl::Factory::~Factory() = default;
 
 DisconnectTetheringRequestSenderImpl::DisconnectTetheringRequestSenderImpl(
-    device_sync::DeviceSyncClient* device_sync_client,
-    secure_channel::SecureChannelClient* secure_channel_client,
+    raw_ptr<HostConnection::Factory> host_connection_factory,
     TetherHostFetcher* tether_host_fetcher)
-    : device_sync_client_(device_sync_client),
-      secure_channel_client_(secure_channel_client),
+    : host_connection_factory_(host_connection_factory),
       tether_host_fetcher_(tether_host_fetcher) {}
 
 DisconnectTetheringRequestSenderImpl::~DisconnectTetheringRequestSenderImpl() {
@@ -80,8 +76,7 @@
 
   std::unique_ptr<DisconnectTetheringOperation> disconnect_tethering_operation =
       DisconnectTetheringOperation::Factory::Create(TetherHost(*tether_host),
-                                                    device_sync_client_,
-                                                    secure_channel_client_);
+                                                    host_connection_factory_);
 
   // Add to the map.
   device_id_to_operation_map_.emplace(
diff --git a/chromeos/ash/components/tether/disconnect_tethering_request_sender_impl.h b/chromeos/ash/components/tether/disconnect_tethering_request_sender_impl.h
index 9196293..74f003e 100644
--- a/chromeos/ash/components/tether/disconnect_tethering_request_sender_impl.h
+++ b/chromeos/ash/components/tether/disconnect_tethering_request_sender_impl.h
@@ -11,14 +11,7 @@
 #include "base/memory/raw_ptr.h"
 #include "chromeos/ash/components/tether/disconnect_tethering_operation.h"
 #include "chromeos/ash/components/tether/disconnect_tethering_request_sender.h"
-
-namespace ash::device_sync {
-class DeviceSyncClient;
-}
-
-namespace ash::secure_channel {
-class SecureChannelClient;
-}
+#include "chromeos/ash/components/tether/host_connection.h"
 
 namespace ash::tether {
 
@@ -31,8 +24,7 @@
   class Factory {
    public:
     static std::unique_ptr<DisconnectTetheringRequestSender> Create(
-        device_sync::DeviceSyncClient* device_sync_client,
-        secure_channel::SecureChannelClient* secure_channel_client,
+        raw_ptr<HostConnection::Factory> host_connection_factory,
         TetherHostFetcher* tether_host_fetcher);
 
     static void SetFactoryForTesting(Factory* factory);
@@ -40,8 +32,7 @@
    protected:
     virtual ~Factory();
     virtual std::unique_ptr<DisconnectTetheringRequestSender> CreateInstance(
-        device_sync::DeviceSyncClient* device_sync_client,
-        secure_channel::SecureChannelClient* secure_channel_client,
+        raw_ptr<HostConnection::Factory> host_connection_factory,
         TetherHostFetcher* tether_host_fetcher) = 0;
 
    private:
@@ -64,13 +55,11 @@
 
  protected:
   DisconnectTetheringRequestSenderImpl(
-      device_sync::DeviceSyncClient* device_sync_client,
-      secure_channel::SecureChannelClient* secure_channel_client,
+      raw_ptr<HostConnection::Factory> host_connection_factory,
       TetherHostFetcher* tether_host_fetcher);
 
  private:
-  raw_ptr<device_sync::DeviceSyncClient> device_sync_client_;
-  raw_ptr<secure_channel::SecureChannelClient> secure_channel_client_;
+  raw_ptr<HostConnection::Factory> host_connection_factory_;
   raw_ptr<TetherHostFetcher> tether_host_fetcher_;
 
   std::map<std::string, std::unique_ptr<DisconnectTetheringOperation>>
diff --git a/chromeos/ash/components/tether/disconnect_tethering_request_sender_impl_unittest.cc b/chromeos/ash/components/tether/disconnect_tethering_request_sender_impl_unittest.cc
index 2a53d961..f88f462 100644
--- a/chromeos/ash/components/tether/disconnect_tethering_request_sender_impl_unittest.cc
+++ b/chromeos/ash/components/tether/disconnect_tethering_request_sender_impl_unittest.cc
@@ -13,10 +13,8 @@
 #include "chromeos/ash/components/multidevice/remote_device_test_util.h"
 #include "chromeos/ash/components/tether/disconnect_tethering_operation.h"
 #include "chromeos/ash/components/tether/disconnect_tethering_request_sender.h"
+#include "chromeos/ash/components/tether/fake_host_connection.h"
 #include "chromeos/ash/components/tether/fake_tether_host_fetcher.h"
-#include "chromeos/ash/services/device_sync/public/cpp/fake_device_sync_client.h"
-#include "chromeos/ash/services/secure_channel/public/cpp/client/fake_secure_channel_client.h"
-#include "chromeos/ash/services/secure_channel/public/cpp/client/secure_channel_client.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 namespace ash {
@@ -29,11 +27,8 @@
  public:
   FakeDisconnectTetheringOperation(
       const TetherHost& tether_host,
-      device_sync::DeviceSyncClient* device_sync_client,
-      secure_channel::SecureChannelClient* secure_channel_client)
-      : DisconnectTetheringOperation(tether_host,
-                                     device_sync_client,
-                                     secure_channel_client),
+      raw_ptr<HostConnection::Factory> host_connection_factory)
+      : DisconnectTetheringOperation(tether_host, host_connection_factory),
         tether_host_(tether_host) {}
 
   ~FakeDisconnectTetheringOperation() override = default;
@@ -63,11 +58,10 @@
   // DisconnectTetheringOperation::Factory:
   std::unique_ptr<DisconnectTetheringOperation> CreateInstance(
       const TetherHost& tether_host,
-      device_sync::DeviceSyncClient* device_sync_client,
-      secure_channel::SecureChannelClient* secure_channel_client) override {
+      raw_ptr<HostConnection::Factory> host_connection_factory) override {
     FakeDisconnectTetheringOperation* operation =
-        new FakeDisconnectTetheringOperation(tether_host, device_sync_client,
-                                             secure_channel_client);
+        new FakeDisconnectTetheringOperation(tether_host,
+                                             host_connection_factory);
     created_operations_.push_back(operation);
     return base::WrapUnique(operation);
   }
@@ -112,12 +106,10 @@
   ~DisconnectTetheringRequestSenderTest() override = default;
 
   void SetUp() override {
-    fake_device_sync_client_ =
-        std::make_unique<device_sync::FakeDeviceSyncClient>();
-    fake_secure_channel_client_ =
-        std::make_unique<secure_channel::FakeSecureChannelClient>();
     fake_tether_host_fetcher_ =
         std::make_unique<FakeTetherHostFetcher>(test_devices_[0]);
+    fake_host_connection_factory_ =
+        std::make_unique<FakeHostConnection::Factory>();
 
     fake_operation_factory_ =
         std::make_unique<FakeDisconnectTetheringOperationFactory>();
@@ -126,7 +118,7 @@
 
     disconnect_tethering_request_sender_ =
         DisconnectTetheringRequestSenderImpl::Factory::Create(
-            fake_device_sync_client_.get(), fake_secure_channel_client_.get(),
+            fake_host_connection_factory_.get(),
             fake_tether_host_fetcher_.get());
 
     fake_disconnect_tethering_request_sender_observer_ =
@@ -178,8 +170,8 @@
     EXPECT_EQ(0u, fake_disconnect_tethering_request_sender_observer_
                       ->num_no_more_pending_requests_events());
 
-    // When multiple concurrent attempts are made to send a request to the same
-    // device, only one DisconnectTetheringOperation is created.
+    // When multiple concurrent attempts are made to send a request to the
+    // same device, only one DisconnectTetheringOperation is created.
     ASSERT_EQ(1u, fake_operation_factory_->created_operations().size());
     EXPECT_EQ(test_devices_[0].GetDeviceId(),
               fake_operation_factory_->created_operations()[0]->GetDeviceId());
@@ -192,10 +184,8 @@
 
   const multidevice::RemoteDeviceRefList test_devices_;
 
-  std::unique_ptr<device_sync::FakeDeviceSyncClient> fake_device_sync_client_;
-  std::unique_ptr<secure_channel::SecureChannelClient>
-      fake_secure_channel_client_;
   std::unique_ptr<FakeTetherHostFetcher> fake_tether_host_fetcher_;
+  std::unique_ptr<FakeHostConnection::Factory> fake_host_connection_factory_;
 
   std::unique_ptr<FakeDisconnectTetheringOperationFactory>
       fake_operation_factory_;
@@ -223,8 +213,8 @@
 
 TEST_F(DisconnectTetheringRequestSenderTest,
        DISABLED_SendRequest_CannotFetchHost) {
-  // Remove hosts from |fake_tether_host_fetcher_|; this will cause the fetcher
-  // to return a null RemoteDevice.
+  // Remove hosts from |fake_tether_host_fetcher_|; this will cause the
+  // fetcher to return a null RemoteDevice.
   fake_tether_host_fetcher_->SetTetherHost(std::nullopt);
 
   disconnect_tethering_request_sender_->SendDisconnectRequestToDevice(
@@ -277,9 +267,9 @@
 
 TEST_F(DisconnectTetheringRequestSenderTest,
        DISABLED_SendMultipleRequests_NotifyFinished) {
-  // When multiple requests are sent, a new DisconnectTetheringOperation will be
-  // created if the previous one has finished. This is true regardless of the
-  // success of the previous operation.
+  // When multiple requests are sent, a new DisconnectTetheringOperation will
+  // be created if the previous one has finished. This is true regardless of
+  // the success of the previous operation.
   disconnect_tethering_request_sender_->SendDisconnectRequestToDevice(
       test_devices_[0].GetDeviceId());
   EXPECT_TRUE(disconnect_tethering_request_sender_->HasPendingRequests());
diff --git a/chromeos/ash/components/tether/fake_asynchronous_shutdown_object_container.cc b/chromeos/ash/components/tether/fake_asynchronous_shutdown_object_container.cc
index 76aee15..158bcad 100644
--- a/chromeos/ash/components/tether/fake_asynchronous_shutdown_object_container.cc
+++ b/chromeos/ash/components/tether/fake_asynchronous_shutdown_object_container.cc
@@ -4,9 +4,7 @@
 
 #include "chromeos/ash/components/tether/fake_asynchronous_shutdown_object_container.h"
 
-namespace ash {
-
-namespace tether {
+namespace ash::tether {
 
 FakeAsynchronousShutdownObjectContainer::
     FakeAsynchronousShutdownObjectContainer(base::OnceClosure deletion_callback)
@@ -42,6 +40,9 @@
   return wifi_hotspot_disconnector_;
 }
 
-}  // namespace tether
+HostConnection::Factory*
+FakeAsynchronousShutdownObjectContainer::host_connection_factory() {
+  return host_connection_factory_;
+}
 
-}  // namespace ash
+}  // namespace ash::tether
diff --git a/chromeos/ash/components/tether/fake_asynchronous_shutdown_object_container.h b/chromeos/ash/components/tether/fake_asynchronous_shutdown_object_container.h
index 9a9c835..ccfbd6e5 100644
--- a/chromeos/ash/components/tether/fake_asynchronous_shutdown_object_container.h
+++ b/chromeos/ash/components/tether/fake_asynchronous_shutdown_object_container.h
@@ -11,9 +11,7 @@
 #include "base/memory/raw_ptr.h"
 #include "chromeos/ash/components/tether/asynchronous_shutdown_object_container.h"
 
-namespace ash {
-
-namespace tether {
+namespace ash::tether {
 
 // Test double for FakeAsynchronousShutdownObjectContainer.
 class FakeAsynchronousShutdownObjectContainer
@@ -53,6 +51,11 @@
     wifi_hotspot_disconnector_ = wifi_hotspot_disconnector;
   }
 
+  void set_host_connection_factory(
+      HostConnection::Factory* host_connection_factory) {
+    host_connection_factory_ = host_connection_factory;
+  }
+
   // AsynchronousShutdownObjectContainer:
   void Shutdown(base::OnceClosure shutdown_complete_callback) override;
   TetherHostFetcher* tether_host_fetcher() override;
@@ -60,20 +63,20 @@
       override;
   NetworkConfigurationRemover* network_configuration_remover() override;
   WifiHotspotDisconnector* wifi_hotspot_disconnector() override;
+  HostConnection::Factory* host_connection_factory() override;
 
  private:
   base::OnceClosure deletion_callback_;
   base::OnceClosure shutdown_complete_callback_;
 
   raw_ptr<TetherHostFetcher> tether_host_fetcher_ = nullptr;
+  raw_ptr<HostConnection::Factory> host_connection_factory_ = nullptr;
   raw_ptr<DisconnectTetheringRequestSender>
       disconnect_tethering_request_sender_ = nullptr;
   raw_ptr<NetworkConfigurationRemover> network_configuration_remover_ = nullptr;
   raw_ptr<WifiHotspotDisconnector> wifi_hotspot_disconnector_ = nullptr;
 };
 
-}  // namespace tether
-
-}  // namespace ash
+}  // namespace ash::tether
 
 #endif  // CHROMEOS_ASH_COMPONENTS_TETHER_FAKE_ASYNCHRONOUS_SHUTDOWN_OBJECT_CONTAINER_H_
diff --git a/chromeos/ash/components/tether/fake_host_connection.cc b/chromeos/ash/components/tether/fake_host_connection.cc
new file mode 100644
index 0000000..1843e64a
--- /dev/null
+++ b/chromeos/ash/components/tether/fake_host_connection.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 "chromeos/ash/components/tether/fake_host_connection.h"
+
+#include "base/functional/callback_helpers.h"
+#include "base/memory/ptr_util.h"
+#include "chromeos/ash/components/multidevice/remote_device_test_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace ash::tether {
+
+FakeHostConnection::Factory::Factory() = default;
+FakeHostConnection::Factory::~Factory() = default;
+
+void FakeHostConnection::Factory::ScanForTetherHostAndCreateConnection(
+    const std::string& device_id,
+    ConnectionPriority connection_priority,
+    raw_ptr<PayloadListener> payload_listener,
+    OnDisconnectionCallback on_disconnection,
+    OnConnectionCreatedCallback on_connection_attempt_finished) {
+  if (base::Contains(pending_connection_attempts_, device_id)) {
+    FakeHostConnection* fake_host_connection =
+        pending_connection_attempts_[device_id];
+    if (fake_host_connection) {
+      fake_host_connection->set_payload_listener(payload_listener);
+      fake_host_connection->set_on_disconnection(std::move(on_disconnection));
+    }
+
+    std::unique_ptr<HostConnection> host_connection =
+        base::WrapUnique<HostConnection>(fake_host_connection);
+    pending_connection_attempts_.erase(device_id);
+    std::move(on_connection_attempt_finished).Run(std::move(host_connection));
+  }
+}
+
+void FakeHostConnection::Factory::Create(
+    const TetherHost& tether_host,
+    ConnectionPriority connection_priority,
+    raw_ptr<PayloadListener> payload_listener,
+    OnDisconnectionCallback on_disconnection,
+    OnConnectionCreatedCallback on_connection_attempt_finished) {
+  ScanForTetherHostAndCreateConnection(
+      tether_host.GetDeviceId(), connection_priority, payload_listener,
+      std::move(on_disconnection), std::move(on_connection_attempt_finished));
+}
+
+FakeHostConnection* FakeHostConnection::Factory::SetupConnectionAttempt(
+    const TetherHost& tether_host) {
+  auto* fake_host_connection = new FakeHostConnection();
+  pending_connection_attempts_[tether_host.GetDeviceId()] =
+      fake_host_connection;
+  return fake_host_connection;
+}
+
+void FakeHostConnection::Factory::SetupConnectionAttempt(
+    const std::string& device_id,
+    FakeHostConnection* host_connection) {
+  EXPECT_FALSE(base::Contains(pending_connection_attempts_, device_id));
+  pending_connection_attempts_[device_id] = host_connection;
+}
+
+FakeHostConnection* FakeHostConnection::Factory::GetPendingConnectionAttempt(
+    const std::string& device_id) {
+  if (!base::Contains(pending_connection_attempts_, device_id)) {
+    return nullptr;
+  }
+
+  return pending_connection_attempts_[device_id];
+}
+
+FakeHostConnection::FakeHostConnection()
+    : HostConnection(
+          /*payload_listener=*/nullptr,
+          /*on_disconnection=*/base::DoNothing()) {}
+
+FakeHostConnection::~FakeHostConnection() = default;
+
+void FakeHostConnection::SendMessage(
+    std::unique_ptr<MessageWrapper> message,
+    OnMessageSentCallback on_message_sent_callback) {
+  sent_messages_.emplace_back(std::move(message),
+                              std::move(on_message_sent_callback));
+}
+
+void FakeHostConnection::ReceiveMessage(
+    std::unique_ptr<MessageWrapper> message) {
+  payload_listener_->OnMessageReceived(std::move(message));
+}
+
+void FakeHostConnection::Close() {
+  std::move(on_disconnection_).Run();
+}
+
+void FakeHostConnection::FinishSendingMessages() {
+  for (auto& message : sent_messages_) {
+    std::move(message.second).Run();
+  }
+}
+
+}  // namespace ash::tether
diff --git a/chromeos/ash/components/tether/fake_host_connection.h b/chromeos/ash/components/tether/fake_host_connection.h
new file mode 100644
index 0000000..a7ec981f7
--- /dev/null
+++ b/chromeos/ash/components/tether/fake_host_connection.h
@@ -0,0 +1,85 @@
+// Copyright 2024 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROMEOS_ASH_COMPONENTS_TETHER_FAKE_HOST_CONNECTION_H_
+#define CHROMEOS_ASH_COMPONENTS_TETHER_FAKE_HOST_CONNECTION_H_
+
+#include "base/containers/contains.h"
+#include "base/containers/flat_map.h"
+#include "base/observer_list.h"
+#include "chromeos/ash/components/tether/host_connection.h"
+
+namespace ash::tether {
+
+class FakeHostConnection : public HostConnection {
+ public:
+  class Factory : public HostConnection::Factory {
+   public:
+    Factory();
+    ~Factory() override;
+
+    // HostConnection::Factory:
+    void ScanForTetherHostAndCreateConnection(
+        const std::string& device_id,
+        ConnectionPriority connection_priority,
+        raw_ptr<PayloadListener> payload_listener,
+        OnDisconnectionCallback on_disconnection,
+        OnConnectionCreatedCallback on_connection_created) override;
+    void Create(const TetherHost& tether_host,
+                ConnectionPriority connection_priority,
+                raw_ptr<PayloadListener> payload_listener,
+                OnDisconnectionCallback on_disconnection,
+                OnConnectionCreatedCallback on_connection_created) override;
+
+    // Creates a new [FakeHostConnection] and sets it as the connection to be
+    // returned when [tether_host] is requested.
+    FakeHostConnection* SetupConnectionAttempt(const TetherHost& tether_host);
+
+    // Setup the connection attempt, passing in a [host_connection] which will
+    // be returned when [device_id] is requested. Pass in [nullptr] to fail the
+    // connection attempt.
+    void SetupConnectionAttempt(const std::string& device_id,
+                                FakeHostConnection* host_connection);
+
+    FakeHostConnection* GetPendingConnectionAttempt(
+        const std::string& device_id);
+
+   private:
+    base::flat_map<std::string, FakeHostConnection*>
+        pending_connection_attempts_;
+  };
+
+  explicit FakeHostConnection();
+  ~FakeHostConnection() override;
+
+  // HostConnection:
+  void SendMessage(std::unique_ptr<MessageWrapper> message,
+                   OnMessageSentCallback on_message_sent_callback) override;
+
+  void ReceiveMessage(std::unique_ptr<MessageWrapper> message);
+  void Close();
+  void FinishSendingMessages();
+
+  void set_on_disconnection(base::OnceClosure on_disconnection) {
+    on_disconnection_ = std::move(on_disconnection);
+  }
+
+  void set_payload_listener(HostConnection::PayloadListener* payload_listener) {
+    payload_listener_ = payload_listener;
+  }
+
+  const std::vector<
+      std::pair<std::unique_ptr<MessageWrapper>, OnMessageSentCallback>>&
+  sent_messages() {
+    return sent_messages_;
+  }
+
+ private:
+  std::vector<std::pair<std::unique_ptr<MessageWrapper>, OnMessageSentCallback>>
+      sent_messages_;
+};
+
+}  // namespace ash::tether
+
+#endif  // CHROMEOS_ASH_COMPONENTS_TETHER_FAKE_HOST_CONNECTION_H_
diff --git a/chromeos/ash/components/tether/fake_tether_availability_operation.cc b/chromeos/ash/components/tether/fake_tether_availability_operation.cc
index 9e1a068..b3ebf99 100644
--- a/chromeos/ash/components/tether/fake_tether_availability_operation.cc
+++ b/chromeos/ash/components/tether/fake_tether_availability_operation.cc
@@ -6,13 +6,13 @@
 #include "chromeos/ash/components/tether/fake_tether_availability_operation.h"
 
 #include "base/containers/contains.h"
+#include "base/memory/ptr_util.h"
 
 namespace ash::tether {
 FakeTetherAvailabilityOperation::Initializer::Initializer()
     : TetherAvailabilityOperation::Initializer::Initializer(
-          /*device_sync_client=*/nullptr,
-          /*secure_channel_client=*/nullptr,
-          /*tether_host_response_recorder=*/nullptr,
+          /*host_connection_factory=*/nullptr,
+          /*tether_host()response_recorder=*/nullptr,
           /*connection_preserver=*/nullptr) {}
 
 FakeTetherAvailabilityOperation::Initializer::~Initializer() = default;
@@ -53,9 +53,8 @@
     base::OnceClosure on_destroyed_callback)
     : TetherAvailabilityOperation(tether_host,
                                   base::DoNothing(),
-                                  /*device_sync_client=*/nullptr,
-                                  /*secure_channel_client=*/nullptr,
-                                  /*tether_host_response_recorder=*/nullptr,
+                                  /*host_connection_factory=*/nullptr,
+                                  /*tether_host()response_recorder=*/nullptr,
                                   /*connection_preserver=*/nullptr),
       on_destroyed_callback_(std::move(on_destroyed_callback)) {}
 
diff --git a/chromeos/ash/components/tether/fake_tether_availability_operation.h b/chromeos/ash/components/tether/fake_tether_availability_operation.h
index 2408609..0bbeddc 100644
--- a/chromeos/ash/components/tether/fake_tether_availability_operation.h
+++ b/chromeos/ash/components/tether/fake_tether_availability_operation.h
@@ -5,6 +5,7 @@
 #ifndef CHROMEOS_ASH_COMPONENTS_TETHER_FAKE_TETHER_AVAILABILITY_OPERATION_H_
 #define CHROMEOS_ASH_COMPONENTS_TETHER_FAKE_TETHER_AVAILABILITY_OPERATION_H_
 
+#include "base/containers/flat_map.h"
 #include "chromeos/ash/components/tether/tether_availability_operation.h"
 
 namespace ash::tether {
diff --git a/chromeos/ash/components/tether/host_connection.h b/chromeos/ash/components/tether/host_connection.h
index 851af13..be1f7a6 100644
--- a/chromeos/ash/components/tether/host_connection.h
+++ b/chromeos/ash/components/tether/host_connection.h
@@ -17,6 +17,7 @@
  public:
   class PayloadListener {
    public:
+    virtual ~PayloadListener() = default;
     virtual void OnMessageReceived(std::unique_ptr<MessageWrapper> message) = 0;
   };
 
diff --git a/chromeos/ash/components/tether/host_scanner_impl.cc b/chromeos/ash/components/tether/host_scanner_impl.cc
index 23778c6599..c2e92fd 100644
--- a/chromeos/ash/components/tether/host_scanner_impl.cc
+++ b/chromeos/ash/components/tether/host_scanner_impl.cc
@@ -102,6 +102,10 @@
   if (scanned_device_list_so_far.empty() && !is_final_scan_result) {
     was_notification_showing_when_current_scan_started_ =
         IsPotentialHotspotNotificationShowing();
+    PA_LOG(VERBOSE) << "Was 'potential hotspot' notification showing when "
+                       "current scan started: ["
+                    << was_notification_showing_when_current_scan_started_
+                    << "]";
   }
 
   // Ensure all results received so far are in the cache (setting entries which
@@ -184,6 +188,7 @@
 
 void HostScannerImpl::OnFinalScanResultReceived(
     const std::vector<ScannedDeviceInfo>& final_scan_results) {
+  PA_LOG(INFO) << __func__;
   // Search through all GUIDs that were in the cache before the scan began. If
   // any of those GUIDs are not present in the final scan results, remove them
   // from the cache.
@@ -258,11 +263,18 @@
   if (first_network && first_network->IsConnectingOrConnected()) {
     // If a network is connecting or connected, the notification should not be
     // shown.
+    PA_LOG(INFO)
+        << __func__
+        << " Returning false because device is connected/connecting to "
+           "a network";
     return false;
   }
 
   if (!IsPotentialHotspotNotificationShowing() &&
       was_notification_shown_in_current_scan_) {
+    PA_LOG(INFO) << __func__
+                 << " Returning false because notification has been shown in "
+                    "the current scan";
     // If a notification was shown in the current scan but it is no longer
     // showing, it has been removed, either due to NotificationRemover or due to
     // the user closing it. Since a scan only lasts on the order of seconds to
@@ -273,18 +285,15 @@
 
   if (!IsPotentialHotspotNotificationShowing() &&
       was_notification_showing_when_current_scan_started_) {
+    PA_LOG(INFO) << __func__
+                 << " Returning false because notification was showing when "
+                    "scan started";
     // If a notification was showing when the scan started but is no longer
     // showing, it has been removed and should not be re-shown.
     return false;
   }
 
-  if (has_notification_been_shown_in_previous_scan_ &&
-      !was_notification_showing_when_current_scan_started_) {
-    // If a notification was shown in a previous scan but was not visible when
-    // the current scan started, it should not be shown because this could be
-    // considered spammy; see https://crbug.com/759078.
-    return false;
-  }
+  PA_LOG(INFO) << __func__ << " Returning true because all checks passed";
 
   return true;
 }
diff --git a/chromeos/ash/components/tether/keep_alive_operation.cc b/chromeos/ash/components/tether/keep_alive_operation.cc
index cf05d077..e58c38be 100644
--- a/chromeos/ash/components/tether/keep_alive_operation.cc
+++ b/chromeos/ash/components/tether/keep_alive_operation.cc
@@ -10,7 +10,6 @@
 #include "chromeos/ash/components/multidevice/logging/logging.h"
 #include "chromeos/ash/components/tether/message_wrapper.h"
 #include "chromeos/ash/components/tether/proto/tether.pb.h"
-#include "chromeos/ash/services/secure_channel/public/cpp/client/secure_channel_client.h"
 
 namespace ash::tether {
 
@@ -21,15 +20,14 @@
 // static
 std::unique_ptr<KeepAliveOperation> KeepAliveOperation::Factory::Create(
     const TetherHost& tether_host,
-    device_sync::DeviceSyncClient* device_sync_client,
-    secure_channel::SecureChannelClient* secure_channel_client) {
+    raw_ptr<HostConnection::Factory> host_connection_factory) {
   if (factory_instance_) {
-    return factory_instance_->CreateInstance(tether_host, device_sync_client,
-                                             secure_channel_client);
+    return factory_instance_->CreateInstance(tether_host,
+                                             host_connection_factory);
   }
 
-  return base::WrapUnique(new KeepAliveOperation(
-      tether_host, device_sync_client, secure_channel_client));
+  return base::WrapUnique(
+      new KeepAliveOperation(tether_host, host_connection_factory));
 }
 
 // static
@@ -41,12 +39,11 @@
 
 KeepAliveOperation::KeepAliveOperation(
     const TetherHost& tether_host,
-    device_sync::DeviceSyncClient* device_sync_client,
-    secure_channel::SecureChannelClient* secure_channel_client)
-    : MessageTransferOperation(tether_host,
-                               secure_channel::ConnectionPriority::kMedium,
-                               device_sync_client,
-                               secure_channel_client),
+    raw_ptr<HostConnection::Factory> host_connection_factory)
+    : MessageTransferOperation(
+          tether_host,
+          HostConnection::Factory::ConnectionPriority::kMedium,
+          host_connection_factory),
       clock_(base::DefaultClock::GetInstance()) {}
 
 KeepAliveOperation::~KeepAliveOperation() = default;
@@ -61,7 +58,8 @@
 
 void KeepAliveOperation::OnDeviceAuthenticated() {
   keep_alive_tickle_request_start_time_ = clock_->Now();
-  SendMessageToDevice(std::make_unique<MessageWrapper>(KeepAliveTickle()));
+  SendMessage(std::make_unique<MessageWrapper>(KeepAliveTickle()),
+              /*on_message_sent=*/base::DoNothing());
 }
 
 void KeepAliveOperation::OnMessageReceived(
diff --git a/chromeos/ash/components/tether/keep_alive_operation.h b/chromeos/ash/components/tether/keep_alive_operation.h
index 5e993025e..785cdd0f 100644
--- a/chromeos/ash/components/tether/keep_alive_operation.h
+++ b/chromeos/ash/components/tether/keep_alive_operation.h
@@ -12,14 +12,6 @@
 #include "base/time/time.h"
 #include "chromeos/ash/components/tether/message_transfer_operation.h"
 
-namespace ash::device_sync {
-class DeviceSyncClient;
-}
-
-namespace ash::secure_channel {
-class SecureChannelClient;
-}
-
 namespace ash::tether {
 
 // Operation which sends a keep-alive message to a tether host and receives an
@@ -30,8 +22,7 @@
    public:
     static std::unique_ptr<KeepAliveOperation> Create(
         const TetherHost& tether_host,
-        device_sync::DeviceSyncClient* device_sync_client,
-        secure_channel::SecureChannelClient* secure_channel_client);
+        raw_ptr<HostConnection::Factory> host_connection_factory);
 
     static void SetFactoryForTesting(Factory* factory);
 
@@ -39,8 +30,7 @@
     virtual ~Factory();
     virtual std::unique_ptr<KeepAliveOperation> CreateInstance(
         const TetherHost& tether_host,
-        device_sync::DeviceSyncClient* device_sync_client,
-        secure_channel::SecureChannelClient* secure_channel_client) = 0;
+        raw_ptr<HostConnection::Factory> host_connection_factory) = 0;
 
    private:
     static Factory* factory_instance_;
@@ -63,10 +53,8 @@
   void RemoveObserver(Observer* observer);
 
  protected:
-  KeepAliveOperation(
-      const TetherHost& tether_host,
-      device_sync::DeviceSyncClient* device_sync_client,
-      secure_channel::SecureChannelClient* secure_channel_client);
+  KeepAliveOperation(const TetherHost& tether_host,
+                     raw_ptr<HostConnection::Factory> host_connection_factory);
 
   // MessageTransferOperation:
   void OnDeviceAuthenticated() override;
diff --git a/chromeos/ash/components/tether/keep_alive_operation_unittest.cc b/chromeos/ash/components/tether/keep_alive_operation_unittest.cc
index 27bb406..72949402 100644
--- a/chromeos/ash/components/tether/keep_alive_operation_unittest.cc
+++ b/chromeos/ash/components/tether/keep_alive_operation_unittest.cc
@@ -13,12 +13,9 @@
 #include "base/test/metrics/histogram_tester.h"
 #include "base/test/simple_test_clock.h"
 #include "chromeos/ash/components/multidevice/remote_device_test_util.h"
+#include "chromeos/ash/components/tether/fake_host_connection.h"
 #include "chromeos/ash/components/tether/message_wrapper.h"
 #include "chromeos/ash/components/tether/proto_test_util.h"
-#include "chromeos/ash/services/device_sync/public/cpp/fake_device_sync_client.h"
-#include "chromeos/ash/services/secure_channel/public/cpp/client/fake_client_channel.h"
-#include "chromeos/ash/services/secure_channel/public/cpp/client/fake_connection_attempt.h"
-#include "chromeos/ash/services/secure_channel/public/cpp/client/fake_secure_channel_client.h"
 #include "components/cross_device/timer_factory/fake_timer_factory.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
@@ -58,61 +55,25 @@
 
  protected:
   KeepAliveOperationTest()
-      : local_device_(multidevice::RemoteDeviceRefBuilder()
-                          .SetPublicKey("local device")
-                          .Build()),
-        remote_device_(multidevice::CreateRemoteDeviceRefListForTest(1)[0]) {}
+      : tether_host_(TetherHost(multidevice::CreateRemoteDeviceRefForTest())) {}
 
   void SetUp() override {
-    fake_device_sync_client_ =
-        std::make_unique<device_sync::FakeDeviceSyncClient>();
-    fake_device_sync_client_->set_local_device_metadata(local_device_);
-    fake_secure_channel_client_ =
-        std::make_unique<secure_channel::FakeSecureChannelClient>();
+    fake_host_connection_factory_ =
+        std::make_unique<FakeHostConnection::Factory>();
+    operation_ = base::WrapUnique(new KeepAliveOperation(
+        tether_host_, fake_host_connection_factory_.get()));
+    operation_->AddObserver(&mock_observer_);
 
-    operation_ = ConstructOperation();
-    operation_->Initialize();
-
-    ConnectAuthenticatedChannelForDevice(remote_device_);
-  }
-
-  std::unique_ptr<KeepAliveOperation> ConstructOperation() {
-    auto connection_attempt =
-        std::make_unique<secure_channel::FakeConnectionAttempt>();
-    connection_attempt_ = connection_attempt.get();
-    fake_secure_channel_client_->set_next_listen_connection_attempt(
-        remote_device_, local_device_, std::move(connection_attempt));
-
-    auto operation = base::WrapUnique(new KeepAliveOperation(
-        TetherHost(remote_device_), fake_device_sync_client_.get(),
-        fake_secure_channel_client_.get()));
-    operation->AddObserver(&mock_observer_);
-
-    operation->SetTimerFactoryForTest(
+    operation_->SetTimerFactoryForTest(
         std::make_unique<cross_device::FakeTimerFactory>());
 
     test_clock_.SetNow(base::Time::UnixEpoch());
-    operation->SetClockForTest(&test_clock_);
-
-    return operation;
+    operation_->SetClockForTest(&test_clock_);
   }
 
-  void ConnectAuthenticatedChannelForDevice(
-      multidevice::RemoteDeviceRef remote_device) {
-    auto fake_client_channel =
-        std::make_unique<secure_channel::FakeClientChannel>();
-    connection_attempt_->NotifyConnection(std::move(fake_client_channel));
-  }
+  const TetherHost tether_host_;
 
-  const multidevice::RemoteDeviceRef local_device_;
-  const multidevice::RemoteDeviceRef remote_device_;
-
-  raw_ptr<secure_channel::FakeConnectionAttempt, DanglingUntriaged>
-      connection_attempt_;
-  std::unique_ptr<device_sync::FakeDeviceSyncClient> fake_device_sync_client_;
-  std::unique_ptr<secure_channel::FakeSecureChannelClient>
-      fake_secure_channel_client_;
-
+  std::unique_ptr<FakeHostConnection::Factory> fake_host_connection_factory_;
   std::unique_ptr<KeepAliveOperation> operation_;
 
   base::SimpleTestClock test_clock_;
@@ -123,25 +84,19 @@
 // Tests that the KeepAliveTickle message is sent to the remote device once the
 // communication channel is connected and authenticated.
 TEST_F(KeepAliveOperationTest, KeepAliveTickleSentOnceAuthenticated) {
-  std::unique_ptr<KeepAliveOperation> operation = ConstructOperation();
-  operation->Initialize();
+  // Setup the connection.
+  auto* fake_host_connection =
+      fake_host_connection_factory_->SetupConnectionAttempt(tether_host_);
 
-  // Create the client channel for the remote device.
-  auto fake_client_channel =
-      std::make_unique<secure_channel::FakeClientChannel>();
-
-  // No requests as a result of creating the client channel.
-  auto& sent_messages = fake_client_channel->sent_messages();
-  EXPECT_EQ(0u, sent_messages.size());
-
-  // Connect and authenticate the client channel.
-  connection_attempt_->NotifyConnection(std::move(fake_client_channel));
+  // Start the operation.
+  operation_->Initialize();
 
   // Verify the KeepAliveTickle message is sent.
   auto message_wrapper = std::make_unique<MessageWrapper>(KeepAliveTickle());
   std::string expected_payload = message_wrapper->ToRawMessage();
-  EXPECT_EQ(1u, sent_messages.size());
-  EXPECT_EQ(expected_payload, sent_messages[0].first);
+  EXPECT_EQ(1u, fake_host_connection->sent_messages().size());
+  EXPECT_EQ(expected_payload,
+            fake_host_connection->sent_messages()[0].first->ToRawMessage());
 }
 
 // Tests that observers are notified when the operation has completed, signified
@@ -149,12 +104,18 @@
 TEST_F(KeepAliveOperationTest, NotifiesObserversOnResponse) {
   DeviceStatus test_status = CreateDeviceStatusWithFakeFields();
 
+  // Setup the connection.
+  fake_host_connection_factory_->SetupConnectionAttempt(tether_host_);
+
   // Verify that the observer is called with the correct parameters.
   EXPECT_CALL(mock_observer_, OnOperationFinishedRaw(NotNull()))
       .WillOnce(Invoke([&test_status](DeviceStatus* status) {
         EXPECT_EQ(test_status.SerializeAsString(), status->SerializeAsString());
       }));
 
+  // Start the operation.
+  operation_->Initialize();
+
   KeepAliveTickleResponse response;
   response.mutable_device_status()->CopyFrom(test_status);
   std::unique_ptr<MessageWrapper> message(new MessageWrapper(response));
@@ -167,6 +128,12 @@
 
   EXPECT_CALL(mock_observer_, OnOperationFinishedRaw(_));
 
+  // Setup the connection.
+  fake_host_connection_factory_->SetupConnectionAttempt(tether_host_);
+
+  // Initialize the operation.
+  operation_->Initialize();
+
   // Advance the clock in order to verify a non-zero response duration is
   // recorded and verified (below).
   test_clock_.Advance(kKeepAliveTickleResponseTime);
diff --git a/chromeos/ash/components/tether/keep_alive_scheduler.cc b/chromeos/ash/components/tether/keep_alive_scheduler.cc
index 57c3ca4..25e825c 100644
--- a/chromeos/ash/components/tether/keep_alive_scheduler.cc
+++ b/chromeos/ash/components/tether/keep_alive_scheduler.cc
@@ -6,9 +6,9 @@
 
 #include "base/functional/bind.h"
 #include "base/metrics/histogram_functions.h"
+#include "chromeos/ash/components/multidevice/logging/logging.h"
 #include "chromeos/ash/components/tether/device_id_tether_network_guid_map.h"
 #include "chromeos/ash/components/tether/host_scan_cache.h"
-#include "chromeos/ash/services/secure_channel/public/cpp/client/secure_channel_client.h"
 
 namespace ash::tether {
 
@@ -16,27 +16,23 @@
 const uint32_t KeepAliveScheduler::kKeepAliveIntervalMinutes = 3;
 
 KeepAliveScheduler::KeepAliveScheduler(
-    device_sync::DeviceSyncClient* device_sync_client,
-    secure_channel::SecureChannelClient* secure_channel_client,
+    raw_ptr<HostConnection::Factory> host_connection_factory,
     ActiveHost* active_host,
     HostScanCache* host_scan_cache,
     DeviceIdTetherNetworkGuidMap* device_id_tether_network_guid_map)
-    : KeepAliveScheduler(device_sync_client,
-                         secure_channel_client,
+    : KeepAliveScheduler(host_connection_factory,
                          active_host,
                          host_scan_cache,
                          device_id_tether_network_guid_map,
                          std::make_unique<base::RepeatingTimer>()) {}
 
 KeepAliveScheduler::KeepAliveScheduler(
-    device_sync::DeviceSyncClient* device_sync_client,
-    secure_channel::SecureChannelClient* secure_channel_client,
+    raw_ptr<HostConnection::Factory> host_connection_factory,
     ActiveHost* active_host,
     HostScanCache* host_scan_cache,
     DeviceIdTetherNetworkGuidMap* device_id_tether_network_guid_map,
     std::unique_ptr<base::RepeatingTimer> timer)
-    : device_sync_client_(device_sync_client),
-      secure_channel_client_(secure_channel_client),
+    : host_connection_factory_(host_connection_factory),
       active_host_(active_host),
       host_scan_cache_(host_scan_cache),
       device_id_tether_network_guid_map_(device_id_tether_network_guid_map),
@@ -112,8 +108,7 @@
   DCHECK(active_host_device_);
 
   keep_alive_operation_ = KeepAliveOperation::Factory::Create(
-      TetherHost(*active_host_device_), device_sync_client_,
-      secure_channel_client_);
+      TetherHost(*active_host_device_), host_connection_factory_);
   keep_alive_operation_->AddObserver(this);
   keep_alive_operation_->Initialize();
 }
diff --git a/chromeos/ash/components/tether/keep_alive_scheduler.h b/chromeos/ash/components/tether/keep_alive_scheduler.h
index 1bc3832..58fca0ca 100644
--- a/chromeos/ash/components/tether/keep_alive_scheduler.h
+++ b/chromeos/ash/components/tether/keep_alive_scheduler.h
@@ -13,16 +13,9 @@
 #include "base/timer/timer.h"
 #include "chromeos/ash/components/tether/active_host.h"
 #include "chromeos/ash/components/tether/device_status_util.h"
+#include "chromeos/ash/components/tether/host_connection.h"
 #include "chromeos/ash/components/tether/keep_alive_operation.h"
 
-namespace ash::device_sync {
-class DeviceSyncClient;
-}
-
-namespace ash::secure_channel {
-class SecureChannelClient;
-}
-
 namespace ash::tether {
 
 class HostScanCache;
@@ -36,8 +29,7 @@
                            public KeepAliveOperation::Observer {
  public:
   KeepAliveScheduler(
-      device_sync::DeviceSyncClient* device_sync_client,
-      secure_channel::SecureChannelClient* secure_channel_client,
+      raw_ptr<HostConnection::Factory> host_connection_factory,
       ActiveHost* active_host,
       HostScanCache* host_scan_cache,
       DeviceIdTetherNetworkGuidMap* device_id_tether_network_guid_map);
@@ -59,8 +51,7 @@
   friend class KeepAliveSchedulerTest;
 
   KeepAliveScheduler(
-      device_sync::DeviceSyncClient* device_sync_client,
-      secure_channel::SecureChannelClient* secure_channel_client,
+      raw_ptr<HostConnection::Factory> host_connection_factory,
       ActiveHost* active_host,
       HostScanCache* host_scan_cache,
       DeviceIdTetherNetworkGuidMap* device_id_tether_network_guid_map,
@@ -70,8 +61,7 @@
 
   static const uint32_t kKeepAliveIntervalMinutes;
 
-  raw_ptr<device_sync::DeviceSyncClient> device_sync_client_;
-  raw_ptr<secure_channel::SecureChannelClient> secure_channel_client_;
+  raw_ptr<HostConnection::Factory> host_connection_factory_;
   raw_ptr<ActiveHost> active_host_;
   raw_ptr<HostScanCache> host_scan_cache_;
   raw_ptr<DeviceIdTetherNetworkGuidMap> device_id_tether_network_guid_map_;
diff --git a/chromeos/ash/components/tether/keep_alive_scheduler_unittest.cc b/chromeos/ash/components/tether/keep_alive_scheduler_unittest.cc
index 93c3f51e..84833b0 100644
--- a/chromeos/ash/components/tether/keep_alive_scheduler_unittest.cc
+++ b/chromeos/ash/components/tether/keep_alive_scheduler_unittest.cc
@@ -13,16 +13,12 @@
 #include "chromeos/ash/components/multidevice/remote_device_test_util.h"
 #include "chromeos/ash/components/tether/device_id_tether_network_guid_map.h"
 #include "chromeos/ash/components/tether/fake_active_host.h"
+#include "chromeos/ash/components/tether/fake_host_connection.h"
 #include "chromeos/ash/components/tether/fake_host_scan_cache.h"
 #include "chromeos/ash/components/tether/proto_test_util.h"
-#include "chromeos/ash/services/device_sync/public/cpp/fake_device_sync_client.h"
-#include "chromeos/ash/services/secure_channel/public/cpp/client/fake_secure_channel_client.h"
-#include "chromeos/ash/services/secure_channel/public/cpp/client/secure_channel_client.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
-namespace ash {
-
-namespace tether {
+namespace ash::tether {
 
 namespace {
 
@@ -38,12 +34,9 @@
  public:
   FakeKeepAliveOperation(
       const TetherHost& tether_host,
-      device_sync::DeviceSyncClient* device_sync_client,
-      secure_channel::SecureChannelClient* secure_channel_client,
+      raw_ptr<HostConnection::Factory> host_connection_factory,
       OperationDeletedHandler* handler)
-      : KeepAliveOperation(tether_host,
-                           device_sync_client,
-                           secure_channel_client),
+      : KeepAliveOperation(tether_host, host_connection_factory),
         tether_host_(tether_host),
         handler_(handler) {}
 
@@ -79,11 +72,10 @@
  protected:
   std::unique_ptr<KeepAliveOperation> CreateInstance(
       const TetherHost& tether_host,
-      device_sync::DeviceSyncClient* device_sync_client,
-      secure_channel::SecureChannelClient* secure_channel_client) override {
+      raw_ptr<HostConnection::Factory> host_connection_factory) override {
     num_created_++;
-    last_created_ = new FakeKeepAliveOperation(tether_host, device_sync_client,
-                                               secure_channel_client, this);
+    last_created_ =
+        new FakeKeepAliveOperation(tether_host, host_connection_factory, this);
     return base::WrapUnique(last_created_.get());
   }
 
@@ -105,10 +97,8 @@
       : test_devices_(multidevice::CreateRemoteDeviceRefListForTest(2)) {}
 
   void SetUp() override {
-    fake_device_sync_client_ =
-        std::make_unique<device_sync::FakeDeviceSyncClient>();
-    fake_secure_channel_client_ =
-        std::make_unique<secure_channel::FakeSecureChannelClient>();
+    fake_host_connection_factory_ =
+        std::make_unique<FakeHostConnection::Factory>();
     fake_active_host_ = std::make_unique<FakeActiveHost>();
     fake_host_scan_cache_ = std::make_unique<FakeHostScanCache>();
     device_id_tether_network_guid_map_ =
@@ -121,9 +111,8 @@
         fake_operation_factory_.get());
 
     scheduler_ = base::WrapUnique(new KeepAliveScheduler(
-        fake_device_sync_client_.get(), fake_secure_channel_client_.get(),
-        fake_active_host_.get(), fake_host_scan_cache_.get(),
-        device_id_tether_network_guid_map_.get(),
+        fake_host_connection_factory_.get(), fake_active_host_.get(),
+        fake_host_scan_cache_.get(), device_id_tether_network_guid_map_.get(),
         base::WrapUnique(mock_timer_.get())));
   }
 
@@ -160,9 +149,7 @@
 
   const multidevice::RemoteDeviceRefList test_devices_;
 
-  std::unique_ptr<device_sync::FakeDeviceSyncClient> fake_device_sync_client_;
-  std::unique_ptr<secure_channel::SecureChannelClient>
-      fake_secure_channel_client_;
+  std::unique_ptr<FakeHostConnection::Factory> fake_host_connection_factory_;
   std::unique_ptr<FakeActiveHost> fake_active_host_;
   std::unique_ptr<FakeHostScanCache> fake_host_scan_cache_;
   // TODO(hansberry): Use a fake for this when a real mapping scheme is created.
@@ -315,6 +302,4 @@
   VerifyTimerRunning(false /* is_running */);
 }
 
-}  // namespace tether
-
-}  // namespace ash
+}  // namespace ash::tether
diff --git a/chromeos/ash/components/tether/message_transfer_operation.cc b/chromeos/ash/components/tether/message_transfer_operation.cc
index 84796182..40fde16 100644
--- a/chromeos/ash/components/tether/message_transfer_operation.cc
+++ b/chromeos/ash/components/tether/message_transfer_operation.cc
@@ -11,26 +11,17 @@
 #include "base/functional/bind.h"
 #include "chromeos/ash/components/multidevice/logging/logging.h"
 #include "chromeos/ash/components/tether/message_wrapper.h"
-#include "chromeos/ash/services/secure_channel/public/cpp/client/secure_channel_client.h"
 #include "components/cross_device/timer_factory/timer_factory_impl.h"
 
 namespace ash::tether {
 
-namespace {
-
-const char kTetherFeature[] = "magic_tether";
-
-}  // namespace
-
 MessageTransferOperation::MessageTransferOperation(
     const TetherHost& tether_host,
-    secure_channel::ConnectionPriority connection_priority,
-    device_sync::DeviceSyncClient* device_sync_client,
-    secure_channel::SecureChannelClient* secure_channel_client)
+    HostConnection::Factory::ConnectionPriority connection_priority,
+    raw_ptr<HostConnection::Factory> host_connection_factory)
     : tether_host_(tether_host),
-      device_sync_client_(device_sync_client),
-      secure_channel_client_(secure_channel_client),
       connection_priority_(connection_priority),
+      host_connection_factory_(host_connection_factory),
       timer_factory_(cross_device::TimerFactoryImpl::Factory::Create()) {}
 
 MessageTransferOperation::~MessageTransferOperation() {
@@ -51,14 +42,6 @@
     return;
   }
 
-  std::optional<multidevice::RemoteDeviceRef> local_device =
-      device_sync_client_->GetLocalDeviceMetadata();
-  if (!local_device) {
-    PA_LOG(ERROR) << "MessageTransferOperation::" << __func__
-                  << ": Local device unexpectedly null.";
-    return;
-  }
-
   initialized_ = true;
 
   // Store the message type for this connection as a private field. This is
@@ -71,20 +54,12 @@
   OnOperationStarted();
 
   StartConnectionTimerForDevice();
-  connection_attempt_ = secure_channel_client_->ListenForConnectionFromDevice(
-      tether_host_.remote_device_ref().value(), *local_device, kTetherFeature,
-      secure_channel::ConnectionMedium::kBluetoothLowEnergy,
-      connection_priority_);
-
-  connection_attempt_->SetDelegate(this);
-}
-
-void MessageTransferOperation::OnMessageReceived(const std::string& payload) {
-  std::unique_ptr<MessageWrapper> message_wrapper =
-      MessageWrapper::FromRawMessage(payload);
-  if (message_wrapper) {
-    OnMessageReceived(std::move(message_wrapper));
-  }
+  host_connection_factory_->Create(
+      tether_host_, connection_priority_, /*payload_listener=*/this,
+      base::BindOnce(&MessageTransferOperation::OnDisconnected,
+                     weak_ptr_factory_.GetWeakPtr()),
+      base::BindOnce(&MessageTransferOperation::OnConnectionAttemptComplete,
+                     weak_ptr_factory_.GetWeakPtr()));
 }
 
 void MessageTransferOperation::StopOperation() {
@@ -93,52 +68,45 @@
 
   StopTimerForDeviceIfRunning();
 
-  connection_attempt_.reset();
-  if (client_channel_ != nullptr) {
-    client_channel_.reset();
-  }
+  host_connection_.reset();
 
   if (!shutting_down_) {
     OnOperationFinished();
   }
 }
 
-int MessageTransferOperation::SendMessageToDevice(
-    std::unique_ptr<MessageWrapper> message_wrapper) {
-  DCHECK(client_channel_ != nullptr);
-  int sequence_number = next_message_sequence_number_++;
-  bool success = client_channel_->SendMessage(
-      message_wrapper->ToRawMessage(),
-      base::BindOnce(&MessageTransferOperation::OnMessageSent,
-                     weak_ptr_factory_.GetWeakPtr(), sequence_number));
-  return success ? sequence_number : -1;
+void MessageTransferOperation::SendMessage(
+    std::unique_ptr<MessageWrapper> message_wrapper,
+    HostConnection::OnMessageSentCallback on_message_sent) {
+  CHECK(host_connection_);
+  host_connection_->SendMessage(std::move(message_wrapper),
+                                std::move(on_message_sent));
 }
 
 uint32_t MessageTransferOperation::GetMessageTimeoutSeconds() {
   return MessageTransferOperation::kDefaultMessageTimeoutSeconds;
 }
 
-void MessageTransferOperation::OnConnectionAttemptFailure(
-    secure_channel::mojom::ConnectionAttemptFailureReason reason) {
-  PA_LOG(WARNING) << "Failed to connect to device "
-                  << GetDeviceId(/*truncate_for_logs=*/true)
-                  << ", error: " << reason;
-  StopOperation();
-}
+void MessageTransferOperation::OnConnectionAttemptComplete(
+    std::unique_ptr<HostConnection> host_connection) {
+  if (!host_connection) {
+    PA_LOG(WARNING) << "Failed to connect to device ["
+                    << GetDeviceId(/*truncate_for_logs=*/true) << "].";
+    StopOperation();
+  } else {
+    host_connection_ = std::move(host_connection);
 
-void MessageTransferOperation::OnConnection(
-    std::unique_ptr<secure_channel::ClientChannel> channel) {
-  client_channel_ = std::move(channel);
-  client_channel_->AddObserver(this);
+    // Stop the timer which was started from StartConnectionTimerForDevice()
+    // since the connection has now been established. Start another timer now
+    // via StartMessageTimerForDevice() while waiting for messages to be sent to
+    // and received by |remote_device|.
+    StopTimerForDeviceIfRunning();
+    StartMessageTimerForDevice();
 
-  // Stop the timer which was started from StartConnectionTimerForDevice() since
-  // the connection has now been established. Start another timer now via
-  // StartMessageTimerForDevice() while waiting for messages to be sent to and
-  // received by |remote_device|.
-  StopTimerForDeviceIfRunning();
-  StartMessageTimerForDevice();
-
-  OnDeviceAuthenticated();
+    PA_LOG(INFO) << "Successfully opened connection to ["
+                 << GetDeviceId(/*truncate_for_logs=*/true) << "].";
+    OnDeviceAuthenticated();
+  }
 }
 
 void MessageTransferOperation::OnDisconnected() {
diff --git a/chromeos/ash/components/tether/message_transfer_operation.h b/chromeos/ash/components/tether/message_transfer_operation.h
index b48453f4..f7fc8034 100644
--- a/chromeos/ash/components/tether/message_transfer_operation.h
+++ b/chromeos/ash/components/tether/message_transfer_operation.h
@@ -12,18 +12,10 @@
 #include "base/memory/raw_ptr.h"
 #include "base/timer/timer.h"
 #include "base/unguessable_token.h"
+#include "chromeos/ash/components/tether/host_connection.h"
 #include "chromeos/ash/components/tether/message_wrapper.h"
 #include "chromeos/ash/components/tether/proto/tether.pb.h"
 #include "chromeos/ash/components/tether/tether_host.h"
-#include "chromeos/ash/services/device_sync/public/cpp/device_sync_client.h"
-#include "chromeos/ash/services/secure_channel/public/cpp/client/client_channel.h"
-#include "chromeos/ash/services/secure_channel/public/cpp/client/connection_attempt.h"
-#include "chromeos/ash/services/secure_channel/public/cpp/shared/connection_priority.h"
-#include "chromeos/ash/services/secure_channel/public/mojom/secure_channel.mojom.h"
-
-namespace ash::secure_channel {
-class SecureChannelClient;
-}
 
 namespace cross_device {
 class TimerFactory;
@@ -33,15 +25,12 @@
 
 // Abstract base class used for operations which send and/or receive messages
 // from remote devices.
-class MessageTransferOperation
-    : public secure_channel::ClientChannel::Observer,
-      public secure_channel::ConnectionAttempt::Delegate {
+class MessageTransferOperation : public HostConnection::PayloadListener {
  public:
   MessageTransferOperation(
       const TetherHost& tether_host,
-      secure_channel::ConnectionPriority connection_priority,
-      device_sync::DeviceSyncClient* device_sync_client,
-      secure_channel::SecureChannelClient* secure_channel_client);
+      HostConnection::Factory::ConnectionPriority connection_priority,
+      raw_ptr<HostConnection::Factory> host_connection_factory);
 
   MessageTransferOperation(const MessageTransferOperation&) = delete;
   MessageTransferOperation& operator=(const MessageTransferOperation&) = delete;
@@ -56,25 +45,22 @@
   // Manually ends the operation.
   void StopOperation();
 
-  // Sends |message_wrapper|'s message to |remote_device_| and returns the
-  // associated message's sequence number.
-  int SendMessageToDevice(std::unique_ptr<MessageWrapper> message_wrapper);
-
-  // Callback executed when a device is authenticated (i.e., it is in a state
+  // Callback executed when a host is authenticated (i.e., it is in a state
   // which allows messages to be sent/received). Should be overridden by derived
-  // classes which intend to send a message to |remote_device_| as soon as an
-  // authenticated channel has been established to that device.
+  // classes which intend to send a message to |tether_host_| as soon as an
+  // authenticated channel has been established to that host.
   virtual void OnDeviceAuthenticated() {}
 
-  // Callback executed when a tether protocol message is received. Should be
-  // overridden by derived classes which intend to handle messages received from
-  // |remote_device_|.
-  virtual void OnMessageReceived(
-      std::unique_ptr<MessageWrapper> message_wrapper) {}
+  void SendMessage(std::unique_ptr<MessageWrapper> message_wrapper,
+                   HostConnection::OnMessageSentCallback on_message_sent);
 
-  // Callback executed a tether protocol message is sent. |sequence_number| is
-  // the value returned by SendMessageToDevice().
-  virtual void OnMessageSent(int sequence_number) {}
+  // HostConnection::PayloadListener:
+  void OnMessageReceived(
+      std::unique_ptr<MessageWrapper> message_wrapper) override {}
+
+  void OnConnectionAttemptComplete(
+      std::unique_ptr<HostConnection> host_connection);
+  void OnDisconnected();
 
   // Callback executed when the operation has started (i.e., in Initialize()).
   virtual void OnOperationStarted() {}
@@ -94,9 +80,6 @@
 
   const std::string GetDeviceId(bool truncate_for_logs) const;
 
-  std::unique_ptr<secure_channel::ConnectionAttempt> connection_attempt_;
-  std::unique_ptr<secure_channel::ClientChannel> client_channel_;
-
  private:
   friend class ConnectTetheringOperationTest;
   friend class DisconnectTetheringOperationTest;
@@ -104,16 +87,6 @@
   friend class KeepAliveOperationTest;
   friend class MessageTransferOperationTest;
 
-  // secure_channel::ConnectionAttempt::Delegate:
-  void OnConnectionAttemptFailure(
-      secure_channel::mojom::ConnectionAttemptFailureReason reason) override;
-  void OnConnection(
-      std::unique_ptr<secure_channel::ClientChannel> channel) override;
-
-  // secure_channel::ClientChannel::Observer:
-  void OnDisconnected() override;
-  void OnMessageReceived(const std::string& payload) override;
-
   // The maximum expected time to connect to a remote device, if it can be
   // connected to. This number has been determined by examining metrics.
   static constexpr const uint32_t kConnectionTimeoutSeconds = 15;
@@ -141,9 +114,9 @@
       std::unique_ptr<cross_device::TimerFactory> timer_factory_for_test);
 
   TetherHost tether_host_;
-  raw_ptr<device_sync::DeviceSyncClient> device_sync_client_;
-  raw_ptr<secure_channel::SecureChannelClient> secure_channel_client_;
-  const secure_channel::ConnectionPriority connection_priority_;
+  const HostConnection::Factory::ConnectionPriority connection_priority_;
+  std::unique_ptr<HostConnection> host_connection_;
+  raw_ptr<HostConnection::Factory> host_connection_factory_;
 
   std::unique_ptr<cross_device::TimerFactory> timer_factory_;
 
@@ -151,10 +124,7 @@
   bool shutting_down_ = false;
   MessageType message_type_for_connection_;
 
-  int next_message_sequence_number_ = 0;
-
   std::unique_ptr<base::OneShotTimer> remote_device_timer_;
-
   base::WeakPtrFactory<MessageTransferOperation> weak_ptr_factory_{this};
 };
 
diff --git a/chromeos/ash/components/tether/message_transfer_operation_unittest.cc b/chromeos/ash/components/tether/message_transfer_operation_unittest.cc
index 8d457ef..e61210e8 100644
--- a/chromeos/ash/components/tether/message_transfer_operation_unittest.cc
+++ b/chromeos/ash/components/tether/message_transfer_operation_unittest.cc
@@ -10,13 +10,9 @@
 #include "base/memory/raw_ptr.h"
 #include "base/timer/mock_timer.h"
 #include "chromeos/ash/components/multidevice/remote_device_test_util.h"
+#include "chromeos/ash/components/tether/fake_host_connection.h"
 #include "chromeos/ash/components/tether/message_wrapper.h"
 #include "chromeos/ash/components/tether/proto_test_util.h"
-#include "chromeos/ash/services/device_sync/public/cpp/fake_device_sync_client.h"
-#include "chromeos/ash/services/secure_channel/public/cpp/client/fake_client_channel.h"
-#include "chromeos/ash/services/secure_channel/public/cpp/client/fake_connection_attempt.h"
-#include "chromeos/ash/services/secure_channel/public/cpp/client/fake_secure_channel_client.h"
-#include "chromeos/ash/services/secure_channel/public/cpp/client/secure_channel_client.h"
 #include "components/cross_device/timer_factory/fake_one_shot_timer.h"
 #include "components/cross_device/timer_factory/fake_timer_factory.h"
 #include "testing/gmock/include/gmock/gmock.h"
@@ -32,20 +28,18 @@
 
 const uint32_t kTestTimeoutSeconds = 5;
 
-const char kTetherFeature[] = "magic_tether";
-
 // A test double for MessageTransferOperation is needed because
 // MessageTransferOperation has pure virtual methods which must be overridden in
 // order to create a concrete instantiation of the class.
 class TestOperation : public MessageTransferOperation {
  public:
-  TestOperation(const multidevice::RemoteDeviceRef& device_to_connect,
-                device_sync::DeviceSyncClient* device_sync_client,
-                secure_channel::SecureChannelClient* secure_channel_client)
-      : MessageTransferOperation(TetherHost(device_to_connect),
-                                 secure_channel::ConnectionPriority::kLow,
-                                 device_sync_client,
-                                 secure_channel_client) {}
+  TestOperation(const TetherHost& tether_host,
+                raw_ptr<HostConnection::Factory> host_connection_factory)
+      : MessageTransferOperation(
+            tether_host,
+            HostConnection::Factory::ConnectionPriority::kLow,
+            host_connection_factory) {}
+
   ~TestOperation() override = default;
 
   // MessageTransferOperation:
@@ -68,10 +62,6 @@
     return kTestMessageType;
   }
 
-  void OnMessageSent(int sequence_number) override {
-    last_sequence_number_ = sequence_number;
-  }
-
   uint32_t GetMessageTimeoutSeconds() override { return timeout_seconds_; }
 
   void set_timeout_seconds(uint32_t timeout_seconds) {
@@ -90,22 +80,10 @@
 
   bool has_operation_finished() { return has_operation_finished_; }
 
-  std::optional<int> last_sequence_number() { return last_sequence_number_; }
-
   const std::vector<std::unique_ptr<MessageWrapper>>& get_received_messages() {
     return received_messages_;
   }
 
-  secure_channel::FakeConnectionAttempt* get_connection_attempt() {
-    return static_cast<secure_channel::FakeConnectionAttempt*>(
-        connection_attempt_.get());
-  }
-
-  secure_channel::FakeClientChannel* get_client_channel() {
-    return static_cast<secure_channel::FakeClientChannel*>(
-        client_channel_.get());
-  }
-
  private:
   bool has_device_authenticated_ = false;
   std::vector<std::unique_ptr<MessageWrapper>> received_messages_;
@@ -114,7 +92,6 @@
   bool should_stop_operation_on_message_received_ = false;
   bool has_operation_started_ = false;
   bool has_operation_finished_ = false;
-  std::optional<int> last_sequence_number_;
 };
 
 TetherAvailabilityResponse CreateTetherAvailabilityResponse() {
@@ -137,42 +114,17 @@
 
  protected:
   MessageTransferOperationTest()
-      : test_local_device_(multidevice::RemoteDeviceRefBuilder()
-                               .SetPublicKey("local device")
-                               .Build()),
-        test_device_(multidevice::CreateRemoteDeviceRefForTest()) {}
+      : tether_host_(TetherHost(multidevice::CreateRemoteDeviceRefForTest())) {}
 
   void SetUp() override {
-    fake_device_sync_client_ =
-        std::make_unique<device_sync::FakeDeviceSyncClient>();
-    fake_device_sync_client_->set_local_device_metadata(test_local_device_);
-    fake_secure_channel_client_ =
-        std::make_unique<secure_channel::FakeSecureChannelClient>();
-    // Prepare for connection timeout timers to be made for the remote
-    // device.
-    fake_secure_channel_client_->set_next_listen_connection_attempt(
-        test_device_, test_local_device_,
-        std::make_unique<secure_channel::FakeConnectionAttempt>());
-
-    operation_ = base::WrapUnique(
-        new TestOperation(test_device_, fake_device_sync_client_.get(),
-                          fake_secure_channel_client_.get()));
+    fake_host_connection_factory_ =
+        std::make_unique<FakeHostConnection::Factory>();
+    operation_ = std::make_unique<TestOperation>(
+        tether_host_, fake_host_connection_factory_.get());
     operation_->SetTimerFactoryForTest(
         std::make_unique<cross_device::FakeTimerFactory>());
     VerifyOperationStartedAndFinished(false /* has_started */,
                                       false /* has_finished */);
-    operation_->Initialize();
-
-    for (const auto* arguments :
-         fake_secure_channel_client_
-             ->last_listen_for_connection_request_arguments_list()) {
-      EXPECT_EQ(kTetherFeature, arguments->feature);
-    }
-
-    VerifyOperationStartedAndFinished(true /* has_started */,
-                                      false /* has_finished */);
-
-    VerifyConnectionTimerCreated();
   }
 
   void VerifyOperationStartedAndFinished(bool has_started, bool has_finished) {
@@ -180,11 +132,6 @@
     EXPECT_EQ(has_finished, operation_->has_operation_finished());
   }
 
-  void CreateAuthenticatedChannel() {
-    operation_->get_connection_attempt()->NotifyConnection(
-        std::make_unique<secure_channel::FakeClientChannel>());
-  }
-
   cross_device::FakeOneShotTimer* GetOperationTimer() {
     return static_cast<cross_device::FakeOneShotTimer*>(
         operation_->remote_device_timer_.get());
@@ -202,23 +149,22 @@
               GetOperationTimer()->GetCurrentDelay());
   }
 
-  int SendMessageToDevice(std::unique_ptr<MessageWrapper> message_wrapper) {
-    return operation_->SendMessageToDevice(std::move(message_wrapper));
+  void SendMessageToDevice(std::unique_ptr<MessageWrapper> message_wrapper) {
+    return operation_->SendMessage(std::move(message_wrapper),
+                                   base::DoNothing());
   }
 
-  const multidevice::RemoteDeviceRef test_local_device_;
-  const multidevice::RemoteDeviceRef test_device_;
+  const TetherHost tether_host_;
 
-  std::unique_ptr<device_sync::FakeDeviceSyncClient> fake_device_sync_client_;
-  std::unique_ptr<secure_channel::FakeSecureChannelClient>
-      fake_secure_channel_client_;
+  std::unique_ptr<FakeHostConnection::Factory> fake_host_connection_factory_;
   std::unique_ptr<TestOperation> operation_;
 };
 
 TEST_F(MessageTransferOperationTest, TestFailedConnection) {
-  operation_->get_connection_attempt()->NotifyConnectionAttemptFailure(
-      secure_channel::mojom::ConnectionAttemptFailureReason::
-          AUTHENTICATION_ERROR);
+  fake_host_connection_factory_->SetupConnectionAttempt(
+      tether_host_.GetDeviceId(), /*host_connection=*/nullptr);
+
+  operation_->Initialize();
 
   VerifyOperationStartedAndFinished(true /* has_started */,
                                     true /* has_finished */);
@@ -232,25 +178,25 @@
   // device.
   operation_->set_should_stop_operation_on_message_received(true);
 
-  CreateAuthenticatedChannel();
+  auto* host_connection =
+      fake_host_connection_factory_->SetupConnectionAttempt(tether_host_);
+
+  operation_->Initialize();
   EXPECT_TRUE(operation_->has_device_authenticated());
   VerifyDefaultTimerCreated();
 
   auto message_wrapper =
       std::make_unique<MessageWrapper>(TetherAvailabilityRequest());
   std::string expected_payload = message_wrapper->ToRawMessage();
-  int sequence_number = SendMessageToDevice(std::move(message_wrapper));
-  std::vector<std::pair<std::string, base::OnceClosure>>& sent_messages =
-      operation_->get_client_channel()->sent_messages();
+  SendMessageToDevice(std::move(message_wrapper));
+  const std::vector<std::pair<std::unique_ptr<MessageWrapper>,
+                              base::OnceClosure>>& sent_messages =
+      host_connection->sent_messages();
   EXPECT_EQ(1u, sent_messages.size());
-  EXPECT_EQ(expected_payload, sent_messages[0].first);
+  EXPECT_EQ(expected_payload, sent_messages[0].first->ToRawMessage());
 
-  EXPECT_FALSE(operation_->last_sequence_number());
-  std::move(sent_messages[0].second).Run();
-  EXPECT_EQ(sequence_number, operation_->last_sequence_number());
-
-  operation_->get_client_channel()->NotifyMessageReceived(
-      MessageWrapper(CreateTetherAvailabilityResponse()).ToRawMessage());
+  host_connection->ReceiveMessage(
+      std::make_unique<MessageWrapper>(CreateTetherAvailabilityResponse()));
 
   EXPECT_EQ(1u, operation_->get_received_messages().size());
   const auto& message = operation_->get_received_messages()[0];
@@ -261,12 +207,16 @@
 }
 
 TEST_F(MessageTransferOperationTest, TestTimesOutBeforeAuthentication) {
+  operation_->Initialize();
   GetOperationTimer()->Fire();
   EXPECT_TRUE(operation_->has_operation_finished());
 }
 
 TEST_F(MessageTransferOperationTest, TestAuthenticatesButThenTimesOut) {
-  CreateAuthenticatedChannel();
+  fake_host_connection_factory_->SetupConnectionAttempt(tether_host_);
+
+  operation_->Initialize();
+
   EXPECT_TRUE(operation_->has_device_authenticated());
   VerifyDefaultTimerCreated();
 
diff --git a/chromeos/ash/components/tether/secure_channel_host_connection_unittest.cc b/chromeos/ash/components/tether/secure_channel_host_connection_unittest.cc
index 4fc7158..5ba9e178 100644
--- a/chromeos/ash/components/tether/secure_channel_host_connection_unittest.cc
+++ b/chromeos/ash/components/tether/secure_channel_host_connection_unittest.cc
@@ -22,7 +22,7 @@
 class FakeHostConnectionPayloadListener
     : public HostConnection::PayloadListener {
  public:
-  virtual ~FakeHostConnectionPayloadListener() = default;
+  ~FakeHostConnectionPayloadListener() override = default;
 
   // HostConnection::PayloadListener:
   void OnMessageReceived(std::unique_ptr<MessageWrapper> message) override {
diff --git a/chromeos/ash/components/tether/secure_channel_tether_availability_operation_orchestrator.cc b/chromeos/ash/components/tether/secure_channel_tether_availability_operation_orchestrator.cc
index 412c5a0..376d976 100644
--- a/chromeos/ash/components/tether/secure_channel_tether_availability_operation_orchestrator.cc
+++ b/chromeos/ash/components/tether/secure_channel_tether_availability_operation_orchestrator.cc
@@ -11,12 +11,12 @@
 SecureChannelTetherAvailabilityOperationOrchestrator::Factory::Factory(
     raw_ptr<TetherHostFetcher> tether_host_fetcher,
     raw_ptr<device_sync::DeviceSyncClient> device_sync_client,
-    raw_ptr<secure_channel::SecureChannelClient> secure_channel_client,
+    raw_ptr<HostConnection::Factory> host_connection_factory,
     raw_ptr<TetherHostResponseRecorder> tether_host_response_recorder,
     raw_ptr<ConnectionPreserver> connection_preserver)
     : device_sync_client_(device_sync_client),
-      secure_channel_client_(secure_channel_client),
       tether_host_response_recorder_(tether_host_response_recorder),
+      host_connection_factory_(host_connection_factory),
       connection_preserver_(connection_preserver),
       tether_host_fetcher_(tether_host_fetcher) {}
 
@@ -28,8 +28,8 @@
     CreateInstance() {
   return std::make_unique<SecureChannelTetherAvailabilityOperationOrchestrator>(
       std::make_unique<TetherAvailabilityOperation::Initializer>(
-          device_sync_client_, secure_channel_client_,
-          tether_host_response_recorder_, connection_preserver_),
+          host_connection_factory_, tether_host_response_recorder_,
+          connection_preserver_),
       tether_host_fetcher_);
 }
 
diff --git a/chromeos/ash/components/tether/secure_channel_tether_availability_operation_orchestrator.h b/chromeos/ash/components/tether/secure_channel_tether_availability_operation_orchestrator.h
index d6e140f..b90974b 100644
--- a/chromeos/ash/components/tether/secure_channel_tether_availability_operation_orchestrator.h
+++ b/chromeos/ash/components/tether/secure_channel_tether_availability_operation_orchestrator.h
@@ -7,6 +7,7 @@
 
 #include "chromeos/ash/components/tether/tether_availability_operation_orchestrator.h"
 #include "chromeos/ash/components/tether/tether_host_fetcher.h"
+#include "chromeos/ash/services/device_sync/public/cpp/device_sync_client.h"
 
 namespace ash::tether {
 
@@ -17,7 +18,7 @@
    public:
     Factory(raw_ptr<TetherHostFetcher> tether_host_fetcher,
             raw_ptr<device_sync::DeviceSyncClient> device_sync_client,
-            raw_ptr<secure_channel::SecureChannelClient> secure_channel_client,
+            raw_ptr<HostConnection::Factory> host_connection_factory,
             raw_ptr<TetherHostResponseRecorder> tether_host_response_recorder,
             raw_ptr<ConnectionPreserver> connection_preserver);
 
@@ -29,8 +30,8 @@
 
    private:
     raw_ptr<device_sync::DeviceSyncClient> device_sync_client_;
-    raw_ptr<secure_channel::SecureChannelClient> secure_channel_client_;
     raw_ptr<TetherHostResponseRecorder> tether_host_response_recorder_;
+    raw_ptr<HostConnection::Factory> host_connection_factory_;
     raw_ptr<ConnectionPreserver> connection_preserver_;
     raw_ptr<TetherHostFetcher> tether_host_fetcher_;
   };
diff --git a/chromeos/ash/components/tether/secure_channel_tether_availability_operation_orchestrator_unittest.cc b/chromeos/ash/components/tether/secure_channel_tether_availability_operation_orchestrator_unittest.cc
index 1e455f2..f6cc66a 100644
--- a/chromeos/ash/components/tether/secure_channel_tether_availability_operation_orchestrator_unittest.cc
+++ b/chromeos/ash/components/tether/secure_channel_tether_availability_operation_orchestrator_unittest.cc
@@ -79,6 +79,8 @@
       TetherHost(multidevice::CreateRemoteDeviceRefForTest());
   FakeTetherHostFetcher fake_tether_host_fetcher(
       *test_device.remote_device_ref());
+  fake_tether_host_fetcher.SetTetherHost(
+      test_device.remote_device_ref().value());
   FakeTetherAvailabilityOperation::Initializer*
       fake_tether_availability_operation_initializer =
           new FakeTetherAvailabilityOperation::Initializer();
diff --git a/chromeos/ash/components/tether/synchronous_shutdown_object_container_impl.cc b/chromeos/ash/components/tether/synchronous_shutdown_object_container_impl.cc
index 671707d..e2188a1 100644
--- a/chromeos/ash/components/tether/synchronous_shutdown_object_container_impl.cc
+++ b/chromeos/ash/components/tether/synchronous_shutdown_object_container_impl.cc
@@ -35,9 +35,7 @@
 #include "components/cross_device/timer_factory/timer_factory.h"
 #include "components/cross_device/timer_factory/timer_factory_impl.h"
 
-namespace ash {
-
-namespace tether {
+namespace ash::tether {
 
 // static
 SynchronousShutdownObjectContainerImpl::Factory*
@@ -124,8 +122,7 @@
           top_level_host_scan_cache_.get(),
           active_host_.get())),
       keep_alive_scheduler_(std::make_unique<KeepAliveScheduler>(
-          device_sync_client,
-          secure_channel_client,
+          asychronous_container->host_connection_factory(),
           active_host_.get(),
           top_level_host_scan_cache_.get(),
           device_id_tether_network_guid_map_.get())),
@@ -144,7 +141,7 @@
               SecureChannelTetherAvailabilityOperationOrchestrator::Factory>(
               asychronous_container->tether_host_fetcher(),
               device_sync_client,
-              secure_channel_client,
+              asychronous_container->host_connection_factory(),
               tether_host_response_recorder_.get(),
               connection_preserver_.get()),
           network_state_handler_,
@@ -161,8 +158,7 @@
       host_connection_metrics_logger_(
           std::make_unique<HostConnectionMetricsLogger>(active_host_.get())),
       tether_connector_(std::make_unique<TetherConnectorImpl>(
-          device_sync_client,
-          secure_channel_client,
+          asychronous_container->host_connection_factory(),
           network_state_handler_,
           wifi_hotspot_connector_.get(),
           active_host_.get(),
@@ -220,6 +216,4 @@
   return tether_disconnector_.get();
 }
 
-}  // namespace tether
-
-}  // namespace ash
+}  // namespace ash::tether
diff --git a/chromeos/ash/components/tether/tether_availability_operation.cc b/chromeos/ash/components/tether/tether_availability_operation.cc
index 3b267f7..6eb55d1 100644
--- a/chromeos/ash/components/tether/tether_availability_operation.cc
+++ b/chromeos/ash/components/tether/tether_availability_operation.cc
@@ -17,7 +17,6 @@
 #include "chromeos/ash/components/tether/message_wrapper.h"
 #include "chromeos/ash/components/tether/proto/tether.pb.h"
 #include "chromeos/ash/components/tether/tether_host_response_recorder.h"
-#include "chromeos/ash/services/secure_channel/public/cpp/client/secure_channel_client.h"
 
 namespace ash::tether {
 
@@ -75,12 +74,10 @@
 }  // namespace
 
 TetherAvailabilityOperation::Initializer::Initializer(
-    raw_ptr<device_sync::DeviceSyncClient> device_sync_client,
-    raw_ptr<secure_channel::SecureChannelClient> secure_channel_client,
+    raw_ptr<HostConnection::Factory> host_connection_factory,
     raw_ptr<TetherHostResponseRecorder> tether_host_response_recorder,
     raw_ptr<ConnectionPreserver> connection_preserver)
-    : device_sync_client_(device_sync_client),
-      secure_channel_client_(secure_channel_client),
+    : host_connection_factory_(host_connection_factory),
       tether_host_response_recorder_(tether_host_response_recorder),
       connection_preserver_(connection_preserver) {}
 
@@ -92,9 +89,8 @@
     TetherAvailabilityOperation::OnTetherAvailabilityOperationFinishedCallback
         callback) {
   auto operation = std::make_unique<TetherAvailabilityOperation>(
-      tether_host, std::move(callback), device_sync_client_,
-      secure_channel_client_, tether_host_response_recorder_,
-      connection_preserver_);
+      tether_host, std::move(callback), host_connection_factory_,
+      tether_host_response_recorder_, connection_preserver_);
   operation->Initialize();
   return operation;
 }
@@ -103,14 +99,13 @@
     const TetherHost& tether_host,
     TetherAvailabilityOperation::OnTetherAvailabilityOperationFinishedCallback
         callback,
-    device_sync::DeviceSyncClient* device_sync_client,
-    secure_channel::SecureChannelClient* secure_channel_client,
+    raw_ptr<HostConnection::Factory> host_connection_factory,
     TetherHostResponseRecorder* tether_host_response_recorder,
     ConnectionPreserver* connection_preserver)
-    : MessageTransferOperation(tether_host,
-                               secure_channel::ConnectionPriority::kLow,
-                               device_sync_client,
-                               secure_channel_client),
+    : MessageTransferOperation(
+          tether_host,
+          HostConnection::Factory::ConnectionPriority::kLow,
+          host_connection_factory),
       tether_host_(tether_host),
       tether_host_response_recorder_(tether_host_response_recorder),
       connection_preserver_(connection_preserver),
@@ -125,8 +120,8 @@
   tether_availability_request_start_time_ = clock_->Now();
   PA_LOG(VERBOSE) << "Sending TetherAvailabilityRequest message to "
                   << GetDeviceId(/*truncate_for_logs=*/true) << ".";
-  SendMessageToDevice(
-      std::make_unique<MessageWrapper>(TetherAvailabilityRequest()));
+  SendMessage(std::make_unique<MessageWrapper>(TetherAvailabilityRequest()),
+              /*on_message_sent=*/base::DoNothing());
 }
 
 void TetherAvailabilityOperation::OnMessageReceived(
diff --git a/chromeos/ash/components/tether/tether_availability_operation.h b/chromeos/ash/components/tether/tether_availability_operation.h
index acd873f78..6273e3f 100644
--- a/chromeos/ash/components/tether/tether_availability_operation.h
+++ b/chromeos/ash/components/tether/tether_availability_operation.h
@@ -17,14 +17,6 @@
 #include "chromeos/ash/components/tether/scanned_device_info.h"
 #include "chromeos/ash/components/tether/tether_host.h"
 
-namespace ash::device_sync {
-class DeviceSyncClient;
-}
-
-namespace ash::secure_channel {
-class SecureChannelClient;
-}
-
 namespace ash::tether {
 
 class ConnectionPreserver;
@@ -44,8 +36,7 @@
   class Initializer {
    public:
     Initializer(
-        raw_ptr<device_sync::DeviceSyncClient> device_sync_client,
-        raw_ptr<secure_channel::SecureChannelClient> secure_channel_client,
+        raw_ptr<HostConnection::Factory> host_connection_factory,
         raw_ptr<TetherHostResponseRecorder> tether_host_response_recorder,
         raw_ptr<ConnectionPreserver> connection_preserver);
 
@@ -56,8 +47,7 @@
     virtual ~Initializer();
 
    private:
-    raw_ptr<device_sync::DeviceSyncClient> device_sync_client_;
-    raw_ptr<secure_channel::SecureChannelClient> secure_channel_client_;
+    raw_ptr<HostConnection::Factory> host_connection_factory_;
     raw_ptr<TetherHostResponseRecorder> tether_host_response_recorder_;
     raw_ptr<ConnectionPreserver> connection_preserver_;
   };
@@ -69,8 +59,7 @@
   TetherAvailabilityOperation(
       const TetherHost& tether_host,
       OnTetherAvailabilityOperationFinishedCallback on_operation_finished,
-      device_sync::DeviceSyncClient* device_sync_client,
-      secure_channel::SecureChannelClient* secure_channel_client,
+      raw_ptr<HostConnection::Factory> host_connection_factory,
       TetherHostResponseRecorder* tether_host_response_recorder,
       ConnectionPreserver* connection_preserver);
 
@@ -104,8 +93,6 @@
   FRIEND_TEST_ALL_PREFIXES(TetherAvailabilityOperationTest,
                            TestMultipleDevices);
 
-  using MessageTransferOperation::StopOperation;
-
   void SetTestDoubles(base::Clock* clock_for_test,
                       scoped_refptr<base::TaskRunner> test_task_runner);
   void RecordTetherAvailabilityResponseDuration(const std::string device_id);
diff --git a/chromeos/ash/components/tether/tether_availability_operation_orchestrator.cc b/chromeos/ash/components/tether/tether_availability_operation_orchestrator.cc
index e0f81e71..48a6fc2 100644
--- a/chromeos/ash/components/tether/tether_availability_operation_orchestrator.cc
+++ b/chromeos/ash/components/tether/tether_availability_operation_orchestrator.cc
@@ -5,6 +5,7 @@
 #include "chromeos/ash/components/tether/tether_availability_operation_orchestrator.h"
 
 #include "base/containers/contains.h"
+#include "chromeos/ash/components/multidevice/logging/logging.h"
 
 namespace ash::tether {
 
diff --git a/chromeos/ash/components/tether/tether_availability_operation_orchestrator.h b/chromeos/ash/components/tether/tether_availability_operation_orchestrator.h
index 55bb705..527e230 100644
--- a/chromeos/ash/components/tether/tether_availability_operation_orchestrator.h
+++ b/chromeos/ash/components/tether/tether_availability_operation_orchestrator.h
@@ -5,6 +5,7 @@
 #ifndef CHROMEOS_ASH_COMPONENTS_TETHER_TETHER_AVAILABILITY_OPERATION_ORCHESTRATOR_H_
 #define CHROMEOS_ASH_COMPONENTS_TETHER_TETHER_AVAILABILITY_OPERATION_ORCHESTRATOR_H_
 
+#include "base/containers/flat_map.h"
 #include "base/observer_list.h"
 #include "chromeos/ash/components/tether/scanned_device_info.h"
 #include "chromeos/ash/components/tether/tether_availability_operation.h"
diff --git a/chromeos/ash/components/tether/tether_availability_operation_unittest.cc b/chromeos/ash/components/tether/tether_availability_operation_unittest.cc
index a0cfee4..7cb4c1d0 100644
--- a/chromeos/ash/components/tether/tether_availability_operation_unittest.cc
+++ b/chromeos/ash/components/tether/tether_availability_operation_unittest.cc
@@ -16,14 +16,11 @@
 #include "base/test/test_simple_task_runner.h"
 #include "chromeos/ash/components/multidevice/remote_device_test_util.h"
 #include "chromeos/ash/components/tether/fake_connection_preserver.h"
+#include "chromeos/ash/components/tether/fake_host_connection.h"
 #include "chromeos/ash/components/tether/message_wrapper.h"
 #include "chromeos/ash/components/tether/mock_tether_host_response_recorder.h"
 #include "chromeos/ash/components/tether/proto/tether.pb.h"
 #include "chromeos/ash/components/tether/proto_test_util.h"
-#include "chromeos/ash/services/device_sync/public/cpp/fake_device_sync_client.h"
-#include "chromeos/ash/services/secure_channel/public/cpp/client/fake_client_channel.h"
-#include "chromeos/ash/services/secure_channel/public/cpp/client/fake_connection_attempt.h"
-#include "chromeos/ash/services/secure_channel/public/cpp/client/fake_secure_channel_client.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 using testing::_;
@@ -51,46 +48,22 @@
 
  protected:
   TetherAvailabilityOperationTest()
-      : local_device_(multidevice::RemoteDeviceRefBuilder()
-                          .SetPublicKey("local device")
-                          .Build()),
-        remote_device_(multidevice::CreateRemoteDeviceRefListForTest(1)[0]) {}
+      : tether_host_(TetherHost(multidevice::CreateRemoteDeviceRefForTest())) {}
 
   void SetUp() override {
-    fake_device_sync_client_.set_local_device_metadata(local_device_);
+    fake_host_connection_factory_ =
+        std::make_unique<FakeHostConnection::Factory>();
 
-    operation_ = ConstructOperation();
-    operation_->Initialize();
-
-    ConnectAuthenticatedChannelForDevice(remote_device_);
-  }
-
-  std::unique_ptr<TetherAvailabilityOperation> ConstructOperation() {
-    auto connection_attempt =
-        std::make_unique<secure_channel::FakeConnectionAttempt>();
-    connection_attempt_ = connection_attempt.get();
-    fake_secure_channel_client_.set_next_listen_connection_attempt(
-        remote_device_, local_device_, std::move(connection_attempt));
-
-    auto operation = std::make_unique<TetherAvailabilityOperation>(
-        TetherHost(remote_device_),
+    operation_ = std::make_unique<TetherAvailabilityOperation>(
+        tether_host_,
         base::BindOnce(&TetherAvailabilityOperationTest::OnResponse,
                        weak_ptr_factory_.GetWeakPtr()),
-        &fake_device_sync_client_, &fake_secure_channel_client_,
+        fake_host_connection_factory_.get(),
         &mock_tether_host_response_recorder_, &fake_connection_preserver_);
 
     test_clock_.SetNow(base::Time::UnixEpoch());
     test_task_runner_ = base::MakeRefCounted<base::TestSimpleTaskRunner>();
-    operation->SetTestDoubles(&test_clock_, test_task_runner_);
-
-    return operation;
-  }
-
-  void ConnectAuthenticatedChannelForDevice(
-      multidevice::RemoteDeviceRef remote_device) {
-    auto fake_client_channel =
-        std::make_unique<secure_channel::FakeClientChannel>();
-    connection_attempt_->NotifyConnection(std::move(fake_client_channel));
+    operation_->SetTestDoubles(&test_clock_, test_task_runner_);
   }
 
   void OnResponse(std::optional<ScannedDeviceInfo> result) {
@@ -98,13 +71,9 @@
   }
 
   std::optional<ScannedDeviceInfo> received_result_;
-  const multidevice::RemoteDeviceRef local_device_;
-  const multidevice::RemoteDeviceRef remote_device_;
+  const TetherHost tether_host_;
 
-  raw_ptr<secure_channel::FakeConnectionAttempt, DanglingUntriaged>
-      connection_attempt_;
-  device_sync::FakeDeviceSyncClient fake_device_sync_client_;
-  secure_channel::FakeSecureChannelClient fake_secure_channel_client_;
+  std::unique_ptr<FakeHostConnection::Factory> fake_host_connection_factory_;
   StrictMock<MockTetherHostResponseRecorder>
       mock_tether_host_response_recorder_;
   FakeConnectionPreserver fake_connection_preserver_;
@@ -120,29 +89,30 @@
 
 TEST_F(TetherAvailabilityOperationTest,
        SendsTetherAvailabilityRequestOnceAuthenticated) {
-  std::unique_ptr<TetherAvailabilityOperation> operation = ConstructOperation();
-  operation->Initialize();
+  // Setup the host connection.
+  auto* fake_host_connection =
+      fake_host_connection_factory_->SetupConnectionAttempt(tether_host_);
 
-  // Create the client channel to the remote device.
-  auto fake_client_channel =
-      std::make_unique<secure_channel::FakeClientChannel>();
-
-  // No requests as a result of creating the client channel.
-  auto& sent_messages = fake_client_channel->sent_messages();
-  EXPECT_EQ(0u, sent_messages.size());
-
-  // Connect and authenticate the client channel.
-  connection_attempt_->NotifyConnection(std::move(fake_client_channel));
+  // Start the operation.
+  operation_->Initialize();
 
   // Verify the TetherAvailabilityRequest message is sent.
   auto message_wrapper =
       std::make_unique<MessageWrapper>(TetherAvailabilityRequest());
   std::string expected_payload = message_wrapper->ToRawMessage();
+
+  auto& sent_messages = fake_host_connection->sent_messages();
   EXPECT_EQ(1u, sent_messages.size());
-  EXPECT_EQ(expected_payload, sent_messages[0].first);
+  EXPECT_EQ(expected_payload, sent_messages[0].first->ToRawMessage());
 }
 
 TEST_F(TetherAvailabilityOperationTest, RecordsResponseDuration) {
+  // Setup the host connection.
+  fake_host_connection_factory_->SetupConnectionAttempt(tether_host_);
+
+  // Start the operation.
+  operation_->Initialize();
+
   static constexpr base::TimeDelta kTetherAvailabilityResponseTime =
       base::Seconds(3);
 
@@ -166,6 +136,11 @@
       TetherAvailabilityResponse_ResponseCode_UNKNOWN_ERROR,
       TetherAvailabilityResponse_ResponseCode_NO_RECEPTION,
       TetherAvailabilityResponse_ResponseCode_NO_SIM_CARD};
+  // Setup the host connection.
+  fake_host_connection_factory_->SetupConnectionAttempt(tether_host_);
+
+  // Start the operation.
+  operation_->Initialize();
 
   for (auto response_code : kErrorResponseCodes) {
     // No response should be recorded.
@@ -244,12 +219,12 @@
   // The scanned device is recorded.
   EXPECT_CALL(
       mock_tether_host_response_recorder_,
-      RecordSuccessfulTetherAvailabilityResponse(remote_device_.GetDeviceId()));
+      RecordSuccessfulTetherAvailabilityResponse(tether_host_.GetDeviceId()));
 
   // The observer is notified of the scanned device.
   DeviceStatus device_status = CreateFakeDeviceStatus();
   ScannedDeviceInfo scanned_device(
-      remote_device_.GetDeviceId(), remote_device_.name(), device_status,
+      tether_host_.GetDeviceId(), tether_host_.GetName(), device_status,
       false /* setup_required */, /*notifications_enabled=*/true);
 
   // Respond with TETHER_AVAILABLE response code and the device info and status.
@@ -263,7 +238,7 @@
   test_task_runner_->RunUntilIdle();
 
   // Connection is preserved.
-  EXPECT_EQ(remote_device_.GetDeviceId(),
+  EXPECT_EQ(tether_host_.GetDeviceId(),
             fake_connection_preserver_
                 .last_requested_preserved_connection_device_id());
 
@@ -275,12 +250,12 @@
   // The scanned device is recorded.
   EXPECT_CALL(
       mock_tether_host_response_recorder_,
-      RecordSuccessfulTetherAvailabilityResponse(remote_device_.GetDeviceId()));
+      RecordSuccessfulTetherAvailabilityResponse(tether_host_.GetDeviceId()));
 
   // The observer is notified of the scanned device.
   DeviceStatus device_status = CreateFakeDeviceStatus();
   ScannedDeviceInfo scanned_device(
-      remote_device_.GetDeviceId(), remote_device_.name(), device_status,
+      tether_host_.GetDeviceId(), tether_host_.GetName(), device_status,
       false /* setup_required */, /*notifications_enabled=*/true);
   std::vector<ScannedDeviceInfo> scanned_devices({scanned_device});
 
@@ -293,7 +268,7 @@
   operation_->OnMessageReceived(std::move(message));
 
   // Connection is preserved.
-  EXPECT_EQ(remote_device_.GetDeviceId(),
+  EXPECT_EQ(tether_host_.GetDeviceId(),
             fake_connection_preserver_
                 .last_requested_preserved_connection_device_id());
 
@@ -306,13 +281,13 @@
   // The scanned device is recorded.
   EXPECT_CALL(
       mock_tether_host_response_recorder_,
-      RecordSuccessfulTetherAvailabilityResponse(remote_device_.GetDeviceId()));
+      RecordSuccessfulTetherAvailabilityResponse(tether_host_.GetDeviceId()));
 
   // The observer is notified that the scanned device has the |setup_required|
   // flag set.
   DeviceStatus device_status = CreateFakeDeviceStatus();
   ScannedDeviceInfo scanned_device(
-      remote_device_.GetDeviceId(), remote_device_.name(), device_status,
+      tether_host_.GetDeviceId(), tether_host_.GetName(), device_status,
       true /* setup_required */, /*notifications_enabled=*/true);
   std::vector<ScannedDeviceInfo> scanned_devices({scanned_device});
 
@@ -327,7 +302,7 @@
   test_task_runner_->RunUntilIdle();
 
   // Connection is preserved.
-  EXPECT_EQ(remote_device_.GetDeviceId(),
+  EXPECT_EQ(tether_host_.GetDeviceId(),
             fake_connection_preserver_
                 .last_requested_preserved_connection_device_id());
   EXPECT_EQ(scanned_device, received_result_.value());
diff --git a/chromeos/ash/components/tether/tether_connector_impl.cc b/chromeos/ash/components/tether/tether_connector_impl.cc
index 932e3e91..f3c5464 100644
--- a/chromeos/ash/components/tether/tether_connector_impl.cc
+++ b/chromeos/ash/components/tether/tether_connector_impl.cc
@@ -24,7 +24,6 @@
 #include "chromeos/ash/components/tether/tether_host_response_recorder.h"
 #include "chromeos/ash/components/tether/wifi_hotspot_connector.h"
 #include "chromeos/ash/components/tether/wifi_hotspot_disconnector.h"
-#include "chromeos/ash/services/secure_channel/public/cpp/client/secure_channel_client.h"
 
 namespace ash::tether {
 
@@ -83,8 +82,7 @@
 }  // namespace
 
 TetherConnectorImpl::TetherConnectorImpl(
-    device_sync::DeviceSyncClient* device_sync_client,
-    secure_channel::SecureChannelClient* secure_channel_client,
+    raw_ptr<HostConnection::Factory> host_connection_factory,
     NetworkStateHandler* network_state_handler,
     WifiHotspotConnector* wifi_hotspot_connector,
     ActiveHost* active_host,
@@ -96,8 +94,7 @@
     HostConnectionMetricsLogger* host_connection_metrics_logger,
     DisconnectTetheringRequestSender* disconnect_tethering_request_sender,
     WifiHotspotDisconnector* wifi_hotspot_disconnector)
-    : device_sync_client_(device_sync_client),
-      secure_channel_client_(secure_channel_client),
+    : host_connection_factory_(host_connection_factory),
       network_state_handler_(network_state_handler),
       wifi_hotspot_connector_(wifi_hotspot_connector),
       active_host_(active_host),
@@ -170,8 +167,7 @@
   }
 
   connect_tethering_operation_ = ConnectTetheringOperation::Factory::Create(
-      TetherHost(*tether_host_to_connect), device_sync_client_,
-      secure_channel_client_,
+      TetherHost(*tether_host_to_connect), host_connection_factory_,
       host_scan_cache_->DoesHostRequireSetup(tether_network_guid));
   connect_tethering_operation_->AddObserver(this);
   connect_tethering_operation_->Initialize();
diff --git a/chromeos/ash/components/tether/tether_connector_impl.h b/chromeos/ash/components/tether/tether_connector_impl.h
index 81a9f54..61aa0bf5 100644
--- a/chromeos/ash/components/tether/tether_connector_impl.h
+++ b/chromeos/ash/components/tether/tether_connector_impl.h
@@ -13,20 +13,13 @@
 #include "base/types/expected.h"
 #include "chromeos/ash/components/network/network_connection_handler.h"
 #include "chromeos/ash/components/tether/connect_tethering_operation.h"
+#include "chromeos/ash/components/tether/host_connection.h"
 #include "chromeos/ash/components/tether/host_connection_metrics_logger.h"
 #include "chromeos/ash/components/tether/tether_connector.h"
 #include "chromeos/ash/components/tether/wifi_hotspot_connector.h"
 
 namespace ash {
 
-namespace device_sync {
-class DeviceSyncClient;
-}
-
-namespace secure_channel {
-class SecureChannelClient;
-}
-
 class NetworkStateHandler;
 
 namespace tether {
@@ -50,8 +43,7 @@
                             public ConnectTetheringOperation::Observer {
  public:
   TetherConnectorImpl(
-      device_sync::DeviceSyncClient* device_sync_client,
-      secure_channel::SecureChannelClient* secure_channel_client,
+      raw_ptr<HostConnection::Factory> host_connection_factory,
       NetworkStateHandler* network_state_handler,
       WifiHotspotConnector* wifi_hotspot_connector,
       ActiveHost* active_host,
@@ -99,8 +91,7 @@
       const std::string& device_id,
       ConnectTetheringOperation::HostResponseErrorCode error_code);
 
-  raw_ptr<device_sync::DeviceSyncClient> device_sync_client_;
-  raw_ptr<secure_channel::SecureChannelClient> secure_channel_client_;
+  raw_ptr<HostConnection::Factory> host_connection_factory_;
   raw_ptr<NetworkConnectionHandler> network_connection_handler_;
   raw_ptr<NetworkStateHandler> network_state_handler_;
   raw_ptr<WifiHotspotConnector> wifi_hotspot_connector_;
diff --git a/chromeos/ash/components/tether/tether_connector_impl_unittest.cc b/chromeos/ash/components/tether/tether_connector_impl_unittest.cc
index 5e0cb5f..4c8d365 100644
--- a/chromeos/ash/components/tether/tether_connector_impl_unittest.cc
+++ b/chromeos/ash/components/tether/tether_connector_impl_unittest.cc
@@ -22,6 +22,7 @@
 #include "chromeos/ash/components/tether/device_id_tether_network_guid_map.h"
 #include "chromeos/ash/components/tether/fake_active_host.h"
 #include "chromeos/ash/components/tether/fake_disconnect_tethering_request_sender.h"
+#include "chromeos/ash/components/tether/fake_host_connection.h"
 #include "chromeos/ash/components/tether/fake_host_scan_cache.h"
 #include "chromeos/ash/components/tether/fake_notification_presenter.h"
 #include "chromeos/ash/components/tether/fake_tether_host_fetcher.h"
@@ -30,9 +31,6 @@
 #include "chromeos/ash/components/tether/host_connection_metrics_logger.h"
 #include "chromeos/ash/components/tether/mock_host_connection_metrics_logger.h"
 #include "chromeos/ash/components/tether/mock_tether_host_response_recorder.h"
-#include "chromeos/ash/services/device_sync/public/cpp/fake_device_sync_client.h"
-#include "chromeos/ash/services/secure_channel/public/cpp/client/fake_secure_channel_client.h"
-#include "chromeos/ash/services/secure_channel/public/cpp/client/secure_channel_client.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/cros_system_api/dbus/shill/dbus-constants.h"
@@ -68,12 +66,10 @@
  public:
   FakeConnectTetheringOperation(
       const TetherHost& tether_host,
-      device_sync::DeviceSyncClient* device_sync_client,
-      secure_channel::SecureChannelClient* secure_channel_client,
+      HostConnection::Factory* host_connection_factory,
       bool setup_required)
       : ConnectTetheringOperation(tether_host,
-                                  device_sync_client,
-                                  secure_channel_client,
+                                  host_connection_factory,
                                   setup_required),
         tether_host_(tether_host),
         setup_required_(setup_required) {}
@@ -118,12 +114,10 @@
   // ConnectTetheringOperation::Factory:
   std::unique_ptr<ConnectTetheringOperation> CreateInstance(
       const TetherHost& tether_host,
-      device_sync::DeviceSyncClient* device_sync_client,
-      secure_channel::SecureChannelClient* secure_channel_client,
+      raw_ptr<HostConnection::Factory> host_connection_factory,
       bool setup_required) override {
     FakeConnectTetheringOperation* operation =
-        new FakeConnectTetheringOperation(tether_host, device_sync_client,
-                                          secure_channel_client,
+        new FakeConnectTetheringOperation(tether_host, host_connection_factory,
                                           setup_required);
     created_operations_.push_back(operation);
     return base::WrapUnique(operation);
@@ -154,11 +148,6 @@
         base::WrapUnique(new FakeConnectTetheringOperationFactory());
     ConnectTetheringOperation::Factory::SetFactoryForTesting(
         fake_operation_factory_.get());
-
-    fake_device_sync_client_ =
-        std::make_unique<device_sync::FakeDeviceSyncClient>();
-    fake_secure_channel_client_ =
-        std::make_unique<secure_channel::FakeSecureChannelClient>();
     fake_wifi_hotspot_connector_ =
         std::make_unique<FakeWifiHotspotConnector>(NetworkHandler::Get());
     fake_active_host_ = std::make_unique<FakeActiveHost>();
@@ -178,11 +167,13 @@
         std::make_unique<FakeDisconnectTetheringRequestSender>();
     fake_wifi_hotspot_disconnector_ =
         std::make_unique<FakeWifiHotspotDisconnector>();
+    fake_host_connection_factory_ =
+        std::make_unique<FakeHostConnection::Factory>();
 
     result_.clear();
 
     tether_connector_ = base::WrapUnique(new TetherConnectorImpl(
-        fake_device_sync_client_.get(), fake_secure_channel_client_.get(),
+        fake_host_connection_factory_.get(),
         NetworkHandler::Get()->network_state_handler(),
         fake_wifi_hotspot_connector_.get(), fake_active_host_.get(),
         fake_tether_host_fetcher_.get(),
@@ -281,6 +272,8 @@
         test_devices_[setup_required ? 1 : 0];
 
     fake_tether_host_fetcher_->SetTetherHost(test_device);
+    fake_host_connection_factory_->SetupConnectionAttempt(
+        TetherHost(test_device));
     CallConnect(GetTetherNetworkGuid(test_device.GetDeviceId()));
     EXPECT_EQ(ActiveHost::ActiveHostStatus::CONNECTING,
               fake_active_host_->GetActiveHostStatus());
@@ -328,10 +321,8 @@
   std::unique_ptr<FakeConnectTetheringOperationFactory> fake_operation_factory_;
   std::unique_ptr<FakeWifiHotspotConnector> fake_wifi_hotspot_connector_;
   std::unique_ptr<FakeActiveHost> fake_active_host_;
+  std::unique_ptr<FakeHostConnection::Factory> fake_host_connection_factory_;
   std::unique_ptr<FakeTetherHostFetcher> fake_tether_host_fetcher_;
-  std::unique_ptr<device_sync::FakeDeviceSyncClient> fake_device_sync_client_;
-  std::unique_ptr<secure_channel::SecureChannelClient>
-      fake_secure_channel_client_;
   std::unique_ptr<MockTetherHostResponseRecorder>
       mock_tether_host_response_recorder_;
   // TODO(hansberry): Use a fake for this when a real mapping scheme is created.
@@ -383,6 +374,8 @@
                       USER_CANCELLATION,
                   test_devices_[0].GetDeviceId(), Eq(std::nullopt)));
 
+  fake_host_connection_factory_->SetupConnectionAttempt(
+      TetherHost(test_devices_[0]));
   CallConnect(GetTetherNetworkGuid(test_devices_[0].GetDeviceId()));
   EXPECT_EQ(ActiveHost::ActiveHostStatus::CONNECTING,
             fake_active_host_->GetActiveHostStatus());
@@ -525,6 +518,8 @@
   fake_notification_presenter_->NotifyConnectionToHostFailed();
 
   // Starting a connection should result in it being removed.
+  fake_host_connection_factory_->SetupConnectionAttempt(
+      TetherHost(test_devices_[0]));
   CallConnect(GetTetherNetworkGuid(test_devices_[0].GetDeviceId()));
   EXPECT_FALSE(
       fake_notification_presenter_->is_connection_failed_notification_shown());
@@ -539,6 +534,8 @@
           Optional(HostConnectionMetricsLogger::ConnectionToHostInternalError::
                        CLIENT_CONNECTION_TIMEOUT)));
 
+  fake_host_connection_factory_->SetupConnectionAttempt(
+      TetherHost(test_devices_[0]));
   CallConnect(GetTetherNetworkGuid(test_devices_[0].GetDeviceId()));
   EXPECT_EQ(ActiveHost::ActiveHostStatus::CONNECTING,
             fake_active_host_->GetActiveHostStatus());
@@ -584,6 +581,8 @@
                       USER_CANCELLATION,
                   test_devices_[0].GetDeviceId(), Eq(std::nullopt)));
 
+  fake_host_connection_factory_->SetupConnectionAttempt(
+      TetherHost(test_devices_[0]));
   CallConnect(GetTetherNetworkGuid(test_devices_[0].GetDeviceId()));
   EXPECT_EQ(ActiveHost::ActiveHostStatus::CONNECTING,
             fake_active_host_->GetActiveHostStatus());
@@ -640,6 +639,8 @@
       RecordSuccessfulConnectTetheringResponse(test_devices_[0].GetDeviceId()));
 
   fake_tether_host_fetcher_->SetTetherHost(test_devices_[0]);
+  fake_host_connection_factory_->SetupConnectionAttempt(
+      TetherHost(test_devices_[0]));
   CallConnect(GetTetherNetworkGuid(test_devices_[0].GetDeviceId()));
   EXPECT_EQ(ActiveHost::ActiveHostStatus::CONNECTING,
             fake_active_host_->GetActiveHostStatus());
@@ -695,6 +696,8 @@
       fake_notification_presenter_->is_setup_required_notification_shown());
 
   fake_tether_host_fetcher_->SetTetherHost(test_devices_[1]);
+  fake_host_connection_factory_->SetupConnectionAttempt(
+      TetherHost(test_devices_[0]));
   CallConnect(GetTetherNetworkGuid(test_devices_[1].GetDeviceId()));
   EXPECT_FALSE(
       fake_notification_presenter_->is_setup_required_notification_shown());
@@ -734,7 +737,10 @@
                   test_devices_[1].GetDeviceId(), Eq(std::nullopt)));
 
   fake_tether_host_fetcher_->SetTetherHost(test_devices_[0]);
+  fake_host_connection_factory_->SetupConnectionAttempt(
+      TetherHost(test_devices_[0]));
   CallConnect(GetTetherNetworkGuid(test_devices_[0].GetDeviceId()));
+
   EXPECT_EQ(ActiveHost::ActiveHostStatus::CONNECTING,
             fake_active_host_->GetActiveHostStatus());
   EXPECT_EQ(test_devices_[0].GetDeviceId(),
@@ -748,7 +754,10 @@
 
   // Before the created operation replies, start a new connection to device 1.
   fake_tether_host_fetcher_->SetTetherHost(test_devices_[1]);
+  fake_host_connection_factory_->SetupConnectionAttempt(
+      TetherHost(test_devices_[0]));
   CallConnect(GetTetherNetworkGuid(test_devices_[1].GetDeviceId()));
+
   // The first connection attempt should have resulted in a connect canceled
   // error.
   EXPECT_EQ(NetworkConnectionHandler::kErrorConnectCanceled,
@@ -801,7 +810,10 @@
                   test_devices_[1].GetDeviceId(), Eq(std::nullopt)));
 
   fake_tether_host_fetcher_->SetTetherHost(test_devices_[0]);
+  fake_host_connection_factory_->SetupConnectionAttempt(
+      TetherHost(test_devices_[0]));
   CallConnect(GetTetherNetworkGuid(test_devices_[0].GetDeviceId()));
+
   EXPECT_EQ(ActiveHost::ActiveHostStatus::CONNECTING,
             fake_active_host_->GetActiveHostStatus());
   EXPECT_EQ(test_devices_[0].GetDeviceId(),
@@ -822,7 +834,10 @@
   // While the connection to the Wi-Fi network is in progress, start a new
   // connection attempt.
   fake_tether_host_fetcher_->SetTetherHost(test_devices_[1]);
+  fake_host_connection_factory_->SetupConnectionAttempt(
+      TetherHost(test_devices_[0]));
   CallConnect(GetTetherNetworkGuid(test_devices_[1].GetDeviceId()));
+
   // The first connection attempt should have resulted in a connect canceled
   // error.
   EXPECT_EQ(NetworkConnectionHandler::kErrorConnectCanceled,
diff --git a/chromeos/ash/services/cellular_setup/esim_manager.cc b/chromeos/ash/services/cellular_setup/esim_manager.cc
index 6309f7e2..b65e72cb2 100644
--- a/chromeos/ash/services/cellular_setup/esim_manager.cc
+++ b/chromeos/ash/services/cellular_setup/esim_manager.cc
@@ -46,8 +46,7 @@
 std::string ESimManager::GetRootSmdsAddress() {
   // This function returns which server should be used when performing an SM-DS
   // scan and will only ever return the root GSM Association server or the Stork
-  // server; to use the Android staging server please enable the |SmdsSupport|
-  // feature flag.
+  // server.
   return features::ShouldUseStorkSmds() ? kStorkSmdsServerAddress
                                         : std::string();
 }
diff --git a/chromeos/ash/services/cellular_setup/euicc.cc b/chromeos/ash/services/cellular_setup/euicc.cc
index f97f01b..6dc6c01 100644
--- a/chromeos/ash/services/cellular_setup/euicc.cc
+++ b/chromeos/ash/services/cellular_setup/euicc.cc
@@ -37,32 +37,10 @@
 
 namespace {
 
-// Delay before pending profile refresh callback is called. This ensures that
-// eSIM profiles are updated before callback returns.
-constexpr base::TimeDelta kPendingProfileRefreshDelay = base::Milliseconds(150);
 
 // Prefix for EID when encoded in QR Code.
 const char kEidQrCodePrefix[] = "EID:";
 
-// Measures the time from which this function is called to when |callback|
-// is expected to run. The measured time difference should capture the time it
-// took for a profile discovery request to complete.
-Euicc::RequestPendingProfilesCallback CreateTimedRequestPendingProfilesCallback(
-    Euicc::RequestPendingProfilesCallback callback) {
-  return base::BindOnce(
-      [](Euicc::RequestPendingProfilesCallback callback,
-         base::Time refresh_profile_start_time,
-         mojom::ESimOperationResult result) -> void {
-        std::move(callback).Run(result);
-        if (result != mojom::ESimOperationResult::kSuccess)
-          return;
-        UMA_HISTOGRAM_MEDIUM_TIMES(
-            "Network.Cellular.ESim.ProfileDiscovery.Latency",
-            base::Time::Now() - refresh_profile_start_time);
-      },
-      std::move(callback), base::Time::Now());
-}
-
 CellularNetworkMetricsLogger::ESimUserInstallMethod ProfileInstallMethodToEnum(
     mojom::ProfileInstallMethod install_method) {
   using mojom::ProfileInstallMethod;
@@ -119,38 +97,8 @@
     const std::string& confirmation_code,
     mojom::ProfileInstallMethod install_method,
     InstallProfileFromActivationCodeCallback callback) {
-  if (!ash::features::IsSmdsSupportEnabled()) {
-    ESimProfile* profile_info = nullptr;
-    mojom::ProfileInstallResult status =
-        GetPendingProfileInfoFromActivationCode(activation_code, &profile_info);
-
-    // Return early if profile was found but not in the correct state.
-    if (profile_info && status != mojom::ProfileInstallResult::kSuccess) {
-      NET_LOG(ERROR) << "EUICC could not install profile: " << status;
-      std::move(callback).Run(status, mojo::NullRemote());
-      return;
-    }
-
-    if (profile_info) {
-      NET_LOG(USER) << "Installing profile with path "
-                    << profile_info->path().value();
-      profile_info->InstallProfile(
-          confirmation_code,
-          base::BindOnce(
-              [](InstallProfileFromActivationCodeCallback callback,
-                 ESimProfile* esim_profile,
-                 mojom::ProfileInstallResult status) -> void {
-                std::move(callback).Run(status, esim_profile->CreateRemote());
-              },
-              std::move(callback), profile_info));
-      return;
-    }
-  }
-
-  if (ash::features::IsSmdsSupportEnabled()) {
-    CellularNetworkMetricsLogger::LogESimUserInstallMethod(
-        ProfileInstallMethodToEnum(install_method));
-  }
+  CellularNetworkMetricsLogger::LogESimUserInstallMethod(
+      ProfileInstallMethodToEnum(install_method));
 
   esim_manager_->cellular_esim_installer()->InstallProfileFromActivationCode(
       activation_code, confirmation_code, path_,
@@ -188,7 +136,6 @@
 
 void Euicc::RequestAvailableProfiles(
     RequestAvailableProfilesCallback callback) {
-  DCHECK(ash::features::IsSmdsSupportEnabled());
   esim_manager_->cellular_esim_profile_handler()->RequestAvailableProfiles(
       path_,
       base::BindOnce(&Euicc::OnRequestAvailableProfiles,
@@ -197,7 +144,6 @@
 
 void Euicc::RefreshInstalledProfiles(
     RefreshInstalledProfilesCallback callback) {
-  DCHECK(ash::features::IsSmdsSupportEnabled());
   NET_LOG(EVENT) << "Refreshing installed profiles";
   esim_manager_->cellular_esim_profile_handler()->RefreshProfileList(
       path_,
@@ -211,19 +157,6 @@
           std::move(callback)));
 }
 
-void Euicc::RequestPendingProfiles(RequestPendingProfilesCallback callback) {
-  // Before requesting pending profiles, we also request installed profiles.
-  // This ensures that if an error occurs and Chrome's installed profile cache
-  // goes out of sync with Hermes, we re-sync at this point. See b/187459880 for
-  // details.
-  NET_LOG(EVENT) << "Requesting installed and pending profiles";
-  esim_manager_->cellular_esim_profile_handler()->RefreshProfileList(
-      path_,
-      base::BindOnce(
-          &Euicc::PerformRequestPendingProfiles, weak_ptr_factory_.GetWeakPtr(),
-          CreateTimedRequestPendingProfilesCallback(std::move(callback))));
-}
-
 void Euicc::GetEidQRCode(GetEidQRCodeCallback callback) {
   // Format EID to string that should be encoded in the QRCode.
   std::string qr_code_string =
@@ -306,33 +239,10 @@
   return nullptr;
 }
 
-void Euicc::PerformRequestPendingProfiles(
-    RequestPendingProfilesCallback callback,
-    std::unique_ptr<CellularInhibitor::InhibitLock> inhibit_lock) {
-  if (!inhibit_lock) {
-    NET_LOG(ERROR) << "Error requesting installed profiles. Path: "
-                   << path_.value();
-    RecordRequestPendingProfilesResult(
-        RequestPendingProfilesResult::kInhibitFailed);
-    std::move(callback).Run(mojom::ESimOperationResult::kFailure);
-    return;
-  }
-
-  NET_LOG(EVENT) << "Requesting pending profiles";
-  HermesEuiccClient::Get()->RefreshSmdxProfiles(
-      path_, /*activation_code=*/ESimManager::GetRootSmdsAddress(),
-      /*restore_slot=*/true,
-      base::BindOnce(&Euicc::OnRefreshSmdxProfilesResult,
-                     weak_ptr_factory_.GetWeakPtr(), std::move(callback),
-                     std::move(inhibit_lock)));
-}
-
 void Euicc::OnRequestAvailableProfiles(
     RequestAvailableProfilesCallback callback,
     mojom::ESimOperationResult result,
     std::vector<CellularESimProfile> profile_list) {
-  DCHECK(ash::features::IsSmdsSupportEnabled());
-
   std::vector<mojom::ESimProfilePropertiesPtr> profile_properties_list;
   for (const auto& profile : profile_list) {
     mojom::ESimProfilePropertiesPtr properties =
@@ -349,45 +259,6 @@
   std::move(callback).Run(result, std::move(profile_properties_list));
 }
 
-void Euicc::OnRefreshSmdxProfilesResult(
-    RequestPendingProfilesCallback callback,
-    std::unique_ptr<CellularInhibitor::InhibitLock> inhibit_lock,
-    HermesResponseStatus status,
-    const std::vector<dbus::ObjectPath>& profile_paths) {
-  NET_LOG(EVENT) << "Refresh SM-DX profiles found " << profile_paths.size()
-                 << " available profiles";
-  // TODO(crbug.com/1216693) Update with more robust way of waiting for eSIM
-  // profile objects to be loaded.
-  base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
-      FROM_HERE,
-      base::BindOnce(std::move(callback),
-                     status == HermesResponseStatus::kSuccess
-                         ? mojom::ESimOperationResult::kSuccess
-                         : mojom::ESimOperationResult::kFailure),
-      kPendingProfileRefreshDelay);
-}
-
-mojom::ProfileInstallResult Euicc::GetPendingProfileInfoFromActivationCode(
-    const std::string& activation_code,
-    ESimProfile** profile_info) {
-  const auto iter = base::ranges::find(
-      esim_profiles_, activation_code, [](const auto& esim_profile) {
-        return esim_profile->properties()->activation_code;
-      });
-  if (iter == esim_profiles_.end()) {
-    NET_LOG(EVENT) << "Get pending profile with activation failed: No profile "
-                      "with activation_code.";
-    return mojom::ProfileInstallResult::kFailure;
-  }
-  *profile_info = iter->get();
-  if ((*profile_info)->properties()->state != mojom::ProfileState::kPending) {
-    NET_LOG(ERROR) << "Get pending profile with activation code failed: Profile"
-                      "is not in pending state.";
-    return mojom::ProfileInstallResult::kFailure;
-  }
-  return mojom::ProfileInstallResult::kSuccess;
-}
-
 ESimProfile* Euicc::UpdateOrCreateESimProfile(
     const CellularESimProfile& esim_profile_state) {
   ESimProfile* esim_profile = GetProfileFromPath(esim_profile_state.path());
diff --git a/chromeos/ash/services/cellular_setup/euicc.h b/chromeos/ash/services/cellular_setup/euicc.h
index 216b826..53919777 100644
--- a/chromeos/ash/services/cellular_setup/euicc.h
+++ b/chromeos/ash/services/cellular_setup/euicc.h
@@ -47,7 +47,6 @@
       RequestAvailableProfilesCallback callback) override;
   void RefreshInstalledProfiles(
       RefreshInstalledProfilesCallback callback) override;
-  void RequestPendingProfiles(RequestPendingProfilesCallback callback) override;
   void GetEidQRCode(GetEidQRCodeCallback callback) override;
 
   // Updates list of eSIM profiles for this euicc from with the given
@@ -68,11 +67,6 @@
   const mojom::EuiccPropertiesPtr& properties() { return properties_; }
 
  private:
-  FRIEND_TEST_ALL_PREFIXES(EuiccTest_SmdsSupportDisabled,
-                           RequestPendingProfiles);
-  FRIEND_TEST_ALL_PREFIXES(EuiccTest_SmdsSupportEnabled,
-                           RequestPendingProfiles);
-
   // These values are persisted to logs. Entries should not be renumbered and
   // numeric values should never be reused.
   enum class RequestPendingProfilesResult {
@@ -89,21 +83,10 @@
       HermesResponseStatus hermes_status,
       std::optional<dbus::ObjectPath> profile_path,
       std::optional<std::string> service_path);
-  void PerformRequestPendingProfiles(
-      RequestPendingProfilesCallback callback,
-      std::unique_ptr<CellularInhibitor::InhibitLock> inhibit_lock);
   void OnRequestAvailableProfiles(
       RequestAvailableProfilesCallback callback,
       mojom::ESimOperationResult result,
       std::vector<CellularESimProfile> profile_list);
-  void OnRefreshSmdxProfilesResult(
-      RequestPendingProfilesCallback callback,
-      std::unique_ptr<CellularInhibitor::InhibitLock> inhibit_lock,
-      HermesResponseStatus status,
-      const std::vector<dbus::ObjectPath>& profile_paths);
-  mojom::ProfileInstallResult GetPendingProfileInfoFromActivationCode(
-      const std::string& activation_code,
-      ESimProfile** profile_info);
   // Updates an ESimProfile in |esim_profiles_| with values from given
   // |esim_profile_state| or creates new one if it doesn't exist. Returns
   // pointer to ESimProfile object if one was created.
diff --git a/chromeos/ash/services/cellular_setup/euicc_unittest.cc b/chromeos/ash/services/cellular_setup/euicc_unittest.cc
index aa519f7e..8b7bd13 100644
--- a/chromeos/ash/services/cellular_setup/euicc_unittest.cc
+++ b/chromeos/ash/services/cellular_setup/euicc_unittest.cc
@@ -31,21 +31,6 @@
 using InstallResultPair = std::pair<mojom::ProfileInstallResult,
                                     mojo::PendingRemote<mojom::ESimProfile>>;
 
-mojom::ESimOperationResult RequestPendingProfiles(
-    mojo::Remote<mojom::Euicc>& euicc) {
-  mojom::ESimOperationResult result;
-  base::RunLoop run_loop;
-  euicc->RequestPendingProfiles(base::BindOnce(
-      [](mojom::ESimOperationResult* out, base::OnceClosure quit_closure,
-         mojom::ESimOperationResult result) {
-        *out = result;
-        std::move(quit_closure).Run();
-      },
-      &result, run_loop.QuitClosure()));
-  run_loop.Run();
-  return result;
-}
-
 mojom::ESimOperationResult RefreshInstalledProfiles(
     mojo::Remote<mojom::Euicc>& euicc) {
   mojom::ESimOperationResult result;
@@ -304,27 +289,6 @@
   EXPECT_EQ(3u, observer()->profile_list_change_calls().size());
 }
 
-TEST_F(EuiccTest, RequestPendingProfiles) {
-  mojo::Remote<mojom::Euicc> euicc = GetEuiccForEid(ESimTestBase::kTestEid);
-  ASSERT_TRUE(euicc.is_bound());
-
-  HermesEuiccClient::TestInterface* euicc_test =
-      HermesEuiccClient::Get()->GetTestInterface();
-  // Verify that pending profile request errors are return properly.
-  euicc_test->QueueHermesErrorStatus(HermesResponseStatus::kErrorNoResponse);
-  EXPECT_EQ(mojom::ESimOperationResult::kFailure,
-            RequestPendingProfiles(euicc));
-  EXPECT_EQ(0u, observer()->profile_list_change_calls().size());
-
-  constexpr base::TimeDelta kHermesInteractiveDelay = base::Milliseconds(3000);
-  HermesEuiccClient::Get()->GetTestInterface()->SetInteractiveDelay(
-      kHermesInteractiveDelay);
-
-  // Verify that successful request returns correct status code.
-  EXPECT_EQ(mojom::ESimOperationResult::kSuccess,
-            RequestPendingProfiles(euicc));
-}
-
 TEST_F(EuiccTest, GetEidQRCode) {
   mojo::Remote<mojom::Euicc> euicc = GetEuiccForEid(ESimTestBase::kTestEid);
   ASSERT_TRUE(euicc.is_bound());
diff --git a/chromeos/ash/services/cellular_setup/public/mojom/esim_manager.mojom b/chromeos/ash/services/cellular_setup/public/mojom/esim_manager.mojom
index 26f45e1..e83cb25 100644
--- a/chromeos/ash/services/cellular_setup/public/mojom/esim_manager.mojom
+++ b/chromeos/ash/services/cellular_setup/public/mojom/esim_manager.mojom
@@ -128,15 +128,6 @@
   // will also update the cache of profiles that Chrome uses for the UI.
   RefreshInstalledProfiles() => (ESimOperationResult result);
 
-  // TODO(b/281904820): This API is deprecated as part of the SM-DS Support
-  // project and should be removed in favor of RequestAvailableProfiles().
-  // Starts a request for pending profiles for this Euicc from SMDS.
-  // Returns a status indicating result of the operation. This
-  // method updates the pending profiles list before succeeding. This method
-  // is used to allow the user to explicit trigger a request to check for
-  // new profiles that may have been pushed to SMDS.
-  RequestPendingProfiles() => (ESimOperationResult result);
-
   // Installs a profile with given |activation_code| and |confirmation_code|
   // on this Euicc. Returns the  result code and ESimProfile that was just
   // installed. A non-success result code is returned in case of errors.
diff --git a/chromeos/test/data/oobe_configuration/enrollment_token_configuration.json b/chromeos/test/data/oobe_configuration/enrollment_token_configuration.json
new file mode 100644
index 0000000..6e91d5d
--- /dev/null
+++ b/chromeos/test/data/oobe_configuration/enrollment_token_configuration.json
@@ -0,0 +1,3 @@
+{
+	"enrollmentToken": "test-enrollment-token"
+}
diff --git a/chromeos/test/data/oobe_configuration/flex_enrollment_configuration.json b/chromeos/test/data/oobe_configuration/flex_enrollment_configuration.json
deleted file mode 100644
index 1e4abd8b..0000000
--- a/chromeos/test/data/oobe_configuration/flex_enrollment_configuration.json
+++ /dev/null
@@ -1,3 +0,0 @@
-{
-	"flexToken": "test-flex-token"
-}
diff --git a/clank b/clank
index 86a403c..53d4ad8 160000
--- a/clank
+++ b/clank
@@ -1 +1 @@
-Subproject commit 86a403cb8a162ba2670eafa8f962ae894531a42b
+Subproject commit 53d4ad86f9f87e560f3ebd5feefa263a4aeae823
diff --git a/components/BUILD.gn b/components/BUILD.gn
index be8b448..3d17be6 100644
--- a/components/BUILD.gn
+++ b/components/BUILD.gn
@@ -445,6 +445,7 @@
       "//components/favicon/content:unit_tests",
       "//components/feed/core/v2:core_unit_tests",
       "//components/file_access:unit_tests",
+      "//components/fingerprinting_protection_filter/browser:unit_tests",
       "//components/gcm_driver/instance_id:unit_tests",
       "//components/heavy_ad_intervention:unit_tests",
       "//components/history/content/browser:unit_tests",
diff --git a/components/autofill/core/browser/address_data_manager.cc b/components/autofill/core/browser/address_data_manager.cc
index 04f389f..f8d95ae5 100644
--- a/components/autofill/core/browser/address_data_manager.cc
+++ b/components/autofill/core/browser/address_data_manager.cc
@@ -13,6 +13,7 @@
 #include "base/notreached.h"
 #include "base/ranges/algorithm.h"
 #include "base/strings/utf_string_conversions.h"
+#include "components/autofill/core/browser/address_data_cleaner.h"
 #include "components/autofill/core/browser/country_type.h"
 #include "components/autofill/core/browser/geo/alternative_state_name_map_updater.h"
 #include "components/autofill/core/browser/geo/autofill_country.h"
diff --git a/components/autofill/core/browser/autofill_client.cc b/components/autofill/core/browser/autofill_client.cc
index eaa33d3..17b04b3 100644
--- a/components/autofill/core/browser/autofill_client.cc
+++ b/components/autofill/core/browser/autofill_client.cc
@@ -210,6 +210,12 @@
     bool should_show_prompt,
     SaveIbanPromptCallback callback) {}
 
+bool AutofillClient::ShowTouchToFillIban(
+    base::WeakPtr<TouchToFillDelegate> delegate,
+    base::span<const autofill::Iban> ibans_to_suggest) {
+  return false;
+}
+
 void AutofillClient::UpdateOfferNotification(
     const AutofillOfferData* offer,
     const OfferNotificationOptions& options) {
diff --git a/components/autofill/core/browser/autofill_client.h b/components/autofill/core/browser/autofill_client.h
index 4ebfaaa..f6ffbae6 100644
--- a/components/autofill/core/browser/autofill_client.h
+++ b/components/autofill/core/browser/autofill_client.h
@@ -690,6 +690,14 @@
       base::WeakPtr<TouchToFillDelegate> delegate,
       base::span<const autofill::CreditCard> cards_to_suggest) = 0;
 
+  // Shows the Touch To Fill surface for filling IBAN information, if
+  // possible, returning `true` on success. `delegate` will be notified of
+  // events. This function is not implemented on iOS and iOS WebView, and
+  // should not be used on those platforms.
+  virtual bool ShowTouchToFillIban(
+      base::WeakPtr<TouchToFillDelegate> delegate,
+      base::span<const autofill::Iban> ibans_to_suggest);
+
   // Hides the Touch To Fill surface for filling credit card information
   // if one is currently shown. Should be called only if the feature is
   // supported by the platform.
diff --git a/components/autofill/core/browser/browser_autofill_manager.cc b/components/autofill/core/browser/browser_autofill_manager.cc
index ace09cd..8214fd19 100644
--- a/components/autofill/core/browser/browser_autofill_manager.cc
+++ b/components/autofill/core/browser/browser_autofill_manager.cc
@@ -1887,7 +1887,6 @@
   // TODO(b/309163415): Replace parameter of FormFieldData in
   // `TryToShowTouchToFill` by FieldGlobalId.
   if (form_element_was_clicked && touch_to_fill_delegate_ &&
-      base::FeatureList::IsEnabled(features::kAutofillEnableServerIban) &&
       touch_to_fill_delegate_->TryToShowTouchToFill(
           form, *form.FindFieldByGlobalId(field_id))) {
     return;
diff --git a/components/autofill/core/browser/payments_data_manager.h b/components/autofill/core/browser/payments_data_manager.h
index 4cff3b30..4922e8f 100644
--- a/components/autofill/core/browser/payments_data_manager.h
+++ b/components/autofill/core/browser/payments_data_manager.h
@@ -26,6 +26,7 @@
 #include "components/autofill/core/browser/data_model/iban.h"
 #include "components/autofill/core/browser/metrics/autofill_metrics.h"
 #include "components/autofill/core/browser/payments/account_info_getter.h"
+#include "components/autofill/core/browser/payments/payments_customer_data.h"
 #include "components/autofill/core/browser/webdata/autofill_webdata_service.h"
 #include "components/autofill/core/browser/webdata/autofill_webdata_service_observer.h"
 #include "components/prefs/pref_change_registrar.h"
diff --git a/components/autofill/core/browser/personal_data_manager.cc b/components/autofill/core/browser/personal_data_manager.cc
index d7e95f6..bef2a5d 100644
--- a/components/autofill/core/browser/personal_data_manager.cc
+++ b/components/autofill/core/browser/personal_data_manager.cc
@@ -4,16 +4,8 @@
 
 #include "components/autofill/core/browser/personal_data_manager.h"
 
-#include "base/functional/bind.h"
-#include "base/functional/callback.h"
-#include "base/functional/callback_helpers.h"
-#include "base/memory/raw_ptr.h"
-#include "base/observer_list.h"
 #include "components/autofill/core/browser/address_data_manager.h"
-#include "components/autofill/core/browser/country_type.h"
 #include "components/autofill/core/browser/crowdsourcing/autofill_crowdsourcing_manager.h"
-#include "components/autofill/core/browser/data_model/autofill_profile.h"
-#include "components/autofill/core/browser/data_model/bank_account.h"
 #include "components/autofill/core/browser/manual_testing_import.h"
 #include "components/autofill/core/browser/metrics/autofill_metrics.h"
 #include "components/autofill/core/browser/payments_data_manager.h"
diff --git a/components/autofill/core/browser/personal_data_manager.h b/components/autofill/core/browser/personal_data_manager.h
index ac39e6b..b48bf4e 100644
--- a/components/autofill/core/browser/personal_data_manager.h
+++ b/components/autofill/core/browser/personal_data_manager.h
@@ -7,26 +7,13 @@
 
 #include <memory>
 #include <string>
-#include <string_view>
-#include <vector>
 
-#include "base/containers/span.h"
 #include "base/memory/raw_ptr.h"
 #include "base/observer_list.h"
 #include "base/scoped_observation.h"
-#include "components/autofill/core/browser/address_data_cleaner.h"
 #include "components/autofill/core/browser/address_data_manager.h"
 #include "components/autofill/core/browser/autofill_shared_storage_handler.h"
 #include "components/autofill/core/browser/country_type.h"
-#include "components/autofill/core/browser/data_model/autofill_offer_data.h"
-#include "components/autofill/core/browser/data_model/autofill_profile.h"
-#include "components/autofill/core/browser/data_model/bank_account.h"
-#include "components/autofill/core/browser/data_model/credit_card.h"
-#include "components/autofill/core/browser/data_model/credit_card_benefit.h"
-#include "components/autofill/core/browser/data_model/credit_card_cloud_token_data.h"
-#include "components/autofill/core/browser/field_types.h"
-#include "components/autofill/core/browser/geo/alternative_state_name_map_updater.h"
-#include "components/autofill/core/browser/payments/payments_customer_data.h"
 #include "components/autofill/core/browser/payments_data_manager.h"
 #include "components/autofill/core/browser/webdata/autofill_webdata_service.h"
 #include "components/history/core/browser/history_service.h"
@@ -199,13 +186,12 @@
   base::ScopedObservation<PaymentsDataManager, PaymentsDataManager::Observer>
       payments_data_manager_observation_{this};
 
-  // The observers.
-  base::ObserverList<PersonalDataManagerObserver>::Unchecked observers_;
-
   // The PrefService that this instance uses. Must outlive this instance.
   raw_ptr<PrefService> pref_service_ = nullptr;
 
  private:
+  base::ObserverList<PersonalDataManagerObserver>::Unchecked observers_;
+
   // Stores the |app_locale| supplied on construction.
   const std::string app_locale_;
 
diff --git a/components/autofill/core/browser/test_autofill_client.h b/components/autofill/core/browser/test_autofill_client.h
index f5d92e54..98100fb 100644
--- a/components/autofill/core/browser/test_autofill_client.h
+++ b/components/autofill/core/browser/test_autofill_client.h
@@ -403,6 +403,12 @@
     return false;
   }
 
+  bool ShowTouchToFillIban(
+      base::WeakPtr<TouchToFillDelegate> delegate,
+      base::span<const autofill::Iban> ibans_to_suggest) override {
+    return false;
+  }
+
   void HideTouchToFillCreditCard() override {}
 
   void ShowAutofillSuggestions(
diff --git a/components/autofill/core/common/autofill_features.cc b/components/autofill/core/common/autofill_features.cc
index 90ef9c4..88662f7 100644
--- a/components/autofill/core/common/autofill_features.cc
+++ b/components/autofill/core/common/autofill_features.cc
@@ -468,7 +468,7 @@
 // TODO(crbug.com/40146444): Remove once launched.
 BASE_FEATURE(kAutofillParsingPatternProvider,
              "AutofillParsingPatternProvider",
-             base::FEATURE_DISABLED_BY_DEFAULT);
+             base::FEATURE_ENABLED_BY_DEFAULT);
 
 // The specific pattern set is controlled by the `kAutofillParsingPatternActive`
 // parameter. One of "default", "experimental", "nextgen".
diff --git a/components/autofill/core/common/autofill_test_utils.cc b/components/autofill/core/common/autofill_test_utils.cc
index 8eeba28..1dcafc39c 100644
--- a/components/autofill/core/common/autofill_test_utils.cc
+++ b/components/autofill/core/common/autofill_test_utils.cc
@@ -11,6 +11,7 @@
 
 #include "base/check.h"
 #include "base/location.h"
+#include "base/strings/strcat.h"
 #include "base/strings/utf_string_conversions.h"
 #include "base/test/scoped_feature_list.h"
 #include "base/unguessable_token.h"
@@ -24,6 +25,22 @@
 
 namespace autofill::test {
 
+namespace {
+
+FormData ConstructFormWithNameRenderIdAndProtocol(bool is_https) {
+  FormData form;
+  form.name = u"MyForm";
+  form.renderer_id = MakeFormRendererId();
+  std::string_view protocol = is_https ? "https://" : "http://";
+  form.url = GURL(base::StrCat({protocol, "myform.com/form.html"}));
+  form.action = GURL(base::StrCat({protocol, "myform.com/submit.html"}));
+  form.main_frame_origin = url::Origin::Create(
+      GURL(base::StrCat({protocol, "myform_root.com/form.html"})));
+  return form;
+}
+
+}  // namespace
+
 AutofillTestEnvironment* AutofillTestEnvironment::current_instance_ = nullptr;
 
 AutofillTestEnvironment& AutofillTestEnvironment::GetCurrent(
@@ -246,13 +263,7 @@
 }
 
 FormData CreateTestPersonalInformationFormData() {
-  FormData form;
-  form.renderer_id = MakeFormRendererId();
-  form.name = u"MyForm";
-  form.url = GURL("https://myform.com/form.html");
-  form.action = GURL("https://myform.com/submit.html");
-  form.main_frame_origin =
-      url::Origin::Create(GURL("https://myform_root.com/form.html"));
+  FormData form = ConstructFormWithNameRenderIdAndProtocol(/*is_https=*/true);
   form.fields = {
       CreateTestFormField("First Name", "firstname", "",
                           FormControlType::kInputText),
@@ -267,20 +278,7 @@
 FormData CreateTestCreditCardFormData(bool is_https,
                                       bool use_month_type,
                                       bool split_names) {
-  FormData form;
-  form.renderer_id = MakeFormRendererId();
-  form.name = u"MyForm";
-  if (is_https) {
-    form.url = GURL("https://myform.com/form.html");
-    form.action = GURL("https://myform.com/submit.html");
-    form.main_frame_origin =
-        url::Origin::Create(GURL("https://myform_root.com/form.html"));
-  } else {
-    form.url = GURL("http://myform.com/form.html");
-    form.action = GURL("http://myform.com/submit.html");
-    form.main_frame_origin =
-        url::Origin::Create(GURL("http://myform_root.com/form.html"));
-  }
+  FormData form = ConstructFormWithNameRenderIdAndProtocol(is_https);
 
   if (split_names) {
     form.fields.push_back(
@@ -309,9 +307,8 @@
   return form;
 }
 
-FormData CreateTestIbanFormData(std::string_view value) {
-  FormData form;
-  form.url = GURL("https://www.foo.com");
+FormData CreateTestIbanFormData(std::string_view value, bool is_https) {
+  FormData form = ConstructFormWithNameRenderIdAndProtocol(is_https);
   form.fields = {CreateTestFormField("IBAN Value:", "iban_value", value,
                                      FormControlType::kInputText)};
   return form;
diff --git a/components/autofill/core/common/autofill_test_utils.h b/components/autofill/core/common/autofill_test_utils.h
index d0ffae83..98a59347 100644
--- a/components/autofill/core/common/autofill_test_utils.h
+++ b/components/autofill/core/common/autofill_test_utils.h
@@ -212,7 +212,8 @@
 // single IBAN field). Note that this actually appends fields to the form data,
 // which can be useful for building up more complex test forms.
 [[nodiscard]] FormData CreateTestIbanFormData(
-    std::string_view value = kIbanValue);
+    std::string_view value = kIbanValue,
+    bool is_https = true);
 
 // Creates a 'FormData` with a username and a password fields.
 [[nodiscard]] FormData CreateTestPasswordFormData();
diff --git a/components/autofill/ios/browser/BUILD.gn b/components/autofill/ios/browser/BUILD.gn
index d80e73e..ad24da41 100644
--- a/components/autofill/ios/browser/BUILD.gn
+++ b/components/autofill/ios/browser/BUILD.gn
@@ -27,6 +27,8 @@
     "form_suggestion_provider.h",
     "form_suggestion_provider_query.h",
     "form_suggestion_provider_query.mm",
+    "password_autofill_agent.h",
+    "password_autofill_agent.mm",
     "personal_data_manager_observer_bridge.h",
     "personal_data_manager_observer_bridge.mm",
     "suggestion_controller_java_script_feature.h",
@@ -109,6 +111,8 @@
     "fake_autofill_agent.h",
     "fake_autofill_agent.mm",
     "ios_test_event_waiter.h",
+    "mock_password_autofill_agent_delegate.h",
+    "mock_password_autofill_agent_delegate.mm",
     "test_autofill_manager_injector.h",
   ]
   public_deps = [ ":browser" ]
@@ -130,6 +134,7 @@
     "autofill_util_unittests.mm",
     "autofill_xhr_sumission_detection_unittest.mm",
     "form_suggestion_provider_query_unittests.mm",
+    "password_autofill_agent_unittest.mm",
   ]
   deps = [
     ":browser",
diff --git a/components/autofill/ios/browser/autofill_across_iframes_unittest.mm b/components/autofill/ios/browser/autofill_across_iframes_unittest.mm
index bb14652..ce1db48f 100644
--- a/components/autofill/ios/browser/autofill_across_iframes_unittest.mm
+++ b/components/autofill/ios/browser/autofill_across_iframes_unittest.mm
@@ -16,6 +16,7 @@
 #import "components/autofill/ios/browser/autofill_driver_ios.h"
 #import "components/autofill/ios/browser/autofill_driver_ios_factory.h"
 #import "components/autofill/ios/browser/autofill_java_script_feature.h"
+#import "components/autofill/ios/browser/mock_password_autofill_agent_delegate.h"
 #import "components/autofill/ios/browser/test_autofill_manager_injector.h"
 #import "components/autofill/ios/form_util/autofill_test_with_web_state.h"
 #import "components/autofill/ios/form_util/child_frame_registrar.h"
@@ -120,6 +121,10 @@
     autofill::AutofillDriverIOSFactory::CreateForWebState(
         web_state(), &autofill_client_, /*bridge=*/autofill_agent_,
         /*locale=*/"en");
+
+    // Password autofill agent needs to exist before any call to fill data.
+    autofill::PasswordAutofillAgent::CreateForWebState(web_state(),
+                                                       &delegate_mock_);
   }
 
   web::WebFrame* WaitForMainFrame() {
@@ -181,6 +186,7 @@
   std::unique_ptr<PrefService> prefs_;
   autofill::TestAutofillClient autofill_client_;
   AutofillAgent* autofill_agent_;
+  autofill::MockPasswordAutofillAgentDelegate delegate_mock_;
   base::test::ScopedFeatureList feature_list_;
 
   net::EmbeddedTestServer test_server_;
diff --git a/components/autofill/ios/browser/autofill_agent.mm b/components/autofill/ios/browser/autofill_agent.mm
index 1124451..ef17399 100644
--- a/components/autofill/ios/browser/autofill_agent.mm
+++ b/components/autofill/ios/browser/autofill_agent.mm
@@ -6,11 +6,14 @@
 
 #import <UIKit/UIKit.h>
 
+#import <cstdint>
 #import <memory>
 #import <string>
+#import <tuple>
 #import <utility>
 
 #import "base/apple/foundation_util.h"
+#import "base/containers/map_util.h"
 #import "base/feature_list.h"
 #import "base/format_macros.h"
 #import "base/json/json_reader.h"
@@ -44,11 +47,13 @@
 #import "components/autofill/core/common/form_data.h"
 #import "components/autofill/core/common/form_data_predictions.h"
 #import "components/autofill/core/common/form_field_data.h"
+#import "components/autofill/core/common/unique_ids.h"
 #import "components/autofill/ios/browser/autofill_driver_ios.h"
 #import "components/autofill/ios/browser/autofill_java_script_feature.h"
 #import "components/autofill/ios/browser/autofill_util.h"
 #import "components/autofill/ios/browser/form_suggestion.h"
 #import "components/autofill/ios/browser/form_suggestion_provider.h"
+#import "components/autofill/ios/browser/password_autofill_agent.h"
 #import "components/autofill/ios/common/features.h"
 #import "components/autofill/ios/common/field_data_manager_factory_ios.h"
 #import "components/autofill/ios/form_util/form_activity_observer_bridge.h"
@@ -90,6 +95,17 @@
 namespace {
 
 using FormDataVector = std::vector<autofill::FormData>;
+// Maps each field id to their respective host form id. This is needed as the
+// information linking the fields to their host form is lost between the moment
+// of filling and when receiving the filling response.
+using FieldToFormLookupMap = std::map<FieldRendererId, FormRendererId>;
+
+// Contains the data for doing filling.
+struct AutofillData {
+  std::string frameID;
+  base::Value::Dict payload;
+  FieldToFormLookupMap fieldToFormLookupMap;
+};
 
 // The type of the completion handler block for
 // |fetchFormsWithName:completionHandler|
@@ -173,10 +189,8 @@
   // BrowserAutofillManagerDelegate.
   base::WeakPtr<autofill::AutofillPopupDelegate> _popupDelegate;
 
-  // The autofill data that needs to be send when the |webState_| is shown.
-  // The pair contains the frame ID and the base::Value::Dict to send.
-  // If the value is nullopt, no data needs to be sent.
-  std::optional<std::pair<std::string, base::Value::Dict>> _pendingFormData;
+  // The autofill data that needs to be sent when the |webState_| is shown.
+  std::optional<AutofillData> _pendingFormData;
 
   // Bridge to listen to pref changes.
   std::unique_ptr<PrefObserverBridge> _prefObserverBridge;
@@ -399,19 +413,6 @@
   completion(_mostRecentSuggestions, self);
 }
 
-- (void)updateFieldManagerForClearedIDs:(NSString*)jsonString
-                                inFrame:(web::WebFrame*)frame {
-  std::optional<std::set<FieldRendererId>> clearingResults =
-      autofill::ExtractIDs<FieldRendererId>(jsonString);
-
-  if (clearingResults) {
-    for (auto uniqueID : *clearingResults) {
-      FieldDataManagerFactoryIOS::FromWebFrame(frame)->UpdateFieldDataMap(
-          uniqueID, std::u16string(), kAutofilledOnUserTrigger);
-    }
-  }
-}
-
 - (void)didSelectSuggestion:(FormSuggestion*)suggestion
                        form:(NSString*)formName
              formRendererID:(FormRendererId)formRendererID
@@ -432,15 +433,17 @@
                       kA11yAnnouncementQueueDelay.InNanoseconds()),
         dispatch_get_main_queue(), ^{
           AutofillAgent* strongSelf = weakSelf;
-          if (!strongSelf)
+          if (!strongSelf) {
             return;
+          }
 
-          // Queueing flag allows to preserve standard announcements, they
-          // are conveyed first and then announce this message.
-          // This is a tradeoff as there is no control over the standard
-          // utterances (they are interrupting) and it is not desirable
-          // to interrupt them. Hence acceptance announcement is done after
-          // standard ones (which takes seconds).
+          // Queueing flag allows to preserve standard announcements,
+          // they are conveyed first and then announce this message.
+          // This is a tradeoff as there is no control over the
+          // standard utterances (they are interrupting) and it is
+          // not desirable to interrupt them. Hence acceptance
+          // announcement is done after standard ones (which takes
+          // seconds).
           NSAttributedString* message = [[NSAttributedString alloc]
               initWithString:suggestion.acceptanceA11yAnnouncement
                   attributes:@{
@@ -501,22 +504,21 @@
     // FormSuggestion is a simple, single value that can be filled out now.
     [self fillField:SysNSStringToUTF8(fieldIdentifier)
         fieldRendererID:fieldRendererID
+         formRendererID:formRendererID
                formName:SysNSStringToUTF8(formName)
                   value:SysNSStringToUTF16(suggestion.value)
                 inFrame:frame];
   } else if (suggestion.popupItemId == autofill::SuggestionType::kClearForm) {
-    __weak AutofillAgent* weakSelf = self;
+    __weak __typeof(self) weakSelf = self;
     SuggestionHandledCompletion suggestionHandledCompletionCopy =
         [_suggestionHandledCompletion copy];
     _suggestionHandledCompletion = nil;
     AutofillJavaScriptFeature::GetInstance()->ClearAutofilledFieldsForForm(
         frame, formRendererID, fieldRendererID,
         base::BindOnce(^(NSString* jsonString) {
-          AutofillAgent* strongSelf = weakSelf;
-          if (!strongSelf) {
-            return;
-          }
-          [strongSelf updateFieldManagerForClearedIDs:jsonString inFrame:frame];
+          [weakSelf onDidClearFields:jsonString
+                             inFrame:frame
+                              inForm:formRendererID];
           suggestionHandledCompletionCopy();
         }));
 
@@ -546,9 +548,9 @@
 
 - (void)fillData:(const std::vector<autofill::FormFieldData::FillData>&)data
          inFrame:(web::WebFrame*)frame {
-  base::Value::Dict autofillData;
-
   base::Value::Dict fieldsData;
+  FieldToFormLookupMap fieldToFormLookupMap;
+
   for (const auto& field : data) {
     // Skip empty fields and those that are not autofilled.
     if (field.value.empty() || !field.is_autofilled) {
@@ -560,14 +562,19 @@
     fieldData.Set("section", field.section.ToString());
     fieldData.Set("hostFormId", static_cast<int>(*field.host_form_id));
     fieldsData.Set(NumberToString(*field.renderer_id), std::move(fieldData));
+
+    fieldToFormLookupMap[field.renderer_id] = field.host_form_id;
   }
-  autofillData.Set("fields", std::move(fieldsData));
+  auto payload = base::Value::Dict().Set("fields", std::move(fieldsData));
+  AutofillData autofillData = {
+      .frameID = frame ? frame->GetFrameId() : "",
+      .payload = std::move(payload),
+      .fieldToFormLookupMap = std::move(fieldToFormLookupMap)};
 
   // Store the form data when WebState is not visible, to send it as soon as it
   // becomes visible again, e.g., when the CVC unmask prompt is showing.
   if (!_webState->IsVisible()) {
-    _pendingFormData = std::make_pair(frame ? frame->GetFrameId() : "",
-                                      std::move(autofillData));
+    _pendingFormData = std::move(autofillData);
   } else {
     [self sendData:std::move(autofillData) toFrame:frame];
   }
@@ -784,20 +791,18 @@
 
 - (void)webStateWasShown:(web::WebState*)webState {
   DCHECK_EQ(_webState, webState);
-  if (!_pendingFormData.has_value()) {
+  if (!_pendingFormData) {
     return;
   }
 
-  std::pair<std::string, base::Value::Dict> pendingFormData =
-      std::move(_pendingFormData).value();
-  _pendingFormData = std::nullopt;
-
   // The frameID cannot be empty.
-  DCHECK(!pendingFormData.first.empty());
+  const std::string& frameID = _pendingFormData->frameID;
+  CHECK(!frameID.empty());
   web::WebFramesManager* frames_manager =
       AutofillJavaScriptFeature::GetInstance()->GetWebFramesManager(_webState);
-  web::WebFrame* frame = frames_manager->GetFrameWithId(pendingFormData.first);
-  [self sendData:std::move(pendingFormData.second) toFrame:frame];
+  web::WebFrame* frame = frames_manager->GetFrameWithId(frameID);
+  [self sendData:std::move(*_pendingFormData) toFrame:frame];
+  _pendingFormData.reset();
 }
 
 - (void)webState:(web::WebState*)webState didLoadPageWithSuccess:(BOOL)success {
@@ -1056,7 +1061,7 @@
          _webState->ContentIsHTML();
 }
 
-// Complete a field identified with |fieldIdentifier| on the form named
+// Fills a field identified with |fieldIdentifier| on the form named
 // |formName| in |frame| using |value| then move the cursor.
 // TODO(crbug.com/41284261): |dataString| ends up at fillFormField() in
 // autofill_controller.js. fillFormField() expects an AutofillFormFieldData
@@ -1065,6 +1070,7 @@
 // 'is_checked' to exist.
 - (void)fillField:(const std::string&)fieldIdentifier
     fieldRendererID:(FieldRendererId)fieldRendererID
+     formRendererID:(FormRendererId)formRendererID
            formName:(const std::string&)formName
               value:(const std::u16string)value
             inFrame:(web::WebFrame*)frame {
@@ -1085,23 +1091,66 @@
         if (!strongSelf)
           return;
         if (success) {
-          [strongSelf updateFieldManagerForSpecificField:fieldRendererID
-                                                 inFrame:frame
-                                               withValue:value];
+          [strongSelf onDidFillField:fieldRendererID
+                                form:formRendererID
+                               frame:frame
+                               value:value];
         }
         suggestionHandledCompletionCopy();
       }));
 }
 
-- (void)updateFieldManagerWithFillingResults:(NSString*)jsonString
-                                     inFrame:(web::WebFrame*)frame {
+// Called when did fill a specific field.
+- (void)onDidFillField:(FieldRendererId)fieldID
+                  form:(FormRendererId)formID
+                 frame:(web::WebFrame*)frame
+                 value:(const std::u16string&)value {
+  [self updateFieldManagerForSpecificField:fieldID
+                                   inFrame:frame
+                                 withValue:value];
+  [self notifyAboutValueChangeOnField:fieldID
+                               inForm:formID
+                                frame:frame
+                            withValue:value];
+}
+
+// Called when did fill multiple fields and received results serialized in a
+// JSON string.
+- (void)onDidFillWithResults:(NSString*)resultsAsJsonStr
+                     inFrame:(web::WebFrame*)frame
+        fieldToFormLookupMap:(const FieldToFormLookupMap&)fieldToFormLookupMap {
   std::map<uint32_t, std::u16string> fillingResults;
-  if (autofill::ExtractFillingResults(jsonString, &fillingResults)) {
-    for (auto& fillData : fillingResults) {
-      FieldDataManagerFactoryIOS::FromWebFrame(frame)->UpdateFieldDataMap(
-          FieldRendererId(fillData.first), fillData.second,
-          kAutofilledOnUserTrigger);
-    }
+  if (!autofill::ExtractFillingResults(resultsAsJsonStr, &fillingResults)) {
+    return;
+  }
+  [self updateFieldManagerWithFillingResults:fillingResults inFrame:frame];
+  [self notifyAboutFormFillingResults:fillingResults
+                              inFrame:frame
+                 fieldToFormLookupMap:fieldToFormLookupMap];
+}
+
+// Called when did clear fields.
+- (void)onDidClearFields:(NSString*)clearedFieldsAsJsonStr
+                 inFrame:(web::WebFrame*)frame
+                  inForm:(FormRendererId)formID {
+  const auto clearedIDs =
+      autofill::ExtractIDs<FieldRendererId>(clearedFieldsAsJsonStr);
+  if (!clearedIDs) {
+    return;
+  }
+
+  [self updateFieldManagerForClearedIDs:*clearedIDs inFrame:frame];
+  [self notifyAboutClearedFields:*clearedIDs inFrame:frame inForm:formID];
+}
+
+// Updates field managers with filling results.
+- (void)updateFieldManagerWithFillingResults:
+            (const std::map<uint32_t, std::u16string>&)fillingResults
+                                     inFrame:(web::WebFrame*)frame {
+  for (auto& fillData : fillingResults) {
+    [self updateFieldManagerForSpecificField:FieldRendererId(fillData.first)
+                                     inFrame:frame
+                                   withValue:fillData.second];
   }
 
   // TODO(crbug.com/40150011): Remove once the experiment is over.
@@ -1120,8 +1169,65 @@
       fieldRendererID, value, kAutofilledOnUserTrigger);
 }
 
+// Updates field managers for cleared fields.
+- (void)updateFieldManagerForClearedIDs:
+            (const std::set<FieldRendererId>&)clearedFields
+                                inFrame:(web::WebFrame*)frame {
+  for (const auto fieldID : clearedFields) {
+    [self updateFieldManagerForSpecificField:fieldID
+                                     inFrame:frame
+                                   withValue:u""];
+  }
+}
+
+// Notifies the PasswordAutofillAgent that the value of a field has changed.
+- (void)notifyAboutValueChangeOnField:(FieldRendererId)fieldID
+                               inForm:(FormRendererId)formID
+                                frame:(web::WebFrame*)frame
+                            withValue:(const std::u16string&)value {
+  CHECK(frame);
+
+  autofill::PasswordAutofillAgent* agent =
+      autofill::PasswordAutofillAgent::FromWebState(_webState);
+  agent->DidFillField(frame, formID, fieldID, value);
+}
+
+// Notifies that form filling results were received.
+- (void)notifyAboutFormFillingResults:
+            (const std::map<uint32_t, std::u16string>&)fillingResults
+                              inFrame:(web::WebFrame*)frame
+                 fieldToFormLookupMap:
+                     (const FieldToFormLookupMap&)fieldToFormLookupMap {
+  CHECK(frame);
+
+  for (auto& fillData : fillingResults) {
+    FieldRendererId fieldID = FieldRendererId(fillData.first);
+    if (const FormRendererId* formID =
+            base::FindOrNull(fieldToFormLookupMap, fieldID)) {
+      [self notifyAboutValueChangeOnField:fieldID
+                                   inForm:*formID
+                                    frame:frame
+                                withValue:fillData.second];
+    }
+  }
+}
+
+// Notifies that fields were cleared.
+- (void)notifyAboutClearedFields:(const std::set<FieldRendererId>&)clearedFields
+                         inFrame:(web::WebFrame*)frame
+                          inForm:(FormRendererId)formID {
+  CHECK(frame);
+
+  for (auto fieldID : clearedFields) {
+    [self notifyAboutValueChangeOnField:fieldID
+                                 inForm:formID
+                                  frame:frame
+                              withValue:u""];
+  }
+}
+
 // Sends the the |data| to |frame| to actually fill the data.
-- (void)sendData:(base::Value::Dict)data toFrame:(web::WebFrame*)frame {
+- (void)sendData:(AutofillData)data toFrame:(web::WebFrame*)frame {
   DCHECK(_webState->IsVisible());
   __weak AutofillAgent* weakSelf = self;
   SuggestionHandledCompletion suggestionHandledCompletionCopy =
@@ -1129,19 +1235,20 @@
   _suggestionHandledCompletion = nil;
 
   AutofillJavaScriptFeature::GetInstance()->FillForm(
-      frame, std::move(data), _pendingAutocompleteFieldID,
-      base::BindOnce(^(NSString* jsonString) {
-        AutofillAgent* strongSelf = weakSelf;
-        if (!strongSelf)
-          return;
-        [strongSelf updateFieldManagerWithFillingResults:jsonString
-                                                 inFrame:frame];
-        // It is possible that the fill was not initiated by selecting
-        // a suggestion in this case the callback is nil.
-        if (suggestionHandledCompletionCopy) {
-          suggestionHandledCompletionCopy();
-        }
-      }));
+      frame, std::move(data.payload), _pendingAutocompleteFieldID,
+      base::BindOnce(
+          ^(const FieldToFormLookupMap& map, NSString* jsonString) {
+            [weakSelf onDidFillWithResults:jsonString
+                                   inFrame:frame
+                      fieldToFormLookupMap:map];
+
+            // It is possible that the fill was not initiated by selecting
+            // a suggestion in this case the callback is nil.
+            if (suggestionHandledCompletionCopy) {
+              suggestionHandledCompletionCopy();
+            }
+          },
+          std::move(data.fieldToFormLookupMap)));
 }
 
 // Helper method used to implement the aynchronous completion block of
diff --git a/components/autofill/ios/browser/autofill_agent_unittests.mm b/components/autofill/ios/browser/autofill_agent_unittests.mm
index 5c3aa4a..82433b0 100644
--- a/components/autofill/ios/browser/autofill_agent_unittests.mm
+++ b/components/autofill/ios/browser/autofill_agent_unittests.mm
@@ -4,16 +4,21 @@
 
 #import "components/autofill/ios/browser/autofill_agent.h"
 
+#import <string>
+
 #import "base/apple/bundle_locations.h"
+#import "base/json/json_writer.h"
 #import "base/memory/raw_ptr.h"
 #import "base/memory/weak_ptr.h"
 #import "base/strings/strcat.h"
+#import "base/strings/string_number_conversions.h"
 #import "base/strings/sys_string_conversions.h"
 #import "base/strings/utf_string_conversions.h"
 #import "base/test/gtest_util.h"
 #import "base/test/ios/wait_util.h"
 #import "base/test/scoped_feature_list.h"
 #import "base/test/test_timeouts.h"
+#import "base/values.h"
 #import "components/autofill/core/browser/autofill_test_utils.h"
 #import "components/autofill/core/browser/data_model/credit_card.h"
 #import "components/autofill/core/browser/filling_product.h"
@@ -26,11 +31,15 @@
 #import "components/autofill/core/common/autofill_prefs.h"
 #import "components/autofill/core/common/field_data_manager.h"
 #import "components/autofill/core/common/form_data.h"
+#include "components/autofill/core/common/form_field_data.h"
 #import "components/autofill/core/common/mojom/autofill_types.mojom-shared.h"
+#import "components/autofill/core/common/unique_ids.h"
 #import "components/autofill/ios/browser/autofill_driver_ios.h"
 #import "components/autofill/ios/browser/autofill_driver_ios_factory.h"
 #import "components/autofill/ios/browser/autofill_java_script_feature.h"
 #import "components/autofill/ios/browser/autofill_util.h"
+#import "components/autofill/ios/browser/mock_password_autofill_agent_delegate.h"
+#import "components/autofill/ios/browser/password_autofill_agent.h"
 #import "components/autofill/ios/common/field_data_manager_factory_ios.h"
 #import "components/autofill/ios/form_util/form_handlers_java_script_feature.h"
 #import "components/autofill/ios/form_util/form_util_java_script_feature.h"
@@ -39,6 +48,7 @@
 #import "ios/web/public/test/fakes/fake_web_frame.h"
 #import "ios/web/public/test/fakes/fake_web_frames_manager.h"
 #import "ios/web/public/test/fakes/fake_web_state.h"
+#import "ios/web/public/test/task_observer_util.h"
 #import "ios/web/public/test/web_test.h"
 #import "services/metrics/public/cpp/ukm_builders.h"
 #import "testing/gmock/include/gmock/gmock.h"
@@ -56,6 +66,33 @@
 using autofill::SuggestionType;
 using base::test::ios::WaitUntilConditionOrTimeout;
 
+namespace {
+
+// Returns the minimal FormData content for testing filling.
+std::vector<autofill::FormFieldData::FillData>
+MinimalFormFieldDataForFilling() {
+  autofill::FormFieldData field;
+  field.set_value(u"test-username");
+  field.set_host_form_id(FormRendererId(1));
+  field.set_renderer_id(FieldRendererId(2));
+  field.set_is_autofilled(true);
+  return {autofill::FormFieldData::FillData(std::move(field))};
+}
+
+// Returns a simple form suggestion that only consists of a `value` and an
+// `item_id`.
+FormSuggestion* SimpleFormSuggestion(std::u16string value,
+                                     autofill::SuggestionType item_id) {
+  return [FormSuggestion suggestionWithValue:base::SysUTF16ToNSString(value)
+                          displayDescription:@""
+                                        icon:nil
+                                 popupItemId:item_id
+                           backendIdentifier:@""
+                              requiresReauth:NO];
+}
+
+}  // namespace
+
 @interface AutofillAgent (Testing)
 - (void)updateFieldManagerWithFillingResults:(NSString*)jsonString
                                      inFrame:(web::WebFrame*)frame;
@@ -101,6 +138,9 @@
     fake_main_frame_ = main_frame.get();
     AddWebFrame(std::move(main_frame));
 
+    autofill::PasswordAutofillAgent::CreateForWebState(&fake_web_state_,
+                                                       &delegate_mock_);
+
     prefs_ = autofill::test::PrefServiceForTesting();
     autofill::prefs::SetAutofillProfileEnabled(prefs_.get(), true);
     autofill::prefs::SetAutofillPaymentMethodsEnabled(prefs_.get(), true);
@@ -134,6 +174,7 @@
   raw_ptr<web::FakeWebFrame> fake_main_frame_ = nullptr;
   raw_ptr<web::FakeWebFramesManager> fake_web_frames_manager_ = nullptr;
   AutofillAgent* autofill_agent_;
+  autofill::MockPasswordAutofillAgentDelegate delegate_mock_;
 };
 
 // Tests that form's name and fields' identifiers, values, and whether they are
@@ -877,20 +918,45 @@
   RemoveWebFrame(iframe->GetFrameId());
 }
 
-TEST_F(AutofillAgentTests, UpdateFieldManagerWithFillingResults) {
+TEST_F(AutofillAgentTests, FillData_UpdateWithResults) {
   auto test_recorder = std::make_unique<ukm::TestAutoSetUkmRecorder>();
 
   std::string locale("en");
   autofill::AutofillDriverIOSFactory::CreateForWebState(&fake_web_state_,
                                                         &client_, nil, locale);
 
-  [autofill_agent_ updateFieldManagerWithFillingResults:@"{\"2\":\"Val1\"}"
-                                                inFrame:fake_main_frame_];
+  std::vector<autofill::FormFieldData::FillData> fields =
+      MinimalFormFieldDataForFilling();
+  const std::u16string& field_value = fields[0].value;
+  const FormRendererId form_id = fields[0].host_form_id;
+  const FieldRendererId field_id = fields[0].renderer_id;
 
-  // Check recorded FieldDataManager data.
+  // Set the result returned from filling.
+  std::string serializedResult;
+  ASSERT_TRUE(base::JSONWriter::Write(
+      base::Value::Dict().Set(base::NumberToString(field_id.value()),
+                              base::UTF16ToUTF8(field_value)),
+      &serializedResult));
+  base::Value result(serializedResult);
+  fake_main_frame_->AddJsResultForFunctionCall(&result, "autofill.fillForm");
+
+  EXPECT_CALL(delegate_mock_, DidFillField(fake_main_frame_.get(), form_id,
+                                           field_id, field_value));
+
+  // Declare the page as shown to allow filling.
+  fake_web_state_.WasShown();
+
+  // Fill form data.
+  [autofill_agent_ fillData:fields inFrame:fake_main_frame_];
+
+  // Run queues to yield the filling results.
+  web::test::WaitForBackgroundTasks();
+
+  // Check that the field value update was propagated to the FieldDataManager of
+  // the web frame.
   FieldDataManager* fieldDataManager =
       autofill::FieldDataManagerFactoryIOS::FromWebFrame(fake_main_frame_);
-  EXPECT_TRUE(fieldDataManager->WasAutofilledOnUserTrigger(FieldRendererId(2)));
+  EXPECT_TRUE(fieldDataManager->WasAutofilledOnUserTrigger(field_id));
 
   // Check recorded UKM.
   auto entries = test_recorder->GetEntriesByName(
@@ -899,3 +965,139 @@
   ASSERT_EQ(1u, entries.size());
   test_recorder->ExpectEntryMetric(entries[0], "FormFillSuccess", true);
 }
+
+// Tests that if there is an unknown field id in the results, the agent isn't
+// notified.
+TEST_F(AutofillAgentTests, FillData_UnknowFieldIdInResults) {
+  autofill::AutofillDriverIOSFactory::CreateForWebState(&fake_web_state_,
+                                                        &client_, nil, "en");
+
+  std::vector<autofill::FormFieldData::FillData> fields =
+      MinimalFormFieldDataForFilling();
+  const FieldRendererId unknown_field_id = FieldRendererId(101);
+
+  // Set the result returned from filling.
+  std::string serializedResult;
+  ASSERT_TRUE(base::JSONWriter::Write(
+      base::Value::Dict().Set(base::NumberToString(unknown_field_id.value()),
+                              base::UTF16ToUTF8(fields[0].value)),
+      &serializedResult));
+  base::Value result(serializedResult);
+  fake_main_frame_->AddJsResultForFunctionCall(&result, "autofill.fillForm");
+
+  EXPECT_CALL(delegate_mock_, DidFillField).Times(0);
+
+  // Declare the page as shown to allow filling.
+  fake_web_state_.WasShown();
+
+  // Fill form data.
+  [autofill_agent_ fillData:fields inFrame:fake_main_frame_];
+
+  // Run queues to yield the filling results.
+  web::test::WaitForBackgroundTasks();
+}
+
+// Tests selecting an autocomplete suggestion.
+TEST_F(AutofillAgentTests, DidSelectSuggestion_AutocompleteEntry) {
+  autofill::AutofillDriverIOSFactory::CreateForWebState(
+      &fake_web_state_, &client_, nil, /*locale=*/"en");
+
+  FormRendererId form_id(1);
+  FieldRendererId field1_id(2);
+  const std::u16string field1_value = u"test-value";
+
+  // Set the result returned from filling as a success.
+  base::Value result(true);
+  fake_main_frame_->AddJsResultForFunctionCall(&result,
+                                               "autofill.fillActiveFormField");
+
+  // Declare the page as shown to allow field filling.
+  fake_web_state_.WasShown();
+
+  // Select suggestion to trigger field filling.
+  __block BOOL completion_handler_called = NO;
+  FormSuggestion* form_suggestion = SimpleFormSuggestion(
+      field1_value, autofill::SuggestionType::kAutocompleteEntry);
+  [autofill_agent_ didSelectSuggestion:form_suggestion
+                                  form:@"single-username-form"
+                        formRendererID:form_id
+                       fieldIdentifier:@"username-field-1"
+                       fieldRendererID:field1_id
+                               frameID:base::SysUTF8ToNSString(
+                                           fake_main_frame_->GetFrameId())
+                     completionHandler:^() {
+                       completion_handler_called = YES;
+                     }];
+
+  EXPECT_CALL(delegate_mock_, DidFillField(fake_main_frame_.get(), form_id,
+                                           field1_id, field1_value));
+
+  // Run queues to yield the field filling results from the JS call.
+  web::test::WaitForBackgroundTasks();
+
+  // Check that the field value update was propagated to the FieldDataManager of
+  // the web frame.
+  FieldDataManager* fieldDataManager =
+      autofill::FieldDataManagerFactoryIOS::FromWebFrame(fake_main_frame_);
+  EXPECT_TRUE(fieldDataManager->WasAutofilledOnUserTrigger(field1_id));
+
+  // Check that the completion handler was called after handling the results
+  // from the JS call.
+  EXPECT_TRUE(completion_handler_called);
+}
+
+TEST_F(AutofillAgentTests, DidSelectSuggestion_ClearFormEntry) {
+  autofill::AutofillDriverIOSFactory::CreateForWebState(
+      &fake_web_state_, &client_, nil, /*locale=*/"en");
+
+  FormRendererId form_id(1);
+  FieldRendererId field1_id(2);
+  FieldRendererId field2_id(3);
+
+  // Set the result returned from filling.
+  std::string serializedResult;
+  ASSERT_TRUE(base::JSONWriter::Write(
+      base::Value::List()
+          .Append(base::Value(base::NumberToString(field1_id.value())))
+          .Append(base::Value(base::NumberToString(field2_id.value()))),
+      &serializedResult));
+  base::Value result(serializedResult);
+  fake_main_frame_->AddJsResultForFunctionCall(
+      &result, "autofill.clearAutofilledFields");
+
+  // Declare the page as shown to allow field filling.
+  fake_web_state_.WasShown();
+
+  // Select suggestion to trigger field filling.
+  __block BOOL completion_handler_called = NO;
+  FormSuggestion* form_suggestion =
+      SimpleFormSuggestion(u"", autofill::SuggestionType::kClearForm);
+  [autofill_agent_ didSelectSuggestion:form_suggestion
+                                  form:@"single-username-form"
+                        formRendererID:form_id
+                       fieldIdentifier:@"username-field-1"
+                       fieldRendererID:field1_id
+                               frameID:base::SysUTF8ToNSString(
+                                           fake_main_frame_->GetFrameId())
+                     completionHandler:^() {
+                       completion_handler_called = YES;
+                     }];
+
+  EXPECT_CALL(delegate_mock_, DidFillField(fake_main_frame_.get(), form_id,
+                                           field1_id, ::testing::IsEmpty()));
+  EXPECT_CALL(delegate_mock_, DidFillField(fake_main_frame_.get(), form_id,
+                                           field2_id, ::testing::IsEmpty()));
+
+  // Run queues to yield the field filling results from the JS call.
+  web::test::WaitForBackgroundTasks();
+
+  // Check that the cleared field IDs aren't labeled as filled.
+  FieldDataManager* fieldDataManager =
+      autofill::FieldDataManagerFactoryIOS::FromWebFrame(fake_main_frame_);
+  EXPECT_FALSE(fieldDataManager->WasAutofilledOnUserTrigger(field1_id));
+  EXPECT_FALSE(fieldDataManager->WasAutofilledOnUserTrigger(field2_id));
+
+  // Check that the completion handler was called after handling the results
+  // from the JS call.
+  EXPECT_TRUE(completion_handler_called);
+}
diff --git a/components/autofill/ios/browser/mock_password_autofill_agent_delegate.h b/components/autofill/ios/browser/mock_password_autofill_agent_delegate.h
new file mode 100644
index 0000000..b2b1acb
--- /dev/null
+++ b/components/autofill/ios/browser/mock_password_autofill_agent_delegate.h
@@ -0,0 +1,39 @@
+// 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_AUTOFILL_IOS_BROWSER_MOCK_PASSWORD_AUTOFILL_AGENT_DELEGATE_H_
+#define COMPONENTS_AUTOFILL_IOS_BROWSER_MOCK_PASSWORD_AUTOFILL_AGENT_DELEGATE_H_
+
+#import <string>
+
+#import "components/autofill/core/common/unique_ids.h"
+#import "components/autofill/ios/browser/password_autofill_agent.h"
+#import "testing/gmock/include/gmock/gmock.h"
+
+namespace web {
+class WebFrame;
+}
+
+namespace autofill {
+
+class MockPasswordAutofillAgentDelegate : public PasswordAutofillAgentDelegate {
+ public:
+  MockPasswordAutofillAgentDelegate();
+  ~MockPasswordAutofillAgentDelegate() override;
+
+  MockPasswordAutofillAgentDelegate(const MockPasswordAutofillAgentDelegate&) =
+      delete;
+  MockPasswordAutofillAgentDelegate& operator=(
+      const MockPasswordAutofillAgentDelegate&) = delete;
+
+  MOCK_METHOD(
+      void,
+      DidFillField,
+      (web::WebFrame*, FormRendererId, FieldRendererId, const std::u16string&),
+      (override));
+};
+
+}  // namespace autofill
+
+#endif  // COMPONENTS_AUTOFILL_IOS_BROWSER_MOCK_PASSWORD_AUTOFILL_AGENT_DELEGATE_H_
diff --git a/components/autofill/ios/browser/mock_password_autofill_agent_delegate.mm b/components/autofill/ios/browser/mock_password_autofill_agent_delegate.mm
new file mode 100644
index 0000000..a2f74175
--- /dev/null
+++ b/components/autofill/ios/browser/mock_password_autofill_agent_delegate.mm
@@ -0,0 +1,15 @@
+// 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 "components/autofill/ios/browser/mock_password_autofill_agent_delegate.h"
+
+namespace autofill {
+
+MockPasswordAutofillAgentDelegate::MockPasswordAutofillAgentDelegate() =
+    default;
+
+MockPasswordAutofillAgentDelegate::~MockPasswordAutofillAgentDelegate() =
+    default;
+
+}  // namespace autofill
diff --git a/components/autofill/ios/browser/password_autofill_agent.h b/components/autofill/ios/browser/password_autofill_agent.h
new file mode 100644
index 0000000..67bed7b
--- /dev/null
+++ b/components/autofill/ios/browser/password_autofill_agent.h
@@ -0,0 +1,76 @@
+// 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_AUTOFILL_IOS_BROWSER_PASSWORD_AUTOFILL_AGENT_H_
+#define COMPONENTS_AUTOFILL_IOS_BROWSER_PASSWORD_AUTOFILL_AGENT_H_
+
+#import <string>
+
+#import "base/memory/raw_ptr.h"
+#import "components/autofill/core/common/unique_ids.h"
+#import "ios/web/public/web_state_observer.h"
+#import "ios/web/public/web_state_user_data.h"
+
+namespace web {
+class WebFrame;
+class WebState;
+}  // namespace web
+
+namespace autofill {
+
+// Delegate that handles things related to the Password Manager for the agent.
+// This allows cutting the dependency cycle between the agent and Password
+// Manager.
+class PasswordAutofillAgentDelegate {
+ public:
+  PasswordAutofillAgentDelegate() = default;
+  virtual ~PasswordAutofillAgentDelegate() = default;
+
+  PasswordAutofillAgentDelegate(const PasswordAutofillAgentDelegate&) = delete;
+  PasswordAutofillAgentDelegate& operator=(
+      const PasswordAutofillAgentDelegate&) = delete;
+
+  // Indicates that the user did fill the field with `field_value`.
+  virtual void DidFillField(web::WebFrame* frame,
+                            autofill::FormRendererId form_id,
+                            autofill::FieldRendererId field_id,
+                            const std::u16string& field_value) = 0;
+};
+
+// Agent that represents the Password Manager in Autofill. Plays the role of
+// components/autofill/content/renderer/password_autofill_agent.h on the other
+// platforms.
+class PasswordAutofillAgent
+    : public web::WebStateObserver,
+      public web::WebStateUserData<PasswordAutofillAgent> {
+ public:
+  PasswordAutofillAgent();
+  ~PasswordAutofillAgent() override;
+
+  PasswordAutofillAgent(const PasswordAutofillAgent&) = delete;
+  PasswordAutofillAgent& operator=(const PasswordAutofillAgent&) = delete;
+
+  // Indicates to the agent that the user did an action on the form, e.g. fill
+  // the form.
+  void DidFillField(web::WebFrame* frame,
+                    autofill::FormRendererId form_id,
+                    autofill::FieldRendererId field_id,
+                    const std::u16string& field_value);
+
+  // web::WebStateObserver:
+  void WebStateDestroyed(web::WebState* web_state) override;
+
+ private:
+  friend class web::WebStateUserData<PasswordAutofillAgent>;
+
+  PasswordAutofillAgent(web::WebState* web_state,
+                        PasswordAutofillAgentDelegate* delegate);
+
+  raw_ptr<PasswordAutofillAgentDelegate> delegate_;
+  WEB_STATE_USER_DATA_KEY_DECL();
+};
+
+}  // namespace autofill
+
+#endif  // COMPONENTS_AUTOFILL_IOS_BROWSER_PASSWORD_AUTOFILL_AGENT_H_
diff --git a/components/autofill/ios/browser/password_autofill_agent.mm b/components/autofill/ios/browser/password_autofill_agent.mm
new file mode 100644
index 0000000..fe423da
--- /dev/null
+++ b/components/autofill/ios/browser/password_autofill_agent.mm
@@ -0,0 +1,36 @@
+// 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 "components/autofill/ios/browser/password_autofill_agent.h"
+
+namespace autofill {
+
+PasswordAutofillAgent::PasswordAutofillAgent() = default;
+
+PasswordAutofillAgent::~PasswordAutofillAgent() = default;
+
+void PasswordAutofillAgent::DidFillField(web::WebFrame* frame,
+                                         autofill::FormRendererId form_id,
+                                         autofill::FieldRendererId field_id,
+                                         const std::u16string& field_value) {
+  if (delegate_) {
+    delegate_->DidFillField(frame, form_id, field_id, field_value);
+  }
+}
+
+void PasswordAutofillAgent::WebStateDestroyed(web::WebState* web_state) {
+  delegate_ = nullptr;
+  web_state->RemoveObserver(this);
+}
+
+PasswordAutofillAgent::PasswordAutofillAgent(
+    web::WebState* web_state,
+    PasswordAutofillAgentDelegate* delegate)
+    : delegate_(delegate) {
+  web_state->AddObserver(this);
+}
+
+WEB_STATE_USER_DATA_KEY_IMPL(PasswordAutofillAgent)
+
+}  // namespace autofill
diff --git a/components/autofill/ios/browser/password_autofill_agent_unittest.mm b/components/autofill/ios/browser/password_autofill_agent_unittest.mm
new file mode 100644
index 0000000..795d548a
--- /dev/null
+++ b/components/autofill/ios/browser/password_autofill_agent_unittest.mm
@@ -0,0 +1,37 @@
+// 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 "components/autofill/ios/browser/password_autofill_agent.h"
+
+#import "components/autofill/core/common/unique_ids.h"
+#import "components/autofill/ios/browser/mock_password_autofill_agent_delegate.h"
+#import "ios/web/public/test/fakes/fake_web_frame.h"
+#import "ios/web/public/test/fakes/fake_web_state.h"
+#import "testing/gmock/include/gmock/gmock.h"
+#import "testing/gtest/include/gtest/gtest.h"
+#import "testing/platform_test.h"
+#import "url/gurl.h"
+
+namespace autofill {
+
+using PasswordAutofillAgentTest = PlatformTest;
+
+// Tests that DidFillField() correctly calls the delegate.
+TEST_F(PasswordAutofillAgentTest, DidFillField) {
+  GURL url("https://example.com");
+  auto frame = web::FakeWebFrame::Create("frameID", true, url);
+  autofill::FormRendererId form_id(1);
+  autofill::FieldRendererId field_id(2);
+  const std::u16string value(u"value");
+
+  MockPasswordAutofillAgentDelegate delegate_mock;
+  EXPECT_CALL(delegate_mock,
+              DidFillField(frame.get(), form_id, field_id, value));
+  web::FakeWebState fake_web_state;
+  PasswordAutofillAgent::CreateForWebState(&fake_web_state, &delegate_mock);
+  PasswordAutofillAgent::FromWebState(&fake_web_state)
+      ->DidFillField(frame.get(), form_id, field_id, value);
+}
+
+}  // namespace autofill
diff --git a/components/browser_ui/widget/android/BUILD.gn b/components/browser_ui/widget/android/BUILD.gn
index abdee2b5..99f5220 100644
--- a/components/browser_ui/widget/android/BUILD.gn
+++ b/components/browser_ui/widget/android/BUILD.gn
@@ -244,6 +244,7 @@
     "java/res/layout/expand_arrow_with_separator.xml",
     "java/res/layout/indeterminate_progress_view.xml",
     "java/res/layout/material_switch_with_text.xml",
+    "java/res/layout/modern_list_item_small_icon_view.xml",
     "java/res/layout/modern_list_item_view.xml",
     "java/res/layout/more_progress_button.xml",
     "java/res/layout/number_roll_view.xml",
diff --git a/components/browser_ui/widget/android/java/res/layout/modern_list_item_small_icon_view.xml b/components/browser_ui/widget/android/java/res/layout/modern_list_item_small_icon_view.xml
new file mode 100644
index 0000000..0576a020
--- /dev/null
+++ b/components/browser_ui/widget/android/java/res/layout/modern_list_item_small_icon_view.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+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.
+-->
+
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/content"
+    style="@style/ListItemSmallIconContainer" >
+
+    <org.chromium.ui.widget.ChromeImageView
+        android:id="@+id/start_icon"
+        style="@style/ListItemSmallStartIcon"
+        android:importantForAccessibility="no" />
+
+    <!-- Common title/description shared between list item views. -->
+    <include layout="@layout/title_and_description_layout" />
+
+    <org.chromium.ui.widget.ChromeImageButton
+        android:id="@+id/end_button"
+        android:visibility="gone"
+        style="@style/ListItemEndIcon" />
+
+</LinearLayout>
diff --git a/components/browser_ui/widget/android/java/res/values/dimens.xml b/components/browser_ui/widget/android/java/res/values/dimens.xml
index 8ecd56e..edd7146 100644
--- a/components/browser_ui/widget/android/java/res/values/dimens.xml
+++ b/components/browser_ui/widget/android/java/res/values/dimens.xml
@@ -50,6 +50,8 @@
     <dimen name="list_item_start_icon_right_margin">20dp</dimen>
     <dimen name="list_item_start_icon_width">36dp</dimen>
     <dimen name="list_item_end_icon_width">56dp</dimen>
+    <dimen name="list_item_start_icon_small_right_margin">16dp</dimen>
+    <dimen name="list_item_start_icon_small_width">20dp</dimen>
 
     <!-- SelectableListLayout dimensions -->
     <dimen name="selectable_list_action_bar_end_padding">6dp</dimen>
diff --git a/components/browser_ui/widget/android/java/res/values/styles.xml b/components/browser_ui/widget/android/java/res/values/styles.xml
index 446dbb35..d452b88e 100644
--- a/components/browser_ui/widget/android/java/res/values/styles.xml
+++ b/components/browser_ui/widget/android/java/res/values/styles.xml
@@ -75,6 +75,15 @@
         <item name="android:background">@null</item>
         <item name="android:scaleType">center</item>
     </style>
+    <style name="ListItemSmallIconContainer" parent="ListItemContainer">
+        <item name="android:minHeight">@dimen/min_touch_target_size</item>
+    </style>
+    <style name="ListItemSmallStartIcon" parent="ListItemStartIcon">
+        <item name="android:layout_width">@dimen/list_item_start_icon_small_width</item>
+        <item name="android:layout_height">@dimen/list_item_start_icon_small_width</item>
+        <item name="android:layout_marginEnd">@dimen/list_item_start_icon_small_right_margin</item>
+        <item name="android:minHeight">@dimen/min_touch_target_size</item>
+    </style>
     <!-- Promo dialogs -->
     <style name="PromoDialog" >
         <item name="android:background">@android:color/transparent</item>
diff --git a/components/commerce/core/product_specifications/product_specifications_sync_bridge_unittest.cc b/components/commerce/core/product_specifications/product_specifications_sync_bridge_unittest.cc
index 732365f2..2462a09 100644
--- a/components/commerce/core/product_specifications/product_specifications_sync_bridge_unittest.cc
+++ b/components/commerce/core/product_specifications/product_specifications_sync_bridge_unittest.cc
@@ -24,18 +24,18 @@
 
 namespace {
 
-const std::vector<std::string> kInitUuid = {
+constexpr std::string_view kInitUuid[] = {
     "10000000-0000-0000-0000-000000000000",
     "20000000-0000-0000-0000-000000000000",
     "30000000-0000-0000-0000-000000000000"};
-const std::vector<std::string> kInitName = {"my_name", "another name",
-                                            "yet another name"};
+constexpr std::string_view kInitName[] = {"my_name", "another name",
+                                          "yet another name"};
 const std::vector<int64_t> kCreationTime = {1710953277, 1711035900, 1711118523};
 const std::vector<int64_t> kUpdateTime = {
     kCreationTime[0] + base::Time::kMillisecondsPerDay,
     kCreationTime[1] + 2 * base::Time::kMillisecondsPerDay,
     kCreationTime[2] + base::Time::kMillisecondsPerDay};
-const std::vector<const std::vector<const std::string>> kCompareUrls = {
+constexpr std::pair<std::string_view, std::string_view> kCompareUrls[] = {
     {"https://foo.com/", "https://bar.com/"},
     {"https://foo-bar.com", "https://bar-foo.com"},
     {"https://amazon.com/dp/12345",
@@ -57,34 +57,31 @@
             compare_specifics->creation_time_unix_epoch_micros());
   EXPECT_EQ(kUpdateTime[idx],
             compare_specifics->update_time_unix_epoch_micros());
-  int j = 0;
-  for (auto& data : compare_specifics->data()) {
-    EXPECT_EQ(kCompareUrls[idx][j], data.url());
-    j++;
-  }
+  EXPECT_EQ(kCompareUrls[idx].first, compare_specifics->data()[0].url());
+  EXPECT_EQ(kCompareUrls[idx].second, compare_specifics->data()[1].url());
 }
 
 std::string GetName(uint64_t idx) {
-  return base::StringPrintf("%s_%s", kInitName[idx].c_str(),
-                            kInitUuid[idx].c_str());
+  return base::StringPrintf("%s_%s", kInitName[idx].data(),
+                            kInitUuid[idx].data());
 }
 
 sync_pb::CompareSpecifics BuildCompareSpecifics(
-    const std::string& uuid,
+    const std::string_view uuid,
     int64_t creation_time_micros_epoch,
     int64_t update_time_micros_epoch,
-    const std::string& name,
-    const std::vector<const std::string> urls) {
+    const std::string_view name,
+    const std::pair<std::string_view, std::string_view>& urls) {
   sync_pb::CompareSpecifics specifics;
-  specifics.set_uuid(uuid);
+  specifics.set_uuid(uuid.data());
   specifics.set_creation_time_unix_epoch_micros(creation_time_micros_epoch);
   specifics.set_update_time_unix_epoch_micros(update_time_micros_epoch);
-  specifics.set_name(name);
+  specifics.set_name(name.data());
 
-  for (auto& url : urls) {
-    sync_pb::ComparisonData* compare_data = specifics.add_data();
-    compare_data->set_url(url);
-  }
+  sync_pb::ComparisonData* compare_data = specifics.add_data();
+  compare_data->set_url(urls.first.data());
+  compare_data = specifics.add_data();
+  compare_data->set_url(urls.second.data());
   return specifics;
 }
 
@@ -163,16 +160,16 @@
   void AddInitialSpecifics() {
     std::unique_ptr<syncer::ModelTypeStore::WriteBatch> batch =
         store_->CreateWriteBatch();
-    for (uint64_t i = 0; i < kInitUuid.size(); i++) {
+    for (uint64_t i = 0; i < std::size(kInitUuid); i++) {
       sync_pb::CompareSpecifics compare_specifics;
-      compare_specifics.set_uuid(kInitUuid[i]);
-      compare_specifics.set_name(kInitName[i]);
+      compare_specifics.set_uuid(kInitUuid[i].data());
+      compare_specifics.set_name(kInitName[i].data());
       compare_specifics.set_creation_time_unix_epoch_micros(kCreationTime[i]);
       compare_specifics.set_update_time_unix_epoch_micros(kUpdateTime[i]);
-      for (auto& compare_url : kCompareUrls[i]) {
-        sync_pb::ComparisonData* compare_data = compare_specifics.add_data();
-        compare_data->set_url(compare_url);
-      }
+      sync_pb::ComparisonData* compare_data = compare_specifics.add_data();
+      compare_data->set_url(kCompareUrls[i].first.data());
+      compare_data = compare_specifics.add_data();
+      compare_data->set_url(kCompareUrls[i].second.data());
       batch->WriteData(compare_specifics.uuid(),
                        compare_specifics.SerializeAsString());
     }
@@ -180,7 +177,7 @@
   }
 
   std::optional<sync_pb::CompareSpecifics> AddProductSpecifications(
-      const std::string& name,
+      const std::string name,
       const std::vector<GURL> urls) {
     return bridge().AddProductSpecifications(name, urls);
   }
@@ -315,8 +312,8 @@
   // Leave out first entry. GetData takes in a set of keys
   // so we want to ensure the entries with keys specified are
   // returned, rather than all entries.
-  storage_keys.push_back(kInitUuid[1]);
-  storage_keys.push_back(kInitUuid[2]);
+  storage_keys.push_back(kInitUuid[1].data());
+  storage_keys.push_back(kInitUuid[2].data());
   base::RunLoop run_loop;
   bridge().GetData(
       std::move(storage_keys),
@@ -347,7 +344,7 @@
         std::vector<syncer::KeyAndData> key_and_data =
             GetKeyAndData(data_batch.get());
         EXPECT_EQ(3u, key_and_data.size());
-        for (uint64_t i = 0; i < kInitUuid.size(); i++) {
+        for (uint64_t i = 0; i < std::size(kInitUuid); i++) {
           EXPECT_EQ(kInitUuid[i], key_and_data[i].first);
           EXPECT_EQ(GetName(i), key_and_data[i].second->name);
           VerifySpecificsAgainstIndex(
@@ -445,13 +442,14 @@
 TEST_F(ProductSpecificationsSyncBridgeTest, AddProductSpecifications) {
   const std::optional<sync_pb::CompareSpecifics> new_specifics =
       AddProductSpecifications(
-          kInitName[0], {GURL(kCompareUrls[0][0]), GURL(kCompareUrls[0][1])});
+          kInitName[0].data(),
+          {GURL(kCompareUrls[0].first), GURL(kCompareUrls[0].second)});
   EXPECT_TRUE(new_specifics.has_value());
   base::RunLoop().RunUntilIdle();
   EXPECT_TRUE(new_specifics.has_value());
   EXPECT_EQ(kInitName[0], new_specifics->name());
-  EXPECT_EQ(kCompareUrls[0][0], new_specifics->data()[0].url());
-  EXPECT_EQ(kCompareUrls[0][1], new_specifics->data()[1].url());
+  EXPECT_EQ(kCompareUrls[0].first, new_specifics->data()[0].url());
+  EXPECT_EQ(kCompareUrls[0].second, new_specifics->data()[1].url());
   VerifySpecificsInitialNonExistence(new_specifics.value());
   VerifySpecificsExists(new_specifics.value());
   VerifyStoreAndEntriesSizeIncreasedBy(1);
diff --git a/components/commerce_strings.grdp b/components/commerce_strings.grdp
index a0530aa..feb6b438 100644
--- a/components/commerce_strings.grdp
+++ b/components/commerce_strings.grdp
@@ -510,5 +510,17 @@
   <message name="IDS_PRODUCT_SPECIFICATIONS_EMPTY_PRODUCT_SELECTOR" translateable="false" desc="The text displayed in the product selector when there is no currently selected product.">
     Add a tab
   </message>
+  <message name="IDS_PRODUCT_SPECIFICATIONS_PAGE_ACTION_ADD_DEFAULT" translateable="false" desc="The default text for the Product Specifications page action chip before adding the current page.">
+    Add to analysis?
+  </message>
+  <message name="IDS_PRODUCT_SPECIFICATIONS_PAGE_ACTION_ADDED_DEFAULT" translateable="false" desc="The default text for the Product Specifications page action chip after adding the current page.">
+    Added to analysis
+  </message>
+  <message name="IDS_PRODUCT_SPECIFICATIONS_PAGE_ACTION_ADD" translateable="false" desc="The text for the Product Specifications page action chip with a placeholder for what the specifications are for, before adding the current page.">
+    Add to <ph name="SPECIFICATION">$1<ex>analysis</ex></ph>?
+  </message>
+  <message name="IDS_PRODUCT_SPECIFICATIONS_PAGE_ACTION_ADDED" translateable="false" desc="The text for the Product Specifications page action chip with a placeholder for what the specifications are for, after adding the current page.">
+    Added to <ph name="SPECIFICATION">$1<ex>analysis</ex></ph>
+  </message>
 
 </grit-part>
diff --git a/components/compose/core/browser/compose_manager_impl.cc b/components/compose/core/browser/compose_manager_impl.cc
index e12a595..776a269a 100644
--- a/components/compose/core/browser/compose_manager_impl.cc
+++ b/components/compose/core/browser/compose_manager_impl.cc
@@ -196,20 +196,11 @@
   if (!has_session &&
       base::FeatureList::IsEnabled(features::kEnableComposeProactiveNudge)) {
     // Add compose child suggestions
-    Suggestion never_show_on_site = Suggestion(
-        l10n_util::GetStringUTF16(
-            IDS_COMPOSE_NEVER_SHOW_ON_THIS_SITE_AGAIN_CHILD_SUGGESTION_TEXT),
-        SuggestionType::kComposeNeverShowOnThisSiteAgain);
     Suggestion disable =
         Suggestion(l10n_util::GetStringUTF16(
                        IDS_COMPOSE_DISABLE_HELP_ME_WRITE_CHILD_SUGGESTION_TEXT),
                    SuggestionType::kComposeDisable);
-    Suggestion go_to_settings =
-        Suggestion(l10n_util::GetStringUTF16(
-                       IDS_COMPOSE_GO_TO_SETTINGS_CHILD_SUGGESTION_TEXT),
-                   SuggestionType::kComposeGoToSettings);
-    suggestion.children = {std::move(never_show_on_site), std::move(disable),
-                           std::move(go_to_settings)};
+    suggestion.children = {std::move(disable)};
   }
 
   return suggestion;
@@ -217,14 +208,20 @@
 
 void ComposeManagerImpl::NeverShowComposeForOrigin(const url::Origin& origin) {
   // TODO(b/333929225): Implement.
+  compose::LogComposeProactiveNudgeCtr(
+      compose::ComposeProactiveNudgeCtrEvent::kUserDisabledSite);
 }
 
 void ComposeManagerImpl::DisableCompose() {
   client_->DisableProactiveNudge();
+  compose::LogComposeProactiveNudgeCtr(
+      compose::ComposeProactiveNudgeCtrEvent::kUserDisabledProactiveNudge);
 }
 
 void ComposeManagerImpl::GoToSettings() {
   // TODO(b/333929225): Implement.
+  compose::LogComposeProactiveNudgeCtr(
+      compose::ComposeProactiveNudgeCtrEvent::kOpenSettings);
 }
 
 }  // namespace compose
diff --git a/components/compose/core/browser/compose_manager_impl_unittest.cc b/components/compose/core/browser/compose_manager_impl_unittest.cc
index a67bef1..efab297c4 100644
--- a/components/compose/core/browser/compose_manager_impl_unittest.cc
+++ b/components/compose/core/browser/compose_manager_impl_unittest.cc
@@ -194,11 +194,7 @@
   // Checks that the 3 expected child suggestions exist.
   EXPECT_THAT(
       suggestion->children,
-      ElementsAre(
-          EqualsSuggestion(
-              autofill::SuggestionType::kComposeNeverShowOnThisSiteAgain),
-          EqualsSuggestion(autofill::SuggestionType::kComposeDisable),
-          EqualsSuggestion(autofill::SuggestionType::kComposeGoToSettings)));
+      ElementsAre(EqualsSuggestion(autofill::SuggestionType::kComposeDisable)));
 }
 
 TEST_F(
diff --git a/components/compose/core/browser/compose_metrics.cc b/components/compose/core/browser/compose_metrics.cc
index f2db9ba..9c79f7e 100644
--- a/components/compose/core/browser/compose_metrics.cc
+++ b/components/compose/core/browser/compose_metrics.cc
@@ -47,6 +47,9 @@
 const char kInnerTextNodeOffsetFound[] =
     "Compose.Dialog.InnerTextNodeOffsetFound";
 const char kComposeContextMenuCtr[] = "Compose.ContextMenu.CTR";
+const char kComposeProactiveNudgeCtr[] = "Compose.ProactiveNudge.CTR";
+const char kComposeProactiveNudgeShowStatus[] =
+    "Compose.ProactiveNudge.ShowStatus";
 const char kOpenComposeDialogResult[] =
     "Compose.ContextMenu.OpenComposeDialogResult";
 const char kComposeSelectAll[] = "Compose.ContextMenu.SelectedAll";
@@ -251,6 +254,14 @@
   base::UmaHistogramEnumeration(kComposeShowStatus, status);
 }
 
+void LogComposeProactiveNudgeCtr(ComposeProactiveNudgeCtrEvent event) {
+  base::UmaHistogramEnumeration(kComposeProactiveNudgeCtr, event);
+}
+
+void LogComposeProactiveNudgeShowStatus(ComposeShowStatus status) {
+  base::UmaHistogramEnumeration(kComposeProactiveNudgeShowStatus, status);
+}
+
 void LogOpenComposeDialogResult(OpenComposeDialogResult result) {
   base::UmaHistogramEnumeration(kOpenComposeDialogResult, result);
 }
diff --git a/components/compose/core/browser/compose_metrics.h b/components/compose/core/browser/compose_metrics.h
index c55bb946..bb7a1ce 100644
--- a/components/compose/core/browser/compose_metrics.h
+++ b/components/compose/core/browser/compose_metrics.h
@@ -38,6 +38,8 @@
 extern const char kComposeMSBBSessionDialogShownCount[];
 extern const char kInnerTextNodeOffsetFound[];
 extern const char kComposeContextMenuCtr[];
+extern const char kComposeProactiveNudgeCtr[];
+extern const char kComposeProactiveNudgeShowStatus[];
 extern const char kOpenComposeDialogResult[];
 
 // Enum for calculating the CTR of the Compose context menu item.
@@ -127,9 +129,9 @@
   kMaxValue = kEditedResultInserted,
 };
 
-// Enum for recording the show status of the Compose context menu item.
-// These values are persisted to logs. Entries should not be renumbered and
-// numeric values should never be reused. Keep in sync with
+// Enum for recording the show status of both the HMW context menu item and
+// the proactive nudge. These values are persisted to logs. Entries should not
+// be renumbered and numeric values should never be reused. Keep in sync with
 // ComposeShowStatus in
 // src/tools/metrics/histograms/metadata/compose/enums.xml.
 enum class ComposeShowStatus {
@@ -146,9 +148,29 @@
   kNotComposeEligible = 9,
   kIncorrectScheme = 10,
   kFormFieldNestedInFencedFrame = 11,
-  kFeatureFlagDisabled = 12,
+  kComposeFeatureFlagDisabled = 12,
   kDisabledOnChromeOS = 13,
-  kMaxValue = kDisabledOnChromeOS,
+  kAutocompleteOff = 14,
+  kWritingSuggestionsFalse = 15,
+  kProactiveNudgeFeatureDisabled = 16,
+  kPractiveNudgeDisabledGloballyByUserPreference = 17,
+  kPractiveNudgeDisabledForSiteByUserPreference = 18,
+  kPractiveNudgeDisabledByServerConfig = 19,
+  kPractiveNudgeUnknownServerConfig = 20,
+  kRandomlyBlocked = 21,
+  kMaxValue = kRandomlyBlocked,
+};
+
+// Enum for calculating the CTR of the Compose proactive nudge.
+// Keep in sync with ComposeProactiveNudgeCtrEvent in
+// src/tools/metrics/histograms/metadata/compose/enums.xml.
+enum class ComposeProactiveNudgeCtrEvent {
+  kNudgeDisplayed = 0,
+  kDialogOpened = 1,
+  kUserDisabledProactiveNudge = 2,
+  kUserDisabledSite = 3,
+  kOpenSettings = 4,
+  kMaxValue = kOpenSettings,
 };
 
 enum class EvalLocation : int {
@@ -332,6 +354,10 @@
 
 void LogComposeContextMenuShowStatus(ComposeShowStatus status);
 
+void LogComposeProactiveNudgeCtr(ComposeProactiveNudgeCtrEvent event);
+
+void LogComposeProactiveNudgeShowStatus(ComposeShowStatus status);
+
 void LogOpenComposeDialogResult(OpenComposeDialogResult result);
 
 void LogComposeRequestReason(ComposeRequestReason reason);
diff --git a/components/download/database/download_db_conversions.cc b/components/download/database/download_db_conversions.cc
index ade1132..752c9b9a 100644
--- a/components/download/database/download_db_conversions.cc
+++ b/components/download/database/download_db_conversions.cc
@@ -87,6 +87,8 @@
       return DownloadSource::RETRY;
     case download_pb::DownloadSource::RETRY_FROM_BUBBLE:
       return DownloadSource::RETRY_FROM_BUBBLE;
+    case download_pb::DownloadSource::TOOLBAR_MENU:
+      return DownloadSource::TOOLBAR_MENU;
   }
   NOTREACHED();
   return DownloadSource::UNKNOWN;
@@ -120,6 +122,8 @@
       return download_pb::DownloadSource::RETRY;
     case DownloadSource::RETRY_FROM_BUBBLE:
       return download_pb::DownloadSource::RETRY_FROM_BUBBLE;
+    case DownloadSource::TOOLBAR_MENU:
+      return download_pb::DownloadSource::TOOLBAR_MENU;
   }
   NOTREACHED();
   return download_pb::DownloadSource::UNKNOWN;
diff --git a/components/download/database/proto/download_source.proto b/components/download/database/proto/download_source.proto
index 5d654d1..43a8f1c4 100644
--- a/components/download/database/proto/download_source.proto
+++ b/components/download/database/proto/download_source.proto
@@ -23,4 +23,5 @@
   CONTEXT_MENU = 9;
   RETRY = 10;
   RETRY_FROM_BUBBLE = 11;
+  TOOLBAR_MENU = 12;
 }
diff --git a/components/download/internal/common/download_stats.cc b/components/download/internal/common/download_stats.cc
index 0086f65..53546b3 100644
--- a/components/download/internal/common/download_stats.cc
+++ b/components/download/internal/common/download_stats.cc
@@ -119,6 +119,9 @@
     case DownloadSource::RETRY_FROM_BUBBLE:
       suffix = "RetryFromBubble";
       break;
+    case DownloadSource::TOOLBAR_MENU:
+      suffix = "ToolbarMenu";
+      break;
   }
 
   return name + "." + suffix;
diff --git a/components/download/public/common/download_source.h b/components/download/public/common/download_source.h
index f7209e9..544b049e 100644
--- a/components/download/public/common/download_source.h
+++ b/components/download/public/common/download_source.h
@@ -48,6 +48,9 @@
 
   // Retry download triggered through the downloads bubble.
   RETRY_FROM_BUBBLE = 11,
+
+  // Toolbar menu, only available on Android.
+  TOOLBAR_MENU = 12,
 };
 
 }  // namespace download
diff --git a/components/drive/drive_notification_manager.cc b/components/drive/drive_notification_manager.cc
index f67e0bdb..da1f793 100644
--- a/components/drive/drive_notification_manager.cc
+++ b/components/drive/drive_notification_manager.cc
@@ -105,11 +105,6 @@
     it->second = invalidation.version();
   }
 
-  // This effectively disables 'local acks'.  It tells the invalidations system
-  // to not bother saving invalidations across restarts for us.
-  // See crbug.com/320878.
-  invalidation.Acknowledge();
-
   if (!batch_timer_.IsRunning() && !invalidated_change_ids_.empty()) {
     // Stop the polling timer as we'll be sending a batch soon.
     polling_timer_.Stop();
diff --git a/components/drive/drive_notification_manager_unittest.cc b/components/drive/drive_notification_manager_unittest.cc
index d63f3d4f..ed5e8ac 100644
--- a/components/drive/drive_notification_manager_unittest.cc
+++ b/components/drive/drive_notification_manager_unittest.cc
@@ -81,6 +81,12 @@
         drive_notification_observer_.get());
   }
 
+  bool IsInvalidatorRegistered(const invalidation::Topic& topic) {
+    return fake_invalidation_service_->invalidator_registrar()
+        .GetRegisteredTopics(drive_notification_manager_.get())
+        .contains(topic);
+  }
+
   scoped_refptr<base::TestMockTimeTaskRunner> task_runner_;
   std::unique_ptr<invalidation::FakeInvalidationService>
       fake_invalidation_service_;
@@ -159,6 +165,7 @@
 
 TEST_F(DriveNotificationManagerTest, TestBatchInvalidation) {
   // By default we'll be registered for the default change notification.
+  EXPECT_TRUE(IsInvalidatorRegistered(kDefaultCorpusTopic));
 
   // Emitting an invalidation should not call our observer until the timer
   // expires.
@@ -180,6 +187,7 @@
   const auto team_drive_1_topic =
       CreateTeamDriveInvalidationTopic(team_drive_id_1);
   drive_notification_manager_->UpdateTeamDriveIds({team_drive_id_1}, {});
+  EXPECT_TRUE(IsInvalidatorRegistered(team_drive_1_topic));
 
   // Emit invalidation for default corpus, should not emit a team drive
   // invalidation.
diff --git a/components/facilitated_payments/core/browser/BUILD.gn b/components/facilitated_payments/core/browser/BUILD.gn
index 45fe6387..d50ae3a 100644
--- a/components/facilitated_payments/core/browser/BUILD.gn
+++ b/components/facilitated_payments/core/browser/BUILD.gn
@@ -25,6 +25,7 @@
     "//base",
     "//components/autofill/core/browser",
     "//components/facilitated_payments/core/features",
+    "//components/facilitated_payments/core/metrics",
     "//components/facilitated_payments/core/mojom:facilitated_payments_agent_mojom",
     "//components/optimization_guide/core",
     "//services/data_decoder/public/cpp",
diff --git a/components/facilitated_payments/core/browser/facilitated_payments_manager.cc b/components/facilitated_payments/core/browser/facilitated_payments_manager.cc
index 0c0940a..5e643c2 100644
--- a/components/facilitated_payments/core/browser/facilitated_payments_manager.cc
+++ b/components/facilitated_payments/core/browser/facilitated_payments_manager.cc
@@ -9,11 +9,13 @@
 
 #include "base/check.h"
 #include "base/functional/callback_helpers.h"
+#include "components/autofill/core/browser/data_model/bank_account.h"
 #include "components/autofill/core/browser/payments/payments_util.h"
 #include "components/autofill/core/browser/personal_data_manager.h"
 #include "components/facilitated_payments/core/browser/facilitated_payments_api_client.h"
 #include "components/facilitated_payments/core/browser/facilitated_payments_client.h"
 #include "components/facilitated_payments/core/features/features.h"
+#include "components/facilitated_payments/core/metrics/facilitated_payments_metrics.h"
 #include "services/metrics/public/cpp/ukm_builders.h"
 
 namespace payments::facilitated {
@@ -165,6 +167,7 @@
   }
 
   initiate_payment_request_details_->pix_code_ = std::move(pix_code);
+  api_availability_check_latency_ = base::TimeTicks::Now();
   api_client_->IsAvailable(
       base::BindOnce(&FacilitatedPaymentsManager::OnApiAvailabilityReceived,
                      weak_ptr_factory_.GetWeakPtr()));
@@ -182,6 +185,8 @@
 
 void FacilitatedPaymentsManager::OnApiAvailabilityReceived(
     bool is_api_available) {
+  LogIsApiAvailableResult(is_api_available, (base::TimeTicks::Now() -
+                                             api_availability_check_latency_));
   if (!is_api_available) {
     Reset();
     return;
@@ -234,7 +239,7 @@
     return;
   }
   initiate_payment_request_details_->instrument_id_ = selected_instrument_id;
-
+  get_client_token_loading_latency_ = base::TimeTicks::Now();
   api_client_->GetClientToken(
       base::BindOnce(&FacilitatedPaymentsManager::OnGetClientToken,
                      weak_ptr_factory_.GetWeakPtr()));
@@ -242,6 +247,9 @@
 
 void FacilitatedPaymentsManager::OnGetClientToken(
     std::vector<uint8_t> client_token) {
+  LogGetClientTokenResult(
+      !client_token.empty(),
+      (base::TimeTicks::Now() - get_client_token_loading_latency_));
   if (client_token.empty()) {
     Reset();
     return;
diff --git a/components/facilitated_payments/core/browser/facilitated_payments_manager.h b/components/facilitated_payments/core/browser/facilitated_payments_manager.h
index 84d34a8..e8e2bf7 100644
--- a/components/facilitated_payments/core/browser/facilitated_payments_manager.h
+++ b/components/facilitated_payments/core/browser/facilitated_payments_manager.h
@@ -120,6 +120,10 @@
       DoesNotRetrieveClientTokenIfPixPaymentPromptRejected);
   FRIEND_TEST_ALL_PREFIXES(FacilitatedPaymentsManagerTest,
                            RetrievesClientTokenIfPixPaymentPromptAccepted);
+  FRIEND_TEST_ALL_PREFIXES(FacilitatedPaymentsManagerTest,
+                           GetClientTokenHistogram_ClientTokenNotEmpty);
+  FRIEND_TEST_ALL_PREFIXES(FacilitatedPaymentsManagerTest,
+                           GetClientTokenHistogram_ClientTokenEmpty);
   FRIEND_TEST_ALL_PREFIXES(
       FacilitatedPaymentsManagerTest,
       TriggerPixDetectionOnDomContentLoadedExpDisabled_Ukm);
@@ -147,6 +151,8 @@
                            PixCodeValidated_ApiClientTriggered);
   FRIEND_TEST_ALL_PREFIXES(FacilitatedPaymentsManagerWithPixPaymentsEnabledTest,
                            PixCodeValidationFailed_NoApiClientTriggered);
+  FRIEND_TEST_ALL_PREFIXES(FacilitatedPaymentsManagerWithPixPaymentsEnabledTest,
+                           ApiAvailabilityHistogram);
   FRIEND_TEST_ALL_PREFIXES(
       FacilitatedPaymentsManagerWithPixPaymentsEnabledTest,
       PixCodeValidatorTerminatedUnexpectedly_NoApiClientTriggered);
@@ -232,6 +238,14 @@
   // Measures the time taken to scan the document for the PIX code.
   base::TimeTicks pix_code_detection_latency_measuring_timestamp_;
 
+  // Measures the time taken to check the availability of the facilitated
+  // payments API client.
+  base::TimeTicks api_availability_check_latency_;
+
+  // Measures the time take to load the client token from the facilitated
+  // payments API client.
+  base::TimeTicks get_client_token_loading_latency_;
+
   // Contains the details required for the `InitiatePayment` request to be sent
   // to the Payments server.
   std::unique_ptr<FacilitatedPaymentsInitiatePaymentRequestDetails>
diff --git a/components/facilitated_payments/core/browser/facilitated_payments_manager_unittest.cc b/components/facilitated_payments/core/browser/facilitated_payments_manager_unittest.cc
index 7b41b3fa..1d641e3b 100644
--- a/components/facilitated_payments/core/browser/facilitated_payments_manager_unittest.cc
+++ b/components/facilitated_payments/core/browser/facilitated_payments_manager_unittest.cc
@@ -8,6 +8,7 @@
 
 #include "base/functional/callback.h"
 #include "base/test/gmock_callback_support.h"
+#include "base/test/metrics/histogram_tester.h"
 #include "base/test/scoped_feature_list.h"
 #include "base/test/task_environment.h"
 #include "base/types/expected.h"
@@ -871,6 +872,52 @@
                                      /*selected_instrument_id=*/-1);
 }
 
+// The GetClientToken async call is made after the user has accepted the payment
+// prompt. This test verifies that the result and latency of the GetClientToken
+// call is logged correctly.
+TEST_F(FacilitatedPaymentsManagerTest,
+       GetClientTokenHistogram_ClientTokenNotEmpty) {
+  base::HistogramTester histogram_tester;
+  EXPECT_CALL(*api_client_, GetClientToken(testing::_));
+  manager_->OnPixPaymentPromptResult(/*is_prompt_accepted=*/true,
+                                     /*selected_instrument_id=*/-1);
+  FastForwardBy(base::Seconds(2));
+
+  manager_->OnGetClientToken(std::vector<uint8_t>{'t', 'o', 'k', 'e', 'n'});
+
+  histogram_tester.ExpectUniqueSample(
+      "FacilitatedPayments.Pix.GetClientToken.Result",
+      /*sample=*/true,
+      /*expected_bucket_count=*/1);
+  histogram_tester.ExpectUniqueSample(
+      "FacilitatedPayments.Pix.GetClientToken.Latency",
+      /*sample=*/2000,
+      /*expected_bucket_count=*/1);
+}
+
+// The GetClientToken async call is made after the user has accepted the payment
+// prompt. This test verifies that the result and latency of the GetClientToken
+// call is logged correctly.
+TEST_F(FacilitatedPaymentsManagerTest,
+       GetClientTokenHistogram_ClientTokenEmpty) {
+  base::HistogramTester histogram_tester;
+  EXPECT_CALL(*api_client_, GetClientToken(testing::_));
+  manager_->OnPixPaymentPromptResult(/*is_prompt_accepted=*/true,
+                                     /*selected_instrument_id=*/-1);
+  FastForwardBy(base::Seconds(2));
+
+  manager_->OnGetClientToken(std::vector<uint8_t>{});
+
+  histogram_tester.ExpectUniqueSample(
+      "FacilitatedPayments.Pix.GetClientToken.Result",
+      /*sample=*/false,
+      /*expected_bucket_count=*/1);
+  histogram_tester.ExpectUniqueSample(
+      "FacilitatedPayments.Pix.GetClientToken.Latency",
+      /*sample=*/2000,
+      /*expected_bucket_count=*/1);
+}
+
 TEST_F(FacilitatedPaymentsManagerTest,
        TriggerPixDetectionOnDomContentLoadedExpDisabled_Ukm) {
   features_.InitAndDisableFeature(kEnablePixDetectionOnDomContentLoaded);
@@ -1083,4 +1130,29 @@
   task_environment_.RunUntilIdle();
 }
 
+// The `IsAvailable` async call is made after a valid Pix code has been
+// detected. This test verifies that the result and latency are logged after the
+// async call is completed.
+TEST_F(FacilitatedPaymentsManagerWithPixPaymentsEnabledTest,
+       ApiAvailabilityHistogram) {
+  base::HistogramTester histogram_tester;
+  personal_data_manager_->payments_data_manager().AddMaskedBankAccountForTest(
+      CreatePixBankAccount(1));
+  EXPECT_CALL(*api_client_, IsAvailable(testing::_));
+  manager_->OnPixCodeValidated(/*pix_code=*/std::string(),
+                               /*is_pix_code_valid=*/true);
+  FastForwardBy(base::Seconds(2));
+
+  manager_->OnApiAvailabilityReceived(true);
+
+  histogram_tester.ExpectUniqueSample(
+      "FacilitatedPayments.Pix.IsApiAvailable.Result",
+      /*sample=*/true,
+      /*expected_bucket_count=*/1);
+  histogram_tester.ExpectUniqueSample(
+      "FacilitatedPayments.Pix.IsApiAvailable.Latency",
+      /*sample=*/2000,
+      /*expected_bucket_count=*/1);
+}
+
 }  // namespace payments::facilitated
diff --git a/components/fingerprinting_protection_filter/DEPS b/components/fingerprinting_protection_filter/DEPS
index dd69f61..904fee12 100644
--- a/components/fingerprinting_protection_filter/DEPS
+++ b/components/fingerprinting_protection_filter/DEPS
@@ -1,5 +1,6 @@
 include_rules = [
   "+components/subresource_filter/core",
-  "+content/public/browser",
   "+components/subresource_filter/content/shared/browser",
+  "+content/public/browser",
+  "+content/public/test",
 ]
diff --git a/components/fingerprinting_protection_filter/browser/BUILD.gn b/components/fingerprinting_protection_filter/browser/BUILD.gn
index dc3abfb62..dc652d6 100644
--- a/components/fingerprinting_protection_filter/browser/BUILD.gn
+++ b/components/fingerprinting_protection_filter/browser/BUILD.gn
@@ -15,10 +15,10 @@
     ":features",
     "//base",
     "//components/subresource_filter/content/shared/browser",
-    "//components/subresource_filter/core/browser",
     "//content/public/browser",
   ]
   deps = [
+    "//components/subresource_filter/core/browser",
     "//components/subresource_filter/core/common",
     "//components/subresource_filter/core/mojom",
   ]
@@ -32,4 +32,19 @@
     "fingerprinting_protection_filter_features.h",
   ]
   public_deps = [ "//base" ]
+  deps = [ "//components/subresource_filter/core/mojom" ]
+}
+
+source_set("unit_tests") {
+  testonly = true
+  sources = [ "fingerprinting_protection_page_activation_throttle_unittest.cc" ]
+  deps = [
+    ":browser",
+    "//base/test:test_support",
+    "//components/subresource_filter/core/common",
+    "//components/subresource_filter/core/mojom",
+    "//content/public/browser",
+    "//content/test:test_support",
+    "//testing/gtest",
+  ]
 }
diff --git a/components/fingerprinting_protection_filter/browser/fingerprinting_protection_filter_features.cc b/components/fingerprinting_protection_filter/browser/fingerprinting_protection_filter_features.cc
index 2e5259f..4598ecf78 100644
--- a/components/fingerprinting_protection_filter/browser/fingerprinting_protection_filter_features.cc
+++ b/components/fingerprinting_protection_filter/browser/fingerprinting_protection_filter_features.cc
@@ -4,6 +4,10 @@
 
 #include "components/fingerprinting_protection_filter/browser/fingerprinting_protection_filter_features.h"
 
+#include "base/feature_list.h"
+#include "base/metrics/field_trial_params.h"
+#include "components/subresource_filter/core/mojom/subresource_filter.mojom.h"
+
 namespace fingerprinting_protection_filter::features {
 
 // When enabled, loads the Fingerprinting Protection component and evaluates
@@ -13,4 +17,15 @@
              "EnableFingerprintingProtectionFilter",
              base::FEATURE_DISABLED_BY_DEFAULT);
 
+constexpr base::FeatureParam<subresource_filter::mojom::ActivationLevel>::Option
+    kActivationLevelOptions[] = {
+        {subresource_filter::mojom::ActivationLevel::kDisabled, "disabled"},
+        {subresource_filter::mojom::ActivationLevel::kDryRun, "dry_run"},
+        {subresource_filter::mojom::ActivationLevel::kEnabled, "enabled"}};
+
+const base::FeatureParam<subresource_filter::mojom::ActivationLevel>
+    kActivationLevel{&kEnableFingerprintingProtectionFilter, "activation_level",
+                     subresource_filter::mojom::ActivationLevel::kEnabled,
+                     &kActivationLevelOptions};
+
 }  // namespace fingerprinting_protection_filter::features
diff --git a/components/fingerprinting_protection_filter/browser/fingerprinting_protection_filter_features.h b/components/fingerprinting_protection_filter/browser/fingerprinting_protection_filter_features.h
index c9037ccd..53326ff 100644
--- a/components/fingerprinting_protection_filter/browser/fingerprinting_protection_filter_features.h
+++ b/components/fingerprinting_protection_filter/browser/fingerprinting_protection_filter_features.h
@@ -7,6 +7,11 @@
 
 #include "base/component_export.h"
 #include "base/feature_list.h"
+#include "base/metrics/field_trial_params.h"
+
+namespace subresource_filter::mojom {
+enum class ActivationLevel;
+}  // namespace subresource_filter::mojom
 
 namespace fingerprinting_protection_filter::features {
 
@@ -14,6 +19,10 @@
 COMPONENT_EXPORT(FINGERPRINTING_PROTECTION_FILTER_FEATURES)
 BASE_DECLARE_FEATURE(kEnableFingerprintingProtectionFilter);
 
+COMPONENT_EXPORT(FINGERPRINTING_PROTECTION_FILTER_FEATURES)
+extern const base::FeatureParam<subresource_filter::mojom::ActivationLevel>
+    kActivationLevel;
+
 }  // namespace fingerprinting_protection_filter::features
 
 #endif  // COMPONENTS_FINGERPRINTING_PROTECTION_FILTER_BROWSER_FINGERPRINTING_PROTECTION_FILTER_FEATURES_H_
diff --git a/components/fingerprinting_protection_filter/browser/fingerprinting_protection_page_activation_throttle.cc b/components/fingerprinting_protection_filter/browser/fingerprinting_protection_page_activation_throttle.cc
index 2dcddc1..746f426 100644
--- a/components/fingerprinting_protection_filter/browser/fingerprinting_protection_page_activation_throttle.cc
+++ b/components/fingerprinting_protection_filter/browser/fingerprinting_protection_page_activation_throttle.cc
@@ -7,12 +7,13 @@
 #include "base/feature_list.h"
 #include "components/fingerprinting_protection_filter/browser/fingerprinting_protection_filter_features.h"
 #include "components/fingerprinting_protection_filter/browser/fingerprinting_protection_web_contents_helper.h"
+#include "components/subresource_filter/content/shared/browser/page_activation_throttle_delegate.h"
+#include "components/subresource_filter/core/common/activation_decision.h"
 #include "components/subresource_filter/core/mojom/subresource_filter.mojom.h"
-#include "content/public/browser/navigation_handle.h"
+#include "content/public/browser/navigation_throttle.h"
 
 using subresource_filter::ActivationDecision;
 using subresource_filter::mojom::ActivationLevel;
-using subresource_filter::mojom::ActivationState;
 
 namespace fingerprinting_protection_filter {
 
@@ -32,10 +33,7 @@
 
 content::NavigationThrottle::ThrottleCheckResult
 FingerprintingProtectionPageActivationThrottle::WillProcessResponse() {
-  if (GetActivationDecision() == ActivationDecision::ACTIVATED) {
-    // TODO(crbug/327005578): Defer to consult UX.
-    NotifyResult();
-  }
+  NotifyResult(GetActivationDecision());
   return PROCEED;
 }
 
@@ -46,20 +44,24 @@
 
 ActivationDecision
 FingerprintingProtectionPageActivationThrottle::GetActivationDecision() const {
-  if (base::FeatureList::IsEnabled(
+  if (!base::FeatureList::IsEnabled(
           features::kEnableFingerprintingProtectionFilter)) {
-    return ActivationDecision::ACTIVATED;
+    return ActivationDecision::UNKNOWN;
   }
-  return ActivationDecision::UNKNOWN;
+  if (fingerprinting_protection_filter::features::kActivationLevel.Get() ==
+      subresource_filter::mojom::ActivationLevel::kDisabled) {
+    return ActivationDecision::ACTIVATION_DISABLED;
+  }
+  // Either enabled or dry_run
+  return ActivationDecision::ACTIVATED;
 }
 
-void FingerprintingProtectionPageActivationThrottle::NotifyResult() {
+void FingerprintingProtectionPageActivationThrottle::NotifyResult(
+    subresource_filter::ActivationDecision decision) {
   // TODO(crbug/327005578): Notify UX of the activation decision made.
-  ActivationState state;
-  state.activation_level = ActivationLevel::kEnabled;
   FingerprintingProtectionWebContentsHelper::FromWebContents(
       navigation_handle()->GetWebContents())
-      ->NotifyPageActivationComputed(navigation_handle(), state);
+      ->NotifyPageActivationComputed(navigation_handle(), decision);
 }
 
 void FingerprintingProtectionPageActivationThrottle::LogMetricsOnChecksComplete(
diff --git a/components/fingerprinting_protection_filter/browser/fingerprinting_protection_page_activation_throttle.h b/components/fingerprinting_protection_filter/browser/fingerprinting_protection_page_activation_throttle.h
index fc68924..f886bd1d 100644
--- a/components/fingerprinting_protection_filter/browser/fingerprinting_protection_page_activation_throttle.h
+++ b/components/fingerprinting_protection_filter/browser/fingerprinting_protection_page_activation_throttle.h
@@ -9,14 +9,17 @@
 #include "base/memory/weak_ptr.h"
 #include "base/time/time.h"
 #include "components/subresource_filter/content/shared/browser/page_activation_throttle_delegate.h"
-#include "components/subresource_filter/core/common/activation_decision.h"
 #include "content/public/browser/navigation_throttle.h"
 
+namespace subresource_filter {
+enum class ActivationDecision;
+}
+
 namespace fingerprinting_protection_filter {
 
 // Navigation throttle responsible for activating subresource filtering on page
 // loads that match the Fingerprinting Protection Filtering criteria.
-class FingerprintingProtectionPageActivationThrottle final
+class FingerprintingProtectionPageActivationThrottle
     : public content::NavigationThrottle {
  public:
   // |delegate| is allowed to be null, in which case the client creating this
@@ -42,7 +45,7 @@
 
  private:
   void CheckCurrentUrl();
-  void NotifyResult();
+  virtual void NotifyResult(subresource_filter::ActivationDecision decision);
 
   void LogMetricsOnChecksComplete(
       subresource_filter::ActivationDecision decision,
diff --git a/components/fingerprinting_protection_filter/browser/fingerprinting_protection_page_activation_throttle_unittest.cc b/components/fingerprinting_protection_filter/browser/fingerprinting_protection_page_activation_throttle_unittest.cc
new file mode 100644
index 0000000..e2f8468
--- /dev/null
+++ b/components/fingerprinting_protection_filter/browser/fingerprinting_protection_page_activation_throttle_unittest.cc
@@ -0,0 +1,158 @@
+// Copyright 2024 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/fingerprinting_protection_filter/browser/fingerprinting_protection_page_activation_throttle.h"
+
+#include <gmock/gmock.h>
+
+#include <memory>
+
+#include "base/test/scoped_feature_list.h"
+#include "components/fingerprinting_protection_filter/browser/fingerprinting_protection_filter_features.h"
+#include "components/fingerprinting_protection_filter/browser/fingerprinting_protection_web_contents_helper.h"
+#include "components/subresource_filter/core/common/activation_decision.h"
+#include "content/public/browser/navigation_handle.h"
+#include "content/public/browser/render_frame_host.h"
+#include "content/public/test/mock_navigation_handle.h"
+#include "content/public/test/test_renderer_host.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace fingerprinting_protection_filter {
+
+namespace {
+
+class MockFingerprintingProtectionPageActivationThrottle
+    : public FingerprintingProtectionPageActivationThrottle {
+ public:
+  MOCK_METHOD(void,
+              NotifyResult,
+              (subresource_filter::ActivationDecision),
+              (override));
+  using FingerprintingProtectionPageActivationThrottle::
+      FingerprintingProtectionPageActivationThrottle;
+  using FingerprintingProtectionPageActivationThrottle::WillProcessResponse;
+};
+
+}  // namespace
+
+class FingerprintingProtectionPageActivationThrottleTest
+    : public content::RenderViewHostTestHarness {
+ public:
+  FingerprintingProtectionPageActivationThrottleTest() = default;
+
+  FingerprintingProtectionPageActivationThrottleTest(
+      const FingerprintingProtectionPageActivationThrottleTest&) = delete;
+  FingerprintingProtectionPageActivationThrottleTest& operator=(
+      const FingerprintingProtectionPageActivationThrottleTest&) = delete;
+
+  ~FingerprintingProtectionPageActivationThrottleTest() override = default;
+
+  void SetUp() override {
+    content::RenderViewHostTestHarness::SetUp();
+    auto* contents = RenderViewHostTestHarness::web_contents();
+    mock_nav_handle_ =
+        std::make_unique<content::MockNavigationHandle>(contents);
+  }
+
+  void TearDown() override {
+    scoped_feature_list_.Reset();
+    content::RenderViewHostTestHarness::TearDown();
+  }
+
+  content::MockNavigationHandle* navigation_handle() {
+    return mock_nav_handle_.get();
+  }
+
+ protected:
+  base::test::ScopedFeatureList scoped_feature_list_;
+
+ private:
+  std::unique_ptr<content::MockNavigationHandle> mock_nav_handle_;
+};
+
+TEST_F(FingerprintingProtectionPageActivationThrottleTest,
+       FlagDisabled_IsUnknown) {
+  // Disable the feature.
+  scoped_feature_list_.InitAndDisableFeature(
+      features::kEnableFingerprintingProtectionFilter);
+
+  // Initialize the WebContentsHelper and Throttle to be tested.
+  FingerprintingProtectionWebContentsHelper::CreateForWebContents(
+      navigation_handle()->GetWebContents());
+  auto throttle_under_test = MockFingerprintingProtectionPageActivationThrottle(
+      navigation_handle(), nullptr);
+
+  // Expect that NotifyResult is called with UNKNOWN ActivationDecision.
+  EXPECT_CALL(throttle_under_test,
+              NotifyResult(subresource_filter::ActivationDecision::UNKNOWN))
+      .WillOnce(testing::Return());
+  EXPECT_EQ(throttle_under_test.WillProcessResponse().action(),
+            content::NavigationThrottle::ThrottleAction::PROCEED);
+}
+
+TEST_F(FingerprintingProtectionPageActivationThrottleTest,
+       FlagEnabledDefaultActivatedParams_IsActivated) {
+  // Enable the feature with default params, i.e. activation_level = enabled.
+  scoped_feature_list_.InitAndEnableFeature(
+      features::kEnableFingerprintingProtectionFilter);
+
+  // Initialize the WebContentsHelper and Throttle to be tested.
+  FingerprintingProtectionWebContentsHelper::CreateForWebContents(
+      navigation_handle()->GetWebContents());
+  auto throttle_under_test = MockFingerprintingProtectionPageActivationThrottle(
+      navigation_handle(), nullptr);
+
+  // Expect NotifyResult is called with ACTIVATED ActivationDecision.
+  EXPECT_CALL(throttle_under_test,
+              NotifyResult(subresource_filter::ActivationDecision::ACTIVATED))
+      .WillOnce(testing::Return());
+  EXPECT_EQ(throttle_under_test.WillProcessResponse().action(),
+            content::NavigationThrottle::ThrottleAction::PROCEED);
+}
+
+TEST_F(FingerprintingProtectionPageActivationThrottleTest,
+       FlagEnabledWithDryRun_IsActivated) {
+  // Enable the feature with dry_run params: activation_level = dry_run.
+  scoped_feature_list_.InitAndEnableFeatureWithParameters(
+      features::kEnableFingerprintingProtectionFilter,
+      {{"activation_level", "dry_run"}});
+
+  // Initialize the WebContentsHelper and Throttle to be tested.
+  FingerprintingProtectionWebContentsHelper::CreateForWebContents(
+      navigation_handle()->GetWebContents());
+  auto throttle_under_test = MockFingerprintingProtectionPageActivationThrottle(
+      navigation_handle(), nullptr);
+
+  // Expect that NotifyResult is called with ACTIVATED ActivationDecision.
+  EXPECT_CALL(throttle_under_test,
+              NotifyResult(subresource_filter::ActivationDecision::ACTIVATED))
+      .WillOnce(testing::Return());
+  EXPECT_EQ(throttle_under_test.WillProcessResponse().action(),
+            content::NavigationThrottle::ThrottleAction::PROCEED);
+}
+
+TEST_F(FingerprintingProtectionPageActivationThrottleTest,
+       FlagEnabledWithAllSitesDisabledParams_IsDisabled) {
+  // Enable the feature with disabling params, i.e. activation_level = disabled.
+  scoped_feature_list_.InitAndEnableFeatureWithParameters(
+      features::kEnableFingerprintingProtectionFilter,
+      {{"activation_level", "disabled"}});
+
+  // Initialize the WebContentsHelper and Throttle to be tested.
+  FingerprintingProtectionWebContentsHelper::CreateForWebContents(
+      navigation_handle()->GetWebContents());
+  auto throttle_under_test = MockFingerprintingProtectionPageActivationThrottle(
+      navigation_handle(), nullptr);
+
+  // Expect that NotifyResult is called with ACTIVATION_DISABLED
+  // ActivationDecision.
+  EXPECT_CALL(
+      throttle_under_test,
+      NotifyResult(subresource_filter::ActivationDecision::ACTIVATION_DISABLED))
+      .WillOnce(testing::Return());
+  EXPECT_EQ(throttle_under_test.WillProcessResponse().action(),
+            content::NavigationThrottle::ThrottleAction::PROCEED);
+}
+
+}  // namespace fingerprinting_protection_filter
diff --git a/components/fingerprinting_protection_filter/browser/fingerprinting_protection_web_contents_helper.cc b/components/fingerprinting_protection_filter/browser/fingerprinting_protection_web_contents_helper.cc
index 8de9fcac..9fcf828f 100644
--- a/components/fingerprinting_protection_filter/browser/fingerprinting_protection_web_contents_helper.cc
+++ b/components/fingerprinting_protection_filter/browser/fingerprinting_protection_web_contents_helper.cc
@@ -5,11 +5,18 @@
 #include "components/fingerprinting_protection_filter/browser/fingerprinting_protection_web_contents_helper.h"
 
 #include "components/fingerprinting_protection_filter/browser/fingerprinting_protection_filter_features.h"
-#include "components/subresource_filter/core/common/load_policy.h"
-#include "components/subresource_filter/core/mojom/subresource_filter.mojom.h"
-#include "content/public/browser/web_contents.h"
 #include "content/public/browser/web_contents_user_data.h"
 
+namespace content {
+class NavigationHandle;
+class WebContents;
+}  // namespace content
+
+namespace subresource_filter {
+enum class ActivationDecision;
+enum class LoadPolicy;
+}  // namespace subresource_filter
+
 namespace fingerprinting_protection_filter {
 
 // static
@@ -39,7 +46,7 @@
 
 void FingerprintingProtectionWebContentsHelper::NotifyPageActivationComputed(
     content::NavigationHandle* navigation_handle,
-    const subresource_filter::mojom::ActivationState& activation_state) {
+    const subresource_filter::ActivationDecision& activation_decision) {
   // TODO(crbug.com/327005578): Notify ThrottleManager
 }
 
diff --git a/components/fingerprinting_protection_filter/browser/fingerprinting_protection_web_contents_helper.h b/components/fingerprinting_protection_filter/browser/fingerprinting_protection_web_contents_helper.h
index a07bef1..ffcdbaa 100644
--- a/components/fingerprinting_protection_filter/browser/fingerprinting_protection_web_contents_helper.h
+++ b/components/fingerprinting_protection_filter/browser/fingerprinting_protection_web_contents_helper.h
@@ -5,7 +5,6 @@
 #ifndef COMPONENTS_FINGERPRINTING_PROTECTION_FILTER_BROWSER_FINGERPRINTING_PROTECTION_WEB_CONTENTS_HELPER_H_
 #define COMPONENTS_FINGERPRINTING_PROTECTION_FILTER_BROWSER_FINGERPRINTING_PROTECTION_WEB_CONTENTS_HELPER_H_
 
-#include "components/subresource_filter/core/common/load_policy.h"
 #include "content/public/browser/web_contents_user_data.h"
 
 namespace content {
@@ -13,9 +12,10 @@
 class WebContents;
 }  // namespace content
 
-namespace subresource_filter::mojom {
-class ActivationState;
-}  // namespace subresource_filter::mojom
+namespace subresource_filter {
+enum class ActivationDecision;
+enum class LoadPolicy;
+}  // namespace subresource_filter
 
 namespace fingerprinting_protection_filter {
 
@@ -37,7 +37,7 @@
   // throttles created in MaybeAppendNavigationThrottles().
   void NotifyPageActivationComputed(
       content::NavigationHandle* navigation_handle,
-      const subresource_filter::mojom::ActivationState& activation_state);
+      const subresource_filter::ActivationDecision& activation_decision);
 
   // Called in WillStartRequest or WillRedirectRequest stage from a
   // ChildFrameNavigationFilteringThrottle.
diff --git a/components/invalidation/impl/BUILD.gn b/components/invalidation/impl/BUILD.gn
index ebd820a..1c993201 100644
--- a/components/invalidation/impl/BUILD.gn
+++ b/components/invalidation/impl/BUILD.gn
@@ -19,8 +19,6 @@
   sources = [
     "channels_states.cc",
     "channels_states.h",
-    "fake_ack_handler.cc",
-    "fake_ack_handler.h",
     "fcm_invalidation_listener.cc",
     "fcm_invalidation_listener.h",
     "fcm_invalidation_service.cc",
diff --git a/components/invalidation/impl/fake_ack_handler.cc b/components/invalidation/impl/fake_ack_handler.cc
deleted file mode 100644
index 35e066e..0000000
--- a/components/invalidation/impl/fake_ack_handler.cc
+++ /dev/null
@@ -1,79 +0,0 @@
-// Copyright 2014 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/invalidation/impl/fake_ack_handler.h"
-
-#include "base/ranges/algorithm.h"
-#include "base/task/single_thread_task_runner.h"
-#include "components/invalidation/public/ack_handle.h"
-#include "components/invalidation/public/invalidation.h"
-
-namespace invalidation {
-
-namespace {
-
-struct AckHandleMatcher {
-  explicit AckHandleMatcher(const AckHandle& handle);
-  bool operator()(const Invalidation& invalidation) const;
-
-  AckHandle handle_;
-};
-
-AckHandleMatcher::AckHandleMatcher(const AckHandle& handle) : handle_(handle) {}
-
-bool AckHandleMatcher::operator()(const Invalidation& invalidation) const {
-  return handle_.Equals(invalidation.ack_handle());
-}
-
-}  // namespace
-
-FakeAckHandler::FakeAckHandler() = default;
-
-FakeAckHandler::~FakeAckHandler() = default;
-
-void FakeAckHandler::RegisterInvalidation(Invalidation* invalidation) {
-  unacked_invalidations_.push_back(*invalidation);
-  invalidation->SetAckHandler(
-      weak_ptr_factory_.GetWeakPtr(),
-      base::SingleThreadTaskRunner::GetCurrentDefault());
-}
-
-void FakeAckHandler::RegisterUnsentInvalidation(Invalidation* invalidation) {
-  unsent_invalidations_.push_back(*invalidation);
-}
-
-bool FakeAckHandler::IsUnacked(const Invalidation& invalidation) const {
-  return base::ranges::any_of(unacked_invalidations_,
-                              AckHandleMatcher(invalidation.ack_handle()));
-}
-
-bool FakeAckHandler::IsAcknowledged(const Invalidation& invalidation) const {
-  return base::ranges::any_of(acked_invalidations_,
-                              AckHandleMatcher(invalidation.ack_handle()));
-}
-
-bool FakeAckHandler::IsUnsent(const Invalidation& invalidation) const {
-  return base::ranges::any_of(unsent_invalidations_,
-                              AckHandleMatcher(invalidation.ack_handle()));
-}
-
-bool FakeAckHandler::AllInvalidationsAccountedFor() const {
-  return unacked_invalidations_.empty() && unrecovered_drop_events_.empty();
-}
-
-void FakeAckHandler::Acknowledge(const Topic& topic, const AckHandle& handle) {
-  auto it =
-      base::ranges::find_if(unacked_invalidations_, AckHandleMatcher(handle));
-  if (it != unacked_invalidations_.end()) {
-    acked_invalidations_.push_back(*it);
-    unacked_invalidations_.erase(it);
-  }
-
-  auto it2 = unrecovered_drop_events_.find(topic);
-  if (it2 != unrecovered_drop_events_.end() && it2->second.Equals(handle)) {
-    unrecovered_drop_events_.erase(it2);
-  }
-}
-
-}  // namespace invalidation
diff --git a/components/invalidation/impl/fake_ack_handler.h b/components/invalidation/impl/fake_ack_handler.h
deleted file mode 100644
index f50fbb7..0000000
--- a/components/invalidation/impl/fake_ack_handler.h
+++ /dev/null
@@ -1,67 +0,0 @@
-// Copyright 2014 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_INVALIDATION_IMPL_FAKE_ACK_HANDLER_H_
-#define COMPONENTS_INVALIDATION_IMPL_FAKE_ACK_HANDLER_H_
-
-#include <map>
-#include <vector>
-
-#include "base/compiler_specific.h"
-#include "base/memory/weak_ptr.h"
-#include "components/invalidation/public/ack_handler.h"
-#include "components/invalidation/public/invalidation_export.h"
-#include "components/invalidation/public/invalidation_util.h"
-
-namespace invalidation {
-
-class Invalidation;
-
-// This AckHandler implementation colaborates with the FakeInvalidationService
-// to enable unit tests to assert that invalidations are being acked properly.
-class INVALIDATION_EXPORT FakeAckHandler final : public AckHandler {
- public:
-  FakeAckHandler();
-  ~FakeAckHandler() override;
-
-  // Sets up some internal state to track this invalidation, and modifies it so
-  // that its Acknowledge() and Drop() methods will route back to us.
-  void RegisterInvalidation(Invalidation* invalidation);
-
-  // No one was listening for this invalidation, so no one will receive it or
-  // ack it.  We keep track of it anyway to let tests make assertions about it.
-  void RegisterUnsentInvalidation(Invalidation* invalidation);
-
-  // Returns true if the specified invalidaition has been delivered, but has not
-  // been acknowledged yet.
-  bool IsUnacked(const Invalidation& invalidation) const;
-
-  // Returns true if the specified invalidation has been delivered and
-  // acknowledged.
-  bool IsAcknowledged(const Invalidation& invalidation) const;
-
-  // Returns true if the specified invalidation was never delivered.
-  bool IsUnsent(const Invalidation& invalidation) const;
-
-  // Returns true if all invalidations have been acked and all drops recovered.
-  bool AllInvalidationsAccountedFor() const;
-
-  // Implementation of AckHandler.
-  void Acknowledge(const Topic& topic, const AckHandle& handle) override;
-
- private:
-  typedef std::vector<Invalidation> InvalidationVector;
-
-  InvalidationVector unsent_invalidations_;
-  InvalidationVector unacked_invalidations_;
-  InvalidationVector acked_invalidations_;
-
-  std::map<Topic, AckHandle> unrecovered_drop_events_;
-
-  base::WeakPtrFactory<FakeAckHandler> weak_ptr_factory_{this};
-};
-
-}  // namespace invalidation
-
-#endif  // COMPONENTS_INVALIDATION_IMPL_FAKE_ACK_HANDLER_H_
diff --git a/components/invalidation/impl/fake_invalidation_service.cc b/components/invalidation/impl/fake_invalidation_service.cc
index 34a316f..46d1372 100644
--- a/components/invalidation/impl/fake_invalidation_service.cc
+++ b/components/invalidation/impl/fake_invalidation_service.cc
@@ -60,25 +60,7 @@
 
 void FakeInvalidationService::EmitInvalidationForTest(
     const Invalidation& invalidation) {
-  // This function might need to modify the |invalidation|, so we start by
-  // making an identical copy of it.
-  Invalidation invalidation_copy(invalidation);
-
-  // If no one is listening to this invalidation, do not send it out.
-  TopicMap subscribed_topics = invalidator_registrar_->GetAllSubscribedTopics();
-  if (subscribed_topics.find(invalidation.topic()) == subscribed_topics.end()) {
-    fake_ack_handler_.RegisterUnsentInvalidation(&invalidation_copy);
-    return;
-  }
-
-  // Otherwise, register the invalidation with the fake_ack_handler_ and deliver
-  // it to the appropriate consumer.
-  fake_ack_handler_.RegisterInvalidation(&invalidation_copy);
-  invalidator_registrar_->DispatchInvalidationToHandlers(invalidation_copy);
-}
-
-FakeAckHandler* FakeInvalidationService::GetFakeAckHandler() {
-  return &fake_ack_handler_;
+  invalidator_registrar_->DispatchInvalidationToHandlers(invalidation);
 }
 
 }  // namespace invalidation
diff --git a/components/invalidation/impl/fake_invalidation_service.h b/components/invalidation/impl/fake_invalidation_service.h
index c363cc3d..449408c 100644
--- a/components/invalidation/impl/fake_invalidation_service.h
+++ b/components/invalidation/impl/fake_invalidation_service.h
@@ -6,10 +6,9 @@
 #define COMPONENTS_INVALIDATION_IMPL_FAKE_INVALIDATION_SERVICE_H_
 
 #include <memory>
-#include <utility>
+#include <optional>
+#include <string>
 
-#include "base/functional/callback_forward.h"
-#include "components/invalidation/impl/fake_ack_handler.h"
 #include "components/invalidation/impl/invalidator_registrar_with_memory.h"
 #include "components/invalidation/public/invalidation_service.h"
 #include "components/prefs/testing_pref_service.h"
@@ -19,7 +18,8 @@
 class Invalidation;
 
 // An InvalidationService that emits invalidations only when
-// its EmitInvalidationForTest method is called.
+// its EmitInvalidationForTest method is called (and a handler is interested
+// in the topic of that invalidation).
 class FakeInvalidationService : public InvalidationService {
  public:
   FakeInvalidationService();
@@ -44,16 +44,11 @@
 
   void EmitInvalidationForTest(const Invalidation& invalidation);
 
-  // Emitted invalidations will be hooked up to this AckHandler.  Clients can
-  // query it to assert the invalidaitons are being acked properly.
-  FakeAckHandler* GetFakeAckHandler();
-
  private:
   std::string client_id_;
   // |pref_service_| must outlive |invalidator_registrar_|.
   TestingPrefServiceSimple pref_service_;
   std::unique_ptr<InvalidatorRegistrarWithMemory> invalidator_registrar_;
-  FakeAckHandler fake_ack_handler_;
 };
 
 }  // namespace invalidation
diff --git a/components/invalidation/public/BUILD.gn b/components/invalidation/public/BUILD.gn
index 1669ae68..416a8a1 100644
--- a/components/invalidation/public/BUILD.gn
+++ b/components/invalidation/public/BUILD.gn
@@ -4,9 +4,6 @@
 
 static_library("public") {
   sources = [
-    "ack_handle.cc",
-    "ack_handle.h",
-    "ack_handler.h",
     "identity_provider.cc",
     "identity_provider.h",
     "invalidation.cc",
diff --git a/components/invalidation/public/ack_handle.cc b/components/invalidation/public/ack_handle.cc
deleted file mode 100644
index 7209c30..0000000
--- a/components/invalidation/public/ack_handle.cc
+++ /dev/null
@@ -1,39 +0,0 @@
-// Copyright 2014 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/invalidation/public/ack_handle.h"
-
-#include <stddef.h>
-#include <stdint.h>
-
-#include "base/rand_util.h"
-#include "base/strings/string_number_conversions.h"
-
-namespace invalidation {
-
-namespace {
-std::string GetRandomId() {
-  // Hopefully enough bytes for uniqueness.
-  constexpr size_t kBytesInHandle = 16;
-
-  // This isn't a valid UUID, so we don't attempt to format it like one.
-  uint8_t random_bytes[kBytesInHandle];
-  base::RandBytes(random_bytes);
-  return base::HexEncode(random_bytes);
-}
-}  // namespace
-
-AckHandle::AckHandle() : state_(GetRandomId()), timestamp_(base::Time::Now()) {}
-
-AckHandle::AckHandle(const AckHandle& other) = default;
-
-AckHandle& AckHandle::operator=(const AckHandle& other) = default;
-
-AckHandle::~AckHandle() = default;
-
-bool AckHandle::Equals(const AckHandle& other) const {
-  return state_ == other.state_ && timestamp_ == other.timestamp_;
-}
-
-}  // namespace invalidation
diff --git a/components/invalidation/public/ack_handle.h b/components/invalidation/public/ack_handle.h
deleted file mode 100644
index 5a49052..0000000
--- a/components/invalidation/public/ack_handle.h
+++ /dev/null
@@ -1,33 +0,0 @@
-// Copyright 2014 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_INVALIDATION_PUBLIC_ACK_HANDLE_H_
-#define COMPONENTS_INVALIDATION_PUBLIC_ACK_HANDLE_H_
-
-#include <string>
-
-#include "base/time/time.h"
-#include "components/invalidation/public/invalidation_export.h"
-
-namespace invalidation {
-
-// Opaque class that represents a local ack handle. We don't reuse the
-// invalidation ack handles to avoid unnecessary dependencies.
-class INVALIDATION_EXPORT AckHandle {
- public:
-  AckHandle();
-  AckHandle(const AckHandle& other);
-  AckHandle& operator=(const AckHandle& other);
-  ~AckHandle();
-
-  bool Equals(const AckHandle& other) const;
-
- private:
-  std::string state_;
-  base::Time timestamp_;
-};
-
-}  // namespace invalidation
-
-#endif  // COMPONENTS_INVALIDATION_PUBLIC_ACK_HANDLE_H_
diff --git a/components/invalidation/public/ack_handler.h b/components/invalidation/public/ack_handler.h
deleted file mode 100644
index 74d8bae..0000000
--- a/components/invalidation/public/ack_handler.h
+++ /dev/null
@@ -1,30 +0,0 @@
-// Copyright 2014 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_INVALIDATION_PUBLIC_ACK_HANDLER_H_
-#define COMPONENTS_INVALIDATION_PUBLIC_ACK_HANDLER_H_
-
-#include "components/invalidation/public/invalidation_export.h"
-#include "components/invalidation/public/invalidation_util.h"
-
-namespace invalidation {
-
-class AckHandle;
-
-// An interface for classes that keep track of invalidation acknowledgements.
-//
-// We don't expect to support more than one "real" implementation of AckHandler,
-// but this interface is very useful for testing and implementation hiding.
-class INVALIDATION_EXPORT AckHandler {
- public:
-  AckHandler() = default;
-  virtual ~AckHandler() = default;
-
-  // Record the local acknowledgement of an invalidation identified by |handle|.
-  virtual void Acknowledge(const Topic& topic, const AckHandle& handle) = 0;
-};
-
-}  // namespace invalidation
-
-#endif  // COMPONENTS_INVALIDATION_PUBLIC_ACK_HANDLER_H_
diff --git a/components/invalidation/public/invalidation.cc b/components/invalidation/public/invalidation.cc
index 075e589..9152c3218 100644
--- a/components/invalidation/public/invalidation.cc
+++ b/components/invalidation/public/invalidation.cc
@@ -4,9 +4,8 @@
 
 #include "components/invalidation/public/invalidation.h"
 
-#include "base/functional/bind.h"
-#include "base/task/sequenced_task_runner.h"
-#include "components/invalidation/public/ack_handler.h"
+#include <string>
+
 #include "components/invalidation/public/invalidation_util.h"
 
 namespace invalidation {
@@ -34,28 +33,6 @@
   return payload_;
 }
 
-const AckHandle& Invalidation::ack_handle() const {
-  return ack_handle_;
-}
-
-void Invalidation::SetAckHandler(
-    base::WeakPtr<AckHandler> handler,
-    scoped_refptr<base::SequencedTaskRunner> handler_task_runner) {
-  ack_handler_ = handler;
-  ack_handler_task_runner_ = handler_task_runner;
-}
-
-void Invalidation::Acknowledge() const {
-  if (ack_handler_task_runner_) {
-    ack_handler_task_runner_->PostTask(
-        FROM_HERE, base::BindOnce(&AckHandler::Acknowledge, ack_handler_,
-                                  topic(), ack_handle_));
-  }
-}
-
-bool Invalidation::operator==(const Invalidation& other) const {
-  return topic_ == other.topic_ &&
-         version_ == other.version_ && payload_ == other.payload_;
-}
+bool Invalidation::operator==(const Invalidation& other) const = default;
 
 }  // namespace invalidation
diff --git a/components/invalidation/public/invalidation.h b/components/invalidation/public/invalidation.h
index 50a2c105..c7a9464e 100644
--- a/components/invalidation/public/invalidation.h
+++ b/components/invalidation/public/invalidation.h
@@ -9,18 +9,12 @@
 
 #include <string>
 
-#include "base/memory/weak_ptr.h"
-#include "base/task/sequenced_task_runner.h"
-#include "components/invalidation/public/ack_handle.h"
 #include "components/invalidation/public/invalidation_export.h"
 #include "components/invalidation/public/invalidation_util.h"
 
 namespace invalidation {
 
-class AckHandler;
-
-// Represents a local invalidation. This class supports "local" ack-tracking
-// and simple serialization to pref values.
+// Represents a local invalidation.
 class INVALIDATION_EXPORT Invalidation {
  public:
   Invalidation(const Topic& topic, int64_t version, const std::string& payload);
@@ -28,34 +22,13 @@
   Invalidation& operator=(const Invalidation& other);
   ~Invalidation();
 
-  // Compares two invalidations.  The comparison ignores ack-tracking state.
+  // Compares two invalidations.
   bool operator==(const Invalidation& other) const;
 
   Topic topic() const;
   int64_t version() const;
   const std::string& payload() const;
 
-  const AckHandle& ack_handle() const;
-
-  // Sets the AckHandler to be used to track this Invalidation.
-  //
-  // This should be set by the class that generates the invalidation.  Clients
-  // of the Invalidations API should not need to call this.
-  //
-  // Note that some sources of invalidations do not support ack tracking, and do
-  // not set the ack_handler.  This will be hidden from users of this class.
-  void SetAckHandler(
-      base::WeakPtr<AckHandler> handler,
-      scoped_refptr<base::SequencedTaskRunner> handler_task_runner);
-
-  // Acknowledges the receipt of this invalidation.
-  //
-  // Clients should call this on a received invalidation when they have fully
-  // processed the invalidation and persisted the results to disk.  Once this
-  // function is called, the invalidations system is under no obligation to
-  // re-deliver this invalidation in the event of a crash or restart.
-  void Acknowledge() const;
-
  private:
   // The Topic to which this invalidation belongs.
   Topic topic_;
@@ -63,15 +36,8 @@
   // The version number of this invalidation.
   int64_t version_;
 
-  // The payaload associated with this invalidation.
+  // The payload associated with this invalidation.
   std::string payload_;
-
-  // A locally generated unique ID used to manage local acknowledgements.
-  AckHandle ack_handle_;
-
-  // The acknowledgement tracking handler and its thread.
-  base::WeakPtr<AckHandler> ack_handler_;
-  scoped_refptr<base::SequencedTaskRunner> ack_handler_task_runner_;
 };
 
 }  // namespace invalidation
diff --git a/components/invalidation/public/invalidation_service.h b/components/invalidation/public/invalidation_service.h
index 6e3b7d11..24a2af87 100644
--- a/components/invalidation/public/invalidation_service.h
+++ b/components/invalidation/public/invalidation_service.h
@@ -47,10 +47,6 @@
 // unregister themselves before then. (Depending on the
 // InvalidationService, shutdown may be equivalent to destruction, or
 // a separate function call like Shutdown()).
-//
-// NOTE(akalin): Invalidations that come in during browser shutdown may get
-// dropped.  This won't matter once we have an Acknowledge API, though: see
-// http://crbug.com/78462 and http://crbug.com/124149.
 class InvalidationService {
  public:
   InvalidationService() = default;
diff --git a/components/metrics/structured/structured_metrics_features.cc b/components/metrics/structured/structured_metrics_features.cc
index 5a1e2690..8e35130c 100644
--- a/components/metrics/structured/structured_metrics_features.cc
+++ b/components/metrics/structured/structured_metrics_features.cc
@@ -8,10 +8,6 @@
 
 namespace metrics::structured {
 
-BASE_FEATURE(kFastPairMetrics,
-             "FastPairMetrics",
-             base::FEATURE_ENABLED_BY_DEFAULT);
-
 BASE_FEATURE(kNearbyShareMetrics,
              "NearbyShareMetrics",
              base::FEATURE_ENABLED_BY_DEFAULT);
diff --git a/components/metrics/structured/structured_metrics_features.h b/components/metrics/structured/structured_metrics_features.h
index 20600e54..6b44ffa5 100644
--- a/components/metrics/structured/structured_metrics_features.h
+++ b/components/metrics/structured/structured_metrics_features.h
@@ -19,9 +19,6 @@
 
 namespace metrics::structured {
 
-// Controls whether fast pair logging is enabled or not.
-BASE_DECLARE_FEATURE(kFastPairMetrics);
-
 // Controls whether nearby share logging is enabled or not.
 BASE_DECLARE_FEATURE(kNearbyShareMetrics);
 
diff --git a/components/omnibox/browser/android/java/src/org/chromium/components/omnibox/AutocompleteMatch.java b/components/omnibox/browser/android/java/src/org/chromium/components/omnibox/AutocompleteMatch.java
index 4b2a003..0067c260 100644
--- a/components/omnibox/browser/android/java/src/org/chromium/components/omnibox/AutocompleteMatch.java
+++ b/components/omnibox/browser/android/java/src/org/chromium/components/omnibox/AutocompleteMatch.java
@@ -152,7 +152,7 @@
         mActions = actions != null ? actions : Arrays.asList();
         mAllowedToBeDefaultMatch = allowedToBeDefaultMatch;
         mInlineAutocompletion = inlineAutocompletion;
-        mAdditionalText = inlineAutocompletion;
+        mAdditionalText = additionalText;
     }
 
     @CalledByNative
diff --git a/components/omnibox/browser/android/java/src/org/chromium/components/omnibox/AutocompleteResult.java b/components/omnibox/browser/android/java/src/org/chromium/components/omnibox/AutocompleteResult.java
index 0b221c8..de6d758 100644
--- a/components/omnibox/browser/android/java/src/org/chromium/components/omnibox/AutocompleteResult.java
+++ b/components/omnibox/browser/android/java/src/org/chromium/components/omnibox/AutocompleteResult.java
@@ -235,6 +235,20 @@
         }
     }
 
+    /**
+     * This is a counterpart of native AutocompleteResult#default_match.
+     *
+     * @return The default match if it exists, or nullptr otherwise.
+     */
+    @Nullable
+    public AutocompleteMatch getDefaultMatch() {
+        if (mSuggestions.size() > 0 && mSuggestions.get(0).allowedToBeDefaultMatch()) {
+            return mSuggestions.get(0);
+        }
+
+        return null;
+    }
+
     @NativeMethods
     interface Natives {
         void groupSuggestionsBySearchVsURL(
diff --git a/components/omnibox/browser/android/java/src/org/chromium/components/omnibox/AutocompleteResultUnitTest.java b/components/omnibox/browser/android/java/src/org/chromium/components/omnibox/AutocompleteResultUnitTest.java
index e3472f7..8518582 100644
--- a/components/omnibox/browser/android/java/src/org/chromium/components/omnibox/AutocompleteResultUnitTest.java
+++ b/components/omnibox/browser/android/java/src/org/chromium/components/omnibox/AutocompleteResultUnitTest.java
@@ -332,4 +332,45 @@
         res.notifyNativeDestroyed();
         Assert.assertFalse(res.isFromCachedResult());
     }
+
+    @Test
+    public void getDefaultMatch_emptyList() {
+        AutocompleteResult emptyResult = new AutocompleteResult(0x12345678, null, null);
+        Assert.assertNull(emptyResult.getDefaultMatch());
+    }
+
+    @Test
+    public void getDefaultMatch_nonDefaultFirstMatch() {
+        List<AutocompleteMatch> list =
+                Arrays.asList(
+                        buildSuggestionForIndex(1),
+                        buildSuggestionForIndex(2),
+                        buildSuggestionForIndex(3));
+        AutocompleteResult autocompleteResult = new AutocompleteResult(0x12345678, list, null);
+        Assert.assertNull(autocompleteResult.getDefaultMatch());
+    }
+
+    @Test
+    public void getDefaultMatch_defaultFirstMatch() {
+        List<AutocompleteMatch> list =
+                Arrays.asList(
+                        AutocompleteMatchBuilder.searchWithType(
+                                        OmniboxSuggestionType.SEARCH_SUGGEST)
+                                .setDisplayText("Dummy Suggestion 1")
+                                .setDescription("Dummy Description 1")
+                                .setAllowedToBeDefaultMatch(true)
+                                .setInlineAutocompletion("inline_autocomplete")
+                                .setAdditionalText("additional_text")
+                                .build(),
+                        buildSuggestionForIndex(2),
+                        buildSuggestionForIndex(3));
+        AutocompleteResult autocompleteResult = new AutocompleteResult(0x12345678, list, null);
+        Assert.assertNotNull(autocompleteResult.getDefaultMatch());
+        Assert.assertTrue(autocompleteResult.getDefaultMatch().allowedToBeDefaultMatch());
+        Assert.assertEquals(
+                "inline_autocomplete",
+                autocompleteResult.getDefaultMatch().getInlineAutocompletion());
+        Assert.assertEquals(
+                "additional_text", autocompleteResult.getDefaultMatch().getAdditionalText());
+    }
 }
diff --git a/components/omnibox/common/android/java/src/org/chromium/components/omnibox/OmniboxFeatures.java b/components/omnibox/common/android/java/src/org/chromium/components/omnibox/OmniboxFeatures.java
index eee5305..7e8738b 100644
--- a/components/omnibox/common/android/java/src/org/chromium/components/omnibox/OmniboxFeatures.java
+++ b/components/omnibox/common/android/java/src/org/chromium/components/omnibox/OmniboxFeatures.java
@@ -12,7 +12,6 @@
 import org.chromium.base.cached_flags.BooleanCachedFieldTrialParameter;
 import org.chromium.base.cached_flags.CachedFieldTrialParameter;
 import org.chromium.base.cached_flags.CachedFlag;
-import org.chromium.base.cached_flags.CachedFlagUtils;
 import org.chromium.base.cached_flags.IntCachedFieldTrialParameter;
 
 import java.util.ArrayList;
@@ -128,13 +127,9 @@
         return param;
     }
 
-    /**
-     * Persist cached feature flags and parameters.
-     *
-     * <p>Persists all flags that were statically instantiated as part of this class.
-     */
-    public static void cacheFeatureFlags() {
-        CachedFlagUtils.cacheNativeFlags(sCachedFlags);
+    /** Retrieve list of CachedFlags that should be cached. */
+    public static List<CachedFlag> getFieldTrialsToCache() {
+        return sCachedFlags;
     }
 
     /** Retrieve list of FieldTrialParams that should be cached. */
diff --git a/components/optimization_guide/internal b/components/optimization_guide/internal
index 9ab8d0e..4aa4612 160000
--- a/components/optimization_guide/internal
+++ b/components/optimization_guide/internal
@@ -1 +1 @@
-Subproject commit 9ab8d0e807bfc59c021093c7cb67a841c2347832
+Subproject commit 4aa461253ca6e5465faba2c96c60c269be1c17dc
diff --git a/components/page_info/android/BUILD.gn b/components/page_info/android/BUILD.gn
index 9226b17b..ccfb739 100644
--- a/components/page_info/android/BUILD.gn
+++ b/components/page_info/android/BUILD.gn
@@ -36,6 +36,12 @@
 android_resources("java_resources") {
   sources = [
     "java/res/drawable/page_info_bg.xml",
+    "java/res/drawable/tp_cookie.xml",
+    "java/res/drawable/tp_cookie_off.xml",
+    "java/res/drawable/tp_fingerprint.xml",
+    "java/res/drawable/tp_fingerprint_off.xml",
+    "java/res/drawable/tp_ip_protection_off.xml",
+    "java/res/drawable/tp_ip_protection_on.xml",
     "java/res/layout/button_preference_text_button.xml",
     "java/res/layout/connection_info.xml",
     "java/res/layout/page_info.xml",
diff --git a/components/page_info/android/java/res/drawable/tp_cookie.xml b/components/page_info/android/java/res/drawable/tp_cookie.xml
new file mode 100644
index 0000000..a23b250
--- /dev/null
+++ b/components/page_info/android/java/res/drawable/tp_cookie.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+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.
+-->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="960"
+    android:viewportHeight="960"
+    android:tint="@macro/default_icon_color">
+  <path
+      android:pathData="M480,880Q397,880 324,848.5Q251,817 197,763Q143,709 111.5,636Q80,563 80,480Q80,405 109,333Q138,261 190,204.5Q242,148 315,113.5Q388,79 475,79Q496,79 518,81Q540,83 563,88Q554,133 569,173Q584,213 614,239.5Q644,266 685.5,276Q727,286 771,271Q745,330 778.5,384Q812,438 878,440Q879,451 879.5,460.5Q880,470 880,481Q880,563 848.5,635.5Q817,708 763,762.5Q709,817 636,848.5Q563,880 480,880ZM420,400Q445,400 462.5,382.5Q480,365 480,340Q480,315 462.5,297.5Q445,280 420,280Q395,280 377.5,297.5Q360,315 360,340Q360,365 377.5,382.5Q395,400 420,400ZM340,600Q365,600 382.5,582.5Q400,565 400,540Q400,515 382.5,497.5Q365,480 340,480Q315,480 297.5,497.5Q280,515 280,540Q280,565 297.5,582.5Q315,600 340,600ZM600,640Q617,640 628.5,628.5Q640,617 640,600Q640,583 628.5,571.5Q617,560 600,560Q583,560 571.5,571.5Q560,583 560,600Q560,617 571.5,628.5Q583,640 600,640ZM480,800Q602,800 696.5,716Q791,632 800,502Q750,480 721.5,442Q693,404 683,357Q606,346 551,291Q496,236 483,159Q403,157 342.5,188Q282,219 241.5,267.5Q201,316 180.5,373Q160,430 160,480Q160,613 253.5,706.5Q347,800 480,800ZM480,476Q480,476 480,476Q480,476 480,476Q480,476 480,476Q480,476 480,476Q480,476 480,476Q480,476 480,476Q480,476 480,476Q480,476 480,476Q480,476 480,476Q480,476 480,476Q480,476 480,476Q480,476 480,476Z"
+      android:fillColor="@android:color/white"/>
+</vector>
diff --git a/components/page_info/android/java/res/drawable/tp_cookie_off.xml b/components/page_info/android/java/res/drawable/tp_cookie_off.xml
new file mode 100644
index 0000000..e90dec82
--- /dev/null
+++ b/components/page_info/android/java/res/drawable/tp_cookie_off.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+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.
+-->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="960"
+    android:viewportHeight="960"
+    android:tint="@macro/default_icon_color">
+  <path
+      android:pathData="M815,700L757,642Q775,611 786,575.5Q797,540 800,502Q750,480 721.5,442Q693,404 683,357Q606,346 551,291Q496,236 483,159Q434,157 393,169Q352,181 317,202L260,145Q321,103 397.5,86.5Q474,70 563,88Q554,133 569,172.5Q584,212 614,239Q644,266 685,276Q726,286 771,271Q740,340 782,389Q824,438 878,440Q886,512 868.5,578Q851,644 815,700ZM340,600Q315,600 297.5,582.5Q280,565 280,540Q280,515 297.5,497.5Q315,480 340,480Q365,480 382.5,497.5Q400,515 400,540Q400,565 382.5,582.5Q365,600 340,600ZM819,932L701,814Q653,846 597.5,863Q542,880 480,880Q397,880 324,848.5Q251,817 197,763Q143,709 111.5,636Q80,563 80,480Q80,418 97,362.5Q114,307 146,259L27,140L84,83L876,875L819,932ZM480,800Q525,800 565.5,788Q606,776 642,755L205,318Q184,354 172,394.5Q160,435 160,480Q160,613 253.5,706.5Q347,800 480,800ZM424,536L424,536L424,536L424,536L424,536ZM559,399L559,399Q559,399 559,399Q559,399 559,399Q559,399 559,399Q559,399 559,399Q559,399 559,399Q559,399 559,399Q559,399 559,399Q559,399 559,399Z"
+      android:fillColor="@android:color/white"/>
+</vector>
diff --git a/components/page_info/android/java/res/drawable/tp_fingerprint.xml b/components/page_info/android/java/res/drawable/tp_fingerprint.xml
new file mode 100644
index 0000000..79112b2
--- /dev/null
+++ b/components/page_info/android/java/res/drawable/tp_fingerprint.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+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.
+-->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="960"
+    android:viewportHeight="960"
+    android:tint="@macro/default_icon_color">
+  <path
+      android:pathData="M481,179Q587,179 681,224.5Q775,270 838,356Q845,365 842.5,372Q840,379 834,384Q828,389 820,388.5Q812,388 806,380Q751,302 664.5,260.5Q578,219 481,219Q384,219 299,260.5Q214,302 158,380Q152,389 144,390Q136,391 130,386Q123,381 121.5,373.5Q120,366 126,358Q188,273 281.5,226Q375,179 481,179ZM481,273Q616,273 713,363Q810,453 810,586Q810,636 774.5,669.5Q739,703 688,703Q637,703 600.5,669.5Q564,636 564,586Q564,553 539.5,530.5Q515,508 481,508Q447,508 422.5,530.5Q398,553 398,586Q398,683 455.5,748Q513,813 604,839Q613,842 616,849Q619,856 617,864Q615,871 609,876Q603,881 594,879Q490,853 424,775.5Q358,698 358,586Q358,536 394,502Q430,468 481,468Q532,468 568,502Q604,536 604,586Q604,619 629,641.5Q654,664 688,664Q722,664 746,641.5Q770,619 770,586Q770,470 685,391Q600,312 482,312Q364,312 279,391Q194,470 194,585Q194,609 198.5,645Q203,681 220,729Q223,738 219.5,745Q216,752 208,755Q200,758 192.5,754.5Q185,751 182,743Q167,704 160.5,665.5Q154,627 154,586Q154,453 250.5,363Q347,273 481,273ZM481,81Q545,81 606,96.5Q667,112 724,141Q733,146 734.5,153Q736,160 733,167Q730,174 723,178Q716,182 706,177Q653,150 596.5,135.5Q540,121 481,121Q423,121 367,134.5Q311,148 260,177Q252,182 244,179.5Q236,177 232,169Q228,161 230,154.5Q232,148 240,143Q296,113 357,97Q418,81 481,81ZM481,370Q574,370 641,432.5Q708,495 708,586Q708,595 702.5,600.5Q697,606 688,606Q680,606 674,600.5Q668,595 668,586Q668,511 612.5,460.5Q557,410 481,410Q405,410 350.5,460.5Q296,511 296,586Q296,667 324,723.5Q352,780 406,837Q412,843 412,851Q412,859 406,865Q400,871 392,871Q384,871 378,865Q319,803 287.5,738.5Q256,674 256,586Q256,495 322,432.5Q388,370 481,370ZM480,566Q489,566 494.5,572Q500,578 500,586Q500,661 554,709Q608,757 680,757Q686,757 697,756Q708,755 720,753Q729,751 735.5,755.5Q742,760 744,769Q746,777 741,783Q736,789 728,791Q710,796 696.5,796.5Q683,797 680,797Q591,797 525.5,737Q460,677 460,586Q460,578 465.5,572Q471,566 480,566Z"
+      android:fillColor="@android:color/white"/>
+</vector>
diff --git a/components/page_info/android/java/res/drawable/tp_fingerprint_off.xml b/components/page_info/android/java/res/drawable/tp_fingerprint_off.xml
new file mode 100644
index 0000000..8b2e41ba
--- /dev/null
+++ b/components/page_info/android/java/res/drawable/tp_fingerprint_off.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+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.
+-->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="960"
+    android:viewportHeight="960"
+    android:tint="@macro/default_icon_color">
+  <path
+      android:pathData="M833,919L435,520Q416,531 406,548.5Q396,566 396,586Q396,678 451.5,747Q507,816 601,840Q609,842 613,849.5Q617,857 615,865Q613,873 606,877Q599,881 591,879Q483,851 419.5,771.5Q356,692 356,586Q356,558 369,533.5Q382,509 406,492L364,450Q330,476 312,511Q294,546 294,586Q294,661 319.5,718Q345,775 405,837Q411,843 411,851.5Q411,860 405,866Q399,872 390.5,872Q382,872 376,866Q310,799 282,735.5Q254,672 254,586Q254,538 276,494.5Q298,451 336,421L292,378Q234,432 212,477Q190,522 190,586Q190,622 196.5,659Q203,696 217,731Q220,739 217,746Q214,753 206,756Q198,759 190.5,756Q183,753 180,745Q165,703 158,664Q151,625 151,586Q151,513 176.5,458.5Q202,404 264,349L223,308Q203,325 186.5,342.5Q170,360 156,380Q152,387 144,388.5Q136,390 128,385Q121,380 120,372Q119,364 124,357Q138,336 156,317Q174,298 195,280L42,126L84,84L876,876L833,919ZM688,606Q680,606 674,600.5Q668,595 668,586Q668,514 617,465Q566,416 496,411Q496,411 496,411Q496,411 496,411L456,371Q462,370 468.5,370Q475,370 481,370Q574,370 641,432.5Q708,495 708,586Q708,595 702.5,600.5Q697,606 688,606ZM481,81Q545,81 606,96.5Q667,112 724,141Q733,146 734.5,153Q736,160 733,167Q730,174 723,178Q716,182 706,177Q653,150 596.5,135.5Q540,121 481,121Q423,121 367.5,134Q312,147 261,176L232,147Q289,115 352,98Q415,81 481,81ZM481,179Q587,179 681,224.5Q775,270 838,356Q845,365 842.5,372Q840,379 834,384Q828,389 820,388.5Q812,388 806,380Q751,302 664.5,260.5Q578,219 481,219Q442,219 404.5,226Q367,233 332,247L302,217Q344,198 388.5,188.5Q433,179 481,179ZM481,273Q616,273 713,363Q810,453 810,586Q810,615 797,638.5Q784,662 763,678L735,650Q751,639 760.5,622.5Q770,606 770,586Q770,470 685,391Q600,312 482,312Q462,312 443.5,314.5Q425,317 407,322L375,290Q400,282 426.5,277.5Q453,273 481,273ZM674,798Q585,798 521.5,737Q458,676 458,587Q458,579 463.5,573Q469,567 478,567Q487,567 492.5,573Q498,579 498,587Q498,659 550,708Q602,757 674,757Q680,757 691,756.5Q702,756 714,754Q723,752 729.5,756.5Q736,761 738,770Q740,778 735,784Q730,790 722,792Q704,797 690.5,797.5Q677,798 674,798Z"
+      android:fillColor="@android:color/white"/>
+</vector>
diff --git a/components/page_info/android/java/res/drawable/tp_ip_protection_off.xml b/components/page_info/android/java/res/drawable/tp_ip_protection_off.xml
new file mode 100644
index 0000000..95f907a1
--- /dev/null
+++ b/components/page_info/android/java/res/drawable/tp_ip_protection_off.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+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.
+-->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="960"
+    android:viewportHeight="960"
+    android:tint="@macro/default_icon_color">
+  <path
+      android:pathData="M440,920L440,840Q315,826 225.5,736.5Q136,647 122,522L42,522L42,442L122,442Q136,317 225.5,227.5Q315,138 440,124L440,44L520,44L520,124Q645,138 734.5,227.5Q824,317 838,442L918,442L918,522L838,522Q824,647 734.5,736.5Q645,826 520,840L520,920L440,920ZM480,762Q596,762 678,680Q760,598 760,482Q760,366 678,284Q596,202 480,202Q364,202 282,284Q200,366 200,482Q200,598 282,680Q364,762 480,762Z"
+      android:fillColor="@android:color/white"/>
+</vector>
diff --git a/components/page_info/android/java/res/drawable/tp_ip_protection_on.xml b/components/page_info/android/java/res/drawable/tp_ip_protection_on.xml
new file mode 100644
index 0000000..c97292d1
--- /dev/null
+++ b/components/page_info/android/java/res/drawable/tp_ip_protection_on.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+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.
+-->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="960"
+    android:viewportHeight="960"
+    android:tint="@macro/default_icon_color">
+  <path
+      android:pathData="M784,674L726,616Q743,586 751.5,552Q760,518 760,482Q760,366 678,284Q596,202 480,202Q444,202 410,210.5Q376,219 346,236L288,178Q323,157 360.5,143Q398,129 440,124L440,44L520,44L520,124Q645,138 734.5,227.5Q824,317 838,442L918,442L918,522L838,522Q833,564 819,601.5Q805,639 784,674ZM440,920L440,840Q315,826 225.5,736.5Q136,647 122,522L42,522L42,442L122,442Q127,400 141,362.5Q155,325 176,290L56,170L112,114L848,850L790,906L672,786Q637,807 599.5,821Q562,835 520,840L520,920L440,920ZM480,762Q516,762 550,753.5Q584,745 614,728L234,348Q217,378 208.5,412Q200,446 200,482Q200,598 282,680Q364,762 480,762Z"
+      android:fillColor="@android:color/white"/>
+</vector>
diff --git a/components/page_info/android/java/res/xml/page_info_tracking_protection_launch_preference.xml b/components/page_info/android/java/res/xml/page_info_tracking_protection_launch_preference.xml
index 475aa81..dd6d918 100644
--- a/components/page_info/android/java/res/xml/page_info_tracking_protection_launch_preference.xml
+++ b/components/page_info/android/java/res/xml/page_info_tracking_protection_launch_preference.xml
@@ -20,7 +20,7 @@
         app:allowDividerBelow="true" />
 
     <org.chromium.components.browser_ui.settings.TextMessagePreference
-        android:key="tpc_title"
+        android:key="tp_title"
         android:layout="@layout/page_info_title"
         app:allowDividerAbove="true" />
 
diff --git a/components/page_info/android/java/src/org/chromium/components/page_info/PageInfoTrackingProtectionLaunchSettings.java b/components/page_info/android/java/src/org/chromium/components/page_info/PageInfoTrackingProtectionLaunchSettings.java
index fd16b710..fbbae84 100644
--- a/components/page_info/android/java/src/org/chromium/components/page_info/PageInfoTrackingProtectionLaunchSettings.java
+++ b/components/page_info/android/java/src/org/chromium/components/page_info/PageInfoTrackingProtectionLaunchSettings.java
@@ -33,19 +33,18 @@
 /** View showing a toggle and a description for tracking protection for a site. */
 public class PageInfoTrackingProtectionLaunchSettings extends BaseSiteSettingsFragment {
     private static final String COOKIE_SUMMARY_PREFERENCE = "cookie_summary";
+    private static final String TP_TITLE = "tp_title";
     private static final String TP_SWITCH_PREFERENCE = "tp_switch";
     private static final String TP_STATUS_PREFERENCE = "tp_status";
     private static final String STORAGE_IN_USE_PREFERENCE = "storage_in_use";
     private static final String FPS_IN_USE_PREFERENCE = "fps_in_use";
-    private static final String TPC_TITLE = "tpc_title";
     private static final String TPC_SUMMARY = "tpc_summary";
     private static final int EXPIRATION_FOR_TESTING = 33;
 
-    private ChromeSwitchPreference mCookieSwitch;
+    private ChromeSwitchPreference mTpSwitch;
     private ChromeImageViewPreference mStorageInUse;
     private ChromeImageViewPreference mFPSInUse;
-    private TextMessagePreference mThirdPartyCookiesTitle;
-    private Preference mThirdPartyCookiesSummary;
+    private TextMessagePreference mTpTitle;
     private TrackingProtectionStatusPreference mTpStatus;
     private Runnable mOnClearCallback;
     private Runnable mOnCookieSettingsLinkClicked;
@@ -87,16 +86,15 @@
         }
         SettingsUtils.addPreferencesFromResource(
                 this, R.xml.page_info_tracking_protection_launch_preference);
-        mThirdPartyCookiesSummary = findPreference(TP_SWITCH_PREFERENCE);
 
-        mCookieSwitch = findPreference(TP_SWITCH_PREFERENCE);
-        mCookieSwitch.setUseSummaryAsTitle(false);
+        mTpSwitch = findPreference(TP_SWITCH_PREFERENCE);
+        mTpSwitch.setUseSummaryAsTitle(false);
 
         mTpStatus = findPreference(TP_STATUS_PREFERENCE);
         mStorageInUse = findPreference(STORAGE_IN_USE_PREFERENCE);
         mFPSInUse = findPreference(FPS_IN_USE_PREFERENCE);
         mFPSInUse.setVisible(false);
-        mThirdPartyCookiesTitle = findPreference(TPC_TITLE);
+        mTpTitle = findPreference(TP_TITLE);
     }
 
     @Override
@@ -138,8 +136,8 @@
                         new SpanApplier.SpanInfo("<link>", "</link>", linkSpan)));
 
         // TODO(crbug.com/40129299): Set a ManagedPreferenceDelegate?
-        mCookieSwitch.setVisible(params.thirdPartyCookieBlockingEnabled);
-        mCookieSwitch.setOnPreferenceChangeListener(
+        mTpSwitch.setVisible(params.thirdPartyCookieBlockingEnabled);
+        mTpSwitch.setOnPreferenceChangeListener(
                 (preference, newValue) -> {
                     boolean boolValue = (Boolean) newValue;
                     mTpStatus.setTrackingProtectionStatus(boolValue);
@@ -191,8 +189,8 @@
 
         if (enforcement == CookieControlsEnforcement.ENFORCED_BY_TPCD_GRANT) {
             // Hide all the 3PC controls.
-            mCookieSwitch.setVisible(false);
-            mThirdPartyCookiesTitle.setVisible(false);
+            mTpSwitch.setVisible(false);
+            mTpTitle.setVisible(false);
             findPreference(COOKIE_SUMMARY_PREFERENCE).setVisible(false);
             ClickableSpan linkSpan =
                     new ClickableSpan() {
@@ -211,7 +209,7 @@
                                             R.color.default_text_color_link_baseline));
                         }
                     };
-            mThirdPartyCookiesSummary.setSummary(
+            mTpSwitch.setSummary(
                     SpanApplier.applySpans(
                             getString(
                                     R.string.page_info_tracking_protection_site_grant_description),
@@ -219,16 +217,15 @@
             return;
         }
 
-        mCookieSwitch.setVisible(controlsVisible);
-        mThirdPartyCookiesTitle.setVisible(controlsVisible);
-        mThirdPartyCookiesSummary.setVisible(controlsVisible);
+        mTpSwitch.setVisible(controlsVisible);
+        mTpTitle.setVisible(controlsVisible);
 
         if (!controlsVisible) return;
 
-        mCookieSwitch.setChecked(protectionsOn);
+        mTpSwitch.setChecked(protectionsOn);
         mTpStatus.setTrackingProtectionStatus(protectionsOn);
-        mCookieSwitch.setEnabled(!isEnforced);
-        mCookieSwitch.setManagedPreferenceDelegate(
+        mTpSwitch.setEnabled(!isEnforced);
+        mTpSwitch.setManagedPreferenceDelegate(
                 new ForwardingManagedPreferenceDelegate(
                         getSiteSettingsDelegate().getManagedPreferenceDelegate()) {
                     @Override
@@ -247,19 +244,13 @@
                         });
 
         if (protectionsOn) {
-            mThirdPartyCookiesTitle.setTitle(
-                    getString(R.string.page_info_tracking_protection_title_on));
-            int resId =
-                    willCreatePermanentException()
-                            ? R.string.page_info_cookies_site_not_working_description_permanent
-                            : R.string
-                                    .page_info_cookies_site_not_working_description_tracking_protection;
-            mThirdPartyCookiesSummary.setSummary(getString(resId));
+            mTpTitle.setTitle(getString(R.string.page_info_tracking_protection_title_on));
+            mTpSwitch.setSummary(R.string.page_info_tracking_protection_toggle_on);
         } else if (permanentException) {
-            mThirdPartyCookiesTitle.setTitle(
+            mTpTitle.setTitle(
                     getString(R.string.page_info_tracking_protection_title_off_permanent));
             int resId = R.string.page_info_cookies_tracking_protection_description;
-            mThirdPartyCookiesSummary.setSummary(
+            mTpSwitch.setSummary(
                     SpanApplier.applySpans(
                             getString(resId),
                             new SpanApplier.SpanInfo("<link>", "</link>", feedbackSpan)));
@@ -271,7 +262,7 @@
                                     TimeUtils.currentTimeMillis(), expiration);
             updateTrackingProtectionTitleTemporary(days);
             int resId = R.string.page_info_cookies_tracking_protection_description;
-            mThirdPartyCookiesSummary.setSummary(
+            mTpSwitch.setSummary(
                     SpanApplier.applySpans(
                             getString(resId),
                             new SpanApplier.SpanInfo("<link>", "</link>", feedbackSpan)));
@@ -359,7 +350,11 @@
     }
 
     private void updateTrackingProtectionTitleTemporary(int days) {
-        mThirdPartyCookiesTitle.setTitle(
+        if (days == 0) {
+            mTpTitle.setTitle(getString(R.string.page_info_tracking_protection_title_off_today));
+            return;
+        }
+        mTpTitle.setTitle(
                 getQuantityString(R.plurals.page_info_tracking_protection_title_off, days));
     }
 
diff --git a/components/page_info/android/java/src/org/chromium/components/page_info/TrackingProtectionStatusPreference.java b/components/page_info/android/java/src/org/chromium/components/page_info/TrackingProtectionStatusPreference.java
index d1ed6cd..35ab9b8e 100644
--- a/components/page_info/android/java/src/org/chromium/components/page_info/TrackingProtectionStatusPreference.java
+++ b/components/page_info/android/java/src/org/chromium/components/page_info/TrackingProtectionStatusPreference.java
@@ -41,19 +41,17 @@
         mStatus = enabled;
         if (mCookieStatus == null) return;
 
-        // TODO(b/330745124): Use the actual icons.
         Drawable cookieIcon =
                 AppCompatResources.getDrawable(
-                        getContext(),
-                        enabled ? R.drawable.ic_eye_crossed : R.drawable.ic_cookie_24dp);
+                        getContext(), enabled ? R.drawable.tp_cookie_off : R.drawable.tp_cookie);
         Drawable ipIcon =
                 AppCompatResources.getDrawable(
                         getContext(),
-                        enabled ? R.drawable.ic_eye_crossed : R.drawable.ic_cookie_24dp);
+                        enabled ? R.drawable.tp_ip_protection_on : R.drawable.tp_ip_protection_off);
         Drawable fingerprintIcon =
                 AppCompatResources.getDrawable(
                         getContext(),
-                        enabled ? R.drawable.ic_eye_crossed : R.drawable.ic_cookie_24dp);
+                        enabled ? R.drawable.tp_fingerprint_off : R.drawable.tp_fingerprint);
 
         // TODO(b/330745124): Use the actual strings.
         mCookieStatus.setText(
diff --git a/components/password_manager/core/browser/BUILD.gn b/components/password_manager/core/browser/BUILD.gn
index 2920c44..eca9ed8 100644
--- a/components/password_manager/core/browser/BUILD.gn
+++ b/components/password_manager/core/browser/BUILD.gn
@@ -350,6 +350,8 @@
     "mock_password_form_cache.h",
     "mock_password_form_manager_for_ui.cc",
     "mock_password_form_manager_for_ui.h",
+    "mock_password_manager.cc",
+    "mock_password_manager.h",
     "mock_password_manager_settings_service.cc",
     "mock_password_manager_settings_service.h",
     "mock_password_reuse_manager.cc",
diff --git a/components/password_manager/core/browser/mock_password_manager.cc b/components/password_manager/core/browser/mock_password_manager.cc
new file mode 100644
index 0000000..30cfeba
--- /dev/null
+++ b/components/password_manager/core/browser/mock_password_manager.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 "components/password_manager/core/browser/mock_password_manager.h"
+
+namespace password_manager {
+
+MockPasswordManager::MockPasswordManager() = default;
+MockPasswordManager::~MockPasswordManager() = default;
+
+}  // namespace password_manager
diff --git a/components/password_manager/core/browser/mock_password_manager.h b/components/password_manager/core/browser/mock_password_manager.h
new file mode 100644
index 0000000..b8bc93f
--- /dev/null
+++ b/components/password_manager/core/browser/mock_password_manager.h
@@ -0,0 +1,99 @@
+// 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_PASSWORD_MANAGER_CORE_BROWSER_MOCK_PASSWORD_MANAGER_H_
+#define COMPONENTS_PASSWORD_MANAGER_CORE_BROWSER_MOCK_PASSWORD_MANAGER_H_
+
+#include "components/password_manager/core/browser/password_manager_interface.h"
+#include "testing/gmock/include/gmock/gmock.h"
+
+namespace password_manager {
+class MockPasswordManager : public password_manager::PasswordManagerInterface {
+ public:
+  MockPasswordManager();
+  ~MockPasswordManager() override;
+
+  MockPasswordManager(const MockPasswordManager&) = delete;
+  MockPasswordManager& operator=(const MockPasswordManager&) = delete;
+
+  // FormSubmissionObserver:
+  MOCK_METHOD(void,
+              DidNavigateMainFrame,
+              (bool form_may_be_submitted),
+              (override));
+
+  // PasswordManagerInterface:
+  MOCK_METHOD(void,
+              OnPasswordFormsParsed,
+              (PasswordManagerDriver*, const std::vector<autofill::FormData>&),
+              (override));
+  MOCK_METHOD(void,
+              OnPasswordFormsRendered,
+              (PasswordManagerDriver*, const std::vector<autofill::FormData>&),
+              (override));
+  MOCK_METHOD(void,
+              OnPasswordFormSubmitted,
+              (PasswordManagerDriver*, const autofill::FormData&),
+              (override));
+  MOCK_METHOD(void,
+              OnPasswordFormCleared,
+              (PasswordManagerDriver*, const autofill::FormData&),
+              (override));
+  MOCK_METHOD(void,
+              SetGenerationElementAndTypeForForm,
+              (PasswordManagerDriver*,
+               autofill::FormRendererId,
+               autofill::FieldRendererId,
+               autofill::password_generation::PasswordGenerationType),
+              (override));
+  MOCK_METHOD(void,
+              OnPresaveGeneratedPassword,
+              (PasswordManagerDriver*,
+               const autofill::FormData&,
+               const std::u16string&),
+              (override));
+  MOCK_METHOD(
+      void,
+      ProcessAutofillPredictions,
+      (PasswordManagerDriver*,
+       const autofill::FormData&,
+       (const base::flat_map<autofill::FieldGlobalId,
+                             autofill::AutofillType::ServerPrediction>&)),
+      (override));
+  MOCK_METHOD(PasswordManagerClient*, GetClient, (), (override));
+#if BUILDFLAG(IS_IOS)
+  MOCK_METHOD(void,
+              OnSubframeFormSubmission,
+              (PasswordManagerDriver*, const autofill::FormData&),
+              (override));
+  MOCK_METHOD(void,
+              UpdateStateOnUserInput,
+              (password_manager::PasswordManagerDriver*,
+               autofill::FormRendererId,
+               autofill::FieldRendererId,
+               const std::u16string&),
+              (override));
+  MOCK_METHOD(void, OnPasswordNoLongerGenerated, (), (override));
+  MOCK_METHOD(void,
+              OnPasswordFormsRemoved,
+              (PasswordManagerDriver*,
+               const autofill::FieldDataManager&,
+               const std::set<autofill::FormRendererId>&,
+               const std::set<autofill::FieldRendererId>&),
+              (override));
+  MOCK_METHOD(void,
+              OnIframeDetach,
+              (const std::string&,
+               PasswordManagerDriver*,
+               const autofill::FieldDataManager&),
+              (override));
+  MOCK_METHOD(void,
+              PropagateFieldDataManagerInfo,
+              (const autofill::FieldDataManager&, const PasswordManagerDriver*),
+              (override));
+#endif
+};
+}  // namespace password_manager
+
+#endif  // COMPONENTS_PASSWORD_MANAGER_CORE_BROWSER_MOCK_PASSWORD_MANAGER_H_
diff --git a/components/password_manager/ios/password_form_helper.mm b/components/password_manager/ios/password_form_helper.mm
index a99afbe..f0a0d5e 100644
--- a/components/password_manager/ios/password_form_helper.mm
+++ b/components/password_manager/ios/password_form_helper.mm
@@ -24,6 +24,8 @@
 #import "components/autofill/ios/common/field_data_manager_factory_ios.h"
 #import "components/autofill/ios/form_util/form_util_java_script_feature.h"
 #import "components/password_manager/ios/account_select_fill_data.h"
+#import "components/password_manager/ios/ios_password_manager_driver.h"
+#import "components/password_manager/ios/ios_password_manager_driver_factory.h"
 #import "components/password_manager/ios/password_manager_ios_util.h"
 #import "components/password_manager/ios/password_manager_java_script_feature.h"
 #import "components/password_manager/ios/password_manager_tab_helper.h"
@@ -248,7 +250,8 @@
 // operation is considered as a success.
 - (BOOL)handleFillResult:(const base::Value*)result
             fromFillData:(password_manager::FillData)fillData
-    withFieldDataManager:(autofill::FieldDataManager*)manager {
+    withFieldDataManager:(autofill::FieldDataManager*)manager
+                  driver:(IOSPasswordManagerDriver*)driver {
   if (!result->is_dict()) {
     return NO;
   }
@@ -271,11 +274,18 @@
     manager->UpdateFieldDataMap(fillData.username_element_id,
                                 fillData.username_value,
                                 FieldPropertiesFlags::kAutofilledOnUserTrigger);
+
+    driver->GetPasswordManager()->UpdateStateOnUserInput(
+        driver, fillData.form_id, fillData.username_element_id,
+        fillData.username_value);
   }
   if (fillData.password_element_id && success && *did_fill_password) {
     manager->UpdateFieldDataMap(fillData.password_element_id,
                                 fillData.password_value,
                                 FieldPropertiesFlags::kAutofilledOnUserTrigger);
+    driver->GetPasswordManager()->UpdateStateOnUserInput(
+        driver, fillData.form_id, fillData.password_element_id,
+        fillData.password_value);
   }
 
   return success;
@@ -288,6 +298,8 @@
                        (nullable void (^)(BOOL))completionHandler {
   const scoped_refptr<autofill::FieldDataManager> fieldDataManager =
       autofill::FieldDataManagerFactoryIOS::GetRetainable(frame);
+  const scoped_refptr<IOSPasswordManagerDriver> driver =
+      IOSPasswordManagerDriverFactory::GetRetainableDriver(_webState, frame);
 
   // Do not fill the username if filling was triggered on a password field and
   // the username field has user typed input.
@@ -300,10 +312,11 @@
                          UTF16ToUTF8(fillData.username_value),
                          UTF16ToUTF8(fillData.password_value),
                          base::BindOnce(^(const base::Value* result) {
-                           const BOOL success = [weakSelf
-                                   handleFillResult:result
-                                       fromFillData:fillData
-                               withFieldDataManager:fieldDataManager.get()];
+                           const BOOL success =
+                               [weakSelf handleFillResult:result
+                                             fromFillData:fillData
+                                     withFieldDataManager:fieldDataManager.get()
+                                                   driver:driver.get()];
 
                            if (completionHandler) {
                              completionHandler(success);
diff --git a/components/password_manager/ios/password_form_helper_unittest.mm b/components/password_manager/ios/password_form_helper_unittest.mm
index 84adaf62..18df0f5 100644
--- a/components/password_manager/ios/password_form_helper_unittest.mm
+++ b/components/password_manager/ios/password_form_helper_unittest.mm
@@ -6,6 +6,8 @@
 
 #import <stddef.h>
 
+#import <string>
+
 #import "base/apple/bundle_locations.h"
 #import "base/strings/sys_string_conversions.h"
 #import "base/strings/utf_string_conversions.h"
@@ -16,13 +18,19 @@
 #import "components/autofill/core/common/field_data_manager.h"
 #import "components/autofill/core/common/form_data.h"
 #import "components/autofill/core/common/password_form_fill_data.h"
+#import "components/autofill/core/common/unique_ids.h"
 #import "components/autofill/ios/browser/autofill_java_script_feature.h"
 #import "components/autofill/ios/common/field_data_manager_factory_ios.h"
 #import "components/autofill/ios/form_util/autofill_test_with_web_state.h"
 #import "components/autofill/ios/form_util/form_handlers_java_script_feature.h"
 #import "components/autofill/ios/form_util/form_util_java_script_feature.h"
+#import "components/password_manager/core/browser/mock_password_manager.h"
+#import "components/password_manager/core/browser/password_manager_driver.h"
+#import "components/password_manager/core/browser/password_manager_interface.h"
 #import "components/password_manager/ios/account_select_fill_data.h"
+#import "components/password_manager/ios/ios_password_manager_driver_factory.h"
 #import "components/password_manager/ios/password_manager_java_script_feature.h"
+#import "components/password_manager/ios/shared_password_controller.h"
 #import "components/password_manager/ios/test_helpers.h"
 #import "components/ukm/ios/ukm_url_recorder.h"
 #import "components/ukm/test_ukm_recorder.h"
@@ -91,6 +99,10 @@
   void SetUp() override {
     WebTestWithWebState::SetUp();
 
+    IOSPasswordManagerDriverFactory::CreateForWebState(
+        web_state(), OCMStrictClassMock([SharedPasswordController class]),
+        &password_manager_);
+
     helper_ = [[PasswordFormHelper alloc] initWithWebState:web_state()];
     ukm::InitializeSourceUrlRecorderForWebState(web_state());
   }
@@ -158,6 +170,7 @@
  protected:
   // PasswordFormHelper for testing.
   PasswordFormHelper* helper_;
+  password_manager::MockPasswordManager password_manager_;
 };
 
 struct FindPasswordFormTestData {
@@ -294,16 +307,35 @@
 
   ASSERT_TRUE(SetUpUniqueIDs());
   const std::string base_url = BaseUrl();
+  FormRendererId form_id(1);
   FieldRendererId username_field_id(2);
+  const std::u16string username_value = u"john.doe@gmail.com";
   FieldRendererId password_field_id(3);
+  const std::u16string password_value = u"super!secret";
   FillData fill_data;
-  SetFillData(base_url, 1, username_field_id.value(), "john.doe@gmail.com",
-              password_field_id.value(), "super!secret", &fill_data);
+  SetFillData(base_url, form_id.value(), username_field_id.value(),
+              base::UTF16ToUTF8(username_value).c_str(),
+              password_field_id.value(),
+              base::UTF16ToUTF8(password_value).c_str(), &fill_data);
+
+  web::WebFrame* frame = GetMainFrame();
+
+  // Expect calls to the PasswordManager to update its state from the filled
+  // inputs.
+  IOSPasswordManagerDriver* driver =
+      IOSPasswordManagerDriverFactory::FromWebStateAndWebFrame(web_state(),
+                                                               frame);
+  EXPECT_CALL(password_manager_,
+              UpdateStateOnUserInput(driver, form_id, username_field_id,
+                                     username_value));
+  EXPECT_CALL(password_manager_,
+              UpdateStateOnUserInput(driver, form_id, password_field_id,
+                                     password_value));
 
   __block bool called = false;
   __block BOOL succeeded = false;
   [helper_ fillPasswordFormWithFillData:fill_data
-                                inFrame:GetMainFrame()
+                                inFrame:frame
                        triggeredOnField:username_field_id
                       completionHandler:^(BOOL success) {
                         called = true;
@@ -357,6 +389,10 @@
 
   ASSERT_TRUE(SetUpUniqueIDs());
 
+  // Don't expect calls to the PasswordManager to update its state when failure
+  // to fill.
+  EXPECT_CALL(password_manager_, UpdateStateOnUserInput).Times(0);
+
   const std::string base_url = BaseUrl();
   FieldRendererId username_field_id(2);
   FieldRendererId password_field_id(3);
@@ -412,9 +448,16 @@
 TEST_F(PasswordFormHelperTest, FillPasswordFormWithFillData_Failure) {
   ukm::TestAutoSetUkmRecorder test_recorder;
   base::HistogramTester histogram_tester;
+
   LoadHtml(@"<form><input id='p1' type='password' name='pw1'></form>");
+  web::WebFrame* frame = GetMainFrame();
 
   ASSERT_TRUE(SetUpUniqueIDs());
+
+  // Don't expect calls to the PasswordManager to update its state when failure
+  // to fill.
+  EXPECT_CALL(password_manager_, UpdateStateOnUserInput).Times(0);
+
   const std::string base_url = BaseUrl();
   FieldRendererId username_field_id(0);
   // The password renderer id does not exist, that's why the filling will fail
@@ -426,7 +469,7 @@
   __block bool called = false;
   __block BOOL succeeded = false;
   [helper_ fillPasswordFormWithFillData:fill_data
-                                inFrame:GetMainFrame()
+                                inFrame:frame
                        triggeredOnField:username_field_id
                       completionHandler:^(BOOL success) {
                         called = true;
@@ -444,7 +487,7 @@
   // Check that username and password fields were NOT updated as filled in the
   // FieldDataManager.
   autofill::FieldDataManager* fieldDataManager =
-      autofill::FieldDataManagerFactoryIOS::FromWebFrame(GetMainFrame());
+      autofill::FieldDataManagerFactoryIOS::FromWebFrame(frame);
   EXPECT_FALSE(fieldDataManager->WasAutofilledOnUserTrigger(username_field_id));
   EXPECT_FALSE(fieldDataManager->WasAutofilledOnUserTrigger(password_field_id));
 
@@ -468,8 +511,13 @@
 
   ASSERT_TRUE(SetUpUniqueIDs());
 
+  FormRendererId form_id(1);
   FieldRendererId username_field_id(2);
+  const std::u16string username_value = u"john.doe@gmail.com";
   FieldRendererId password_field_id(3);
+  const std::u16string password_value = u"store!pw";
+
+  web::WebFrame* frame = GetMainFrame();
 
   // Type on username field.
   ExecuteJavaScript(
@@ -480,8 +528,24 @@
 
   // Try to autofill the form.
   FillData fill_data;
-  SetFillData(BaseUrl(), 1, username_field_id.value(), "someacc@store.com",
-              password_field_id.value(), "store!pw", &fill_data);
+  SetFillData(BaseUrl(), form_id.value(), username_field_id.value(),
+              base::UTF16ToUTF8(username_value).c_str(),
+              password_field_id.value(),
+              base::UTF16ToUTF8(password_value).c_str(), &fill_data);
+
+  IOSPasswordManagerDriver* driver =
+      IOSPasswordManagerDriverFactory::FromWebStateAndWebFrame(web_state(),
+                                                               frame);
+  // Don't expect to update the state for the username field because it was
+  // skipped.
+  EXPECT_CALL(password_manager_,
+              UpdateStateOnUserInput(driver, form_id, username_field_id,
+                                     username_value))
+      .Times(0);
+  // Expect a state update on the password field.
+  EXPECT_CALL(password_manager_,
+              UpdateStateOnUserInput(driver, form_id, password_field_id,
+                                     password_value));
 
   __block bool called = NO;
   __block bool succeeded = NO;
@@ -595,6 +659,13 @@
   auto* main_frame_ptr = main_frame.get();
   web_frames_manager_ptr->AddWebFrame(std::move(main_frame));
 
+  IOSPasswordManagerDriverFactory::CreateForWebState(
+      &fake_web_state, OCMStrictClassMock([SharedPasswordController class]),
+      &password_manager_);
+  // Don't expect calls to the PasswordManager to update its state when failure
+  // to fill.
+  EXPECT_CALL(password_manager_, UpdateStateOnUserInput).Times(0);
+
   FieldRendererId username_field_id(2);
   FieldRendererId password_field_id(3);
   FillData fill_data;
diff --git a/components/password_manager/ios/resources/password_controller.ts b/components/password_manager/ios/resources/password_controller.ts
index 076dbd8..b98174c 100644
--- a/components/password_manager/ios/resources/password_controller.ts
+++ b/components/password_manager/ios/resources/password_controller.ts
@@ -316,9 +316,8 @@
     }
   }
 
-  const isUsernameEditable: boolean =
-      (!!usernameInput && !usernameInput.readOnly && !usernameInput.disabled) as
-      boolean;
+  const isUsernameEditable: boolean = Boolean(
+      !!usernameInput && !usernameInput.readOnly && !usernameInput.disabled);
 
   // Fill the username if needed and if it doesn't look like it was already
   // pre-filled by the website.
@@ -327,8 +326,10 @@
        gCrWeb.fill.setInputElementValue(username, usernameInput)) as boolean;
 
   // Fill the password if needed.
-  const didFillPassword: boolean = !!passwordInput &&
-      gCrWeb.fill.setInputElementValue(password, passwordInput) as boolean;
+  const didFillPassword: boolean =
+      Boolean(
+          !!passwordInput &&
+          gCrWeb.fill.setInputElementValue(password, passwordInput)) as boolean;
 
   return {
     didFillUsername,
diff --git a/components/password_manager/ios/shared_password_controller.mm b/components/password_manager/ios/shared_password_controller.mm
index 238112b..c7e3eb44 100644
--- a/components/password_manager/ios/shared_password_controller.mm
+++ b/components/password_manager/ios/shared_password_controller.mm
@@ -4,61 +4,61 @@
 
 #import "components/password_manager/ios/shared_password_controller.h"
 
-#include <stddef.h>
+#import <stddef.h>
 
-#include <algorithm>
-#include <map>
-#include <memory>
-#include <string>
-#include <utility>
-#include <vector>
+#import <algorithm>
+#import <map>
+#import <memory>
+#import <string>
+#import <utility>
+#import <vector>
 
-#include "base/apple/foundation_util.h"
-#include "base/feature_list.h"
-#include "base/functional/bind.h"
+#import "base/apple/foundation_util.h"
+#import "base/feature_list.h"
+#import "base/functional/bind.h"
 #import "base/memory/raw_ptr.h"
-#include "base/metrics/histogram_macros.h"
-#include "base/scoped_multi_source_observation.h"
-#include "base/strings/sys_string_conversions.h"
-#include "base/strings/utf_string_conversions.h"
-#include "base/values.h"
-#include "components/autofill/core/browser/filling_product.h"
-#include "components/autofill/core/browser/form_structure.h"
-#include "components/autofill/core/browser/ui/suggestion_type.h"
-#include "components/autofill/core/common/autofill_features.h"
-#include "components/autofill/core/common/form_data.h"
-#include "components/autofill/core/common/password_form_fill_data.h"
-#include "components/autofill/core/common/password_form_generation_data.h"
-#include "components/autofill/core/common/password_generation_util.h"
-#include "components/autofill/core/common/signatures.h"
-#include "components/autofill/core/common/unique_ids.h"
+#import "base/metrics/histogram_macros.h"
+#import "base/scoped_multi_source_observation.h"
+#import "base/strings/sys_string_conversions.h"
+#import "base/strings/utf_string_conversions.h"
+#import "base/values.h"
+#import "components/autofill/core/browser/filling_product.h"
+#import "components/autofill/core/browser/form_structure.h"
+#import "components/autofill/core/browser/ui/suggestion_type.h"
+#import "components/autofill/core/common/autofill_features.h"
+#import "components/autofill/core/common/form_data.h"
+#import "components/autofill/core/common/password_form_fill_data.h"
+#import "components/autofill/core/common/password_form_generation_data.h"
+#import "components/autofill/core/common/password_generation_util.h"
+#import "components/autofill/core/common/signatures.h"
+#import "components/autofill/core/common/unique_ids.h"
 #import "components/autofill/ios/browser/autofill_driver_ios.h"
 #import "components/autofill/ios/browser/autofill_manager_observer_bridge.h"
-#include "components/autofill/ios/browser/autofill_util.h"
+#import "components/autofill/ios/browser/autofill_util.h"
 #import "components/autofill/ios/browser/form_suggestion_provider_query.h"
+#import "components/autofill/ios/browser/password_autofill_agent.h"
 #import "components/autofill/ios/form_util/form_activity_observer_bridge.h"
-#include "components/autofill/ios/form_util/form_activity_params.h"
-#include "components/password_manager/core/browser/password_bubble_experiment.h"
+#import "components/autofill/ios/form_util/form_activity_params.h"
+#import "components/password_manager/core/browser/password_bubble_experiment.h"
 #import "components/password_manager/core/browser/password_feature_manager.h"
-#include "components/password_manager/core/browser/password_generation_frame_helper.h"
-#include "components/password_manager/core/browser/password_manager_client.h"
-#include "components/password_manager/core/common/password_manager_features.h"
-#include "components/password_manager/ios/account_select_fill_data.h"
-#include "components/password_manager/ios/ios_password_manager_driver_factory.h"
+#import "components/password_manager/core/browser/password_generation_frame_helper.h"
+#import "components/password_manager/core/browser/password_manager_client.h"
+#import "components/password_manager/core/common/password_manager_features.h"
+#import "components/password_manager/ios/account_select_fill_data.h"
 #import "components/password_manager/ios/ios_password_manager_driver_factory.h"
-#include "components/password_manager/ios/password_manager_ios_util.h"
+#import "components/password_manager/ios/password_manager_ios_util.h"
 #import "components/password_manager/ios/password_manager_java_script_feature.h"
 #import "components/password_manager/ios/shared_password_controller+private.h"
-#include "components/strings/grit/components_strings.h"
-#include "ios/web/common/url_scheme_util.h"
-#include "ios/web/public/js_messaging/web_frame.h"
+#import "components/strings/grit/components_strings.h"
+#import "ios/web/common/url_scheme_util.h"
+#import "ios/web/public/js_messaging/web_frame.h"
 #import "ios/web/public/js_messaging/web_frames_manager.h"
 #import "ios/web/public/js_messaging/web_frames_manager_observer_bridge.h"
-#include "ios/web/public/navigation/navigation_context.h"
+#import "ios/web/public/navigation/navigation_context.h"
 #import "ios/web/public/web_state.h"
-#include "services/network/public/cpp/shared_url_loader_factory.h"
-#include "ui/base/l10n/l10n_util_mac.h"
-#include "url/gurl.h"
+#import "services/network/public/cpp/shared_url_loader_factory.h"
+#import "ui/base/l10n/l10n_util_mac.h"
+#import "url/gurl.h"
 
 using autofill::AutofillManager;
 using autofill::AutofillManagerObserverBridge;
@@ -93,6 +93,33 @@
 // Password is considered not generated when user edits it below 4 characters.
 constexpr int kMinimumLengthForEditedPassword = 4;
 
+class PasswordAutofillAgentDelegateImpl
+    : public autofill::PasswordAutofillAgentDelegate {
+ public:
+  ~PasswordAutofillAgentDelegateImpl() override = default;
+  explicit PasswordAutofillAgentDelegateImpl(web::WebState* web_state)
+      : web_state_(web_state) {}
+
+  PasswordAutofillAgentDelegateImpl(const PasswordAutofillAgentDelegateImpl&) =
+      delete;
+  PasswordAutofillAgentDelegateImpl& operator=(
+      const PasswordAutofillAgentDelegateImpl&) = delete;
+
+  void DidFillField(web::WebFrame* frame,
+                    autofill::FormRendererId form_id,
+                    autofill::FieldRendererId field_id,
+                    const std::u16string& field_value) override {
+    auto* driver = IOSPasswordManagerDriverFactory::FromWebStateAndWebFrame(
+        web_state_, frame);
+    CHECK(driver);
+    driver->GetPasswordManager()->UpdateStateOnUserInput(driver, form_id,
+                                                         field_id, field_value);
+  }
+
+ private:
+  web::WebState* web_state_;
+};
+
 }  // namespace
 
 NSString* const kPasswordFormSuggestionSuffix = @" ••••••••";
@@ -160,6 +187,10 @@
   // be deleted with the frame, and the driver needs to be alive after the
   // frame deletion for submission detecting purposes.
   scoped_refptr<IOSPasswordManagerDriver> _lastSubmittedPasswordManagerDriver;
+
+  // Delegate for the PasswordAutofillAgent that receives information from
+  // Autofill.
+  std::unique_ptr<PasswordAutofillAgentDelegateImpl> _agentDelegate;
 }
 
 - (instancetype)initWithWebState:(web::WebState*)webState
@@ -173,6 +204,11 @@
     DCHECK(webState);
     IOSPasswordManagerDriverFactory::CreateForWebState(webState, self,
                                                        passwordManager);
+    _agentDelegate =
+        std::make_unique<PasswordAutofillAgentDelegateImpl>(webState);
+    autofill::PasswordAutofillAgent::CreateForWebState(webState,
+                                                       _agentDelegate.get());
+
     _webState = webState;
     _webStateObserverBridge =
         std::make_unique<web::WebStateObserverBridge>(self);
@@ -357,6 +393,7 @@
   _lastFocusedFrame = nullptr;
   _passwordManager = nullptr;
   _lastSubmittedPasswordManagerDriver = nullptr;
+  _agentDelegate.reset();
 }
 
 #pragma mark - AutofillManagerObserver
diff --git a/components/password_manager/ios/shared_password_controller_unittest.mm b/components/password_manager/ios/shared_password_controller_unittest.mm
index 3358d79..2ce9794 100644
--- a/components/password_manager/ios/shared_password_controller_unittest.mm
+++ b/components/password_manager/ios/shared_password_controller_unittest.mm
@@ -18,8 +18,10 @@
 #import "components/autofill/ios/browser/autofill_driver_ios_factory.h"
 #import "components/autofill/ios/browser/form_suggestion.h"
 #import "components/autofill/ios/browser/form_suggestion_provider_query.h"
+#import "components/autofill/ios/browser/password_autofill_agent.h"
 #import "components/autofill/ios/browser/test_autofill_manager_injector.h"
 #import "components/autofill/ios/form_util/form_activity_params.h"
+#import "components/password_manager/core/browser/mock_password_manager.h"
 #import "components/password_manager/core/browser/password_generation_frame_helper.h"
 #import "components/password_manager/core/browser/password_manager_interface.h"
 #import "components/password_manager/core/browser/stub_password_manager_client.h"
@@ -70,77 +72,6 @@
 NSString* kTestFrameID = @"dummy-frame-id";
 
 }  // namespace
-class MockPasswordManager : public PasswordManagerInterface {
- public:
-  MOCK_METHOD(void, DidNavigateMainFrame, (bool), (override));
-  MOCK_METHOD(void,
-              OnPasswordFormsParsed,
-              (PasswordManagerDriver*, const std::vector<autofill::FormData>&),
-              (override));
-  MOCK_METHOD(void,
-              OnPasswordFormsRendered,
-              (PasswordManagerDriver*, const std::vector<autofill::FormData>&),
-              (override));
-  MOCK_METHOD(void,
-              OnPasswordFormSubmitted,
-              (PasswordManagerDriver*, const autofill::FormData&),
-              (override));
-  MOCK_METHOD(void,
-              OnPasswordFormCleared,
-              (PasswordManagerDriver*, const autofill::FormData&),
-              (override));
-  MOCK_METHOD(void,
-              SetGenerationElementAndTypeForForm,
-              (PasswordManagerDriver*,
-               autofill::FormRendererId,
-               autofill::FieldRendererId,
-               autofill::password_generation::PasswordGenerationType),
-              (override));
-  MOCK_METHOD(void,
-              OnPresaveGeneratedPassword,
-              (PasswordManagerDriver*,
-               const autofill::FormData&,
-               const std::u16string&),
-              (override));
-  MOCK_METHOD(void,
-              OnSubframeFormSubmission,
-              (PasswordManagerDriver*, const autofill::FormData&),
-              (override));
-  MOCK_METHOD(void,
-              UpdateStateOnUserInput,
-              (PasswordManagerDriver*,
-               autofill::FormRendererId,
-               autofill::FieldRendererId,
-               const std::u16string&),
-              (override));
-  MOCK_METHOD(void, OnPasswordNoLongerGenerated, (), (override));
-  MOCK_METHOD(void,
-              OnPasswordFormsRemoved,
-              (PasswordManagerDriver*,
-               const autofill::FieldDataManager&,
-               const std::set<autofill::FormRendererId>&,
-               const std::set<autofill::FieldRendererId>&),
-              (override));
-  MOCK_METHOD(void,
-              OnIframeDetach,
-              (const std::string&,
-               PasswordManagerDriver*,
-               const autofill::FieldDataManager&),
-              (override));
-  MOCK_METHOD(void,
-              PropagateFieldDataManagerInfo,
-              (const autofill::FieldDataManager&, const PasswordManagerDriver*),
-              (override));
-  MOCK_METHOD(
-      void,
-      ProcessAutofillPredictions,
-      (PasswordManagerDriver * driver,
-       const autofill::FormData&,
-       (const base::flat_map<autofill::FieldGlobalId,
-                             autofill::AutofillType::ServerPrediction>)&),
-      (override));
-  MOCK_METHOD(PasswordManagerClient*, GetClient, (), (override));
-};
 
 class MockPasswordGenerationFrameHelper : public PasswordGenerationFrameHelper {
  public:
@@ -1489,6 +1420,25 @@
                  completionHandler:nil];
 }
 
+// Tests that upon calling DidFillField() on the agent, the delegate implemented
+// and owned by the SharedPasswordController correctly calls the password
+// manager to update its state.
+TEST_F(SharedPasswordControllerTest, DidFillField) {
+  GURL url("https://example.com");
+  auto frame = web::FakeWebFrame::Create("frameID", true, url);
+  autofill::FormRendererId form_id(1);
+  autofill::FieldRendererId field_id(2);
+  const std::u16string value(u"value");
+  auto* driver = IOSPasswordManagerDriverFactory::FromWebStateAndWebFrame(
+      &web_state_, frame.get());
+
+  EXPECT_CALL(password_manager_,
+              UpdateStateOnUserInput(driver, form_id, field_id, value));
+
+  auto* agent = autofill::PasswordAutofillAgent::FromWebState(&web_state_);
+  agent->DidFillField(frame.get(), form_id, field_id, value);
+}
+
 // TODO(crbug.com/40701292): Finish unit testing the rest of the public API.
 
 }  // namespace password_manager
diff --git a/components/performance_manager/BUILD.gn b/components/performance_manager/BUILD.gn
index 57e57cc6..ce46cad 100644
--- a/components/performance_manager/BUILD.gn
+++ b/components/performance_manager/BUILD.gn
@@ -52,6 +52,8 @@
     "execution_context_priority/frame_visibility_voter.h",
     "execution_context_priority/inherit_client_priority_voter.cc",
     "execution_context_priority/inherit_client_priority_voter.h",
+    "execution_context_priority/loading_page_voter.cc",
+    "execution_context_priority/loading_page_voter.h",
     "execution_context_priority/max_vote_aggregator.cc",
     "execution_context_priority/max_vote_aggregator.h",
     "execution_context_priority/override_vote_aggregator.cc",
@@ -348,6 +350,7 @@
     "execution_context_priority/frame_capturing_media_stream_voter_unittest.cc",
     "execution_context_priority/frame_visibility_voter_unittest.cc",
     "execution_context_priority/inherit_client_priority_voter_unittest.cc",
+    "execution_context_priority/loading_page_voter_unittest.cc",
     "execution_context_priority/max_vote_aggregator_unittest.cc",
     "execution_context_priority/override_vote_aggregator_unittest.cc",
     "execution_context_priority/root_vote_observer_unittest.cc",
diff --git a/components/performance_manager/execution_context_priority/execution_context_priority_decorator.cc b/components/performance_manager/execution_context_priority/execution_context_priority_decorator.cc
index c5dfc4e..3a6fdf0 100644
--- a/components/performance_manager/execution_context_priority/execution_context_priority_decorator.cc
+++ b/components/performance_manager/execution_context_priority/execution_context_priority_decorator.cc
@@ -4,6 +4,7 @@
 
 #include "components/performance_manager/execution_context_priority/execution_context_priority_decorator.h"
 
+#include "base/feature_list.h"
 #include "components/performance_manager/public/execution_context/execution_context_registry.h"
 #include "components/performance_manager/public/features.h"
 
@@ -61,6 +62,7 @@
       max_vote_aggregator_.GetVotingChannel());
   inherit_client_priority_voter_.SetVotingChannel(
       max_vote_aggregator_.GetVotingChannel());
+  loading_page_voter_.SetVotingChannel(max_vote_aggregator_.GetVotingChannel());
 }
 
 ExecutionContextPriorityDecorator::~ExecutionContextPriorityDecorator() =
@@ -76,6 +78,10 @@
   graph->AddInitializingFrameNodeObserver(&frame_capturing_media_stream_voter_);
   graph->AddFrameNodeObserver(&inherit_client_priority_voter_);
   graph->AddWorkerNodeObserver(&inherit_client_priority_voter_);
+  if (base::FeatureList::IsEnabled(features::kPMLoadingPageVoter)) {
+    graph->AddPageNodeObserver(&loading_page_voter_);
+    graph->AddInitializingFrameNodeObserver(&loading_page_voter_);
+  }
 #if BUILDFLAG(IS_MAC)
   if (features::kBoostChildFrames.Get()) {
     graph->AddInitializingFrameNodeObserver(child_frame_booster_.get());
@@ -90,6 +96,10 @@
     graph->RemoveInitializingFrameNodeObserver(child_frame_booster_.get());
   }
 #endif
+  if (base::FeatureList::IsEnabled(features::kPMLoadingPageVoter)) {
+    graph->RemoveInitializingFrameNodeObserver(&loading_page_voter_);
+    graph->RemovePageNodeObserver(&loading_page_voter_);
+  }
   graph->RemoveWorkerNodeObserver(&inherit_client_priority_voter_);
   graph->RemoveFrameNodeObserver(&inherit_client_priority_voter_);
   graph->RemoveInitializingFrameNodeObserver(
diff --git a/components/performance_manager/execution_context_priority/execution_context_priority_decorator.h b/components/performance_manager/execution_context_priority/execution_context_priority_decorator.h
index 37646cb..b75128b 100644
--- a/components/performance_manager/execution_context_priority/execution_context_priority_decorator.h
+++ b/components/performance_manager/execution_context_priority/execution_context_priority_decorator.h
@@ -13,6 +13,7 @@
 #include "components/performance_manager/execution_context_priority/frame_capturing_media_stream_voter.h"
 #include "components/performance_manager/execution_context_priority/frame_visibility_voter.h"
 #include "components/performance_manager/execution_context_priority/inherit_client_priority_voter.h"
+#include "components/performance_manager/execution_context_priority/loading_page_voter.h"
 #include "components/performance_manager/execution_context_priority/max_vote_aggregator.h"
 #include "components/performance_manager/execution_context_priority/override_vote_aggregator.h"
 #include "components/performance_manager/execution_context_priority/root_vote_observer.h"
@@ -75,6 +76,9 @@
   // Casts a vote for each child worker with the client's priority.
   InheritClientPriorityVoter inherit_client_priority_voter_;
 
+  // Casts a USER_VISIBLE vote for all frames in a loading page.
+  LoadingPageVoter loading_page_voter_;
+
 #if BUILDFLAG(IS_MAC)
   //  Boosts the priority of non-ad child frames.
   std::unique_ptr<ChildFrameBooster> child_frame_booster_;
diff --git a/components/performance_manager/execution_context_priority/loading_page_voter.cc b/components/performance_manager/execution_context_priority/loading_page_voter.cc
new file mode 100644
index 0000000..569c719
--- /dev/null
+++ b/components/performance_manager/execution_context_priority/loading_page_voter.cc
@@ -0,0 +1,125 @@
+// 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/performance_manager/execution_context_priority/loading_page_voter.h"
+
+#include <utility>
+
+#include "components/performance_manager/public/execution_context/execution_context_registry.h"
+
+namespace performance_manager::execution_context_priority {
+
+namespace {
+
+const execution_context::ExecutionContext* GetExecutionContext(
+    const FrameNode* frame_node) {
+  return execution_context::ExecutionContextRegistry::GetFromGraph(
+             frame_node->GetGraph())
+      ->GetExecutionContextForFrameNode(frame_node);
+}
+
+// Returns true if `loading_state` represent an actively loading state.
+bool IsLoading(PageNode::LoadingState loading_state) {
+  return loading_state == PageNode::LoadingState::kLoading ||
+         loading_state == PageNode::LoadingState::kLoadedBusy;
+}
+
+}  // namespace
+
+// static
+const char LoadingPageVoter::kPageIsLoadingReason[] = "Page is loading.";
+
+LoadingPageVoter::LoadingPageVoter() = default;
+
+LoadingPageVoter::~LoadingPageVoter() = default;
+
+void LoadingPageVoter::SetVotingChannel(VotingChannel voting_channel) {
+  voting_channel_ = std::move(voting_channel);
+}
+
+void LoadingPageVoter::OnPageNodeAdded(const PageNode* page_node) {
+  if (!IsLoading(page_node->GetLoadingState())) {
+    return;
+  }
+
+  OnPageNodeStartedLoading(page_node);
+}
+
+void LoadingPageVoter::OnBeforePageNodeRemoved(const PageNode* page_node) {
+  if (!IsLoading(page_node->GetLoadingState())) {
+    return;
+  }
+
+  OnPageNodeStoppedLoading(page_node);
+}
+
+void LoadingPageVoter::OnLoadingStateChanged(
+    const PageNode* page_node,
+    PageNode::LoadingState previous_state) {
+  const bool was_loading = IsLoading(previous_state);
+  const bool is_loading = IsLoading(page_node->GetLoadingState());
+
+  if (was_loading && !is_loading) {
+    OnPageNodeStoppedLoading(page_node);
+  }
+  if (!was_loading && is_loading) {
+    OnPageNodeStartedLoading(page_node);
+  }
+}
+
+void LoadingPageVoter::OnFrameNodeInitializing(const FrameNode* frame_node) {
+  if (!IsLoading(frame_node->GetPageNode()->GetLoadingState())) {
+    return;
+  }
+
+  voting_channel_.SubmitVote(
+      GetExecutionContext(frame_node),
+      Vote(base::TaskPriority::USER_VISIBLE, kPageIsLoadingReason));
+}
+
+void LoadingPageVoter::OnFrameNodeTearingDown(const FrameNode* frame_node) {
+  if (!IsLoading(frame_node->GetPageNode()->GetLoadingState())) {
+    return;
+  }
+
+  voting_channel_.InvalidateVote(GetExecutionContext(frame_node));
+}
+
+void LoadingPageVoter::OnPageNodeStartedLoading(const PageNode* page_node) {
+  page_node->VisitMainFrameNodes([this](const FrameNode* main_frame_node) {
+    SubmitVoteForSubtree(main_frame_node);
+    return true;
+  });
+}
+
+void LoadingPageVoter::OnPageNodeStoppedLoading(const PageNode* page_node) {
+  page_node->VisitMainFrameNodes([this](const FrameNode* main_frame_node) {
+    InvalidateVoteForSubtree(main_frame_node);
+    return true;
+  });
+}
+
+void LoadingPageVoter::SubmitVoteForSubtree(const FrameNode* frame_node) {
+  voting_channel_.SubmitVote(
+      GetExecutionContext(frame_node),
+      Vote(base::TaskPriority::USER_VISIBLE, kPageIsLoadingReason));
+
+  // Recurse through subtree.
+  frame_node->VisitChildFrameNodes([this](const FrameNode* frame_node) {
+    SubmitVoteForSubtree(frame_node);
+    return true;
+  });
+}
+
+void LoadingPageVoter::InvalidateVoteForSubtree(const FrameNode* frame_node) {
+  voting_channel_.InvalidateVote(GetExecutionContext(frame_node));
+
+  // Recurse through subtree.
+  frame_node->VisitChildFrameNodes([this](const FrameNode* frame_node) {
+    InvalidateVoteForSubtree(frame_node);
+    return true;
+  });
+}
+
+}  // namespace performance_manager::execution_context_priority
diff --git a/components/performance_manager/execution_context_priority/loading_page_voter.h b/components/performance_manager/execution_context_priority/loading_page_voter.h
new file mode 100644
index 0000000..60472c85
--- /dev/null
+++ b/components/performance_manager/execution_context_priority/loading_page_voter.h
@@ -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.
+
+#ifndef COMPONENTS_PERFORMANCE_MANAGER_EXECUTION_CONTEXT_PRIORITY_LOADING_PAGE_VOTER_H_
+#define COMPONENTS_PERFORMANCE_MANAGER_EXECUTION_CONTEXT_PRIORITY_LOADING_PAGE_VOTER_H_
+
+#include "components/performance_manager/graph/initializing_frame_node_observer.h"
+#include "components/performance_manager/public/execution_context_priority/execution_context_priority.h"
+#include "components/performance_manager/public/graph/page_node.h"
+
+namespace performance_manager::execution_context_priority {
+
+// This voter casts a TaskPriority::USER_VISIBLE vote to all frames that are
+// part of a loading page. This makes switching to a loading tab faster.
+// Note: Uses `InitializingFrameNodeObserver` because it can affect the initial
+// priority of a frame.
+class LoadingPageVoter : public PageNode::ObserverDefaultImpl,
+                         public InitializingFrameNodeObserver {
+ public:
+  static const char kPageIsLoadingReason[];
+
+  LoadingPageVoter();
+  ~LoadingPageVoter() override;
+
+  LoadingPageVoter(const LoadingPageVoter&) = delete;
+  LoadingPageVoter& operator=(const LoadingPageVoter&) = delete;
+
+  // Sets the voting channel where the votes will be cast.
+  void SetVotingChannel(VotingChannel voting_channel);
+
+  // PageNodeObserver:
+  void OnPageNodeAdded(const PageNode* page_node) override;
+  void OnBeforePageNodeRemoved(const PageNode* page_node) override;
+  void OnLoadingStateChanged(const PageNode* page_node,
+                             PageNode::LoadingState previous_state) override;
+
+  // InitializingFrameNodeObserver:
+  void OnFrameNodeInitializing(const FrameNode* frame_node) override;
+  void OnFrameNodeTearingDown(const FrameNode* frame_node) override;
+
+ private:
+  // Called when a page node starts/stops loading, which will submit/invalidate
+  // a vote for every frame in that page, respectively.
+  void OnPageNodeStartedLoading(const PageNode* page_node);
+  void OnPageNodeStoppedLoading(const PageNode* page_node);
+
+  // Submits/Invalidates a vote for `frame_node` and its subtree.
+  void SubmitVoteForSubtree(const FrameNode* frame_node);
+  void InvalidateVoteForSubtree(const FrameNode* frame_node);
+
+  VotingChannel voting_channel_;
+};
+
+}  // namespace performance_manager::execution_context_priority
+
+#endif  // COMPONENTS_PERFORMANCE_MANAGER_EXECUTION_CONTEXT_PRIORITY_LOADING_PAGE_VOTER_H_
diff --git a/components/performance_manager/execution_context_priority/loading_page_voter_unittest.cc b/components/performance_manager/execution_context_priority/loading_page_voter_unittest.cc
new file mode 100644
index 0000000..07666d02
--- /dev/null
+++ b/components/performance_manager/execution_context_priority/loading_page_voter_unittest.cc
@@ -0,0 +1,170 @@
+// 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/performance_manager/execution_context_priority/loading_page_voter.h"
+
+#include "base/memory/raw_ptr.h"
+#include "components/performance_manager/public/execution_context/execution_context.h"
+#include "components/performance_manager/public/graph/graph.h"
+#include "components/performance_manager/test_support/graph_test_harness.h"
+#include "components/performance_manager/test_support/mock_graphs.h"
+#include "components/performance_manager/test_support/voting.h"
+
+namespace performance_manager::execution_context_priority {
+
+using DummyVoteObserver = voting::test::DummyVoteObserver<Vote>;
+
+namespace {
+
+const execution_context::ExecutionContext* GetExecutionContext(
+    const FrameNode* frame_node) {
+  return execution_context::ExecutionContext::From(frame_node);
+}
+
+// Both the voting channel and the LoadingPageVoter are expected live on the
+// graph, without being actual GraphOwned objects. This class wraps both to
+// allow this.
+class GraphOwnedWrapper : public GraphOwned {
+ public:
+  GraphOwnedWrapper() {
+    VotingChannel voting_channel = observer_.BuildVotingChannel();
+    voter_id_ = voting_channel.voter_id();
+    loading_page_voter_.SetVotingChannel(std::move(voting_channel));
+  }
+
+  ~GraphOwnedWrapper() override = default;
+
+  GraphOwnedWrapper(const GraphOwnedWrapper&) = delete;
+  GraphOwnedWrapper& operator=(const GraphOwnedWrapper&) = delete;
+
+  // GraphOwned:
+  void OnPassedToGraph(Graph* graph) override {
+    graph->AddPageNodeObserver(&loading_page_voter_);
+    graph->AddInitializingFrameNodeObserver(&loading_page_voter_);
+  }
+  void OnTakenFromGraph(Graph* graph) override {
+    graph->RemoveInitializingFrameNodeObserver(&loading_page_voter_);
+    graph->RemovePageNodeObserver(&loading_page_voter_);
+  }
+
+  // Exposes the DummyVoteObserver to validate expectations.
+  const DummyVoteObserver& observer() const { return observer_; }
+
+  VoterId voter_id() const { return voter_id_; }
+
+ private:
+  DummyVoteObserver observer_;
+  LoadingPageVoter loading_page_voter_;
+  VoterId voter_id_;
+};
+
+}  // namespace
+
+class LoadingPageVoterTest : public GraphTestHarness {
+ public:
+  using Super = GraphTestHarness;
+
+  LoadingPageVoterTest() = default;
+  ~LoadingPageVoterTest() override = default;
+
+  LoadingPageVoterTest(const LoadingPageVoterTest&) = delete;
+  LoadingPageVoterTest& operator=(const LoadingPageVoterTest&) = delete;
+
+  void SetUp() override {
+    GetGraphFeatures().EnableExecutionContextRegistry();
+    Super::SetUp();
+    auto wrapper = std::make_unique<GraphOwnedWrapper>();
+    wrapper_ = wrapper.get();
+    graph()->PassToGraph(std::move(wrapper));
+  }
+
+  // Exposes the DummyVoteObserver to validate expectations.
+  const DummyVoteObserver& observer() const { return wrapper_->observer(); }
+
+  VoterId voter_id() const { return wrapper_->voter_id(); }
+
+ private:
+  raw_ptr<GraphOwnedWrapper> wrapper_ = nullptr;
+};
+
+// Tests that the LoadingPageVoter correctly casts a vote for every frame when
+// the page is loading.
+TEST_F(LoadingPageVoterTest, VoteIfLoading) {
+  MockSinglePageWithMultipleProcessesGraph mock_graph(graph());
+  auto& frame_node = mock_graph.frame;
+  auto& child_frame_node = mock_graph.child_frame;
+
+  EXPECT_EQ(observer().GetVoteCount(), 0u);
+  EXPECT_FALSE(
+      observer().HasVote(voter_id(), GetExecutionContext(frame_node.get())));
+  EXPECT_FALSE(observer().HasVote(voter_id(),
+                                  GetExecutionContext(child_frame_node.get())));
+
+  mock_graph.page->SetLoadingState(PageNode::LoadingState::kLoading);
+
+  EXPECT_EQ(observer().GetVoteCount(), 2u);
+  EXPECT_TRUE(observer().HasVote(voter_id(),
+                                 GetExecutionContext(frame_node.get()),
+                                 base::TaskPriority::USER_VISIBLE,
+                                 LoadingPageVoter::kPageIsLoadingReason));
+  EXPECT_TRUE(observer().HasVote(voter_id(),
+                                 GetExecutionContext(child_frame_node.get()),
+                                 base::TaskPriority::USER_VISIBLE,
+                                 LoadingPageVoter::kPageIsLoadingReason));
+
+  // Still voting when the page is in the state kLoadedBusy.
+  mock_graph.page->SetLoadingState(PageNode::LoadingState::kLoadedBusy);
+
+  EXPECT_EQ(observer().GetVoteCount(), 2u);
+  EXPECT_TRUE(observer().HasVote(voter_id(),
+                                 GetExecutionContext(frame_node.get()),
+                                 base::TaskPriority::USER_VISIBLE,
+                                 LoadingPageVoter::kPageIsLoadingReason));
+  EXPECT_TRUE(observer().HasVote(voter_id(),
+                                 GetExecutionContext(child_frame_node.get()),
+                                 base::TaskPriority::USER_VISIBLE,
+                                 LoadingPageVoter::kPageIsLoadingReason));
+
+  // Add a frame while the page is loading.
+  auto other_child_frame_node = graph()->CreateFrameNodeAutoId(
+      mock_graph.process.get(), mock_graph.page.get(), frame_node.get());
+
+  EXPECT_EQ(observer().GetVoteCount(), 3u);
+  EXPECT_TRUE(observer().HasVote(voter_id(),
+                                 GetExecutionContext(frame_node.get()),
+                                 base::TaskPriority::USER_VISIBLE,
+                                 LoadingPageVoter::kPageIsLoadingReason));
+  EXPECT_TRUE(observer().HasVote(voter_id(),
+                                 GetExecutionContext(child_frame_node.get()),
+                                 base::TaskPriority::USER_VISIBLE,
+                                 LoadingPageVoter::kPageIsLoadingReason));
+  EXPECT_TRUE(observer().HasVote(
+      voter_id(), GetExecutionContext(other_child_frame_node.get()),
+      base::TaskPriority::USER_VISIBLE,
+      LoadingPageVoter::kPageIsLoadingReason));
+
+  // Remove a frame while the page is loading.
+  other_child_frame_node.reset();
+
+  EXPECT_EQ(observer().GetVoteCount(), 2u);
+  EXPECT_TRUE(observer().HasVote(voter_id(),
+                                 GetExecutionContext(frame_node.get()),
+                                 base::TaskPriority::USER_VISIBLE,
+                                 LoadingPageVoter::kPageIsLoadingReason));
+  EXPECT_TRUE(observer().HasVote(voter_id(),
+                                 GetExecutionContext(child_frame_node.get()),
+                                 base::TaskPriority::USER_VISIBLE,
+                                 LoadingPageVoter::kPageIsLoadingReason));
+
+  // Finish loading.
+  mock_graph.page->SetLoadingState(PageNode::LoadingState::kLoadedIdle);
+
+  EXPECT_EQ(observer().GetVoteCount(), 0u);
+  EXPECT_FALSE(
+      observer().HasVote(voter_id(), GetExecutionContext(frame_node.get())));
+  EXPECT_FALSE(observer().HasVote(voter_id(),
+                                  GetExecutionContext(child_frame_node.get())));
+}
+
+}  // namespace performance_manager::execution_context_priority
diff --git a/components/performance_manager/features.cc b/components/performance_manager/features.cc
index 647f8db7..2bd701e 100644
--- a/components/performance_manager/features.cc
+++ b/components/performance_manager/features.cc
@@ -135,6 +135,10 @@
 const base::FeatureParam<bool> kDownvoteAdFrames{&kPMProcessPriorityPolicy,
                                                  "downvote_ad_frames", false};
 
+BASE_FEATURE(kPMLoadingPageVoter,
+             "PMLoadingPageVoter",
+             base::FEATURE_DISABLED_BY_DEFAULT);
+
 BASE_FEATURE(kModalMemorySaver,
              "ModalMemorySaver",
              base::FEATURE_ENABLED_BY_DEFAULT);
diff --git a/components/performance_manager/public/features.h b/components/performance_manager/public/features.h
index a2ebb90..a63a9780 100644
--- a/components/performance_manager/public/features.h
+++ b/components/performance_manager/public/features.h
@@ -137,6 +137,8 @@
 
 extern const base::FeatureParam<bool> kDownvoteAdFrames;
 
+BASE_DECLARE_FEATURE(kPMLoadingPageVoter);
+
 // When enabled, Memory Saver supports the different modes defined in the
 // `ModalMemorySaverMode` enum.
 BASE_DECLARE_FEATURE(kModalMemorySaver);
diff --git a/components/plus_addresses/affiliations/plus_address_affiliation_source_adapter.cc b/components/plus_addresses/affiliations/plus_address_affiliation_source_adapter.cc
index 232e23c..8a62ad1 100644
--- a/components/plus_addresses/affiliations/plus_address_affiliation_source_adapter.cc
+++ b/components/plus_addresses/affiliations/plus_address_affiliation_source_adapter.cc
@@ -11,7 +11,7 @@
 namespace plus_addresses {
 namespace {
 using affiliations::FacetURI;
-}
+}  // namespace
 
 PlusAddressAffiliationSourceAdapter::PlusAddressAffiliationSourceAdapter(
     PlusAddressService* service)
diff --git a/components/policy/core/common/remote_commands/remote_commands_invalidator.cc b/components/policy/core/common/remote_commands/remote_commands_invalidator.cc
index 821cf1c..1a6fad42 100644
--- a/components/policy/core/common/remote_commands/remote_commands_invalidator.cc
+++ b/components/policy/core/common/remote_commands/remote_commands_invalidator.cc
@@ -89,8 +89,6 @@
 
   CHECK(invalidation.topic() == topic_);
 
-  invalidation.Acknowledge();
-
   DoRemoteCommandsFetch(invalidation);
 }
 
diff --git a/components/policy/core/common/remote_commands/remote_commands_invalidator_unittest.cc b/components/policy/core/common/remote_commands/remote_commands_invalidator_unittest.cc
index 85ab80f3..b3dbf08b 100644
--- a/components/policy/core/common/remote_commands/remote_commands_invalidator_unittest.cc
+++ b/components/policy/core/common/remote_commands/remote_commands_invalidator_unittest.cc
@@ -4,9 +4,11 @@
 
 #include "components/policy/core/common/remote_commands/remote_commands_invalidator.h"
 
+#include <set>
+#include <string>
+
 #include "base/run_loop.h"
 #include "base/test/task_environment.h"
-#include "components/invalidation/impl/fake_ack_handler.h"
 #include "components/invalidation/impl/fake_invalidation_service.h"
 #include "components/invalidation/impl/invalidator_registrar_with_memory.h"
 #include "components/invalidation/public/invalidation.h"
@@ -87,6 +89,8 @@
                                       /*payload=*/"foo_bar");
   }
 
+  // Will send invalidation to handler if `IsInvalidatorRegistered(topic) ==
+  // true`.
   invalidation::Invalidation FireInvalidation(
       const invalidation::Topic& topic) {
     const invalidation::Invalidation invalidation = CreateInvalidation(topic);
@@ -94,20 +98,14 @@
     return invalidation;
   }
 
-  bool IsInvalidationSent(const invalidation::Invalidation& invalidation) {
-    return !invalidation_service_.GetFakeAckHandler()->IsUnsent(invalidation);
+  bool IsInvalidatorRegistered() const {
+    return invalidation_service_.HasObserver(&invalidator_);
   }
 
-  bool IsInvalidationAcknowledged(
-      const invalidation::Invalidation& invalidation) {
-    return invalidation_service_.GetFakeAckHandler()->IsAcknowledged(
-        invalidation);
-  }
-
-  bool IsInvalidatorRegistered() {
-    return !invalidation_service_.invalidator_registrar()
-                .GetRegisteredTopics(&invalidator_)
-                .empty();
+  bool IsInvalidatorRegistered(const invalidation::Topic& topic) {
+    return invalidation_service_.invalidator_registrar()
+        .GetRegisteredTopics(&invalidator_)
+        .contains(topic);
   }
 
   std::set<std::string> GetSubscribedTopics() {
@@ -155,42 +153,15 @@
     VerifyExpectations();
   }
 
-  // Fire an invalidation to verify that invalidation is not working.
+  // Test that the invalidator is not registered to `topic`.
   void VerifyInvalidationDisabled(const invalidation::Topic& topic) {
-    const invalidation::Invalidation invalidation = FireInvalidation(topic);
-
-    base::RunLoop().RunUntilIdle();
-    EXPECT_FALSE(IsInvalidationSent(invalidation));
+    EXPECT_FALSE(IsInvalidatorRegistered(topic));
   }
 
-  // Fire an invalidation to verify that invalidation works.
+  // Test that the invalidator is enabled and registered to `topic`.
   void VerifyInvalidationEnabled(const invalidation::Topic& topic) {
     EXPECT_TRUE(invalidator_.invalidations_enabled());
-
-    EXPECT_CALL(invalidator_,
-                DoRemoteCommandsFetch(Eq(CreateInvalidation(topic))))
-        .Times(1);
-    const invalidation::Invalidation invalidation = FireInvalidation(topic);
-
-    base::RunLoop().RunUntilIdle();
-    EXPECT_TRUE(IsInvalidationSent(invalidation));
-    EXPECT_TRUE(IsInvalidationAcknowledged(invalidation));
-    VerifyExpectations();
-  }
-
-  // Fire an invalidation to verify that invalidation is not working. It is
-  // expected that invalidator did not receive/acknowledged the invalidation.
-  void VerifyTopicSubscribedButInvalidationDisabled(
-      const invalidation::Topic& topic) {
-    EXPECT_FALSE(invalidator_.invalidations_enabled());
-    EXPECT_CALL(invalidator_, DoRemoteCommandsFetch(_)).Times(0);
-    const invalidation::Invalidation invalidation = FireInvalidation(topic);
-
-    EXPECT_TRUE(
-        invalidation_service_.GetFakeAckHandler()->IsUnacked(invalidation));
-    EXPECT_FALSE(invalidation_service_.GetFakeAckHandler()->IsAcknowledged(
-        invalidation));
-    VerifyExpectations();
+    EXPECT_TRUE(IsInvalidatorRegistered(topic));
   }
 
   invalidation::Topic kTestingTopic1;
@@ -217,26 +188,17 @@
   // No invalidation will be received if no invalidation is fired.
   VerifyExpectations();
 
-  // Fire an invalidation with different object id, no invalidation will be
-  // received.
-  const invalidation::Invalidation invalidation1 =
-      FireInvalidation(kTestingTopic2);
-
-  base::RunLoop().RunUntilIdle();
-  EXPECT_FALSE(IsInvalidationSent(invalidation1));
-  VerifyExpectations();
+  // Invalidator does not subscribe to other stuff.
+  VerifyInvalidationDisabled(kTestingTopic2);
 
   // Fire the invalidation, it should be acknowledged and trigger a remote
   // commands fetch.
   EXPECT_CALL(invalidator_,
               DoRemoteCommandsFetch(Eq(CreateInvalidation(kTestingTopic1))))
       .Times(1);
-  const invalidation::Invalidation invalidation2 =
       FireInvalidation(kTestingTopic1);
 
   base::RunLoop().RunUntilIdle();
-  EXPECT_TRUE(IsInvalidationSent(invalidation2));
-  EXPECT_TRUE(IsInvalidationAcknowledged(invalidation2));
   VerifyExpectations();
 
   StopAndShutdown();
@@ -245,7 +207,7 @@
 // Verifies that no invalidation will be received when invalidator is shutdown.
 TEST_F(RemoteCommandsInvalidatorTest, ShutDown) {
   EXPECT_FALSE(invalidator_.invalidations_enabled());
-  FireInvalidation(kTestingTopic1);
+  EXPECT_FALSE(IsInvalidatorRegistered());
 
   base::RunLoop().RunUntilIdle();
   EXPECT_FALSE(invalidator_.invalidations_enabled());
@@ -258,7 +220,7 @@
   VerifyExpectations();
 
   EXPECT_FALSE(invalidator_.invalidations_enabled());
-  FireInvalidation(kTestingTopic2);
+  EXPECT_FALSE(IsInvalidatorRegistered());
 
   base::RunLoop().RunUntilIdle();
   EXPECT_FALSE(invalidator_.invalidations_enabled());
@@ -284,7 +246,6 @@
   invalidator_.Stop();
   VerifyExpectations();
 
-  VerifyTopicSubscribedButInvalidationDisabled(kTestingTopic1);
   EXPECT_FALSE(invalidator_.invalidations_enabled());
   EXPECT_EQ(GetSubscribedTopics(), std::set<std::string>{kTestingTopic1});
   EXPECT_EQ(GetRegisteredTopics(), std::set<std::string>{});
diff --git a/components/privacy_sandbox/BUILD.gn b/components/privacy_sandbox/BUILD.gn
index 1ac859f..022d40c01 100644
--- a/components/privacy_sandbox/BUILD.gn
+++ b/components/privacy_sandbox/BUILD.gn
@@ -103,6 +103,20 @@
   public_deps = [ "//base" ]
 }
 
+source_set("privacy_sandbox_notice_storage") {
+  sources = [
+    "privacy_sandbox_notice_storage.cc",
+    "privacy_sandbox_notice_storage.h",
+  ]
+
+  deps = [
+    "//components/pref_registry:pref_registry",
+    "//components/prefs",
+  ]
+
+  public_deps = [ "//base" ]
+}
+
 # These sources depend on targets from "//content/browser", which means they can't be
 # loaded on ios.
 # TOOD(b/301947962): Delete this check after CookieSettings is removed from iOS.
@@ -197,10 +211,14 @@
 
 source_set("unit_tests") {
   testonly = true
-  sources = [ "tracking_protection_onboarding_unittest.cc" ]
+  sources = [
+    "privacy_sandbox_notice_storage_unittest.cc",
+    "tracking_protection_onboarding_unittest.cc",
+  ]
 
   deps = [
     ":features",
+    ":privacy_sandbox_notice_storage",
     ":privacy_sandbox_prefs",
     ":tracking_protection_onboarding",
     ":tracking_protection_prefs",
diff --git a/components/privacy_sandbox/mock_privacy_sandbox_settings.h b/components/privacy_sandbox/mock_privacy_sandbox_settings.h
index a27d2b9..4f8c465 100644
--- a/components/privacy_sandbox/mock_privacy_sandbox_settings.h
+++ b/components/privacy_sandbox/mock_privacy_sandbox_settings.h
@@ -82,15 +82,16 @@
               (const url::Origin&,
                const url::Origin&,
                std::string*,
-               content::RenderFrameHost*),
+               content::RenderFrameHost*,
+               bool*),
               (override, const));
   MOCK_METHOD(bool,
               IsSharedStorageSelectURLAllowed,
-              (const url::Origin&, const url::Origin&, std::string*),
+              (const url::Origin&, const url::Origin&, std::string*, bool*),
               (override, const));
   MOCK_METHOD(bool,
               IsPrivateAggregationAllowed,
-              (const url::Origin&, const url::Origin&),
+              (const url::Origin&, const url::Origin&, bool*),
               (override, const));
   MOCK_METHOD(bool,
               IsPrivateAggregationDebugModeAllowed,
diff --git a/components/privacy_sandbox/privacy_sandbox_notice_storage.cc b/components/privacy_sandbox/privacy_sandbox_notice_storage.cc
new file mode 100644
index 0000000..50c6062
--- /dev/null
+++ b/components/privacy_sandbox/privacy_sandbox_notice_storage.cc
@@ -0,0 +1,175 @@
+// 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/privacy_sandbox/privacy_sandbox_notice_storage.h"
+
+#include <string>
+
+#include "base/json/values_util.h"
+#include "base/no_destructor.h"
+#include "base/strings/strcat.h"
+#include "base/time/time.h"
+#include "components/prefs/pref_registry.h"
+#include "components/prefs/pref_registry_simple.h"
+#include "components/prefs/scoped_user_pref_update.h"
+#include "privacy_sandbox_notice_storage.h"
+
+namespace privacy_sandbox {
+namespace {
+
+// Notice data will be saved as a dictionary in the PrefService of a profile.
+
+// PrefService path.
+constexpr char kPrivacySandboxNoticeDataPath[] = "privacy_sandbox.notices";
+
+// Unsynced pref that indicates the schema version this profile is using in
+// regards to the data model.
+constexpr char kPrivacySandboxSchemaVersion[] = "schema_version";
+
+// Unsynced pref that indicates the action taken relating to the notice.
+constexpr char kPrivacySandboxNoticeActionTaken[] = "notice_action_taken";
+
+// Unsynced pref that indicates the timestamp at which the action was taken. The
+// action taken can be determined by the `notice_action_taken` pref.
+constexpr char kPrivacySandboxNoticeActionTakenTime[] =
+    "notice_action_taken_time";
+
+// Unsynced pref that indicates when the notice was first shown. If this value
+// isn't set, we can assume the notice was never shown.
+constexpr char kPrivacySandboxNoticeFirstShown[] = "notice_first_shown";
+
+// Unsynced pref that indicates when the notice was last shown across all
+// sessions.
+constexpr char kPrivacySandboxNoticeLastShown[] = "notice_last_shown";
+
+// Unsynced pref that indicates the duration of how long the notice was shown
+// across all sessions to when a user took action.
+constexpr char kPrivacySandboxNoticeShownDuration[] = "notice_shown_duration";
+
+std::string CreatePrefPath(std::string_view notice,
+                           std::string_view pref_name) {
+  return base::StrCat({notice, ".", pref_name});
+}
+
+}  // namespace
+
+// PrivacySandboxNoticeData definitions.
+PrivacySandboxNoticeData::PrivacySandboxNoticeData() = default;
+PrivacySandboxNoticeData::PrivacySandboxNoticeData(
+    int schema_version,
+    NoticeActionTaken notice_action_taken,
+    base::Time notice_action_taken_time,
+    base::Time notice_first_shown,
+    base::Time notice_last_shown,
+    base::TimeDelta notice_shown_duration)
+    : schema_version(schema_version),
+      notice_action_taken(notice_action_taken),
+      notice_action_taken_time(notice_action_taken_time),
+      notice_first_shown(notice_first_shown),
+      notice_last_shown(notice_last_shown),
+      notice_shown_duration(notice_shown_duration) {}
+PrivacySandboxNoticeData& PrivacySandboxNoticeData::operator=(
+    const PrivacySandboxNoticeData&) = default;
+PrivacySandboxNoticeData::~PrivacySandboxNoticeData() = default;
+
+// PrivacySandboxNoticeStorage definitions.
+void PrivacySandboxNoticeStorage::RegisterProfilePrefs(
+    PrefRegistrySimple* registry) {
+  registry->RegisterDictionaryPref(kPrivacySandboxNoticeDataPath);
+}
+
+std::optional<PrivacySandboxNoticeData>
+PrivacySandboxNoticeStorage::ReadNoticeData(PrefService* pref_service,
+                                            std::string_view notice) {
+  const base::Value::Dict& pref_data =
+      pref_service->GetDict(kPrivacySandboxNoticeDataPath);
+  if (pref_data.empty()) {
+    return std::nullopt;
+  }
+
+  // Populate notice data values.
+  std::optional<PrivacySandboxNoticeData> notice_data =
+      PrivacySandboxNoticeData();
+  notice_data->schema_version = *pref_data.FindIntByDottedPath(
+      CreatePrefPath(notice, kPrivacySandboxSchemaVersion));
+  notice_data->notice_action_taken_time =
+      *base::ValueToTime(pref_data.FindByDottedPath(
+          CreatePrefPath(notice, kPrivacySandboxNoticeActionTakenTime)));
+  notice_data->notice_first_shown =
+      *base::ValueToTime(pref_data.FindByDottedPath(
+          CreatePrefPath(notice, kPrivacySandboxNoticeFirstShown)));
+  notice_data->notice_last_shown =
+      *base::ValueToTime(pref_data.FindByDottedPath(
+          CreatePrefPath(notice, kPrivacySandboxNoticeLastShown)));
+  notice_data->notice_shown_duration =
+      *base::ValueToTimeDelta(pref_data.FindByDottedPath(
+          CreatePrefPath(notice, kPrivacySandboxNoticeShownDuration)));
+
+  // Enum handling.
+  std::optional<int> notice_action_taken = pref_data.FindIntByDottedPath(
+      CreatePrefPath(notice, kPrivacySandboxNoticeActionTaken));
+  if (!notice_action_taken || *notice_action_taken < 0 ||
+      *notice_action_taken > static_cast<int>(NoticeActionTaken::kMaxValue)) {
+    notice_data->notice_action_taken = NoticeActionTaken::kNotSet;
+  } else {
+    notice_data->notice_action_taken =
+        static_cast<NoticeActionTaken>(*notice_action_taken);
+  }
+
+  return notice_data;
+}
+
+void PrivacySandboxNoticeStorage::SetSchemaVersion(PrefService* pref_service,
+                                                   std::string_view notice,
+                                                   int schema_version) {
+  ScopedDictPrefUpdate update(pref_service, kPrivacySandboxNoticeDataPath);
+  update.Get().SetByDottedPath(
+      CreatePrefPath(notice, kPrivacySandboxSchemaVersion), schema_version);
+}
+
+void PrivacySandboxNoticeStorage::SetNoticeActionTaken(
+    PrefService* pref_service,
+    std::string_view notice,
+    NoticeActionTaken notice_action_taken,
+    base::Time notice_action_taken_time) {
+  ScopedDictPrefUpdate update(pref_service, kPrivacySandboxNoticeDataPath);
+  update.Get().SetByDottedPath(
+      CreatePrefPath(notice, kPrivacySandboxNoticeActionTaken),
+      static_cast<int>(notice_action_taken));
+  update.Get().SetByDottedPath(
+      CreatePrefPath(notice, kPrivacySandboxNoticeActionTakenTime),
+      base::TimeToValue(notice_action_taken_time));
+}
+
+void PrivacySandboxNoticeStorage::SetNoticeShown(PrefService* pref_service,
+                                                 std::string_view notice,
+                                                 base::Time notice_shown_time) {
+  ScopedDictPrefUpdate update(pref_service, kPrivacySandboxNoticeDataPath);
+  const base::Value::Dict& pref_data =
+      pref_service->GetDict(kPrivacySandboxNoticeDataPath);
+  // Only set notice first shown if it hasn't previously been set.
+  if (!pref_data.empty() && !pref_data.FindByDottedPath(CreatePrefPath(
+                                notice, kPrivacySandboxNoticeFirstShown))) {
+    update.Get().SetByDottedPath(
+        CreatePrefPath(notice, kPrivacySandboxNoticeFirstShown),
+        base::TimeToValue(notice_shown_time));
+  }
+
+  // Always set notice last shown.
+  update.Get().SetByDottedPath(
+      CreatePrefPath(notice, kPrivacySandboxNoticeLastShown),
+      base::TimeToValue(notice_shown_time));
+}
+
+void PrivacySandboxNoticeStorage::SetNoticeShownDuration(
+    PrefService* pref_service,
+    std::string_view notice,
+    base::TimeDelta notice_shown_duration) {
+  ScopedDictPrefUpdate update(pref_service, kPrivacySandboxNoticeDataPath);
+  update.Get().SetByDottedPath(
+      CreatePrefPath(notice, kPrivacySandboxNoticeShownDuration),
+      base::TimeDeltaToValue(notice_shown_duration));
+}
+
+}  // namespace privacy_sandbox
diff --git a/components/privacy_sandbox/privacy_sandbox_notice_storage.h b/components/privacy_sandbox/privacy_sandbox_notice_storage.h
new file mode 100644
index 0000000..f6c650d
--- /dev/null
+++ b/components/privacy_sandbox/privacy_sandbox_notice_storage.h
@@ -0,0 +1,96 @@
+// 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_PRIVACY_SANDBOX_PRIVACY_SANDBOX_NOTICE_STORAGE_H_
+#define COMPONENTS_PRIVACY_SANDBOX_PRIVACY_SANDBOX_NOTICE_STORAGE_H_
+
+#include <optional>
+#include <string>
+
+#include "base/no_destructor.h"
+#include "base/time/time.h"
+#include "components/prefs/pref_registry_simple.h"
+
+class PrefService;
+
+namespace privacy_sandbox {
+
+// Different notice actions stored in the pref above.
+enum class NoticeActionTaken {
+  // No Ack action set.
+  kNotSet = 0,
+  // Action taken some other way.
+  kOther = 1,
+  // ACK'ed the notice using 'GotIt' or some other form of acknowledgement.
+  kAck = 2,
+  // Opted in/Consented to the notice using 'Turn it on' or some other form of
+  // explicit consent.
+  kOptIn = 3,
+  // Action taken to dismiss or opt out of the notice using 'No Thanks' or some
+  // other form of dismissal.
+  kOptOut = 4,
+  // Action taken clicking the settings button.
+  kSettings = 5,
+  // Action taken clicking the learn more button.
+  kLearnMore = 6,
+  // Action taken clicking the 'x' button, closing the browser etc.
+  kClosed = 7,
+  kMaxValue = kClosed,
+};
+
+// Stores information about profile interactions on a notice.
+struct PrivacySandboxNoticeData {
+  PrivacySandboxNoticeData();
+  PrivacySandboxNoticeData& operator=(const PrivacySandboxNoticeData&);
+  ~PrivacySandboxNoticeData();
+  PrivacySandboxNoticeData(int schema_version,
+                           NoticeActionTaken notice_action_taken,
+                           base::Time notice_action_taken_time,
+                           base::Time notice_first_shown,
+                           base::Time notice_last_shown,
+                           base::TimeDelta notice_shown_duration);
+  int schema_version = 0;
+  NoticeActionTaken notice_action_taken = NoticeActionTaken::kNotSet;
+  base::Time notice_action_taken_time;
+  base::Time notice_first_shown;
+  base::Time notice_last_shown;
+  base::TimeDelta notice_shown_duration;
+};
+
+class PrivacySandboxNoticeStorage {
+ public:
+  static void RegisterProfilePrefs(PrefRegistrySimple* registry);
+
+  std::optional<PrivacySandboxNoticeData> ReadNoticeData(
+      PrefService* pref_service,
+      std::string_view notice);
+
+  void SetSchemaVersion(PrefService* pref_service,
+                        std::string_view notice,
+                        int schema_version);
+  void SetNoticeActionTaken(PrefService* pref_service,
+                            std::string_view notice,
+                            NoticeActionTaken notice_action_taken,
+                            base::Time notice_action_taken_time);
+  void SetNoticeShown(PrefService* pref_service,
+                      std::string_view notice,
+                      base::Time notice_shown_time);
+  void SetNoticeShownDuration(PrefService* pref_service,
+                              std::string_view notice,
+                              base::TimeDelta notice_shown_duration);
+
+  PrivacySandboxNoticeStorage(const PrivacySandboxNoticeStorage&) = delete;
+  PrivacySandboxNoticeStorage& operator=(const PrivacySandboxNoticeStorage&) =
+      delete;
+
+ private:
+  friend class PrivacySandboxNoticeStorageTestPeer;  // For testing.
+  friend base::NoDestructor<PrivacySandboxNoticeStorage>;
+  PrivacySandboxNoticeStorage() = default;
+  ~PrivacySandboxNoticeStorage() = default;
+};
+
+}  // namespace privacy_sandbox
+
+#endif  // COMPONENTS_PRIVACY_SANDBOX_PRIVACY_SANDBOX_NOTICE_STORAGE_H_
diff --git a/components/privacy_sandbox/privacy_sandbox_notice_storage_unittest.cc b/components/privacy_sandbox/privacy_sandbox_notice_storage_unittest.cc
new file mode 100644
index 0000000..1e94150
--- /dev/null
+++ b/components/privacy_sandbox/privacy_sandbox_notice_storage_unittest.cc
@@ -0,0 +1,162 @@
+// 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/privacy_sandbox/privacy_sandbox_notice_storage.h"
+
+#include "base/test/task_environment.h"
+#include "base/time/time.h"
+#include "components/prefs/testing_pref_service.h"
+#include "privacy_sandbox_notice_storage.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace privacy_sandbox {
+
+class PrivacySandboxNoticeStorageTestPeer {
+ public:
+  static PrivacySandboxNoticeStorage* GetPrivacySandboxNoticeStorageInstance() {
+    static base::NoDestructor<PrivacySandboxNoticeStorage> instance;
+    return instance.get();
+  }
+};
+
+namespace {
+
+class PrivacySandboxNoticeStorageTest : public testing::Test {
+ public:
+  PrivacySandboxNoticeStorageTest()
+      : task_env_(base::test::TaskEnvironment::TimeSource::MOCK_TIME) {
+    PrivacySandboxNoticeStorage::RegisterProfilePrefs(prefs()->registry());
+  }
+
+  PrivacySandboxNoticeData NoticeTestData() {
+    return PrivacySandboxNoticeData{
+        /*schema_version=*/0,
+        /*notice_action_taken=*/NoticeActionTaken::kAck,
+        /*notice_action_taken_time=*/
+        base::Time::FromMillisecondsSinceUnixEpoch(100),
+        /*notice_first_shown=*/base::Time::FromMillisecondsSinceUnixEpoch(200),
+        /*notice_last_shown=*/base::Time::FromMillisecondsSinceUnixEpoch(200),
+        /*notice_shown_duration=*/base::Microseconds(199000),
+    };
+  }
+
+  PrivacySandboxNoticeStorage* notice_storage() {
+    return PrivacySandboxNoticeStorageTestPeer::
+        GetPrivacySandboxNoticeStorageInstance();
+  }
+
+  // Sets notice related prefs.
+  void SaveNoticeData(const PrivacySandboxNoticeData& notice_data,
+                      std::string_view notice) {
+    notice_storage()->SetSchemaVersion(prefs(), notice,
+                                       notice_data.schema_version);
+    notice_storage()->SetNoticeActionTaken(
+        prefs(), notice, notice_data.notice_action_taken,
+        notice_data.notice_action_taken_time);
+    notice_storage()->SetNoticeShown(prefs(), notice,
+                                     notice_data.notice_last_shown);
+    notice_storage()->SetNoticeShownDuration(prefs(), notice,
+                                             notice_data.notice_shown_duration);
+  }
+
+  // Compares notice related data.
+  void CompareNoticeData(const PrivacySandboxNoticeData& expected,
+                         const PrivacySandboxNoticeData& actual) {
+    EXPECT_EQ(expected.schema_version, actual.schema_version);
+    EXPECT_EQ(expected.notice_action_taken, actual.notice_action_taken);
+    EXPECT_EQ(expected.notice_action_taken_time,
+              actual.notice_action_taken_time);
+    EXPECT_EQ(expected.notice_first_shown, actual.notice_first_shown);
+    EXPECT_EQ(expected.notice_last_shown, actual.notice_last_shown);
+    EXPECT_EQ(expected.notice_shown_duration, actual.notice_shown_duration);
+  }
+
+  TestingPrefServiceSimple* prefs() { return &prefs_; }
+
+ private:
+  base::test::TaskEnvironment task_env_;
+  TestingPrefServiceSimple prefs_;
+};
+
+TEST_F(PrivacySandboxNoticeStorageTest, NoticePathNotFound) {
+  const auto actual =
+      notice_storage()->ReadNoticeData(prefs(),
+                                       /*notice=*/"NoticeTest1_v0");
+  EXPECT_FALSE(actual.has_value());
+}
+
+TEST_F(PrivacySandboxNoticeStorageTest, NoNoticeDataSet) {
+  PrivacySandboxNoticeData data = {};
+  SaveNoticeData(data, /*notice=*/"NoticeTest1_v0");
+  const auto actual =
+      notice_storage()->ReadNoticeData(prefs(),
+                                       /*notice=*/"NoticeTest1_v0");
+  CompareNoticeData(data, *actual);
+}
+
+TEST_F(PrivacySandboxNoticeStorageTest, OnlySchemaVersionSet) {
+  PrivacySandboxNoticeData data;
+  data.schema_version = 1;
+  SaveNoticeData(data, /*notice=*/"NoticeTest1_v0");
+  const auto actual =
+      notice_storage()->ReadNoticeData(prefs(),
+                                       /*notice=*/"NoticeTest1_v0");
+  CompareNoticeData(data, *actual);
+}
+
+TEST_F(PrivacySandboxNoticeStorageTest, SetsValuesAndReadsData) {
+  const auto expected = NoticeTestData();
+  SaveNoticeData(expected, /*notice=*/"NoticeTest1_v0");
+  const auto actual =
+      notice_storage()->ReadNoticeData(prefs(),
+                                       /*notice=*/"NoticeTest1_v0");
+  CompareNoticeData(expected, *actual);
+}
+
+TEST_F(PrivacySandboxNoticeStorageTest, UpdateNoticeShownValue) {
+  SaveNoticeData(NoticeTestData(), /*notice=*/"NoticeTest1_v0");
+  auto actual = notice_storage()->ReadNoticeData(prefs(),
+                                                 /*notice=*/"NoticeTest1_v0");
+  EXPECT_EQ(base::Time::FromMillisecondsSinceUnixEpoch(200),
+            actual->notice_first_shown);
+  EXPECT_EQ(base::Time::FromMillisecondsSinceUnixEpoch(200),
+            actual->notice_last_shown);
+
+  // Set notice shown value again.
+  notice_storage()->SetNoticeShown(
+      prefs(), "NoticeTest1_v0",
+      base::Time::FromMillisecondsSinceUnixEpoch(400));
+  actual = notice_storage()->ReadNoticeData(prefs(),
+                                            /*notice=*/"NoticeTest1_v0");
+  EXPECT_EQ(base::Time::FromMillisecondsSinceUnixEpoch(200),
+            actual->notice_first_shown);
+  EXPECT_EQ(base::Time::FromMillisecondsSinceUnixEpoch(400),
+            actual->notice_last_shown);
+}
+
+TEST_F(PrivacySandboxNoticeStorageTest, SetMultipleNotices) {
+  // Notice data 1.
+  const auto expected_notice1 = NoticeTestData();
+  SaveNoticeData(expected_notice1, /*notice=*/"NoticeTest1_v0");
+  const auto actual_notice1 =
+      notice_storage()->ReadNoticeData(prefs(),
+                                       /*notice=*/"NoticeTest1_v0");
+
+  // Notice data 2.
+  auto expected_notice2 = NoticeTestData();
+  expected_notice2.notice_action_taken = NoticeActionTaken::kSettings;
+  expected_notice2.notice_action_taken_time =
+      base::Time::FromMillisecondsSinceUnixEpoch(300);
+  SaveNoticeData(expected_notice2, /*notice=*/"NoticeTest2_v0");
+  const auto actual_notice2 =
+      notice_storage()->ReadNoticeData(prefs(),
+                                       /*notice=*/"NoticeTest2_v0");
+
+  CompareNoticeData(expected_notice1, *actual_notice1);
+  CompareNoticeData(expected_notice2, *actual_notice2);
+}
+
+}  // namespace
+}  // namespace privacy_sandbox
diff --git a/components/privacy_sandbox/privacy_sandbox_settings.h b/components/privacy_sandbox/privacy_sandbox_settings.h
index c7c4501d..b49ce43 100644
--- a/components/privacy_sandbox/privacy_sandbox_settings.h
+++ b/components/privacy_sandbox/privacy_sandbox_settings.h
@@ -237,11 +237,18 @@
   //
   // If provided, `console_frame` is used to log errors to the console upon
   // attestation failure.
+  //
+  // The out parameter `out_block_is_site_setting_specific` will be set to true
+  // in the case that the return value is false and the failure to be allowed is
+  // due to site-settings. Otherwise the parameter will be set to false (because
+  // either the return value is true, or the failure is due to a
+  // non-site-setting-specific reason).
   virtual bool IsSharedStorageAllowed(
       const url::Origin& top_frame_origin,
       const url::Origin& accessing_origin,
-      std::string* out_debug_message = nullptr,
-      content::RenderFrameHost* console_frame = nullptr) const = 0;
+      std::string* out_debug_message,
+      content::RenderFrameHost* console_frame,
+      bool* out_block_is_site_setting_specific) const = 0;
 
   // Controls whether Shared Storage SelectURL is allowable for
   // `accessing_origin` in the context of `top_frame_origin`. Does not override
@@ -250,20 +257,30 @@
   // If non-null, `out_debug_message` is updated in this call to relay details
   // back to the caller about how the returned boolean result was obtained.
   //
-  // TODO(crbug.com/40244046): This just redirects to the general
-  // IsSharedStorageAllowed(). The implementation needs to be updated to reflect
-  // the M1 preferences when release 4 is enabled.
+  // The out parameter `out_block_is_site_setting_specific` will be set to true
+  // in the case that the return value is false and the failure to be allowed is
+  // due to site-settings. Otherwise the parameter will be set to false (because
+  // either the return value is true, or the failure is due to a
+  // non-site-setting-specific reason).
   virtual bool IsSharedStorageSelectURLAllowed(
       const url::Origin& top_frame_origin,
       const url::Origin& accessing_origin,
-      std::string* out_debug_message = nullptr) const = 0;
+      std::string* out_debug_message,
+      bool* out_block_is_site_setting_specific) const = 0;
 
   // Determines whether the Private Aggregation API is allowable in a particular
   // context. `top_frame_origin` is the associated top-frame origin of the
   // calling context. Applicable to all uses of Private Aggregation.
+  //
+  // The out parameter `out_block_is_site_setting_specific` will be set to true
+  // in the case that the return value is false and the failure to be allowed is
+  // due to site-settings. Otherwise the parameter will be set to false (because
+  // either the return value is true, or the failure is due to a
+  // non-site-setting-specific reason).
   virtual bool IsPrivateAggregationAllowed(
       const url::Origin& top_frame_origin,
-      const url::Origin& reporting_origin) const = 0;
+      const url::Origin& reporting_origin,
+      bool* out_block_is_site_setting_specific) const = 0;
 
   // Determines whether the Private Aggregation API's debug mode is allowable in
   // a particular context. Note that if IsPrivateAggregationAllowed() is false,
diff --git a/components/privacy_sandbox/privacy_sandbox_settings_impl.cc b/components/privacy_sandbox/privacy_sandbox_settings_impl.cc
index 2e41bcac..96771ce1 100644
--- a/components/privacy_sandbox/privacy_sandbox_settings_impl.cc
+++ b/components/privacy_sandbox/privacy_sandbox_settings_impl.cc
@@ -630,12 +630,15 @@
     const url::Origin& top_frame_origin,
     const url::Origin& accessing_origin,
     std::string* out_debug_message,
-    content::RenderFrameHost* console_frame) const {
+    content::RenderFrameHost* console_frame,
+    bool* out_block_is_site_setting_specific) const {
   // Check for attestation on the caller's site.
   Status attestation_status =
       PrivacySandboxAttestations::GetInstance()->IsSiteAttested(
           net::SchemefulSite(accessing_origin),
           PrivacySandboxAttestationsGatedAPI::kSharedStorage);
+  SetOutBlockIsSiteSettingSpecificFromStatus(
+      attestation_status, out_block_is_site_setting_specific);
   if (!IsAllowed(attestation_status)) {
     JoinHistogram(kIsSharedStorageAllowedHistogram, attestation_status);
     std::string error_message =
@@ -657,9 +660,13 @@
   }
 
   Status status = GetPrivacySandboxAllowedStatus();
+  SetOutBlockIsSiteSettingSpecificFromStatus(
+      status, out_block_is_site_setting_specific);
   if (IsAllowed(status)) {
     status =
         GetSiteAccessAllowedStatus(top_frame_origin, accessing_origin.GetURL());
+    SetOutBlockIsSiteSettingSpecificFromStatus(
+        status, out_block_is_site_setting_specific);
     if (out_debug_message) {
       *out_debug_message = base::StrCat(
           {"Site access settings returned status ",
@@ -685,8 +692,11 @@
 bool PrivacySandboxSettingsImpl::IsSharedStorageSelectURLAllowed(
     const url::Origin& top_frame_origin,
     const url::Origin& accessing_origin,
-    std::string* out_debug_message) const {
+    std::string* out_debug_message,
+    bool* out_block_is_site_setting_specific) const {
   Status status = GetM1FledgeAllowedStatus(top_frame_origin, accessing_origin);
+  SetOutBlockIsSiteSettingSpecificFromStatus(
+      status, out_block_is_site_setting_specific);
   JoinHistogram(kIsSharedStorageSelectURLAllowedHistogram, status);
   if (out_debug_message) {
     *out_debug_message = base::StrCat(
@@ -703,12 +713,15 @@
 
 bool PrivacySandboxSettingsImpl::IsPrivateAggregationAllowed(
     const url::Origin& top_frame_origin,
-    const url::Origin& reporting_origin) const {
+    const url::Origin& reporting_origin,
+    bool* out_block_is_site_setting_specific) const {
   // Check for attestation on the worklet's site.
   Status attestation_status =
       PrivacySandboxAttestations::GetInstance()->IsSiteAttested(
           net::SchemefulSite(reporting_origin),
           PrivacySandboxAttestationsGatedAPI::kPrivateAggregation);
+  SetOutBlockIsSiteSettingSpecificFromStatus(
+      attestation_status, out_block_is_site_setting_specific);
   if (!IsAllowed(attestation_status)) {
     JoinHistogram(kIsPrivateAggregationAllowedHistogram, attestation_status);
     return false;
@@ -716,6 +729,8 @@
 
   Status status =
       GetM1AdMeasurementAllowedStatus(top_frame_origin, reporting_origin);
+  SetOutBlockIsSiteSettingSpecificFromStatus(
+      status, out_block_is_site_setting_specific);
   JoinHistogram(kIsPrivateAggregationAllowedHistogram, status);
   return IsAllowed(status);
 }
@@ -723,7 +738,9 @@
 bool PrivacySandboxSettingsImpl::IsPrivateAggregationDebugModeAllowed(
     const url::Origin& top_frame_origin,
     const url::Origin& reporting_origin) const {
-  if (!IsPrivateAggregationAllowed(top_frame_origin, reporting_origin)) {
+  if (!IsPrivateAggregationAllowed(
+          top_frame_origin, reporting_origin,
+          /*out_block_is_site_setting_specific=*/nullptr)) {
     return false;
   }
 
@@ -902,4 +919,13 @@
       prefs::kPrivacySandboxRelatedWebsiteSetsEnabled);
 }
 
+void PrivacySandboxSettingsImpl::SetOutBlockIsSiteSettingSpecificFromStatus(
+    Status status,
+    bool* out_block_is_site_setting_specific) const {
+  if (out_block_is_site_setting_specific != nullptr) {
+    *out_block_is_site_setting_specific =
+        status == Status::kSiteDataAccessBlocked;
+  }
+}
+
 }  // namespace privacy_sandbox
diff --git a/components/privacy_sandbox/privacy_sandbox_settings_impl.h b/components/privacy_sandbox/privacy_sandbox_settings_impl.h
index bd77c9db..573af8c 100644
--- a/components/privacy_sandbox/privacy_sandbox_settings_impl.h
+++ b/components/privacy_sandbox/privacy_sandbox_settings_impl.h
@@ -87,15 +87,18 @@
   bool IsSharedStorageAllowed(
       const url::Origin& top_frame_origin,
       const url::Origin& accessing_origin,
-      std::string* out_debug_message = nullptr,
-      content::RenderFrameHost* console_frame = nullptr) const override;
+      std::string* out_debug_message,
+      content::RenderFrameHost* console_frame,
+      bool* out_block_is_site_setting_specific) const override;
   bool IsSharedStorageSelectURLAllowed(
       const url::Origin& top_frame_origin,
       const url::Origin& accessing_origin,
-      std::string* out_debug_message = nullptr) const override;
+      std::string* out_debug_message,
+      bool* out_block_is_site_setting_specific) const override;
   bool IsPrivateAggregationAllowed(
       const url::Origin& top_frame_origin,
-      const url::Origin& reporting_origin) const override;
+      const url::Origin& reporting_origin,
+      bool* out_block_is_site_setting_specific) const override;
   bool IsPrivateAggregationDebugModeAllowed(
       const url::Origin& top_frame_origin,
       const url::Origin& reporting_origin) const override;
@@ -206,6 +209,12 @@
   // From TrackingProtectionSettingsObserver.
   void OnBlockAllThirdPartyCookiesChanged() override;
 
+  // Sets the out parameter `out_block_is_site_setting_specific` if it is
+  // non-null, based on the given `status`.
+  void SetOutBlockIsSiteSettingSpecificFromStatus(
+      Status status,
+      bool* out_block_is_site_setting_specific) const;
+
   base::ObserverList<Observer>::Unchecked observers_;
 
   std::unique_ptr<Delegate> delegate_;
diff --git a/components/privacy_sandbox/privacy_sandbox_settings_impl_unittest.cc b/components/privacy_sandbox/privacy_sandbox_settings_impl_unittest.cc
index 6489875..a953584 100644
--- a/components/privacy_sandbox/privacy_sandbox_settings_impl_unittest.cc
+++ b/components/privacy_sandbox/privacy_sandbox_settings_impl_unittest.cc
@@ -838,6 +838,18 @@
         managed_provider_raw, TestCase(test_state, test_input, test_output));
   }
 
+ protected:
+  // Pseudo-constants for the convenience of tests that need to check values of
+  // type bool*. We can't actually make these const, as then dereferencing them
+  // would give the wrong type (i.e. const bool*). Since we are using
+  // absl::variant for the test outputs, the types must exactly match.
+  bool kTrue_ = true;
+  bool kFalse_ = false;
+
+  bool actual_out_shared_storage_block_is_site_setting_specific_ = false;
+  bool actual_out_select_url_block_is_site_setting_specific_ = false;
+  bool actual_out_private_aggregation_block_is_site_setting_specific_ = false;
+
  private:
   bool test_case_run_ = false;
 };
@@ -858,7 +870,14 @@
           {kAdMeasurementSourceOrigin,
            url::Origin::Create(GURL("https://source-origin.com"))},
           {kAdMeasurementDestinationOrigin,
-           url::Origin::Create(GURL("https://dest-origin.com"))}},
+           url::Origin::Create(GURL("https://dest-origin.com"))},
+
+          {kOutSharedStorageBlockIsSiteSettingSpecific,
+           &actual_out_shared_storage_block_is_site_setting_specific_},
+          {kOutSharedStorageSelectURLBlockIsSiteSettingSpecific,
+           &actual_out_select_url_block_is_site_setting_specific_},
+          {kOutPrivateAggregationBlockIsSiteSettingSpecific,
+           &actual_out_private_aggregation_block_is_site_setting_specific_}},
       TestOutput{
           {MultipleOutputKeys{
                kIsTopicsAllowed, kIsTopicsAllowedForContext,
@@ -880,7 +899,11 @@
                kMaySendAttributionReportMetric, kIsSharedStorageAllowedMetric,
                kIsSharedStorageSelectURLAllowedMetric,
                kIsPrivateAggregationAllowedMetric},
-           static_cast<int>(Status::kAllowed)}});
+           static_cast<int>(Status::kAllowed)},
+          {MultipleOutputKeys{kIsSharedStorageBlockSiteSettingSpecific,
+                              kIsSharedStorageSelectURLBlockSiteSettingSpecific,
+                              kIsPrivateAggregationBlockSiteSettingSpecific},
+           &kFalse_}});
 }
 
 TEST_F(PrivacySandboxSettingsM1Test, ApiPreferenceDisabled) {
@@ -899,7 +922,14 @@
           {kAdMeasurementSourceOrigin,
            url::Origin::Create(GURL("https://source-origin.com"))},
           {kAdMeasurementDestinationOrigin,
-           url::Origin::Create(GURL("https://dest-origin.com"))}},
+           url::Origin::Create(GURL("https://dest-origin.com"))},
+
+          {kOutSharedStorageBlockIsSiteSettingSpecific,
+           &actual_out_shared_storage_block_is_site_setting_specific_},
+          {kOutSharedStorageSelectURLBlockIsSiteSettingSpecific,
+           &actual_out_select_url_block_is_site_setting_specific_},
+          {kOutPrivateAggregationBlockIsSiteSettingSpecific,
+           &actual_out_private_aggregation_block_is_site_setting_specific_}},
       TestOutput{
           {MultipleOutputKeys{
                kIsTopicsAllowed, kIsTopicsAllowedForContext,
@@ -922,7 +952,11 @@
                kIsSharedStorageSelectURLAllowedMetric,
                kIsPrivateAggregationAllowedMetric},
            static_cast<int>(Status::kApisDisabled)},
-          {kIsSharedStorageAllowedMetric, static_cast<int>(Status::kAllowed)}});
+          {kIsSharedStorageAllowedMetric, static_cast<int>(Status::kAllowed)},
+          {MultipleOutputKeys{kIsSharedStorageBlockSiteSettingSpecific,
+                              kIsSharedStorageSelectURLBlockSiteSettingSpecific,
+                              kIsPrivateAggregationBlockSiteSettingSpecific},
+           &kFalse_}});
 }
 
 TEST_F(PrivacySandboxSettingsM1Test, CookieControlsModeHasNoEffect) {
@@ -987,7 +1021,14 @@
           {kAdMeasurementSourceOrigin,
            url::Origin::Create(GURL("https://source-origin.com"))},
           {kAdMeasurementDestinationOrigin,
-           url::Origin::Create(GURL("https://dest-origin.com"))}},
+           url::Origin::Create(GURL("https://dest-origin.com"))},
+
+          {kOutSharedStorageBlockIsSiteSettingSpecific,
+           &actual_out_shared_storage_block_is_site_setting_specific_},
+          {kOutSharedStorageSelectURLBlockIsSiteSettingSpecific,
+           &actual_out_select_url_block_is_site_setting_specific_},
+          {kOutPrivateAggregationBlockIsSiteSettingSpecific,
+           &actual_out_private_aggregation_block_is_site_setting_specific_}},
       TestOutput{
           {MultipleOutputKeys{kIsTopicsAllowed,
                               kIsAttributionReportingEverAllowed},
@@ -1013,7 +1054,11 @@
                kMaySendAttributionReportMetric, kIsSharedStorageAllowedMetric,
                kIsSharedStorageSelectURLAllowedMetric,
                kIsPrivateAggregationAllowedMetric},
-           static_cast<int>(Status::kSiteDataAccessBlocked)}});
+           static_cast<int>(Status::kSiteDataAccessBlocked)},
+          {MultipleOutputKeys{kIsSharedStorageBlockSiteSettingSpecific,
+                              kIsSharedStorageSelectURLBlockSiteSettingSpecific,
+                              kIsPrivateAggregationBlockSiteSettingSpecific},
+           &kTrue_}});
 }
 
 TEST_F(PrivacySandboxSettingsM1Test, SiteDataBlockExceptionApplies) {
@@ -1271,7 +1316,14 @@
           {kAdMeasurementSourceOrigin,
            url::Origin::Create(GURL("https://source-origin.com"))},
           {kAdMeasurementDestinationOrigin,
-           url::Origin::Create(GURL("https://dest-origin.com"))}},
+           url::Origin::Create(GURL("https://dest-origin.com"))},
+
+          {kOutSharedStorageBlockIsSiteSettingSpecific,
+           &actual_out_shared_storage_block_is_site_setting_specific_},
+          {kOutSharedStorageSelectURLBlockIsSiteSettingSpecific,
+           &actual_out_select_url_block_is_site_setting_specific_},
+          {kOutPrivateAggregationBlockIsSiteSettingSpecific,
+           &actual_out_private_aggregation_block_is_site_setting_specific_}},
       TestOutput{
           {MultipleOutputKeys{
                kIsTopicsAllowed, kIsTopicsAllowedForContext,
@@ -1293,7 +1345,11 @@
                kMaySendAttributionReportMetric, kIsSharedStorageAllowedMetric,
                kIsSharedStorageSelectURLAllowedMetric,
                kIsPrivateAggregationAllowedMetric},
-           static_cast<int>(Status::kIncognitoProfile)}});
+           static_cast<int>(Status::kIncognitoProfile)},
+          {MultipleOutputKeys{kIsSharedStorageBlockSiteSettingSpecific,
+                              kIsSharedStorageSelectURLBlockSiteSettingSpecific,
+                              kIsPrivateAggregationBlockSiteSettingSpecific},
+           &kFalse_}});
 }
 
 TEST_F(PrivacySandboxSettingsM1Test, ApisAreOffForRestrictedAccounts) {
@@ -1312,7 +1368,14 @@
           {kAdMeasurementSourceOrigin,
            url::Origin::Create(GURL("https://source-origin.com"))},
           {kAdMeasurementDestinationOrigin,
-           url::Origin::Create(GURL("https://dest-origin.com"))}},
+           url::Origin::Create(GURL("https://dest-origin.com"))},
+
+          {kOutSharedStorageBlockIsSiteSettingSpecific,
+           &actual_out_shared_storage_block_is_site_setting_specific_},
+          {kOutSharedStorageSelectURLBlockIsSiteSettingSpecific,
+           &actual_out_select_url_block_is_site_setting_specific_},
+          {kOutPrivateAggregationBlockIsSiteSettingSpecific,
+           &actual_out_private_aggregation_block_is_site_setting_specific_}},
       TestOutput{
           {MultipleOutputKeys{
                kIsTopicsAllowed, kIsTopicsAllowedForContext,
@@ -1334,7 +1397,11 @@
                kMaySendAttributionReportMetric, kIsSharedStorageAllowedMetric,
                kIsSharedStorageSelectURLAllowedMetric,
                kIsPrivateAggregationAllowedMetric},
-           static_cast<int>(Status::kRestricted)}});
+           static_cast<int>(Status::kRestricted)},
+          {MultipleOutputKeys{kIsSharedStorageBlockSiteSettingSpecific,
+                              kIsSharedStorageSelectURLBlockSiteSettingSpecific,
+                              kIsPrivateAggregationBlockSiteSettingSpecific},
+           &kFalse_}});
 }
 
 TEST_F(PrivacySandboxSettingsM1Test,
@@ -1539,7 +1606,12 @@
            url::Origin::Create(GURL(top_frame_url))},
           {kAdMeasurementDestinationOrigin,
            url::Origin::Create(GURL(top_frame_url))},
-          {kAccessingOrigin, url::Origin::Create(enrollee_url)}},
+          {kAccessingOrigin, url::Origin::Create(enrollee_url)},
+
+          {kOutSharedStorageBlockIsSiteSettingSpecific,
+           &actual_out_shared_storage_block_is_site_setting_specific_},
+          {kOutPrivateAggregationBlockIsSiteSettingSpecific,
+           &actual_out_private_aggregation_block_is_site_setting_specific_}},
       TestOutput{
           {MultipleOutputKeys{
                kIsTopicsAllowedForContext, kIsAttributionReportingAllowed,
@@ -1563,7 +1635,10 @@
                kIsEventReportingDestinationAttestedForFledgeMetric},
            static_cast<int>(IsAttestationsDefaultAllowed()
                                 ? Status::kAllowed
-                                : Status::kAttestationsFileNotYetReady)}});
+                                : Status::kAttestationsFileNotYetReady)},
+          {MultipleOutputKeys{kIsSharedStorageBlockSiteSettingSpecific,
+                              kIsPrivateAggregationBlockSiteSettingSpecific},
+           &kFalse_}});
 }
 
 // When the attestations map has no enrollments at all (i.e., no enrollment
@@ -1587,7 +1662,12 @@
            url::Origin::Create(GURL(top_frame_url))},
           {kAdMeasurementDestinationOrigin,
            url::Origin::Create(GURL(top_frame_url))},
-          {kAccessingOrigin, url::Origin::Create(enrollee_url)}},
+          {kAccessingOrigin, url::Origin::Create(enrollee_url)},
+
+          {kOutSharedStorageBlockIsSiteSettingSpecific,
+           &actual_out_shared_storage_block_is_site_setting_specific_},
+          {kOutPrivateAggregationBlockIsSiteSettingSpecific,
+           &actual_out_private_aggregation_block_is_site_setting_specific_}},
       TestOutput{
           {MultipleOutputKeys{
                kIsTopicsAllowedForContext, kIsAttributionReportingAllowed,
@@ -1609,7 +1689,10 @@
                kIsPrivateAggregationAllowedMetric,
                kIsEventReportingDestinationAttestedForSharedStorageMetric,
                kIsEventReportingDestinationAttestedForFledgeMetric},
-           static_cast<int>(Status::kAttestationFailed)}});
+           static_cast<int>(Status::kAttestationFailed)},
+          {MultipleOutputKeys{kIsSharedStorageBlockSiteSettingSpecific,
+                              kIsPrivateAggregationBlockSiteSettingSpecific},
+           &kFalse_}});
 }
 
 // When the site in question is enrolled but has no attestations at all (i.e.,
@@ -1732,7 +1815,12 @@
            url::Origin::Create(GURL(top_frame_url))},
           {kAdMeasurementDestinationOrigin,
            url::Origin::Create(GURL(top_frame_url))},
-          {kAccessingOrigin, url::Origin::Create(enrollee_url)}},
+          {kAccessingOrigin, url::Origin::Create(enrollee_url)},
+
+          {kOutSharedStorageBlockIsSiteSettingSpecific,
+           &actual_out_shared_storage_block_is_site_setting_specific_},
+          {kOutPrivateAggregationBlockIsSiteSettingSpecific,
+           &actual_out_private_aggregation_block_is_site_setting_specific_}},
       TestOutput{
           {MultipleOutputKeys{kIsPrivateAggregationAllowed,
                               kIsPrivateAggregationDebugModeAllowed},
@@ -1757,7 +1845,10 @@
                kIsSharedStorageAllowedMetric,
                kIsEventReportingDestinationAttestedForSharedStorageMetric,
                kIsEventReportingDestinationAttestedForFledgeMetric},
-           static_cast<int>(Status::kAttestationFailed)}});
+           static_cast<int>(Status::kAttestationFailed)},
+          {MultipleOutputKeys{kIsSharedStorageBlockSiteSettingSpecific,
+                              kIsPrivateAggregationBlockSiteSettingSpecific},
+           &kFalse_}});
 }
 
 TEST_P(PrivacySandboxAttestationsTest, SharedStorageAttestation) {
@@ -1782,7 +1873,12 @@
            url::Origin::Create(GURL(top_frame_url))},
           {kAdMeasurementDestinationOrigin,
            url::Origin::Create(GURL(top_frame_url))},
-          {kAccessingOrigin, url::Origin::Create(enrollee_url)}},
+          {kAccessingOrigin, url::Origin::Create(enrollee_url)},
+
+          {kOutSharedStorageBlockIsSiteSettingSpecific,
+           &actual_out_shared_storage_block_is_site_setting_specific_},
+          {kOutPrivateAggregationBlockIsSiteSettingSpecific,
+           &actual_out_private_aggregation_block_is_site_setting_specific_}},
       TestOutput{
           {MultipleOutputKeys{
                kIsSharedStorageAllowed,
@@ -1809,7 +1905,10 @@
                kIsFledgeSellAllowedMetric, kIsFledgeBuyAllowedMetric,
                kIsPrivateAggregationAllowedMetric,
                kIsEventReportingDestinationAttestedForFledgeMetric},
-           static_cast<int>(Status::kAttestationFailed)}});
+           static_cast<int>(Status::kAttestationFailed)},
+          {MultipleOutputKeys{kIsSharedStorageBlockSiteSettingSpecific,
+                              kIsPrivateAggregationBlockSiteSettingSpecific},
+           &kFalse_}});
 }
 
 TEST_P(PrivacySandboxAttestationsTest, FledgeAttestation) {
@@ -2204,7 +2303,12 @@
           {kOutSharedStorageDebugMessage,
            &actual_out_shared_storage_debug_message_},
           {kOutSharedStorageSelectURLDebugMessage,
-           &actual_out_select_url_debug_message_}},
+           &actual_out_select_url_debug_message_},
+
+          {kOutSharedStorageBlockIsSiteSettingSpecific,
+           &actual_out_shared_storage_block_is_site_setting_specific_},
+          {kOutSharedStorageSelectURLBlockIsSiteSettingSpecific,
+           &actual_out_select_url_block_is_site_setting_specific_}},
       TestOutput{{MultipleOutputKeys{kIsSharedStorageAllowed,
                                      kIsSharedStorageSelectURLAllowed},
                   true},
@@ -2214,7 +2318,11 @@
                  {kIsSharedStorageAllowedDebugMessage,
                   &expected_out_shared_storage_debug_message},
                  {kIsSharedStorageSelectURLAllowedDebugMessage,
-                  &expected_out_select_url_debug_message}});
+                  &expected_out_select_url_debug_message},
+                 {MultipleOutputKeys{
+                      kIsSharedStorageBlockSiteSettingSpecific,
+                      kIsSharedStorageSelectURLBlockSiteSettingSpecific},
+                  &kFalse_}});
 }
 
 TEST_F(PrivacySandboxSettingsSharedStorageDebugTest, ApiPreferenceDisabled) {
@@ -2235,7 +2343,12 @@
           {kOutSharedStorageDebugMessage,
            &actual_out_shared_storage_debug_message_},
           {kOutSharedStorageSelectURLDebugMessage,
-           &actual_out_select_url_debug_message_}},
+           &actual_out_select_url_debug_message_},
+
+          {kOutSharedStorageBlockIsSiteSettingSpecific,
+           &actual_out_shared_storage_block_is_site_setting_specific_},
+          {kOutSharedStorageSelectURLBlockIsSiteSettingSpecific,
+           &actual_out_select_url_block_is_site_setting_specific_}},
       TestOutput{
           {kIsSharedStorageAllowed, true},
           {kIsSharedStorageSelectURLAllowed, false},
@@ -2245,7 +2358,11 @@
           {kIsSharedStorageAllowedDebugMessage,
            &expected_out_shared_storage_debug_message},
           {kIsSharedStorageSelectURLAllowedDebugMessage,
-           &expected_out_select_url_debug_message}});
+           &expected_out_select_url_debug_message},
+          {MultipleOutputKeys{
+               kIsSharedStorageBlockSiteSettingSpecific,
+               kIsSharedStorageSelectURLBlockSiteSettingSpecific},
+           &kFalse_}});
 }
 
 TEST_F(PrivacySandboxSettingsSharedStorageDebugTest,
@@ -2268,7 +2385,12 @@
           {kOutSharedStorageDebugMessage,
            &actual_out_shared_storage_debug_message_},
           {kOutSharedStorageSelectURLDebugMessage,
-           &actual_out_select_url_debug_message_}},
+           &actual_out_select_url_debug_message_},
+
+          {kOutSharedStorageBlockIsSiteSettingSpecific,
+           &actual_out_shared_storage_block_is_site_setting_specific_},
+          {kOutSharedStorageSelectURLBlockIsSiteSettingSpecific,
+           &actual_out_select_url_block_is_site_setting_specific_}},
       TestOutput{{MultipleOutputKeys{kIsSharedStorageAllowed,
                                      kIsSharedStorageSelectURLAllowed},
                   false},
@@ -2278,7 +2400,11 @@
                  {kIsSharedStorageAllowedDebugMessage,
                   &expected_out_shared_storage_debug_message},
                  {kIsSharedStorageSelectURLAllowedDebugMessage,
-                  &expected_out_select_url_debug_message}});
+                  &expected_out_select_url_debug_message},
+                 {MultipleOutputKeys{
+                      kIsSharedStorageBlockSiteSettingSpecific,
+                      kIsSharedStorageSelectURLBlockSiteSettingSpecific},
+                  &kFalse_}});
 }
 
 TEST_F(PrivacySandboxSettingsSharedStorageDebugTest,
@@ -2301,7 +2427,12 @@
           {kOutSharedStorageDebugMessage,
            &actual_out_shared_storage_debug_message_},
           {kOutSharedStorageSelectURLDebugMessage,
-           &actual_out_select_url_debug_message_}},
+           &actual_out_select_url_debug_message_},
+
+          {kOutSharedStorageBlockIsSiteSettingSpecific,
+           &actual_out_shared_storage_block_is_site_setting_specific_},
+          {kOutSharedStorageSelectURLBlockIsSiteSettingSpecific,
+           &actual_out_select_url_block_is_site_setting_specific_}},
       TestOutput{{MultipleOutputKeys{kIsSharedStorageAllowed,
                                      kIsSharedStorageSelectURLAllowed},
                   false},
@@ -2311,13 +2442,18 @@
                  {kIsSharedStorageAllowedDebugMessage,
                   &expected_out_shared_storage_debug_message},
                  {kIsSharedStorageSelectURLAllowedDebugMessage,
-                  &expected_out_select_url_debug_message}});
+                  &expected_out_select_url_debug_message},
+                 {MultipleOutputKeys{
+                      kIsSharedStorageBlockSiteSettingSpecific,
+                      kIsSharedStorageSelectURLBlockSiteSettingSpecific},
+                  &kTrue_}});
 }
 
 TEST_F(PrivacySandboxSettingsSharedStorageDebugTest, NoEnrollments) {
   std::string expected_out_shared_storage_debug_message =
       base::StringPrintf(attestation_failed_template, "https://embedded.com",
                          static_cast<int>(Status::kAttestationFailed));
+  ;
 
   // Confirm that the expected debug message is received when attestation fails
   // due to no enrollments.
@@ -2328,12 +2464,16 @@
           {kTopFrameOrigin, url::Origin::Create(GURL("https://top-frame.com"))},
           {kAccessingOrigin, url::Origin::Create(GURL("https://embedded.com"))},
           {kOutSharedStorageDebugMessage,
-           &actual_out_shared_storage_debug_message_}},
+           &actual_out_shared_storage_debug_message_},
+
+          {kOutSharedStorageBlockIsSiteSettingSpecific,
+           &actual_out_shared_storage_block_is_site_setting_specific_}},
       TestOutput{{kIsSharedStorageAllowed, false},
                  {kIsSharedStorageAllowedMetric,
                   static_cast<int>(Status::kAttestationFailed)},
                  {kIsSharedStorageAllowedDebugMessage,
-                  &expected_out_shared_storage_debug_message}});
+                  &expected_out_shared_storage_debug_message},
+                 {kIsSharedStorageBlockSiteSettingSpecific, &kFalse_}});
 }
 
 }  // namespace privacy_sandbox
diff --git a/components/privacy_sandbox/privacy_sandbox_test_util.cc b/components/privacy_sandbox/privacy_sandbox_test_util.cc
index e1fa03a..f35cdd4 100644
--- a/components/privacy_sandbox/privacy_sandbox_test_util.cc
+++ b/components/privacy_sandbox/privacy_sandbox_test_util.cc
@@ -506,8 +506,11 @@
       auto accessing_origin =
           GetItemValueForKey<url::Origin>(InputKey::kAccessingOrigin, input);
       auto return_value = GetItemValue<bool>(output_value);
-      ASSERT_EQ(return_value, privacy_sandbox_settings->IsSharedStorageAllowed(
-                                  top_frame_origin, accessing_origin));
+      ASSERT_EQ(return_value,
+                privacy_sandbox_settings->IsSharedStorageAllowed(
+                    top_frame_origin, accessing_origin,
+                    /*out_debug_message=*/nullptr, /*console_frame=*/nullptr,
+                    /*out_block_is_site_setting_specific=*/nullptr));
       return;
     }
 
@@ -518,9 +521,11 @@
       auto accessing_origin =
           GetItemValueForKey<url::Origin>(InputKey::kAccessingOrigin, input);
       auto return_value = GetItemValue<bool>(output_value);
-      ASSERT_EQ(return_value,
-                privacy_sandbox_settings->IsSharedStorageSelectURLAllowed(
-                    top_frame_origin, accessing_origin));
+      ASSERT_EQ(
+          return_value,
+          privacy_sandbox_settings->IsSharedStorageSelectURLAllowed(
+              top_frame_origin, accessing_origin, /*out_debug_message=*/nullptr,
+              /*out_block_is_site_setting_specific=*/nullptr));
       return;
     }
 
@@ -533,7 +538,8 @@
       auto return_value = GetItemValue<bool>(output_value);
       ASSERT_EQ(return_value,
                 privacy_sandbox_settings->IsPrivateAggregationAllowed(
-                    top_frame_origin, reporting_origin));
+                    top_frame_origin, reporting_origin,
+                    /*out_block_is_site_setting_specific=*/nullptr));
       return;
     }
 
@@ -686,7 +692,9 @@
       auto accessing_origin =
           GetItemValueForKey<url::Origin>(InputKey::kAccessingOrigin, input);
       std::ignore = privacy_sandbox_settings->IsSharedStorageAllowed(
-          top_frame_origin, accessing_origin);
+          top_frame_origin, accessing_origin, /*out_debug_message=*/nullptr,
+          /*console_frame=*/nullptr,
+          /*out_block_is_site_setting_specific=*/nullptr);
       auto histogram_value = GetItemValue<int>(output_value);
       histogram_tester.ExpectUniqueSample(
           "PrivacySandbox.IsSharedStorageAllowed", histogram_value, 1);
@@ -701,7 +709,8 @@
       auto accessing_origin =
           GetItemValueForKey<url::Origin>(InputKey::kAccessingOrigin, input);
       std::ignore = privacy_sandbox_settings->IsSharedStorageSelectURLAllowed(
-          top_frame_origin, accessing_origin);
+          top_frame_origin, accessing_origin, /*out_debug_message=*/nullptr,
+          /*out_block_is_site_setting_specific=*/nullptr);
       auto histogram_value = GetItemValue<int>(output_value);
       histogram_tester.ExpectUniqueSample(
           "PrivacySandbox.IsSharedStorageSelectURLAllowed", histogram_value, 1);
@@ -715,7 +724,8 @@
       auto reporting_origin = GetItemValueForKey<url::Origin>(
           InputKey::kAdMeasurementReportingOrigin, input);
       std::ignore = privacy_sandbox_settings->IsPrivateAggregationAllowed(
-          top_frame_origin, reporting_origin);
+          top_frame_origin, reporting_origin,
+          /*out_block_is_site_setting_specific=*/nullptr);
       auto histogram_value = GetItemValue<int>(output_value);
       histogram_tester.ExpectUniqueSample(
           "PrivacySandbox.IsPrivateAggregationAllowed", histogram_value, 1);
@@ -898,7 +908,9 @@
       std::string* actual_out_debug_message = GetItemValueForKey<std::string*>(
           InputKey::kOutSharedStorageDebugMessage, input);
       privacy_sandbox_settings->IsSharedStorageAllowed(
-          top_frame_origin, accessing_origin, actual_out_debug_message);
+          top_frame_origin, accessing_origin, actual_out_debug_message,
+          /*console_frame=*/nullptr,
+          /*out_block_is_site_setting_specific=*/nullptr);
       std::string* expected_out_debug_message =
           GetItemValue<std::string*>(output_value);
       ASSERT_EQ(!!actual_out_debug_message, !!expected_out_debug_message);
@@ -919,7 +931,8 @@
       std::string* actual_out_debug_message = GetItemValueForKey<std::string*>(
           InputKey::kOutSharedStorageSelectURLDebugMessage, input);
       privacy_sandbox_settings->IsSharedStorageSelectURLAllowed(
-          top_frame_origin, accessing_origin, actual_out_debug_message);
+          top_frame_origin, accessing_origin, actual_out_debug_message,
+          /*out_block_is_site_setting_specific=*/nullptr);
       std::string* expected_out_debug_message =
           GetItemValue<std::string*>(output_value);
       ASSERT_EQ(!!actual_out_debug_message, !!expected_out_debug_message);
@@ -928,6 +941,76 @@
       }
       return;
     }
+    case (OutputKey::kIsSharedStorageBlockSiteSettingSpecific): {
+      SCOPED_TRACE(
+          "Check Output: Verify out_is_block_site_specific in "
+          "IsSharedStorageAllowed()");
+      auto top_frame_origin =
+          GetItemValueForKey<url::Origin>(InputKey::kTopFrameOrigin, input);
+      auto accessing_origin =
+          GetItemValueForKey<url::Origin>(InputKey::kAccessingOrigin, input);
+      bool* actual_out_is_block_site_specific = GetItemValueForKey<bool*>(
+          InputKey::kOutSharedStorageBlockIsSiteSettingSpecific, input);
+      privacy_sandbox_settings->IsSharedStorageAllowed(
+          top_frame_origin, accessing_origin, /*out_debug_message=*/nullptr,
+          /*console_frame=*/nullptr, actual_out_is_block_site_specific);
+      bool* expected_out_is_block_site_specific =
+          GetItemValue<bool*>(output_value);
+      ASSERT_EQ(!!actual_out_is_block_site_specific,
+                !!expected_out_is_block_site_specific);
+      if (expected_out_is_block_site_specific) {
+        ASSERT_EQ(*actual_out_is_block_site_specific,
+                  *expected_out_is_block_site_specific);
+      }
+      return;
+    }
+    case (OutputKey::kIsSharedStorageSelectURLBlockSiteSettingSpecific): {
+      SCOPED_TRACE(
+          "Check Output: Verify out_is_block_site_specific in "
+          "IsSharedStorageSelectURLAllowed()");
+      auto top_frame_origin =
+          GetItemValueForKey<url::Origin>(InputKey::kTopFrameOrigin, input);
+      auto accessing_origin =
+          GetItemValueForKey<url::Origin>(InputKey::kAccessingOrigin, input);
+      bool* actual_out_is_block_site_specific = GetItemValueForKey<bool*>(
+          InputKey::kOutSharedStorageSelectURLBlockIsSiteSettingSpecific,
+          input);
+      privacy_sandbox_settings->IsSharedStorageSelectURLAllowed(
+          top_frame_origin, accessing_origin, /*out_debug_message=*/nullptr,
+          actual_out_is_block_site_specific);
+      bool* expected_out_is_block_site_specific =
+          GetItemValue<bool*>(output_value);
+      ASSERT_EQ(!!actual_out_is_block_site_specific,
+                !!expected_out_is_block_site_specific);
+      if (expected_out_is_block_site_specific) {
+        ASSERT_EQ(*actual_out_is_block_site_specific,
+                  *expected_out_is_block_site_specific);
+      }
+      return;
+    }
+    case (OutputKey::kIsPrivateAggregationBlockSiteSettingSpecific): {
+      SCOPED_TRACE(
+          "Check Output: Verify out_is_block_site_specific in "
+          "IsPrivateAggregationAllowed()");
+      auto top_frame_origin =
+          GetItemValueForKey<url::Origin>(InputKey::kTopFrameOrigin, input);
+      auto accessing_origin =
+          GetItemValueForKey<url::Origin>(InputKey::kAccessingOrigin, input);
+      bool* actual_out_is_block_site_specific = GetItemValueForKey<bool*>(
+          InputKey::kOutPrivateAggregationBlockIsSiteSettingSpecific, input);
+      privacy_sandbox_settings->IsPrivateAggregationAllowed(
+          top_frame_origin, accessing_origin,
+          actual_out_is_block_site_specific);
+      bool* expected_out_is_block_site_specific =
+          GetItemValue<bool*>(output_value);
+      ASSERT_EQ(!!actual_out_is_block_site_specific,
+                !!expected_out_is_block_site_specific);
+      if (expected_out_is_block_site_specific) {
+        ASSERT_EQ(*actual_out_is_block_site_specific,
+                  *expected_out_is_block_site_specific);
+      }
+      return;
+    }
   }
 }
 
diff --git a/components/privacy_sandbox/privacy_sandbox_test_util.h b/components/privacy_sandbox/privacy_sandbox_test_util.h
index 511e0094..35b5ecd 100644
--- a/components/privacy_sandbox/privacy_sandbox_test_util.h
+++ b/components/privacy_sandbox/privacy_sandbox_test_util.h
@@ -187,6 +187,9 @@
   kEventReportingDestinationOrigin = 11,
   kOutSharedStorageDebugMessage = 12,
   kOutSharedStorageSelectURLDebugMessage = 13,
+  kOutSharedStorageBlockIsSiteSettingSpecific = 14,
+  kOutSharedStorageSelectURLBlockIsSiteSettingSpecific = 15,
+  kOutPrivateAggregationBlockIsSiteSettingSpecific = 16,
 };
 
 // Defines the expected output of the functions under test, when the profile is
@@ -239,6 +242,9 @@
   kIsPrivateAggregationDebugModeAllowed = 47,
   kIsSharedStorageAllowedDebugMessage = 48,
   kIsSharedStorageSelectURLAllowedDebugMessage = 49,
+  kIsSharedStorageBlockSiteSettingSpecific = 50,
+  kIsSharedStorageSelectURLBlockSiteSettingSpecific = 51,
+  kIsPrivateAggregationBlockSiteSettingSpecific = 52,
 };
 
 // To allow multiple input keys to map to the same value, without having to
@@ -264,6 +270,7 @@
 // a particular value type, and will error otherwise.
 using TestCaseItemValue = absl::variant<
     bool,
+    bool*,
     std::string,
     std::string*,
     url::Origin,
diff --git a/components/privacy_sandbox/privacy_sandbox_test_util_unittest.cc b/components/privacy_sandbox/privacy_sandbox_test_util_unittest.cc
index 6f3c2b4..a8f8193 100644
--- a/components/privacy_sandbox/privacy_sandbox_test_util_unittest.cc
+++ b/components/privacy_sandbox/privacy_sandbox_test_util_unittest.cc
@@ -23,6 +23,17 @@
 
 namespace {
 
+constexpr char kAccessingOrigin[] = "https://storage.com";
+constexpr char kTopFrameOrigin[] = "https://top-frame.com";
+
+static url::Origin AccessingOrigin() {
+  return url::Origin::Create(GURL(kAccessingOrigin));
+}
+
+static url::Origin TopFrameOrigin() {
+  return url::Origin::Create(GURL(kTopFrameOrigin));
+}
+
 class MockPrivacySandboxServiceTestInterface
     : public PrivacySandboxServiceTestInterface {
  public:
@@ -320,14 +331,13 @@
 
 TEST_F(PrivacySandboxTestUtilTest, OutputKey_IsTopicsAllowedForContext) {
   GURL kTopicsURL = GURL("https://topics.com");
-  url::Origin kTopFrameOrigin =
-      url::Origin::Create(GURL("https://top-frame.com"));
+
   EXPECT_CALL(*mock_privacy_sandbox_settings(),
-              IsTopicsAllowedForContext(kTopFrameOrigin, kTopicsURL, nullptr))
+              IsTopicsAllowedForContext(TopFrameOrigin(), kTopicsURL, nullptr))
       .WillOnce(testing::Return(true));
 
   CheckOutput({{InputKey::kTopicsURL, kTopicsURL},
-               {InputKey::kTopFrameOrigin, kTopFrameOrigin}},
+               {InputKey::kTopFrameOrigin, TopFrameOrigin()}},
               {OutputKey::kIsTopicsAllowedForContext, true});
 }
 
@@ -340,32 +350,30 @@
 TEST_F(PrivacySandboxTestUtilTest, OutputKey_IsFledgeAllowed) {
   url::Origin kFledgeAuctionPartyOrigin =
       url::Origin::Create(GURL("https://fledge.com"));
-  url::Origin kTopFrameOrigin =
-      url::Origin::Create(GURL("https://top-frame.com"));
+
   EXPECT_CALL(
       *mock_privacy_sandbox_settings(),
-      IsFledgeAllowed(kTopFrameOrigin, kFledgeAuctionPartyOrigin,
+      IsFledgeAllowed(TopFrameOrigin(), kFledgeAuctionPartyOrigin,
                       content::InterestGroupApiOperation::kJoin, nullptr))
       .WillOnce(testing::Return(true));
 
   CheckOutput({{InputKey::kFledgeAuctionPartyOrigin, kFledgeAuctionPartyOrigin},
-               {InputKey::kTopFrameOrigin, kTopFrameOrigin}},
+               {InputKey::kTopFrameOrigin, TopFrameOrigin()}},
               {OutputKey::kIsFledgeJoinAllowed, true});
 }
 
 TEST_F(PrivacySandboxTestUtilTest, OutputKey_IsAttributionReportingAllowed) {
   url::Origin kAdMeasurementReportingOrigin =
       url::Origin::Create(GURL("https://measurement.com"));
-  url::Origin kTopFrameOrigin =
-      url::Origin::Create(GURL("https://top-frame.com"));
+
   EXPECT_CALL(*mock_privacy_sandbox_settings(),
               IsAttributionReportingAllowed(
-                  kTopFrameOrigin, kAdMeasurementReportingOrigin, nullptr))
+                  TopFrameOrigin(), kAdMeasurementReportingOrigin, nullptr))
       .WillOnce(testing::Return(true));
 
   CheckOutput(
       {{InputKey::kAdMeasurementReportingOrigin, kAdMeasurementReportingOrigin},
-       {InputKey::kTopFrameOrigin, kTopFrameOrigin}},
+       {InputKey::kTopFrameOrigin, TopFrameOrigin()}},
       {OutputKey::kIsAttributionReportingAllowed, true});
 }
 
@@ -392,49 +400,45 @@
 }
 
 TEST_F(PrivacySandboxTestUtilTest, OutputKey_IsSharedStorageAllowed) {
-  url::Origin kAccessingOrigin =
-      url::Origin::Create(GURL("https://storage.com"));
-  url::Origin kTopFrameOrigin =
-      url::Origin::Create(GURL("https://top-frame.com"));
-  EXPECT_CALL(*mock_privacy_sandbox_settings(),
-              IsSharedStorageAllowed(kTopFrameOrigin, kAccessingOrigin,
-                                     /*out_debug_message=*/nullptr,
-                                     /*console_frame=*/nullptr))
+  EXPECT_CALL(
+      *mock_privacy_sandbox_settings(),
+      IsSharedStorageAllowed(TopFrameOrigin(), AccessingOrigin(),
+                             /*out_debug_message=*/nullptr,
+                             /*console_frame=*/nullptr,
+                             /*out_block_is_site_setting_specific=*/nullptr))
       .WillOnce(testing::Return(true));
 
-  CheckOutput({{InputKey::kAccessingOrigin, kAccessingOrigin},
-               {InputKey::kTopFrameOrigin, kTopFrameOrigin}},
+  CheckOutput({{InputKey::kAccessingOrigin, AccessingOrigin()},
+               {InputKey::kTopFrameOrigin, TopFrameOrigin()}},
               {OutputKey::kIsSharedStorageAllowed, true});
 }
 
 TEST_F(PrivacySandboxTestUtilTest, OutputKey_IsSharedStorageSelectURLAllowed) {
-  url::Origin kAccessingOrigin =
-      url::Origin::Create(GURL("https://storage.com"));
-  url::Origin kTopFrameOrigin =
-      url::Origin::Create(GURL("https://top-frame.com"));
   EXPECT_CALL(*mock_privacy_sandbox_settings(),
-              IsSharedStorageSelectURLAllowed(kTopFrameOrigin, kAccessingOrigin,
-                                              /*out_debug_message=*/nullptr))
+              IsSharedStorageSelectURLAllowed(
+                  TopFrameOrigin(), AccessingOrigin(),
+                  /*out_debug_message=*/nullptr,
+                  /*out_block_is_site_setting_specific=*/nullptr))
       .WillOnce(testing::Return(true));
 
-  CheckOutput({{InputKey::kAccessingOrigin, kAccessingOrigin},
-               {InputKey::kTopFrameOrigin, kTopFrameOrigin}},
+  CheckOutput({{InputKey::kAccessingOrigin, AccessingOrigin()},
+               {InputKey::kTopFrameOrigin, TopFrameOrigin()}},
               {OutputKey::kIsSharedStorageSelectURLAllowed, true});
 }
 
 TEST_F(PrivacySandboxTestUtilTest, OutputKey_IsPrivateAggregationAllowed) {
   url::Origin kAdMeasurementReportingOrigin =
       url::Origin::Create(GURL("https://reporting.com"));
-  url::Origin kTopFrameOrigin =
-      url::Origin::Create(GURL("https://top-frame.com"));
+
   EXPECT_CALL(*mock_privacy_sandbox_settings(),
-              IsPrivateAggregationAllowed(kTopFrameOrigin,
-                                          kAdMeasurementReportingOrigin))
+              IsPrivateAggregationAllowed(
+                  TopFrameOrigin(), kAdMeasurementReportingOrigin,
+                  /*out_block_is_site_setting_specific=*/nullptr))
       .WillOnce(testing::Return(true));
 
   CheckOutput(
       {{InputKey::kAdMeasurementReportingOrigin, kAdMeasurementReportingOrigin},
-       {InputKey::kTopFrameOrigin, kTopFrameOrigin}},
+       {InputKey::kTopFrameOrigin, TopFrameOrigin()}},
       {OutputKey::kIsPrivateAggregationAllowed, true});
 }
 
@@ -471,24 +475,21 @@
 
 TEST_F(PrivacySandboxTestUtilTest,
        OutputKey_IsSharedStorageAllowedDebugMessage) {
-  url::Origin kAccessingOrigin =
-      url::Origin::Create(GURL("https://storage.com"));
-  url::Origin kTopFrameOrigin =
-      url::Origin::Create(GURL("https://top-frame.com"));
   std::string actual_out_debug_message;
   EXPECT_CALL(
       *mock_privacy_sandbox_settings(),
-      IsSharedStorageAllowed(kTopFrameOrigin, kAccessingOrigin,
+      IsSharedStorageAllowed(TopFrameOrigin(), AccessingOrigin(),
                              /*out_debug_message=*/&actual_out_debug_message,
-                             /*console_frame=*/nullptr))
+                             /*console_frame=*/nullptr,
+                             /*out_block_is_site_setting_specific=*/nullptr))
       .WillOnce(testing::Return(true));
 
   // The expected debug message is a non-null empty string here because we using
   // a mock method.
   std::string expected_out_debug_message;
   CheckOutput(
-      {{InputKey::kAccessingOrigin, kAccessingOrigin},
-       {InputKey::kTopFrameOrigin, kTopFrameOrigin},
+      {{InputKey::kAccessingOrigin, AccessingOrigin()},
+       {InputKey::kTopFrameOrigin, TopFrameOrigin()},
        {InputKey::kOutSharedStorageDebugMessage, &actual_out_debug_message}},
       {OutputKey::kIsSharedStorageAllowedDebugMessage,
        &expected_out_debug_message});
@@ -496,26 +497,92 @@
 
 TEST_F(PrivacySandboxTestUtilTest,
        OutputKey_IsSharedStorageSelectURLAllowedDebugMessage) {
-  url::Origin kAccessingOrigin =
-      url::Origin::Create(GURL("https://storage.com"));
-  url::Origin kTopFrameOrigin =
-      url::Origin::Create(GURL("https://top-frame.com"));
   std::string actual_out_debug_message;
   EXPECT_CALL(*mock_privacy_sandbox_settings(),
               IsSharedStorageSelectURLAllowed(
-                  kTopFrameOrigin, kAccessingOrigin,
-                  /*out_debug_message=*/&actual_out_debug_message))
+                  TopFrameOrigin(), AccessingOrigin(),
+                  /*out_debug_message=*/&actual_out_debug_message,
+                  /*out_block_is_site_setting_specific=*/nullptr))
       .WillOnce(testing::Return(true));
 
   // The expected debug message is a non-null empty string here because we using
   // a mock method.
   std::string expected_out_debug_message;
-  CheckOutput({{InputKey::kAccessingOrigin, kAccessingOrigin},
-               {InputKey::kTopFrameOrigin, kTopFrameOrigin},
+  CheckOutput({{InputKey::kAccessingOrigin, AccessingOrigin()},
+               {InputKey::kTopFrameOrigin, TopFrameOrigin()},
                {InputKey::kOutSharedStorageSelectURLDebugMessage,
                 &actual_out_debug_message}},
               {OutputKey::kIsSharedStorageSelectURLAllowedDebugMessage,
                &expected_out_debug_message});
 }
 
+TEST_F(PrivacySandboxTestUtilTest,
+       OutputKey_IsSharedStorageBlockSiteSettingSpecific) {
+  bool actual_out_block_is_site_setting_specific = true;
+  EXPECT_CALL(
+      *mock_privacy_sandbox_settings(),
+      IsSharedStorageAllowed(TopFrameOrigin(), AccessingOrigin(),
+                             /*out_debug_message=*/nullptr,
+                             /*console_frame=*/nullptr,
+                             /*out_block_is_site_setting_specific=*/
+                             &actual_out_block_is_site_setting_specific))
+      .WillOnce(testing::DoAll(testing::SetArgPointee<4>(false),
+                               testing::Return(true)));
+
+  // The expected value for `out_block_is_site_setting_specific` here is false
+  // because we are using a mock method that sets it to false.
+  bool expected_out_block_is_site_setting_specific = false;
+  CheckOutput({{InputKey::kAccessingOrigin, AccessingOrigin()},
+               {InputKey::kTopFrameOrigin, TopFrameOrigin()},
+               {InputKey::kOutSharedStorageBlockIsSiteSettingSpecific,
+                &actual_out_block_is_site_setting_specific}},
+              {OutputKey::kIsSharedStorageBlockSiteSettingSpecific,
+               &expected_out_block_is_site_setting_specific});
+}
+
+TEST_F(PrivacySandboxTestUtilTest,
+       OutputKey_IsSharedStorageSelectURLBlockSiteSettingSpecific) {
+  bool actual_out_block_is_site_setting_specific = true;
+  EXPECT_CALL(*mock_privacy_sandbox_settings(),
+              IsSharedStorageSelectURLAllowed(
+                  TopFrameOrigin(), AccessingOrigin(),
+                  /*out_debug_message=*/nullptr,
+                  /*out_block_is_site_setting_specific=*/
+                  &actual_out_block_is_site_setting_specific))
+      .WillOnce(testing::DoAll(testing::SetArgPointee<3>(false),
+                               testing::Return(true)));
+
+  // The expected value for `out_block_is_site_setting_specific` here is false
+  // because we are using a mock method that sets it to false.
+  bool expected_out_block_is_site_setting_specific = false;
+  CheckOutput({{InputKey::kAccessingOrigin, AccessingOrigin()},
+               {InputKey::kTopFrameOrigin, TopFrameOrigin()},
+               {InputKey::kOutSharedStorageSelectURLBlockIsSiteSettingSpecific,
+                &actual_out_block_is_site_setting_specific}},
+              {OutputKey::kIsSharedStorageSelectURLBlockSiteSettingSpecific,
+               &expected_out_block_is_site_setting_specific});
+}
+
+TEST_F(PrivacySandboxTestUtilTest,
+       OutputKey_IsPrivateAggregationBlockSiteSettingSpecific) {
+  bool actual_out_block_is_site_setting_specific = true;
+  EXPECT_CALL(
+      *mock_privacy_sandbox_settings(),
+      IsPrivateAggregationAllowed(TopFrameOrigin(), AccessingOrigin(),
+                                  /*out_block_is_site_setting_specific=*/
+                                  &actual_out_block_is_site_setting_specific))
+      .WillOnce(testing::DoAll(testing::SetArgPointee<2>(false),
+                               testing::Return(true)));
+
+  // The expected value for `out_block_is_site_setting_specific` here is false
+  // because we are using a mock method that sets it to false.
+  bool expected_out_block_is_site_setting_specific = false;
+  CheckOutput({{InputKey::kAccessingOrigin, AccessingOrigin()},
+               {InputKey::kTopFrameOrigin, TopFrameOrigin()},
+               {InputKey::kOutPrivateAggregationBlockIsSiteSettingSpecific,
+                &actual_out_block_is_site_setting_specific}},
+              {OutputKey::kIsPrivateAggregationBlockSiteSettingSpecific,
+               &expected_out_block_is_site_setting_specific});
+}
+
 }  // namespace privacy_sandbox_test_util
diff --git a/components/privacy_sandbox_strings.grd b/components/privacy_sandbox_strings.grd
index 6b3b07c..165c561 100644
--- a/components/privacy_sandbox_strings.grd
+++ b/components/privacy_sandbox_strings.grd
@@ -289,13 +289,18 @@
       </message>
       <message name="IDS_PAGE_INFO_TRACKING_PROTECTION_TITLE_OFF" desc="" formatter_data="android_java" translateable="false">
         {COUNT, plural,
-              =0 {Protections are off until today}
               =1 {Protections are off until tomorrow}
               other {Protections are off for # days}}
       </message>
+      <message name="IDS_PAGE_INFO_TRACKING_PROTECTION_TITLE_OFF_TODAY" desc="" formatter_data="android_java" translateable="false">
+        Protections are off until today
+      </message>
       <message name="IDS_PAGE_INFO_TRACKING_PROTECTION_TITLE_OFF_PERMANENT" desc="" formatter_data="android_java" translateable="false">
         Protections are off
       </message>
+      <message name="IDS_PAGE_INFO_TRACKING_PROTECTION_TOGGLE_ON" desc="" formatter_data="android_java" translateable="false">
+        You have extra protections on this site to limit tracking while you browse, but some features might not work.
+      </message>
       <message name="IDS_PRIVACY_SANDBOX_TRACKING_PROTECTION_SNACKBAR_TITLE" desc="Title of the Tracking Protection snackbar." formatter_data="android_java" translateable="false">
         App not working?
       </message>
diff --git a/components/safe_browsing/core/common/features.cc b/components/safe_browsing/core/common/features.cc
index c1d010d..92a2b94 100644
--- a/components/safe_browsing/core/common/features.cc
+++ b/components/safe_browsing/core/common/features.cc
@@ -305,10 +305,6 @@
              "TailoredSecurityIntegration",
              base::FEATURE_ENABLED_BY_DEFAULT);
 
-BASE_FEATURE(kTailoredSecurityUpdatedMessages,
-             "TailoredSecurityUpdatedMessages",
-             base::FEATURE_ENABLED_BY_DEFAULT);
-
 BASE_FEATURE(kThreatDomDetailsTagAndAttributeFeature,
              "ThreatDomDetailsTagAttributes",
              base::FEATURE_DISABLED_BY_DEFAULT);
diff --git a/components/safe_browsing/core/common/features.h b/components/safe_browsing/core/common/features.h
index f96b4d1..28201699 100644
--- a/components/safe_browsing/core/common/features.h
+++ b/components/safe_browsing/core/common/features.h
@@ -247,9 +247,6 @@
 // Controls whether the integration of tailored security settings is enabled.
 BASE_DECLARE_FEATURE(kTailoredSecurityIntegration);
 
-// Enable new updated strings and icons for the Tailored Security dialogs.
-BASE_DECLARE_FEATURE(kTailoredSecurityUpdatedMessages);
-
 // Specifies which non-resource HTML Elements to collect based on their tag and
 // attributes. It's a single param containing a comma-separated list of pairs.
 // For example: "tag1,id,tag1,height,tag2,foo" - this will collect elements with
diff --git a/components/saved_tab_groups/saved_tab_group_model.cc b/components/saved_tab_groups/saved_tab_group_model.cc
index b511b2ae..0e7d151 100644
--- a/components/saved_tab_groups/saved_tab_group_model.cc
+++ b/components/saved_tab_groups/saved_tab_group_model.cc
@@ -563,6 +563,11 @@
   SavedTabGroup& saved_group = saved_tab_groups_[index.value()];
   saved_group.SetLocalGroupId(std::nullopt);
 
+  // Remove the ID mappings from the tabs as well, since the group is closed.
+  for (SavedTabGroupTab& saved_tab : saved_group.saved_tabs()) {
+    saved_tab.SetLocalTabID(std::nullopt);
+  }
+
   for (auto& observer : observers_) {
     observer.SavedTabGroupLocalIdChanged(saved_group.saved_guid());
   }
diff --git a/components/saved_tab_groups/tab_group_sync_service_impl.cc b/components/saved_tab_groups/tab_group_sync_service_impl.cc
index e1f066b..363fc29 100644
--- a/components/saved_tab_groups/tab_group_sync_service_impl.cc
+++ b/components/saved_tab_groups/tab_group_sync_service_impl.cc
@@ -375,6 +375,16 @@
   }
 }
 
+void TabGroupSyncServiceImpl::SavedTabGroupLocalIdChanged(
+    const base::Uuid& group_guid) {
+  VLOG(2) << __func__;
+  const SavedTabGroup* saved_tab_group = model_->Get(group_guid);
+  CHECK(saved_tab_group);
+  for (auto& observer : observers_) {
+    observer.OnTabGroupUpdated(*saved_tab_group, TriggerSource::LOCAL);
+  }
+}
+
 void TabGroupSyncServiceImpl::SavedTabGroupModelLoaded() {
   VLOG(2) << __func__;
 
diff --git a/components/saved_tab_groups/tab_group_sync_service_impl.h b/components/saved_tab_groups/tab_group_sync_service_impl.h
index 219cf0d..5679404 100644
--- a/components/saved_tab_groups/tab_group_sync_service_impl.h
+++ b/components/saved_tab_groups/tab_group_sync_service_impl.h
@@ -107,6 +107,7 @@
   void SavedTabGroupRemovedFromSync(
       const SavedTabGroup* removed_group) override;
   void SavedTabGroupRemovedLocally(const SavedTabGroup* removed_group) override;
+  void SavedTabGroupLocalIdChanged(const base::Uuid& saved_group_id) override;
   void SavedTabGroupModelLoaded() override;
 
   // Called on reading ID mapping from tab group store.
diff --git a/components/saved_tab_groups/tab_group_sync_service_unittest.cc b/components/saved_tab_groups/tab_group_sync_service_unittest.cc
index 4fe16837..fc8c378 100644
--- a/components/saved_tab_groups/tab_group_sync_service_unittest.cc
+++ b/components/saved_tab_groups/tab_group_sync_service_unittest.cc
@@ -528,6 +528,35 @@
   model_->UpdateVisualData(group_1_.saved_guid(), &visual_data);
 }
 
+TEST_F(TabGroupSyncServiceTest, OnTabGroupUpdatedOnTabGroupIdMappingChange) {
+  // Close a group.
+  EXPECT_CALL(*observer_, OnTabGroupUpdated(UuidEq(group_1_.saved_guid()),
+                                            Eq(TriggerSource::LOCAL)))
+      .Times(1);
+  model_->OnGroupClosedInTabStrip(local_group_id_1_);
+
+  // Open a group.
+  EXPECT_CALL(*observer_, OnTabGroupUpdated(UuidEq(group_2_.saved_guid()),
+                                            Eq(TriggerSource::LOCAL)))
+      .Times(1);
+  model_->OnGroupOpenedInTabStrip(group_2_.saved_guid(),
+                                  test::GenerateRandomTabGroupID());
+}
+
+TEST_F(TabGroupSyncServiceTest, TabIDMappingIsCleardOnGroupClose) {
+  auto group = tab_group_sync_service_->GetGroup(group_1_.saved_guid());
+  EXPECT_TRUE(group->local_group_id().has_value());
+  EXPECT_TRUE(group->saved_tabs()[0].local_tab_id().has_value());
+
+  // Close a group.
+  model_->OnGroupClosedInTabStrip(local_group_id_1_);
+
+  // Verify that tab IDs are unmapped.
+  group = tab_group_sync_service_->GetGroup(group_1_.saved_guid());
+  EXPECT_FALSE(group->local_group_id().has_value());
+  EXPECT_FALSE(group->saved_tabs()[0].local_tab_id().has_value());
+}
+
 TEST_F(TabGroupSyncServiceTest, OnTabGroupAddedNoTabs) {
   // Create a group with no tabs. Observers won't be notified.
   SavedTabGroup group_4 = test::CreateTestSavedTabGroupWithNoTabs();
diff --git a/components/segmentation_platform/public/features.cc b/components/segmentation_platform/public/features.cc
index 2d76a43..fb41e5c 100644
--- a/components/segmentation_platform/public/features.cc
+++ b/components/segmentation_platform/public/features.cc
@@ -69,10 +69,6 @@
              "ContextualPageActions",
              base::FEATURE_ENABLED_BY_DEFAULT);
 
-BASE_FEATURE(kContextualPageActionPriceTracking,
-             "ContextualPageActionPriceTracking",
-             base::FEATURE_ENABLED_BY_DEFAULT);
-
 BASE_FEATURE(kContextualPageActionReaderMode,
              "ContextualPageActionReaderMode",
              base::FEATURE_ENABLED_BY_DEFAULT);
diff --git a/components/segmentation_platform/public/features.h b/components/segmentation_platform/public/features.h
index 8447a81c..d0f958a 100644
--- a/components/segmentation_platform/public/features.h
+++ b/components/segmentation_platform/public/features.h
@@ -47,9 +47,6 @@
 // Feature flag for device switcher segment.
 BASE_DECLARE_FEATURE(kSegmentationPlatformDeviceSwitcher);
 
-// Feature flag for enabling price tracking action feature.
-BASE_DECLARE_FEATURE(kContextualPageActionPriceTracking);
-
 // Feature flag for enabling reader mode action feature.
 BASE_DECLARE_FEATURE(kContextualPageActionReaderMode);
 
diff --git a/components/signin/public/identity_manager/access_token_constants.cc b/components/signin/public/identity_manager/access_token_constants.cc
index bf71284..d9ca3b042 100644
--- a/components/signin/public/identity_manager/access_token_constants.cc
+++ b/components/signin/public/identity_manager/access_token_constants.cc
@@ -87,6 +87,9 @@
       GaiaConstants::kOptimizationGuideServiceGetHintsOAuth2Scope,
       GaiaConstants::kOptimizationGuideServiceModelExecutionOAuth2Scope,
 
+      // Required by Lens.
+      GaiaConstants::kLensOAuth2Scope,
+
       // Required by Omnibox / DocumentSuggestionsService.
       GaiaConstants::kCloudSearchQueryOAuth2Scope,
 
diff --git a/components/soda/soda_util.cc b/components/soda/soda_util.cc
index b3903159..8e8440d 100644
--- a/components/soda/soda_util.cc
+++ b/components/soda/soda_util.cc
@@ -4,12 +4,12 @@
 
 #include "components/soda/soda_util.h"
 
-#include "base/cpu.h"
-#include "base/feature_list.h"
 #include "build/build_config.h"
+#include "build/chromeos_buildflags.h"
 
 #if BUILDFLAG(IS_CHROMEOS_ASH)
 #include "ash/constants/ash_features.h"
+#include "base/feature_list.h"
 #endif
 
 #if BUILDFLAG(IS_CHROMEOS_LACROS)
@@ -20,9 +20,16 @@
 #include "base/win/windows_version.h"
 #endif
 
+#if BUILDFLAG(IS_LINUX) && defined(ARCH_CPU_X86_FAMILY)
+#include "base/cpu.h"
+#endif
+
 namespace speech {
 
-bool IsOnDeviceSpeechRecognitionSupported() {
+namespace {
+
+#if BUILDFLAG(IS_CHROMEOS)
+bool IsSupportedChromeOS() {
 // Some Chrome OS devices do not support on-device speech.
 #if BUILDFLAG(IS_CHROMEOS_ASH)
   if (!base::FeatureList::IsEnabled(
@@ -33,23 +40,48 @@
   if (!chromeos::BrowserParamsProxy::Get()->IsOndeviceSpeechSupported()) {
     return false;
   }
-#endif
+#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
+  return true;
+}
+#endif  // BUILDFLAG(IS_CHROMEOS)
 
 #if BUILDFLAG(IS_LINUX)
+bool IsSupportedLinux() {
+#if defined(ARCH_CPU_X86_FAMILY)
   // Check if the CPU has the required instruction set to run the Speech
   // On-Device API (SODA) library.
   static bool has_sse41 = base::CPU().has_sse41();
-  if (!has_sse41) {
-    return false;
-  }
-#endif
+  return has_sse41;
+#else
+  // Other architectures are not supported.
+  return false;
+#endif  // defined(ARCH_CPU_X86_FAMILY)
+}
+#endif  // BUILDFLAG(IS_LINUX)
 
-#if BUILDFLAG(IS_WIN) && defined(ARCH_CPU_ARM64)
+#if BUILDFLAG(IS_WIN)
+bool IsSupportedWin() {
+#if defined(ARCH_CPU_ARM64)
   // The Speech On-Device API (SODA) component does not support Windows on
   // arm64.
   return false;
 #else
   return true;
+#endif  // defined(ARCH_CPU_ARM64)
+}
+#endif  // BUILDFLAG(IS_WIN)
+
+}  // namespace
+
+bool IsOnDeviceSpeechRecognitionSupported() {
+#if BUILDFLAG(IS_CHROMEOS)
+  return IsSupportedChromeOS();
+#elif BUILDFLAG(IS_LINUX)
+  return IsSupportedLinux();
+#elif BUILDFLAG(IS_WIN)
+  return IsSupportedWin();
+#else
+  return true;
 #endif
 }
 
diff --git a/components/trusted_vault/proto/vault.proto b/components/trusted_vault/proto/vault.proto
index 8964936..39dedb17 100644
--- a/components/trusted_vault/proto/vault.proto
+++ b/components/trusted_vault/proto/vault.proto
@@ -50,6 +50,10 @@
   int32 nanos = 2;
 }
 
+message LskfMetadata {
+  Timestamp expiration_time = 1;
+}
+
 message GooglePasswordManagerPinMetadata {
   Timestamp expiration_time = 1;
   bytes encrypted_pin_hash = 2;
@@ -80,6 +84,7 @@
   message MemberMetadata {
     bool usable_for_retrieval = 1;
     oneof member_metadata_variant {
+      LskfMetadata lskf_metadata = 4;
       GooglePasswordManagerPinMetadata google_password_manager_pin_metadata = 5;
     }
   }
diff --git a/components/trusted_vault/trusted_vault_connection.h b/components/trusted_vault/trusted_vault_connection.h
index d8c09dc3..61007eb 100644
--- a/components/trusted_vault/trusted_vault_connection.h
+++ b/components/trusted_vault/trusted_vault_connection.h
@@ -139,6 +139,9 @@
   // version.
   std::optional<int> key_version;
 
+  // The expiry time of any LSKF virtual devices.
+  std::vector<base::Time> lskf_expiries;
+
   // If a Google Password Manager PIN is a member of the domain, and is usable
   // for retrieval, then this will contain its metadata.
   std::optional<GpmPinMetadata> gpm_pin_metadata;
diff --git a/components/trusted_vault/trusted_vault_connection_impl.cc b/components/trusted_vault/trusted_vault_connection_impl.cc
index dcece729..5924e61 100644
--- a/components/trusted_vault/trusted_vault_connection_impl.cc
+++ b/components/trusted_vault/trusted_vault_connection_impl.cc
@@ -426,6 +426,12 @@
         if (public_key) {
           result_.icloud_keys.push_back(std::move(public_key));
         }
+      } else if (member.member_type() ==
+                     trusted_vault_pb::SecurityDomainMember::
+                         MEMBER_TYPE_LOCKSCREEN_KNOWLEDGE_FACTOR &&
+                 member.member_metadata().has_lskf_metadata()) {
+        const auto& metadata = member.member_metadata().lskf_metadata();
+        result_.lskf_expiries.push_back(ToTime(metadata.expiration_time()));
       }
     }
 
diff --git a/components/trusted_vault/trusted_vault_connection_impl_unittest.cc b/components/trusted_vault/trusted_vault_connection_impl_unittest.cc
index a08cd5a..066440d 100644
--- a/components/trusted_vault/trusted_vault_connection_impl_unittest.cc
+++ b/components/trusted_vault/trusted_vault_connection_impl_unittest.cc
@@ -87,6 +87,7 @@
     "13C99EDFC5B28BD119C80AD034DE52819963F3056E0F230264D62828";
 constexpr int kTestKeyVersion = 100;
 constexpr int kTestGPMExpirySeconds = 1000000;
+constexpr int kTestLSKFExpirySeconds = 1000001;
 
 enum class Member {
   kPhysical,
@@ -135,11 +136,16 @@
         member->set_member_type(trusted_vault_pb::SecurityDomainMember::
                                     MEMBER_TYPE_LOCKSCREEN_KNOWLEDGE_FACTOR);
         break;
-      case Member::kUsableVirtual:
+      case Member::kUsableVirtual: {
         member->set_member_type(trusted_vault_pb::SecurityDomainMember::
                                     MEMBER_TYPE_LOCKSCREEN_KNOWLEDGE_FACTOR);
         member->mutable_member_metadata()->set_usable_for_retrieval(true);
+        auto* metadata =
+            member->mutable_member_metadata()->mutable_lskf_metadata();
+        metadata->mutable_expiration_time()->set_seconds(
+            kTestLSKFExpirySeconds);
         break;
+      }
       case Member::kICloudKeychain:
         member->set_member_type(trusted_vault_pb::SecurityDomainMember::
                                     MEMBER_TYPE_ICLOUD_KEYCHAIN);
@@ -1137,6 +1143,7 @@
   const GpmPinMetadata gpm_pin_metadata(
       std::move(member_public_key_bytes), kTestSerializedWrappedPIN,
       /*expiry=*/base::Time::FromTimeT(kTestGPMExpirySeconds));
+  const base::Time lskf_expiry = base::Time::FromTimeT(kTestLSKFExpirySeconds);
   const struct TestCase {
     // responses contains the set of security domain members included in each
     // page of results from the "server".
@@ -1144,6 +1151,7 @@
     State expected_result;
     std::optional<int> expected_key_version;
     std::optional<GpmPinMetadata> expected_gpm_pin_metadata;
+    std::vector<base::Time> expected_lskf_expiries;
     std::vector<std::string> expected_icloud_keys;
   } kTestCases[] = {
       {
@@ -1151,66 +1159,77 @@
           State::kEmpty,
           /*expected_key_version=*/std::nullopt,
           /*expected_gpm_pin_metadata=*/std::nullopt,
+          /*expected_lskf_expiries=*/{},
       },
       {
           {{}, {}},
           State::kEmpty,
           /*expected_key_version=*/std::nullopt,
           /*expected_gpm_pin_metadata=*/std::nullopt,
+          /*expected_lskf_expiries=*/{},
       },
       {
           {{Member::kOtherSecurityDomain}, {Member::kOtherSecurityDomain}},
           State::kEmpty,
           /*expected_key_version=*/std::nullopt,
           /*expected_gpm_pin_metadata=*/std::nullopt,
+          /*expected_lskf_expiries=*/{},
       },
       {
           {{Member::kPhysical}},
           State::kIrrecoverable,
           /*expected_key_version=*/kTestKeyVersion,
           /*expected_gpm_pin_metadata=*/std::nullopt,
+          /*expected_lskf_expiries=*/{},
       },
       {
           {{Member::kPhysical, Member::kUsableVirtual}},
           State::kRecoverable,
           /*expected_key_version=*/kTestKeyVersion,
           /*expected_gpm_pin_metadata=*/std::nullopt,
+          /*expected_lskf_expiries=*/{lskf_expiry},
       },
       {
           {{Member::kPhysical, Member::kUnusableVirtual}},
           State::kIrrecoverable,
           /*expected_key_version=*/kTestKeyVersion,
           /*expected_gpm_pin_metadata=*/std::nullopt,
+          /*expected_lskf_expiries=*/{},
       },
       {
           {{Member::kPhysical}, {}, {Member::kUsableVirtual}},
           State::kRecoverable,
           /*expected_key_version=*/kTestKeyVersion,
           /*expected_gpm_pin_metadata=*/std::nullopt,
+          /*expected_lskf_expiries=*/{lskf_expiry},
       },
       {
           {{Member::kUsableVirtual}, {}, {Member::kPhysical}},
           State::kRecoverable,
           /*expected_key_version=*/kTestKeyVersion,
           /*expected_gpm_pin_metadata=*/std::nullopt,
+          /*expected_lskf_expiries=*/{lskf_expiry},
       },
       {
           {{Member::kPhysical}, {}, {Member::kUnusableVirtual}},
           State::kIrrecoverable,
           /*expected_key_version=*/kTestKeyVersion,
           /*expected_gpm_pin_metadata=*/std::nullopt,
+          /*expected_lskf_expiries=*/{},
       },
       {
           {{Member::kPhysical}, {}, {Member::kOtherSecurityDomain}},
           State::kIrrecoverable,
           /*expected_key_version=*/kTestKeyVersion,
           /*expected_gpm_pin_metadata=*/std::nullopt,
+          /*expected_lskf_expiries=*/{},
       },
       {
           {{Member::kGooglePasswordManagerPIN}, {Member::kOtherSecurityDomain}},
           State::kRecoverable,
           /*expected_key_version=*/kTestKeyVersion,
           /*expected_gpm_pin_metadata=*/gpm_pin_metadata,
+          /*expected_lskf_expiries=*/{},
       },
       {
           {{Member::kGooglePasswordManagerPIN},
@@ -1218,12 +1237,14 @@
           State::kRecoverable,
           /*expected_key_version=*/kTestKeyVersion,
           /*expected_gpm_pin_metadata=*/gpm_pin_metadata,
+          /*expected_lskf_expiries=*/{},
       },
       {
           {{Member::kICloudKeychain}},
           State::kIrrecoverable,
           /*expected_key_version=*/kTestKeyVersion,
           /*expected_gpm_pin_metadata=*/std::nullopt,
+          /*expected_lskf_expiries=*/{},
           /*expected_icloud_keys=*/{kTestMemberPublicKey},
       },
       {
@@ -1231,6 +1252,15 @@
           State::kIrrecoverable,
           /*expected_key_version=*/kTestKeyVersion,
           /*expected_gpm_pin_metadata=*/std::nullopt,
+          /*expected_lskf_expiries=*/{},
+          /*expected_icloud_keys=*/{},
+      },
+      {
+          {{Member::kUsableVirtual, Member::kUsableVirtual}},
+          State::kRecoverable,
+          /*expected_key_version=*/kTestKeyVersion,
+          /*expected_gpm_pin_metadata=*/std::nullopt,
+          /*expected_lskf_expiries=*/{lskf_expiry, lskf_expiry},
           /*expected_icloud_keys=*/{},
       },
   };
@@ -1279,6 +1309,7 @@
     EXPECT_EQ(num_pages_downloaded, test.responses.size());
     EXPECT_EQ(result->state, test.expected_result);
     EXPECT_EQ(result->gpm_pin_metadata, test.expected_gpm_pin_metadata);
+    EXPECT_EQ(result->lskf_expiries, test.expected_lskf_expiries);
     std::vector<std::string> result_icloud_keys;
     for (const auto& key : result->icloud_keys) {
       result_icloud_keys.push_back(base::HexEncode(key->ExportToBytes()));
diff --git a/components/vector_icons/BUILD.gn b/components/vector_icons/BUILD.gn
index 098cea09..18f926c1 100644
--- a/components/vector_icons/BUILD.gn
+++ b/components/vector_icons/BUILD.gn
@@ -272,6 +272,7 @@
       "google_chrome/google_lens_full_logo.icon",
       "google_chrome/google_lens_full_logo_dark.icon",
       "google_chrome/google_lens_logo.icon",
+      "google_chrome/google_lens_monochrome_logo.icon",
       "google_chrome/google_password_manager.icon",
       "google_chrome/google_pay_logo.icon",
       "google_chrome/google_search_companion_logo.icon",
diff --git a/content/app_shim_remote_cocoa/render_widget_host_view_cocoa.h b/content/app_shim_remote_cocoa/render_widget_host_view_cocoa.h
index 89a5aa5..bb4107e 100644
--- a/content/app_shim_remote_cocoa/render_widget_host_view_cocoa.h
+++ b/content/app_shim_remote_cocoa/render_widget_host_view_cocoa.h
@@ -83,8 +83,6 @@
 - (void)setCloseOnDeactivate:(BOOL)b;
 // Indicate that the host was destroyed and can't be called back into.
 - (void)setHostDisconnected;
-// True for always-on-top special windows (e.g. Balloons and Panels).
-- (BOOL)acceptsMouseEventsWhenInactive;
 // Cancel ongoing composition (abandon the marked text).
 - (void)cancelComposition;
 // Confirm ongoing composition.
diff --git a/content/app_shim_remote_cocoa/render_widget_host_view_cocoa.mm b/content/app_shim_remote_cocoa/render_widget_host_view_cocoa.mm
index e76ac0d4..1cb6eb6 100644
--- a/content/app_shim_remote_cocoa/render_widget_host_view_cocoa.mm
+++ b/content/app_shim_remote_cocoa/render_widget_host_view_cocoa.mm
@@ -743,15 +743,27 @@
   _canBeKeyView = can;
 }
 
-- (BOOL)acceptsMouseEventsWhenInactive {
-  // Some types of windows (balloons, always-on-top panels) want to accept mouse
-  // clicks w/o the first click being treated as 'activation'. Same applies to
-  // mouse move events.
-  return [[self window] level] > NSNormalWindowLevel;
+- (AcceptMouseEventsOption)acceptsMouseEventsOption {
+  // Always-on-top windows, e.g picture-in-picture window, accepts all mouse
+  // events even if the window or the application is inactive.
+  if ([[self window] level] > NSNormalWindowLevel) {
+    return kAcceptMouseEventsAlways;
+  }
+
+  // By default, only active window accepts mouse events. The embedder may
+  // override this to mimic the hover and click behavior of native UIs.
+  if (_responderDelegate && [_responderDelegate respondsToSelector:@selector
+                                                (acceptsMouseEventsOption)]) {
+    return [_responderDelegate acceptsMouseEventsOption];
+  }
+
+  // By default, only active window accepts mouse events.
+  return kAcceptMouseEventsInActiveWindow;
 }
 
 - (BOOL)acceptsFirstMouse:(NSEvent*)theEvent {
-  return [self acceptsMouseEventsWhenInactive];
+  // Enable "click-through" if mouse clicks are accepted in inactive windows
+  return [self acceptsMouseEventsOption] > kAcceptMouseEventsInActiveWindow;
 }
 
 - (void)setCloseOnDeactivate:(BOOL)b {
@@ -820,12 +832,21 @@
 
 - (BOOL)shouldIgnoreMouseEvent:(NSEvent*)theEvent {
   NSWindow* window = [self window];
-  // If this is a background window, don't handle mouse movement events. This
-  // is the expected behavior on the Mac as evidenced by other applications.
-  if ([theEvent type] == NSEventTypeMouseMoved &&
-      ![self acceptsMouseEventsWhenInactive] && ![window isMainWindow] &&
-      ![window isKeyWindow]) {
-    return YES;
+  if ([theEvent type] == NSEventTypeMouseMoved) {
+    bool inActiveWindow = [window isMainWindow] || [window isKeyWindow];
+    bool inActiveApp = [[NSApplication sharedApplication] isActive];
+    AcceptMouseEventsOption option = [self acceptsMouseEventsOption];
+    // If events are accepted only in active window but this window is inactive,
+    // ignore this event. This is the default behavior.
+    if (option == kAcceptMouseEventsInActiveWindow && !inActiveWindow) {
+      return YES;
+    }
+    // If events are accepted in active app but the app in active, ignore this
+    // event. This only happens if the content embedder overrides the default
+    // behavior.
+    if (option == kAcceptMouseEventsInActiveApp && !inActiveApp) {
+      return YES;
+    }
   }
 
   NSView* contentView = [window contentView];
diff --git a/content/browser/accessibility/dump_accessibility_browsertest_base.cc b/content/browser/accessibility/dump_accessibility_browsertest_base.cc
index d81c8b6..71097c1 100644
--- a/content/browser/accessibility/dump_accessibility_browsertest_base.cc
+++ b/content/browser/accessibility/dump_accessibility_browsertest_base.cc
@@ -231,6 +231,10 @@
   // corresponding code in AXPosition on the browser that collects those
   // markers.
   enabled_features->emplace_back(features::kUseAXPositionForDocumentMarkers);
+  // For improved test coverage ahead of a finch trial, enable the feature that
+  // prunes redundant text for inline text boxes.
+  enabled_features->emplace_back(
+      features::kAccessibilityPruneRedundantInlineText);
 }
 
 std::string DumpAccessibilityTestBase::DumpTreeAsString() const {
diff --git a/content/browser/aggregation_service/aggregation_service_features.cc b/content/browser/aggregation_service/aggregation_service_features.cc
index 081ea33..d987ddb 100644
--- a/content/browser/aggregation_service/aggregation_service_features.cc
+++ b/content/browser/aggregation_service/aggregation_service_features.cc
@@ -22,6 +22,6 @@
 
 BASE_FEATURE(kPrivacySandboxAggregationServiceFilteringIds,
              "PrivacySandboxAggregationServiceFilteringIds",
-             base::FEATURE_DISABLED_BY_DEFAULT);
+             base::FEATURE_ENABLED_BY_DEFAULT);
 
 }  // namespace content
diff --git a/content/browser/attribution_reporting/attribution_data_host_manager_impl.cc b/content/browser/attribution_reporting/attribution_data_host_manager_impl.cc
index 3098494..0bbae7b 100644
--- a/content/browser/attribution_reporting/attribution_data_host_manager_impl.cc
+++ b/content/browser/attribution_reporting/attribution_data_host_manager_impl.cc
@@ -760,10 +760,37 @@
                  : std::nullopt;
     };
 
-    std::optional<std::string> web_source_header = get_header(
-        attribution_reporting::kAttributionReportingRegisterSourceHeader);
-    std::optional<std::string> web_trigger_header = get_header(
-        attribution_reporting::kAttributionReportingRegisterTriggerHeader);
+    std::optional<std::string> web_source_header;
+    {
+      std::string value;
+      size_t iter = 0;
+      while (headers->EnumerateHeader(
+          &iter,
+          attribution_reporting::kAttributionReportingRegisterSourceHeader,
+          &value)) {
+        if (web_source_header.has_value()) {
+          // TODO(https://crbug.com/40242261): Log an audit issue.
+          return std::nullopt;
+        }
+        web_source_header = std::move(value);
+      }
+    }
+
+    std::optional<std::string> web_trigger_header;
+    {
+      std::string value;
+      size_t iter = 0;
+      while (headers->EnumerateHeader(
+          &iter,
+          attribution_reporting::kAttributionReportingRegisterTriggerHeader,
+          &value)) {
+        if (web_trigger_header.has_value()) {
+          // TODO(https://crbug.com/40242261): Log an audit issue.
+          return std::nullopt;
+        }
+        web_trigger_header = std::move(value);
+      }
+    }
     std::optional<std::string> os_source_header = get_header(
         attribution_reporting::kAttributionReportingRegisterOsSourceHeader,
         cross_app_web_enabled);
diff --git a/content/browser/attribution_reporting/attribution_src_browsertest.cc b/content/browser/attribution_reporting/attribution_src_browsertest.cc
index 6b0ae139..b31a3d1 100644
--- a/content/browser/attribution_reporting/attribution_src_browsertest.cc
+++ b/content/browser/attribution_reporting/attribution_src_browsertest.cc
@@ -2,6 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#include <stddef.h>
+
 #include <memory>
 #include <utility>
 #include <vector>
@@ -90,6 +92,7 @@
 using ::testing::StrictMock;
 
 using attribution_reporting::kAttributionReportingRegisterSourceHeader;
+using attribution_reporting::kAttributionReportingRegisterTriggerHeader;
 
 }  // namespace
 
@@ -465,6 +468,134 @@
 }
 
 IN_PROC_BROWSER_TEST_P(AttributionSrcBrowserTest,
+                       RegistrationWithMultipleHeadersAreRejected) {
+  const char* kTestCases[] = {
+      "createAttributionEligibleImgSrc($1);", "createAttributionSrcScript($1);",
+      "doAttributionEligibleFetch($1);", "doAttributionEligibleXHR($1);",
+      "createAttributionEligibleScriptSrc($1);"};
+  for (const char* registration_js : kTestCases) {
+    SCOPED_TRACE(registration_js);
+
+    // Create a separate server as we cannot register a
+    // `ControllableHttpResponse` after the server starts.
+    std::unique_ptr<EmbeddedTestServer> https_server =
+        CreateAttributionTestHttpsServer();
+
+    auto register_sources_response =
+        std::make_unique<net::test_server::ControllableHttpResponse>(
+            https_server.get(), "/register_sources");
+    auto register_triggers_response =
+        std::make_unique<net::test_server::ControllableHttpResponse>(
+            https_server.get(), "/register_triggers");
+    auto register_sources_and_trigger_response =
+        std::make_unique<net::test_server::ControllableHttpResponse>(
+            https_server.get(), "/register_sources_and_trigger");
+    auto register_source_response =
+        std::make_unique<net::test_server::ControllableHttpResponse>(
+            https_server.get(), "/register_source");
+
+    ASSERT_TRUE(https_server->Start());
+
+    GURL page_url =
+        https_server->GetURL("d.test", "/page_with_impression_creator.html");
+    EXPECT_TRUE(NavigateToURL(web_contents(), page_url));
+
+    base::RunLoop run_loop;
+
+    EXPECT_CALL(mock_attribution_manager(),
+                HandleSource(SourceRegistrationIs(AllOf(Field(
+                                 &SourceRegistration::source_event_id, 15u))),
+                             _))
+        .Times(1)
+        .WillOnce([&run_loop]() { run_loop.Quit(); });
+
+    EXPECT_CALL(mock_attribution_manager(), HandleTrigger).Times(0);
+
+    GURL register_multiple_sources_url =
+        https_server->GetURL("d.test", "/register_sources");
+    GURL register_multiple_triggers_url =
+        https_server->GetURL("d.test", "/register_triggers");
+    GURL register_multiple_sources_and_triger_url =
+        https_server->GetURL("d.test", "/register_sources_and_trigger");
+    GURL register_single_source_url =
+        https_server->GetURL("d.test", "/register_source");
+
+    // Multiple source headers
+    {
+      EXPECT_TRUE(
+          ExecJs(web_contents(),
+                 JsReplace(registration_js, register_multiple_sources_url)));
+      register_sources_response->WaitForRequest();
+      auto http_response =
+          std::make_unique<net::test_server::BasicHttpResponse>();
+      http_response->set_code(net::HTTP_MOVED_PERMANENTLY);
+      http_response->AddCustomHeader("Location", "/register_triggers");
+      for (size_t i = 0; i < 3; ++i) {
+        http_response->AddCustomHeader(
+            kAttributionReportingRegisterSourceHeader,
+            R"({"destination":"https://d.test"})");
+      }
+      register_sources_response->Send(http_response->ToResponseString());
+      register_sources_response->Done();
+    }
+
+    // Multiple trigger headers
+    {
+      register_triggers_response->WaitForRequest();
+      auto http_response =
+          std::make_unique<net::test_server::BasicHttpResponse>();
+      http_response->set_code(net::HTTP_MOVED_PERMANENTLY);
+      http_response->AddCustomHeader("Location",
+                                     "/register_sources_and_trigger");
+      for (size_t i = 0; i < 3; ++i) {
+        http_response->AddCustomHeader(
+            kAttributionReportingRegisterTriggerHeader, R"({})");
+      }
+      register_triggers_response->Send(http_response->ToResponseString());
+      register_triggers_response->Done();
+    }
+
+    // Multiple source headers and 1 trigger header
+    {
+      register_sources_and_trigger_response->WaitForRequest();
+      auto http_response =
+          std::make_unique<net::test_server::BasicHttpResponse>();
+      http_response->set_code(net::HTTP_MOVED_PERMANENTLY);
+      http_response->AddCustomHeader("Location", "/register_source");
+      for (size_t i = 0; i < 3; ++i) {
+        http_response->AddCustomHeader(
+            kAttributionReportingRegisterSourceHeader,
+            R"({"destination":"https://d.test"})");
+      }
+      http_response->AddCustomHeader(kAttributionReportingRegisterTriggerHeader,
+                                     R"({})");
+      register_sources_and_trigger_response->Send(
+          http_response->ToResponseString());
+      register_sources_and_trigger_response->Done();
+    }
+
+    // Register a single source (success). This allows us to have a hook to
+    // wait on for async operations to complete. Since it is the last request in
+    // the chain, we know that once it is received, if no other registrations
+    // have been received, it means that they were invalid.
+    {
+      register_source_response->WaitForRequest();
+      auto http_response =
+          std::make_unique<net::test_server::BasicHttpResponse>();
+      http_response->set_code(net::HTTP_OK);
+      http_response->AddCustomHeader(
+          kAttributionReportingRegisterSourceHeader,
+          R"({"source_event_id":"15", "destination":"https://d.test"})");
+      register_source_response->Send(http_response->ToResponseString());
+      register_source_response->Done();
+    }
+
+    run_loop.Run();
+    testing::Mock::VerifyAndClear(&mock_attribution_manager());
+  }
+}
+
+IN_PROC_BROWSER_TEST_P(AttributionSrcBrowserTest,
                        AttributionSrcImgSlowResponse_SourceRegistered) {
   // Create a separate server as we cannot register a `ControllableHttpResponse`
   // after the server starts.
diff --git a/content/browser/fenced_frame/fenced_frame_reporter_unittest.cc b/content/browser/fenced_frame/fenced_frame_reporter_unittest.cc
index ff0c735..9d74591 100644
--- a/content/browser/fenced_frame/fenced_frame_reporter_unittest.cc
+++ b/content/browser/fenced_frame/fenced_frame_reporter_unittest.cc
@@ -70,7 +70,7 @@
                     blink::mojom::AggregatableReportHistogramContribution::New(
                         /*bucket=*/3,
                         /*value=*/4,
-                        /*filtering_id=*/std::nullopt)),
+                        /*filtering_id=*/1)),
             blink::mojom::AggregationServiceMode::kDefault,
             blink::mojom::DebugModeDetails::New());
 
diff --git a/content/browser/interest_group/ad_auction_service_impl_unittest.cc b/content/browser/interest_group/ad_auction_service_impl_unittest.cc
index def3a80..226365e 100644
--- a/content/browser/interest_group/ad_auction_service_impl_unittest.cc
+++ b/content/browser/interest_group/ad_auction_service_impl_unittest.cc
@@ -4,6 +4,8 @@
 
 #include "content/browser/interest_group/ad_auction_service_impl.h"
 
+#include <stddef.h>
+
 #include <memory>
 #include <optional>
 #include <string>
@@ -10249,6 +10251,7 @@
                      std::optional<std::string> context_id,
                      std::optional<base::TimeDelta> timeout,
                      std::optional<url::Origin> aggregation_coordinator_origin,
+                     size_t filtering_id_max_bytes,
                      mojo::PendingReceiver<blink::mojom::PrivateAggregationHost>
                          pending_receiver) -> bool {
                 check_coordinator_.Run(aggregation_coordinator_origin,
@@ -10257,7 +10260,7 @@
                     std::move(worklet_origin), std::move(top_frame_origin),
                     api_for_budgeting, std::move(context_id), timeout,
                     std::move(aggregation_coordinator_origin),
-                    std::move(pending_receiver));
+                    filtering_id_max_bytes, std::move(pending_receiver));
               });
     }
 
@@ -10271,6 +10274,7 @@
                  std::optional<std::string>,
                  std::optional<base::TimeDelta>,
                  std::optional<url::Origin>,
+                 size_t,
                  mojo::PendingReceiver<blink::mojom::PrivateAggregationHost>),
                 (override));
 
diff --git a/content/browser/interest_group/auction_runner.h b/content/browser/interest_group/auction_runner.h
index bcf339d..2585857 100644
--- a/content/browser/interest_group/auction_runner.h
+++ b/content/browser/interest_group/auction_runner.h
@@ -79,9 +79,6 @@
   // optional size returned by the winning bidder. Null if there is no winner or
   // no list was returned.
   //
-  // `report_urls` Reporting URLs returned by seller worklet reportResult()
-  //  methods and the winning bidder's reportWin() methods, if any.
-  //
   // `errors` are various error messages to be used for debugging. These are too
   //  sensitive for the renderers to see.
   using RunAuctionCallback = base::OnceCallback<void(
diff --git a/content/browser/interest_group/auction_runner_unittest.cc b/content/browser/interest_group/auction_runner_unittest.cc
index ebffc9c..66fb8ef 100644
--- a/content/browser/interest_group/auction_runner_unittest.cc
+++ b/content/browser/interest_group/auction_runner_unittest.cc
@@ -39,6 +39,7 @@
 #include "components/cbor/values.h"
 #include "components/cbor/writer.h"
 #include "components/ukm/test_ukm_recorder.h"
+#include "content/browser/aggregation_service/aggregation_service_features.h"
 #include "content/browser/fenced_frame/fenced_frame_reporter.h"
 #include "content/browser/interest_group/ad_auction_page_data.h"
 #include "content/browser/interest_group/additional_bids_test_util.h"
@@ -67,6 +68,7 @@
 #include "content/services/auction_worklet/public/mojom/auction_worklet_service.mojom.h"
 #include "content/services/auction_worklet/public/mojom/bidder_worklet.mojom.h"
 #include "content/services/auction_worklet/public/mojom/private_aggregation_request.mojom.h"
+#include "content/services/auction_worklet/public/mojom/real_time_reporting.mojom.h"
 #include "content/services/auction_worklet/public/mojom/seller_worklet.mojom.h"
 #include "content/services/auction_worklet/worklet_devtools_debug_test_util.h"
 #include "content/services/auction_worklet/worklet_test_util.h"
@@ -1243,7 +1245,8 @@
 BuildPrivateAggregationRequest(
     absl::uint128 bucket,
     int value,
-    blink::mojom::DebugModeDetailsPtr debug_mode_details = nullptr) {
+    blink::mojom::DebugModeDetailsPtr debug_mode_details = nullptr,
+    std::optional<uint64_t> filtering_id = std::nullopt) {
   if (!debug_mode_details) {
     debug_mode_details = blink::mojom::DebugModeDetails::New();
   }
@@ -1251,19 +1254,21 @@
       auction_worklet::mojom::AggregatableReportContribution::
           NewHistogramContribution(
               blink::mojom::AggregatableReportHistogramContribution::New(
-                  bucket, value, /*filtering_id=*/std::nullopt)),
+                  bucket, value, filtering_id)),
       blink::mojom::AggregationServiceMode::kDefault,
       std::move(debug_mode_details));
 }
 
 const auction_worklet::mojom::PrivateAggregationRequestPtr
-BuildPrivateAggregationForEventRequest(absl::uint128 bucket,
-                                       int value,
-                                       std::string event_type) {
+BuildPrivateAggregationForEventRequest(
+    absl::uint128 bucket,
+    int value,
+    std::string event_type,
+    std::optional<uint64_t> filtering_id = std::nullopt) {
   auction_worklet::mojom::AggregatableReportForEventContribution contribution(
       auction_worklet::mojom::ForEventSignalBucket::NewIdBucket(bucket),
       auction_worklet::mojom::ForEventSignalValue::NewIntValue(value),
-      event_type);
+      filtering_id, event_type);
 
   return auction_worklet::mojom::PrivateAggregationRequest::New(
       auction_worklet::mojom::AggregatableReportContribution::
@@ -1276,13 +1281,14 @@
 BuildPrivateAggregationForBaseValue(
     absl::uint128 bucket,
     auction_worklet::mojom::BaseValue base_value,
-    std::string event_type) {
+    std::string event_type,
+    std::optional<uint64_t> filtering_id = std::nullopt) {
   auction_worklet::mojom::AggregatableReportForEventContribution contribution(
       auction_worklet::mojom::ForEventSignalBucket::NewIdBucket(bucket),
       auction_worklet::mojom::ForEventSignalValue::NewSignalValue(
           auction_worklet::mojom::SignalValue::New(base_value, /*scale=*/1.0,
                                                    /*offset=*/0)),
-      event_type);
+      filtering_id, event_type);
 
   return auction_worklet::mojom::PrivateAggregationRequest::New(
       auction_worklet::mojom::AggregatableReportContribution::
@@ -2269,6 +2275,7 @@
                 /*scoring_signals_data_version=*/std::nullopt,
                 /*debug_loss_report_url=*/std::nullopt,
                 /*debug_win_report_url=*/std::nullopt, /*pa_requests=*/{},
+                /*real_time_contributions=*/{},
                 /*scoring_latency=*/base::TimeDelta(),
                 /*score_ad_dependency_latencies=*/
                 auction_worklet::mojom::ScoreAdDependencyLatencies::New(
@@ -10485,6 +10492,7 @@
           /*scoring_signals_data_version=*/std::nullopt,
           /*debug_loss_report_url=*/std::nullopt,
           /*debug_win_report_url=*/std::nullopt, /*pa_requests=*/{},
+          /*real_time_contributions=*/{},
           /*scoring_latency=*/base::TimeDelta(),
           /*score_ad_dependency_latencies=*/
           auction_worklet::mojom::ScoreAdDependencyLatencies::New(
@@ -10507,6 +10515,7 @@
           /*scoring_signals_data_version=*/std::nullopt,
           /*debug_loss_report_url=*/std::nullopt,
           /*debug_win_report_url=*/std::nullopt, /*pa_requests=*/{},
+          /*real_time_contributions=*/{},
           /*scoring_latency=*/base::TimeDelta(),
           /*score_ad_dependency_latencies=*/
           auction_worklet::mojom::ScoreAdDependencyLatencies::New(
@@ -10767,6 +10776,7 @@
             /*scoring_signals_data_version=*/std::nullopt,
             /*debug_loss_report_url=*/std::nullopt,
             /*debug_win_report_url=*/std::nullopt, /*pa_requests=*/{},
+            /*real_time_contributions=*/{},
             /*scoring_latency=*/base::TimeDelta(),
             /*score_ad_dependency_latencies=*/
             auction_worklet::mojom::ScoreAdDependencyLatencies::New(
@@ -11021,6 +11031,7 @@
           /*scoring_signals_data_version=*/std::nullopt,
           /*debug_loss_report_url=*/std::nullopt,
           /*debug_win_report_url=*/std::nullopt, /*pa_requests=*/{},
+          /*real_time_contributions=*/{},
           /*scoring_latency=*/base::TimeDelta(),
           /*score_ad_dependency_latencies=*/
           auction_worklet::mojom::ScoreAdDependencyLatencies::New(
@@ -11047,6 +11058,7 @@
           /*scoring_signals_data_version=*/std::nullopt,
           /*debug_loss_report_url=*/std::nullopt,
           /*debug_win_report_url=*/std::nullopt, /*pa_requests=*/{},
+          /*real_time_contributions=*/{},
           /*scoring_latency=*/base::TimeDelta(),
           /*score_ad_dependency_latencies=*/
           auction_worklet::mojom::ScoreAdDependencyLatencies::New(
@@ -11241,6 +11253,7 @@
             /*debug_loss_report_url=*/std::nullopt,
             /*debug_win_report_url=*/std::nullopt,
             /*pa_requests=*/{},
+            /*real_time_contributions=*/{},
             /*scoring_latency=*/base::TimeDelta(),
             /*score_ad_dependency_latencies=*/
             auction_worklet::mojom::ScoreAdDependencyLatencies::New(
@@ -11318,6 +11331,7 @@
           /*scoring_signals_data_version=*/std::nullopt,
           /*debug_loss_report_url=*/std::nullopt,
           /*debug_win_report_url=*/std::nullopt, /*pa_requests=*/{},
+          /*real_time_contributions=*/{},
           /*scoring_latency=*/base::TimeDelta(),
           /*score_ad_dependency_latencies=*/
           auction_worklet::mojom::ScoreAdDependencyLatencies::New(
@@ -11404,6 +11418,7 @@
               /*scoring_signals_data_version=*/std::nullopt,
               /*debug_loss_report_url=*/std::nullopt,
               /*debug_win_report_url=*/std::nullopt, /*pa_requests=*/{},
+              /*real_time_contributions=*/{},
               /*scoring_latency=*/base::TimeDelta(),
               /*score_ad_dependency_latencies=*/
               auction_worklet::mojom::ScoreAdDependencyLatencies::New(
@@ -11527,6 +11542,7 @@
               /*scoring_signals_data_version=*/std::nullopt,
               /*debug_loss_report_url=*/std::nullopt,
               /*debug_win_report_url=*/std::nullopt, /*pa_requests=*/{},
+              /*real_time_contributions=*/{},
               /*scoring_latency=*/base::TimeDelta(),
               /*score_ad_dependency_latencies=*/
               auction_worklet::mojom::ScoreAdDependencyLatencies::New(
@@ -11814,6 +11830,7 @@
         std::nullopt, /*debug_loss_report_url=*/std::nullopt,
         /*debug_win_report_url=*/std::nullopt,
         /*pa_requests=*/{},
+        /*real_time_contributions=*/{},
         /*dependency_latencies=*/
         auction_worklet::mojom::GenerateBidDependencyLatenciesPtr(),
         test_case.reject_reason);
@@ -11903,6 +11920,7 @@
             /*scoring_signals_data_version=*/std::nullopt,
             /*debug_loss_report_url=*/std::nullopt,
             /*debug_win_report_url=*/std::nullopt, /*pa_requests=*/{},
+            /*real_time_contributions=*/{},
             /*scoring_latency=*/base::TimeDelta(),
             /*score_ad_dependency_latencies=*/
             auction_worklet::mojom::ScoreAdDependencyLatencies::New(
@@ -11956,6 +11974,7 @@
           /*scoring_signals_data_version=*/std::nullopt,
           /*debug_loss_report_url=*/std::nullopt,
           /*debug_win_report_url=*/std::nullopt, /*pa_requests=*/{},
+          /*real_time_contributions=*/{},
           /*scoring_latency=*/base::TimeDelta(),
           /*score_ad_dependency_latencies=*/
           auction_worklet::mojom::ScoreAdDependencyLatencies::New(
@@ -12036,6 +12055,7 @@
             /*scoring_signals_data_version=*/std::nullopt,
             /*debug_loss_report_url=*/std::nullopt,
             /*debug_win_report_url=*/std::nullopt, /*pa_requests=*/{},
+            /*real_time_contributions=*/{},
             /*scoring_latency=*/base::TimeDelta(),
             /*score_ad_dependency_latencies=*/
             auction_worklet::mojom::ScoreAdDependencyLatencies::New(
@@ -12062,6 +12082,7 @@
             /*scoring_signals_data_version=*/std::nullopt,
             /*debug_loss_report_url=*/std::nullopt,
             /*debug_win_report_url=*/std::nullopt, /*pa_requests=*/{},
+            /*real_time_contributions=*/{},
             /*scoring_latency=*/base::TimeDelta(),
             /*score_ad_dependency_latencies=*/
             auction_worklet::mojom::ScoreAdDependencyLatencies::New(
@@ -12209,6 +12230,7 @@
                     /*scoring_signals_data_version=*/std::nullopt,
                     /*debug_loss_report_url=*/std::nullopt,
                     /*debug_win_report_url=*/std::nullopt, /*pa_requests=*/{},
+                    /*real_time_contributions=*/{},
                     /*scoring_latency=*/base::TimeDelta(),
                     /*score_ad_dependency_latencies=*/
                     auction_worklet::mojom::ScoreAdDependencyLatencies::New(
@@ -12233,6 +12255,7 @@
                     /*debug_loss_report_url=*/std::nullopt,
                     /*debug_win_report_url=*/std::nullopt,
                     /*pa_requests=*/{},
+                    /*real_time_contributions=*/{},
                     /*scoring_latency=*/base::TimeDelta(),
                     /*score_ad_dependency_latencies=*/
                     auction_worklet::mojom::ScoreAdDependencyLatencies::New(
@@ -12525,6 +12548,7 @@
           /*scoring_signals_data_version=*/std::nullopt,
           /*debug_loss_report_url=*/std::nullopt,
           /*debug_win_report_url=*/std::nullopt, /*pa_requests=*/{},
+          /*real_time_contributions=*/{},
           /*scoring_latency=*/base::TimeDelta(),
           /*score_ad_dependency_latencies=*/
           auction_worklet::mojom::ScoreAdDependencyLatencies::New(
@@ -12649,6 +12673,7 @@
           /*scoring_signals_data_version=*/std::nullopt,
           /*debug_loss_report_url=*/std::nullopt,
           /*debug_win_report_url=*/std::nullopt, /*pa_requests=*/{},
+          /*real_time_contributions=*/{},
           /*scoring_latency=*/base::TimeDelta(),
           /*score_ad_dependency_latencies=*/
           auction_worklet::mojom::ScoreAdDependencyLatencies::New(
@@ -13916,6 +13941,7 @@
         /*debug_loss_report_url=*/std::nullopt,
         /*debug_win_report_url=*/std::nullopt,
         /*pa_requests=*/{},
+        /*real_time_contributions=*/{},
         /*dependency_latencies=*/
         GenerateBidDependencyLatencies::New(test_case.dependency_latencies));
 
@@ -13934,6 +13960,7 @@
             /*scoring_signals_data_version=*/std::nullopt,
             /*debug_loss_report_url=*/std::nullopt,
             /*debug_win_report_url=*/std::nullopt, /*pa_requests=*/{},
+            /*real_time_contributions=*/{},
             /*scoring_latency=*/base::TimeDelta(),
             /*score_ad_dependency_latencies=*/
             auction_worklet::mojom::ScoreAdDependencyLatencies::New(
@@ -14150,6 +14177,7 @@
         /*debug_loss_report_url=*/std::nullopt,
         /*debug_win_report_url=*/std::nullopt,
         /*pa_requests=*/{},
+        /*real_time_contributions=*/{},
         /*dependency_latencies=*/
         auction_worklet::mojom::GenerateBidDependencyLatencies::New(
             /*code_ready_latency=*/std::nullopt,
@@ -14172,7 +14200,8 @@
             /*scoring_signals_data_version=*/std::nullopt,
             /*debug_loss_report_url=*/std::nullopt,
             /*debug_win_report_url=*/std::nullopt, /*pa_requests=*/{},
-            /*score_ad_latency=*/base::Milliseconds(0),
+            /*real_time_contributions=*/{},
+            /*scoring_latency=*/base::Milliseconds(0),
             /*score_ad_dependency_latencies=*/
             ScoreAdDependencyLatencies::New(test_case.dependency_latencies),
             /*errors=*/{});
@@ -14974,6 +15003,128 @@
                   BuildPrivateAggregationRequest(/*bucket=*/0, /*value=*/1)))));
 }
 
+TEST_F(AuctionRunnerTest, PrivateAggregationRequestForEventFilteringId) {
+  base::test::ScopedFeatureList scoped_feature_list;
+  scoped_feature_list.InitWithFeatures(
+      /*enabled_features=*/{blink::features::kPrivateAggregationApiFilteringIds,
+                            kPrivacySandboxAggregationServiceFilteringIds},
+      /*disabled_features=*/{});
+
+  // Only one bidder participating the auction, to keep things simple.
+  interest_group_buyers_ = {{kBidder1}};
+
+  const char kBidScript[] = R"(
+    const bid = %d;
+
+    function generateBid(
+        interestGroup, auctionSignals, perBuyerSignals, trustedBiddingSignals,
+        browserSignals) {
+      privateAggregation.contributeToHistogramOnEvent('reserved.always', {
+        bucket: 123n,
+        value: 4,
+        filteringId: 1n,
+      });
+      return {bid: bid, render: interestGroup.ads[0].renderURL};
+    }
+
+    function reportWin(
+        auctionSignals, perBuyerSignals, sellerSignals, browserSignals) {
+      privateAggregation.contributeToHistogramOnEvent('click', {
+        bucket: 456n,
+        value: 7,
+        filteringId: 2n,
+      });
+      privateAggregation.contributeToHistogramOnEvent('reserved.always', {
+        bucket: 123n,
+        value: 4,
+        // filteringId not specified
+      });
+    }
+  )";
+
+  const std::string kSellerScript = R"(
+    function scoreAd(adMetadata, bid, auctionConfig, browserSignals) {
+      privateAggregation.contributeToHistogramOnEvent('reserved.always', {
+        bucket: 234n,
+        value: 5,
+        filteringId: 0n,
+      });
+      return bid;
+    }
+
+    function reportResult(auctionConfig, browserSignals) {
+      privateAggregation.contributeToHistogramOnEvent('reserved.always', {
+        bucket: 234n,
+        value: 5,
+        filteringId: 255n,
+      });
+    }
+  )";
+
+  auction_worklet::AddJavascriptResponse(&url_loader_factory_, kBidder1Url,
+                                         base::StringPrintf(kBidScript, 1));
+  auction_worklet::AddJavascriptResponse(&url_loader_factory_, kSellerUrl,
+                                         kSellerScript);
+
+  RunStandardAuction(/*request_trusted_bidding_signals=*/false);
+  EXPECT_THAT(result_.errors, testing::UnorderedElementsAre());
+  EXPECT_FALSE(result_.aborted_by_script);
+  EXPECT_EQ(kBidder1Key, result_.winning_group_id);
+  EXPECT_EQ(GURL("https://ad1.com/"), result_.ad_descriptor->url);
+
+  EXPECT_THAT(
+      private_aggregation_manager_.TakePrivateAggregationRequests(),
+      testing::UnorderedElementsAre(
+          testing::Pair(
+              kBidder1,
+              ElementsAreRequests(
+                  // generateBid().
+                  BuildPrivateAggregationRequest(
+                      /*bucket=*/123, /*value=*/4, /*debug_mode_details=*/
+                      blink::mojom::DebugModeDetails::New(),
+                      /*filtering_id=*/1),
+                  // reportWin().
+                  BuildPrivateAggregationRequest(
+                      /*bucket=*/123, /*value=*/4, /*debug_mode_details=*/
+                      blink::mojom::DebugModeDetails::New(),
+                      /*filtering_id=*/std::nullopt))),
+          testing::Pair(
+              kSeller,
+              ElementsAreRequests(
+                  // scoreAd().
+                  BuildPrivateAggregationRequest(
+                      /*bucket=*/234, /*value=*/5, /*debug_mode_details=*/
+                      blink::mojom::DebugModeDetails::New(),
+                      /*filtering_id=*/0),
+                  // reportResult().
+                  BuildPrivateAggregationRequest(
+                      /*bucket=*/234, /*value=*/5, /*debug_mode_details=*/
+                      blink::mojom::DebugModeDetails::New(),
+                      /*filtering_id=*/255)))));
+  EXPECT_THAT(
+      private_aggregation_manager_.TakeLoggedPrivateAggregationRequests(),
+      ElementsAreRequests(
+          BuildPrivateAggregationForEventRequest(
+              /*bucket=*/123, /*value=*/4,
+              /*event_type=*/"reserved.always",
+              /*filtering_id=*/1),
+          BuildPrivateAggregationForEventRequest(
+              /*bucket=*/123, /*value=*/4,
+              /*event_type=*/"reserved.always",
+              /*filtering_id=*/std::nullopt),
+          BuildPrivateAggregationForEventRequest(
+              /*bucket=*/234, /*value=*/5,
+              /*event_type=*/"reserved.always",
+              /*filtering_id=*/0),
+          BuildPrivateAggregationForEventRequest(
+              /*bucket=*/234, /*value=*/5,
+              /*event_type=*/"reserved.always",
+              /*filtering_id=*/255),
+          BuildPrivateAggregationForEventRequest(/*bucket=*/456, /*value=*/7,
+                                                 /*event_type=*/"click",
+                                                 /*filtering_id=*/2)));
+}
+
 TEST_F(AuctionRunnerTest,
        PrivateAggregationReportGenerateBidInvalidReservedEventType) {
   StartStandardAuctionWithMockService();
@@ -15033,6 +15184,7 @@
           /*debug_loss_report_url=*/std::nullopt,
           /*debug_win_report_url=*/std::nullopt,
           std::move(score_ad_1_pa_requests),
+          /*real_time_contributions=*/{},
           /*scoring_latency=*/base::TimeDelta(),
           /*score_ad_dependency_latencies=*/
           auction_worklet::mojom::ScoreAdDependencyLatencies::New(
@@ -15151,6 +15303,7 @@
             /*debug_loss_report_url=*/std::nullopt,
             /*debug_win_report_url=*/std::nullopt,
             /*pa_requests=*/std::move(seller_pa_requests),
+            /*real_time_contributions=*/{},
             /*scoring_latency=*/base::Milliseconds(100 * i + 20),
             /*score_ad_dependency_latencies=*/
             auction_worklet::mojom::ScoreAdDependencyLatencies::New(
@@ -15308,6 +15461,7 @@
             /*debug_loss_report_url=*/std::nullopt,
             /*debug_win_report_url=*/std::nullopt,
             /*pa_requests=*/std::move(component_seller_pa_requests),
+            /*real_time_contributions=*/{},
             /*scoring_latency=*/base::Milliseconds(100 * i + 20),
             /*score_ad_dependency_latencies=*/
             auction_worklet::mojom::ScoreAdDependencyLatencies::New(
@@ -15340,6 +15494,7 @@
           /*debug_loss_report_url=*/std::nullopt,
           /*debug_win_report_url=*/std::nullopt,
           /*pa_requests=*/std::move(top_seller_pa_requests),
+          /*real_time_contributions=*/{},
           /*scoring_latency=*/base::Milliseconds(30),
           /*score_ad_dependency_latencies=*/
           auction_worklet::mojom::ScoreAdDependencyLatencies::New(
@@ -18830,6 +18985,7 @@
             /*scoring_signals_data_version=*/std::nullopt,
             test_case.seller_debug_loss_report_url,
             test_case.seller_debug_win_report_url, /*pa_requests=*/{},
+            /*real_time_contributions=*/{},
             /*scoring_latency=*/base::TimeDelta(),
             /*score_ad_dependency_latencies=*/
             auction_worklet::mojom::ScoreAdDependencyLatencies::New(
@@ -18907,6 +19063,7 @@
           /*scoring_signals_data_version=*/std::nullopt,
           GURL("https://seller-debug-loss-reporting.com/1"),
           GURL("https://seller-debug-win-reporting.com/1"), /*pa_requests=*/{},
+          /*real_time_contributions=*/{},
           /*scoring_latency=*/base::TimeDelta(),
           /*score_ad_dependency_latencies=*/
           auction_worklet::mojom::ScoreAdDependencyLatencies::New(
diff --git a/content/browser/interest_group/interest_group_auction.cc b/content/browser/interest_group/interest_group_auction.cc
index 66d7510..9ff1c6bf 100644
--- a/content/browser/interest_group/interest_group_auction.cc
+++ b/content/browser/interest_group/interest_group_auction.cc
@@ -73,6 +73,7 @@
 #include "content/services/auction_worklet/public/mojom/bidder_worklet.mojom-forward.h"
 #include "content/services/auction_worklet/public/mojom/bidder_worklet.mojom.h"
 #include "content/services/auction_worklet/public/mojom/private_aggregation_request.mojom.h"
+#include "content/services/auction_worklet/public/mojom/real_time_reporting.mojom.h"
 #include "content/services/auction_worklet/public/mojom/seller_worklet.mojom.h"
 #include "crypto/sha2.h"
 #include "mojo/public/cpp/bindings/associated_receiver.h"
@@ -1226,6 +1227,7 @@
           update_priority_signals_overrides,
       PrivateAggregationRequests pa_requests,
       PrivateAggregationRequests non_kanon_pa_requests,
+      RealTimeReportingContributions real_time_contributions,
       base::TimeDelta bidding_latency,
       auction_worklet::mojom::GenerateBidDependencyLatenciesPtr
           generate_bid_dependency_latencies,
@@ -1250,7 +1252,8 @@
         state, std::move(mojo_bids), bidding_signals_data_version,
         debug_loss_report_url, debug_win_report_url, set_priority,
         std::move(update_priority_signals_overrides), std::move(pa_requests),
-        std::move(non_kanon_pa_requests), reject_reason, errors);
+        std::move(non_kanon_pa_requests), std::move(real_time_contributions),
+        reject_reason, errors);
   }
 
   void SetForDebuggingOnlyInCooldownOrLockout(
@@ -1551,6 +1554,7 @@
         /*update_priority_signals_overrides=*/{},
         /*pa_requests=*/{},
         /*non_kanon_pa_requests=*/{},
+        /*real_time_contributions=*/{},
         /*reject_reason=*/auction_worklet::mojom::RejectReason::kNotAvailable,
         /*errors=*/{});
   }
@@ -1732,6 +1736,7 @@
           /*update_priority_signals_overrides=*/{},
           /*pa_requests=*/{},
           /*non_kanon_pa_requests=*/{},
+          /*real_time_contributions=*/{},
           /*reject_reason=*/auction_worklet::mojom::RejectReason::kNotAvailable,
           /*errors=*/{});
       // If this was the last bidder, and it was filtered out, there's nothing
@@ -1826,6 +1831,7 @@
           update_priority_signals_overrides,
       PrivateAggregationRequests pa_requests,
       PrivateAggregationRequests non_kanon_pa_requests,
+      RealTimeReportingContributions real_time_contributions,
       auction_worklet::mojom::RejectReason reject_reason,
       const std::vector<std::string>& errors) {
     DCHECK(!state->made_bid);
@@ -1880,19 +1886,11 @@
                      request_ptr) { return request_ptr.is_null(); }),
           base::NotFatalUntil::M128);
 
-    // TODO(crbug.com/330744610): Allow filtering IDs to be set.
-    if (base::ranges::any_of(
-            pa_requests,
-            [](const auction_worklet::mojom::PrivateAggregationRequestPtr&
-                   request_ptr) {
-              return request_ptr->contribution->is_histogram_contribution() &&
-                     request_ptr->contribution->get_histogram_contribution()
-                         ->filtering_id.has_value();
-            })) {
+    if (!base::ranges::all_of(pa_requests, HasValidFilteringId)) {
       mojo_bids.clear();
       pa_requests.clear();
       generate_bid_client_receiver_set_.ReportBadMessage(
-          "Filtering ID set inappropriately");
+          "Private Aggregation filtering ID invalid");
     }
     if (base::ranges::any_of(
             non_kanon_pa_requests,
@@ -3327,6 +3325,7 @@
   // TODO(caraitto): Consider adding renderer and Mojo validation to ensure that
   // bucket sums can't be out of range, and scales can't be negative, infinite,
   // or NaN.
+  // TODO(crbug.com/330744610): Consider allowing filtering ID to be set.
   InterestGroupAuctionReporter::PrivateAggregationKey agg_key = {
       config_->seller, config_->aggregation_coordinator_origin};
   PrivateAggregationRequests& destination_vector =
@@ -3339,7 +3338,6 @@
                       *bucket_base + report_buyers_config->bucket,
                       base::saturated_cast<int32_t>(
                           std::max(0.0, value * report_buyers_config->scale)),
-                      // TODO(crbug.com/330744610): Allow filtering ID to be set
                       /*filtering_id=*/std::nullopt)),
           // TODO(caraitto): Consider allowing this to be set.
           blink::mojom::AggregationServiceMode::kDefault,
@@ -4600,18 +4598,12 @@
     return false;
   }
 
-  // TODO(crbug.com/330744610): Allow filtering IDs to be set.
-  if (base::ranges::any_of(
-          pa_requests,
-          [](const auction_worklet::mojom::PrivateAggregationRequestPtr&
-                 request_ptr) {
-            return request_ptr->contribution->is_histogram_contribution() &&
-                   request_ptr->contribution->get_histogram_contribution()
-                       ->filtering_id.has_value();
-          })) {
-    score_ad_receivers_.ReportBadMessage("Filtering ID set inappropriately");
+  if (!base::ranges::all_of(pa_requests, HasValidFilteringId)) {
+    score_ad_receivers_.ReportBadMessage(
+        "Private Aggregation filtering ID invalid");
     return false;
   }
+
   return true;
 }
 
@@ -4625,6 +4617,7 @@
     const std::optional<GURL>& debug_loss_report_url,
     const std::optional<GURL>& debug_win_report_url,
     PrivateAggregationRequests pa_requests,
+    RealTimeReportingContributions real_time_contributions,
     base::TimeDelta scoring_latency,
     auction_worklet::mojom::ScoreAdDependencyLatenciesPtr
         score_ad_dependency_latencies,
diff --git a/content/browser/interest_group/interest_group_auction.h b/content/browser/interest_group/interest_group_auction.h
index e6a09258..a78e9d71 100644
--- a/content/browser/interest_group/interest_group_auction.h
+++ b/content/browser/interest_group/interest_group_auction.h
@@ -38,6 +38,7 @@
 #include "content/public/browser/content_browser_client.h"
 #include "content/services/auction_worklet/public/mojom/bidder_worklet.mojom.h"
 #include "content/services/auction_worklet/public/mojom/private_aggregation_request.mojom.h"
+#include "content/services/auction_worklet/public/mojom/real_time_reporting.mojom.h"
 #include "content/services/auction_worklet/public/mojom/seller_worklet.mojom.h"
 #include "mojo/public/cpp/base/big_buffer.h"
 #include "mojo/public/cpp/bindings/associated_receiver_set.h"
@@ -168,6 +169,9 @@
   using PrivateAggregationRequests =
       std::vector<auction_worklet::mojom::PrivateAggregationRequestPtr>;
 
+  using RealTimeReportingContributions =
+      std::vector<auction_worklet::mojom::RealTimeReportingContributionPtr>;
+
   // Helps determine which level of worklet a particular PA request came from.
   enum class PrivateAggregationPhase {
     kBidder,
@@ -1025,6 +1029,7 @@
       const std::optional<GURL>& debug_loss_report_url,
       const std::optional<GURL>& debug_win_report_url,
       PrivateAggregationRequests pa_requests,
+      RealTimeReportingContributions real_time_contributions,
       base::TimeDelta scoring_latency,
       auction_worklet::mojom::ScoreAdDependencyLatenciesPtr
           score_ad_dependency_latencies,
diff --git a/content/browser/interest_group/interest_group_auction_reporter.cc b/content/browser/interest_group/interest_group_auction_reporter.cc
index 80f042b2..34f44d4 100644
--- a/content/browser/interest_group/interest_group_auction_reporter.cc
+++ b/content/browser/interest_group/interest_group_auction_reporter.cc
@@ -597,11 +597,8 @@
   timings.script_run_time = reporting_latency;
   for (auction_worklet::mojom::PrivateAggregationRequestPtr& request :
        pa_requests) {
-    // TODO(crbug.com/330744610): Allow filtering ID to be set.
-    if (request->contribution->is_histogram_contribution() &&
-        request->contribution->get_histogram_contribution()
-            ->filtering_id.has_value()) {
-      mojo::ReportBadMessage("Filtering ID set inappropriately");
+    if (!HasValidFilteringId(request)) {
+      mojo::ReportBadMessage("Private Aggregation filtering ID invalid");
     }
 
     // reportResult() only gets executed for seller when there was an auction
@@ -919,11 +916,8 @@
   timings.script_run_time = reporting_latency;
   for (auction_worklet::mojom::PrivateAggregationRequestPtr& request :
        pa_requests) {
-    // TODO(crbug.com/330744610): Allow filtering ID to be set.
-    if (request->contribution->is_histogram_contribution() &&
-        request->contribution->get_histogram_contribution()
-            ->filtering_id.has_value()) {
-      mojo::ReportBadMessage("Filtering ID set inappropriately");
+    if (!HasValidFilteringId(request)) {
+      mojo::ReportBadMessage("Private Aggregation filtering ID invalid");
     }
 
     // Only winner's reportWin() gets executed, so is_winner is true, which
diff --git a/content/browser/interest_group/interest_group_auction_reporter_unittest.cc b/content/browser/interest_group/interest_group_auction_reporter_unittest.cc
index 34c1087..bdbceb30 100644
--- a/content/browser/interest_group/interest_group_auction_reporter_unittest.cc
+++ b/content/browser/interest_group/interest_group_auction_reporter_unittest.cc
@@ -120,9 +120,13 @@
       : RenderViewHostTestHarness(
             base::test::TaskEnvironment::TimeSource::MOCK_TIME),
         winning_bid_info_(GetWinningBidInfo()) {
-    feature_list_.InitAndEnableFeatureWithParameters(
-        blink::features::kPrivateAggregationApi,
-        {{"fledge_extensions_enabled", "true"}});
+    feature_list_.InitWithFeaturesAndParameters(
+        /*enabled_features=*/{{blink::features::kPrivateAggregationApi,
+                               {{"fledge_extensions_enabled", "true"}}},
+                              {blink::features::
+                                   kPrivateAggregationApiFilteringIds,
+                               {}}},
+        /*disabled_features=*/{});
 
     mojo::SetDefaultProcessErrorHandler(
         base::BindRepeating(&InterestGroupAuctionReporterTest::OnBadMessage,
@@ -519,7 +523,7 @@
                       blink::mojom::AggregatableReportHistogramContribution::
                           New(/*bucket=*/3,
                               /*value=*/4,
-                              /*filtering_id=*/std::nullopt)),
+                              /*filtering_id=*/0)),
               blink::mojom::AggregationServiceMode::kDefault,
               blink::mojom::DebugModeDetails::New());
   const auction_worklet::mojom::PrivateAggregationRequestPtr
@@ -530,7 +534,7 @@
                       blink::mojom::AggregatableReportHistogramContribution::
                           New(/*bucket=*/5,
                               /*value=*/6,
-                              /*filtering_id=*/std::nullopt)),
+                              /*filtering_id=*/1)),
               blink::mojom::AggregationServiceMode::kDefault,
               blink::mojom::DebugModeDetails::New());
   const auction_worklet::mojom::PrivateAggregationRequestPtr
@@ -541,7 +545,7 @@
                       blink::mojom::AggregatableReportHistogramContribution::
                           New(/*bucket=*/7,
                               /*value=*/8,
-                              /*filtering_id=*/std::nullopt)),
+                              /*filtering_id=*/255)),
               blink::mojom::AggregationServiceMode::kDefault,
               blink::mojom::DebugModeDetails::New());
   const auction_worklet::mojom::PrivateAggregationRequestPtr
@@ -577,6 +581,7 @@
                                   NewIdBucket(1),
                               auction_worklet::mojom::ForEventSignalValue::
                                   NewIntValue(2),
+                              /*filtering_id=*/std::nullopt,
                               "event_type")),
               blink::mojom::AggregationServiceMode::kDefault,
               blink::mojom::DebugModeDetails::New());
@@ -591,6 +596,7 @@
                                   NewIdBucket(42),
                               auction_worklet::mojom::ForEventSignalValue::
                                   NewIntValue(24),
+                              /*filtering_id=*/std::nullopt,
                               "event_type2")),
               blink::mojom::AggregationServiceMode::kDefault,
               blink::mojom::DebugModeDetails::New());
@@ -605,6 +611,7 @@
                                   NewIdBucket(3),
                               auction_worklet::mojom::ForEventSignalValue::
                                   NewIntValue(4),
+                              /*filtering_id=*/0,
                               "event_type")),
               blink::mojom::AggregationServiceMode::kDefault,
               blink::mojom::DebugModeDetails::New());
diff --git a/content/browser/interest_group/interest_group_browsertest.cc b/content/browser/interest_group/interest_group_browsertest.cc
index 99fbf02..546200c 100644
--- a/content/browser/interest_group/interest_group_browsertest.cc
+++ b/content/browser/interest_group/interest_group_browsertest.cc
@@ -727,6 +727,7 @@
         /*disabled_features=*/
         {blink::features::kFencedFrames,
          blink::features::kFledgeEnforceKAnonymity,
+         blink::features::kFledgePermitCrossOriginTrustedSignals,
          features::kCookieDeprecationFacilitatedTesting});
   }
 
diff --git a/content/browser/interest_group/interest_group_pa_report_util.cc b/content/browser/interest_group/interest_group_pa_report_util.cc
index b017991b..60183caf 100644
--- a/content/browser/interest_group/interest_group_pa_report_util.cc
+++ b/content/browser/interest_group/interest_group_pa_report_util.cc
@@ -22,6 +22,7 @@
 #include "base/numerics/clamped_math.h"
 #include "components/aggregation_service/aggregation_coordinator_utils.h"
 #include "components/aggregation_service/features.h"
+#include "content/browser/private_aggregation/private_aggregation_host.h"
 #include "content/browser/private_aggregation/private_aggregation_manager.h"
 #include "content/common/content_export.h"
 #include "content/services/auction_worklet/public/mojom/private_aggregation_request.mojom.h"
@@ -226,9 +227,14 @@
     value = value_opt.value();
   }
 
-  // TODO(crbug.com/330744610): Allow filtering ID to be set.
+  std::optional<uint64_t> filtering_id;
+  if (base::FeatureList::IsEnabled(
+          blink::features::kPrivateAggregationApiFilteringIds)) {
+    filtering_id = contribution->filtering_id;
+  }
+
   return blink::mojom::AggregatableReportHistogramContribution::New(
-      bucket, value, /*filtering_id=*/std::nullopt);
+      bucket, value, filtering_id);
 }
 
 }  // namespace
@@ -382,6 +388,7 @@
         PrivateAggregationBudgetKey::Api::kProtectedAudience,
         /*context_id=*/std::nullopt,
         /*timeout=*/std::nullopt, aggregation_coordinator_origin,
+        PrivateAggregationHost::kDefaultFilteringIdMaxBytes,
         remote_host.BindNewPipeAndPassReceiver());
 
     // The worklet origin should be potentially trustworthy (and no context ID
@@ -397,4 +404,24 @@
   }
 }
 
+bool HasValidFilteringId(
+    const auction_worklet::mojom::PrivateAggregationRequestPtr& request) {
+  std::optional<uint64_t> filtering_id;
+  if (request->contribution->is_histogram_contribution()) {
+    filtering_id =
+        request->contribution->get_histogram_contribution()->filtering_id;
+  } else {
+    CHECK(request->contribution->is_for_event_contribution());
+    filtering_id =
+        request->contribution->get_for_event_contribution()->filtering_id;
+  }
+
+  if (!base::FeatureList::IsEnabled(
+          blink::features::kPrivateAggregationApiFilteringIds)) {
+    return filtering_id == std::nullopt;
+  }
+
+  return filtering_id.value_or(0) <= 255;
+}
+
 }  // namespace content
diff --git a/content/browser/interest_group/interest_group_pa_report_util.h b/content/browser/interest_group/interest_group_pa_report_util.h
index 35c4240..9c5cbee 100644
--- a/content/browser/interest_group/interest_group_pa_report_util.h
+++ b/content/browser/interest_group/interest_group_pa_report_util.h
@@ -78,6 +78,10 @@
     std::optional<url::Origin> aggregation_coordinator_origin,
     const url::Origin& main_frame_origin);
 
+// Returns false if request has an invalid filtering ID.
+CONTENT_EXPORT bool HasValidFilteringId(
+    const auction_worklet::mojom::PrivateAggregationRequestPtr& request);
+
 }  // namespace content
 
 #endif  // CONTENT_BROWSER_INTEREST_GROUP_INTEREST_GROUP_PA_REPORT_UTIL_H_
diff --git a/content/browser/interest_group/interest_group_pa_report_util_unittest.cc b/content/browser/interest_group/interest_group_pa_report_util_unittest.cc
index 030735e..f0fdd5e 100644
--- a/content/browser/interest_group/interest_group_pa_report_util_unittest.cc
+++ b/content/browser/interest_group/interest_group_pa_report_util_unittest.cc
@@ -6,13 +6,16 @@
 
 #include <stdint.h>
 
+#include <limits>
 #include <optional>
 #include <string>
 #include <utility>
 
+#include "base/test/scoped_feature_list.h"
 #include "content/services/auction_worklet/public/mojom/private_aggregation_request.mojom.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/abseil-cpp/absl/numeric/int128.h"
+#include "third_party/blink/public/common/features.h"
 #include "third_party/blink/public/mojom/aggregation_service/aggregatable_report.mojom.h"
 
 namespace content {
@@ -55,12 +58,13 @@
 // uint128 `bucket` and int `value`.
 auction_worklet::mojom::PrivateAggregationRequestPtr CreateHistogramRequest(
     absl::uint128 bucket,
-    int32_t value) {
+    int32_t value,
+    std::optional<uint64_t> filtering_id = std::nullopt) {
   return auction_worklet::mojom::PrivateAggregationRequest::New(
       auction_worklet::mojom::AggregatableReportContribution::
           NewHistogramContribution(
               blink::mojom::AggregatableReportHistogramContribution::New(
-                  bucket, value, /*filtering_id=*/std::nullopt)),
+                  bucket, value, filtering_id)),
       blink::mojom::AggregationServiceMode::kDefault,
       blink::mojom::DebugModeDetails::New());
 }
@@ -70,12 +74,13 @@
 auction_worklet::mojom::PrivateAggregationRequestPtr CreateForEventRequest(
     absl::uint128 bucket,
     int32_t value,
-    const std::string& event_type) {
+    const std::string& event_type,
+    std::optional<uint64_t> filtering_id = std::nullopt) {
   auto contribution =
       auction_worklet::mojom::AggregatableReportForEventContribution::New(
           auction_worklet::mojom::ForEventSignalBucket::NewIdBucket(bucket),
           auction_worklet::mojom::ForEventSignalValue::NewIntValue(value),
-          event_type);
+          filtering_id, event_type);
 
   return auction_worklet::mojom::PrivateAggregationRequest::New(
       auction_worklet::mojom::AggregatableReportContribution::
@@ -90,13 +95,14 @@
 CreateForEventRequestWithBucketObject(
     auction_worklet::mojom::SignalBucketPtr bucket,
     int32_t value,
-    const std::string& event_type) {
+    const std::string& event_type,
+    std::optional<uint64_t> filtering_id = std::nullopt) {
   auto contribution =
       auction_worklet::mojom::AggregatableReportForEventContribution::New(
           auction_worklet::mojom::ForEventSignalBucket::NewSignalBucket(
               std::move(bucket)),
           auction_worklet::mojom::ForEventSignalValue::NewIntValue(value),
-          event_type);
+          filtering_id, event_type);
 
   return auction_worklet::mojom::PrivateAggregationRequest::New(
       auction_worklet::mojom::AggregatableReportContribution::
@@ -111,13 +117,14 @@
 CreateForEventRequestWithValueObject(
     absl::uint128 bucket,
     auction_worklet::mojom::SignalValuePtr value,
-    const std::string& event_type) {
+    const std::string& event_type,
+    std::optional<uint64_t> filtering_id = std::nullopt) {
   auto contribution =
       auction_worklet::mojom::AggregatableReportForEventContribution::New(
           auction_worklet::mojom::ForEventSignalBucket::NewIdBucket(bucket),
           auction_worklet::mojom::ForEventSignalValue::NewSignalValue(
               std::move(value)),
-          event_type);
+          filtering_id, event_type);
 
   return auction_worklet::mojom::PrivateAggregationRequest::New(
       auction_worklet::mojom::AggregatableReportContribution::
@@ -731,4 +738,92 @@
   }
 }
 
+TEST_F(InterestGroupPaReportUtilTest,
+       FilteringIdPassedUnchangedIfFeatureEnabled) {
+  base::test::ScopedFeatureList scoped_feature_list{
+      blink::features::kPrivateAggregationApiFilteringIds};
+
+  const std::optional<uint64_t> kFilteringIdTestCases[] = {std::nullopt, 0, 1,
+                                                           255};
+  for (std::optional<uint64_t> filtering_id : kFilteringIdTestCases) {
+    // Filtering ID here is expected to be unchanged
+    std::optional<uint64_t> expected_filtering_id = filtering_id;
+    EXPECT_EQ(CreatePrivateAggregationRequestWithEventType(
+                  CreateHistogramRequest(/*bucket=*/123, /*value=*/45,
+                                         expected_filtering_id),
+                  /*event_type=*/"click"),
+              FillInPrivateAggregationRequest(
+                  CreateForEventRequest(
+                      /*bucket=*/123, /*value=*/45,
+                      /*event_type=*/"click", filtering_id),
+                  /*winning_bid=*/1, /*highest_scoring_other_bid=*/2,
+                  /*reject_reason=*/std::nullopt, PrivateAggregationTimings(),
+                  /*is_winner=*/true));
+  }
+}
+
+TEST_F(InterestGroupPaReportUtilTest, HasValidFilteringId_FeatureEnabled) {
+  base::test::ScopedFeatureList scoped_feature_list;
+  scoped_feature_list.InitAndEnableFeature(
+      blink::features::kPrivateAggregationApiFilteringIds);
+
+  const struct {
+    std::optional<uint64_t> filtering_id;
+    bool expected_to_be_valid;
+  } kTestCases[] = {
+      {.filtering_id = std::nullopt, .expected_to_be_valid = true},
+      {.filtering_id = 0, .expected_to_be_valid = true},
+      {.filtering_id = 1, .expected_to_be_valid = true},
+      {.filtering_id = 255, .expected_to_be_valid = true},
+      {.filtering_id = 256, .expected_to_be_valid = false},
+      {.filtering_id = std::numeric_limits<uint64_t>::max(),
+       .expected_to_be_valid = false}};
+
+  for (const auto& test_case : kTestCases) {
+    auction_worklet::mojom::PrivateAggregationRequestPtr
+        for_event_contribution = CreateForEventRequest(
+            /*bucket=*/123, /*value=*/45,
+            /*event_type=*/"click", test_case.filtering_id);
+    auction_worklet::mojom::PrivateAggregationRequestPtr
+        histogram_contribution = CreateHistogramRequest(
+            /*bucket=*/123, /*value=*/45, test_case.filtering_id);
+    EXPECT_EQ(HasValidFilteringId(for_event_contribution),
+              test_case.expected_to_be_valid);
+    EXPECT_EQ(HasValidFilteringId(histogram_contribution),
+              test_case.expected_to_be_valid);
+  }
+}
+
+TEST_F(InterestGroupPaReportUtilTest, HasValidFilteringId_FeatureDisabled) {
+  base::test::ScopedFeatureList scoped_feature_list;
+  scoped_feature_list.InitAndDisableFeature(
+      blink::features::kPrivateAggregationApiFilteringIds);
+
+  const struct {
+    std::optional<uint64_t> filtering_id;
+    bool expected_to_be_valid;
+  } kTestCases[] = {
+      {.filtering_id = std::nullopt, .expected_to_be_valid = true},
+      {.filtering_id = 0, .expected_to_be_valid = false},
+      {.filtering_id = 1, .expected_to_be_valid = false},
+      {.filtering_id = 255, .expected_to_be_valid = false},
+      {.filtering_id = 256, .expected_to_be_valid = false},
+      {.filtering_id = std::numeric_limits<uint64_t>::max(),
+       .expected_to_be_valid = false}};
+
+  for (const auto& test_case : kTestCases) {
+    auction_worklet::mojom::PrivateAggregationRequestPtr
+        for_event_contribution = CreateForEventRequest(
+            /*bucket=*/123, /*value=*/45,
+            /*event_type=*/"click", test_case.filtering_id);
+    auction_worklet::mojom::PrivateAggregationRequestPtr
+        histogram_contribution = CreateHistogramRequest(
+            /*bucket=*/123, /*value=*/45, test_case.filtering_id);
+    EXPECT_EQ(HasValidFilteringId(for_event_contribution),
+              test_case.expected_to_be_valid);
+    EXPECT_EQ(HasValidFilteringId(histogram_contribution),
+              test_case.expected_to_be_valid);
+  }
+}
+
 }  // namespace content
diff --git a/content/browser/interest_group/mock_auction_process_manager.cc b/content/browser/interest_group/mock_auction_process_manager.cc
index 3055756..71b0e53ca 100644
--- a/content/browser/interest_group/mock_auction_process_manager.cc
+++ b/content/browser/interest_group/mock_auction_process_manager.cc
@@ -23,6 +23,7 @@
 #include "content/services/auction_worklet/public/mojom/auction_worklet_service.mojom.h"
 #include "content/services/auction_worklet/public/mojom/bidder_worklet.mojom.h"
 #include "content/services/auction_worklet/public/mojom/private_aggregation_request.mojom.h"
+#include "content/services/auction_worklet/public/mojom/real_time_reporting.mojom.h"
 #include "content/services/auction_worklet/public/mojom/seller_worklet.mojom.h"
 #include "mojo/public/cpp/bindings/associated_receiver_set.h"
 #include "mojo/public/cpp/bindings/associated_remote.h"
@@ -221,6 +222,8 @@
     const std::optional<GURL>& debug_win_report_url,
     std::vector<auction_worklet::mojom::PrivateAggregationRequestPtr>
         pa_requests,
+    std::vector<auction_worklet::mojom::RealTimeReportingContributionPtr>
+        real_time_contributions,
     auction_worklet::mojom::GenerateBidDependencyLatenciesPtr
         dependency_latencies,
     auction_worklet::mojom::RejectReason reject_reason) {
@@ -255,6 +258,7 @@
                        auction_worklet::mojom::PrioritySignalsDoublePtr>(),
         /*pa_requests=*/std::move(pa_requests),
         /*non_kanon_pa_requests=*/{},
+        /*real_time_contributions=*/{},
         /*bidding_latency=*/bidding_latency_,
         /*generate_bid_dependency_latencies=*/std::move(dependency_latencies),
         reject_reason,
@@ -278,6 +282,7 @@
                      auction_worklet::mojom::PrioritySignalsDoublePtr>(),
       /*pa_requests=*/std::move(pa_requests),
       /*non_kanon_pa_requests=*/{},
+      /*real_time_contributions=*/std::move(real_time_contributions),
       /*bidding_latency=*/bidding_latency_,
       /*generate_bid_dependency_latencies=*/std::move(dependency_latencies),
       reject_reason,
diff --git a/content/browser/interest_group/mock_auction_process_manager.h b/content/browser/interest_group/mock_auction_process_manager.h
index 1ca8bb96..2b14d023 100644
--- a/content/browser/interest_group/mock_auction_process_manager.h
+++ b/content/browser/interest_group/mock_auction_process_manager.h
@@ -21,6 +21,7 @@
 #include "content/public/browser/site_instance.h"
 #include "content/services/auction_worklet/public/mojom/auction_worklet_service.mojom.h"
 #include "content/services/auction_worklet/public/mojom/bidder_worklet.mojom.h"
+#include "content/services/auction_worklet/public/mojom/real_time_reporting.mojom.h"
 #include "content/services/auction_worklet/public/mojom/seller_worklet.mojom.h"
 #include "mojo/public/cpp/bindings/associated_receiver_set.h"
 #include "mojo/public/cpp/bindings/associated_remote.h"
@@ -161,6 +162,8 @@
       const std::optional<GURL>& debug_win_report_url = std::nullopt,
       std::vector<auction_worklet::mojom::PrivateAggregationRequestPtr>
           pa_requests = {},
+      std::vector<auction_worklet::mojom::RealTimeReportingContributionPtr>
+          real_time_contributions = {},
       auction_worklet::mojom::GenerateBidDependencyLatenciesPtr
           dependency_latencies =
               auction_worklet::mojom::GenerateBidDependencyLatenciesPtr(),
diff --git a/content/browser/interest_group/test_interest_group_private_aggregation_manager.cc b/content/browser/interest_group/test_interest_group_private_aggregation_manager.cc
index c3aba5ae..c510870 100644
--- a/content/browser/interest_group/test_interest_group_private_aggregation_manager.cc
+++ b/content/browser/interest_group/test_interest_group_private_aggregation_manager.cc
@@ -4,6 +4,8 @@
 
 #include "content/browser/interest_group/test_interest_group_private_aggregation_manager.h"
 
+#include <stddef.h>
+
 #include <map>
 #include <optional>
 #include <string>
@@ -46,6 +48,7 @@
     std::optional<std::string> context_id,
     std::optional<base::TimeDelta> timeout,
     std::optional<url::Origin> aggregation_coordinator_origin,
+    size_t filtering_id_max_bytes,
     mojo::PendingReceiver<blink::mojom::PrivateAggregationHost>
         pending_receiver) {
   EXPECT_EQ(expected_top_frame_origin_, top_frame_origin);
@@ -53,6 +56,7 @@
             api_for_budgeting);
   EXPECT_FALSE(context_id.has_value());
   EXPECT_FALSE(timeout.has_value());
+  EXPECT_EQ(filtering_id_max_bytes, 1u);
 
   // TODO(alexmt): Change once selecting the origin is possible.
   EXPECT_FALSE(aggregation_coordinator_origin.has_value());
diff --git a/content/browser/interest_group/test_interest_group_private_aggregation_manager.h b/content/browser/interest_group/test_interest_group_private_aggregation_manager.h
index a3b12a9..e88f8138 100644
--- a/content/browser/interest_group/test_interest_group_private_aggregation_manager.h
+++ b/content/browser/interest_group/test_interest_group_private_aggregation_manager.h
@@ -5,6 +5,8 @@
 #ifndef CONTENT_BROWSER_INTEREST_GROUP_TEST_INTEREST_GROUP_PRIVATE_AGGREGATION_MANAGER_H_
 #define CONTENT_BROWSER_INTEREST_GROUP_TEST_INTEREST_GROUP_PRIVATE_AGGREGATION_MANAGER_H_
 
+#include <stddef.h>
+
 #include <map>
 #include <optional>
 #include <string>
@@ -45,6 +47,7 @@
       std::optional<std::string> context_id,
       std::optional<base::TimeDelta> timeout,
       std::optional<url::Origin> aggregation_coordinator_origin,
+      size_t filtering_id_max_bytes,
       mojo::PendingReceiver<blink::mojom::PrivateAggregationHost>
           pending_receiver) override;
   void ClearBudgetData(base::Time delete_begin,
diff --git a/content/browser/private_aggregation/private_aggregation_host.cc b/content/browser/private_aggregation/private_aggregation_host.cc
index 18c14e8..80c6730a 100644
--- a/content/browser/private_aggregation/private_aggregation_host.cc
+++ b/content/browser/private_aggregation/private_aggregation_host.cc
@@ -6,6 +6,7 @@
 
 #include <stddef.h>
 
+#include <bit>
 #include <iterator>
 #include <map>
 #include <optional>
@@ -32,6 +33,7 @@
 #include "components/aggregation_service/aggregation_coordinator_utils.h"
 #include "components/aggregation_service/features.h"
 #include "content/browser/aggregation_service/aggregatable_report.h"
+#include "content/browser/aggregation_service/aggregation_service_features.h"
 #include "content/browser/private_aggregation/private_aggregation_budget_key.h"
 #include "content/browser/private_aggregation/private_aggregation_budgeter.h"
 #include "content/browser/private_aggregation/private_aggregation_features.h"
@@ -79,6 +81,7 @@
   PrivateAggregationBudgetKey::Api api_for_budgeting;
   std::optional<std::string> context_id;
   std::optional<url::Origin> aggregation_coordinator_origin;
+  size_t filtering_id_max_bytes;
 
   // If contributions have been truncated, tracks this for triggering the right
   // histogram value.
@@ -145,6 +148,7 @@
     std::optional<std::string> context_id,
     std::optional<base::TimeDelta> timeout,
     std::optional<url::Origin> aggregation_coordinator_origin,
+    size_t filtering_id_max_bytes,
     mojo::PendingReceiver<blink::mojom::PrivateAggregationHost>
         pending_receiver) {
   // If rejected, let the pending receiver be destroyed as it goes out of scope
@@ -172,7 +176,20 @@
     return false;
   }
 
-  if (timeout.has_value() && !context_id.has_value()) {
+  if (!base::FeatureList::IsEnabled(
+          blink::features::kPrivateAggregationApiFilteringIds)) {
+    filtering_id_max_bytes = kDefaultFilteringIdMaxBytes;
+  }
+  if (filtering_id_max_bytes < 1 ||
+      filtering_id_max_bytes >
+          AggregationServicePayloadContents::kMaximumFilteringIdMaxBytes) {
+    return false;
+  }
+
+  // Timeouts should only be set for deterministic reports.
+  // TODO(alexmt): Consider requiring timeouts for deterministic reports.
+  if (timeout.has_value() && !context_id.has_value() &&
+      filtering_id_max_bytes == kDefaultFilteringIdMaxBytes) {
     return false;
   }
 
@@ -182,7 +199,8 @@
                           .api_for_budgeting = api_for_budgeting,
                           .context_id = std::move(context_id),
                           .aggregation_coordinator_origin =
-                              std::move(aggregation_coordinator_origin)});
+                              std::move(aggregation_coordinator_origin),
+                          .filtering_id_max_bytes = filtering_id_max_bytes});
 
   ReceiverContext* receiver_context_raw_ptr = receiver_context.get();
 
@@ -262,12 +280,15 @@
     return;
   }
 
-  // TODO(crbug.com/330744610): Allow filtering ID to be set.
-  if (base::ranges::any_of(incoming_ptrs,
-                           [&](const ContributionPtr& contribution) {
-                             return contribution->filtering_id.has_value();
-                           })) {
-    mojo::ReportBadMessage("Filtering ID set inappropriately");
+  if (base::FeatureList::IsEnabled(
+          blink::features::kPrivateAggregationApiFilteringIds) &&
+      base::ranges::any_of(
+          incoming_ptrs, [&](const ContributionPtr& contribution) {
+            return static_cast<size_t>(
+                       std::bit_width(contribution->filtering_id.value_or(0))) >
+                   8 * receiver_set_.current_context()->filtering_id_max_bytes;
+          })) {
+    mojo::ReportBadMessage("Filtering ID too big for max bytes");
     CloseCurrentPipe(PipeResult::kFilteringIdInvalid);
     return;
   }
@@ -297,9 +318,27 @@
     PrivateAggregationBudgetKey::Api api_for_budgeting,
     std::optional<std::string> context_id,
     std::optional<url::Origin> aggregation_coordinator_origin,
+    size_t specified_filtering_id_max_bytes,
     std::vector<blink::mojom::AggregatableReportHistogramContribution>
         contributions) {
   CHECK(context_id.has_value() || !contributions.empty());
+  CHECK(debug_mode_details);
+
+  bool use_new_report_version =
+      base::FeatureList::IsEnabled(
+          blink::features::kPrivateAggregationApiFilteringIds) &&
+      base::FeatureList::IsEnabled(
+          kPrivacySandboxAggregationServiceFilteringIds);
+
+  std::optional<size_t> applied_filtering_id_max_bytes =
+      specified_filtering_id_max_bytes;
+  if (!use_new_report_version) {
+    applied_filtering_id_max_bytes.reset();
+    base::ranges::for_each(
+        contributions,
+        [&](blink::mojom::AggregatableReportHistogramContribution&
+                contribution) { contribution.filtering_id.reset(); });
+  }
 
   AggregationServicePayloadContents payload_contents(
       AggregationServicePayloadContents::Operation::kHistogram,
@@ -307,17 +346,17 @@
       // TODO(alexmt): Consider allowing this to be set.
       blink::mojom::AggregationServiceMode::kDefault,
       std::move(aggregation_coordinator_origin), kMaxNumberOfContributions,
-      // TODO(crbug.com/330744610): Allow this to be set.
-      /*filtering_id_max_bytes=*/std::nullopt);
+      applied_filtering_id_max_bytes);
 
-  CHECK(debug_mode_details);
   AggregatableReportSharedInfo shared_info(
       scheduled_report_time, std::move(report_id), reporting_origin,
       debug_mode_details->is_enabled
           ? AggregatableReportSharedInfo::DebugMode::kEnabled
           : AggregatableReportSharedInfo::DebugMode::kDisabled,
       /*additional_fields=*/base::Value::Dict(),
-      /*api_version=*/kApiReportVersion,
+      /*api_version=*/
+      use_new_report_version ? kApiReportVersionWithFilteringId
+                             : kApiReportVersionWithoutFilteringId,
       /*api_identifier=*/
       private_aggregation::GetApiIdentifier(api_for_budgeting));
 
@@ -486,7 +525,8 @@
       /*report_id=*/base::Uuid::GenerateRandomV4(), reporting_origin,
       receiver_context.api_for_budgeting,
       std::move(receiver_context.context_id),
-      std::move(receiver_context.aggregation_coordinator_origin));
+      std::move(receiver_context.aggregation_coordinator_origin),
+      receiver_context.filtering_id_max_bytes);
 
   RecordPipeResultHistogram(
       receiver_context.too_many_contributions
diff --git a/content/browser/private_aggregation/private_aggregation_host.h b/content/browser/private_aggregation/private_aggregation_host.h
index 44ff8e0..0f7abe3c 100644
--- a/content/browser/private_aggregation/private_aggregation_host.h
+++ b/content/browser/private_aggregation/private_aggregation_host.h
@@ -72,13 +72,17 @@
   using ReportRequestGenerator = base::OnceCallback<AggregatableReportRequest(
       std::vector<blink::mojom::AggregatableReportHistogramContribution>)>;
 
-  // Version string for the reports generated by this API.
-  static constexpr char kApiReportVersion[] = "0.1";
+  // Version string for the reports generated by this API. Varies based on
+  // whether the filtering ID feature is enabled. See crbug.com/330744610.
+  static constexpr char kApiReportVersionWithoutFilteringId[] = "0.1";
+  static constexpr char kApiReportVersionWithFilteringId[] = "1.0";
 
   // The maximum number of contributions that can go in an `AggregatableReport`.
   // Aligns with `attribution_reporting::kMaxAggregationKeysPerSource`.
   static constexpr size_t kMaxNumberOfContributions = 20;
 
+  static constexpr size_t kDefaultFilteringIdMaxBytes = 1;
+
   // The maximum allowed context_id string length.
   static constexpr int kMaxContextIdLength = 64;
   static_assert(kMaxContextIdLength ==
@@ -109,8 +113,11 @@
   // If `aggregation_coordinator_origin` is set, the origin must be on the
   // allowlist. But if the `kPrivateAggregationApiMultipleCloudProviders`
   // feature is disabled, this function will act as if
-  // `aggregation_coordinator_origin` was not set. The return value indicates
-  // whether the receiver was accepted. Virtual for testing.
+  // `aggregation_coordinator_origin` was not set. `filtering_id_max_bytes` must
+  // be positive and no greater than
+  // `AggregationServicePayloadContents::kMaximumFilteringIdMaxBytes`. The
+  // return value indicates whether the receiver was accepted. Virtual for
+  // testing.
   [[nodiscard]] virtual bool BindNewReceiver(
       url::Origin worklet_origin,
       url::Origin top_frame_origin,
@@ -118,6 +125,7 @@
       std::optional<std::string> context_id,
       std::optional<base::TimeDelta> timeout,
       std::optional<url::Origin> aggregation_coordinator_origin,
+      size_t filtering_id_max_bytes,
       mojo::PendingReceiver<blink::mojom::PrivateAggregationHost>
           pending_receiver);
 
@@ -146,6 +154,7 @@
       PrivateAggregationBudgetKey::Api api_for_budgeting,
       std::optional<std::string> context_id,
       std::optional<url::Origin> aggregation_coordinator_origin,
+      size_t specified_filtering_id_max_bytes,
       std::vector<blink::mojom::AggregatableReportHistogramContribution>
           contributions);
 
diff --git a/content/browser/private_aggregation/private_aggregation_host_unittest.cc b/content/browser/private_aggregation/private_aggregation_host_unittest.cc
index 135e8ec4..ccbef8f 100644
--- a/content/browser/private_aggregation/private_aggregation_host_unittest.cc
+++ b/content/browser/private_aggregation/private_aggregation_host_unittest.cc
@@ -6,6 +6,7 @@
 
 #include <stddef.h>
 
+#include <limits>
 #include <memory>
 #include <optional>
 #include <string_view>
@@ -25,6 +26,7 @@
 #include "base/values.h"
 #include "components/aggregation_service/aggregation_coordinator_utils.h"
 #include "content/browser/aggregation_service/aggregatable_report.h"
+#include "content/browser/aggregation_service/aggregation_service_features.h"
 #include "content/browser/aggregation_service/aggregation_service_test_utils.h"
 #include "content/browser/private_aggregation/private_aggregation_budget_key.h"
 #include "content/browser/private_aggregation/private_aggregation_budgeter.h"
@@ -120,6 +122,7 @@
       PrivateAggregationBudgetKey::Api::kProtectedAudience,
       /*context_id=*/std::nullopt, /*timeout=*/std::nullopt,
       /*aggregation_coordinator_origin=*/std::nullopt,
+      PrivateAggregationHost::kDefaultFilteringIdMaxBytes,
       remote.BindNewPipeAndPassReceiver()));
 
   std::optional<AggregatableReportRequest> validated_request;
@@ -210,6 +213,7 @@
         kExampleOrigin, kMainFrameOrigin, apis[i], /*context_id=*/std::nullopt,
         /*timeout=*/std::nullopt,
         /*aggregation_coordinator_origin=*/std::nullopt,
+        PrivateAggregationHost::kDefaultFilteringIdMaxBytes,
         remotes[i].BindNewPipeAndPassReceiver()));
     EXPECT_CALL(mock_callback_,
                 Run(_, _, Property(&PrivateAggregationBudgetKey::api, apis[i]),
@@ -278,6 +282,7 @@
         PrivateAggregationBudgetKey::Api::kProtectedAudience,
         /*context_id=*/std::nullopt, /*timeout=*/std::nullopt,
         /*aggregation_coordinator_origin=*/std::nullopt,
+        PrivateAggregationHost::kDefaultFilteringIdMaxBytes,
         remotes[i].BindNewPipeAndPassReceiver()));
 
     std::vector<blink::mojom::AggregatableReportHistogramContributionPtr>
@@ -334,24 +339,28 @@
       PrivateAggregationBudgetKey::Api::kProtectedAudience,
       /*context_id=*/std::nullopt, /*timeout=*/std::nullopt,
       /*aggregation_coordinator_origin=*/std::nullopt,
+      PrivateAggregationHost::kDefaultFilteringIdMaxBytes,
       remotes[0].BindNewPipeAndPassReceiver()));
   EXPECT_TRUE(host_->BindNewReceiver(
       kExampleOriginB, kMainFrameOrigin,
       PrivateAggregationBudgetKey::Api::kProtectedAudience,
       /*context_id=*/std::nullopt, /*timeout=*/std::nullopt,
       /*aggregation_coordinator_origin=*/std::nullopt,
+      PrivateAggregationHost::kDefaultFilteringIdMaxBytes,
       remotes[1].BindNewPipeAndPassReceiver()));
   EXPECT_TRUE(host_->BindNewReceiver(
       kExampleOriginA, kMainFrameOrigin,
       PrivateAggregationBudgetKey::Api::kSharedStorage,
       /*context_id=*/std::nullopt, /*timeout=*/std::nullopt,
       /*aggregation_coordinator_origin=*/std::nullopt,
+      PrivateAggregationHost::kDefaultFilteringIdMaxBytes,
       remotes[2].BindNewPipeAndPassReceiver()));
   EXPECT_TRUE(host_->BindNewReceiver(
       kExampleOriginB, kMainFrameOrigin,
       PrivateAggregationBudgetKey::Api::kSharedStorage,
       /*context_id=*/std::nullopt, /*timeout=*/std::nullopt,
       /*aggregation_coordinator_origin=*/std::nullopt,
+      PrivateAggregationHost::kDefaultFilteringIdMaxBytes,
       remotes[3].BindNewPipeAndPassReceiver()));
 
   // Use the bucket as a sentinel to ensure that calls were routed correctly.
@@ -443,6 +452,7 @@
       PrivateAggregationBudgetKey::Api::kProtectedAudience,
       /*context_id=*/std::nullopt, /*timeout=*/std::nullopt,
       /*aggregation_coordinator_origin=*/std::nullopt,
+      PrivateAggregationHost::kDefaultFilteringIdMaxBytes,
       remote_1.BindNewPipeAndPassReceiver()));
 
   mojo::Remote<blink::mojom::PrivateAggregationHost> remote_2;
@@ -451,6 +461,7 @@
       PrivateAggregationBudgetKey::Api::kProtectedAudience,
       /*context_id=*/std::nullopt, /*timeout=*/std::nullopt,
       /*aggregation_coordinator_origin=*/std::nullopt,
+      PrivateAggregationHost::kDefaultFilteringIdMaxBytes,
       remote_2.BindNewPipeAndPassReceiver()));
 
   // Attempt to send a message to an unconnected remote. The request should
@@ -489,6 +500,7 @@
       PrivateAggregationBudgetKey::Api::kProtectedAudience, kTooLongContextId,
       /*timeout=*/std::nullopt,
       /*aggregation_coordinator_origin=*/std::nullopt,
+      PrivateAggregationHost::kDefaultFilteringIdMaxBytes,
       remote.BindNewPipeAndPassReceiver()));
 
   // Attempt to send a message to an unconnected remote. The request should
@@ -509,7 +521,7 @@
   histogram.ExpectTotalCount(kPipeResultHistogram, 0);
 }
 
-TEST_F(PrivateAggregationHostTest, TimeoutSetWithoutContextId_Fails) {
+TEST_F(PrivateAggregationHostTest, TimeoutSetWithoutDeterministicReport_Fails) {
   const url::Origin kExampleOrigin =
       url::Origin::Create(GURL("https://example.com"));
   const url::Origin kMainFrameOrigin =
@@ -522,9 +534,50 @@
       /*context_id=*/std::nullopt,
       /*timeout=*/base::Minutes(1),
       /*aggregation_coordinator_origin=*/std::nullopt,
+      PrivateAggregationHost::kDefaultFilteringIdMaxBytes,
       remote.BindNewPipeAndPassReceiver()));
 }
 
+TEST_F(PrivateAggregationHostTest, TimeoutSetWithContextId_Succeeds) {
+  const url::Origin kExampleOrigin =
+      url::Origin::Create(GURL("https://example.com"));
+  const url::Origin kMainFrameOrigin =
+      url::Origin::Create(GURL("https://main_frame.com"));
+
+  mojo::Remote<blink::mojom::PrivateAggregationHost> remote;
+  EXPECT_TRUE(host_->BindNewReceiver(
+      kExampleOrigin, kMainFrameOrigin,
+      PrivateAggregationBudgetKey::Api::kProtectedAudience,
+      /*context_id=*/"example_context_id",
+      /*timeout=*/base::Minutes(1),
+      /*aggregation_coordinator_origin=*/std::nullopt,
+      PrivateAggregationHost::kDefaultFilteringIdMaxBytes,
+      remote.BindNewPipeAndPassReceiver()));
+}
+
+TEST_F(PrivateAggregationHostTest,
+       TimeoutSetWithNonDefaultFilteringIdMaxBytes_Succeeds) {
+  base::test::ScopedFeatureList scoped_feature_list;
+  scoped_feature_list.InitWithFeatures(
+      /*enabled_features=*/{blink::features::kPrivateAggregationApiFilteringIds,
+                            kPrivacySandboxAggregationServiceFilteringIds},
+      /*disabled_features=*/{});
+
+  const url::Origin kExampleOrigin =
+      url::Origin::Create(GURL("https://example.com"));
+  const url::Origin kMainFrameOrigin =
+      url::Origin::Create(GURL("https://main_frame.com"));
+
+  mojo::Remote<blink::mojom::PrivateAggregationHost> remote;
+  EXPECT_TRUE(host_->BindNewReceiver(
+      kExampleOrigin, kMainFrameOrigin,
+      PrivateAggregationBudgetKey::Api::kProtectedAudience,
+      /*context_id=*/std::nullopt,
+      /*timeout=*/base::Minutes(1),
+      /*aggregation_coordinator_origin=*/std::nullopt,
+      /*filtering_id_max_bytes=*/3, remote.BindNewPipeAndPassReceiver()));
+}
+
 TEST_F(PrivateAggregationHostTest, InvalidRequest_Rejected) {
   const url::Origin kExampleOrigin =
       url::Origin::Create(GURL("https://example.com"));
@@ -553,6 +606,7 @@
         PrivateAggregationBudgetKey::Api::kProtectedAudience,
         /*context_id=*/std::nullopt, /*timeout=*/std::nullopt,
         /*aggregation_coordinator_origin=*/std::nullopt,
+        PrivateAggregationHost::kDefaultFilteringIdMaxBytes,
         remote.BindNewPipeAndPassReceiver()));
 
     base::HistogramTester histogram;
@@ -570,6 +624,7 @@
         PrivateAggregationBudgetKey::Api::kProtectedAudience,
         /*context_id=*/std::nullopt, /*timeout=*/std::nullopt,
         /*aggregation_coordinator_origin=*/std::nullopt,
+        PrivateAggregationHost::kDefaultFilteringIdMaxBytes,
         remote.BindNewPipeAndPassReceiver()));
 
     base::HistogramTester histogram;
@@ -600,6 +655,7 @@
       PrivateAggregationBudgetKey::Api::kProtectedAudience,
       /*context_id=*/std::nullopt, /*timeout=*/std::nullopt,
       /*aggregation_coordinator_origin=*/std::nullopt,
+      PrivateAggregationHost::kDefaultFilteringIdMaxBytes,
       remote.BindNewPipeAndPassReceiver()));
   std::vector<blink::mojom::AggregatableReportHistogramContributionPtr>
       too_many_contributions;
@@ -647,6 +703,7 @@
       PrivateAggregationBudgetKey::Api::kProtectedAudience,
       /*context_id=*/std::nullopt, /*timeout=*/std::nullopt,
       /*aggregation_coordinator_origin=*/std::nullopt,
+      PrivateAggregationHost::kDefaultFilteringIdMaxBytes,
       remote.BindNewPipeAndPassReceiver()));
 
   // If the API is enabled, the call should succeed.
@@ -688,6 +745,7 @@
       PrivateAggregationBudgetKey::Api::kProtectedAudience,
       /*context_id=*/std::nullopt, /*timeout=*/std::nullopt,
       /*aggregation_coordinator_origin=*/std::nullopt,
+      PrivateAggregationHost::kDefaultFilteringIdMaxBytes,
       remote.BindNewPipeAndPassReceiver()));
 
   // If the API is enabled, the call should succeed.
@@ -725,6 +783,7 @@
       PrivateAggregationBudgetKey::Api::kProtectedAudience,
       "example_context_id", /*timeout=*/std::nullopt,
       /*aggregation_coordinator_origin=*/std::nullopt,
+      PrivateAggregationHost::kDefaultFilteringIdMaxBytes,
       remote.BindNewPipeAndPassReceiver()));
 
   constexpr base::TimeDelta kTimeToGenerateReportRequest =
@@ -795,6 +854,7 @@
         PrivateAggregationBudgetKey::Api::kProtectedAudience,
         "example_context_id", /*timeout=*/std::nullopt,
         /*aggregation_coordinator_origin=*/std::nullopt,
+        PrivateAggregationHost::kDefaultFilteringIdMaxBytes,
         remote.BindNewPipeAndPassReceiver()));
 
     if (debug_mode_details_arg->is_enabled) {
@@ -839,6 +899,7 @@
         PrivateAggregationBudgetKey::Api::kProtectedAudience,
         /*context_id=*/std::nullopt, /*timeout=*/std::nullopt,
         /*aggregation_coordinator_origin=*/std::nullopt,
+        PrivateAggregationHost::kDefaultFilteringIdMaxBytes,
         remote.BindNewPipeAndPassReceiver()));
 
     EXPECT_TRUE(remote.is_connected());
@@ -853,6 +914,7 @@
         PrivateAggregationBudgetKey::Api::kProtectedAudience,
         /*context_id=*/std::nullopt, /*timeout=*/std::nullopt,
         /*aggregation_coordinator_origin=*/std::nullopt,
+        PrivateAggregationHost::kDefaultFilteringIdMaxBytes,
         remote.BindNewPipeAndPassReceiver()));
 
     // Enabling the debug mode has no effect.
@@ -918,6 +980,7 @@
         PrivateAggregationBudgetKey::Api::kProtectedAudience,
         /*context_id=*/std::nullopt, /*timeout=*/std::nullopt,
         test_case.aggregation_coordinator_origin,
+        PrivateAggregationHost::kDefaultFilteringIdMaxBytes,
         remote.BindNewPipeAndPassReceiver());
 
     EXPECT_EQ(bind_result, test_case.expected_bind_result);
@@ -984,6 +1047,7 @@
         kExampleOrigin, kMainFrameOrigin,
         PrivateAggregationBudgetKey::Api::kProtectedAudience,
         /*context_id=*/std::nullopt, /*timeout=*/std::nullopt, test_case,
+        PrivateAggregationHost::kDefaultFilteringIdMaxBytes,
         remote.BindNewPipeAndPassReceiver());
 
     // The provided origin should be ignored.
@@ -1013,6 +1077,316 @@
   }
 }
 
+TEST_F(PrivateAggregationHostTest, FilteringIdMaxBytesValidated) {
+  base::test::ScopedFeatureList scoped_feature_list;
+  scoped_feature_list.InitWithFeatures(
+      /*enabled_features=*/{blink::features::kPrivateAggregationApiFilteringIds,
+                            kPrivacySandboxAggregationServiceFilteringIds},
+      /*disabled_features=*/{});
+
+  const url::Origin kExampleOrigin =
+      url::Origin::Create(GURL("https://example.com"));
+  const url::Origin kMainFrameOrigin =
+      url::Origin::Create(GURL("https://main_frame.com"));
+
+  const struct {
+    const char* description;
+    const size_t filtering_id_max_bytes;
+    bool expected_bind_result;
+  } kTestCases[] = {
+      {
+          "filtering_id_max_bytes zero",
+          0,
+          false,
+      },
+      {
+          "filtering_id_max_bytes minimum valid value",
+          1,
+          true,
+      },
+      {
+          "filtering_id_max_bytes maximum valid value",
+          AggregationServicePayloadContents::kMaximumFilteringIdMaxBytes,
+          true,
+      },
+      {
+          "filtering_id_max_bytes too large",
+          AggregationServicePayloadContents::kMaximumFilteringIdMaxBytes + 1,
+          false,
+      },
+  };
+
+  for (const auto& test_case : kTestCases) {
+    SCOPED_TRACE(test_case.description);
+
+    mojo::Remote<blink::mojom::PrivateAggregationHost> remote;
+    bool bind_result = host_->BindNewReceiver(
+        kExampleOrigin, kMainFrameOrigin,
+        PrivateAggregationBudgetKey::Api::kProtectedAudience,
+        /*context_id=*/std::nullopt, /*timeout=*/std::nullopt,
+        /*aggregation_coordinator_origin=*/std::nullopt,
+        /*filtering_id_max_bytes=*/test_case.filtering_id_max_bytes,
+        remote.BindNewPipeAndPassReceiver());
+
+    EXPECT_EQ(bind_result, test_case.expected_bind_result);
+  }
+}
+
+TEST_F(PrivateAggregationHostTest, FilteringIdValidatedToFitInMaxBytes) {
+  base::test::ScopedFeatureList scoped_feature_list;
+  scoped_feature_list.InitWithFeatures(
+      /*enabled_features=*/{blink::features::kPrivateAggregationApiFilteringIds,
+                            kPrivacySandboxAggregationServiceFilteringIds},
+      /*disabled_features=*/{});
+
+  const url::Origin kExampleOrigin =
+      url::Origin::Create(GURL("https://example.com"));
+  const url::Origin kMainFrameOrigin =
+      url::Origin::Create(GURL("https://main_frame.com"));
+
+  const struct {
+    const char* description;
+    const size_t filtering_id_max_bytes;
+    const std::optional<uint64_t> filtering_id;
+    bool expected_to_be_valid;
+  } kTestCases[] = {
+      {
+          "filtering_id null",
+          1,
+          std::nullopt,
+          true,
+      },
+      {
+          "filtering_id 0",
+          1,
+          0,
+          true,
+      },
+      {
+          "filtering_id max for one byte",
+          1,
+          255,
+          true,
+      },
+      {
+          "filtering_id too big",
+          1,
+          256,
+          false,
+      },
+      {
+          "filtering_id max value for max maxBytes",
+          8,
+          std::numeric_limits<uint64_t>::max(),
+          true,
+      },
+  };
+
+  for (const auto& test_case : kTestCases) {
+    SCOPED_TRACE(test_case.description);
+
+    base::HistogramTester histogram;
+
+    mojo::Remote<blink::mojom::PrivateAggregationHost> remote;
+    bool bind_result = host_->BindNewReceiver(
+        kExampleOrigin, kMainFrameOrigin,
+        PrivateAggregationBudgetKey::Api::kProtectedAudience,
+        /*context_id=*/std::nullopt, /*timeout=*/std::nullopt,
+        /*aggregation_coordinator_origin=*/std::nullopt,
+        /*filtering_id_max_bytes=*/test_case.filtering_id_max_bytes,
+        remote.BindNewPipeAndPassReceiver());
+
+    ASSERT_TRUE(bind_result);
+
+    std::optional<AggregatableReportRequest> validated_request;
+    if (test_case.expected_to_be_valid) {
+      EXPECT_CALL(mock_callback_, Run)
+          .WillOnce(GenerateAndSaveReportRequest(&validated_request));
+    } else {
+      EXPECT_CALL(mock_callback_, Run).Times(0);
+    }
+
+    std::vector<blink::mojom::AggregatableReportHistogramContributionPtr>
+        contributions;
+    contributions.push_back(
+        blink::mojom::AggregatableReportHistogramContribution::New(
+            /*bucket=*/123, /*value=*/456, test_case.filtering_id));
+    remote->ContributeToHistogram(std::move(contributions));
+
+    // The pipe should've been closed in case of a validation error.
+    remote.FlushForTesting();
+    EXPECT_EQ(remote.is_connected(), test_case.expected_to_be_valid);
+
+    remote.reset();
+    host_->FlushReceiverSetForTesting();
+
+    histogram.ExpectUniqueSample(
+        kPipeResultHistogram,
+        test_case.expected_to_be_valid
+            ? PrivateAggregationHost::PipeResult::kReportSuccess
+            : PrivateAggregationHost::PipeResult::kFilteringIdInvalid,
+        1);
+
+    EXPECT_EQ(validated_request.has_value(), test_case.expected_to_be_valid);
+    if (!validated_request.has_value()) {
+      continue;
+    }
+    ASSERT_EQ(validated_request->payload_contents().contributions.size(), 1u);
+    EXPECT_EQ(validated_request->payload_contents().filtering_id_max_bytes,
+              test_case.filtering_id_max_bytes);
+    EXPECT_EQ(
+        validated_request->payload_contents().contributions[0].filtering_id,
+        test_case.filtering_id);
+  }
+}
+
+TEST_F(PrivateAggregationHostTest,
+       FilteringIdMaxBytesNotValidatedIfFeatureDisabled) {
+  base::test::ScopedFeatureList scoped_feature_list;
+  scoped_feature_list.InitAndDisableFeature(
+      blink::features::kPrivateAggregationApiFilteringIds);
+
+  const url::Origin kExampleOrigin =
+      url::Origin::Create(GURL("https://example.com"));
+  const url::Origin kMainFrameOrigin =
+      url::Origin::Create(GURL("https://main_frame.com"));
+
+  const struct {
+    const char* description;
+    const size_t filtering_id_max_bytes;
+  } kTestCases[] = {
+      {
+          "filtering_id_max_bytes zero",
+          0,
+      },
+      {
+          "filtering_id_max_bytes minimum valid value",
+          1,
+      },
+      {
+          "filtering_id_max_bytes maximum valid value",
+          AggregationServicePayloadContents::kMaximumFilteringIdMaxBytes,
+      },
+      {
+          "filtering_id_max_bytes too large",
+          AggregationServicePayloadContents::kMaximumFilteringIdMaxBytes + 1,
+      },
+  };
+
+  for (const auto& test_case : kTestCases) {
+    SCOPED_TRACE(test_case.description);
+
+    mojo::Remote<blink::mojom::PrivateAggregationHost> remote;
+    bool bind_result = host_->BindNewReceiver(
+        kExampleOrigin, kMainFrameOrigin,
+        PrivateAggregationBudgetKey::Api::kProtectedAudience,
+        /*context_id=*/std::nullopt, /*timeout=*/std::nullopt,
+        /*aggregation_coordinator_origin=*/std::nullopt,
+        /*filtering_id_max_bytes=*/test_case.filtering_id_max_bytes,
+        remote.BindNewPipeAndPassReceiver());
+
+    EXPECT_TRUE(bind_result);
+  }
+}
+
+TEST_F(PrivateAggregationHostTest,
+       FilteringIdNotValidatedToFitInMaxBytesIfFeatureDisabled) {
+  base::test::ScopedFeatureList scoped_feature_list;
+  scoped_feature_list.InitAndDisableFeature(
+      blink::features::kPrivateAggregationApiFilteringIds);
+
+  const url::Origin kExampleOrigin =
+      url::Origin::Create(GURL("https://example.com"));
+  const url::Origin kMainFrameOrigin =
+      url::Origin::Create(GURL("https://main_frame.com"));
+
+  const struct {
+    const char* description;
+    const size_t filtering_id_max_bytes;
+    const std::optional<uint64_t> filtering_id;
+  } kTestCases[] = {
+      {
+          "filtering_id null",
+          1,
+          std::nullopt,
+      },
+      {
+          "filtering_id 0",
+          1,
+          0,
+      },
+      {
+          "filtering_id max for one byte",
+          1,
+          255,
+      },
+      {
+          "filtering_id too big",
+          1,
+          256,
+      },
+      {
+          "filtering_id max value for max maxBytes",
+          8,
+          std::numeric_limits<uint64_t>::max(),
+      },
+  };
+
+  for (const auto& test_case : kTestCases) {
+    SCOPED_TRACE(test_case.description);
+    for (bool should_enable_debug_mode : {false, true}) {
+      base::HistogramTester histogram;
+
+      mojo::Remote<blink::mojom::PrivateAggregationHost> remote;
+      bool bind_result = host_->BindNewReceiver(
+          kExampleOrigin, kMainFrameOrigin,
+          PrivateAggregationBudgetKey::Api::kProtectedAudience,
+          /*context_id=*/std::nullopt, /*timeout=*/std::nullopt,
+          /*aggregation_coordinator_origin=*/std::nullopt,
+          /*filtering_id_max_bytes=*/test_case.filtering_id_max_bytes,
+          remote.BindNewPipeAndPassReceiver());
+
+      ASSERT_TRUE(bind_result);
+
+      // We shouldn't have any validation errors if the feature is disabled.
+      std::optional<AggregatableReportRequest> validated_request;
+      EXPECT_CALL(mock_callback_, Run)
+          .WillOnce(GenerateAndSaveReportRequest(&validated_request));
+      if (should_enable_debug_mode) {
+        remote->EnableDebugMode(/*debug_key=*/nullptr);
+      }
+
+      std::vector<blink::mojom::AggregatableReportHistogramContributionPtr>
+          contributions;
+      contributions.push_back(
+          blink::mojom::AggregatableReportHistogramContribution::New(
+              /*bucket=*/123, /*value=*/456, test_case.filtering_id));
+      remote->ContributeToHistogram(std::move(contributions));
+
+      // The pipe should've been closed in case of a validation error.
+      remote.FlushForTesting();
+      EXPECT_TRUE(remote.is_connected());
+
+      remote.reset();
+      host_->FlushReceiverSetForTesting();
+
+      histogram.ExpectUniqueSample(
+          kPipeResultHistogram,
+          PrivateAggregationHost::PipeResult::kReportSuccess, 1);
+
+      ASSERT_TRUE(validated_request.has_value());
+      ASSERT_EQ(validated_request->payload_contents().contributions.size(), 1u);
+
+      // The filtering IDs should always be default if the feature is disabled.
+      EXPECT_FALSE(validated_request->payload_contents()
+                       .filtering_id_max_bytes.has_value());
+      EXPECT_FALSE(validated_request->payload_contents()
+                       .contributions[0]
+                       .filtering_id.has_value());
+    }
+  }
+}
+
 TEST_F(PrivateAggregationHostTest,
        DebugModeFeatureParamsAndSettingsCheckAppliedCorrectly) {
   struct {
@@ -1073,6 +1447,7 @@
         PrivateAggregationBudgetKey::Api::kProtectedAudience,
         /*context_id=*/std::nullopt, /*timeout=*/std::nullopt,
         /*aggregation_coordinator_origin=*/std::nullopt,
+        PrivateAggregationHost::kDefaultFilteringIdMaxBytes,
         remote.BindNewPipeAndPassReceiver()));
 
     std::optional<AggregatableReportRequest> validated_request;
@@ -1137,6 +1512,7 @@
       PrivateAggregationBudgetKey::Api::kProtectedAudience,
       /*context_id=*/std::nullopt, /*timeout=*/std::nullopt,
       /*aggregation_coordinator_origin=*/std::nullopt,
+      PrivateAggregationHost::kDefaultFilteringIdMaxBytes,
       remote.BindNewPipeAndPassReceiver()));
 
   EXPECT_TRUE(remote.is_connected());
@@ -1164,6 +1540,7 @@
       PrivateAggregationBudgetKey::Api::kProtectedAudience,
       /*context_id=*/std::nullopt, /*timeout=*/std::nullopt,
       /*aggregation_coordinator_origin=*/std::nullopt,
+      PrivateAggregationHost::kDefaultFilteringIdMaxBytes,
       remote.BindNewPipeAndPassReceiver()));
 
   task_environment_.FastForwardBy(base::Minutes(10));
@@ -1222,6 +1599,7 @@
       PrivateAggregationBudgetKey::Api::kSharedStorage, "example_context_id",
       /*timeout=*/base::Minutes(1),
       /*aggregation_coordinator_origin=*/std::nullopt,
+      PrivateAggregationHost::kDefaultFilteringIdMaxBytes,
       remote.BindNewPipeAndPassReceiver()));
 
   remote->EnableDebugMode(blink::mojom::DebugKey::New(/*value=*/1234u));
@@ -1297,6 +1675,7 @@
       PrivateAggregationBudgetKey::Api::kSharedStorage, "example_context_id",
       /*timeout=*/base::Minutes(1),
       /*aggregation_coordinator_origin=*/std::nullopt,
+      PrivateAggregationHost::kDefaultFilteringIdMaxBytes,
       remote.BindNewPipeAndPassReceiver()));
 
   remote->EnableDebugMode(blink::mojom::DebugKey::New(/*value=*/1234u));
@@ -1338,6 +1717,7 @@
       PrivateAggregationBudgetKey::Api::kSharedStorage, "example_context_id",
       /*timeout=*/base::Minutes(1),
       /*aggregation_coordinator_origin=*/std::nullopt,
+      PrivateAggregationHost::kDefaultFilteringIdMaxBytes,
       remote1.BindNewPipeAndPassReceiver()));
 
   EXPECT_TRUE(host_->BindNewReceiver(
@@ -1345,6 +1725,7 @@
       PrivateAggregationBudgetKey::Api::kSharedStorage, "example_context_id",
       /*timeout=*/base::Seconds(61),
       /*aggregation_coordinator_origin=*/std::nullopt,
+      PrivateAggregationHost::kDefaultFilteringIdMaxBytes,
       remote2.BindNewPipeAndPassReceiver()));
 
   remote1->EnableDebugMode(blink::mojom::DebugKey::New(/*value=*/1234u));
@@ -1419,6 +1800,7 @@
       PrivateAggregationBudgetKey::Api::kSharedStorage, "example_context_id",
       /*timeout=*/base::Minutes(1),
       /*aggregation_coordinator_origin=*/std::nullopt,
+      PrivateAggregationHost::kDefaultFilteringIdMaxBytes,
       remote1.BindNewPipeAndPassReceiver()));
 
   EXPECT_TRUE(host_->BindNewReceiver(
@@ -1426,6 +1808,7 @@
       PrivateAggregationBudgetKey::Api::kSharedStorage, "example_context_id",
       /*timeout=*/base::Seconds(61),
       /*aggregation_coordinator_origin=*/std::nullopt,
+      PrivateAggregationHost::kDefaultFilteringIdMaxBytes,
       remote2.BindNewPipeAndPassReceiver()));
 
   remote1->EnableDebugMode(blink::mojom::DebugKey::New(/*value=*/1234u));
@@ -1484,6 +1867,7 @@
       PrivateAggregationBudgetKey::Api::kSharedStorage, "example_context_id",
       /*timeout=*/base::Minutes(1),
       /*aggregation_coordinator_origin=*/std::nullopt,
+      PrivateAggregationHost::kDefaultFilteringIdMaxBytes,
       remote.BindNewPipeAndPassReceiver()));
 
   remote->EnableDebugMode(blink::mojom::DebugKey::New(/*value=*/1234u));
@@ -1516,6 +1900,7 @@
       PrivateAggregationBudgetKey::Api::kSharedStorage, "example_context_id",
       /*timeout=*/base::Minutes(1),
       /*aggregation_coordinator_origin=*/std::nullopt,
+      PrivateAggregationHost::kDefaultFilteringIdMaxBytes,
       remote.BindNewPipeAndPassReceiver()));
 
   remote->EnableDebugMode(blink::mojom::DebugKey::New(/*value=*/1234u));
@@ -1547,6 +1932,7 @@
       PrivateAggregationBudgetKey::Api::kSharedStorage, "example_context_id",
       /*timeout=*/base::Minutes(1),
       /*aggregation_coordinator_origin=*/std::nullopt,
+      PrivateAggregationHost::kDefaultFilteringIdMaxBytes,
       remote1.BindNewPipeAndPassReceiver()));
 
   EXPECT_TRUE(host_->BindNewReceiver(
@@ -1554,6 +1940,7 @@
       PrivateAggregationBudgetKey::Api::kSharedStorage, "example_context_id",
       /*timeout=*/base::Minutes(1),
       /*aggregation_coordinator_origin=*/std::nullopt,
+      PrivateAggregationHost::kDefaultFilteringIdMaxBytes,
       remote2.BindNewPipeAndPassReceiver()));
 
   remote1->EnableDebugMode(blink::mojom::DebugKey::New(/*value=*/1234u));
@@ -1593,6 +1980,7 @@
       PrivateAggregationBudgetKey::Api::kProtectedAudience,
       /*context_id=*/std::nullopt, /*timeout=*/std::nullopt,
       /*aggregation_coordinator_origin=*/std::nullopt,
+      PrivateAggregationHost::kDefaultFilteringIdMaxBytes,
       remote.BindNewPipeAndPassReceiver()));
 
   std::optional<AggregatableReportRequest> validated_request;
@@ -1633,6 +2021,7 @@
       PrivateAggregationBudgetKey::Api::kProtectedAudience,
       /*context_id=*/"example_context_id", /*timeout=*/base::Seconds(30),
       /*aggregation_coordinator_origin=*/std::nullopt,
+      PrivateAggregationHost::kDefaultFilteringIdMaxBytes,
       remote.BindNewPipeAndPassReceiver()));
 
   std::optional<AggregatableReportRequest> validated_request;
diff --git a/content/browser/private_aggregation/private_aggregation_manager.h b/content/browser/private_aggregation/private_aggregation_manager.h
index 4749e4cc..c29c78e5 100644
--- a/content/browser/private_aggregation/private_aggregation_manager.h
+++ b/content/browser/private_aggregation/private_aggregation_manager.h
@@ -5,6 +5,8 @@
 #ifndef CONTENT_BROWSER_PRIVATE_AGGREGATION_PRIVATE_AGGREGATION_MANAGER_H_
 #define CONTENT_BROWSER_PRIVATE_AGGREGATION_PRIVATE_AGGREGATION_MANAGER_H_
 
+#include <stddef.h>
+
 #include <optional>
 #include <string>
 
@@ -41,7 +43,8 @@
   // if the pipe closed after the timeout, regardless of when the disconnection
   // actually happens. `timeout` must be positive if set. If
   // `aggregation_coordinator_origin` is set, the origin must be on the
-  // allowlist.
+  // allowlist. `filtering_id_max_bytes` must be positive and no greater than
+  // `AggregationServicePayloadContents::kMaximumFilteringIdMaxBytes`.
   [[nodiscard]] virtual bool BindNewReceiver(
       url::Origin worklet_origin,
       url::Origin top_frame_origin,
@@ -49,6 +52,7 @@
       std::optional<std::string> context_id,
       std::optional<base::TimeDelta> timeout,
       std::optional<url::Origin> aggregation_coordinator_origin,
+      size_t filtering_id_max_bytes,
       mojo::PendingReceiver<blink::mojom::PrivateAggregationHost>
           pending_receiver) = 0;
 
diff --git a/content/browser/private_aggregation/private_aggregation_manager_impl.cc b/content/browser/private_aggregation/private_aggregation_manager_impl.cc
index af29252..812b4cc 100644
--- a/content/browser/private_aggregation/private_aggregation_manager_impl.cc
+++ b/content/browser/private_aggregation/private_aggregation_manager_impl.cc
@@ -4,6 +4,8 @@
 
 #include "content/browser/private_aggregation/private_aggregation_manager_impl.h"
 
+#include <stddef.h>
+
 #include <memory>
 #include <numeric>
 #include <optional>
@@ -110,12 +112,14 @@
     std::optional<std::string> context_id,
     std::optional<base::TimeDelta> timeout,
     std::optional<url::Origin> aggregation_coordinator_origin,
+    size_t filtering_id_max_bytes,
     mojo::PendingReceiver<blink::mojom::PrivateAggregationHost>
         pending_receiver) {
   return host_->BindNewReceiver(
       std::move(worklet_origin), std::move(top_frame_origin), api_for_budgeting,
       std::move(context_id), std::move(timeout),
-      std::move(aggregation_coordinator_origin), std::move(pending_receiver));
+      std::move(aggregation_coordinator_origin), filtering_id_max_bytes,
+      std::move(pending_receiver));
 }
 
 void PrivateAggregationManagerImpl::ClearBudgetData(
diff --git a/content/browser/private_aggregation/private_aggregation_manager_impl.h b/content/browser/private_aggregation/private_aggregation_manager_impl.h
index 9ceb089..05caaa21 100644
--- a/content/browser/private_aggregation/private_aggregation_manager_impl.h
+++ b/content/browser/private_aggregation/private_aggregation_manager_impl.h
@@ -5,6 +5,8 @@
 #ifndef CONTENT_BROWSER_PRIVATE_AGGREGATION_PRIVATE_AGGREGATION_MANAGER_IMPL_H_
 #define CONTENT_BROWSER_PRIVATE_AGGREGATION_PRIVATE_AGGREGATION_MANAGER_IMPL_H_
 
+#include <stddef.h>
+
 #include <memory>
 #include <optional>
 #include <string>
@@ -71,6 +73,7 @@
       std::optional<std::string> context_id,
       std::optional<base::TimeDelta> timeout,
       std::optional<url::Origin> aggregation_coordinator_origin,
+      size_t filtering_id_max_bytes,
       mojo::PendingReceiver<blink::mojom::PrivateAggregationHost>
           pending_receiver) override;
   void ClearBudgetData(base::Time delete_begin,
diff --git a/content/browser/private_aggregation/private_aggregation_manager_impl_unittest.cc b/content/browser/private_aggregation/private_aggregation_manager_impl_unittest.cc
index ecb539c..e5028cf 100644
--- a/content/browser/private_aggregation/private_aggregation_manager_impl_unittest.cc
+++ b/content/browser/private_aggregation/private_aggregation_manager_impl_unittest.cc
@@ -892,40 +892,43 @@
                           example_origin, example_main_frame_origin,
                           PrivateAggregationBudgetKey::Api::kProtectedAudience,
                           testing::Eq(std::nullopt), testing::Eq(std::nullopt),
-                          testing::Eq(std::nullopt), _))
+                          testing::Eq(std::nullopt), 1, _))
       .WillOnce(Return(true));
   EXPECT_TRUE(manager_.BindNewReceiver(
       example_origin, example_main_frame_origin,
       PrivateAggregationBudgetKey::Api::kProtectedAudience,
       /*context_id=*/std::nullopt, /*timeout=*/std::nullopt,
       /*aggregation_coordinator_origin=*/std::nullopt,
+      /*filtering_id_max_bytes=*/1,
       mojo::PendingReceiver<blink::mojom::PrivateAggregationHost>()));
 
   EXPECT_CALL(*host_, BindNewReceiver(
                           example_origin, example_main_frame_origin,
                           PrivateAggregationBudgetKey::Api::kSharedStorage,
                           testing::Eq(std::nullopt), testing::Eq(std::nullopt),
-                          testing::Eq(std::nullopt), _))
+                          testing::Eq(std::nullopt), 1, _))
       .WillOnce(Return(false));
   EXPECT_FALSE(manager_.BindNewReceiver(
       example_origin, example_main_frame_origin,
       PrivateAggregationBudgetKey::Api::kSharedStorage,
       /*context_id=*/std::nullopt, /*timeout=*/std::nullopt,
       /*aggregation_coordinator_origin=*/std::nullopt,
+      /*filtering_id_max_bytes=*/1,
       mojo::PendingReceiver<blink::mojom::PrivateAggregationHost>()));
 
-  EXPECT_CALL(
-      *host_,
-      BindNewReceiver(example_origin, example_main_frame_origin,
-                      PrivateAggregationBudgetKey::Api::kProtectedAudience,
-                      testing::Eq("example_context_id"),
-                      testing::Eq(std::nullopt), testing::Eq(std::nullopt), _))
+  EXPECT_CALL(*host_,
+              BindNewReceiver(
+                  example_origin, example_main_frame_origin,
+                  PrivateAggregationBudgetKey::Api::kProtectedAudience,
+                  testing::Eq("example_context_id"), testing::Eq(std::nullopt),
+                  testing::Eq(std::nullopt), 1, _))
       .WillOnce(Return(true));
   EXPECT_TRUE(manager_.BindNewReceiver(
       example_origin, example_main_frame_origin,
       PrivateAggregationBudgetKey::Api::kProtectedAudience,
       "example_context_id", /*timeout=*/std::nullopt,
       /*aggregation_coordinator_origin=*/std::nullopt,
+      /*filtering_id_max_bytes=*/1,
       mojo::PendingReceiver<blink::mojom::PrivateAggregationHost>()));
 
   EXPECT_CALL(*host_,
@@ -933,26 +936,41 @@
                               PrivateAggregationBudgetKey::Api::kSharedStorage,
                               testing::Eq("example_context_id"),
                               testing::Eq(base::Seconds(5)),
-                              testing::Eq(std::nullopt), _))
+                              testing::Eq(std::nullopt), 1, _))
       .WillOnce(Return(true));
   EXPECT_TRUE(manager_.BindNewReceiver(
       example_origin, example_main_frame_origin,
       PrivateAggregationBudgetKey::Api::kSharedStorage, "example_context_id",
       /*timeout=*/base::Seconds(5),
       /*aggregation_coordinator_origin=*/std::nullopt,
+      /*filtering_id_max_bytes=*/1,
       mojo::PendingReceiver<blink::mojom::PrivateAggregationHost>()));
 
   EXPECT_CALL(*host_, BindNewReceiver(
                           example_origin, example_main_frame_origin,
                           PrivateAggregationBudgetKey::Api::kProtectedAudience,
                           testing::Eq(std::nullopt), testing::Eq(std::nullopt),
-                          testing::Eq(example_coordinator_origin), _))
+                          testing::Eq(example_coordinator_origin), 1, _))
       .WillOnce(Return(true));
   EXPECT_TRUE(manager_.BindNewReceiver(
       example_origin, example_main_frame_origin,
       PrivateAggregationBudgetKey::Api::kProtectedAudience,
       /*context_id=*/std::nullopt, /*timeout=*/std::nullopt,
-      example_coordinator_origin,
+      example_coordinator_origin, /*filtering_id_max_bytes=*/1,
+      mojo::PendingReceiver<blink::mojom::PrivateAggregationHost>()));
+
+  EXPECT_CALL(*host_, BindNewReceiver(
+                          example_origin, example_main_frame_origin,
+                          PrivateAggregationBudgetKey::Api::kProtectedAudience,
+                          testing::Eq(std::nullopt), testing::Eq(std::nullopt),
+                          testing::Eq(std::nullopt), 8, _))
+      .WillOnce(Return(true));
+  EXPECT_TRUE(manager_.BindNewReceiver(
+      example_origin, example_main_frame_origin,
+      PrivateAggregationBudgetKey::Api::kProtectedAudience,
+      /*context_id=*/std::nullopt, /*timeout=*/std::nullopt,
+      /*aggregation_coordinator_origin=*/std::nullopt,
+      /*filtering_id_max_bytes=*/8,
       mojo::PendingReceiver<blink::mojom::PrivateAggregationHost>()));
 }
 
diff --git a/content/browser/private_aggregation/private_aggregation_report_golden_unittest.cc b/content/browser/private_aggregation/private_aggregation_report_golden_unittest.cc
index ab1657b..fa1d22f 100644
--- a/content/browser/private_aggregation/private_aggregation_report_golden_unittest.cc
+++ b/content/browser/private_aggregation/private_aggregation_report_golden_unittest.cc
@@ -4,6 +4,7 @@
 
 #include <stdint.h>
 
+#include <limits>
 #include <optional>
 #include <string>
 #include <string_view>
@@ -109,7 +110,8 @@
           contributions,
       PrivateAggregationBudgetKey::Api api_identifier,
       std::string_view report_file,
-      std::string_view cleartext_payloads_file) {
+      std::string_view cleartext_payloads_file,
+      size_t filtering_id_max_bytes) {
     const url::Origin kExampleOrigin =
         url::Origin::Create(GURL("https://report.test"));
 
@@ -138,6 +140,7 @@
             /*context_id=*/std::nullopt,
             // TODO(alexmt): Generate golden reports for multiple coordinators.
             /*aggregation_coordinator_origin=*/std::nullopt,
+            /*specified_filtering_id_max_bytes=*/filtering_id_max_bytes,
             std::move(contributions));
 
     base::RunLoop run_loop;
@@ -342,6 +345,7 @@
     PrivateAggregationBudgetKey::Api api_identifier;
     std::string_view report_file;
     std::string_view cleartext_payloads_file;
+    size_t filtering_id_max_bytes = 1;
   } kTestCases[] = {
       {.debug_details = blink::mojom::DebugModeDetails::New(
            /*is_enabled=*/true,
@@ -353,7 +357,7 @@
        .cleartext_payloads_file = "report_1_cleartext_payloads.json"},
       {.debug_details = blink::mojom::DebugModeDetails::New(),
        .contributions = {blink::mojom::AggregatableReportHistogramContribution(
-           /*bucket==*/1, /*value=*/2, /*filtering_id=*/std::nullopt)},
+           /*bucket=*/1, /*value=*/2, /*filtering_id=*/std::nullopt)},
        .api_identifier = PrivateAggregationBudgetKey::Api::kProtectedAudience,
        .report_file = "report_2.json",
        .cleartext_payloads_file = "report_2_cleartext_payloads.json"},
@@ -361,20 +365,20 @@
            /*is_enabled=*/true,
            /*debug_key=*/blink::mojom::DebugKey::New(/*value=*/123u)),
        .contributions = {blink::mojom::AggregatableReportHistogramContribution(
-                             /*bucket==*/1, /*value=*/2,
+                             /*bucket=*/1, /*value=*/2,
                              /*filtering_id=*/std::nullopt),
                          blink::mojom::AggregatableReportHistogramContribution(
-                             /*bucket==*/3, /*value=*/4,
+                             /*bucket=*/3, /*value=*/4,
                              /*filtering_id=*/std::nullopt)},
        .api_identifier = PrivateAggregationBudgetKey::Api::kSharedStorage,
        .report_file = "report_3.json",
        .cleartext_payloads_file = "report_3_cleartext_payloads.json"},
       {.debug_details = blink::mojom::DebugModeDetails::New(),
        .contributions = {blink::mojom::AggregatableReportHistogramContribution(
-                             /*bucket==*/1, /*value=*/2,
+                             /*bucket=*/1, /*value=*/2,
                              /*filtering_id=*/std::nullopt),
                          blink::mojom::AggregatableReportHistogramContribution(
-                             /*bucket==*/3, /*value=*/4,
+                             /*bucket=*/3, /*value=*/4,
                              /*filtering_id=*/std::nullopt)},
        .api_identifier = PrivateAggregationBudgetKey::Api::kSharedStorage,
        .report_file = "report_4.json",
@@ -383,29 +387,53 @@
            /*is_enabled=*/true,
            /*debug_key=*/blink::mojom::DebugKey::New(/*value=*/123u)),
        .contributions = {blink::mojom::AggregatableReportHistogramContribution(
-           /*bucket==*/1, /*value=*/2, /*filtering_id=*/std::nullopt)},
+           /*bucket=*/1, /*value=*/2, /*filtering_id=*/std::nullopt)},
        .api_identifier = PrivateAggregationBudgetKey::Api::kProtectedAudience,
        .report_file = "report_5.json",
        .cleartext_payloads_file = "report_5_cleartext_payloads.json"},
       {.debug_details = blink::mojom::DebugModeDetails::New(),
        .contributions = {blink::mojom::AggregatableReportHistogramContribution(
-           /*bucket==*/1, /*value=*/2, /*filtering_id=*/std::nullopt)},
+           /*bucket=*/1, /*value=*/2, /*filtering_id=*/std::nullopt)},
        .api_identifier = PrivateAggregationBudgetKey::Api::kProtectedAudience,
        .report_file = "report_6.json",
        .cleartext_payloads_file = "report_6_cleartext_payloads.json"},
       {.debug_details = blink::mojom::DebugModeDetails::New(),
        .contributions = {blink::mojom::AggregatableReportHistogramContribution(
-           /*bucket==*/0, /*value=*/0, /*filtering_id=*/std::nullopt)},
+           /*bucket=*/0, /*value=*/0, /*filtering_id=*/std::nullopt)},
        .api_identifier = PrivateAggregationBudgetKey::Api::kSharedStorage,
        .report_file = "report_7.json",
        .cleartext_payloads_file = "report_7_cleartext_payloads.json"},
+      {.debug_details = blink::mojom::DebugModeDetails::New(
+           /*is_enabled=*/true,
+           /*debug_key=*/blink::mojom::DebugKey::New(/*value=*/123u)),
+       .contributions = {blink::mojom::AggregatableReportHistogramContribution(
+           /*bucket=*/1, /*value=*/2, /*filtering_id=*/3)},
+       .api_identifier = PrivateAggregationBudgetKey::Api::kProtectedAudience,
+       .report_file = "report_8.json",
+       .cleartext_payloads_file = "report_8_cleartext_payloads.json"},
+      {.debug_details = blink::mojom::DebugModeDetails::New(),
+       .contributions = {blink::mojom::AggregatableReportHistogramContribution(
+           /*bucket=*/1, /*value=*/2, /*filtering_id=*/3)},
+       .api_identifier = PrivateAggregationBudgetKey::Api::kProtectedAudience,
+       .report_file = "report_9.json",
+       .cleartext_payloads_file = "report_9_cleartext_payloads.json"},
+      {.debug_details = blink::mojom::DebugModeDetails::New(
+           /*is_enabled=*/true,
+           /*debug_key=*/blink::mojom::DebugKey::New(/*value=*/123u)),
+       .contributions = {blink::mojom::AggregatableReportHistogramContribution(
+           /*bucket=*/1, /*value=*/2,
+           /*filtering_id=*/std::numeric_limits<uint64_t>::max())},
+       .api_identifier = PrivateAggregationBudgetKey::Api::kProtectedAudience,
+       .report_file = "report_10.json",
+       .cleartext_payloads_file = "report_10_cleartext_payloads.json",
+       .filtering_id_max_bytes = 8},
   };
 
   for (auto& test_case : kTestCases) {
     AssembleAndVerifyReport(
         std::move(test_case.debug_details), std::move(test_case.contributions),
         std::move(test_case.api_identifier), test_case.report_file,
-        test_case.cleartext_payloads_file);
+        test_case.cleartext_payloads_file, test_case.filtering_id_max_bytes);
   }
 }
 
diff --git a/content/browser/private_aggregation/private_aggregation_test_utils.h b/content/browser/private_aggregation/private_aggregation_test_utils.h
index 81c7362..d7febd661 100644
--- a/content/browser/private_aggregation/private_aggregation_test_utils.h
+++ b/content/browser/private_aggregation/private_aggregation_test_utils.h
@@ -5,6 +5,8 @@
 #ifndef CONTENT_BROWSER_PRIVATE_AGGREGATION_PRIVATE_AGGREGATION_TEST_UTILS_H_
 #define CONTENT_BROWSER_PRIVATE_AGGREGATION_PRIVATE_AGGREGATION_TEST_UTILS_H_
 
+#include <stddef.h>
+
 #include <optional>
 #include <set>
 #include <string>
@@ -81,6 +83,7 @@
                std::optional<std::string>,
                std::optional<base::TimeDelta>,
                std::optional<url::Origin>,
+               size_t,
                mojo::PendingReceiver<blink::mojom::PrivateAggregationHost>),
               (override));
 
@@ -109,6 +112,7 @@
                std::optional<std::string>,
                std::optional<base::TimeDelta>,
                std::optional<url::Origin>,
+               size_t,
                mojo::PendingReceiver<blink::mojom::PrivateAggregationHost>),
               (override));
 
diff --git a/content/browser/renderer_host/view_transition_commit_deferring_condition.cc b/content/browser/renderer_host/view_transition_commit_deferring_condition.cc
index 945ecc23..7101c06 100644
--- a/content/browser/renderer_host/view_transition_commit_deferring_condition.cc
+++ b/content/browser/renderer_host/view_transition_commit_deferring_condition.cc
@@ -37,6 +37,12 @@
       return nullptr;
   };
 
+  if (!navigation_request.IsInMainFrame() &&
+      !base::FeatureList::IsEnabled(
+          blink::features::kViewTransitionOnNavigationForIframes)) {
+    return nullptr;
+  }
+
   if (!navigation_request.ShouldDispatchPageSwapEvent()) {
     return nullptr;
   }
diff --git a/content/browser/shared_storage/shared_storage_browsertest.cc b/content/browser/shared_storage/shared_storage_browsertest.cc
index 15bc943..add9169 100644
--- a/content/browser/shared_storage/shared_storage_browsertest.cc
+++ b/content/browser/shared_storage/shared_storage_browsertest.cc
@@ -3,6 +3,7 @@
 // found in the LICENSE file.
 
 #include <cmath>
+#include <limits>
 #include <map>
 #include <memory>
 #include <optional>
@@ -27,6 +28,7 @@
 #include "base/test/test_future.h"
 #include "base/test/with_feature_override.h"
 #include "base/time/time.h"
+#include "content/browser/aggregation_service/aggregatable_report.h"
 #include "content/browser/private_aggregation/private_aggregation_manager_impl.h"
 #include "content/browser/private_aggregation/private_aggregation_test_utils.h"
 #include "content/browser/renderer_host/navigation_request.h"
@@ -983,6 +985,7 @@
       size_t expected_total_host_count = 1u,
       bool keep_alive_after_operation = true,
       std::optional<std::string> context_id = std::nullopt,
+      std::optional<std::string> filtering_id_max_bytes = std::nullopt,
       std::string* out_error = nullptr,
       bool wait_for_operation_finish = true) {
     DCHECK(out_module_script_url);
@@ -1019,14 +1022,25 @@
         execution_target,
         JsReplace("window.keepWorklet = $1;", keep_alive_after_operation)));
 
+    std::string private_aggregation_config_js = "";
+    if (context_id.has_value() || filtering_id_max_bytes.has_value()) {
+      private_aggregation_config_js = base::StrCat(
+          {", privateAggregationConfig: {",
+           context_id.has_value()
+               ? JsReplace("contextId: $1,", context_id.value())
+               : "",
+           filtering_id_max_bytes.has_value()
+               ? base::StrCat({"filteringIdMaxBytes: ",
+                               filtering_id_max_bytes.value(), ","})
+               : "",
+           "}"});
+    }
+
     testing::AssertionResult result = ExecJs(
         execution_target,
         base::StrCat(
             {"sharedStorage.run('test-operation', {keepAlive: keepWorklet",
-             context_id.has_value()
-                 ? JsReplace(", privateAggregationConfig: {contextId: $1}});",
-                             context_id.value())
-                 : "});"}));
+             private_aggregation_config_js, "});"}));
     EXPECT_EQ(!!result, out_error == nullptr);
     if (out_error) {
       *out_error = std::string(result.message());
@@ -7235,6 +7249,7 @@
                          &out_script_url, /*expected_total_host_count=*/1u,
                          /*keep_alive_after_operation=*/true,
                          /*context_id=*/"example_context_id",
+                         /*filtering_id_max_bytes=*/std::nullopt,
                          /*out_error=*/nullptr,
                          /*wait_for_operation_finish=*/false);
 
@@ -7459,7 +7474,7 @@
       /*keep_alive_after_operation=*/true,
       /*context_id=*/
       "this_is_an_example_of_a_context_id_that_is_too_long_to_be_allowed",
-      &out_error);
+      /*filtering_id_max_bytes=*/std::nullopt, &out_error);
 
   EXPECT_THAT(
       out_error,
@@ -7468,6 +7483,1140 @@
   EXPECT_TRUE(console_observer.messages().empty());
 }
 
+class SharedStoragePrivateAggregationFilteringIdEnabledBrowserTest
+    : public SharedStoragePrivateAggregationEnabledBrowserTest {
+ public:
+  SharedStoragePrivateAggregationFilteringIdEnabledBrowserTest() = default;
+
+ private:
+  base::test::ScopedFeatureList scoped_feature_list_{
+      blink::features::kPrivateAggregationApiFilteringIds};
+};
+
+IN_PROC_BROWSER_TEST_F(
+    SharedStoragePrivateAggregationFilteringIdEnabledBrowserTest,
+    BasicFilteringId_Success) {
+  WebContentsConsoleObserver console_observer(shell()->web_contents());
+
+  base::RunLoop run_loop;
+
+  EXPECT_CALL(mock_callback(), Run)
+      .WillOnce(testing::Invoke(
+          [&](PrivateAggregationHost::ReportRequestGenerator generator,
+              std::vector<blink::mojom::AggregatableReportHistogramContribution>
+                  contributions,
+              PrivateAggregationBudgetKey budget_key,
+              PrivateAggregationBudgeter::BudgetDeniedBehavior
+                  budget_denied_behavior) {
+            AggregatableReportRequest request =
+                std::move(generator).Run(contributions);
+            ASSERT_EQ(request.payload_contents().contributions.size(), 1u);
+            EXPECT_EQ(request.payload_contents().contributions[0].filtering_id,
+                      3);
+            EXPECT_EQ(request.payload_contents().filtering_id_max_bytes, 1);
+            EXPECT_EQ(request.shared_info().debug_mode,
+                      AggregatableReportSharedInfo::DebugMode::kDisabled);
+            run_loop.Quit();
+          }));
+
+  EXPECT_CALL(browser_client(),
+              LogWebFeatureForCurrentPage(
+                  shell()->web_contents()->GetPrimaryMainFrame(),
+                  blink::mojom::WebFeature::kPrivateAggregationApiAll));
+  EXPECT_CALL(
+      browser_client(),
+      LogWebFeatureForCurrentPage(
+          shell()->web_contents()->GetPrimaryMainFrame(),
+          blink::mojom::WebFeature::kPrivateAggregationApiSharedStorage));
+  EXPECT_CALL(
+      browser_client(),
+      LogWebFeatureForCurrentPage(
+          shell()->web_contents()->GetPrimaryMainFrame(),
+          blink::mojom::WebFeature::kPrivateAggregationApiFilteringIds));
+  ON_CALL(browser_client(), IsPrivateAggregationAllowed)
+      .WillByDefault(testing::Return(true));
+  ON_CALL(browser_client(), IsSharedStorageAllowed)
+      .WillByDefault(testing::Return(true));
+
+  GURL out_script_url;
+  ExecuteScriptInWorklet(shell(), R"(
+      privateAggregation.contributeToHistogram(
+          {bucket: 1n, value: 2, filteringId: 3n});
+    )",
+                         &out_script_url, /*expected_total_host_count=*/1u,
+                         /*keep_alive_after_operation=*/true,
+                         /*context_id=*/std::nullopt);
+
+  EXPECT_TRUE(console_observer.messages().empty());
+
+  run_loop.Run();
+}
+
+IN_PROC_BROWSER_TEST_F(
+    SharedStoragePrivateAggregationFilteringIdEnabledBrowserTest,
+    FilteringIdWithDebugMode_Success) {
+  WebContentsConsoleObserver console_observer(shell()->web_contents());
+
+  base::RunLoop run_loop;
+
+  EXPECT_CALL(mock_callback(), Run)
+      .WillOnce(testing::Invoke(
+          [&](PrivateAggregationHost::ReportRequestGenerator generator,
+              std::vector<blink::mojom::AggregatableReportHistogramContribution>
+                  contributions,
+              PrivateAggregationBudgetKey budget_key,
+              PrivateAggregationBudgeter::BudgetDeniedBehavior
+                  budget_denied_behavior) {
+            AggregatableReportRequest request =
+                std::move(generator).Run(contributions);
+            ASSERT_EQ(request.payload_contents().contributions.size(), 1u);
+            EXPECT_EQ(request.payload_contents().contributions[0].filtering_id,
+                      3);
+            EXPECT_EQ(request.payload_contents().filtering_id_max_bytes,
+                      PrivateAggregationHost::kDefaultFilteringIdMaxBytes);
+            EXPECT_EQ(request.shared_info().debug_mode,
+                      AggregatableReportSharedInfo::DebugMode::kEnabled);
+            run_loop.Quit();
+          }));
+
+  EXPECT_CALL(browser_client(),
+              LogWebFeatureForCurrentPage(
+                  shell()->web_contents()->GetPrimaryMainFrame(),
+                  blink::mojom::WebFeature::kPrivateAggregationApiAll));
+  EXPECT_CALL(
+      browser_client(),
+      LogWebFeatureForCurrentPage(
+          shell()->web_contents()->GetPrimaryMainFrame(),
+          blink::mojom::WebFeature::kPrivateAggregationApiEnableDebugMode));
+  EXPECT_CALL(
+      browser_client(),
+      LogWebFeatureForCurrentPage(
+          shell()->web_contents()->GetPrimaryMainFrame(),
+          blink::mojom::WebFeature::kPrivateAggregationApiSharedStorage));
+  EXPECT_CALL(
+      browser_client(),
+      LogWebFeatureForCurrentPage(
+          shell()->web_contents()->GetPrimaryMainFrame(),
+          blink::mojom::WebFeature::kPrivateAggregationApiFilteringIds));
+  ON_CALL(browser_client(), IsPrivateAggregationAllowed)
+      .WillByDefault(testing::Return(true));
+  ON_CALL(browser_client(), IsPrivateAggregationDebugModeAllowed)
+      .WillByDefault(testing::Return(true));
+  ON_CALL(browser_client(), IsSharedStorageAllowed)
+      .WillByDefault(testing::Return(true));
+
+  GURL out_script_url;
+  ExecuteScriptInWorklet(shell(), R"(
+      privateAggregation.enableDebugMode();
+      privateAggregation.contributeToHistogram(
+          {bucket: 1n, value: 2, filteringId: 3n});
+    )",
+                         &out_script_url, /*expected_total_host_count=*/1u,
+                         /*keep_alive_after_operation=*/true,
+                         /*context_id=*/std::nullopt);
+
+  EXPECT_TRUE(console_observer.messages().empty());
+
+  run_loop.Run();
+}
+
+IN_PROC_BROWSER_TEST_F(
+    SharedStoragePrivateAggregationFilteringIdEnabledBrowserTest,
+    NoFilteringIdSpecified_FilteringIdNull) {
+  WebContentsConsoleObserver console_observer(shell()->web_contents());
+
+  base::RunLoop run_loop;
+
+  EXPECT_CALL(mock_callback(), Run)
+      .WillOnce(testing::Invoke(
+          [&](PrivateAggregationHost::ReportRequestGenerator generator,
+              std::vector<blink::mojom::AggregatableReportHistogramContribution>
+                  contributions,
+              PrivateAggregationBudgetKey budget_key,
+              PrivateAggregationBudgeter::BudgetDeniedBehavior
+                  budget_denied_behavior) {
+            AggregatableReportRequest request =
+                std::move(generator).Run(contributions);
+            ASSERT_EQ(request.payload_contents().contributions.size(), 1u);
+            EXPECT_EQ(request.payload_contents().contributions[0].filtering_id,
+                      std::nullopt);
+            EXPECT_EQ(request.payload_contents().filtering_id_max_bytes, 1);
+            EXPECT_EQ(request.shared_info().debug_mode,
+                      AggregatableReportSharedInfo::DebugMode::kEnabled);
+            run_loop.Quit();
+          }));
+
+  EXPECT_CALL(browser_client(),
+              LogWebFeatureForCurrentPage(
+                  shell()->web_contents()->GetPrimaryMainFrame(),
+                  blink::mojom::WebFeature::kPrivateAggregationApiAll));
+  EXPECT_CALL(
+      browser_client(),
+      LogWebFeatureForCurrentPage(
+          shell()->web_contents()->GetPrimaryMainFrame(),
+          blink::mojom::WebFeature::kPrivateAggregationApiEnableDebugMode));
+  EXPECT_CALL(
+      browser_client(),
+      LogWebFeatureForCurrentPage(
+          shell()->web_contents()->GetPrimaryMainFrame(),
+          blink::mojom::WebFeature::kPrivateAggregationApiSharedStorage));
+  EXPECT_CALL(browser_client(),
+              LogWebFeatureForCurrentPage(
+                  shell()->web_contents()->GetPrimaryMainFrame(),
+                  blink::mojom::WebFeature::kPrivateAggregationApiFilteringIds))
+      .Times(0);
+  ON_CALL(browser_client(), IsPrivateAggregationAllowed)
+      .WillByDefault(testing::Return(true));
+  ON_CALL(browser_client(), IsPrivateAggregationDebugModeAllowed)
+      .WillByDefault(testing::Return(true));
+  ON_CALL(browser_client(), IsSharedStorageAllowed)
+      .WillByDefault(testing::Return(true));
+
+  GURL out_script_url;
+  ExecuteScriptInWorklet(shell(), R"(
+      privateAggregation.enableDebugMode();
+      privateAggregation.contributeToHistogram(
+          {bucket: 1n, value: 2});
+    )",
+                         &out_script_url, /*expected_total_host_count=*/1u,
+                         /*keep_alive_after_operation=*/true,
+                         /*context_id=*/std::nullopt);
+
+  EXPECT_TRUE(console_observer.messages().empty());
+
+  run_loop.Run();
+}
+
+IN_PROC_BROWSER_TEST_F(
+    SharedStoragePrivateAggregationFilteringIdEnabledBrowserTest,
+    ExplicitDefaultFilteringId_FilteringIdNotNull) {
+  WebContentsConsoleObserver console_observer(shell()->web_contents());
+
+  base::RunLoop run_loop;
+
+  EXPECT_CALL(mock_callback(), Run)
+      .WillOnce(testing::Invoke(
+          [&](PrivateAggregationHost::ReportRequestGenerator generator,
+              std::vector<blink::mojom::AggregatableReportHistogramContribution>
+                  contributions,
+              PrivateAggregationBudgetKey budget_key,
+              PrivateAggregationBudgeter::BudgetDeniedBehavior
+                  budget_denied_behavior) {
+            AggregatableReportRequest request =
+                std::move(generator).Run(contributions);
+            ASSERT_EQ(request.payload_contents().contributions.size(), 1u);
+            EXPECT_EQ(request.payload_contents().contributions[0].filtering_id,
+                      0);
+            EXPECT_EQ(request.payload_contents().filtering_id_max_bytes,
+                      PrivateAggregationHost::kDefaultFilteringIdMaxBytes);
+            EXPECT_EQ(request.shared_info().debug_mode,
+                      AggregatableReportSharedInfo::DebugMode::kEnabled);
+            run_loop.Quit();
+          }));
+
+  EXPECT_CALL(browser_client(),
+              LogWebFeatureForCurrentPage(
+                  shell()->web_contents()->GetPrimaryMainFrame(),
+                  blink::mojom::WebFeature::kPrivateAggregationApiAll));
+  EXPECT_CALL(
+      browser_client(),
+      LogWebFeatureForCurrentPage(
+          shell()->web_contents()->GetPrimaryMainFrame(),
+          blink::mojom::WebFeature::kPrivateAggregationApiEnableDebugMode));
+  EXPECT_CALL(
+      browser_client(),
+      LogWebFeatureForCurrentPage(
+          shell()->web_contents()->GetPrimaryMainFrame(),
+          blink::mojom::WebFeature::kPrivateAggregationApiSharedStorage));
+  EXPECT_CALL(
+      browser_client(),
+      LogWebFeatureForCurrentPage(
+          shell()->web_contents()->GetPrimaryMainFrame(),
+          blink::mojom::WebFeature::kPrivateAggregationApiFilteringIds));
+  ON_CALL(browser_client(), IsPrivateAggregationAllowed)
+      .WillByDefault(testing::Return(true));
+  ON_CALL(browser_client(), IsPrivateAggregationDebugModeAllowed)
+      .WillByDefault(testing::Return(true));
+  ON_CALL(browser_client(), IsSharedStorageAllowed)
+      .WillByDefault(testing::Return(true));
+
+  GURL out_script_url;
+  ExecuteScriptInWorklet(shell(), R"(
+      privateAggregation.enableDebugMode();
+      privateAggregation.contributeToHistogram(
+          {bucket: 1n, value: 2, filteringId: 0n});
+    )",
+                         &out_script_url, /*expected_total_host_count=*/1u,
+                         /*keep_alive_after_operation=*/true,
+                         /*context_id=*/std::nullopt);
+
+  EXPECT_TRUE(console_observer.messages().empty());
+
+  run_loop.Run();
+}
+
+IN_PROC_BROWSER_TEST_F(
+    SharedStoragePrivateAggregationFilteringIdEnabledBrowserTest,
+    MaxFilteringIdForByteSize_Success) {
+  WebContentsConsoleObserver console_observer(shell()->web_contents());
+
+  base::RunLoop run_loop;
+
+  EXPECT_CALL(mock_callback(), Run)
+      .WillOnce(testing::Invoke(
+          [&](PrivateAggregationHost::ReportRequestGenerator generator,
+              std::vector<blink::mojom::AggregatableReportHistogramContribution>
+                  contributions,
+              PrivateAggregationBudgetKey budget_key,
+              PrivateAggregationBudgeter::BudgetDeniedBehavior
+                  budget_denied_behavior) {
+            AggregatableReportRequest request =
+                std::move(generator).Run(contributions);
+            ASSERT_EQ(request.payload_contents().contributions.size(), 1u);
+            EXPECT_EQ(request.payload_contents().contributions[0].filtering_id,
+                      255);
+            EXPECT_EQ(request.payload_contents().filtering_id_max_bytes,
+                      PrivateAggregationHost::kDefaultFilteringIdMaxBytes);
+            EXPECT_EQ(request.shared_info().debug_mode,
+                      AggregatableReportSharedInfo::DebugMode::kEnabled);
+            run_loop.Quit();
+          }));
+
+  EXPECT_CALL(browser_client(),
+              LogWebFeatureForCurrentPage(
+                  shell()->web_contents()->GetPrimaryMainFrame(),
+                  blink::mojom::WebFeature::kPrivateAggregationApiAll));
+  EXPECT_CALL(
+      browser_client(),
+      LogWebFeatureForCurrentPage(
+          shell()->web_contents()->GetPrimaryMainFrame(),
+          blink::mojom::WebFeature::kPrivateAggregationApiEnableDebugMode));
+  EXPECT_CALL(
+      browser_client(),
+      LogWebFeatureForCurrentPage(
+          shell()->web_contents()->GetPrimaryMainFrame(),
+          blink::mojom::WebFeature::kPrivateAggregationApiSharedStorage));
+  EXPECT_CALL(
+      browser_client(),
+      LogWebFeatureForCurrentPage(
+          shell()->web_contents()->GetPrimaryMainFrame(),
+          blink::mojom::WebFeature::kPrivateAggregationApiFilteringIds));
+  ON_CALL(browser_client(), IsPrivateAggregationAllowed)
+      .WillByDefault(testing::Return(true));
+  ON_CALL(browser_client(), IsPrivateAggregationDebugModeAllowed)
+      .WillByDefault(testing::Return(true));
+  ON_CALL(browser_client(), IsSharedStorageAllowed)
+      .WillByDefault(testing::Return(true));
+
+  GURL out_script_url;
+  ExecuteScriptInWorklet(shell(), R"(
+      privateAggregation.enableDebugMode();
+      privateAggregation.contributeToHistogram(
+          {bucket: 1n, value: 2, filteringId: 255n});
+    )",
+                         &out_script_url, /*expected_total_host_count=*/1u,
+                         /*keep_alive_after_operation=*/true,
+                         /*context_id=*/std::nullopt);
+
+  EXPECT_TRUE(console_observer.messages().empty());
+
+  run_loop.Run();
+}
+
+IN_PROC_BROWSER_TEST_F(
+    SharedStoragePrivateAggregationFilteringIdEnabledBrowserTest,
+    FilteringIdTooBigForByteSize_Error) {
+  WebContentsConsoleObserver console_observer(shell()->web_contents());
+
+  base::RunLoop run_loop;
+
+  EXPECT_CALL(mock_callback(), Run).Times(0);
+  EXPECT_CALL(browser_client(),
+              LogWebFeatureForCurrentPage(
+                  shell()->web_contents()->GetPrimaryMainFrame(),
+                  blink::mojom::WebFeature::kPrivateAggregationApiAll));
+  EXPECT_CALL(
+      browser_client(),
+      LogWebFeatureForCurrentPage(
+          shell()->web_contents()->GetPrimaryMainFrame(),
+          blink::mojom::WebFeature::kPrivateAggregationApiEnableDebugMode));
+  EXPECT_CALL(
+      browser_client(),
+      LogWebFeatureForCurrentPage(
+          shell()->web_contents()->GetPrimaryMainFrame(),
+          blink::mojom::WebFeature::kPrivateAggregationApiSharedStorage));
+  EXPECT_CALL(
+      browser_client(),
+      LogWebFeatureForCurrentPage(
+          shell()->web_contents()->GetPrimaryMainFrame(),
+          blink::mojom::WebFeature::kPrivateAggregationApiFilteringIds));
+  ON_CALL(browser_client(), IsPrivateAggregationAllowed)
+      .WillByDefault(testing::Return(true));
+  ON_CALL(browser_client(), IsPrivateAggregationDebugModeAllowed)
+      .WillByDefault(testing::Return(true));
+  ON_CALL(browser_client(), IsSharedStorageAllowed)
+      .WillByDefault(testing::Return(true));
+
+  GURL out_script_url;
+  ExecuteScriptInWorklet(shell(), R"(
+      privateAggregation.enableDebugMode();
+      privateAggregation.contributeToHistogram(
+          {bucket: 1n, value: 2, filteringId: 256n});
+    )",
+                         &out_script_url, /*expected_total_host_count=*/1u,
+                         /*keep_alive_after_operation=*/true,
+                         /*context_id=*/std::nullopt,
+                         /*filtering_id_max_bytes=*/std::nullopt);
+
+  ASSERT_EQ(1u, console_observer.messages().size());
+  EXPECT_THAT(base::UTF16ToUTF8(console_observer.messages()[0].message),
+              testing::HasSubstr("contribution['filteringId'] is negative or "
+                                 "does not fit in byte size"));
+  EXPECT_EQ(blink::mojom::ConsoleMessageLevel::kError,
+            console_observer.messages()[0].log_level);
+}
+
+IN_PROC_BROWSER_TEST_F(
+    SharedStoragePrivateAggregationFilteringIdEnabledBrowserTest,
+    FilteringIdNegative_Error) {
+  WebContentsConsoleObserver console_observer(shell()->web_contents());
+
+  base::RunLoop run_loop;
+
+  EXPECT_CALL(mock_callback(), Run).Times(0);
+  EXPECT_CALL(browser_client(),
+              LogWebFeatureForCurrentPage(
+                  shell()->web_contents()->GetPrimaryMainFrame(),
+                  blink::mojom::WebFeature::kPrivateAggregationApiAll));
+  EXPECT_CALL(
+      browser_client(),
+      LogWebFeatureForCurrentPage(
+          shell()->web_contents()->GetPrimaryMainFrame(),
+          blink::mojom::WebFeature::kPrivateAggregationApiEnableDebugMode));
+  EXPECT_CALL(
+      browser_client(),
+      LogWebFeatureForCurrentPage(
+          shell()->web_contents()->GetPrimaryMainFrame(),
+          blink::mojom::WebFeature::kPrivateAggregationApiSharedStorage));
+  EXPECT_CALL(
+      browser_client(),
+      LogWebFeatureForCurrentPage(
+          shell()->web_contents()->GetPrimaryMainFrame(),
+          blink::mojom::WebFeature::kPrivateAggregationApiFilteringIds));
+  ON_CALL(browser_client(), IsPrivateAggregationAllowed)
+      .WillByDefault(testing::Return(true));
+  ON_CALL(browser_client(), IsPrivateAggregationDebugModeAllowed)
+      .WillByDefault(testing::Return(true));
+  ON_CALL(browser_client(), IsSharedStorageAllowed)
+      .WillByDefault(testing::Return(true));
+
+  GURL out_script_url;
+  ExecuteScriptInWorklet(shell(), R"(
+      privateAggregation.enableDebugMode();
+      privateAggregation.contributeToHistogram(
+          {bucket: 1n, value: 2, filteringId: -1n});
+    )",
+                         &out_script_url, /*expected_total_host_count=*/1u,
+                         /*keep_alive_after_operation=*/true,
+                         /*context_id=*/std::nullopt,
+                         /*filtering_id_max_bytes=*/std::nullopt);
+
+  ASSERT_EQ(1u, console_observer.messages().size());
+  EXPECT_THAT(base::UTF16ToUTF8(console_observer.messages()[0].message),
+              testing::HasSubstr("contribution['filteringId'] is negative or "
+                                 "does not fit in byte size"));
+  EXPECT_EQ(blink::mojom::ConsoleMessageLevel::kError,
+            console_observer.messages()[0].log_level);
+}
+
+IN_PROC_BROWSER_TEST_F(
+    SharedStoragePrivateAggregationFilteringIdEnabledBrowserTest,
+    NoFilteringIdWithCustomByteSize_Success) {
+  WebContentsConsoleObserver console_observer(shell()->web_contents());
+
+  base::RunLoop run_loop;
+
+  EXPECT_CALL(mock_callback(), Run)
+      .WillOnce(testing::Invoke(
+          [&](PrivateAggregationHost::ReportRequestGenerator generator,
+              std::vector<blink::mojom::AggregatableReportHistogramContribution>
+                  contributions,
+              PrivateAggregationBudgetKey budget_key,
+              PrivateAggregationBudgeter::BudgetDeniedBehavior
+                  budget_denied_behavior) {
+            AggregatableReportRequest request =
+                std::move(generator).Run(contributions);
+            ASSERT_EQ(request.payload_contents().contributions.size(), 1u);
+            EXPECT_EQ(request.payload_contents().contributions[0].filtering_id,
+                      std::nullopt);
+            EXPECT_EQ(request.payload_contents().filtering_id_max_bytes, 8);
+            EXPECT_EQ(request.shared_info().debug_mode,
+                      AggregatableReportSharedInfo::DebugMode::kEnabled);
+            run_loop.Quit();
+          }));
+
+  EXPECT_CALL(browser_client(),
+              LogWebFeatureForCurrentPage(
+                  shell()->web_contents()->GetPrimaryMainFrame(),
+                  blink::mojom::WebFeature::kPrivateAggregationApiAll));
+  EXPECT_CALL(
+      browser_client(),
+      LogWebFeatureForCurrentPage(
+          shell()->web_contents()->GetPrimaryMainFrame(),
+          blink::mojom::WebFeature::kPrivateAggregationApiEnableDebugMode));
+  EXPECT_CALL(
+      browser_client(),
+      LogWebFeatureForCurrentPage(
+          shell()->web_contents()->GetPrimaryMainFrame(),
+          blink::mojom::WebFeature::kPrivateAggregationApiSharedStorage));
+  EXPECT_CALL(browser_client(),
+              LogWebFeatureForCurrentPage(
+                  shell()->web_contents()->GetPrimaryMainFrame(),
+                  blink::mojom::WebFeature::kPrivateAggregationApiFilteringIds))
+      .Times(0);
+  ON_CALL(browser_client(), IsPrivateAggregationAllowed)
+      .WillByDefault(testing::Return(true));
+  ON_CALL(browser_client(), IsPrivateAggregationDebugModeAllowed)
+      .WillByDefault(testing::Return(true));
+  ON_CALL(browser_client(), IsSharedStorageAllowed)
+      .WillByDefault(testing::Return(true));
+
+  GURL out_script_url;
+  ExecuteScriptInWorklet(shell(), R"(
+      privateAggregation.enableDebugMode();
+      privateAggregation.contributeToHistogram(
+          {bucket: 1n, value: 2});
+    )",
+                         &out_script_url, /*expected_total_host_count=*/1u,
+                         /*keep_alive_after_operation=*/true,
+                         /*context_id=*/std::nullopt,
+                         /*filtering_id_max_bytes=*/"8");
+
+  EXPECT_TRUE(console_observer.messages().empty());
+
+  run_loop.Run();
+}
+
+IN_PROC_BROWSER_TEST_F(
+    SharedStoragePrivateAggregationFilteringIdEnabledBrowserTest,
+    FilteringIdWithCustomByteSize_Success) {
+  WebContentsConsoleObserver console_observer(shell()->web_contents());
+
+  base::RunLoop run_loop;
+
+  EXPECT_CALL(mock_callback(), Run)
+      .WillOnce(testing::Invoke(
+          [&](PrivateAggregationHost::ReportRequestGenerator generator,
+              std::vector<blink::mojom::AggregatableReportHistogramContribution>
+                  contributions,
+              PrivateAggregationBudgetKey budget_key,
+              PrivateAggregationBudgeter::BudgetDeniedBehavior
+                  budget_denied_behavior) {
+            AggregatableReportRequest request =
+                std::move(generator).Run(contributions);
+            ASSERT_EQ(request.payload_contents().contributions.size(), 1u);
+            EXPECT_EQ(request.payload_contents().contributions[0].filtering_id,
+                      1000);
+            EXPECT_EQ(request.payload_contents().filtering_id_max_bytes, 8);
+            EXPECT_EQ(request.shared_info().debug_mode,
+                      AggregatableReportSharedInfo::DebugMode::kEnabled);
+            run_loop.Quit();
+          }));
+
+  EXPECT_CALL(browser_client(),
+              LogWebFeatureForCurrentPage(
+                  shell()->web_contents()->GetPrimaryMainFrame(),
+                  blink::mojom::WebFeature::kPrivateAggregationApiAll));
+  EXPECT_CALL(
+      browser_client(),
+      LogWebFeatureForCurrentPage(
+          shell()->web_contents()->GetPrimaryMainFrame(),
+          blink::mojom::WebFeature::kPrivateAggregationApiEnableDebugMode));
+  EXPECT_CALL(
+      browser_client(),
+      LogWebFeatureForCurrentPage(
+          shell()->web_contents()->GetPrimaryMainFrame(),
+          blink::mojom::WebFeature::kPrivateAggregationApiSharedStorage));
+  EXPECT_CALL(
+      browser_client(),
+      LogWebFeatureForCurrentPage(
+          shell()->web_contents()->GetPrimaryMainFrame(),
+          blink::mojom::WebFeature::kPrivateAggregationApiFilteringIds));
+  ON_CALL(browser_client(), IsPrivateAggregationAllowed)
+      .WillByDefault(testing::Return(true));
+  ON_CALL(browser_client(), IsPrivateAggregationDebugModeAllowed)
+      .WillByDefault(testing::Return(true));
+  ON_CALL(browser_client(), IsSharedStorageAllowed)
+      .WillByDefault(testing::Return(true));
+
+  GURL out_script_url;
+  ExecuteScriptInWorklet(shell(), R"(
+      privateAggregation.enableDebugMode();
+      privateAggregation.contributeToHistogram(
+          {bucket: 1n, value: 2, filteringId: 1000n});
+    )",
+                         &out_script_url, /*expected_total_host_count=*/1u,
+                         /*keep_alive_after_operation=*/true,
+                         /*context_id=*/std::nullopt,
+                         /*filtering_id_max_bytes=*/"8");
+
+  EXPECT_TRUE(console_observer.messages().empty());
+
+  run_loop.Run();
+}
+
+IN_PROC_BROWSER_TEST_F(
+    SharedStoragePrivateAggregationFilteringIdEnabledBrowserTest,
+    MaxFilteringIdWithCustomByteSize_Success) {
+  WebContentsConsoleObserver console_observer(shell()->web_contents());
+
+  base::RunLoop run_loop;
+
+  EXPECT_CALL(mock_callback(), Run)
+      .WillOnce(testing::Invoke(
+          [&](PrivateAggregationHost::ReportRequestGenerator generator,
+              std::vector<blink::mojom::AggregatableReportHistogramContribution>
+                  contributions,
+              PrivateAggregationBudgetKey budget_key,
+              PrivateAggregationBudgeter::BudgetDeniedBehavior
+                  budget_denied_behavior) {
+            AggregatableReportRequest request =
+                std::move(generator).Run(contributions);
+            ASSERT_EQ(request.payload_contents().contributions.size(), 1u);
+            EXPECT_EQ(request.payload_contents().contributions[0].filtering_id,
+                      std::numeric_limits<uint64_t>::max());
+            EXPECT_EQ(request.payload_contents().filtering_id_max_bytes, 8);
+            EXPECT_EQ(request.shared_info().debug_mode,
+                      AggregatableReportSharedInfo::DebugMode::kEnabled);
+            run_loop.Quit();
+          }));
+
+  EXPECT_CALL(browser_client(),
+              LogWebFeatureForCurrentPage(
+                  shell()->web_contents()->GetPrimaryMainFrame(),
+                  blink::mojom::WebFeature::kPrivateAggregationApiAll));
+  EXPECT_CALL(
+      browser_client(),
+      LogWebFeatureForCurrentPage(
+          shell()->web_contents()->GetPrimaryMainFrame(),
+          blink::mojom::WebFeature::kPrivateAggregationApiEnableDebugMode));
+  EXPECT_CALL(
+      browser_client(),
+      LogWebFeatureForCurrentPage(
+          shell()->web_contents()->GetPrimaryMainFrame(),
+          blink::mojom::WebFeature::kPrivateAggregationApiSharedStorage));
+  EXPECT_CALL(
+      browser_client(),
+      LogWebFeatureForCurrentPage(
+          shell()->web_contents()->GetPrimaryMainFrame(),
+          blink::mojom::WebFeature::kPrivateAggregationApiFilteringIds));
+  ON_CALL(browser_client(), IsPrivateAggregationAllowed)
+      .WillByDefault(testing::Return(true));
+  ON_CALL(browser_client(), IsPrivateAggregationDebugModeAllowed)
+      .WillByDefault(testing::Return(true));
+  ON_CALL(browser_client(), IsSharedStorageAllowed)
+      .WillByDefault(testing::Return(true));
+
+  GURL out_script_url;
+  ExecuteScriptInWorklet(shell(), R"(
+      privateAggregation.enableDebugMode();
+      privateAggregation.contributeToHistogram(
+          {bucket: 1n, value: 2, filteringId: (1n << 64n) - 1n});
+    )",
+                         &out_script_url, /*expected_total_host_count=*/1u,
+                         /*keep_alive_after_operation=*/true,
+                         /*context_id=*/std::nullopt,
+                         /*filtering_id_max_bytes=*/"8");
+
+  EXPECT_TRUE(console_observer.messages().empty());
+
+  run_loop.Run();
+}
+
+IN_PROC_BROWSER_TEST_F(
+    SharedStoragePrivateAggregationFilteringIdEnabledBrowserTest,
+    TooBigFilteringIdWithCustomByteSize_Error) {
+  WebContentsConsoleObserver console_observer(shell()->web_contents());
+
+  EXPECT_CALL(mock_callback(), Run).Times(0);
+
+  EXPECT_CALL(browser_client(),
+              LogWebFeatureForCurrentPage(
+                  shell()->web_contents()->GetPrimaryMainFrame(),
+                  blink::mojom::WebFeature::kPrivateAggregationApiAll));
+  EXPECT_CALL(
+      browser_client(),
+      LogWebFeatureForCurrentPage(
+          shell()->web_contents()->GetPrimaryMainFrame(),
+          blink::mojom::WebFeature::kPrivateAggregationApiEnableDebugMode));
+  EXPECT_CALL(
+      browser_client(),
+      LogWebFeatureForCurrentPage(
+          shell()->web_contents()->GetPrimaryMainFrame(),
+          blink::mojom::WebFeature::kPrivateAggregationApiSharedStorage));
+  EXPECT_CALL(
+      browser_client(),
+      LogWebFeatureForCurrentPage(
+          shell()->web_contents()->GetPrimaryMainFrame(),
+          blink::mojom::WebFeature::kPrivateAggregationApiFilteringIds));
+  ON_CALL(browser_client(), IsPrivateAggregationAllowed)
+      .WillByDefault(testing::Return(true));
+  ON_CALL(browser_client(), IsPrivateAggregationDebugModeAllowed)
+      .WillByDefault(testing::Return(true));
+  ON_CALL(browser_client(), IsSharedStorageAllowed)
+      .WillByDefault(testing::Return(true));
+
+  GURL out_script_url;
+  ExecuteScriptInWorklet(shell(), R"(
+      privateAggregation.enableDebugMode();
+      privateAggregation.contributeToHistogram(
+          {bucket: 1n, value: 2, filteringId: (1n << 64n)});
+    )",
+                         &out_script_url, /*expected_total_host_count=*/1u,
+                         /*keep_alive_after_operation=*/true,
+                         /*context_id=*/std::nullopt,
+                         /*filtering_id_max_bytes=*/"8");
+
+  ASSERT_EQ(1u, console_observer.messages().size());
+  EXPECT_THAT(base::UTF16ToUTF8(console_observer.messages()[0].message),
+              testing::HasSubstr("contribution['filteringId'] is negative or "
+                                 "does not fit in byte size"));
+  EXPECT_EQ(blink::mojom::ConsoleMessageLevel::kError,
+            console_observer.messages()[0].log_level);
+}
+
+IN_PROC_BROWSER_TEST_F(
+    SharedStoragePrivateAggregationFilteringIdEnabledBrowserTest,
+    FilteringIdMaxBytesTooBig_Error) {
+  WebContentsConsoleObserver console_observer(shell()->web_contents());
+
+  EXPECT_CALL(mock_callback(), Run).Times(0);
+
+  EXPECT_CALL(browser_client(),
+              LogWebFeatureForCurrentPage(
+                  shell()->web_contents()->GetPrimaryMainFrame(),
+                  blink::mojom::WebFeature::kPrivateAggregationApiAll))
+      .Times(0);
+  EXPECT_CALL(
+      browser_client(),
+      LogWebFeatureForCurrentPage(
+          shell()->web_contents()->GetPrimaryMainFrame(),
+          blink::mojom::WebFeature::kPrivateAggregationApiEnableDebugMode))
+      .Times(0);
+  EXPECT_CALL(
+      browser_client(),
+      LogWebFeatureForCurrentPage(
+          shell()->web_contents()->GetPrimaryMainFrame(),
+          blink::mojom::WebFeature::kPrivateAggregationApiSharedStorage))
+      .Times(0);
+
+  EXPECT_CALL(browser_client(),
+              LogWebFeatureForCurrentPage(
+                  shell()->web_contents()->GetPrimaryMainFrame(),
+                  blink::mojom::WebFeature::kPrivateAggregationApiFilteringIds))
+      .Times(0);
+  ON_CALL(browser_client(), IsPrivateAggregationAllowed)
+      .WillByDefault(testing::Return(true));
+  ON_CALL(browser_client(), IsPrivateAggregationDebugModeAllowed)
+      .WillByDefault(testing::Return(true));
+  ON_CALL(browser_client(), IsSharedStorageAllowed)
+      .WillByDefault(testing::Return(true));
+
+  GURL out_script_url;
+  std::string out_error;
+  ExecuteScriptInWorklet(shell(), "", &out_script_url,
+                         /*expected_total_host_count=*/1u,
+                         /*keep_alive_after_operation=*/true,
+                         /*context_id=*/std::nullopt,
+                         /*filtering_id_max_bytes=*/"9", &out_error);
+
+  EXPECT_THAT(out_error,
+              testing::HasSubstr("Error: filteringIdMaxBytes is too big"));
+}
+
+IN_PROC_BROWSER_TEST_F(
+    SharedStoragePrivateAggregationFilteringIdEnabledBrowserTest,
+    FilteringIdMaxBytesZero_Error) {
+  WebContentsConsoleObserver console_observer(shell()->web_contents());
+
+  EXPECT_CALL(mock_callback(), Run).Times(0);
+
+  EXPECT_CALL(browser_client(),
+              LogWebFeatureForCurrentPage(
+                  shell()->web_contents()->GetPrimaryMainFrame(),
+                  blink::mojom::WebFeature::kPrivateAggregationApiAll))
+      .Times(0);
+  EXPECT_CALL(
+      browser_client(),
+      LogWebFeatureForCurrentPage(
+          shell()->web_contents()->GetPrimaryMainFrame(),
+          blink::mojom::WebFeature::kPrivateAggregationApiEnableDebugMode))
+      .Times(0);
+  EXPECT_CALL(
+      browser_client(),
+      LogWebFeatureForCurrentPage(
+          shell()->web_contents()->GetPrimaryMainFrame(),
+          blink::mojom::WebFeature::kPrivateAggregationApiSharedStorage))
+      .Times(0);
+  EXPECT_CALL(browser_client(),
+              LogWebFeatureForCurrentPage(
+                  shell()->web_contents()->GetPrimaryMainFrame(),
+                  blink::mojom::WebFeature::kPrivateAggregationApiFilteringIds))
+      .Times(0);
+  ON_CALL(browser_client(), IsPrivateAggregationAllowed)
+      .WillByDefault(testing::Return(true));
+  ON_CALL(browser_client(), IsPrivateAggregationDebugModeAllowed)
+      .WillByDefault(testing::Return(true));
+  ON_CALL(browser_client(), IsSharedStorageAllowed)
+      .WillByDefault(testing::Return(true));
+
+  GURL out_script_url;
+  std::string out_error;
+  ExecuteScriptInWorklet(shell(), "", &out_script_url,
+                         /*expected_total_host_count=*/1u,
+                         /*keep_alive_after_operation=*/true,
+                         /*context_id=*/std::nullopt,
+                         /*filtering_id_max_bytes=*/"0", &out_error);
+
+  EXPECT_THAT(out_error, testing::HasSubstr(
+                             "Error: filteringIdMaxBytes must be positive"));
+}
+
+IN_PROC_BROWSER_TEST_F(
+    SharedStoragePrivateAggregationFilteringIdEnabledBrowserTest,
+    FilteringIdMaxBytesNegative_Error) {
+  WebContentsConsoleObserver console_observer(shell()->web_contents());
+
+  EXPECT_CALL(mock_callback(), Run).Times(0);
+
+  EXPECT_CALL(browser_client(),
+              LogWebFeatureForCurrentPage(
+                  shell()->web_contents()->GetPrimaryMainFrame(),
+                  blink::mojom::WebFeature::kPrivateAggregationApiAll))
+      .Times(0);
+  EXPECT_CALL(
+      browser_client(),
+      LogWebFeatureForCurrentPage(
+          shell()->web_contents()->GetPrimaryMainFrame(),
+          blink::mojom::WebFeature::kPrivateAggregationApiEnableDebugMode))
+      .Times(0);
+  EXPECT_CALL(
+      browser_client(),
+      LogWebFeatureForCurrentPage(
+          shell()->web_contents()->GetPrimaryMainFrame(),
+          blink::mojom::WebFeature::kPrivateAggregationApiSharedStorage))
+      .Times(0);
+  EXPECT_CALL(browser_client(),
+              LogWebFeatureForCurrentPage(
+                  shell()->web_contents()->GetPrimaryMainFrame(),
+                  blink::mojom::WebFeature::kPrivateAggregationApiFilteringIds))
+      .Times(0);
+  ON_CALL(browser_client(), IsPrivateAggregationAllowed)
+      .WillByDefault(testing::Return(true));
+  ON_CALL(browser_client(), IsPrivateAggregationDebugModeAllowed)
+      .WillByDefault(testing::Return(true));
+  ON_CALL(browser_client(), IsSharedStorageAllowed)
+      .WillByDefault(testing::Return(true));
+
+  GURL out_script_url;
+  std::string out_error;
+  ExecuteScriptInWorklet(shell(), "", &out_script_url,
+                         /*expected_total_host_count=*/1u,
+                         /*keep_alive_after_operation=*/true,
+                         /*context_id=*/std::nullopt,
+                         /*filtering_id_max_bytes=*/"-1", &out_error);
+
+  EXPECT_THAT(out_error,
+              testing::HasSubstr("Value is outside the 'unsigned long"
+                                 " long' value range."));
+}
+
+class SharedStoragePrivateAggregationFilteringIdDisabledBrowserTest
+    : public SharedStoragePrivateAggregationEnabledBrowserTest {
+ public:
+  SharedStoragePrivateAggregationFilteringIdDisabledBrowserTest() {
+    scoped_feature_list_.InitAndDisableFeature(
+        blink::features::kPrivateAggregationApiFilteringIds);
+  }
+
+ private:
+  base::test::ScopedFeatureList scoped_feature_list_;
+};
+
+IN_PROC_BROWSER_TEST_F(
+    SharedStoragePrivateAggregationFilteringIdDisabledBrowserTest,
+    ValidFilteringId_Ignored) {
+  WebContentsConsoleObserver console_observer(shell()->web_contents());
+
+  base::RunLoop run_loop;
+
+  EXPECT_CALL(mock_callback(), Run)
+      .WillOnce(testing::Invoke(
+          [&](PrivateAggregationHost::ReportRequestGenerator generator,
+              std::vector<blink::mojom::AggregatableReportHistogramContribution>
+                  contributions,
+              PrivateAggregationBudgetKey budget_key,
+              PrivateAggregationBudgeter::BudgetDeniedBehavior
+                  budget_denied_behavior) {
+            AggregatableReportRequest request =
+                std::move(generator).Run(contributions);
+            ASSERT_EQ(request.payload_contents().contributions.size(), 1u);
+            EXPECT_EQ(request.payload_contents().contributions[0].filtering_id,
+                      std::nullopt);
+            EXPECT_EQ(request.payload_contents().filtering_id_max_bytes,
+                      std::nullopt);
+            EXPECT_EQ(request.shared_info().debug_mode,
+                      AggregatableReportSharedInfo::DebugMode::kEnabled);
+            run_loop.Quit();
+          }));
+
+  EXPECT_CALL(browser_client(),
+              LogWebFeatureForCurrentPage(
+                  shell()->web_contents()->GetPrimaryMainFrame(),
+                  blink::mojom::WebFeature::kPrivateAggregationApiAll));
+  EXPECT_CALL(
+      browser_client(),
+      LogWebFeatureForCurrentPage(
+          shell()->web_contents()->GetPrimaryMainFrame(),
+          blink::mojom::WebFeature::kPrivateAggregationApiEnableDebugMode));
+  EXPECT_CALL(
+      browser_client(),
+      LogWebFeatureForCurrentPage(
+          shell()->web_contents()->GetPrimaryMainFrame(),
+          blink::mojom::WebFeature::kPrivateAggregationApiSharedStorage));
+  EXPECT_CALL(browser_client(),
+              LogWebFeatureForCurrentPage(
+                  shell()->web_contents()->GetPrimaryMainFrame(),
+                  blink::mojom::WebFeature::kPrivateAggregationApiFilteringIds))
+      .Times(0);
+  ON_CALL(browser_client(), IsPrivateAggregationAllowed)
+      .WillByDefault(testing::Return(true));
+  ON_CALL(browser_client(), IsPrivateAggregationDebugModeAllowed)
+      .WillByDefault(testing::Return(true));
+  ON_CALL(browser_client(), IsSharedStorageAllowed)
+      .WillByDefault(testing::Return(true));
+
+  GURL out_script_url;
+  ExecuteScriptInWorklet(shell(), R"(
+      privateAggregation.enableDebugMode();
+      privateAggregation.contributeToHistogram(
+          {bucket: 1n, value: 2, filteringId: 3n});
+    )",
+                         &out_script_url, /*expected_total_host_count=*/1u,
+                         /*keep_alive_after_operation=*/true,
+                         /*context_id=*/std::nullopt);
+
+  EXPECT_TRUE(console_observer.messages().empty());
+
+  run_loop.Run();
+}
+
+IN_PROC_BROWSER_TEST_F(
+    SharedStoragePrivateAggregationFilteringIdDisabledBrowserTest,
+    InvalidFilteringId_Ignored) {
+  WebContentsConsoleObserver console_observer(shell()->web_contents());
+
+  base::RunLoop run_loop;
+
+  EXPECT_CALL(mock_callback(), Run)
+      .WillOnce(testing::Invoke(
+          [&](PrivateAggregationHost::ReportRequestGenerator generator,
+              std::vector<blink::mojom::AggregatableReportHistogramContribution>
+                  contributions,
+              PrivateAggregationBudgetKey budget_key,
+              PrivateAggregationBudgeter::BudgetDeniedBehavior
+                  budget_denied_behavior) {
+            AggregatableReportRequest request =
+                std::move(generator).Run(contributions);
+            ASSERT_EQ(request.payload_contents().contributions.size(), 1u);
+            EXPECT_EQ(request.payload_contents().contributions[0].filtering_id,
+                      std::nullopt);
+            EXPECT_EQ(request.payload_contents().filtering_id_max_bytes,
+                      std::nullopt);
+            EXPECT_EQ(request.shared_info().debug_mode,
+                      AggregatableReportSharedInfo::DebugMode::kEnabled);
+            run_loop.Quit();
+          }));
+
+  EXPECT_CALL(browser_client(),
+              LogWebFeatureForCurrentPage(
+                  shell()->web_contents()->GetPrimaryMainFrame(),
+                  blink::mojom::WebFeature::kPrivateAggregationApiAll));
+  EXPECT_CALL(
+      browser_client(),
+      LogWebFeatureForCurrentPage(
+          shell()->web_contents()->GetPrimaryMainFrame(),
+          blink::mojom::WebFeature::kPrivateAggregationApiEnableDebugMode));
+  EXPECT_CALL(
+      browser_client(),
+      LogWebFeatureForCurrentPage(
+          shell()->web_contents()->GetPrimaryMainFrame(),
+          blink::mojom::WebFeature::kPrivateAggregationApiSharedStorage));
+  EXPECT_CALL(browser_client(),
+              LogWebFeatureForCurrentPage(
+                  shell()->web_contents()->GetPrimaryMainFrame(),
+                  blink::mojom::WebFeature::kPrivateAggregationApiFilteringIds))
+      .Times(0);
+  ON_CALL(browser_client(), IsPrivateAggregationAllowed)
+      .WillByDefault(testing::Return(true));
+  ON_CALL(browser_client(), IsPrivateAggregationDebugModeAllowed)
+      .WillByDefault(testing::Return(true));
+  ON_CALL(browser_client(), IsSharedStorageAllowed)
+      .WillByDefault(testing::Return(true));
+
+  GURL out_script_url;
+  ExecuteScriptInWorklet(shell(), R"(
+      privateAggregation.enableDebugMode();
+      privateAggregation.contributeToHistogram(
+          {bucket: 1n, value: 2, filteringId: -1});
+    )",
+                         &out_script_url, /*expected_total_host_count=*/1u,
+                         /*keep_alive_after_operation=*/true,
+                         /*context_id=*/std::nullopt);
+
+  EXPECT_TRUE(console_observer.messages().empty());
+
+  run_loop.Run();
+}
+
+IN_PROC_BROWSER_TEST_F(
+    SharedStoragePrivateAggregationFilteringIdDisabledBrowserTest,
+    CustomFilteringIdMaxBytes_Ignored) {
+  WebContentsConsoleObserver console_observer(shell()->web_contents());
+
+  base::RunLoop run_loop;
+
+  EXPECT_CALL(mock_callback(), Run)
+      .WillOnce(testing::Invoke(
+          [&](PrivateAggregationHost::ReportRequestGenerator generator,
+              std::vector<blink::mojom::AggregatableReportHistogramContribution>
+                  contributions,
+              PrivateAggregationBudgetKey budget_key,
+              PrivateAggregationBudgeter::BudgetDeniedBehavior
+                  budget_denied_behavior) {
+            AggregatableReportRequest request =
+                std::move(generator).Run(contributions);
+            ASSERT_EQ(request.payload_contents().contributions.size(), 1u);
+            EXPECT_EQ(request.payload_contents().contributions[0].filtering_id,
+                      std::nullopt);
+            EXPECT_EQ(request.payload_contents().filtering_id_max_bytes,
+                      std::nullopt);
+            EXPECT_EQ(request.shared_info().debug_mode,
+                      AggregatableReportSharedInfo::DebugMode::kEnabled);
+            run_loop.Quit();
+          }));
+
+  EXPECT_CALL(browser_client(),
+              LogWebFeatureForCurrentPage(
+                  shell()->web_contents()->GetPrimaryMainFrame(),
+                  blink::mojom::WebFeature::kPrivateAggregationApiAll));
+  EXPECT_CALL(
+      browser_client(),
+      LogWebFeatureForCurrentPage(
+          shell()->web_contents()->GetPrimaryMainFrame(),
+          blink::mojom::WebFeature::kPrivateAggregationApiEnableDebugMode));
+  EXPECT_CALL(
+      browser_client(),
+      LogWebFeatureForCurrentPage(
+          shell()->web_contents()->GetPrimaryMainFrame(),
+          blink::mojom::WebFeature::kPrivateAggregationApiSharedStorage));
+  EXPECT_CALL(browser_client(),
+              LogWebFeatureForCurrentPage(
+                  shell()->web_contents()->GetPrimaryMainFrame(),
+                  blink::mojom::WebFeature::kPrivateAggregationApiFilteringIds))
+      .Times(0);
+  ON_CALL(browser_client(), IsPrivateAggregationAllowed)
+      .WillByDefault(testing::Return(true));
+  ON_CALL(browser_client(), IsPrivateAggregationDebugModeAllowed)
+      .WillByDefault(testing::Return(true));
+  ON_CALL(browser_client(), IsSharedStorageAllowed)
+      .WillByDefault(testing::Return(true));
+
+  GURL out_script_url;
+  ExecuteScriptInWorklet(shell(), R"(
+      privateAggregation.enableDebugMode();
+      privateAggregation.contributeToHistogram(
+          {bucket: 1n, value: 2, filteringId: 100000n});
+    )",
+                         &out_script_url, /*expected_total_host_count=*/1u,
+                         /*keep_alive_after_operation=*/true,
+                         /*context_id=*/std::nullopt,
+                         /*filtering_id_max_bytes=*/"2");
+
+  EXPECT_TRUE(console_observer.messages().empty());
+
+  run_loop.Run();
+}
+
+IN_PROC_BROWSER_TEST_F(
+    SharedStoragePrivateAggregationFilteringIdDisabledBrowserTest,
+    InvalidFilteringIdMaxBytes_Ignored) {
+  WebContentsConsoleObserver console_observer(shell()->web_contents());
+
+  base::RunLoop run_loop;
+
+  EXPECT_CALL(mock_callback(), Run)
+      .WillOnce(testing::Invoke(
+          [&](PrivateAggregationHost::ReportRequestGenerator generator,
+              std::vector<blink::mojom::AggregatableReportHistogramContribution>
+                  contributions,
+              PrivateAggregationBudgetKey budget_key,
+              PrivateAggregationBudgeter::BudgetDeniedBehavior
+                  budget_denied_behavior) {
+            AggregatableReportRequest request =
+                std::move(generator).Run(contributions);
+            ASSERT_EQ(request.payload_contents().contributions.size(), 1u);
+            EXPECT_EQ(request.payload_contents().contributions[0].filtering_id,
+                      std::nullopt);
+            EXPECT_EQ(request.payload_contents().filtering_id_max_bytes,
+                      std::nullopt);
+            EXPECT_EQ(request.shared_info().debug_mode,
+                      AggregatableReportSharedInfo::DebugMode::kEnabled);
+            run_loop.Quit();
+          }));
+
+  EXPECT_CALL(browser_client(),
+              LogWebFeatureForCurrentPage(
+                  shell()->web_contents()->GetPrimaryMainFrame(),
+                  blink::mojom::WebFeature::kPrivateAggregationApiAll));
+  EXPECT_CALL(
+      browser_client(),
+      LogWebFeatureForCurrentPage(
+          shell()->web_contents()->GetPrimaryMainFrame(),
+          blink::mojom::WebFeature::kPrivateAggregationApiEnableDebugMode));
+  EXPECT_CALL(
+      browser_client(),
+      LogWebFeatureForCurrentPage(
+          shell()->web_contents()->GetPrimaryMainFrame(),
+          blink::mojom::WebFeature::kPrivateAggregationApiSharedStorage));
+  EXPECT_CALL(browser_client(),
+              LogWebFeatureForCurrentPage(
+                  shell()->web_contents()->GetPrimaryMainFrame(),
+                  blink::mojom::WebFeature::kPrivateAggregationApiFilteringIds))
+      .Times(0);
+  ON_CALL(browser_client(), IsPrivateAggregationAllowed)
+      .WillByDefault(testing::Return(true));
+  ON_CALL(browser_client(), IsPrivateAggregationDebugModeAllowed)
+      .WillByDefault(testing::Return(true));
+  ON_CALL(browser_client(), IsSharedStorageAllowed)
+      .WillByDefault(testing::Return(true));
+
+  GURL out_script_url;
+  ExecuteScriptInWorklet(shell(), R"(
+      privateAggregation.enableDebugMode();
+      privateAggregation.contributeToHistogram(
+          {bucket: 1n, value: 2});
+    )",
+                         &out_script_url, /*expected_total_host_count=*/1u,
+                         /*keep_alive_after_operation=*/true,
+                         /*context_id=*/std::nullopt,
+                         /*filtering_id_max_bytes=*/"-1n");
+
+  EXPECT_TRUE(console_observer.messages().empty());
+
+  run_loop.Run();
+}
+
 IN_PROC_BROWSER_TEST_F(SharedStoragePrivateAggregationEnabledBrowserTest,
                        PrivateAggregationPermissionsPolicyNone) {
   GURL url = https_server()->GetURL(
diff --git a/content/browser/shared_storage/shared_storage_worklet_host.cc b/content/browser/shared_storage/shared_storage_worklet_host.cc
index ea59abc..ce28c32 100644
--- a/content/browser/shared_storage/shared_storage_worklet_host.cc
+++ b/content/browser/shared_storage/shared_storage_worklet_host.cc
@@ -452,6 +452,15 @@
     return;
   }
 
+  if (!blink::IsValidPrivateAggregationFilteringIdMaxBytes(
+          private_aggregation_config->filtering_id_max_bytes)) {
+    receiver_.ReportBadMessage("Invalid fitering_id_byte_size.");
+    LogSharedStorageWorkletError(
+        blink::SharedStorageWorkletErrorType::
+            kSelectURLNonWebVisibleInvalidFilteringIdMaxBytes);
+    return;
+  }
+
   if (!keep_alive_after_operation_) {
     receiver_.ReportBadMessage(
         "Received further operations when previous operation did not include "
@@ -562,7 +571,8 @@
 
   GetAndConnectToSharedStorageWorkletService()->RunURLSelectionOperation(
       name, urls, std::move(serialized_data),
-      MaybeBindPrivateAggregationHost(private_aggregation_config),
+      MaybeConstructPrivateAggregationOperationDetails(
+          private_aggregation_config),
       base::BindOnce(
           &SharedStorageWorkletHost::
               OnRunURLSelectionOperationOnWorkletScriptExecutionFinished,
@@ -603,6 +613,15 @@
     return;
   }
 
+  if (!blink::IsValidPrivateAggregationFilteringIdMaxBytes(
+          private_aggregation_config->filtering_id_max_bytes)) {
+    receiver_.ReportBadMessage("Invalid fitering_id_byte_size.");
+    LogSharedStorageWorkletError(
+        blink::SharedStorageWorkletErrorType::
+            kRunNonWebVisibleInvalidFilteringIdMaxBytes);
+    return;
+  }
+
   if (!keep_alive_after_operation_) {
     receiver_.ReportBadMessage(
         "Received further operations when previous operation did not include "
@@ -651,7 +670,8 @@
 
   GetAndConnectToSharedStorageWorkletService()->RunOperation(
       name, std::move(serialized_data),
-      MaybeBindPrivateAggregationHost(private_aggregation_config),
+      MaybeConstructPrivateAggregationOperationDetails(
+          private_aggregation_config),
       base::BindOnce(&SharedStorageWorkletHost::OnRunOperationOnWorkletFinished,
                      weak_ptr_factory_.GetWeakPtr(), base::TimeTicks::Now()));
 }
@@ -1311,23 +1331,25 @@
   return shared_storage_worklet_service_.get();
 }
 
-mojo::PendingRemote<blink::mojom::PrivateAggregationHost>
-SharedStorageWorkletHost::MaybeBindPrivateAggregationHost(
+blink::mojom::PrivateAggregationOperationDetailsPtr
+SharedStorageWorkletHost::MaybeConstructPrivateAggregationOperationDetails(
     const blink::mojom::PrivateAggregationConfigPtr&
         private_aggregation_config) {
   CHECK(browser_context_, base::NotFatalUntil::M128);
   CHECK(private_aggregation_config);
 
   if (!blink::ShouldDefinePrivateAggregationInSharedStorage()) {
-    return mojo::PendingRemote<blink::mojom::PrivateAggregationHost>();
+    return nullptr;
   }
 
   PrivateAggregationManager* private_aggregation_manager =
       PrivateAggregationManager::GetManager(*browser_context_);
   CHECK(private_aggregation_manager, base::NotFatalUntil::M128);
 
-  mojo::PendingRemote<blink::mojom::PrivateAggregationHost>
-      pending_pa_host_remote;
+  blink::mojom::PrivateAggregationOperationDetailsPtr pa_operation_details =
+      blink::mojom::PrivateAggregationOperationDetails::New(
+          mojo::PendingRemote<blink::mojom::PrivateAggregationHost>(),
+          private_aggregation_config->filtering_id_max_bytes);
 
   std::optional<base::TimeDelta> timeout =
       (base::FeatureList::IsEnabled(blink::features::kSharedStorageAPIM118) &&
@@ -1335,15 +1357,17 @@
           ? std::optional<base::TimeDelta>(base::Seconds(5))
           : std::nullopt;
 
+  // TODO(crbug.com/330744610): Allow filtering ID byte size to be set.
   bool success = private_aggregation_manager->BindNewReceiver(
       shared_storage_origin_, main_frame_origin_,
       PrivateAggregationBudgetKey::Api::kSharedStorage,
       private_aggregation_config->context_id, std::move(timeout),
       private_aggregation_config->aggregation_coordinator_origin,
-      pending_pa_host_remote.InitWithNewPipeAndPassReceiver());
+      private_aggregation_config->filtering_id_max_bytes,
+      pa_operation_details->pa_host.InitWithNewPipeAndPassReceiver());
   CHECK(success);
 
-  return pending_pa_host_remote;
+  return pa_operation_details;
 }
 
 bool SharedStorageWorkletHost::IsSharedStorageAllowed(
diff --git a/content/browser/shared_storage/shared_storage_worklet_host.h b/content/browser/shared_storage/shared_storage_worklet_host.h
index a1b5e6a..110931a 100644
--- a/content/browser/shared_storage/shared_storage_worklet_host.h
+++ b/content/browser/shared_storage/shared_storage_worklet_host.h
@@ -206,11 +206,12 @@
   blink::mojom::SharedStorageWorkletService*
   GetAndConnectToSharedStorageWorkletService();
 
-  // Binds a receiver to the `PrivateAggregationManager` and returns the
-  // `PendingRemote`. If there is no `PrivateAggregationManger`, returns an
-  // invalid `PendingRemote`.
-  mojo::PendingRemote<blink::mojom::PrivateAggregationHost>
-  MaybeBindPrivateAggregationHost(
+  // Constructs a `PrivateAggregationOperationDetails` object, including binding
+  // a receiver to the `PrivateAggregationManager` and returning the
+  // `PendingRemote`. If there is no `PrivateAggregationManger`, returns a null
+  // pointer.
+  blink::mojom::PrivateAggregationOperationDetailsPtr
+  MaybeConstructPrivateAggregationOperationDetails(
       const blink::mojom::PrivateAggregationConfigPtr&
           private_aggregation_config);
 
diff --git a/content/public/browser/render_widget_host_view_mac_delegate.h b/content/public/browser/render_widget_host_view_mac_delegate.h
index d419e87..b0f80d7 100644
--- a/content/public/browser/render_widget_host_view_mac_delegate.h
+++ b/content/public/browser/render_widget_host_view_mac_delegate.h
@@ -16,6 +16,18 @@
 struct DidOverscrollParams;
 }
 
+// The options that define the context under which mouse events are accepted.
+// Acceptance under a lower option implies acceptance under any higher option,
+// but not vice versa.
+enum AcceptMouseEventsOption {
+  // Accepts mouse events only when the window is active.
+  kAcceptMouseEventsInActiveWindow = 0,
+  // Accepts mouse events when any window of the application is active.
+  kAcceptMouseEventsInActiveApp = 1,
+  // Accepts mouse events regardless of window or application activation.
+  kAcceptMouseEventsAlways = 2,
+};
+
 // This protocol is used as a delegate for the NSView class used in the
 // hierarchy. There are two ways to extend the view:
 // - Implement the methods listed in the protocol below.
@@ -66,6 +78,10 @@
 - (void)resignFirstResponder;
 
 - (void)windowDidBecomeKey;
+
+// By default, only active window accepts mouse events. The content embedder may
+// override this method to override the default behavior.
+- (AcceptMouseEventsOption)acceptsMouseEventsOption;
 @end
 
 #endif  // CONTENT_PUBLIC_BROWSER_RENDER_WIDGET_HOST_VIEW_MAC_DELEGATE_H_
diff --git a/content/public/test/browser_test_base.cc b/content/public/test/browser_test_base.cc
index c7df8ff..316dd70d 100644
--- a/content/public/test/browser_test_base.cc
+++ b/content/public/test/browser_test_base.cc
@@ -431,11 +431,6 @@
 #if BUILDFLAG(IS_ANDROID)
   // On Android we always use hardware GL.
   use_software_gl = false;
-
-  if (disable_android_native_fence_sync_) {
-    command_line->AppendSwitch(
-        switches::kDisableAndroidNativeFenceSyncForTesting);
-  }
 #endif
 
 #if BUILDFLAG(IS_FUCHSIA)
@@ -1037,12 +1032,6 @@
 void BrowserTestBase::EnablePixelOutput(float force_device_scale_factor) {
   enable_pixel_output_ = true;
   force_device_scale_factor_ = force_device_scale_factor;
-
-#if BUILDFLAG(IS_ANDROID)
-  if (base::SysInfo::GetAndroidHardwareEGL() == "emulation") {
-    disable_android_native_fence_sync_ = true;
-  }
-#endif
 }
 
 void BrowserTestBase::UseSoftwareCompositing() {
diff --git a/content/public/test/browser_test_base.h b/content/public/test/browser_test_base.h
index dbd626e..f55f589 100644
--- a/content/public/test/browser_test_base.h
+++ b/content/public/test/browser_test_base.h
@@ -300,11 +300,6 @@
 
   bool allow_network_access_to_host_resolutions_ = false;
 
-#if BUILDFLAG(IS_ANDROID)
-  // See GL switch `switches::kDisableAndroidNativeFenceSyncForTesting`.
-  bool disable_android_native_fence_sync_ = false;
-#endif
-
   raw_ptr<BrowserMainParts, AcrossTasksDanglingUntriaged> browser_main_parts_ =
       nullptr;
 
diff --git a/content/renderer/media/codec_factory_mojo.cc b/content/renderer/media/codec_factory_mojo.cc
index 2856467e..4de8ffe 100644
--- a/content/renderer/media/codec_factory_mojo.cc
+++ b/content/renderer/media/codec_factory_mojo.cc
@@ -12,6 +12,7 @@
 #include "media/base/overlay_info.h"
 #include "media/mojo/clients/mojo_video_decoder.h"
 #include "media/mojo/mojom/interface_factory.mojom.h"
+#include "media/mojo/mojom/video_decoder.mojom.h"
 #include "media/video/gpu_video_accelerator_factories.h"
 #include "mojo/public/cpp/bindings/pending_remote.h"
 
diff --git a/content/renderer/media/codec_factory_mojo.h b/content/renderer/media/codec_factory_mojo.h
index 4d57832..53e8917 100644
--- a/content/renderer/media/codec_factory_mojo.h
+++ b/content/renderer/media/codec_factory_mojo.h
@@ -15,6 +15,7 @@
 #include "media/base/video_decoder.h"
 #include "media/mojo/mojom/interface_factory.mojom.h"
 #include "media/mojo/mojom/stable/stable_video_decoder.mojom.h"
+#include "media/mojo/mojom/video_decoder.mojom.h"
 #include "media/video/gpu_video_accelerator_factories.h"
 #include "mojo/public/cpp/bindings/remote.h"
 
diff --git a/content/services/auction_worklet/BUILD.gn b/content/services/auction_worklet/BUILD.gn
index c24a5e4..f56a52c 100644
--- a/content/services/auction_worklet/BUILD.gn
+++ b/content/services/auction_worklet/BUILD.gn
@@ -64,6 +64,8 @@
     "lazy_filler.h",
     "private_aggregation_bindings.cc",
     "private_aggregation_bindings.h",
+    "real_time_reporting_bindings.cc",
+    "real_time_reporting_bindings.h",
     "register_ad_beacon_bindings.cc",
     "register_ad_beacon_bindings.h",
     "register_ad_macro_bindings.cc",
diff --git a/content/services/auction_worklet/bidder_worklet.cc b/content/services/auction_worklet/bidder_worklet.cc
index 7573a56a..0f0b0dc 100644
--- a/content/services/auction_worklet/bidder_worklet.cc
+++ b/content/services/auction_worklet/bidder_worklet.cc
@@ -42,6 +42,8 @@
 #include "content/services/auction_worklet/public/mojom/auction_worklet_service.mojom.h"
 #include "content/services/auction_worklet/public/mojom/bidder_worklet.mojom-shared.h"
 #include "content/services/auction_worklet/public/mojom/private_aggregation_request.mojom.h"
+#include "content/services/auction_worklet/public/mojom/real_time_reporting.mojom.h"
+#include "content/services/auction_worklet/real_time_reporting_bindings.h"
 #include "content/services/auction_worklet/register_ad_beacon_bindings.h"
 #include "content/services/auction_worklet/register_ad_macro_bindings.h"
 #include "content/services/auction_worklet/report_bindings.h"
@@ -759,6 +761,7 @@
     base::flat_map<std::string, mojom::PrioritySignalsDoublePtr>
         update_priority_signals_overrides,
     PrivateAggregationRequests pa_requests,
+    RealTimeReportingContributions real_time_contributions,
     mojom::RejectReason reject_reason,
     std::vector<std::string> error_msgs)
     : context_recycler_for_rerun(std::move(context_recycler_for_rerun)),
@@ -770,6 +773,7 @@
       update_priority_signals_overrides(
           std::move(update_priority_signals_overrides)),
       pa_requests(std::move(pa_requests)),
+      real_time_contributions(std::move(real_time_contributions)),
       reject_reason(reject_reason),
       error_msgs(std::move(error_msgs)) {}
 
@@ -1121,7 +1125,8 @@
   if (!result.has_value()) {
     PostErrorBidCallbackToUserThread(
         std::move(callback),
-        /*bidding_latency=*/base::TimeTicks::Now() - bidding_start);
+        /*bidding_latency=*/base::TimeTicks::Now() - bidding_start,
+        PrivateAggregationRequests(), RealTimeReportingContributions());
     return;
   }
 
@@ -1184,6 +1189,7 @@
             /*set_priority=*/std::nullopt,
             /*update_priority_signals_overrides=*/{},
             /*pa_requests=*/{},
+            /*real_time_contributions=*/{},
             /*reject_reason=*/mojom::RejectReason::kNotAvailable,
             /*error_msgs=*/{});
       }
@@ -1214,7 +1220,8 @@
           PostErrorBidCallbackToUserThread(
               std::move(callback),
               /*bidding_latency=*/base::TimeTicks::Now() - bidding_start,
-              std::move(non_kanon_pa_requests));
+              std::move(non_kanon_pa_requests),
+              std::move(result->real_time_contributions));
           return;
         }
         result = std::move(restricted_result);
@@ -1237,6 +1244,7 @@
                      std::move(result->update_priority_signals_overrides),
                      std::move(result->pa_requests),
                      std::move(result->non_kanon_pa_requests),
+                     std::move(result->real_time_contributions),
                      /*bidding_latency=*/base::TimeTicks::Now() - bidding_start,
                      result->reject_reason, std::move(result->error_msgs)));
 }
@@ -1287,6 +1295,7 @@
         /*set_priority=*/std::nullopt,
         /*update_priority_signals_overrides=*/{},
         /*pa_requests=*/{},
+        /*real_time_contributions=*/{},
         /*reject_reason=*/mojom::RejectReason::kNotAvailable,
         std::move(errors_out)));
   }
@@ -1373,6 +1382,7 @@
           /*set_priority=*/std::nullopt,
           /*update_priority_signals_overrides=*/{},
           /*pa_requests=*/{},
+          /*real_time_contributions=*/{},
           /*reject_reason=*/mojom::RejectReason::kNotAvailable,
           std::move(errors_out)));
     }
@@ -1612,8 +1622,26 @@
               "generateBid", args, total_timeout.get(), errors_out)
           .ToLocal(&generate_bid_result);
   TRACE_EVENT_NESTABLE_ASYNC_END0("fledge", "generate_bid", trace_id);
+
+  base::TimeDelta time_duration = base::TimeTicks::Now() - start;
   base::UmaHistogramTimes("Ads.InterestGroup.Auction.GenerateBidTime",
-                          base::TimeTicks::Now() - start);
+                          time_duration);
+
+  std::vector<auction_worklet::mojom::RealTimeReportingContributionPtr>
+      real_time_contributions = context_recycler->real_time_reporting_bindings()
+                                    ->TakeRealTimeReportingContributions();
+
+  // Remove worklet latency contributions if the worklet execution time is
+  // within the threshold.
+  std::erase_if(
+      real_time_contributions,
+      [time_duration](
+          const auction_worklet::mojom::RealTimeReportingContributionPtr&
+              contribution) {
+        return contribution->latency_threshold.has_value() &&
+               time_duration.InMilliseconds() <=
+                   contribution->latency_threshold.value();
+      });
 
   if (got_return_value) {
     IdlConvert::Status status =
@@ -1628,12 +1656,12 @@
   if (!context_recycler->set_bid_bindings()->has_bids()) {
     // If no bid was returned (due to an error or just not choosing to bid), or
     // the method timed out and no intermediate result was given through
-    // `setBid()`, return an error. Keep debug loss reports and Private
-    // Aggregation API requests since `generateBid()` might use them to detect
-    // script timeout or failures. Keep any set priority and set priority
-    // overrides because an interest group may want to update them even when not
-    // bidding. No need to return a ContextRecycler since this will not be
-    // re-run.
+    // `setBid()`, return an error. Keep debug loss reports, Private
+    // Aggregation requests and real time reporting contributions  since
+    // `generateBid()` might use them to detect script timeout or failures. Keep
+    // any set priority and set priority overrides because an interest group may
+    // want to update them even when not bidding. No need to return a
+    // ContextRecycler since this will not be re-run.
     return std::make_optional(SingleGenerateBidResult(
         std::unique_ptr<ContextRecycler>(),
         std::vector<SetBidBindings::BidAndComponentTarget>(),
@@ -1645,6 +1673,7 @@
             ->TakeSetPrioritySignalsOverrides(),
         context_recycler->private_aggregation_bindings()
             ->TakePrivateAggregationRequests(),
+        std::move(real_time_contributions),
         context_recycler->set_bid_bindings()->reject_reason(),
         std::move(errors_out)));
   }
@@ -1663,7 +1692,8 @@
           ->TakeSetPrioritySignalsOverrides(),
       context_recycler->private_aggregation_bindings()
           ->TakePrivateAggregationRequests(),
-      mojom::RejectReason::kNotAvailable, std::move(errors_out)));
+      std::move(real_time_contributions), mojom::RejectReason::kNotAvailable,
+      std::move(errors_out)));
 }
 
 std::unique_ptr<ContextRecycler>
@@ -1699,6 +1729,7 @@
   context_recycler->AddForDebuggingOnlyBindings();
   context_recycler->AddPrivateAggregationBindings(
       permissions_policy_state_->private_aggregation_allowed);
+  context_recycler->AddRealTimeReportingBindings();
 
   if (base::FeatureList::IsEnabled(blink::features::kSharedStorageAPI)) {
     context_recycler->AddSharedStorageBindings(
@@ -1782,6 +1813,7 @@
     GenerateBidCallbackInternal callback,
     base::TimeDelta bidding_latency,
     PrivateAggregationRequests non_kanon_pa_requests,
+    RealTimeReportingContributions real_time_contributions,
     std::vector<std::string> error_msgs) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(v8_sequence_checker_);
   user_thread_->PostTask(
@@ -1796,7 +1828,7 @@
           base::flat_map<std::string, mojom::PrioritySignalsDoublePtr>(),
           /*pa_requests=*/
           PrivateAggregationRequests(), std::move(non_kanon_pa_requests),
-          bidding_latency,
+          std::move(real_time_contributions), bidding_latency,
           /*reject_reason=*/mojom::RejectReason::kNotAvailable,
           std::move(error_msgs)));
 }
@@ -2280,6 +2312,7 @@
         update_priority_signals_overrides,
     PrivateAggregationRequests pa_requests,
     PrivateAggregationRequests non_kanon_pa_requests,
+    RealTimeReportingContributions real_time_contributions,
     base::TimeDelta bidding_latency,
     mojom::RejectReason reject_reason,
     std::vector<std::string> error_msgs) {
@@ -2299,7 +2332,8 @@
       std::move(bids), bidding_signals_data_version, debug_loss_report_url,
       debug_win_report_url, set_priority,
       std::move(update_priority_signals_overrides), std::move(pa_requests),
-      std::move(non_kanon_pa_requests), bidding_latency,
+      std::move(non_kanon_pa_requests), std::move(real_time_contributions),
+      bidding_latency,
       mojom::GenerateBidDependencyLatencies::New(
           /*code_ready_latency=*/NullOptIfZero(task->wait_code),
           /*config_promises_latency=*/NullOptIfZero(task->wait_promises),
diff --git a/content/services/auction_worklet/bidder_worklet.h b/content/services/auction_worklet/bidder_worklet.h
index 468b70e..091cc8a9 100644
--- a/content/services/auction_worklet/bidder_worklet.h
+++ b/content/services/auction_worklet/bidder_worklet.h
@@ -30,7 +30,8 @@
 #include "content/services/auction_worklet/public/mojom/auction_shared_storage_host.mojom.h"
 #include "content/services/auction_worklet/public/mojom/auction_worklet_service.mojom.h"
 #include "content/services/auction_worklet/public/mojom/bidder_worklet.mojom.h"
-#include "content/services/auction_worklet/public/mojom/private_aggregation_request.mojom-forward.h"
+#include "content/services/auction_worklet/public/mojom/private_aggregation_request.mojom.h"
+#include "content/services/auction_worklet/public/mojom/real_time_reporting.mojom.h"
 #include "content/services/auction_worklet/set_bid_bindings.h"
 #include "content/services/auction_worklet/trusted_signals.h"
 #include "content/services/auction_worklet/trusted_signals_request_manager.h"
@@ -83,6 +84,9 @@
   using PrivateAggregationRequests =
       std::vector<auction_worklet::mojom::PrivateAggregationRequestPtr>;
 
+  using RealTimeReportingContributions =
+      std::vector<auction_worklet::mojom::RealTimeReportingContributionPtr>;
+
   // Classification of how trusted signals related to this worklet.
   enum class SignalsOriginRelation {
     kNoTrustedSignals,
@@ -383,6 +387,7 @@
             update_priority_signals_overrides,
         PrivateAggregationRequests pa_requests,
         PrivateAggregationRequests non_kanon_pa_requests,
+        RealTimeReportingContributions real_time_contributions,
         base::TimeDelta bidding_latency,
         mojom::RejectReason reject_reason,
         std::vector<std::string> error_msgs)>;
@@ -408,6 +413,7 @@
           base::flat_map<std::string, mojom::PrioritySignalsDoublePtr>
               update_priority_signals_overrides,
           PrivateAggregationRequests pa_requests,
+          RealTimeReportingContributions real_time_contributions,
           mojom::RejectReason reject_reason,
           std::vector<std::string> error_msgs);
 
@@ -432,6 +438,7 @@
           update_priority_signals_overrides;
       PrivateAggregationRequests pa_requests;
       PrivateAggregationRequests non_kanon_pa_requests;
+      RealTimeReportingContributions real_time_contributions;
       mojom::RejectReason reject_reason;
       std::vector<std::string> error_msgs;
     };
@@ -562,8 +569,8 @@
     void PostErrorBidCallbackToUserThread(
         GenerateBidCallbackInternal callback,
         base::TimeDelta bidding_latency,
-        PrivateAggregationRequests non_kanon_pa_requests =
-            PrivateAggregationRequests(),
+        PrivateAggregationRequests non_kanon_pa_requests,
+        RealTimeReportingContributions real_time_contributions,
         std::vector<std::string> error_msgs = std::vector<std::string>());
 
     static void PostResumeToUserThread(
@@ -685,6 +692,7 @@
           update_priority_signals_overrides,
       PrivateAggregationRequests pa_requests,
       PrivateAggregationRequests non_kanon_pa_requests,
+      RealTimeReportingContributions real_time_contributions,
       base::TimeDelta bidding_latency,
       mojom::RejectReason reject_reason,
       std::vector<std::string> error_msgs);
diff --git a/content/services/auction_worklet/bidder_worklet_unittest.cc b/content/services/auction_worklet/bidder_worklet_unittest.cc
index 017ef62..58f14ab 100644
--- a/content/services/auction_worklet/bidder_worklet_unittest.cc
+++ b/content/services/auction_worklet/bidder_worklet_unittest.cc
@@ -31,6 +31,7 @@
 #include "content/services/auction_worklet/public/mojom/auction_worklet_service.mojom.h"
 #include "content/services/auction_worklet/public/mojom/bidder_worklet.mojom-forward.h"
 #include "content/services/auction_worklet/public/mojom/private_aggregation_request.mojom.h"
+#include "content/services/auction_worklet/public/mojom/real_time_reporting.mojom.h"
 #include "content/services/auction_worklet/worklet_devtools_debug_test_util.h"
 #include "content/services/auction_worklet/worklet_test_util.h"
 #include "content/services/auction_worklet/worklet_v8_debug_test_util.h"
@@ -67,6 +68,8 @@
 
 using base::test::TaskEnvironment;
 using PrivateAggregationRequests = BidderWorklet::PrivateAggregationRequests;
+using RealTimeReportingContributions =
+    BidderWorklet::RealTimeReportingContributions;
 
 // This was produced by running wat2wasm on this:
 // (module
@@ -153,6 +156,7 @@
           update_priority_signals_overrides,
       PrivateAggregationRequests pa_requests,
       PrivateAggregationRequests non_kanon_pa_requests,
+      RealTimeReportingContributions real_time_contributions,
       base::TimeDelta bidding_latency,
       mojom::GenerateBidDependencyLatenciesPtr
           generate_bid_dependency_latencies,
@@ -205,6 +209,7 @@
                update_priority_signals_overrides,
            PrivateAggregationRequests pa_requests,
            PrivateAggregationRequests non_kanon_pa_requests,
+           RealTimeReportingContributions real_time_contributions,
            base::TimeDelta bidding_latency,
            mojom::GenerateBidDependencyLatenciesPtr
                generate_bid_dependency_latencies,
@@ -245,6 +250,7 @@
           update_priority_signals_overrides,
       PrivateAggregationRequests pa_requests,
       PrivateAggregationRequests non_kanon_pa_requests,
+      RealTimeReportingContributions real_time_contributions,
       base::TimeDelta bidding_latency,
       mojom::GenerateBidDependencyLatenciesPtr
           generate_bid_dependency_latencies,
@@ -258,8 +264,9 @@
              debug_win_report_url, set_priority,
              std::move(update_priority_signals_overrides),
              std::move(pa_requests), std::move(non_kanon_pa_requests),
-             bidding_latency, std::move(generate_bid_dependency_latencies),
-             reject_reason, errors);
+             std::move(real_time_contributions), bidding_latency,
+             std::move(generate_bid_dependency_latencies), reject_reason,
+             errors);
   }
 
  private:
@@ -413,14 +420,16 @@
           expected_update_priority_signals_overrides =
               base::flat_map<std::string, std::optional<double>>(),
       PrivateAggregationRequests expected_pa_requests = {},
-      PrivateAggregationRequests expected_non_kanon_pa_requests = {}) {
+      PrivateAggregationRequests expected_non_kanon_pa_requests = {},
+      RealTimeReportingContributions expected_real_time_contributions = {}) {
     RunGenerateBidWithJavascriptExpectingResult(
         CreateGenerateBidScript(raw_return_value), std::move(expected_bids),
         expected_data_version, expected_errors, expected_debug_loss_report_url,
         expected_debug_win_report_url, expected_set_priority,
         expected_update_priority_signals_overrides,
         std::move(expected_pa_requests),
-        std::move(expected_non_kanon_pa_requests));
+        std::move(expected_non_kanon_pa_requests),
+        std::move(expected_real_time_contributions));
   }
 
   // Configures `url_loader_factory_` to return a script with the specified
@@ -438,7 +447,8 @@
           expected_update_priority_signals_overrides =
               base::flat_map<std::string, std::optional<double>>(),
       PrivateAggregationRequests expected_pa_requests = {},
-      PrivateAggregationRequests expected_non_kanon_pa_requests = {}) {
+      PrivateAggregationRequests expected_non_kanon_pa_requests = {},
+      RealTimeReportingContributions expected_real_time_contributions = {}) {
     SCOPED_TRACE(javascript);
     AddJavascriptResponse(&url_loader_factory_, interest_group_bidding_url_,
                           javascript);
@@ -447,7 +457,8 @@
         expected_debug_loss_report_url, expected_debug_win_report_url,
         expected_set_priority, expected_update_priority_signals_overrides,
         std::move(expected_pa_requests),
-        std::move(expected_non_kanon_pa_requests));
+        std::move(expected_non_kanon_pa_requests),
+        std::move(expected_real_time_contributions));
   }
 
   // Loads and runs a generateBid() script, expecting the provided result to
@@ -467,7 +478,8 @@
           expected_update_priority_signals_overrides =
               base::flat_map<std::string, std::optional<double>>(),
       PrivateAggregationRequests expected_pa_requests = {},
-      PrivateAggregationRequests expected_non_kanon_pa_requests = {}) {
+      PrivateAggregationRequests expected_non_kanon_pa_requests = {},
+      RealTimeReportingContributions expected_real_time_contributions = {}) {
     std::vector<mojom::BidderWorkletBidPtr> expected_bids;
     if (absl::holds_alternative<mojom::BidderWorkletBidPtr>(
             expected_bid_or_bids)) {
@@ -511,6 +523,7 @@
     EXPECT_EQ(expected_debug_win_report_url, bid_debug_win_report_url_);
     EXPECT_EQ(expected_pa_requests, pa_requests_);
     EXPECT_EQ(expected_non_kanon_pa_requests, non_kanon_pa_requests_);
+    EXPECT_EQ(expected_real_time_contributions, real_time_contributions_);
     EXPECT_EQ(expected_set_priority, set_priority_);
     EXPECT_EQ(expected_update_priority_signals_overrides,
               update_priority_signals_overrides_);
@@ -834,6 +847,7 @@
           update_priority_signals_overrides,
       PrivateAggregationRequests pa_requests,
       PrivateAggregationRequests non_kanon_pa_requests,
+      RealTimeReportingContributions real_time_contributions,
       base::TimeDelta bidding_latency,
       mojom::GenerateBidDependencyLatenciesPtr
           generate_bid_dependency_latencies,
@@ -856,6 +870,7 @@
 
     pa_requests_ = std::move(pa_requests);
     non_kanon_pa_requests_ = std::move(non_kanon_pa_requests);
+    real_time_contributions_ = std::move(real_time_contributions);
     generate_bid_dependency_latencies_ =
         std::move(generate_bid_dependency_latencies);
     reject_reason_ = reject_reason;
@@ -1016,6 +1031,7 @@
       update_priority_signals_overrides_;
   PrivateAggregationRequests pa_requests_;
   PrivateAggregationRequests non_kanon_pa_requests_;
+  RealTimeReportingContributions real_time_contributions_;
   mojom::GenerateBidDependencyLatenciesPtr generate_bid_dependency_latencies_;
   mojom::RejectReason reject_reason_ = mojom::RejectReason::kNotAvailable;
   std::vector<std::string> bid_errors_;
@@ -4350,6 +4366,7 @@
                       update_priority_signals_overrides,
                   PrivateAggregationRequests pa_requests,
                   PrivateAggregationRequests non_kanon_pa_requests,
+                  RealTimeReportingContributions real_time_contributions,
                   base::TimeDelta bidding_latency,
                   mojom::GenerateBidDependencyLatenciesPtr
                       generate_bid_dependency_latencies,
@@ -4469,6 +4486,7 @@
                     update_priority_signals_overrides,
                 PrivateAggregationRequests pa_requests,
                 PrivateAggregationRequests non_kanon_pa_requests,
+                RealTimeReportingContributions real_time_contributions,
                 base::TimeDelta bidding_latency,
                 mojom::GenerateBidDependencyLatenciesPtr
                     generate_bid_dependency_latencies,
@@ -4597,6 +4615,7 @@
                     update_priority_signals_overrides,
                 PrivateAggregationRequests pa_requests,
                 PrivateAggregationRequests non_kanon_pa_requests,
+                RealTimeReportingContributions real_time_contributions,
                 base::TimeDelta bidding_latency,
                 mojom::GenerateBidDependencyLatenciesPtr
                     generate_bid_dependency_latencies,
@@ -4731,6 +4750,7 @@
                     update_priority_signals_overrides,
                 PrivateAggregationRequests pa_requests,
                 PrivateAggregationRequests non_kanon_pa_requests,
+                RealTimeReportingContributions real_time_contributions,
                 base::TimeDelta bidding_latency,
                 mojom::GenerateBidDependencyLatenciesPtr
                     generate_bid_dependency_latencies,
@@ -4844,6 +4864,7 @@
                     update_priority_signals_overrides,
                 PrivateAggregationRequests pa_requests,
                 PrivateAggregationRequests non_kanon_pa_requests,
+                RealTimeReportingContributions real_time_contributions,
                 base::TimeDelta bidding_latency,
                 mojom::GenerateBidDependencyLatenciesPtr
                     generate_bid_dependency_latencies,
@@ -9644,12 +9665,11 @@
 class BidderWorkletPrivateAggregationEnabledTest : public BidderWorkletTest {
  public:
   BidderWorkletPrivateAggregationEnabledTest() {
-    scoped_feature_list_.InitAndEnableFeature(
-        blink::features::kPrivateAggregationApi);
+    feature_list_.InitAndEnableFeature(blink::features::kPrivateAggregationApi);
   }
 
  private:
-  base::test::ScopedFeatureList scoped_feature_list_;
+  base::test::ScopedFeatureList feature_list_;
 };
 
 TEST_F(BidderWorkletPrivateAggregationEnabledTest, GenerateBid) {
@@ -9676,6 +9696,7 @@
           mojom::AggregatableReportForEventContribution::New(
               /*bucket=*/mojom::ForEventSignalBucket::NewIdBucket(234),
               /*value=*/mojom::ForEventSignalValue::NewIntValue(56),
+              /*filtering_id=*/std::nullopt,
               /*event_type=*/"reserved.win")),
       blink::mojom::AggregationServiceMode::kDefault,
       blink::mojom::DebugModeDetails::New());
@@ -9686,6 +9707,7 @@
                   absl::MakeInt128(/*high=*/1,
                                    /*low=*/0)),
               /*value=*/mojom::ForEventSignalValue::NewIntValue(2),
+              /*filtering_id=*/std::nullopt,
               /*event_type=*/"reserved.win")),
       blink::mojom::AggregationServiceMode::kDefault,
       blink::mojom::DebugModeDetails::New());
@@ -10010,6 +10032,56 @@
         /*expected_pa_requests=*/{});
   }
 
+  // Filtering IDs are specified
+  {
+    base::test::ScopedFeatureList scoped_feature_list{
+        blink::features::kPrivateAggregationApiFilteringIds};
+
+    PrivateAggregationRequests expected_pa_requests;
+    expected_pa_requests.push_back(mojom::PrivateAggregationRequest::New(
+        mojom::AggregatableReportContribution::NewHistogramContribution(
+            blink::mojom::AggregatableReportHistogramContribution::New(
+                /*bucket=*/123,
+                /*value=*/45,
+                /*filtering_id=*/0)),
+        blink::mojom::AggregationServiceMode::kDefault,
+        blink::mojom::DebugModeDetails::New()));
+    expected_pa_requests.push_back(mojom::PrivateAggregationRequest::New(
+        mojom::AggregatableReportContribution::NewForEventContribution(
+            mojom::AggregatableReportForEventContribution::New(
+                /*bucket=*/mojom::ForEventSignalBucket::NewIdBucket(234),
+                /*value=*/mojom::ForEventSignalValue::NewIntValue(56),
+                /*filtering_id=*/255,
+                /*event_type=*/"reserved.win")),
+        blink::mojom::AggregationServiceMode::kDefault,
+        blink::mojom::DebugModeDetails::New()));
+
+    RunGenerateBidWithJavascriptExpectingResult(
+        CreateGenerateBidScript(
+            R"({ad: "ad", bid:1, render:"https://response.test/" })",
+            /*extra_code=*/R"(
+            privateAggregation.contributeToHistogram(
+                {bucket: 123n, value: 45, filteringId: 0n});
+            privateAggregation.contributeToHistogramOnEvent(
+                "reserved.win", {bucket: 234n, value: 56, filteringId: 255n});
+          )"),
+        /*expected_bid=*/
+        mojom::BidderWorkletBid::New(
+            auction_worklet::mojom::BidRole::kUnenforcedKAnon, "\"ad\"", 1,
+            /*bid_currency=*/std::nullopt,
+            /*ad_cost=*/std::nullopt,
+            blink::AdDescriptor(GURL("https://response.test/")),
+            /*ad_component_descriptors=*/std::nullopt,
+            /*modeling_signals=*/std::nullopt, base::TimeDelta()),
+        /*expected_data_version=*/std::nullopt,
+        /*expected_errors=*/{},
+        /*expected_debug_loss_report_url=*/std::nullopt,
+        /*expected_debug_win_report_url=*/std::nullopt,
+        /*expected_set_priority=*/std::nullopt,
+        /*expected_update_priority_signals_overrides=*/{},
+        std::move(expected_pa_requests));
+  }
+
   // Requests where reject reason is specified and k-anonymity enforcement is
   // active.
   kanon_mode_ = mojom::KAnonymityBidMode::kEnforce;
@@ -10030,6 +10102,7 @@
                             /*baseValue=*/mojom::BaseValue::kBidRejectReason,
                             /*scale=*/1.0,
                             /*offset=*/0)),
+                    /*filtering_id=*/std::nullopt,
                     /*event_type=*/"reserved.loss")),
             blink::mojom::AggregationServiceMode::kDefault,
             blink::mojom::DebugModeDetails::New()));
@@ -10095,6 +10168,7 @@
           mojom::AggregatableReportForEventContribution::New(
               /*bucket=*/mojom::ForEventSignalBucket::NewIdBucket(234),
               /*value=*/mojom::ForEventSignalValue::NewIntValue(56),
+              /*filtering_id=*/std::nullopt,
               /*event_type=*/"reserved.win")),
       blink::mojom::AggregationServiceMode::kDefault,
       blink::mojom::DebugModeDetails::New());
@@ -10105,6 +10179,7 @@
                   absl::MakeInt128(/*high=*/1,
                                    /*low=*/0)),
               /*value=*/mojom::ForEventSignalValue::NewIntValue(2),
+              /*filtering_id=*/std::nullopt,
               /*event_type=*/"reserved.win")),
       blink::mojom::AggregationServiceMode::kDefault,
       blink::mojom::DebugModeDetails::New());
@@ -10281,17 +10356,54 @@
         std::move(expected_pa_requests),
         /*expected_errors=*/{});
   }
+
+  // Filtering IDs specified
+  {
+    base::test::ScopedFeatureList scoped_feature_list{
+        blink::features::kPrivateAggregationApiFilteringIds};
+
+    PrivateAggregationRequests expected_pa_requests;
+    expected_pa_requests.push_back(mojom::PrivateAggregationRequest::New(
+        mojom::AggregatableReportContribution::NewHistogramContribution(
+            blink::mojom::AggregatableReportHistogramContribution::New(
+                /*bucket=*/123,
+                /*value=*/45,
+                /*filtering_id=*/0)),
+        blink::mojom::AggregationServiceMode::kDefault,
+        blink::mojom::DebugModeDetails::New()));
+    expected_pa_requests.push_back(mojom::PrivateAggregationRequest::New(
+        mojom::AggregatableReportContribution::NewForEventContribution(
+            mojom::AggregatableReportForEventContribution::New(
+                /*bucket=*/mojom::ForEventSignalBucket::NewIdBucket(234),
+                /*value=*/mojom::ForEventSignalValue::NewIntValue(56),
+                /*filtering_id=*/255,
+                /*event_type=*/"reserved.win")),
+        blink::mojom::AggregationServiceMode::kDefault,
+        blink::mojom::DebugModeDetails::New()));
+
+    RunReportWinWithFunctionBodyExpectingResult(
+        R"(
+          privateAggregation.contributeToHistogram(
+              {bucket: 123n, value: 45, filteringId: 0n});
+          privateAggregation.contributeToHistogramOnEvent(
+              "reserved.win", {bucket: 234n, value: 56, filteringId: 255n});
+        )",
+        /*expected_report_url=*/std::nullopt,
+        /*expected_ad_beacon_map=*/{}, /*expected_ad_macro_map=*/{},
+        std::move(expected_pa_requests),
+        /*expected_errors=*/{});
+  }
 }
 
 class BidderWorkletPrivateAggregationDisabledTest : public BidderWorkletTest {
  public:
   BidderWorkletPrivateAggregationDisabledTest() {
-    scoped_feature_list_.InitAndDisableFeature(
+    feature_list_.InitAndDisableFeature(
         blink::features::kPrivateAggregationApi);
   }
 
  private:
-  base::test::ScopedFeatureList scoped_feature_list_;
+  base::test::ScopedFeatureList feature_list_;
 };
 
 TEST_F(BidderWorkletPrivateAggregationDisabledTest, GenerateBid) {
@@ -11725,14 +11837,14 @@
 class BidderWorkletAdMacroReportingEnabledTest : public BidderWorkletTest {
  public:
   BidderWorkletAdMacroReportingEnabledTest() {
-    scoped_feature_list_.InitWithFeatures(
+    feature_list_.InitWithFeatures(
         /*enabled_features=*/{blink::features::kAdAuctionReportingWithMacroApi,
                               blink::features::kFencedFramesM120FeaturesPart1},
         /*disabled_features=*/{});
   }
 
  private:
-  base::test::ScopedFeatureList scoped_feature_list_;
+  base::test::ScopedFeatureList feature_list_;
 };
 
 TEST_F(BidderWorkletAdMacroReportingEnabledTest, ReportWinRegisterAdMacro) {
@@ -11856,12 +11968,12 @@
 class BidderWorkletSampleDebugReportsDisabledTest : public BidderWorkletTest {
  public:
   BidderWorkletSampleDebugReportsDisabledTest() {
-    scoped_feature_list_.InitAndDisableFeature(
+    feature_list_.InitAndDisableFeature(
         blink::features::kFledgeSampleDebugReports);
   }
 
  private:
-  base::test::ScopedFeatureList scoped_feature_list_;
+  base::test::ScopedFeatureList feature_list_;
 };
 
 TEST_F(BidderWorkletSampleDebugReportsDisabledTest,
@@ -11968,5 +12080,157 @@
                                           /*expected_data_version=*/5);
 }
 
+class BidderWorkletRealTimeReportingEnabledTest : public BidderWorkletTest {
+ public:
+  BidderWorkletRealTimeReportingEnabledTest() {
+    feature_list_.InitAndEnableFeature(
+        blink::features::kFledgeRealTimeReporting);
+  }
+
+ private:
+  base::test::ScopedFeatureList feature_list_;
+};
+
+TEST_F(BidderWorkletRealTimeReportingEnabledTest, RealTimeReporting) {
+  auction_worklet::mojom::RealTimeReportingContribution expected_histogram(
+      /*bucket=*/100, /*priority_weight=*/0.5,
+      /*latency_threshold=*/std::nullopt);
+
+  // Worklet latency contribution is tested in ScriptTimeout.
+  // Cannot reliably test worklet latency contribution here since the script
+  // takes 0ms to run some times, which is not higher than the smallest allowed
+  // latency threshold (0ms), in which case the contribution will be dropped.
+  constexpr char kExtraCode[] = R"(
+realTimeReporting.contributeToRealTimeHistogram(100, {priorityWeight: 0.5})
+)";
+
+  RealTimeReportingContributions expected_real_time_contributions;
+  expected_real_time_contributions.push_back(expected_histogram.Clone());
+
+  RunGenerateBidWithJavascriptExpectingResult(
+      // TODO: rename the function, if really need to use it, to
+      //  CreateBasicGenerateBidScriptWithExtraReport.
+      CreateBasicGenerateBidScriptWithDebuggingReport(kExtraCode),
+      mojom::BidderWorkletBid::New(
+          auction_worklet::mojom::BidRole::kUnenforcedKAnon, "[\"ad\"]", 1,
+          /*bid_currency=*/std::nullopt,
+          /*ad_cost=*/std::nullopt,
+          blink::AdDescriptor(GURL("https://response.test/")),
+          /*ad_component_descriptors=*/std::nullopt,
+          /*modeling_signals=*/std::nullopt, base::TimeDelta()),
+      /*expected_data_version=*/std::nullopt,
+      /*expected_errors=*/{}, std::nullopt, std::nullopt,
+      /*expected_set_priority=*/std::nullopt,
+      /*expected_update_priority_signals_overrides=*/{},
+      /*expected_pa_requests=*/{},
+      /*expected_non_kanon_pa_requests=*/{},
+      std::move(expected_real_time_contributions));
+}
+
+// Real time reporting contributions are allowed when an IG does not bid.
+TEST_F(BidderWorkletRealTimeReportingEnabledTest, NoBid) {
+  auction_worklet::mojom::RealTimeReportingContribution expected_histogram(
+      /*bucket=*/100, /*priority_weight=*/0.5,
+      /*latency_threshold=*/std::nullopt);
+
+  constexpr char kExtraCode[] = R"(
+realTimeReporting.contributeToRealTimeHistogram(100, {priorityWeight: 0.5});
+)";
+
+  RealTimeReportingContributions expected_real_time_contributions;
+  expected_real_time_contributions.push_back(expected_histogram.Clone());
+
+  RunGenerateBidWithJavascriptExpectingResult(
+      CreateGenerateBidScript(R"({bid: 0})", kExtraCode),
+      /*expected_bids=*/mojom::BidderWorkletBidPtr(),
+      /*expected_data_version=*/std::nullopt,
+      /*expected_errors=*/{}, std::nullopt, std::nullopt,
+      /*expected_set_priority=*/std::nullopt,
+      /*expected_update_priority_signals_overrides=*/{},
+      /*expected_pa_requests=*/{},
+      /*expected_non_kanon_pa_requests=*/{},
+      std::move(expected_real_time_contributions));
+}
+
+// Real time reporting contributions registered before script timeout are kept.
+TEST_F(BidderWorkletRealTimeReportingEnabledTest, ScriptTimeout) {
+  // Set timeout to a small number, and use a while loop in the script to let it
+  // timeout. Then the execution time would be higher than the latency threshold
+  // of 1ms thus the latency contribution will be kept.
+  per_buyer_timeout_ = base::Milliseconds(3);
+
+  auction_worklet::mojom::RealTimeReportingContribution expected_histogram(
+      /*bucket=*/100, /*priority_weight=*/0.5,
+      /*latency_threshold=*/std::nullopt);
+  auction_worklet::mojom::RealTimeReportingContribution
+      expected_latency_histogram(
+          /*bucket=*/200, /*priority_weight=*/2, /*latency_threshold=*/1);
+  constexpr char kExtraCode[] = R"(
+realTimeReporting.contributeToRealTimeHistogram(100, {priorityWeight: 0.5});
+realTimeReporting.contributeOnWorkletLatency(
+    200, {priorityWeight: 2, latencyThreshold: 1});
+while (1);
+)";
+
+  RealTimeReportingContributions expected_real_time_contributions;
+  expected_real_time_contributions.push_back(expected_histogram.Clone());
+  expected_real_time_contributions.push_back(
+      expected_latency_histogram.Clone());
+
+  RunGenerateBidWithJavascriptExpectingResult(
+      // TODO(qingxinwu): rename the function, if really need to use it, to
+      //  CreateBasicGenerateBidScriptWithExtraReport.
+      CreateBasicGenerateBidScriptWithDebuggingReport(kExtraCode),
+      /*expected_bids=*/mojom::BidderWorkletBidPtr(),
+      /*expected_data_version=*/std::nullopt,
+      /*expected_errors=*/
+      {"https://url.test/ execution of `generateBid` timed out."}, std::nullopt,
+      std::nullopt,
+      /*expected_set_priority=*/std::nullopt,
+      /*expected_update_priority_signals_overrides=*/{},
+      /*expected_pa_requests=*/{},
+      /*expected_non_kanon_pa_requests=*/{},
+      std::move(expected_real_time_contributions));
+}
+
+// contributeOnWorkletLatency's is dropped when the script's latency does not
+// exceed the threshold.
+TEST_F(BidderWorkletRealTimeReportingEnabledTest,
+       NotExceedingLatencyThreshold) {
+  auction_worklet::mojom::RealTimeReportingContribution expected_histogram(
+      /*bucket=*/100, /*priority_weight=*/0.5,
+      /*latency_threshold=*/std::nullopt);
+  constexpr char kExtraCode[] = R"(
+realTimeReporting.contributeToRealTimeHistogram(100, {priorityWeight: 0.5});
+realTimeReporting.contributeOnWorkletLatency(
+    200, {priorityWeight: 2, latencyThreshold: 10000000})
+)";
+
+  // Only contributeToRealTimeHistogram's contribution is kept.
+  // contributeOnWorkletLatency's is filtered out since the script's latency
+  // didn't exceed the threshold.
+  RealTimeReportingContributions expected_real_time_contributions;
+  expected_real_time_contributions.push_back(expected_histogram.Clone());
+
+  RunGenerateBidWithJavascriptExpectingResult(
+      // TODO: rename the function, if really need to use it, to
+      //  CreateBasicGenerateBidScriptWithExtraReport.
+      CreateBasicGenerateBidScriptWithDebuggingReport(kExtraCode),
+      mojom::BidderWorkletBid::New(
+          auction_worklet::mojom::BidRole::kUnenforcedKAnon, "[\"ad\"]", 1,
+          /*bid_currency=*/std::nullopt,
+          /*ad_cost=*/std::nullopt,
+          blink::AdDescriptor(GURL("https://response.test/")),
+          /*ad_component_descriptors=*/std::nullopt,
+          /*modeling_signals=*/std::nullopt, base::TimeDelta()),
+      /*expected_data_version=*/std::nullopt,
+      /*expected_errors=*/{}, std::nullopt, std::nullopt,
+      /*expected_set_priority=*/std::nullopt,
+      /*expected_update_priority_signals_overrides=*/{},
+      /*expected_pa_requests=*/{},
+      /*expected_non_kanon_pa_requests=*/{},
+      std::move(expected_real_time_contributions));
+}
+
 }  // namespace
 }  // namespace auction_worklet
diff --git a/content/services/auction_worklet/context_recycler.cc b/content/services/auction_worklet/context_recycler.cc
index 519f997..e45ca12 100644
--- a/content/services/auction_worklet/context_recycler.cc
+++ b/content/services/auction_worklet/context_recycler.cc
@@ -12,6 +12,7 @@
 #include "content/services/auction_worklet/bidder_lazy_filler.h"
 #include "content/services/auction_worklet/for_debugging_only_bindings.h"
 #include "content/services/auction_worklet/private_aggregation_bindings.h"
+#include "content/services/auction_worklet/real_time_reporting_bindings.h"
 #include "content/services/auction_worklet/register_ad_beacon_bindings.h"
 #include "content/services/auction_worklet/register_ad_macro_bindings.h"
 #include "content/services/auction_worklet/report_bindings.h"
@@ -52,14 +53,11 @@
   AddBindings(private_aggregation_bindings_.get());
 }
 
-void ContextRecycler::AddSharedStorageBindings(
-    mojom::AuctionSharedStorageHost* shared_storage_host,
-    bool shared_storage_permissions_policy_allowed) {
-  DCHECK(!shared_storage_bindings_);
-  shared_storage_bindings_ = std::make_unique<SharedStorageBindings>(
-      v8_helper_, shared_storage_host,
-      shared_storage_permissions_policy_allowed);
-  AddBindings(shared_storage_bindings_.get());
+void ContextRecycler::AddRealTimeReportingBindings() {
+  DCHECK(!real_time_reporting_bindings_);
+  real_time_reporting_bindings_ =
+      std::make_unique<RealTimeReportingBindings>(v8_helper_);
+  AddBindings(real_time_reporting_bindings_.get());
 }
 
 void ContextRecycler::AddRegisterAdBeaconBindings() {
@@ -95,6 +93,16 @@
   AddBindings(set_priority_bindings_.get());
 }
 
+void ContextRecycler::AddSharedStorageBindings(
+    mojom::AuctionSharedStorageHost* shared_storage_host,
+    bool shared_storage_permissions_policy_allowed) {
+  DCHECK(!shared_storage_bindings_);
+  shared_storage_bindings_ = std::make_unique<SharedStorageBindings>(
+      v8_helper_, shared_storage_host,
+      shared_storage_permissions_policy_allowed);
+  AddBindings(shared_storage_bindings_.get());
+}
+
 void ContextRecycler::AddInterestGroupLazyFiller() {
   DCHECK(!interest_group_lazy_filler_);
   interest_group_lazy_filler_ =
diff --git a/content/services/auction_worklet/context_recycler.h b/content/services/auction_worklet/context_recycler.h
index afae169..1b3515a 100644
--- a/content/services/auction_worklet/context_recycler.h
+++ b/content/services/auction_worklet/context_recycler.h
@@ -25,13 +25,14 @@
 class AuctionV8Logger;
 class ForDebuggingOnlyBindings;
 class PrivateAggregationBindings;
-class SharedStorageBindings;
+class RealTimeReportingBindings;
 class RegisterAdBeaconBindings;
 class RegisterAdMacroBindings;
 class ReportBindings;
 class SetBidBindings;
 class SetPriorityBindings;
 class SetPrioritySignalsOverrideBindings;
+class SharedStorageBindings;
 class AuctionConfigLazyFiller;
 class BiddingBrowserSignalsLazyFiller;
 class InterestGroupLazyFiller;
@@ -97,11 +98,9 @@
     return private_aggregation_bindings_.get();
   }
 
-  void AddSharedStorageBindings(
-      mojom::AuctionSharedStorageHost* shared_storage_host,
-      bool shared_storage_permissions_policy_allowed);
-  SharedStorageBindings* shared_storage_bindings() {
-    return shared_storage_bindings_.get();
+  void AddRealTimeReportingBindings();
+  RealTimeReportingBindings* real_time_reporting_bindings() {
+    return real_time_reporting_bindings_.get();
   }
 
   void AddRegisterAdBeaconBindings();
@@ -130,6 +129,13 @@
     return set_priority_signals_override_bindings_.get();
   }
 
+  void AddSharedStorageBindings(
+      mojom::AuctionSharedStorageHost* shared_storage_host,
+      bool shared_storage_permissions_policy_allowed);
+  SharedStorageBindings* shared_storage_bindings() {
+    return shared_storage_bindings_.get();
+  }
+
   void AddInterestGroupLazyFiller();
   InterestGroupLazyFiller* interest_group_lazy_filler() {
     return interest_group_lazy_filler_.get();
@@ -173,7 +179,7 @@
 
   std::unique_ptr<ForDebuggingOnlyBindings> for_debugging_only_bindings_;
   std::unique_ptr<PrivateAggregationBindings> private_aggregation_bindings_;
-  std::unique_ptr<SharedStorageBindings> shared_storage_bindings_;
+  std::unique_ptr<RealTimeReportingBindings> real_time_reporting_bindings_;
   std::unique_ptr<RegisterAdBeaconBindings> register_ad_beacon_bindings_;
   std::unique_ptr<RegisterAdMacroBindings> register_ad_macro_bindings_;
   std::unique_ptr<ReportBindings> report_bindings_;
@@ -181,6 +187,7 @@
   std::unique_ptr<SetPriorityBindings> set_priority_bindings_;
   std::unique_ptr<SetPrioritySignalsOverrideBindings>
       set_priority_signals_override_bindings_;
+  std::unique_ptr<SharedStorageBindings> shared_storage_bindings_;
 
   // everything here is owned by one of the unique_ptr's above.
   std::vector<raw_ptr<Bindings, VectorExperimental>> bindings_list_;
diff --git a/content/services/auction_worklet/context_recycler_unittest.cc b/content/services/auction_worklet/context_recycler_unittest.cc
index 06b94ea..0fc5e83 100644
--- a/content/services/auction_worklet/context_recycler_unittest.cc
+++ b/content/services/auction_worklet/context_recycler_unittest.cc
@@ -21,6 +21,8 @@
 #include "content/services/auction_worklet/private_aggregation_bindings.h"
 #include "content/services/auction_worklet/public/mojom/bidder_worklet.mojom.h"
 #include "content/services/auction_worklet/public/mojom/private_aggregation_request.mojom.h"
+#include "content/services/auction_worklet/public/mojom/real_time_reporting.mojom.h"
+#include "content/services/auction_worklet/real_time_reporting_bindings.h"
 #include "content/services/auction_worklet/register_ad_beacon_bindings.h"
 #include "content/services/auction_worklet/register_ad_macro_bindings.h"
 #include "content/services/auction_worklet/report_bindings.h"
@@ -46,6 +48,17 @@
 
 namespace auction_worklet {
 
+// Helper to avoid excess boilerplate.
+template <typename... Ts>
+auto ElementsAreRequests(Ts&... requests) {
+  static_assert(
+      std::conjunction<std::is_same<
+          std::remove_const_t<Ts>,
+          auction_worklet::mojom::PrivateAggregationRequestPtr>...>::value);
+  // Need to use `std::ref` as `mojo::StructPtr`s are move-only.
+  return testing::UnorderedElementsAre(testing::Eq(std::ref(requests))...);
+}
+
 class ContextRecyclerTest : public testing::Test {
  public:
   ContextRecyclerTest()
@@ -2462,8 +2475,12 @@
     : public ContextRecyclerTest {
  public:
   ContextRecyclerPrivateAggregationEnabledTest() {
-    scoped_feature_list_.InitAndEnableFeature(
-        blink::features::kPrivateAggregationApi);
+    scoped_feature_list_.InitWithFeaturesAndParameters(
+        /*enabled_features=*/{{blink::features::kPrivateAggregationApi, {}},
+                              {blink::features::
+                                   kPrivateAggregationApiFilteringIds,
+                               {}}},
+        /*disabled_features=*/{});
   }
 
   // Wraps a debug_key into the appropriate dictionary. Templated to allow both
@@ -2483,9 +2500,10 @@
           pa_requests,
       absl::uint128 bucket,
       int value,
-      std::optional<blink::mojom::DebugKeyPtr> debug_key = std::nullopt) {
+      std::optional<blink::mojom::DebugKeyPtr> debug_key = std::nullopt,
+      std::optional<uint64_t> filtering_id = std::nullopt) {
     blink::mojom::AggregatableReportHistogramContribution expected_contribution(
-        bucket, value, /*filtering_id=*/std::nullopt);
+        bucket, value, filtering_id);
 
     blink::mojom::DebugModeDetailsPtr debug_mode_details;
     if (debug_key.has_value()) {
@@ -2828,6 +2846,111 @@
                     .empty());
   }
 
+  // Basic filtering ID
+  {
+    ContextRecyclerScope scope(context_recycler);
+    std::vector<std::string> error_msgs;
+
+    gin::Dictionary dict = gin::Dictionary::CreateEmpty(helper_->isolate());
+    dict.Set("bucket", std::string("123"));
+    dict.Set("value", 45);
+    dict.Set("filteringId", std::string("0"));
+
+    Run(scope, script, "test", error_msgs,
+        gin::ConvertToV8(helper_->isolate(), dict));
+    EXPECT_THAT(error_msgs, ElementsAre());
+
+    ExpectOneHistogramRequestEqualTo(
+        context_recycler.private_aggregation_bindings()
+            ->TakePrivateAggregationRequests(),
+        /*bucket=*/123, /*value=*/45, /*debug_key=*/std::nullopt,
+        /*filtering_id=*/0);
+  }
+
+  // Max filtering ID
+  {
+    ContextRecyclerScope scope(context_recycler);
+    std::vector<std::string> error_msgs;
+
+    gin::Dictionary dict = gin::Dictionary::CreateEmpty(helper_->isolate());
+    dict.Set("bucket", std::string("123"));
+    dict.Set("value", 45);
+    dict.Set("filteringId", std::string("255"));
+
+    Run(scope, script, "test", error_msgs,
+        gin::ConvertToV8(helper_->isolate(), dict));
+    EXPECT_THAT(error_msgs, ElementsAre());
+
+    ExpectOneHistogramRequestEqualTo(
+        context_recycler.private_aggregation_bindings()
+            ->TakePrivateAggregationRequests(),
+        /*bucket=*/123, /*value=*/45, /*debug_key=*/std::nullopt,
+        /*filtering_id=*/255);
+  }
+
+  // Filtering ID negative
+  {
+    ContextRecyclerScope scope(context_recycler);
+    std::vector<std::string> error_msgs;
+
+    gin::Dictionary dict = gin::Dictionary::CreateEmpty(helper_->isolate());
+    dict.Set("bucket", std::string("123"));
+    dict.Set("value", 45);
+    dict.Set("filteringId", std::string("-1"));
+
+    Run(scope, script, "test", error_msgs,
+        gin::ConvertToV8(helper_->isolate(), dict));
+    EXPECT_THAT(error_msgs,
+                ElementsAre("https://example.test/script.js:8 Uncaught "
+                            "TypeError: BigInt must be non-negative."));
+
+    EXPECT_TRUE(context_recycler.private_aggregation_bindings()
+                    ->TakePrivateAggregationRequests()
+                    .empty());
+  }
+
+  // Filtering ID too big
+  {
+    ContextRecyclerScope scope(context_recycler);
+    std::vector<std::string> error_msgs;
+
+    gin::Dictionary dict = gin::Dictionary::CreateEmpty(helper_->isolate());
+    dict.Set("bucket", std::string("123"));
+    dict.Set("value", 45);
+    dict.Set("filteringId", std::string("256"));
+
+    Run(scope, script, "test", error_msgs,
+        gin::ConvertToV8(helper_->isolate(), dict));
+    EXPECT_THAT(error_msgs,
+                ElementsAre("https://example.test/script.js:8 Uncaught "
+                            "TypeError: Filtering ID is too large."));
+
+    EXPECT_TRUE(context_recycler.private_aggregation_bindings()
+                    ->TakePrivateAggregationRequests()
+                    .empty());
+  }
+
+  // Filtering ID not a BigInt
+  {
+    ContextRecyclerScope scope(context_recycler);
+    std::vector<std::string> error_msgs;
+
+    gin::Dictionary dict = gin::Dictionary::CreateEmpty(helper_->isolate());
+    dict.Set("bucket", std::string("123"));
+    dict.Set("value", 45);
+    dict.Set("filteringId", 1);
+
+    Run(scope, script, "test", error_msgs,
+        gin::ConvertToV8(helper_->isolate(), dict));
+    EXPECT_THAT(error_msgs,
+                ElementsAre("https://example.test/script.js:8 Uncaught "
+                            "TypeError: Cannot convert 1 to a BigInt."));
+
+    EXPECT_TRUE(context_recycler.private_aggregation_bindings()
+                    ->TakePrivateAggregationRequests()
+                    .empty());
+  }
+
   // API not called
   {
     ContextRecyclerScope scope(context_recycler);
@@ -3143,20 +3266,25 @@
     : public ContextRecyclerTest {
  public:
   ContextRecyclerPrivateAggregationExtensionsEnabledTest() {
-    scoped_feature_list_.InitAndEnableFeatureWithParameters(
-        blink::features::kPrivateAggregationApi,
-        {{"fledge_extensions_enabled", "true"}});
+    scoped_feature_list_.InitWithFeaturesAndParameters(
+        /*enabled_features=*/{{blink::features::kPrivateAggregationApi,
+                               {{"fledge_extensions_enabled", "true"}}},
+                              {blink::features::
+                                   kPrivateAggregationApiFilteringIds,
+                               {}}},
+        /*disabled_features=*/{});
   }
 
   // Creates a PrivateAggregationRequest with ForEvent contribution.
   auction_worklet::mojom::PrivateAggregationRequestPtr CreateForEventRequest(
       absl::uint128 bucket,
       int value,
-      const std::string& event_type) {
+      const std::string& event_type,
+      std::optional<uint64_t> filtering_id = std::nullopt) {
     auction_worklet::mojom::AggregatableReportForEventContribution contribution(
         auction_worklet::mojom::ForEventSignalBucket::NewIdBucket(bucket),
         auction_worklet::mojom::ForEventSignalValue::NewIntValue(value),
-        std::move(event_type));
+        filtering_id, std::move(event_type));
 
     return auction_worklet::mojom::PrivateAggregationRequest::New(
         auction_worklet::mojom::AggregatableReportContribution::
@@ -3205,6 +3333,9 @@
           typeof args.bucket.offset === 'string') {
         args.bucket.offset = BigInt(args.bucket.offset);
       }
+      if (args.filteringId && typeof args.filteringId === 'string') {
+        args.filteringId = BigInt(args.filteringId);
+      }
       privateAggregation.contributeToHistogramOnEvent('reserved.win', args);
     }
 
@@ -3314,7 +3445,7 @@
         gin::ConvertToV8(helper_->isolate(), dict));
     EXPECT_THAT(
         error_msgs,
-        ElementsAre("https://example.test/script.js:37 Uncaught TypeError: "
+        ElementsAre("https://example.test/script.js:40 Uncaught TypeError: "
                     "privateAggregation.contributeToHistogramOnEvent(): at "
                     "least 2 argument(s) are required."));
 
@@ -3335,7 +3466,7 @@
         gin::ConvertToV8(helper_->isolate(), dict));
     EXPECT_THAT(
         error_msgs,
-        ElementsAre("https://example.test/script.js:41 Uncaught TypeError: "
+        ElementsAre("https://example.test/script.js:44 Uncaught TypeError: "
                     "privateAggregation.contributeToHistogramOnEvent(): at "
                     "least 2 argument(s) are required."));
 
@@ -3357,7 +3488,7 @@
         gin::ConvertToV8(helper_->isolate(), dict));
     EXPECT_THAT(
         error_msgs,
-        ElementsAre("https://example.test/script.js:48 Uncaught TypeError: "
+        ElementsAre("https://example.test/script.js:51 Uncaught TypeError: "
                     "privateAggregation.contributeToHistogramOnEvent() "
                     "'contribution' argument: Value passed as dictionary is "
                     "neither object, null, nor undefined."));
@@ -3406,6 +3537,7 @@
                 NewIdBucket(absl::MakeUint128(/*high=*/1, /*low=*/0)),
             /*value=*/
             auction_worklet::mojom::ForEventSignalValue::NewIntValue(45),
+            /*filtering_id=*/std::nullopt,
             /*event_type=*/kReservedWin);
 
     ExpectOneForEventRequestEqualTo(
@@ -3433,6 +3565,7 @@
                 NewIdBucket(absl::Uint128Max()),
             /*value=*/
             auction_worklet::mojom::ForEventSignalValue::NewIntValue(45),
+            /*filtering_id=*/std::nullopt,
             /*event_type=*/kReservedWin);
 
     ExpectOneForEventRequestEqualTo(
@@ -3460,6 +3593,7 @@
                 NewIdBucket(0),
             /*value=*/
             auction_worklet::mojom::ForEventSignalValue::NewIntValue(45),
+            /*filtering_id=*/std::nullopt,
             /*event_type=*/kReservedWin);
 
     ExpectOneForEventRequestEqualTo(
@@ -3487,6 +3621,7 @@
                 NewIdBucket(123),
             /*value=*/
             auction_worklet::mojom::ForEventSignalValue::NewIntValue(0),
+            /*filtering_id=*/std::nullopt,
             /*event_type=*/kReservedWin);
 
     ExpectOneForEventRequestEqualTo(
@@ -3544,7 +3679,7 @@
         gin::ConvertToV8(helper_->isolate(), dict));
     EXPECT_THAT(
         error_msgs,
-        ElementsAre("https://example.test/script.js:12 Uncaught TypeError: "
+        ElementsAre("https://example.test/script.js:15 Uncaught TypeError: "
                     "BigInt is too large."));
 
     EXPECT_TRUE(context_recycler.private_aggregation_bindings()
@@ -3584,6 +3719,7 @@
                 NewSignalBucket(std::move(signal_bucket)),
             /*value=*/
             auction_worklet::mojom::ForEventSignalValue::NewIntValue(1),
+            /*filtering_id=*/std::nullopt,
             /*event_type=*/kReservedWin);
 
     ExpectOneForEventRequestEqualTo(
@@ -3620,6 +3756,7 @@
                 NewSignalBucket(signal_bucket.Clone()),
             /*value=*/
             auction_worklet::mojom::ForEventSignalValue::NewIntValue(1),
+            /*filtering_id=*/std::nullopt,
             /*event_type=*/kReservedWin);
 
     ExpectOneForEventRequestEqualTo(
@@ -3647,6 +3784,7 @@
                 NewIdBucket(123),
             /*value=*/
             auction_worklet::mojom::ForEventSignalValue::NewIntValue(4),
+            /*filtering_id=*/std::nullopt,
             /*event_type=*/kReservedWin);
 
     ExpectOneForEventRequestEqualTo(
@@ -3673,7 +3811,7 @@
     EXPECT_THAT(
         error_msgs,
         ElementsAre(
-            "https://example.test/script.js:12 Uncaught TypeError: "
+            "https://example.test/script.js:15 Uncaught TypeError: "
             "privateAggregation.contributeToHistogramOnEvent() 'contribution' "
             "argument: Required field 'baseValue' is undefined."));
 
@@ -3698,7 +3836,7 @@
     Run(scope, script, "test", error_msgs,
         gin::ConvertToV8(helper_->isolate(), dict));
     EXPECT_THAT(error_msgs,
-                ElementsAre("https://example.test/script.js:12 Uncaught "
+                ElementsAre("https://example.test/script.js:15 Uncaught "
                             "TypeError: Bucket's 'baseValue' is invalid."));
 
     EXPECT_TRUE(context_recycler.private_aggregation_bindings()
@@ -3750,7 +3888,7 @@
         gin::ConvertToV8(helper_->isolate(), dict));
     EXPECT_THAT(
         error_msgs,
-        ElementsAre("https://example.test/script.js:12 Uncaught TypeError: "
+        ElementsAre("https://example.test/script.js:15 Uncaught TypeError: "
                     "Cannot convert a BigInt value to a number."));
 
     EXPECT_TRUE(context_recycler.private_aggregation_bindings()
@@ -3776,7 +3914,7 @@
         gin::ConvertToV8(helper_->isolate(), dict));
     EXPECT_THAT(
         error_msgs,
-        ElementsAre("https://example.test/script.js:12 Uncaught TypeError: "
+        ElementsAre("https://example.test/script.js:15 Uncaught TypeError: "
                     "privateAggregation.contributeToHistogramOnEvent() "
                     "'contribution' argument: Converting field 'scale' to a "
                     "Number did not produce a finite double."));
@@ -3804,7 +3942,7 @@
         gin::ConvertToV8(helper_->isolate(), dict));
     EXPECT_THAT(
         error_msgs,
-        ElementsAre("https://example.test/script.js:12 Uncaught TypeError: "
+        ElementsAre("https://example.test/script.js:15 Uncaught TypeError: "
                     "privateAggregation.contributeToHistogramOnEvent() "
                     "'contribution' argument: Converting field 'scale' to a "
                     "Number did not produce a finite double."));
@@ -3831,7 +3969,7 @@
     Run(scope, script, "test", error_msgs,
         gin::ConvertToV8(helper_->isolate(), dict));
     EXPECT_THAT(error_msgs,
-                ElementsAre("https://example.test/script.js:12 Uncaught "
+                ElementsAre("https://example.test/script.js:15 Uncaught "
                             "TypeError: Bucket's 'offset' must be BigInt."));
 
     EXPECT_TRUE(context_recycler.private_aggregation_bindings()
@@ -3870,6 +4008,7 @@
             /*value=*/
             auction_worklet::mojom::ForEventSignalValue::NewSignalValue(
                 std::move(signal_value)),
+            /*filtering_id=*/std::nullopt,
             /*event_type=*/kReservedWin);
 
     ExpectOneForEventRequestEqualTo(
@@ -3896,7 +4035,7 @@
     EXPECT_THAT(
         error_msgs,
         ElementsAre(
-            "https://example.test/script.js:12 Uncaught TypeError: "
+            "https://example.test/script.js:15 Uncaught TypeError: "
             "privateAggregation.contributeToHistogramOnEvent() 'contribution' "
             "argument: Required field 'baseValue' is undefined."));
 
@@ -3925,7 +4064,7 @@
         gin::ConvertToV8(helper_->isolate(), dict));
     EXPECT_THAT(
         error_msgs,
-        ElementsAre("https://example.test/script.js:12 Uncaught TypeError: "
+        ElementsAre("https://example.test/script.js:15 Uncaught TypeError: "
                     "Value's 'offset' must be a 32-bit signed integer."));
 
     EXPECT_TRUE(context_recycler.private_aggregation_bindings()
@@ -3949,7 +4088,7 @@
     Run(scope, script, "test", error_msgs,
         gin::ConvertToV8(helper_->isolate(), dict));
     EXPECT_THAT(error_msgs,
-                ElementsAre("https://example.test/script.js:12 Uncaught "
+                ElementsAre("https://example.test/script.js:15 Uncaught "
                             "TypeError: Value's 'baseValue' is invalid."));
 
     EXPECT_TRUE(context_recycler.private_aggregation_bindings()
@@ -3970,7 +4109,7 @@
         gin::ConvertToV8(helper_->isolate(), dict));
     EXPECT_THAT(
         error_msgs,
-        ElementsAre("https://example.test/script.js:12 Uncaught TypeError: "
+        ElementsAre("https://example.test/script.js:15 Uncaught TypeError: "
                     "Cannot convert 12.3 to a BigInt."));
 
     EXPECT_TRUE(context_recycler.private_aggregation_bindings()
@@ -4011,7 +4150,7 @@
         gin::ConvertToV8(helper_->isolate(), dict));
     EXPECT_THAT(
         error_msgs,
-        ElementsAre("https://example.test/script.js:12 Uncaught TypeError: "
+        ElementsAre("https://example.test/script.js:15 Uncaught TypeError: "
                     "Cannot convert a BigInt value to a number."));
 
     EXPECT_TRUE(context_recycler.private_aggregation_bindings()
@@ -4032,7 +4171,7 @@
         gin::ConvertToV8(helper_->isolate(), dict));
     EXPECT_THAT(
         error_msgs,
-        ElementsAre("https://example.test/script.js:12 Uncaught TypeError: "
+        ElementsAre("https://example.test/script.js:15 Uncaught TypeError: "
                     "BigInt must be non-negative."));
 
     EXPECT_TRUE(context_recycler.private_aggregation_bindings()
@@ -4053,7 +4192,7 @@
         gin::ConvertToV8(helper_->isolate(), dict));
     EXPECT_THAT(
         error_msgs,
-        ElementsAre("https://example.test/script.js:12 Uncaught TypeError: "
+        ElementsAre("https://example.test/script.js:15 Uncaught TypeError: "
                     "Value must be non-negative."));
 
     EXPECT_TRUE(context_recycler.private_aggregation_bindings()
@@ -4074,7 +4213,7 @@
     EXPECT_THAT(
         error_msgs,
         ElementsAre(
-            "https://example.test/script.js:12 Uncaught TypeError: "
+            "https://example.test/script.js:15 Uncaught TypeError: "
             "privateAggregation.contributeToHistogramOnEvent() 'contribution' "
             "argument: Required field 'bucket' is undefined."));
 
@@ -4094,7 +4233,7 @@
     Run(scope, script, "test", error_msgs,
         gin::ConvertToV8(helper_->isolate(), dict));
     EXPECT_THAT(error_msgs,
-                ElementsAre("https://example.test/script.js:12 Uncaught "
+                ElementsAre("https://example.test/script.js:15 Uncaught "
                             "TypeError: Cannot convert 123 to a BigInt."));
 
     EXPECT_TRUE(context_recycler.private_aggregation_bindings()
@@ -4115,7 +4254,7 @@
     EXPECT_THAT(
         error_msgs,
         ElementsAre(
-            "https://example.test/script.js:12 Uncaught TypeError: "
+            "https://example.test/script.js:15 Uncaught TypeError: "
             "privateAggregation.contributeToHistogramOnEvent() 'contribution' "
             "argument: Required field 'value' is undefined."));
 
@@ -4139,6 +4278,124 @@
                     ->TakePrivateAggregationRequests()
                     .empty());
   }
+
+  // Basic filtering IDs
+  {
+    ContextRecyclerScope scope(context_recycler);
+    std::vector<std::string> error_msgs;
+
+    gin::Dictionary dict = gin::Dictionary::CreateEmpty(helper_->isolate());
+    dict.Set("bucket", std::string("123"));
+    dict.Set("value", 45);
+    dict.Set("filteringId", std::string("0"));
+
+    Run(scope, script, "test", error_msgs,
+        gin::ConvertToV8(helper_->isolate(), dict));
+    EXPECT_THAT(error_msgs, ElementsAre());
+
+    auto pa_requests = context_recycler.private_aggregation_bindings()
+                           ->TakePrivateAggregationRequests();
+
+    ASSERT_EQ(pa_requests.size(), 1u);
+    EXPECT_EQ(pa_requests[0],
+              CreateForEventRequest(/*bucket=*/123, /*value=*/45,
+                                    /*event_type=*/kReservedWin,
+                                    /*filtering_id=*/
+                                    0));
+    EXPECT_TRUE(context_recycler.private_aggregation_bindings()
+                    ->TakePrivateAggregationRequests()
+                    .empty());
+  }
+
+  // Max filtering IDs
+  {
+    ContextRecyclerScope scope(context_recycler);
+    std::vector<std::string> error_msgs;
+
+    gin::Dictionary dict = gin::Dictionary::CreateEmpty(helper_->isolate());
+    dict.Set("bucket", std::string("123"));
+    dict.Set("value", 45);
+    dict.Set("filteringId", std::string("255"));
+
+    Run(scope, script, "test", error_msgs,
+        gin::ConvertToV8(helper_->isolate(), dict));
+    EXPECT_THAT(error_msgs, ElementsAre());
+
+    auto pa_requests = context_recycler.private_aggregation_bindings()
+                           ->TakePrivateAggregationRequests();
+
+    ASSERT_EQ(pa_requests.size(), 1u);
+    EXPECT_EQ(pa_requests[0], CreateForEventRequest(
+                                  /*bucket=*/123, /*value=*/45,
+                                  /*event_type=*/kReservedWin, /*filtering_id=*/
+                                  255));
+    EXPECT_TRUE(context_recycler.private_aggregation_bindings()
+                    ->TakePrivateAggregationRequests()
+                    .empty());
+  }
+
+  // Filtering ID negative
+  {
+    ContextRecyclerScope scope(context_recycler);
+    std::vector<std::string> error_msgs;
+
+    gin::Dictionary dict = gin::Dictionary::CreateEmpty(helper_->isolate());
+    dict.Set("bucket", std::string("123"));
+    dict.Set("value", 45);
+    dict.Set("filteringId", std::string("-1"));
+
+    Run(scope, script, "test", error_msgs,
+        gin::ConvertToV8(helper_->isolate(), dict));
+    EXPECT_THAT(error_msgs,
+                ElementsAre("https://example.test/script.js:15 Uncaught "
+                            "TypeError: BigInt must be non-negative."));
+
+    EXPECT_TRUE(context_recycler.private_aggregation_bindings()
+                    ->TakePrivateAggregationRequests()
+                    .empty());
+  }
+
+  // Filtering ID too big
+  {
+    ContextRecyclerScope scope(context_recycler);
+    std::vector<std::string> error_msgs;
+
+    gin::Dictionary dict = gin::Dictionary::CreateEmpty(helper_->isolate());
+    dict.Set("bucket", std::string("123"));
+    dict.Set("value", 45);
+    dict.Set("filteringId", std::string("256"));
+
+    Run(scope, script, "test", error_msgs,
+        gin::ConvertToV8(helper_->isolate(), dict));
+    EXPECT_THAT(error_msgs,
+                ElementsAre("https://example.test/script.js:15 Uncaught "
+                            "TypeError: Filtering ID is too large."));
+
+    EXPECT_TRUE(context_recycler.private_aggregation_bindings()
+                    ->TakePrivateAggregationRequests()
+                    .empty());
+  }
+
+  // Filtering ID not a BigInt
+  {
+    ContextRecyclerScope scope(context_recycler);
+    std::vector<std::string> error_msgs;
+
+    gin::Dictionary dict = gin::Dictionary::CreateEmpty(helper_->isolate());
+    dict.Set("bucket", std::string("123"));
+    dict.Set("value", 45);
+    dict.Set("filteringId", 1);
+
+    Run(scope, script, "test", error_msgs,
+        gin::ConvertToV8(helper_->isolate(), dict));
+    EXPECT_THAT(error_msgs,
+                ElementsAre("https://example.test/script.js:15 Uncaught "
+                            "TypeError: Cannot convert 1 to a BigInt."));
+
+    EXPECT_TRUE(context_recycler.private_aggregation_bindings()
+                    ->TakePrivateAggregationRequests()
+                    .empty());
+  }
 }
 
 class ContextRecyclerPrivateAggregationDisabledTest
@@ -4321,6 +4578,144 @@
   }
 }
 
+class ContextRecyclerPrivateAggregationOnlyFilteringIdsDisabledTest
+    : public ContextRecyclerTest {
+ public:
+  ContextRecyclerPrivateAggregationOnlyFilteringIdsDisabledTest() {
+    scoped_feature_list_.InitWithFeaturesAndParameters(
+        /*enabled_features=*/{{blink::features::kPrivateAggregationApi,
+                               {{"fledge_extensions_enabled", "true"}}}},
+        /*disabled_features=*/{
+            blink::features::kPrivateAggregationApiFilteringIds});
+  }
+
+ private:
+  base::test::ScopedFeatureList scoped_feature_list_;
+};
+
+TEST_F(ContextRecyclerPrivateAggregationOnlyFilteringIdsDisabledTest,
+       PrivateAggregationForEventBindings) {
+  const char kScript[] = R"(
+    function test(args) {
+      // Passing BigInts in directly is complicated so we construct them from
+      // strings.
+      if (typeof args.bucket === "string") {
+        args.bucket = BigInt(args.bucket);
+      }
+      if (args.filteringId && typeof args.filteringId === 'string') {
+        args.filteringId = BigInt(args.filteringId);
+      }
+      privateAggregation.contributeToHistogram(args);
+      privateAggregation.contributeToHistogramOnEvent("reserved.win", args);
+    }
+  )";
+
+  v8::Local<v8::UnboundScript> script = Compile(kScript);
+  ASSERT_FALSE(script.IsEmpty());
+
+  ContextRecycler context_recycler(helper_.get());
+  {
+    ContextRecyclerScope scope(context_recycler);  // Initialize context
+    context_recycler.AddPrivateAggregationBindings(
+        /*private_aggregation_permissions_policy_allowed=*/true);
+  }
+
+  const auction_worklet::mojom::PrivateAggregationRequestPtr kExpectedRequest =
+      auction_worklet::mojom::PrivateAggregationRequest::New(
+          auction_worklet::mojom::AggregatableReportContribution::
+              NewHistogramContribution(
+                  blink::mojom::AggregatableReportHistogramContribution::New(
+                      /*bucket=*/123, /*value=*/45,
+                      /*filtering_id=*/std::nullopt)),
+          blink::mojom::AggregationServiceMode::kDefault,
+          blink::mojom::DebugModeDetails::New());
+
+  const auction_worklet::mojom::PrivateAggregationRequestPtr
+      kExpectedForEventRequest =
+          auction_worklet::mojom::PrivateAggregationRequest::New(
+              auction_worklet::mojom::AggregatableReportContribution::
+                  NewForEventContribution(
+                      auction_worklet::mojom::
+                          AggregatableReportForEventContribution::New(
+                              auction_worklet::mojom::ForEventSignalBucket::
+                                  NewIdBucket(123),
+                              auction_worklet::mojom::ForEventSignalValue::
+                                  NewIntValue(45),
+                              /*filtering_id=*/std::nullopt,
+                              std::move(kReservedWin))),
+              blink::mojom::AggregationServiceMode::kDefault,
+              blink::mojom::DebugModeDetails::New());
+
+  // Valid filtering ID ignored
+  {
+    ContextRecyclerScope scope(context_recycler);
+    std::vector<std::string> error_msgs;
+
+    gin::Dictionary dict = gin::Dictionary::CreateEmpty(helper_->isolate());
+    dict.Set("bucket", std::string("123"));
+    dict.Set("value", 45);
+    dict.Set("filteringId", std::string("1"));
+
+    Run(scope, script, "test", error_msgs,
+        gin::ConvertToV8(helper_->isolate(), dict));
+    EXPECT_THAT(error_msgs, ElementsAre());
+
+    EXPECT_THAT(
+        context_recycler.private_aggregation_bindings()
+            ->TakePrivateAggregationRequests(),
+        ElementsAreRequests(kExpectedRequest, kExpectedForEventRequest));
+    EXPECT_TRUE(context_recycler.private_aggregation_bindings()
+                    ->TakePrivateAggregationRequests()
+                    .empty());
+  }
+
+  // Too large filtering ID ignored
+  {
+    ContextRecyclerScope scope(context_recycler);
+    std::vector<std::string> error_msgs;
+
+    gin::Dictionary dict = gin::Dictionary::CreateEmpty(helper_->isolate());
+    dict.Set("bucket", std::string("123"));
+    dict.Set("value", 45);
+    dict.Set("filteringId", std::string("256"));
+
+    Run(scope, script, "test", error_msgs,
+        gin::ConvertToV8(helper_->isolate(), dict));
+    EXPECT_THAT(error_msgs, ElementsAre());
+
+    EXPECT_THAT(
+        context_recycler.private_aggregation_bindings()
+            ->TakePrivateAggregationRequests(),
+        ElementsAreRequests(kExpectedRequest, kExpectedForEventRequest));
+    EXPECT_TRUE(context_recycler.private_aggregation_bindings()
+                    ->TakePrivateAggregationRequests()
+                    .empty());
+  }
+
+  // Invalid filtering ID type ignored
+  {
+    ContextRecyclerScope scope(context_recycler);
+    std::vector<std::string> error_msgs;
+
+    gin::Dictionary dict = gin::Dictionary::CreateEmpty(helper_->isolate());
+    dict.Set("bucket", std::string("123"));
+    dict.Set("value", 45);
+    dict.Set("filteringId", 1);
+
+    Run(scope, script, "test", error_msgs,
+        gin::ConvertToV8(helper_->isolate(), dict));
+    EXPECT_THAT(error_msgs, ElementsAre());
+
+    EXPECT_THAT(
+        context_recycler.private_aggregation_bindings()
+            ->TakePrivateAggregationRequests(),
+        ElementsAreRequests(kExpectedRequest, kExpectedForEventRequest));
+    EXPECT_TRUE(context_recycler.private_aggregation_bindings()
+                    ->TakePrivateAggregationRequests()
+                    .empty());
+  }
+}
+
 class ContextRecyclerAdMacroReportingEnabledTest : public ContextRecyclerTest {
  public:
   ContextRecyclerAdMacroReportingEnabledTest() {
@@ -4370,4 +4765,422 @@
   }
 }
 
+class ContextRecyclerRealTimeReportingEnabledTest : public ContextRecyclerTest {
+ public:
+  ContextRecyclerRealTimeReportingEnabledTest() {
+    scoped_feature_list_.InitAndEnableFeature(
+        blink::features::kFledgeRealTimeReporting);
+  }
+
+ private:
+  base::test::ScopedFeatureList scoped_feature_list_;
+};
+
+// Exercise RealTimeReportingBindings, and make sure they reset properly.
+TEST_F(ContextRecyclerRealTimeReportingEnabledTest, RealTimeReportingBindings) {
+  const char kScript[] = R"(
+    function test(args) {
+      realTimeReporting.contributeToRealTimeHistogram(123,args);
+    }
+    function testNegativeBucket(args) {
+      realTimeReporting.contributeToRealTimeHistogram(-123,args);
+    }
+    function testBiggerBucket(args) {
+      realTimeReporting.contributeToRealTimeHistogram(12345,args);
+    }
+    function testReservedBucket(args) {
+      realTimeReporting.contributeToRealTimeHistogram(1,args);
+    }
+    function testLatency(args) {
+      realTimeReporting.contributeOnWorkletLatency(200, args);
+    }
+    function testMultiCalls(args) {
+      realTimeReporting.contributeToRealTimeHistogram(100,args);
+      // Allow multiple contributions with the same bucket.
+      realTimeReporting.contributeToRealTimeHistogram(100,args);
+      realTimeReporting.contributeToRealTimeHistogram(101,args);
+      realTimeReporting.contributeOnWorkletLatency(200, args);
+      realTimeReporting.contributeOnWorkletLatency(200, args);
+      realTimeReporting.contributeOnWorkletLatency(201, args);
+    }
+
+    function doNothing() {}
+  )";
+
+  v8::Local<v8::UnboundScript> script = Compile(kScript);
+  ASSERT_FALSE(script.IsEmpty());
+
+  ContextRecycler context_recycler(helper_.get());
+  {
+    ContextRecyclerScope scope(context_recycler);  // Initialize context
+    context_recycler.AddRealTimeReportingBindings();
+  }
+
+  // Basic test
+  {
+    ContextRecyclerScope scope(context_recycler);
+    std::vector<std::string> error_msgs;
+
+    gin::Dictionary dict = gin::Dictionary::CreateEmpty(helper_->isolate());
+    dict.Set("priorityWeight", 0.5);
+
+    Run(scope, script, "test", error_msgs,
+        gin::ConvertToV8(helper_->isolate(), dict));
+    EXPECT_THAT(error_msgs, ElementsAre());
+
+    auction_worklet::mojom::RealTimeReportingContribution expected_contribution(
+        /*bucket=*/123,
+        /*priority_weight=*/0.5, /*latency_threshold=*/std::nullopt);
+    auto contributions = context_recycler.real_time_reporting_bindings()
+                             ->TakeRealTimeReportingContributions();
+
+    ASSERT_EQ(contributions.size(), 1u);
+    EXPECT_EQ(contributions[0], expected_contribution.Clone());
+  }
+
+  // Negative bucket.
+  {
+    ContextRecyclerScope scope(context_recycler);
+    std::vector<std::string> error_msgs;
+
+    gin::Dictionary dict = gin::Dictionary::CreateEmpty(helper_->isolate());
+    dict.Set("priorityWeight", 0.5);
+
+    Run(scope, script, "testNegativeBucket", error_msgs,
+        gin::ConvertToV8(helper_->isolate(), dict));
+    EXPECT_THAT(error_msgs, ElementsAre());
+    EXPECT_TRUE(context_recycler.real_time_reporting_bindings()
+                    ->TakeRealTimeReportingContributions()
+                    .empty());
+  }
+
+  // Bigger than supported bucket.
+  {
+    ContextRecyclerScope scope(context_recycler);
+    std::vector<std::string> error_msgs;
+
+    gin::Dictionary dict = gin::Dictionary::CreateEmpty(helper_->isolate());
+    dict.Set("priorityWeight", 0.5);
+
+    Run(scope, script, "testBiggerBucket", error_msgs,
+        gin::ConvertToV8(helper_->isolate(), dict));
+    EXPECT_THAT(error_msgs, ElementsAre());
+    EXPECT_TRUE(context_recycler.real_time_reporting_bindings()
+                    ->TakeRealTimeReportingContributions()
+                    .empty());
+  }
+
+  // Reserved bucket for errors outside of worklets, such as script fetch error.
+  // API calls with these buckets will be ignored.
+  {
+    ContextRecyclerScope scope(context_recycler);
+    std::vector<std::string> error_msgs;
+
+    gin::Dictionary dict = gin::Dictionary::CreateEmpty(helper_->isolate());
+    dict.Set("priorityWeight", 0.5);
+
+    Run(scope, script, "testReservedBucket", error_msgs,
+        gin::ConvertToV8(helper_->isolate(), dict));
+    EXPECT_THAT(error_msgs, ElementsAre());
+    EXPECT_TRUE(context_recycler.real_time_reporting_bindings()
+                    ->TakeRealTimeReportingContributions()
+                    .empty());
+  }
+
+  // Missing priorityWeight.
+  {
+    ContextRecyclerScope scope(context_recycler);
+    std::vector<std::string> error_msgs;
+
+    gin::Dictionary dict = gin::Dictionary::CreateEmpty(helper_->isolate());
+
+    Run(scope, script, "test", error_msgs,
+        gin::ConvertToV8(helper_->isolate(), dict));
+    EXPECT_THAT(
+        error_msgs,
+        ElementsAre("https://example.test/script.js:3 Uncaught TypeError: "
+                    "realTimeReporting.contributeToRealTimeHistogram() 'value' "
+                    "argument: Required field 'priorityWeight' is undefined."));
+    EXPECT_TRUE(context_recycler.real_time_reporting_bindings()
+                    ->TakeRealTimeReportingContributions()
+                    .empty());
+  }
+
+  // Zero priorityWeight.
+  {
+    ContextRecyclerScope scope(context_recycler);
+    std::vector<std::string> error_msgs;
+
+    gin::Dictionary dict = gin::Dictionary::CreateEmpty(helper_->isolate());
+    dict.Set("priorityWeight", 0);
+
+    Run(scope, script, "test", error_msgs,
+        gin::ConvertToV8(helper_->isolate(), dict));
+    EXPECT_THAT(
+        error_msgs,
+        ElementsAre("https://example.test/script.js:3 Uncaught TypeError: "
+                    "priorityWeight must be a positive Number."));
+    EXPECT_TRUE(context_recycler.real_time_reporting_bindings()
+                    ->TakeRealTimeReportingContributions()
+                    .empty());
+  }
+
+  // Negative priorityWeight.
+  {
+    ContextRecyclerScope scope(context_recycler);
+    std::vector<std::string> error_msgs;
+
+    gin::Dictionary dict = gin::Dictionary::CreateEmpty(helper_->isolate());
+    dict.Set("priorityWeight", -0.5);
+
+    Run(scope, script, "test", error_msgs,
+        gin::ConvertToV8(helper_->isolate(), dict));
+    EXPECT_THAT(
+        error_msgs,
+        ElementsAre("https://example.test/script.js:3 Uncaught TypeError: "
+                    "priorityWeight must be a positive Number."));
+    EXPECT_TRUE(context_recycler.real_time_reporting_bindings()
+                    ->TakeRealTimeReportingContributions()
+                    .empty());
+  }
+
+  // NaN priorityWeight.
+  {
+    ContextRecyclerScope scope(context_recycler);
+    std::vector<std::string> error_msgs;
+
+    gin::Dictionary dict = gin::Dictionary::CreateEmpty(helper_->isolate());
+    dict.Set("priorityWeight", std::numeric_limits<double>::quiet_NaN());
+
+    Run(scope, script, "test", error_msgs,
+        gin::ConvertToV8(helper_->isolate(), dict));
+    EXPECT_THAT(
+        error_msgs,
+        ElementsAre("https://example.test/script.js:3 Uncaught TypeError: "
+                    "realTimeReporting.contributeToRealTimeHistogram() 'value' "
+                    "argument: Converting field 'priorityWeight' to a Number "
+                    "did not produce a finite double."));
+    EXPECT_TRUE(context_recycler.real_time_reporting_bindings()
+                    ->TakeRealTimeReportingContributions()
+                    .empty());
+  }
+
+  // Infinity priorityWeight.
+  {
+    ContextRecyclerScope scope(context_recycler);
+    std::vector<std::string> error_msgs;
+
+    gin::Dictionary dict = gin::Dictionary::CreateEmpty(helper_->isolate());
+    dict.Set("priorityWeight", std::numeric_limits<double>::infinity());
+
+    Run(scope, script, "test", error_msgs,
+        gin::ConvertToV8(helper_->isolate(), dict));
+    EXPECT_THAT(
+        error_msgs,
+        ElementsAre("https://example.test/script.js:3 Uncaught TypeError: "
+                    "realTimeReporting.contributeToRealTimeHistogram() 'value' "
+                    "argument: Converting field 'priorityWeight' to a Number "
+                    "did not produce a finite double."));
+    EXPECT_TRUE(context_recycler.real_time_reporting_bindings()
+                    ->TakeRealTimeReportingContributions()
+                    .empty());
+  }
+
+  // latency_threshold is ignored for contributeToRealTimeHistogram().
+  {
+    ContextRecyclerScope scope(context_recycler);
+    std::vector<std::string> error_msgs;
+
+    gin::Dictionary dict = gin::Dictionary::CreateEmpty(helper_->isolate());
+    dict.Set("priorityWeight", 0.5);
+    dict.Set("latencyThreshold", 200);
+    // Other unknown keys are just ignored.
+    dict.Set("someUnknown", 200);
+
+    Run(scope, script, "test", error_msgs,
+        gin::ConvertToV8(helper_->isolate(), dict));
+    EXPECT_THAT(error_msgs, ElementsAre());
+
+    auction_worklet::mojom::RealTimeReportingContribution expected_contribution(
+        /*bucket=*/123,
+        /*priority_weight=*/0.5, /*latency_threshold=*/std::nullopt);
+    auto contributions = context_recycler.real_time_reporting_bindings()
+                             ->TakeRealTimeReportingContributions();
+
+    ASSERT_EQ(contributions.size(), 1u);
+    EXPECT_EQ(contributions[0], expected_contribution.Clone());
+  }
+
+  // Worklet latency basic test.
+  {
+    ContextRecyclerScope scope(context_recycler);
+    std::vector<std::string> error_msgs;
+
+    gin::Dictionary dict = gin::Dictionary::CreateEmpty(helper_->isolate());
+    dict.Set("priorityWeight", 0.5);
+    dict.Set("latencyThreshold", 200);
+
+    Run(scope, script, "testLatency", error_msgs,
+        gin::ConvertToV8(helper_->isolate(), dict));
+    EXPECT_THAT(error_msgs, ElementsAre());
+
+    auction_worklet::mojom::RealTimeReportingContribution expected_contribution(
+        /*bucket=*/200, /*priority_weight*/ 0.5, /*latency_threshold=*/200);
+    auto contributions = context_recycler.real_time_reporting_bindings()
+                             ->TakeRealTimeReportingContributions();
+
+    ASSERT_EQ(contributions.size(), 1u);
+    EXPECT_EQ(contributions[0], expected_contribution.Clone());
+  }
+
+  // Worklet latency API missing priorityWeight.
+  {
+    ContextRecyclerScope scope(context_recycler);
+    std::vector<std::string> error_msgs;
+
+    gin::Dictionary dict = gin::Dictionary::CreateEmpty(helper_->isolate());
+    dict.Set("latencyThreshold", 200);
+
+    Run(scope, script, "testLatency", error_msgs,
+        gin::ConvertToV8(helper_->isolate(), dict));
+    EXPECT_THAT(
+        error_msgs,
+        ElementsAre("https://example.test/script.js:15 Uncaught TypeError: "
+                    "realTimeReporting.contributeOnWorkletLatency() 'value' "
+                    "argument: Required field 'priorityWeight' is undefined."));
+    EXPECT_TRUE(context_recycler.real_time_reporting_bindings()
+                    ->TakeRealTimeReportingContributions()
+                    .empty());
+  }
+
+  // Worklet latency API missing latencyThreshold.
+  {
+    ContextRecyclerScope scope(context_recycler);
+    std::vector<std::string> error_msgs;
+
+    gin::Dictionary dict = gin::Dictionary::CreateEmpty(helper_->isolate());
+    dict.Set("priorityWeight", 0.5);
+
+    Run(scope, script, "testLatency", error_msgs,
+        gin::ConvertToV8(helper_->isolate(), dict));
+    EXPECT_THAT(
+        error_msgs,
+        ElementsAre(
+            "https://example.test/script.js:15 Uncaught TypeError: "
+            "realTimeReporting.contributeOnWorkletLatency() 'value' argument: "
+            "Required field 'latencyThreshold' is undefined."));
+    EXPECT_TRUE(context_recycler.real_time_reporting_bindings()
+                    ->TakeRealTimeReportingContributions()
+                    .empty());
+  }
+
+  // Multi API calls, and calls both APIs.
+  {
+    ContextRecyclerScope scope(context_recycler);
+    std::vector<std::string> error_msgs;
+
+    gin::Dictionary dict = gin::Dictionary::CreateEmpty(helper_->isolate());
+    dict.Set("priorityWeight", 0.5);
+    dict.Set("latencyThreshold", 200);
+
+    Run(scope, script, "testMultiCalls", error_msgs,
+        gin::ConvertToV8(helper_->isolate(), dict));
+    EXPECT_THAT(error_msgs, ElementsAre());
+
+    auto contributions = context_recycler.real_time_reporting_bindings()
+                             ->TakeRealTimeReportingContributions();
+
+    ASSERT_EQ(contributions.size(), 6u);
+  }
+
+  // API not called.
+  {
+    ContextRecyclerScope scope(context_recycler);
+    std::vector<std::string> error_msgs;
+
+    gin::Dictionary dict = gin::Dictionary::CreateEmpty(helper_->isolate());
+
+    Run(scope, script, "doNothing", error_msgs,
+        gin::ConvertToV8(helper_->isolate(), dict));
+    EXPECT_THAT(error_msgs, ElementsAre());
+
+    EXPECT_TRUE(context_recycler.real_time_reporting_bindings()
+                    ->TakeRealTimeReportingContributions()
+                    .empty());
+  }
+}
+
+class ContextRecyclerRealTimeReportingDisabledTest
+    : public ContextRecyclerTest {
+ public:
+  ContextRecyclerRealTimeReportingDisabledTest() {
+    scoped_feature_list_.InitAndDisableFeature(
+        blink::features::kFledgeRealTimeReporting);
+  }
+
+ private:
+  base::test::ScopedFeatureList scoped_feature_list_;
+};
+
+// Exercise RealTimeReportingBindings, and make sure they reset properly.
+TEST_F(ContextRecyclerRealTimeReportingDisabledTest,
+       RealTimeReportingBindings) {
+  const char kScript[] = R"(
+    function test(args) {
+      realTimeReporting.contributeToRealTimeHistogram(123,args);
+    }
+    function testLatency(args) {
+      realTimeReporting.contributeOnWorkletLatency(200, args);
+    }
+  )";
+
+  v8::Local<v8::UnboundScript> script = Compile(kScript);
+  ASSERT_FALSE(script.IsEmpty());
+
+  ContextRecycler context_recycler(helper_.get());
+  {
+    ContextRecyclerScope scope(context_recycler);  // Initialize context
+    context_recycler.AddRealTimeReportingBindings();
+  }
+
+  {
+    ContextRecyclerScope scope(context_recycler);
+    std::vector<std::string> error_msgs;
+
+    gin::Dictionary dict = gin::Dictionary::CreateEmpty(helper_->isolate());
+    dict.Set("priorityWeight", 0.5);
+
+    Run(scope, script, "test", error_msgs,
+        gin::ConvertToV8(helper_->isolate(), dict));
+    EXPECT_THAT(
+        error_msgs,
+        ElementsAre("https://example.test/script.js:3 Uncaught ReferenceError: "
+                    "realTimeReporting is not defined."));
+
+    EXPECT_TRUE(context_recycler.real_time_reporting_bindings()
+                    ->TakeRealTimeReportingContributions()
+                    .empty());
+  }
+
+  {
+    ContextRecyclerScope scope(context_recycler);
+    std::vector<std::string> error_msgs;
+
+    gin::Dictionary dict = gin::Dictionary::CreateEmpty(helper_->isolate());
+    dict.Set("priorityWeight", 0.5);
+    dict.Set("latencyThreshold", 200);
+
+    Run(scope, script, "testLatency", error_msgs,
+        gin::ConvertToV8(helper_->isolate(), dict));
+    EXPECT_THAT(
+        error_msgs,
+        ElementsAre("https://example.test/script.js:6 Uncaught ReferenceError: "
+                    "realTimeReporting is not defined."));
+
+    EXPECT_TRUE(context_recycler.real_time_reporting_bindings()
+                    ->TakeRealTimeReportingContributions()
+                    .empty());
+  }
+}
+
 }  // namespace auction_worklet
diff --git a/content/services/auction_worklet/private_aggregation_bindings.cc b/content/services/auction_worklet/private_aggregation_bindings.cc
index 4052f26..3d2900de 100644
--- a/content/services/auction_worklet/private_aggregation_bindings.cc
+++ b/content/services/auction_worklet/private_aggregation_bindings.cc
@@ -277,12 +277,37 @@
   }
 }
 
+// Returns false in case of an error.
+bool GetFilteringId(v8::Isolate* isolate,
+                    std::optional<v8::Local<v8::BigInt>> idl_filtering_id,
+                    std::optional<uint64_t>* out_filtering_id,
+                    std::string* error) {
+  if (!idl_filtering_id.has_value()) {
+    *out_filtering_id = std::nullopt;
+    return true;
+  }
+
+  std::optional<absl::uint128> maybe_filtering_id =
+      ConvertBigIntToUint128(idl_filtering_id.value(), error);
+  if (!maybe_filtering_id.has_value()) {
+    return false;
+  }
+  if (maybe_filtering_id.value() > 255) {
+    *error = "Filtering ID is too large";
+    return false;
+  }
+
+  *out_filtering_id = absl::Uint128Low64(maybe_filtering_id.value());
+  return true;
+}
+
 auction_worklet::mojom::AggregatableReportForEventContributionPtr
 ParseForEventContribution(
     v8::Isolate* isolate,
     const std::string& event_type,
     absl::variant<PASignalValue, v8::Local<v8::BigInt>> idl_bucket,
     absl::variant<PASignalValue, int32_t> idl_value,
+    std::optional<v8::Local<v8::BigInt>> idl_filtering_id,
     std::string* error) {
   auction_worklet::mojom::ForEventSignalBucketPtr bucket =
       GetBucket(isolate, std::move(idl_bucket), error);
@@ -294,9 +319,14 @@
   if (!value) {
     return nullptr;
   }
+  std::optional<uint64_t> filtering_id;
+  if (!GetFilteringId(isolate, std::move(idl_filtering_id), &filtering_id,
+                      error)) {
+    return nullptr;
+  }
 
   return auction_worklet::mojom::AggregatableReportForEventContribution::New(
-      std::move(bucket), std::move(value), std::move(event_type));
+      std::move(bucket), std::move(value), filtering_id, std::move(event_type));
 }
 
 // In case of failure, will return `std::nullopt` and output an error to
@@ -437,6 +467,7 @@
   args_converter.ConvertArg(0, "contribution", contribution_val);
   v8::Local<v8::BigInt> idl_bucket;
   int32_t idl_value;
+  std::optional<v8::Local<v8::BigInt>> idl_filtering_id;
   if (args_converter.is_success()) {
     // https://patcg-individual-drafts.github.io/private-aggregation-api/#dictdef-pahistogramcontribution
     //
@@ -444,6 +475,7 @@
     // dictionary PAHistogramContribution {
     //   required bigint bucket;
     //   required long value;
+    //   bigint filteringId;
     // };
     DictConverter contribution_converter(
         v8_helper, time_limit_scope,
@@ -451,6 +483,10 @@
         contribution_val);
     contribution_converter.GetRequired("bucket", idl_bucket);
     contribution_converter.GetRequired("value", idl_value);
+    if (base::FeatureList::IsEnabled(
+            blink::features::kPrivateAggregationApiFilteringIds)) {
+      contribution_converter.GetOptional("filteringId", idl_filtering_id);
+    }
     args_converter.SetStatus(contribution_converter.TakeStatus());
   }
 
@@ -476,12 +512,20 @@
     return;
   }
 
-  // TODO(crbug.com/330744610): Allow filtering ID to be set.
+  std::optional<uint64_t> filtering_id;
+  if (!GetFilteringId(isolate, std::move(idl_filtering_id), &filtering_id,
+                      &error)) {
+    CHECK(base::IsStringUTF8(error));
+    isolate->ThrowException(v8::Exception::TypeError(
+        v8_helper->CreateUtf8String(error).ToLocalChecked()));
+    return;
+  }
+
   bindings->private_aggregation_contributions_.push_back(
       auction_worklet::mojom::AggregatableReportContribution::
           NewHistogramContribution(
               blink::mojom::AggregatableReportHistogramContribution::New(
-                  bucket, idl_value, /*filtering_id=*/std::nullopt)));
+                  bucket, idl_value, filtering_id)));
 }
 
 void PrivateAggregationBindings::ContributeToHistogramOnEvent(
@@ -506,10 +550,12 @@
   // dictionary PAExtendedHistogramContribution {
   //   required (PASignalValue or bigint) bucket;
   //   required (PASignalValue or long) value;
+  //   bigint filteringId;
   // };
 
   absl::variant<PASignalValue, v8::Local<v8::BigInt>> bucket;
   absl::variant<PASignalValue, int32_t> value;
+  std::optional<v8::Local<v8::BigInt>> filtering_id;
   if (args_converter.is_success()) {
     DictConverter contribution_converter(
         v8_helper, time_limit_scope,
@@ -530,6 +576,10 @@
             "privateAggregation.contributeToHistogramOnEvent() 'contribution' "
             "argument: ",
             "value", value_val, contribution_converter, value);
+    if (base::FeatureList::IsEnabled(
+            blink::features::kPrivateAggregationApiFilteringIds)) {
+      contribution_converter.GetOptional("filteringId", filtering_id);
+    }
     args_converter.SetStatus(contribution_converter.TakeStatus());
   }
 
@@ -548,7 +598,8 @@
   std::string error;
   auction_worklet::mojom::AggregatableReportForEventContributionPtr
       contribution = ParseForEventContribution(
-          isolate, event_type, std::move(bucket), std::move(value), &error);
+          isolate, event_type, std::move(bucket), std::move(value),
+          std::move(filtering_id), &error);
 
   if (contribution.is_null()) {
     CHECK(base::IsStringUTF8(error));
diff --git a/content/services/auction_worklet/public/mojom/BUILD.gn b/content/services/auction_worklet/public/mojom/BUILD.gn
index 03b7022..005128a 100644
--- a/content/services/auction_worklet/public/mojom/BUILD.gn
+++ b/content/services/auction_worklet/public/mojom/BUILD.gn
@@ -20,6 +20,7 @@
     "auction_worklet_service.mojom",
     "bidder_worklet.mojom",
     "private_aggregation_request.mojom",
+    "real_time_reporting.mojom",
     "reject_reason.mojom",
     "seller_worklet.mojom",
   ]
diff --git a/content/services/auction_worklet/public/mojom/bidder_worklet.mojom b/content/services/auction_worklet/public/mojom/bidder_worklet.mojom
index 6e84fc3b..6a051f1 100644
--- a/content/services/auction_worklet/public/mojom/bidder_worklet.mojom
+++ b/content/services/auction_worklet/public/mojom/bidder_worklet.mojom
@@ -5,6 +5,7 @@
 module auction_worklet.mojom;
 
 import "content/services/auction_worklet/public/mojom/private_aggregation_request.mojom";
+import "content/services/auction_worklet/public/mojom/real_time_reporting.mojom";
 import "content/services/auction_worklet/public/mojom/reject_reason.mojom";
 import "mojo/public/mojom/base/time.mojom";
 import "services/network/public/mojom/url_loader_factory.mojom";
@@ -279,6 +280,8 @@
   //
   // `pa_requests` The various requests made to the Private Aggregation API.
   //
+  // `real_time_contributions` Real time reporting contributions.
+  //
   // `bidding_latency` The amount of time taken running all bids (note that
   // there may be 2 bids if the first bid result was not k-anonymous).
   //
@@ -303,6 +306,7 @@
       map<string, PrioritySignalsDouble?> update_priority_signals_overrides,
       array<PrivateAggregationRequest> pa_requests,
       array<PrivateAggregationRequest> non_kanon_pa_requests,
+      array<RealTimeReportingContribution> real_time_contributions,
       mojo_base.mojom.TimeDelta bidding_latency,
       GenerateBidDependencyLatencies generate_bid_dependency_latencies,
       RejectReason reject_reason,
diff --git a/content/services/auction_worklet/public/mojom/private_aggregation_request.mojom b/content/services/auction_worklet/public/mojom/private_aggregation_request.mojom
index 7933c97..3773bd3e0 100644
--- a/content/services/auction_worklet/public/mojom/private_aggregation_request.mojom
+++ b/content/services/auction_worklet/public/mojom/private_aggregation_request.mojom
@@ -65,12 +65,16 @@
   SignalValue signal_value;
 };
 
-// A for-event contribution contains a bucket, a value, and an event_type.
+// A for-event contribution contains a bucket, a value, a filtering ID, and an
+// event_type.
 // See https://github.com/WICG/turtledove/blob/main/FLEDGE_extended_PA_reporting.md#reporting-api-informal-specification
 struct AggregatableReportForEventContribution {
   ForEventSignalBucket bucket;
   ForEventSignalValue value;
 
+  // Null if not explicitly specified.
+  uint64? filtering_id;
+
   // Identifies the event type that triggers sending this report.
   string event_type;
 };
diff --git a/content/services/auction_worklet/public/mojom/real_time_reporting.mojom b/content/services/auction_worklet/public/mojom/real_time_reporting.mojom
new file mode 100644
index 0000000..ce6e96c0
--- /dev/null
+++ b/content/services/auction_worklet/public/mojom/real_time_reporting.mojom
@@ -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.
+
+module auction_worklet.mojom;
+
+// Represents a Real Time Reporting contribution. It can be from either
+// contributeToRealTimeHistogram() or contributeOnWorkletLatency(), depending on
+// whether there's a `latency_threshold` field.
+struct RealTimeReportingContribution {
+  int32 bucket;
+
+  // Must be greater than 0 and less than infinity, dictates the relative
+  // likelihood of which bucket will get the contribution.
+  double priority_weight = 1.0;
+
+  // Only contributeOnWorkletLatency() has latency_threshold, and it's required.
+  // The contribution is only kept when worklet latency is greater than the
+  // threshold.
+  uint32? latency_threshold;
+};
\ No newline at end of file
diff --git a/content/services/auction_worklet/public/mojom/seller_worklet.mojom b/content/services/auction_worklet/public/mojom/seller_worklet.mojom
index d4b0d91..6eb2dda 100644
--- a/content/services/auction_worklet/public/mojom/seller_worklet.mojom
+++ b/content/services/auction_worklet/public/mojom/seller_worklet.mojom
@@ -5,6 +5,7 @@
 module auction_worklet.mojom;
 
 import "content/services/auction_worklet/public/mojom/private_aggregation_request.mojom";
+import "content/services/auction_worklet/public/mojom/real_time_reporting.mojom";
 import "content/services/auction_worklet/public/mojom/reject_reason.mojom";
 import "mojo/public/mojom/base/time.mojom";
 import "services/network/public/mojom/url_loader_factory.mojom";
@@ -105,6 +106,8 @@
   //
   // `pa_requests` The various requests made to the Private Aggregation API.
   //
+  // `real_time_contributions` Real time reporting contributions.
+  //
   // `scoring_latency` How long it took to execute the scoreAd() JavaScript,
   //  (including the top-level).
   //
@@ -124,6 +127,7 @@
                     url.mojom.Url? debug_loss_report_url,
                     url.mojom.Url? debug_win_report_url,
                     array<PrivateAggregationRequest> pa_requests,
+                    array<RealTimeReportingContribution> real_time_contributions,
                     mojo_base.mojom.TimeDelta scoring_latency,
                     ScoreAdDependencyLatencies score_ad_dependency_latencies,
                     array<string> errors);
diff --git a/content/services/auction_worklet/real_time_reporting_bindings.cc b/content/services/auction_worklet/real_time_reporting_bindings.cc
new file mode 100644
index 0000000..ea518ec
--- /dev/null
+++ b/content/services/auction_worklet/real_time_reporting_bindings.cc
@@ -0,0 +1,173 @@
+// 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/services/auction_worklet/real_time_reporting_bindings.h"
+
+#include <cstdint>
+#include <memory>
+#include <optional>
+#include <string>
+#include <string_view>
+#include <utility>
+
+#include "base/feature_list.h"
+#include "base/functional/bind.h"
+#include "base/functional/callback.h"
+#include "content/services/auction_worklet/auction_v8_helper.h"
+#include "content/services/auction_worklet/public/mojom/real_time_reporting.mojom.h"
+#include "content/services/auction_worklet/webidl_compat.h"
+#include "third_party/blink/public/common/features.h"
+#include "v8/include/v8-exception.h"
+#include "v8/include/v8-external.h"
+#include "v8/include/v8-function-callback.h"
+#include "v8/include/v8-function.h"
+#include "v8/include/v8-template.h"
+
+namespace auction_worklet {
+
+namespace {
+
+// Total number of buckets. Supported buckets will be [0, kNumBuckets).
+// [0, kNumReservedBuckets) are reserved, and [kNumReservedBuckets, kNumBuckets)
+// are for real time reporting APIs to use.
+static const int kNumBuckets = 1024;
+// Reserved buckets, [0, kNumReservedBuckets), for errors not detectable in
+// worklet JS, such as failures to fetch the bidding script, trusted real-time
+// signals.
+static const int kNumReservedBuckets = 40;
+
+// Attempts to parse the elements of `args`, and add the constructed
+// contribution to `contributions_out`. Throws an exception on most failures.
+void ParseAndCollectContribution(
+    AuctionV8Helper* v8_helper,
+    AuctionV8Logger* v8_logger,
+    const v8::FunctionCallbackInfo<v8::Value>& args,
+    bool is_latency,
+    std::vector<auction_worklet::mojom::RealTimeReportingContributionPtr>&
+        contributions_out) {
+  AuctionV8Helper::TimeLimitScope time_limit_scope(v8_helper->GetTimeLimit());
+  std::string function_name = is_latency ? "contributeOnWorkletLatency"
+                                         : "contributeToRealTimeHistogram";
+  ArgsConverter args_converter(
+      v8_helper, time_limit_scope,
+      base::StrCat({"realTimeReporting.", function_name, "(): "}), &args,
+      /*min_required_args=*/2);
+
+  int32_t bucket;
+  double idl_priority_weight;
+  std::optional<uint32_t> latency_threshold;
+  args_converter.ConvertArg(0, "bucket", bucket);
+
+  if (args_converter.is_success()) {
+    DictConverter contribution_converter(
+        v8_helper, time_limit_scope,
+        base::StrCat(
+            {"realTimeReporting.", function_name, "() 'value' argument: "}),
+        args[1]);
+
+    // Note that this happens in lexicographic order of field names, to match
+    // WebIDL behavior.
+    if (is_latency) {
+      uint32_t idl_latency_threshold;
+      contribution_converter.GetRequired("latencyThreshold",
+                                         idl_latency_threshold);
+      latency_threshold = idl_latency_threshold;
+    }
+    contribution_converter.GetRequired("priorityWeight", idl_priority_weight);
+    args_converter.SetStatus(contribution_converter.TakeStatus());
+  }
+
+  if (args_converter.is_failed()) {
+    args_converter.TakeStatus().PropagateErrorsToV8(v8_helper);
+    return;
+  }
+
+  // [0, kNumBuckets) are supported buckets, while [0, kNumReservedBuckets) are
+  // reserved buckets which cannot be used by real time reporting APIs.
+  if (bucket < kNumReservedBuckets || bucket >= kNumBuckets) {
+    // Don't throw, to be forward compatible.
+    return;
+  }
+
+  if (idl_priority_weight <= 0) {
+    args.GetIsolate()->ThrowException(v8::Exception::TypeError(
+        v8_helper->CreateUtf8String("priorityWeight must be a positive Number")
+            .ToLocalChecked()));
+    return;
+  }
+
+  contributions_out.push_back(
+      auction_worklet::mojom::RealTimeReportingContribution::New(
+          bucket, idl_priority_weight, latency_threshold));
+}
+
+}  // namespace
+
+RealTimeReportingBindings::RealTimeReportingBindings(AuctionV8Helper* v8_helper)
+    : v8_helper_(v8_helper) {}
+
+RealTimeReportingBindings::~RealTimeReportingBindings() = default;
+
+void RealTimeReportingBindings::AttachToContext(
+    v8::Local<v8::Context> context) {
+  if (!base::FeatureList::IsEnabled(
+          blink::features::kFledgeRealTimeReporting)) {
+    return;
+  }
+
+  v8::Isolate* isolate = v8_helper_->isolate();
+  v8::Local<v8::External> v8_this = v8::External::New(isolate, this);
+  v8::Local<v8::Object> debugging = v8::Object::New(isolate);
+
+  v8::Local<v8::FunctionTemplate> real_time_histogram_template =
+      v8::FunctionTemplate::New(
+          isolate, &RealTimeReportingBindings::ContributeToRealTimeHistogram,
+          v8_this);
+  v8::Local<v8::FunctionTemplate> worklet_latency_template =
+      v8::FunctionTemplate::New(
+          isolate, &RealTimeReportingBindings::ContributeOnWorkletLatency,
+          v8_this);
+
+  debugging
+      ->Set(
+          context,
+          v8_helper_->CreateStringFromLiteral("contributeToRealTimeHistogram"),
+          real_time_histogram_template->GetFunction(context).ToLocalChecked())
+      .Check();
+
+  debugging
+      ->Set(context,
+            v8_helper_->CreateStringFromLiteral("contributeOnWorkletLatency"),
+            worklet_latency_template->GetFunction(context).ToLocalChecked())
+      .Check();
+
+  context->Global()
+      ->Set(context, v8_helper_->CreateStringFromLiteral("realTimeReporting"),
+            debugging)
+      .Check();
+}
+
+void RealTimeReportingBindings::Reset() {
+  real_time_reporting_contributions_.clear();
+}
+
+void RealTimeReportingBindings::ContributeToRealTimeHistogram(
+    const v8::FunctionCallbackInfo<v8::Value>& args) {
+  RealTimeReportingBindings* bindings = static_cast<RealTimeReportingBindings*>(
+      v8::External::Cast(*args.Data())->Value());
+  ParseAndCollectContribution(
+      bindings->v8_helper_.get(), bindings->v8_logger_.get(), args,
+      /*is_latency=*/false, bindings->real_time_reporting_contributions_);
+}
+
+void RealTimeReportingBindings::ContributeOnWorkletLatency(
+    const v8::FunctionCallbackInfo<v8::Value>& args) {
+  RealTimeReportingBindings* bindings = static_cast<RealTimeReportingBindings*>(
+      v8::External::Cast(*args.Data())->Value());
+  ParseAndCollectContribution(
+      bindings->v8_helper_.get(), bindings->v8_logger_.get(), args,
+      /*is_latency=*/true, bindings->real_time_reporting_contributions_);
+}
+
+}  // namespace auction_worklet
diff --git a/content/services/auction_worklet/real_time_reporting_bindings.h b/content/services/auction_worklet/real_time_reporting_bindings.h
new file mode 100644
index 0000000..c755d5c
--- /dev/null
+++ b/content/services/auction_worklet/real_time_reporting_bindings.h
@@ -0,0 +1,59 @@
+// 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_SERVICES_AUCTION_WORKLET_REAL_TIME_REPORTING_BINDINGS_H_
+#define CONTENT_SERVICES_AUCTION_WORKLET_REAL_TIME_REPORTING_BINDINGS_H_
+
+#include <vector>
+
+#include "base/functional/callback.h"
+#include "base/memory/raw_ptr.h"
+#include "content/common/content_export.h"
+#include "content/services/auction_worklet/auction_v8_helper.h"
+#include "content/services/auction_worklet/context_recycler.h"
+#include "content/services/auction_worklet/public/mojom/real_time_reporting.mojom-forward.h"
+#include "v8/include/v8-forward.h"
+
+namespace auction_worklet {
+
+class AuctionV8Helper;
+
+// Class to manage bindings for the Real Time Reporting API. Expected to be used
+// for a context managed by `ContextRecycler`. Throws exceptions when invalid
+// arguments are detected.
+class CONTENT_EXPORT RealTimeReportingBindings : public Bindings {
+ public:
+  explicit RealTimeReportingBindings(AuctionV8Helper* v8_helper);
+  RealTimeReportingBindings(const RealTimeReportingBindings&) = delete;
+  RealTimeReportingBindings& operator=(const RealTimeReportingBindings&) =
+      delete;
+  ~RealTimeReportingBindings() override;
+
+  // Add realTimeReporting object to the global context. The
+  // RealTimeReportingBindings must outlive the context.
+  void AttachToContext(v8::Local<v8::Context> context) override;
+  void Reset() override;
+
+  std::vector<auction_worklet::mojom::RealTimeReportingContributionPtr>
+  TakeRealTimeReportingContributions() {
+    return std::move(real_time_reporting_contributions_);
+  }
+
+ private:
+  static void ContributeToRealTimeHistogram(
+      const v8::FunctionCallbackInfo<v8::Value>& args);
+  static void ContributeOnWorkletLatency(
+      const v8::FunctionCallbackInfo<v8::Value>& args);
+
+  const raw_ptr<AuctionV8Helper> v8_helper_;
+  const raw_ptr<AuctionV8Logger> v8_logger_;
+
+  // Contributions from calling Real Time Reporting APIs.
+  std::vector<auction_worklet::mojom::RealTimeReportingContributionPtr>
+      real_time_reporting_contributions_;
+};
+
+}  // namespace auction_worklet
+
+#endif  // CONTENT_SERVICES_AUCTION_WORKLET_REAL_TIME_REPORTING_BINDINGS_H_
diff --git a/content/services/auction_worklet/seller_worklet.cc b/content/services/auction_worklet/seller_worklet.cc
index 0d3e369..cecee3a 100644
--- a/content/services/auction_worklet/seller_worklet.cc
+++ b/content/services/auction_worklet/seller_worklet.cc
@@ -37,6 +37,7 @@
 #include "content/services/auction_worklet/public/cpp/auction_network_events_delegate.h"
 #include "content/services/auction_worklet/public/mojom/auction_worklet_service.mojom.h"
 #include "content/services/auction_worklet/public/mojom/seller_worklet.mojom.h"
+#include "content/services/auction_worklet/real_time_reporting_bindings.h"
 #include "content/services/auction_worklet/register_ad_beacon_bindings.h"
 #include "content/services/auction_worklet/report_bindings.h"
 #include "content/services/auction_worklet/seller_lazy_filler.h"
@@ -1005,12 +1006,14 @@
           /*debug_loss_report_url=*/std::nullopt,
           /*debug_win_report_url=*/std::nullopt,
           /*pa_requests=*/{},
+          /*real_time_contributions=*/{},
           /*scoring_latency=*/elapsed_timer.Elapsed(), std::move(errors_out));
       return;
     }
     context_recycler->AddForDebuggingOnlyBindings();
     context_recycler->AddPrivateAggregationBindings(
         permissions_policy_state_->private_aggregation_allowed);
+    context_recycler->AddRealTimeReportingBindings();
     if (base::FeatureList::IsEnabled(blink::features::kSharedStorageAPI)) {
       context_recycler->AddSharedStorageBindings(
           shared_storage_host_remote_.is_bound()
@@ -1032,9 +1035,25 @@
   base::TimeDelta elapsed = elapsed_timer.Elapsed();
   base::UmaHistogramTimes("Ads.InterestGroup.Auction.ScoreAdTime", elapsed);
 
+  std::vector<auction_worklet::mojom::RealTimeReportingContributionPtr>
+      real_time_contributions = context_recycler->real_time_reporting_bindings()
+                                    ->TakeRealTimeReportingContributions();
+
+  // Remove worklet latency contributions if the worklet execution time is
+  // within the threshold.
+  std::erase_if(
+      real_time_contributions,
+      [elapsed](const auction_worklet::mojom::RealTimeReportingContributionPtr&
+                    contribution) {
+        return contribution->latency_threshold.has_value() &&
+               elapsed.InMilliseconds() <=
+                   contribution->latency_threshold.value();
+      });
+
   if (!success) {
-    // Keep debug loss reports and Private Aggregation API requests since
-    // `scoreAd()` might use them to detect script timeout or failures.
+    // Keep debug loss reports, Private Aggregation API requests, and real time
+    // reporting contributions since `scoreAd()` might use them to detect script
+    // timeout or failures.
     PostScoreAdCallbackToUserThread(
         std::move(callback), /*score=*/0,
         /*reject_reason=*/mojom::RejectReason::kNotAvailable,
@@ -1046,6 +1065,7 @@
         /*debug_win_report_url=*/std::nullopt,
         context_recycler->private_aggregation_bindings()
             ->TakePrivateAggregationRequests(),
+        std::move(real_time_contributions),
         /*scoring_latency=*/elapsed, std::move(errors_out));
     return;
   }
@@ -1108,7 +1128,8 @@
           std::move(callback),
           /*scoring_latency=*/elapsed, std::move(errors_out),
           context_recycler->private_aggregation_bindings()
-              ->TakePrivateAggregationRequests());
+              ->TakePrivateAggregationRequests(),
+          std::move(real_time_contributions));
       return;
     }
 
@@ -1147,7 +1168,8 @@
             std::move(callback),
             /*scoring_latency=*/elapsed, std::move(errors_out),
             context_recycler->private_aggregation_bindings()
-                ->TakePrivateAggregationRequests());
+                ->TakePrivateAggregationRequests(),
+            std::move(real_time_contributions));
         return;
       }
       bid_in_seller_currency = result_idl.incoming_bid_in_seller_currency;
@@ -1228,7 +1250,8 @@
         std::move(callback),
         /*scoring_latency=*/elapsed, std::move(errors_out),
         context_recycler->private_aggregation_bindings()
-            ->TakePrivateAggregationRequests());
+            ->TakePrivateAggregationRequests(),
+        std::move(real_time_contributions));
     return;
   }
 
@@ -1243,6 +1266,7 @@
         context_recycler->for_debugging_only_bindings()->TakeWinReportUrl(),
         context_recycler->private_aggregation_bindings()
             ->TakePrivateAggregationRequests(),
+        std::move(real_time_contributions),
         /*scoring_latency=*/elapsed, std::move(errors_out));
     return;
   }
@@ -1261,7 +1285,8 @@
         std::move(callback),
         /*scoring_latency=*/elapsed, std::move(errors_out),
         context_recycler->private_aggregation_bindings()
-            ->TakePrivateAggregationRequests());
+            ->TakePrivateAggregationRequests(),
+        std::move(real_time_contributions));
     return;
   }
 
@@ -1282,7 +1307,8 @@
           std::move(callback),
           /*scoring_latency=*/elapsed, std::move(errors_out),
           context_recycler->private_aggregation_bindings()
-              ->TakePrivateAggregationRequests());
+              ->TakePrivateAggregationRequests(),
+          std::move(real_time_contributions));
       return;
     }
   } else if (browser_signals_other_seller &&
@@ -1309,6 +1335,7 @@
       context_recycler->for_debugging_only_bindings()->TakeWinReportUrl(),
       context_recycler->private_aggregation_bindings()
           ->TakePrivateAggregationRequests(),
+      std::move(real_time_contributions),
       /*scoring_latency=*/elapsed, std::move(errors_out));
 }
 
@@ -1596,7 +1623,8 @@
     ScoreAdCallbackInternal callback,
     base::TimeDelta scoring_latency,
     std::vector<std::string> errors,
-    PrivateAggregationRequests pa_requests) {
+    PrivateAggregationRequests pa_requests,
+    RealTimeReportingContributions real_time_contributions) {
   PostScoreAdCallbackToUserThread(
       std::move(callback), /*score=*/0,
       /*reject_reason=*/mojom::RejectReason::kNotAvailable,
@@ -1605,6 +1633,7 @@
       /*scoring_signals_data_version=*/std::nullopt,
       /*debug_loss_report_url=*/std::nullopt,
       /*debug_win_report_url=*/std::nullopt, std::move(pa_requests),
+      std::move(real_time_contributions),
       /*scoring_latency=*/scoring_latency, std::move(errors));
 }
 
@@ -1619,6 +1648,7 @@
     std::optional<GURL> debug_loss_report_url,
     std::optional<GURL> debug_win_report_url,
     PrivateAggregationRequests pa_requests,
+    RealTimeReportingContributions real_time_contributions,
     base::TimeDelta scoring_latency,
     std::vector<std::string> errors) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(v8_sequence_checker_);
@@ -1629,7 +1659,8 @@
                      bid_in_seller_currency, scoring_signals_data_version,
                      std::move(debug_loss_report_url),
                      std::move(debug_win_report_url), std::move(pa_requests),
-                     scoring_latency, std::move(errors)));
+                     std::move(real_time_contributions), scoring_latency,
+                     std::move(errors)));
 }
 
 void SellerWorklet::V8State::PostReportResultCallbackToUserThread(
@@ -1937,6 +1968,7 @@
     std::optional<GURL> debug_loss_report_url,
     std::optional<GURL> debug_win_report_url,
     PrivateAggregationRequests pa_requests,
+    RealTimeReportingContributions real_time_contributions,
     base::TimeDelta scoring_latency,
     std::vector<std::string> errors) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(user_sequence_checker_);
@@ -1957,7 +1989,7 @@
       score, reject_reason, std::move(component_auction_modified_bid_params),
       std::move(bid_in_seller_currency), scoring_signals_data_version,
       debug_loss_report_url, debug_win_report_url, std::move(pa_requests),
-      scoring_latency,
+      std::move(real_time_contributions), scoring_latency,
       mojom::ScoreAdDependencyLatencies::New(
           /*code_ready_latency=*/NullOptIfZero(task->wait_code),
           /*direct_from_seller_signals_latency=*/
diff --git a/content/services/auction_worklet/seller_worklet.h b/content/services/auction_worklet/seller_worklet.h
index 4b9ca01..24e0b6b 100644
--- a/content/services/auction_worklet/seller_worklet.h
+++ b/content/services/auction_worklet/seller_worklet.h
@@ -27,9 +27,9 @@
 #include "content/services/auction_worklet/context_recycler.h"
 #include "content/services/auction_worklet/direct_from_seller_signals_requester.h"
 #include "content/services/auction_worklet/public/mojom/auction_shared_storage_host.mojom.h"
-#include "content/services/auction_worklet/public/mojom/auction_worklet_service.mojom-forward.h"
 #include "content/services/auction_worklet/public/mojom/auction_worklet_service.mojom.h"
 #include "content/services/auction_worklet/public/mojom/private_aggregation_request.mojom.h"
+#include "content/services/auction_worklet/public/mojom/real_time_reporting.mojom.h"
 #include "content/services/auction_worklet/public/mojom/seller_worklet.mojom.h"
 #include "content/services/auction_worklet/trusted_signals.h"
 #include "content/services/auction_worklet/trusted_signals_request_manager.h"
@@ -64,6 +64,9 @@
   using PrivateAggregationRequests =
       std::vector<auction_worklet::mojom::PrivateAggregationRequestPtr>;
 
+  using RealTimeReportingContributions =
+      std::vector<auction_worklet::mojom::RealTimeReportingContributionPtr>;
+
   // Classification of how trusted signals related to this worklet.
   enum class SignalsOriginRelation {
     kNoTrustedSignals,
@@ -305,6 +308,7 @@
         std::optional<GURL> debug_loss_report_url,
         std::optional<GURL> debug_win_report_url,
         PrivateAggregationRequests pa_requests,
+        RealTimeReportingContributions real_time_contributions,
         base::TimeDelta scoring_latency,
         std::vector<std::string> errors)>;
     using ReportResultCallbackInternal =
@@ -405,7 +409,8 @@
         ScoreAdCallbackInternal callback,
         base::TimeDelta scoring_latency,
         std::vector<std::string> errors,
-        PrivateAggregationRequests pa_requests = {});
+        PrivateAggregationRequests pa_requests = {},
+        RealTimeReportingContributions real_time_contributions = {});
 
     void PostScoreAdCallbackToUserThread(
         ScoreAdCallbackInternal callback,
@@ -418,6 +423,7 @@
         std::optional<GURL> debug_loss_report_url,
         std::optional<GURL> debug_win_report_url,
         PrivateAggregationRequests pa_requests,
+        RealTimeReportingContributions real_time_contributions,
         base::TimeDelta scoring_latency,
         std::vector<std::string> errors);
 
@@ -514,6 +520,7 @@
       std::optional<GURL> debug_loss_report_url,
       std::optional<GURL> debug_win_report_url,
       PrivateAggregationRequests pa_requests,
+      RealTimeReportingContributions real_time_contributions,
       base::TimeDelta scoring_latency,
       std::vector<std::string> errors);
 
diff --git a/content/services/auction_worklet/seller_worklet_unittest.cc b/content/services/auction_worklet/seller_worklet_unittest.cc
index 006d7fc6..c07f7180 100644
--- a/content/services/auction_worklet/seller_worklet_unittest.cc
+++ b/content/services/auction_worklet/seller_worklet_unittest.cc
@@ -27,6 +27,8 @@
 #include "content/services/auction_worklet/auction_v8_helper.h"
 #include "content/services/auction_worklet/public/mojom/auction_network_events_handler.mojom.h"
 #include "content/services/auction_worklet/public/mojom/auction_worklet_service.mojom.h"
+#include "content/services/auction_worklet/public/mojom/private_aggregation_request.mojom.h"
+#include "content/services/auction_worklet/public/mojom/real_time_reporting.mojom.h"
 #include "content/services/auction_worklet/public/mojom/seller_worklet.mojom.h"
 #include "content/services/auction_worklet/worklet_devtools_debug_test_util.h"
 #include "content/services/auction_worklet/worklet_test_util.h"
@@ -52,8 +54,9 @@
 namespace auction_worklet {
 namespace {
 
-using PrivateAggregationRequests =
-    std::vector<mojom::PrivateAggregationRequestPtr>;
+using PrivateAggregationRequests = SellerWorklet::PrivateAggregationRequests;
+using RealTimeReportingContributions =
+    SellerWorklet::RealTimeReportingContributions;
 
 // Very short time used by some tests that want to wait until just before a
 // timer triggers.
@@ -124,6 +127,7 @@
       const std::optional<GURL>& debug_loss_report_url,
       const std::optional<GURL>& debug_win_report_url,
       PrivateAggregationRequests pa_requests,
+      RealTimeReportingContributions real_time_contributions,
       base::TimeDelta scoring_latency,
       mojom::ScoreAdDependencyLatenciesPtr score_ad_dependency_latencies,
       const std::vector<std::string>& errors)>;
@@ -154,6 +158,7 @@
       const std::optional<GURL>& debug_loss_report_url,
       const std::optional<GURL>& debug_win_report_url,
       PrivateAggregationRequests pa_requests,
+      RealTimeReportingContributions real_time_contributions,
       base::TimeDelta scoring_latency,
       mojom::ScoreAdDependencyLatenciesPtr score_ad_dependency_latencies,
       const std::vector<std::string>& errors) override {
@@ -162,7 +167,8 @@
              std::move(component_auction_modified_bid_params),
              std::move(bid_in_seller_currency),
              std::move(scoring_signals_data_version), debug_loss_report_url,
-             debug_win_report_url, std::move(pa_requests), scoring_latency,
+             debug_win_report_url, std::move(pa_requests),
+             std::move(real_time_contributions), scoring_latency,
              std::move(score_ad_dependency_latencies), errors);
   }
 
@@ -176,6 +182,7 @@
            const std::optional<GURL>& debug_loss_report_url,
            const std::optional<GURL>& debug_win_report_url,
            PrivateAggregationRequests pa_requests,
+           RealTimeReportingContributions real_time_contributions,
            base::TimeDelta scoring_latency,
            mojom::ScoreAdDependencyLatenciesPtr score_ad_dependency_latencies,
            const std::vector<std::string>& errors) {
@@ -272,13 +279,16 @@
       mojom::RejectReason expected_reject_reason =
           mojom::RejectReason::kNotAvailable,
       PrivateAggregationRequests expected_pa_requests = {},
+      RealTimeReportingContributions expected_real_time_contributions = {},
       std::optional<double> expected_bid_in_seller_currency = std::nullopt) {
     RunScoreAdWithJavascriptExpectingResult(
         CreateScoreAdScript(raw_return_value), expected_score, expected_errors,
         std::move(expected_component_auction_modified_bid_params),
         expected_data_version, expected_debug_loss_report_url,
         expected_debug_win_report_url, expected_reject_reason,
-        std::move(expected_pa_requests), expected_bid_in_seller_currency);
+        std::move(expected_pa_requests),
+        std::move(expected_real_time_contributions),
+        expected_bid_in_seller_currency);
   }
 
   // Behaves just like RunScoreAdWithReturnValueExpectingResult(), but
@@ -301,6 +311,7 @@
       mojom::RejectReason expected_reject_reason =
           mojom::RejectReason::kNotAvailable,
       PrivateAggregationRequests expected_pa_requests = {},
+      RealTimeReportingContributions expected_real_time_contributions = {},
       std::optional<double> expected_bid_in_seller_currency = std::nullopt) {
     AddJavascriptResponse(&url_loader_factory_, decision_logic_url_,
                           CreateScoreAdScript(raw_return_value),
@@ -313,7 +324,9 @@
         std::move(expected_component_auction_modified_bid_params),
         expected_data_version, expected_debug_loss_report_url,
         expected_debug_win_report_url, expected_reject_reason,
-        std::move(expected_pa_requests), expected_bid_in_seller_currency,
+        std::move(expected_pa_requests),
+        std::move(expected_real_time_contributions),
+        expected_bid_in_seller_currency,
         /*expected_score_ad_timeout=*/false,
         /*expected_signals_fetch_latency=*/std::nullopt,
         /*expected_code_ready_latency=*/std::nullopt, run_loop.QuitClosure());
@@ -339,6 +352,7 @@
       mojom::RejectReason expected_reject_reason =
           mojom::RejectReason::kNotAvailable,
       PrivateAggregationRequests expected_pa_requests = {},
+      RealTimeReportingContributions expected_real_time_contributions = {},
       std::optional<double> expected_bid_in_seller_currency = std::nullopt) {
     SCOPED_TRACE(javascript);
     AddJavascriptResponse(&url_loader_factory_, decision_logic_url_, javascript,
@@ -348,7 +362,9 @@
         std::move(expected_component_auction_modified_bid_params),
         expected_data_version, expected_debug_loss_report_url,
         expected_debug_win_report_url, expected_reject_reason,
-        std::move(expected_pa_requests), expected_bid_in_seller_currency);
+        std::move(expected_pa_requests),
+        std::move(expected_real_time_contributions),
+        expected_bid_in_seller_currency);
   }
 
   // Runs score_ad() script, checking result and invoking provided closure
@@ -364,6 +380,7 @@
       const std::optional<GURL>& expected_debug_win_report_url,
       mojom::RejectReason expected_reject_reason,
       PrivateAggregationRequests expected_pa_requests,
+      RealTimeReportingContributions expected_real_time_contributions,
       std::optional<double> expected_bid_in_seller_currency,
       bool expected_score_ad_timeout,
       std::optional<base::TimeDelta> expected_signals_fetch_latency,
@@ -391,6 +408,7 @@
                const std::optional<GURL>& expected_debug_loss_report_url,
                const std::optional<GURL>& expected_debug_win_report_url,
                PrivateAggregationRequests expected_pa_requests,
+               RealTimeReportingContributions expected_real_time_contributions,
                std::optional<double> expected_bid_in_seller_currency,
                std::optional<base::TimeDelta> expected_score_ad_timeout,
                std::optional<base::TimeDelta> expected_signals_fetch_latency,
@@ -405,6 +423,7 @@
                const std::optional<GURL>& debug_loss_report_url,
                const std::optional<GURL>& debug_win_report_url,
                PrivateAggregationRequests pa_requests,
+               RealTimeReportingContributions real_time_contributions,
                base::TimeDelta scoring_latency,
                mojom::ScoreAdDependencyLatenciesPtr
                    score_ad_dependency_latencies,
@@ -425,9 +444,11 @@
               EXPECT_EQ(expected_debug_loss_report_url, debug_loss_report_url);
               EXPECT_EQ(expected_debug_win_report_url, debug_win_report_url);
               EXPECT_EQ(expected_data_version, scoring_signals_data_version);
+              EXPECT_EQ(expected_pa_requests, pa_requests);
+              EXPECT_EQ(expected_real_time_contributions,
+                        real_time_contributions);
               EXPECT_EQ(expected_bid_in_seller_currency,
                         bid_in_seller_currency);
-              EXPECT_EQ(expected_pa_requests, pa_requests);
               if (expected_score_ad_timeout) {
                 // We only know that about the time of the timeout should have
                 // elapsed, and there may also be some thread skew.
@@ -450,6 +471,7 @@
             std::move(expected_component_auction_modified_bid_params),
             expected_data_version, expected_debug_loss_report_url,
             expected_debug_win_report_url, std::move(expected_pa_requests),
+            std::move(expected_real_time_contributions),
             expected_bid_in_seller_currency,
             expected_score_ad_timeout
                 ? std::make_optional(
@@ -493,6 +515,7 @@
       mojom::RejectReason expected_reject_reason =
           mojom::RejectReason::kNotAvailable,
       PrivateAggregationRequests expected_pa_requests = {},
+      RealTimeReportingContributions expected_real_time_contributions = {},
       std::optional<double> expected_bid_in_seller_currency = std::nullopt) {
     base::RunLoop run_loop;
     RunScoreAdOnWorkletAsync(
@@ -500,7 +523,9 @@
         std::move(expected_component_auction_modified_bid_params),
         expected_data_version, expected_debug_loss_report_url,
         expected_debug_win_report_url, expected_reject_reason,
-        std::move(expected_pa_requests), expected_bid_in_seller_currency,
+        std::move(expected_pa_requests),
+        std::move(expected_real_time_contributions),
+        expected_bid_in_seller_currency,
         /*expected_score_ad_timeout=*/false,
         /*expected_signals_fetch_latency=*/std::nullopt,
         /*expected_code_ready_latency=*/std::nullopt, run_loop.QuitClosure());
@@ -521,6 +546,7 @@
       mojom::RejectReason expected_reject_reason =
           mojom::RejectReason::kNotAvailable,
       PrivateAggregationRequests expected_pa_requests = {},
+      RealTimeReportingContributions expected_real_time_contributions = {},
       std::optional<double> expected_bid_in_seller_currency = std::nullopt) {
     auto seller_worklet = CreateWorklet();
     ASSERT_TRUE(seller_worklet);
@@ -529,7 +555,9 @@
         std::move(expected_component_auction_modified_bid_params),
         expected_data_version, expected_debug_loss_report_url,
         expected_debug_win_report_url, expected_reject_reason,
-        std::move(expected_pa_requests), expected_bid_in_seller_currency);
+        std::move(expected_pa_requests),
+        std::move(expected_real_time_contributions),
+        expected_bid_in_seller_currency);
   }
 
   // Configures `url_loader_factory_` to return a report_result() script created
@@ -1387,6 +1415,7 @@
       /*expected_reject_reason=*/
       mojom::RejectReason::kNotAvailable,
       /*expected_pa_requests=*/{},
+      /*expected_real_time_contributions=*/{},
       /*expected_bid_in_seller_currency=*/100);
 
   // When bid currency matches seller currency, incomingBidInSellerCurrency
@@ -1408,6 +1437,7 @@
       /*expected_reject_reason=*/
       mojom::RejectReason::kNotAvailable,
       /*expected_pa_requests=*/{},
+      /*expected_real_time_contributions=*/{},
       /*expected_bid_in_seller_currency=*/1);
 
   // ...can also have that same-currency bid directly forwarded.
@@ -1422,6 +1452,7 @@
       /*expected_reject_reason=*/
       mojom::RejectReason::kNotAvailable,
       /*expected_pa_requests=*/{},
+      /*expected_real_time_contributions=*/{},
       /*expected_bid_in_seller_currency=*/3.14);
 
   // This should also work if we use the number-only shorthand.
@@ -1435,6 +1466,7 @@
       /*expected_reject_reason=*/
       mojom::RejectReason::kNotAvailable,
       /*expected_pa_requests=*/{},
+      /*expected_real_time_contributions=*/{},
       /*expected_bid_in_seller_currency=*/3.14);
 }
 
@@ -1974,6 +2006,7 @@
       /*expected_debug_win_report_url=*/std::nullopt,
       mojom::RejectReason::kNotAvailable,
       /*expected_pa_requests=*/{},
+      /*expected_real_time_contributions=*/{},
       /*expected_bid_in_seller_currency=*/std::nullopt,
       /*expected_score_ad_timeout=*/false,
       /*expected_signals_fetch_latency=*/kDelay,
@@ -2007,6 +2040,7 @@
       /*expected_debug_win_report_url=*/std::nullopt,
       mojom::RejectReason::kNotAvailable,
       /*expected_pa_requests=*/{},
+      /*expected_real_time_contributions=*/{},
       /*expected_bid_in_seller_currency=*/std::nullopt,
       /*expected_score_ad_timeout=*/false,
       /*expected_signals_fetch_latency=*/std::nullopt,
@@ -2074,6 +2108,7 @@
                              /*expected_reject_reason=*/
                              mojom::RejectReason::kNotAvailable,
                              /*expected_pa_requests=*/{},
+                             /*expected_real_time_contributions=*/{},
                              /*expected_bid_in_seller_currency=*/std::nullopt,
                              /*expected_score_ad_timeout=*/false,
                              /*expected_signals_fetch_latency=*/std::nullopt,
@@ -2123,6 +2158,7 @@
                              /*expected_reject_reason=*/
                              mojom::RejectReason::kNotAvailable,
                              /*expected_pa_requests=*/{},
+                             /*expected_real_time_contributions=*/{},
                              /*expected_bid_in_seller_currency=*/std::nullopt,
                              /*expected_score_ad_timeout=*/false,
                              /*expected_signals_fetch_latency=*/std::nullopt,
@@ -2194,6 +2230,7 @@
                              /*expected_reject_reason=*/
                              mojom::RejectReason::kNotAvailable,
                              /*expected_pa_requests=*/{},
+                             /*expected_real_time_contributions=*/{},
                              /*expected_bid_in_seller_currency=*/std::nullopt,
                              /*expected_score_ad_timeout=*/false,
                              /*expected_signals_fetch_latency=*/std::nullopt,
@@ -2268,6 +2305,7 @@
                              /*expected_reject_reason=*/
                              mojom::RejectReason::kNotAvailable,
                              /*expected_pa_requests=*/{},
+                             /*expected_real_time_contributions=*/{},
                              /*expected_bid_in_seller_currency=*/std::nullopt,
                              /*expected_score_ad_timeout=*/false,
                              /*expected_signals_fetch_latency=*/std::nullopt,
@@ -2333,6 +2371,7 @@
                              /*expected_reject_reason=*/
                              mojom::RejectReason::kNotAvailable,
                              /*expected_pa_requests=*/{},
+                             /*expected_real_time_contributions=*/{},
                              /*expected_bid_in_seller_currency=*/std::nullopt,
                              /*expected_score_ad_timeout=*/false,
                              /*expected_signals_fetch_latency=*/std::nullopt,
@@ -2407,6 +2446,7 @@
                              /*expected_reject_reason=*/
                              mojom::RejectReason::kNotAvailable,
                              /*expected_pa_requests=*/{},
+                             /*expected_real_time_contributions=*/{},
                              /*expected_bid_in_seller_currency=*/std::nullopt,
                              /*expected_score_ad_timeout=*/false,
                              /*expected_signals_fetch_latency=*/std::nullopt,
@@ -2509,6 +2549,7 @@
         /*expected_reject_reason=*/
         mojom::RejectReason::kNotAvailable,
         /*expected_pa_requests=*/{},
+        /*expected_real_time_contributions=*/{},
         /*expected_bid_in_seller_currency=*/std::nullopt,
         /*expected_score_ad_timeout=*/false,
         /*expected_signals_fetch_latency=*/std::nullopt,
@@ -2611,6 +2652,7 @@
       /*expected_reject_reason=*/
       mojom::RejectReason::kNotAvailable,
       /*expected_pa_requests=*/{},
+      /*expected_real_time_contributions=*/{},
       /*expected_bid_in_seller_currency=*/std::nullopt,
       /*expected_score_ad_timeout=*/false,
       /*expected_signals_fetch_latency=*/std::nullopt,
@@ -2627,6 +2669,7 @@
       /*expected_reject_reason=*/
       mojom::RejectReason::kNotAvailable,
       /*expected_pa_requests=*/{},
+      /*expected_real_time_contributions=*/{},
       /*expected_bid_in_seller_currency=*/std::nullopt,
       /*expected_score_ad_timeout=*/false,
       /*expected_signals_fetch_latency=*/std::nullopt,
@@ -3815,18 +3858,20 @@
           seller_timeout_,
           /*trace_id=*/1,
           TestScoreAdClient::Create(base::BindLambdaForTesting(
-              [&run_loop](double score, mojom::RejectReason reject_reason,
-                          mojom::ComponentAuctionModifiedBidParamsPtr
-                              component_auction_modified_bid_params,
-                          std::optional<double> bid_in_seller_currency,
-                          std::optional<uint32_t> scoring_signals_data_version,
-                          const std::optional<GURL>& debug_loss_report_url,
-                          const std::optional<GURL>& debug_win_report_url,
-                          PrivateAggregationRequests pa_requests,
-                          base::TimeDelta scoring_latency,
-                          mojom::ScoreAdDependencyLatenciesPtr
-                              score_ad_dependency_latencies,
-                          const std::vector<std::string>& errors) {
+              [&run_loop](
+                  double score, mojom::RejectReason reject_reason,
+                  mojom::ComponentAuctionModifiedBidParamsPtr
+                      component_auction_modified_bid_params,
+                  std::optional<double> bid_in_seller_currency,
+                  std::optional<uint32_t> scoring_signals_data_version,
+                  const std::optional<GURL>& debug_loss_report_url,
+                  const std::optional<GURL>& debug_win_report_url,
+                  PrivateAggregationRequests pa_requests,
+                  RealTimeReportingContributions real_time_contributions,
+                  base::TimeDelta scoring_latency,
+                  mojom::ScoreAdDependencyLatenciesPtr
+                      score_ad_dependency_latencies,
+                  const std::vector<std::string>& errors) {
                 EXPECT_EQ(2, score);
                 EXPECT_FALSE(scoring_signals_data_version.has_value());
                 EXPECT_TRUE(errors.empty());
@@ -3921,6 +3966,7 @@
                 const std::optional<GURL>& debug_loss_report_url,
                 const std::optional<GURL>& debug_win_report_url,
                 PrivateAggregationRequests pa_requests,
+                RealTimeReportingContributions real_time_contributions,
                 base::TimeDelta scoring_latency,
                 mojom::ScoreAdDependencyLatenciesPtr
                     score_ad_dependency_latencies,
@@ -4009,6 +4055,7 @@
                 const std::optional<GURL>& debug_loss_report_url,
                 const std::optional<GURL>& debug_win_report_url,
                 PrivateAggregationRequests pa_requests,
+                RealTimeReportingContributions real_time_contributions,
                 base::TimeDelta scoring_latency,
                 mojom::ScoreAdDependencyLatenciesPtr
                     score_ad_dependency_latencies,
@@ -4110,6 +4157,7 @@
       /*expected_reject_reason=*/
       mojom::RejectReason::kNotAvailable,
       /*expected_pa_requests=*/{},
+      /*expected_real_time_contributions=*/{},
       /*expected_bid_in_seller_currency=*/std::nullopt,
       /*expected_score_ad_timeout=*/false,
       /*expected_signals_fetch_latency=*/std::nullopt,
@@ -4199,6 +4247,7 @@
       /*expected_reject_reason=*/
       mojom::RejectReason::kNotAvailable,
       /*expected_pa_requests=*/{},
+      /*expected_real_time_contributions=*/{},
       /*expected_bid_in_seller_currency=*/std::nullopt,
       /*expected_score_ad_timeout=*/false,
       /*expected_signals_fetch_latency=*/std::nullopt,
@@ -4218,6 +4267,7 @@
       /*expected_reject_reason=*/
       mojom::RejectReason::kNotAvailable,
       /*expected_pa_requests=*/{},
+      /*expected_real_time_contributions=*/{},
       /*expected_bid_in_seller_currency=*/std::nullopt,
       /*expected_score_ad_timeout=*/false,
       /*expected_signals_fetch_latency=*/std::nullopt,
@@ -4344,6 +4394,7 @@
       /*expected_reject_reason=*/
       mojom::RejectReason::kNotAvailable,
       /*expected_pa_requests=*/{},
+      /*expected_real_time_contributions=*/{},
       /*expected_bid_in_seller_currency=*/std::nullopt,
       /*expected_score_ad_timeout=*/false,
       /*expected_signals_fetch_latency=*/std::nullopt,
@@ -4363,6 +4414,7 @@
       /*expected_reject_reason=*/
       mojom::RejectReason::kNotAvailable,
       /*expected_pa_requests=*/{},
+      /*expected_real_time_contributions=*/{},
       /*expected_bid_in_seller_currency=*/std::nullopt,
       /*expected_score_ad_timeout=*/false,
       /*expected_signals_fetch_latency=*/std::nullopt,
@@ -4517,6 +4569,7 @@
       /*expected_reject_reason=*/
       mojom::RejectReason::kNotAvailable,
       /*expected_pa_requests=*/{},
+      /*expected_real_time_contributions=*/{},
       /*expected_bid_in_seller_currency=*/std::nullopt,
       /*expected_score_ad_timeout=*/false,
       /*expected_signals_fetch_latency=*/std::nullopt,
@@ -4602,6 +4655,7 @@
       /*expected_reject_reason=*/
       mojom::RejectReason::kNotAvailable,
       /*expected_pa_requests=*/{},
+      /*expected_real_time_contributions=*/{},
       /*expected_bid_in_seller_currency=*/std::nullopt,
       /*expected_score_ad_timeout=*/false,
       /*expected_signals_fetch_latency=*/std::nullopt,
@@ -4671,6 +4725,7 @@
       /*expected_reject_reason=*/
       mojom::RejectReason::kNotAvailable,
       /*expected_pa_requests=*/{},
+      /*expected_real_time_contributions=*/{},
       /*expected_bid_in_seller_currency=*/std::nullopt,
       /*expected_score_ad_timeout=*/false,
       /*expected_signals_fetch_latency=*/std::nullopt,
@@ -5170,6 +5225,7 @@
       /*expected_debug_win_report_url=*/std::nullopt,
       mojom::RejectReason::kNotAvailable,
       /*expected_pa_requests=*/{},
+      /*expected_real_time_contributions=*/{},
       /*expected_bid_in_seller_currency=*/std::nullopt,
       /*expected_score_ad_timeout=*/true,
       /*expected_signals_fetch_latency=*/std::nullopt,
@@ -5218,6 +5274,7 @@
       /*expected_debug_win_report_url=*/std::nullopt,
       mojom::RejectReason::kNotAvailable,
       /*expected_pa_requests=*/{},
+      /*expected_real_time_contributions=*/{},
       /*expected_bid_in_seller_currency=*/std::nullopt,
       /*expected_score_ad_timeout=*/true,
       /*expected_signals_fetch_latency=*/std::nullopt,
@@ -5471,6 +5528,7 @@
                         const std::optional<GURL>& debug_loss_report_url,
                         const std::optional<GURL>& debug_win_report_url,
                         PrivateAggregationRequests pa_requests,
+                        RealTimeReportingContributions real_time_contributions,
                         base::TimeDelta scoring_latency,
                         mojom::ScoreAdDependencyLatenciesPtr
                             score_ad_dependency_latencies,
@@ -5495,12 +5553,12 @@
 class SellerWorkletSampleDebugReportsDisabledTest : public SellerWorkletTest {
  public:
   SellerWorkletSampleDebugReportsDisabledTest() {
-    scoped_feature_list_.InitAndDisableFeature(
+    feature_list_.InitAndDisableFeature(
         blink::features::kFledgeSampleDebugReports);
   }
 
  private:
-  base::test::ScopedFeatureList scoped_feature_list_;
+  base::test::ScopedFeatureList feature_list_;
 };
 
 TEST_F(SellerWorkletSampleDebugReportsDisabledTest,
@@ -5514,12 +5572,11 @@
 class SellerWorkletPrivateAggregationEnabledTest : public SellerWorkletTest {
  public:
   SellerWorkletPrivateAggregationEnabledTest() {
-    scoped_feature_list_.InitAndEnableFeature(
-        blink::features::kPrivateAggregationApi);
+    feature_list_.InitAndEnableFeature(blink::features::kPrivateAggregationApi);
   }
 
  private:
-  base::test::ScopedFeatureList scoped_feature_list_;
+  base::test::ScopedFeatureList feature_list_;
 };
 
 TEST_F(SellerWorkletPrivateAggregationEnabledTest, ScoreAd) {
@@ -5545,6 +5602,7 @@
           mojom::AggregatableReportForEventContribution::New(
               /*bucket=*/mojom::ForEventSignalBucket::NewIdBucket(234),
               /*value=*/mojom::ForEventSignalValue::NewIntValue(56),
+              /*filtering_id=*/std::nullopt,
               /*event_type=*/"reserved.win")),
       blink::mojom::AggregationServiceMode::kDefault,
       blink::mojom::DebugModeDetails::New());
@@ -5555,6 +5613,7 @@
                   absl::MakeInt128(/*high=*/1,
                                    /*low=*/0)),
               /*value=*/mojom::ForEventSignalValue::NewIntValue(2),
+              /*filtering_id=*/std::nullopt,
               /*event_type=*/"reserved.win")),
       blink::mojom::AggregationServiceMode::kDefault,
       blink::mojom::DebugModeDetails::New());
@@ -5740,6 +5799,46 @@
         /*expected_reject_reason=*/mojom::RejectReason::kNotAvailable,
         std::move(expected_pa_requests));
   }
+
+  // Filtering IDs are specified
+  {
+    base::test::ScopedFeatureList scoped_feature_list{
+        blink::features::kPrivateAggregationApiFilteringIds};
+
+    PrivateAggregationRequests expected_pa_requests;
+    expected_pa_requests.push_back(mojom::PrivateAggregationRequest::New(
+        mojom::AggregatableReportContribution::NewHistogramContribution(
+            blink::mojom::AggregatableReportHistogramContribution::New(
+                /*bucket=*/123,
+                /*value=*/45,
+                /*filtering_id=*/0)),
+        blink::mojom::AggregationServiceMode::kDefault,
+        blink::mojom::DebugModeDetails::New()));
+    expected_pa_requests.push_back(mojom::PrivateAggregationRequest::New(
+        mojom::AggregatableReportContribution::NewForEventContribution(
+            mojom::AggregatableReportForEventContribution::New(
+                /*bucket=*/mojom::ForEventSignalBucket::NewIdBucket(234),
+                /*value=*/mojom::ForEventSignalValue::NewIntValue(56),
+                /*filtering_id=*/255,
+                /*event_type=*/"reserved.win")),
+        blink::mojom::AggregationServiceMode::kDefault,
+        blink::mojom::DebugModeDetails::New()));
+
+    RunScoreAdWithJavascriptExpectingResult(
+        CreateScoreAdScript("5", R"(
+            privateAggregation.contributeToHistogram(
+                {bucket: 123n, value: 45, filteringId: 0n});
+            privateAggregation.contributeToHistogramOnEvent(
+                "reserved.win", {bucket: 234n, value: 56, filteringId: 255n});
+        )"),
+        5, /*expected_errors=*/{},
+        mojom::ComponentAuctionModifiedBidParamsPtr(),
+        /*expected_data_version=*/std::nullopt,
+        /*expected_debug_loss_report_url=*/std::nullopt,
+        /*expected_debug_win_report_url=*/std::nullopt,
+        /*expected_reject_reason=*/mojom::RejectReason::kNotAvailable,
+        std::move(expected_pa_requests));
+  }
 }
 
 TEST_F(SellerWorkletPrivateAggregationEnabledTest, ReportResult) {
@@ -5764,6 +5863,7 @@
           mojom::AggregatableReportForEventContribution::New(
               /*bucket=*/mojom::ForEventSignalBucket::NewIdBucket(234),
               /*value=*/mojom::ForEventSignalValue::NewIntValue(56),
+              /*filtering_id=*/std::nullopt,
               /*event_type=*/"reserved.win")),
       blink::mojom::AggregationServiceMode::kDefault,
       blink::mojom::DebugModeDetails::New());
@@ -5979,17 +6079,55 @@
         {"https://url.test/:12 Uncaught TypeError: enableDebugMode may be "
          "called at most once."});
   }
+
+  // Filtering IDs are specified
+  {
+    base::test::ScopedFeatureList scoped_feature_list{
+        blink::features::kPrivateAggregationApiFilteringIds};
+
+    PrivateAggregationRequests expected_pa_requests;
+    expected_pa_requests.push_back(mojom::PrivateAggregationRequest::New(
+        mojom::AggregatableReportContribution::NewHistogramContribution(
+            blink::mojom::AggregatableReportHistogramContribution::New(
+                /*bucket=*/123,
+                /*value=*/45,
+                /*filtering_id=*/0)),
+        blink::mojom::AggregationServiceMode::kDefault,
+        blink::mojom::DebugModeDetails::New()));
+    expected_pa_requests.push_back(mojom::PrivateAggregationRequest::New(
+        mojom::AggregatableReportContribution::NewForEventContribution(
+            mojom::AggregatableReportForEventContribution::New(
+                /*bucket=*/mojom::ForEventSignalBucket::NewIdBucket(234),
+                /*value=*/mojom::ForEventSignalValue::NewIntValue(56),
+                /*filtering_id=*/255,
+                /*event_type=*/"reserved.win")),
+        blink::mojom::AggregationServiceMode::kDefault,
+        blink::mojom::DebugModeDetails::New()));
+
+    RunReportResultCreatedScriptExpectingResult(
+        "5",
+        R"(
+            privateAggregation.contributeToHistogram(
+                {bucket: 123n, value: 45, filteringId: 0n});
+            privateAggregation.contributeToHistogramOnEvent(
+                "reserved.win", {bucket: 234n, value: 56, filteringId: 255n});
+        )",
+        /*expected_signals_for_winner=*/"5",
+        /*expected_report_url=*/std::nullopt, /*expected_ad_beacon_map=*/{},
+        std::move(expected_pa_requests),
+        /*expected_errors=*/{});
+  }
 }
 
 class SellerWorkletPrivateAggregationDisabledTest : public SellerWorkletTest {
  public:
   SellerWorkletPrivateAggregationDisabledTest() {
-    scoped_feature_list_.InitAndDisableFeature(
+    feature_list_.InitAndDisableFeature(
         blink::features::kPrivateAggregationApi);
   }
 
  private:
-  base::test::ScopedFeatureList scoped_feature_list_;
+  base::test::ScopedFeatureList feature_list_;
 };
 
 TEST_F(SellerWorkletPrivateAggregationDisabledTest, ScoreAd) {
@@ -6189,6 +6327,7 @@
                              /*expected_debug_win_report_url=*/std::nullopt,
                              mojom::RejectReason::kNotAvailable,
                              /*expected_pa_requests=*/{},
+                             /*expected_real_time_contributions=*/{},
                              /*expected_bid_in_seller_currency=*/std::nullopt,
                              /*expected_score_ad_timeout=*/false,
                              /*expected_signals_fetch_latency=*/std::nullopt,
@@ -6296,6 +6435,7 @@
       /*expected_debug_win_report_url=*/std::nullopt,
       mojom::RejectReason::kNotAvailable,
       /*expected_pa_requests=*/{},
+      /*expected_real_time_contributions=*/{},
       /*expected_bid_in_seller_currency=*/std::nullopt,
       /*expected_score_ad_timeout=*/false,
       /*expected_signals_fetch_latency=*/std::nullopt,
@@ -6356,5 +6496,135 @@
       /*expected_data_version=*/std::nullopt);
 }
 
+// Need to use SYSTEM_TIME, because scoring latency uses elapsed_timer, which is
+// always 0 if not using SYSTEM_TIME.
+class SellerWorkletRealTimeReportingEnabledTest : public SellerWorkletTest {
+ public:
+  SellerWorkletRealTimeReportingEnabledTest()
+      : SellerWorkletTest(
+            base::test::TaskEnvironment::TimeSource::SYSTEM_TIME) {
+    feature_list_.InitAndEnableFeature(
+        blink::features::kFledgeRealTimeReporting);
+  }
+
+ private:
+  base::test::ScopedFeatureList feature_list_;
+};
+
+TEST_F(SellerWorkletRealTimeReportingEnabledTest, RealTimeReporting) {
+  mojom::RealTimeReportingContribution expected_histogram(
+      /*bucket=*/100, /*priority_weight=*/0.5,
+      /*latency_threshold=*/std::nullopt);
+
+  // Worklet latency contribution is tested in ScriptTimeout.
+  // Cannot reliably test worklet latency contribution here since the script
+  // takes 0ms to run some times, which is not higher than the smallest allowed
+  // latency threshold (0ms), in which case the contribution will be dropped.
+  constexpr char kExtraCode[] = R"(
+realTimeReporting.contributeToRealTimeHistogram(100, {priorityWeight: 0.5})
+)";
+
+  RealTimeReportingContributions expected_real_time_contributions;
+  expected_real_time_contributions.push_back(expected_histogram.Clone());
+
+  RunScoreAdWithJavascriptExpectingResult(
+      CreateScoreAdScript("5", kExtraCode), 5, /*expected_errors=*/{},
+      mojom::ComponentAuctionModifiedBidParamsPtr(),
+      /*expected_data_version=*/std::nullopt,
+      /*expected_debug_loss_report_url=*/std::nullopt,
+      /*expected_debug_win_report_url=*/std::nullopt,
+      /*expected_reject_reason=*/mojom::RejectReason::kNotAvailable,
+      /*expected_pa_requests=*/{}, std::move(expected_real_time_contributions));
+}
+
+TEST_F(SellerWorkletRealTimeReportingEnabledTest, InvalidScore) {
+  mojom::RealTimeReportingContribution expected_histogram(
+      /*bucket=*/100, /*priority_weight=*/0.5,
+      /*latency_threshold=*/std::nullopt);
+
+  constexpr char kExtraCode[] = R"(
+realTimeReporting.contributeToRealTimeHistogram(100, {priorityWeight: 0.5})
+)";
+
+  RealTimeReportingContributions expected_real_time_contributions;
+  expected_real_time_contributions.push_back(expected_histogram.Clone());
+
+  RunScoreAdWithJavascriptExpectingResult(
+      CreateScoreAdScript("\"invalid_score\"", kExtraCode), 0,
+      /*expected_errors=*/
+      {"https://url.test/ scoreAd() return: Value passed as dictionary is "
+       "neither object, null, nor undefined."},
+      mojom::ComponentAuctionModifiedBidParamsPtr(),
+      /*expected_data_version=*/std::nullopt,
+      /*expected_debug_loss_report_url=*/std::nullopt,
+      /*expected_debug_win_report_url=*/std::nullopt,
+      /*expected_reject_reason=*/mojom::RejectReason::kNotAvailable,
+      /*expected_pa_requests=*/{}, std::move(expected_real_time_contributions));
+}
+
+TEST_F(SellerWorkletRealTimeReportingEnabledTest, ScriptTimeout) {
+  // Set timeout to a small number, and use a while loop in the script to let it
+  // timeout. Then the execution time would be higher than the latency threshold
+  // of 1ms thus the latency contribution will be kept.
+  seller_timeout_ = base::Milliseconds(3);
+
+  mojom::RealTimeReportingContribution expected_histogram(
+      /*bucket=*/100, /*priority_weight=*/0.5,
+      /*latency_threshold=*/std::nullopt);
+  mojom::RealTimeReportingContribution expected_latency_histogram(
+      /*bucket=*/200, /*priority_weight=*/2, /*latency_threshold=*/1);
+  constexpr char kExtraCode[] = R"(
+realTimeReporting.contributeToRealTimeHistogram(100, {priorityWeight: 0.5});
+realTimeReporting.contributeOnWorkletLatency(
+    200, {priorityWeight: 2, latencyThreshold: 1});
+while (1);
+)";
+
+  RealTimeReportingContributions expected_real_time_contributions;
+  expected_real_time_contributions.push_back(expected_histogram.Clone());
+  expected_real_time_contributions.push_back(
+      expected_latency_histogram.Clone());
+
+  RunScoreAdWithJavascriptExpectingResult(
+      CreateScoreAdScript("5", kExtraCode), 0,
+      /*expected_errors=*/
+      {"https://url.test/ execution of `scoreAd` timed out."},
+      mojom::ComponentAuctionModifiedBidParamsPtr(),
+      /*expected_data_version=*/std::nullopt,
+      /*expected_debug_loss_report_url=*/std::nullopt,
+      /*expected_debug_win_report_url=*/std::nullopt,
+      /*expected_reject_reason=*/mojom::RejectReason::kNotAvailable,
+      /*expected_pa_requests=*/{}, std::move(expected_real_time_contributions));
+}
+
+// contributeOnWorkletLatency's is dropped when the script's latency does not
+// exceed the threshold.
+TEST_F(SellerWorkletRealTimeReportingEnabledTest,
+       NotExceedingLatencyThreshold) {
+  mojom::RealTimeReportingContribution expected_histogram(
+      /*bucket=*/100, /*priority_weight=*/0.5,
+      /*latency_threshold=*/std::nullopt);
+  constexpr char kExtraCode[] = R"(
+realTimeReporting.contributeToRealTimeHistogram(100, {priorityWeight: 0.5});
+realTimeReporting.contributeOnWorkletLatency(
+    200, {priorityWeight: 2, latencyThreshold: 10000000})
+)";
+
+  // Only contributeToRealTimeHistogram's contribution is kept.
+  // contributeOnWorkletLatency's is filtered out since the script's latency
+  // didn't exceed the threshold.
+  RealTimeReportingContributions expected_real_time_contributions;
+  expected_real_time_contributions.push_back(expected_histogram.Clone());
+
+  RunScoreAdWithJavascriptExpectingResult(
+      CreateScoreAdScript("5", kExtraCode), 5, /*expected_errors=*/{},
+      mojom::ComponentAuctionModifiedBidParamsPtr(),
+      /*expected_data_version=*/std::nullopt,
+      /*expected_debug_loss_report_url=*/std::nullopt,
+      /*expected_debug_win_report_url=*/std::nullopt,
+      /*expected_reject_reason=*/mojom::RejectReason::kNotAvailable,
+      /*expected_pa_requests=*/{}, std::move(expected_real_time_contributions));
+}
+
 }  // namespace
 }  // namespace auction_worklet
diff --git a/content/test/content_test_bundle_data.filelist b/content/test/content_test_bundle_data.filelist
index d625299..ccbf977 100644
--- a/content/test/content_test_bundle_data.filelist
+++ b/content/test/content_test_bundle_data.filelist
@@ -7075,6 +7075,8 @@
 data/private_aggregation/aggregatable_report_goldens/latest/private_key.txt
 data/private_aggregation/aggregatable_report_goldens/latest/public_key.json
 data/private_aggregation/aggregatable_report_goldens/latest/report_1.json
+data/private_aggregation/aggregatable_report_goldens/latest/report_10.json
+data/private_aggregation/aggregatable_report_goldens/latest/report_10_cleartext_payloads.json
 data/private_aggregation/aggregatable_report_goldens/latest/report_1_cleartext_payloads.json
 data/private_aggregation/aggregatable_report_goldens/latest/report_2.json
 data/private_aggregation/aggregatable_report_goldens/latest/report_2_cleartext_payloads.json
@@ -7088,6 +7090,10 @@
 data/private_aggregation/aggregatable_report_goldens/latest/report_6_cleartext_payloads.json
 data/private_aggregation/aggregatable_report_goldens/latest/report_7.json
 data/private_aggregation/aggregatable_report_goldens/latest/report_7_cleartext_payloads.json
+data/private_aggregation/aggregatable_report_goldens/latest/report_8.json
+data/private_aggregation/aggregatable_report_goldens/latest/report_8_cleartext_payloads.json
+data/private_aggregation/aggregatable_report_goldens/latest/report_9.json
+data/private_aggregation/aggregatable_report_goldens/latest/report_9_cleartext_payloads.json
 data/private_aggregation/aggregatable_report_goldens/version_/private_key.txt
 data/private_aggregation/aggregatable_report_goldens/version_/public_key.json
 data/push_state.html
diff --git a/content/test/data/accept-header.html b/content/test/data/accept-header.html
index 76b4d82..8f3924e 100644
--- a/content/test/data/accept-header.html
+++ b/content/test/data/accept-header.html
@@ -42,11 +42,10 @@
 navigator.serviceWorker.register('/service_worker.js')
 .catch(function(error) {});
 
-// TOOD(crbug.com/v8/13856): Use `with` once it's unflagged
-import("/test-css-module", { assert: { type: "css" } })
+import("/test-css-module", { with: { type: "css" } })
 .catch(function(error) {});
 
-import("/test-json-module", { assert: { type: "json" } })
+import("/test-json-module", { with: { type: "json" } })
 .catch(function(error) {});
 </script>
 </html>
diff --git a/content/test/data/private_aggregation/aggregatable_report_goldens/latest/report_10.json b/content/test/data/private_aggregation/aggregatable_report_goldens/latest/report_10.json
new file mode 100644
index 0000000..9070a5e
--- /dev/null
+++ b/content/test/data/private_aggregation/aggregatable_report_goldens/latest/report_10.json
@@ -0,0 +1,12 @@
+{
+  "aggregation_coordinator_origin": "https://publickeyservice.msmt.aws.privacysandboxservices.com",
+  "aggregation_service_payloads": [
+    {
+      "debug_cleartext_payload": "omRkYXRhlKJldmFsdWVEAAAAAmZidWNrZXRQAAAAAAAAAAAAAAAAAAAAAaJldmFsdWVEAAAAAGZidWNrZXRQAAAAAAAAAAAAAAAAAAAAAKJldmFsdWVEAAAAAGZidWNrZXRQAAAAAAAAAAAAAAAAAAAAAKJldmFsdWVEAAAAAGZidWNrZXRQAAAAAAAAAAAAAAAAAAAAAKJldmFsdWVEAAAAAGZidWNrZXRQAAAAAAAAAAAAAAAAAAAAAKJldmFsdWVEAAAAAGZidWNrZXRQAAAAAAAAAAAAAAAAAAAAAKJldmFsdWVEAAAAAGZidWNrZXRQAAAAAAAAAAAAAAAAAAAAAKJldmFsdWVEAAAAAGZidWNrZXRQAAAAAAAAAAAAAAAAAAAAAKJldmFsdWVEAAAAAGZidWNrZXRQAAAAAAAAAAAAAAAAAAAAAKJldmFsdWVEAAAAAGZidWNrZXRQAAAAAAAAAAAAAAAAAAAAAKJldmFsdWVEAAAAAGZidWNrZXRQAAAAAAAAAAAAAAAAAAAAAKJldmFsdWVEAAAAAGZidWNrZXRQAAAAAAAAAAAAAAAAAAAAAKJldmFsdWVEAAAAAGZidWNrZXRQAAAAAAAAAAAAAAAAAAAAAKJldmFsdWVEAAAAAGZidWNrZXRQAAAAAAAAAAAAAAAAAAAAAKJldmFsdWVEAAAAAGZidWNrZXRQAAAAAAAAAAAAAAAAAAAAAKJldmFsdWVEAAAAAGZidWNrZXRQAAAAAAAAAAAAAAAAAAAAAKJldmFsdWVEAAAAAGZidWNrZXRQAAAAAAAAAAAAAAAAAAAAAKJldmFsdWVEAAAAAGZidWNrZXRQAAAAAAAAAAAAAAAAAAAAAKJldmFsdWVEAAAAAGZidWNrZXRQAAAAAAAAAAAAAAAAAAAAAKJldmFsdWVEAAAAAGZidWNrZXRQAAAAAAAAAAAAAAAAAAAAAGlvcGVyYXRpb25paGlzdG9ncmFt",
+      "key_id": "example_id",
+      "payload": "gIzoD0vsU5Ef52LdJJXzMFaRRNyEvG6eJn8bivrLgzaQ0380D8OrViNpIMV7U2I9xsRMGwmdyY0GhhzQcjhON1wOxc2+Fa7tuXVJqYJoBZw2HUCSy+fKE1bZ1Fo46VGhc9Sb6XqWeefz+b2fb4Eb2oBBTdm/20y/oqRPieO8EeCJSzUoF+TLxjIfpD8qfT9EAp5lnPx0cfVOIjMvIZw2Zs8iZYe40JrhpSoCegq0hpUhLdKwFR7Ec1dT6ImZcMZS9s0t7tfou3IYshwtNr+fa77IX8pU4KQ65dFbj/i13dPUbc0lZ73NvCtB/EzdFsHX/xfzQ/SAM5F2NaMnrL+NpYhdxPk0XfIcUlsYekZtn4KrW2FPP2UrNcmi+9ARCzA4BYkYKWsJUoiLAHnR9Muc2YTay1T2JCQU6NZuiV7FghJLtvdTY75O6b+6S99XeD8712vjmpPZ5+t9IMY6alra541GGdvzt1Doga75HoD0c7tVCCsOlpKYkGc8hppN1iPNWeZe+RGD/8M9f4uPzR+s+fI2Pd0mGdRNiJ7MwJjoVM5RalEZohm3RPPAxriF223V2cNuZPgk1mqQ/+O3r6JQwXILyfHF4dpQtg/yQtt0no+MCvPL1rb9TW4Vjx2rx9NihE913DQJDInyWXTEG1UWfYg3GP4F3PxfE9R+ohMH34vp6hFAnWEI+vjv9fhVaBtaepBSdZ+rB1PKoXvfunxCM+1rp9kqI85I0v0lHZCVJxiJjgmzcdWhtag8lkfJ1GtieG+hsKJ5yhRLuIQcGiGuUX2B+b6+O2/zw+YX6lnYb57ov7jaHR6eHJloL38PyAdrnRYNfXElQJD//QR68N8tKtUhfXdlvgTQEMkB0bFIU+y9kxNyVc2hihnpdj7ps5JE5sXnm+BoCXxIDNp2LofY95oCKUaBFo8VW3MyqK76uK9uByFRIkp5OsxlnyfvQo5hxcmm4G08RUEGV8cAhlsOOVYgcGCAEqQMET61otZAuKpJksyDzVAvwl8aJn4TuEgd40KLddN5DPIEpXDdGTjmoWQJOTB1erNClzAz"
+    }
+  ],
+  "debug_key": "123",
+  "shared_info": "{\"api\":\"protected-audience\",\"debug_mode\":\"enabled\",\"report_id\":\"21abd97f-73e8-4b88-9389-a9fee6abda5e\",\"reporting_origin\":\"https://report.test\",\"scheduled_report_time\":\"1234486400\",\"version\":\"0.1\"}"
+}
diff --git a/content/test/data/private_aggregation/aggregatable_report_goldens/latest/report_10_cleartext_payloads.json b/content/test/data/private_aggregation/aggregatable_report_goldens/latest/report_10_cleartext_payloads.json
new file mode 100644
index 0000000..7bf83a4
--- /dev/null
+++ b/content/test/data/private_aggregation/aggregatable_report_goldens/latest/report_10_cleartext_payloads.json
@@ -0,0 +1,3 @@
+[
+  "omRkYXRhlKJldmFsdWVEAAAAAmZidWNrZXRQAAAAAAAAAAAAAAAAAAAAAaJldmFsdWVEAAAAAGZidWNrZXRQAAAAAAAAAAAAAAAAAAAAAKJldmFsdWVEAAAAAGZidWNrZXRQAAAAAAAAAAAAAAAAAAAAAKJldmFsdWVEAAAAAGZidWNrZXRQAAAAAAAAAAAAAAAAAAAAAKJldmFsdWVEAAAAAGZidWNrZXRQAAAAAAAAAAAAAAAAAAAAAKJldmFsdWVEAAAAAGZidWNrZXRQAAAAAAAAAAAAAAAAAAAAAKJldmFsdWVEAAAAAGZidWNrZXRQAAAAAAAAAAAAAAAAAAAAAKJldmFsdWVEAAAAAGZidWNrZXRQAAAAAAAAAAAAAAAAAAAAAKJldmFsdWVEAAAAAGZidWNrZXRQAAAAAAAAAAAAAAAAAAAAAKJldmFsdWVEAAAAAGZidWNrZXRQAAAAAAAAAAAAAAAAAAAAAKJldmFsdWVEAAAAAGZidWNrZXRQAAAAAAAAAAAAAAAAAAAAAKJldmFsdWVEAAAAAGZidWNrZXRQAAAAAAAAAAAAAAAAAAAAAKJldmFsdWVEAAAAAGZidWNrZXRQAAAAAAAAAAAAAAAAAAAAAKJldmFsdWVEAAAAAGZidWNrZXRQAAAAAAAAAAAAAAAAAAAAAKJldmFsdWVEAAAAAGZidWNrZXRQAAAAAAAAAAAAAAAAAAAAAKJldmFsdWVEAAAAAGZidWNrZXRQAAAAAAAAAAAAAAAAAAAAAKJldmFsdWVEAAAAAGZidWNrZXRQAAAAAAAAAAAAAAAAAAAAAKJldmFsdWVEAAAAAGZidWNrZXRQAAAAAAAAAAAAAAAAAAAAAKJldmFsdWVEAAAAAGZidWNrZXRQAAAAAAAAAAAAAAAAAAAAAKJldmFsdWVEAAAAAGZidWNrZXRQAAAAAAAAAAAAAAAAAAAAAGlvcGVyYXRpb25paGlzdG9ncmFt"
+]
\ No newline at end of file
diff --git a/content/test/data/private_aggregation/aggregatable_report_goldens/latest/report_8.json b/content/test/data/private_aggregation/aggregatable_report_goldens/latest/report_8.json
new file mode 100644
index 0000000..282f36b
--- /dev/null
+++ b/content/test/data/private_aggregation/aggregatable_report_goldens/latest/report_8.json
@@ -0,0 +1,12 @@
+{
+  "aggregation_coordinator_origin": "https://publickeyservice.msmt.aws.privacysandboxservices.com",
+  "aggregation_service_payloads": [
+    {
+      "debug_cleartext_payload": "omRkYXRhlKJldmFsdWVEAAAAAmZidWNrZXRQAAAAAAAAAAAAAAAAAAAAAaJldmFsdWVEAAAAAGZidWNrZXRQAAAAAAAAAAAAAAAAAAAAAKJldmFsdWVEAAAAAGZidWNrZXRQAAAAAAAAAAAAAAAAAAAAAKJldmFsdWVEAAAAAGZidWNrZXRQAAAAAAAAAAAAAAAAAAAAAKJldmFsdWVEAAAAAGZidWNrZXRQAAAAAAAAAAAAAAAAAAAAAKJldmFsdWVEAAAAAGZidWNrZXRQAAAAAAAAAAAAAAAAAAAAAKJldmFsdWVEAAAAAGZidWNrZXRQAAAAAAAAAAAAAAAAAAAAAKJldmFsdWVEAAAAAGZidWNrZXRQAAAAAAAAAAAAAAAAAAAAAKJldmFsdWVEAAAAAGZidWNrZXRQAAAAAAAAAAAAAAAAAAAAAKJldmFsdWVEAAAAAGZidWNrZXRQAAAAAAAAAAAAAAAAAAAAAKJldmFsdWVEAAAAAGZidWNrZXRQAAAAAAAAAAAAAAAAAAAAAKJldmFsdWVEAAAAAGZidWNrZXRQAAAAAAAAAAAAAAAAAAAAAKJldmFsdWVEAAAAAGZidWNrZXRQAAAAAAAAAAAAAAAAAAAAAKJldmFsdWVEAAAAAGZidWNrZXRQAAAAAAAAAAAAAAAAAAAAAKJldmFsdWVEAAAAAGZidWNrZXRQAAAAAAAAAAAAAAAAAAAAAKJldmFsdWVEAAAAAGZidWNrZXRQAAAAAAAAAAAAAAAAAAAAAKJldmFsdWVEAAAAAGZidWNrZXRQAAAAAAAAAAAAAAAAAAAAAKJldmFsdWVEAAAAAGZidWNrZXRQAAAAAAAAAAAAAAAAAAAAAKJldmFsdWVEAAAAAGZidWNrZXRQAAAAAAAAAAAAAAAAAAAAAKJldmFsdWVEAAAAAGZidWNrZXRQAAAAAAAAAAAAAAAAAAAAAGlvcGVyYXRpb25paGlzdG9ncmFt",
+      "key_id": "example_id",
+      "payload": "pSlIGKIlNST28M05ZPeZQKGgeGlYcHuRV/0ppXqc5WtoCaREhdFQy66dIMVhsgJOf/ChgFrgF27j0Udd7ItofeVjRj6K19yxFiFr2t8dtm1uETy0XCOfB6wgt/YDn92YQ42XMUULdigem2BS964UN6olQAdiBaG2/aI3ZqXoR6PvV47ZBgpcl+vprUkN6dKeODX1X5Ie0TzyXIg+MLeTMm8TSO/WjK4SDOwN+AHNN+HTMH++3Q6j8b8pMWB0m1TN9LVKQHXhXa4CZPKG51t1ZZAP1PZAoEbG/fx4k7o9cYK+uaewen1CIhkxKiiWZkDyysRxdpr2RaI1QtB5qCFw65XkTspW+Kz0ZQPWPfGIAFIwEo14iZcEB4kIrRZGvknmBlWRhL+n3dPgITOcEkxCdSRdrOlB3mu/BwzODIavVC+8DLv2g1twHbGbgEXDQd0iXCwpCzdoqsWw+XDCOQlOGhKNXwFuwhB+DvC3sVMVTVCLtcZ1dOBAaMtFIGOUdqKZhTkdnafd0Oz7082LMKzBO9tKOpSewGmaaB7QOhpJTrWfX+La7Nnor9W7UOMlVXnt9Le/x9671t3tmApm+SEF1oGLlg2jFAAjTu9qYSTjPtagabmjcgJkaf/n1ea7V7h5qYmNIa7Duz99fw/iFG751rrv3mbY5zc9fzJLTNCj7OV+/rjVDQQA6vxMvf8YLcuCzDAhQIn/uVkvu5eGfAV6muERHRdzYpflF/re/xLplfvkumKMNobwDN2QRMayAD46CcAhzPEDMmwTDWiCTyA466dGog/z29arHmRNeLO3Y06sQ31xLPajo+gOdE6rB6n6gGZTyASJbOdNPgADEfJ3UNaAeHJcFlJ2h+5HBGXzAJeh0DAc9MNbSXioxPQvUGxfTr7lDtm7x9FBg25oVzcDVtUSIHE76jIvzx4uU8iZ6cpMq7KRR/rnn/vIXclSQMnaL/sQcjFQuO4STelUddJl1d6JE8B0dSaErQmvDmf2hYC2fRxb78TqxAz/NH0Xhz6p53eJ64m5LrUUr374E1jWyv5hscVXxzAA50CO"
+    }
+  ],
+  "debug_key": "123",
+  "shared_info": "{\"api\":\"protected-audience\",\"debug_mode\":\"enabled\",\"report_id\":\"21abd97f-73e8-4b88-9389-a9fee6abda5e\",\"reporting_origin\":\"https://report.test\",\"scheduled_report_time\":\"1234486400\",\"version\":\"0.1\"}"
+}
diff --git a/content/test/data/private_aggregation/aggregatable_report_goldens/latest/report_8_cleartext_payloads.json b/content/test/data/private_aggregation/aggregatable_report_goldens/latest/report_8_cleartext_payloads.json
new file mode 100644
index 0000000..7bf83a4
--- /dev/null
+++ b/content/test/data/private_aggregation/aggregatable_report_goldens/latest/report_8_cleartext_payloads.json
@@ -0,0 +1,3 @@
+[
+  "omRkYXRhlKJldmFsdWVEAAAAAmZidWNrZXRQAAAAAAAAAAAAAAAAAAAAAaJldmFsdWVEAAAAAGZidWNrZXRQAAAAAAAAAAAAAAAAAAAAAKJldmFsdWVEAAAAAGZidWNrZXRQAAAAAAAAAAAAAAAAAAAAAKJldmFsdWVEAAAAAGZidWNrZXRQAAAAAAAAAAAAAAAAAAAAAKJldmFsdWVEAAAAAGZidWNrZXRQAAAAAAAAAAAAAAAAAAAAAKJldmFsdWVEAAAAAGZidWNrZXRQAAAAAAAAAAAAAAAAAAAAAKJldmFsdWVEAAAAAGZidWNrZXRQAAAAAAAAAAAAAAAAAAAAAKJldmFsdWVEAAAAAGZidWNrZXRQAAAAAAAAAAAAAAAAAAAAAKJldmFsdWVEAAAAAGZidWNrZXRQAAAAAAAAAAAAAAAAAAAAAKJldmFsdWVEAAAAAGZidWNrZXRQAAAAAAAAAAAAAAAAAAAAAKJldmFsdWVEAAAAAGZidWNrZXRQAAAAAAAAAAAAAAAAAAAAAKJldmFsdWVEAAAAAGZidWNrZXRQAAAAAAAAAAAAAAAAAAAAAKJldmFsdWVEAAAAAGZidWNrZXRQAAAAAAAAAAAAAAAAAAAAAKJldmFsdWVEAAAAAGZidWNrZXRQAAAAAAAAAAAAAAAAAAAAAKJldmFsdWVEAAAAAGZidWNrZXRQAAAAAAAAAAAAAAAAAAAAAKJldmFsdWVEAAAAAGZidWNrZXRQAAAAAAAAAAAAAAAAAAAAAKJldmFsdWVEAAAAAGZidWNrZXRQAAAAAAAAAAAAAAAAAAAAAKJldmFsdWVEAAAAAGZidWNrZXRQAAAAAAAAAAAAAAAAAAAAAKJldmFsdWVEAAAAAGZidWNrZXRQAAAAAAAAAAAAAAAAAAAAAKJldmFsdWVEAAAAAGZidWNrZXRQAAAAAAAAAAAAAAAAAAAAAGlvcGVyYXRpb25paGlzdG9ncmFt"
+]
\ No newline at end of file
diff --git a/content/test/data/private_aggregation/aggregatable_report_goldens/latest/report_9.json b/content/test/data/private_aggregation/aggregatable_report_goldens/latest/report_9.json
new file mode 100644
index 0000000..0e863bbf
--- /dev/null
+++ b/content/test/data/private_aggregation/aggregatable_report_goldens/latest/report_9.json
@@ -0,0 +1,10 @@
+{
+  "aggregation_coordinator_origin": "https://publickeyservice.msmt.aws.privacysandboxservices.com",
+  "aggregation_service_payloads": [
+    {
+      "key_id": "example_id",
+      "payload": "985neFLbqDdw4xmuFer+3sbOfUKnrNrwbT4QLNaIhysOxMiE/GFdhrF9poCDLd7ctYDAoG2MuQoog9AH59jOfO6ULyf6cEKf/h8sN4mcTdEIxP6xhkl9u9Xmk6PX+x8ZsSq/QHGxnsuv0pHHWQFf1x/naTfF41UO4aDdxNbORqfPkUsmkK2AipGKFjvlzYBWitAaHI4/ey1BRrTWlcWuXHmSJgmi3dchyDkmOMLP5+DAJk0yXK/ZE/fNqftxgf67XvlT7gnNbUol/O2DF06LAjW+RglKW1skh30PBruuJ8lE8BwjCwt/adQWKkbO5HjOh4lT20KVcp4ZLzjtZYD/zG4fzgSJxuTUV8c4ZFKjMUkhaSy5CJrU4rAoH0JdORFGPl14/RetbV6a8NuRLi9O0FLCwyFkP81FuDkl+Q8wRbgG/1VLQ20gkJcpu9rqrYfu42o7R64jm0ihFVCpvdzjBUGpHQ0l17muineG2+vaCpVG8Q8idgwAE88X1hIGS2QumD5jUXLsWAxGsSHxf+k+FbwpM1Ww/96rZkPQzz5AZ6CUF3zdQUjQehsbdYJrJGNLQ1k/jykYGNcOPSc7KzpZgOkrgXSv4k7CIkyaX8w7b9qGoDCqRaL9P+pyxjpvix7DtsydCj1cp27Ra8Q674TptxImnmmw/tnhYZMePvyb77I4JB4H0ZgVgJrQgcx3Tbaht/DQF7hzmAICYysNeR1CzyDl14NXKmwxbWDo7qc6jRYVHUdUbxeESQkOfjaqs0RUJCgsmIVGPh9GwVi4jpavTsNQMDJ7SwUW3njTYCyraX8lNsHj6fcNqQJzQAcKg7wusLmmL8hc2M7JjdIX61FRynmCLqox6mynxbipoSHjWtC65Iee8sQCzgf5bAhmVM/J4PhMvFk9D0S2bFD+V3hMY1M+CVJ5K6chWz8klJu4dzFHQZAi013q4iBQiCHEfRRyQt9Z18gFLbLiWHDCSo2bHPWT1L4TACH1krEhUjY/F1VMLX1IRV2HoDXFtKVo6+sviDPqTtmaRm/IDLIoW5SBFwkcxJDhPMz+24m+"
+    }
+  ],
+  "shared_info": "{\"api\":\"protected-audience\",\"report_id\":\"21abd97f-73e8-4b88-9389-a9fee6abda5e\",\"reporting_origin\":\"https://report.test\",\"scheduled_report_time\":\"1234486400\",\"version\":\"0.1\"}"
+}
\ No newline at end of file
diff --git a/content/test/data/private_aggregation/aggregatable_report_goldens/latest/report_9_cleartext_payloads.json b/content/test/data/private_aggregation/aggregatable_report_goldens/latest/report_9_cleartext_payloads.json
new file mode 100644
index 0000000..7bf83a4
--- /dev/null
+++ b/content/test/data/private_aggregation/aggregatable_report_goldens/latest/report_9_cleartext_payloads.json
@@ -0,0 +1,3 @@
+[
+  "omRkYXRhlKJldmFsdWVEAAAAAmZidWNrZXRQAAAAAAAAAAAAAAAAAAAAAaJldmFsdWVEAAAAAGZidWNrZXRQAAAAAAAAAAAAAAAAAAAAAKJldmFsdWVEAAAAAGZidWNrZXRQAAAAAAAAAAAAAAAAAAAAAKJldmFsdWVEAAAAAGZidWNrZXRQAAAAAAAAAAAAAAAAAAAAAKJldmFsdWVEAAAAAGZidWNrZXRQAAAAAAAAAAAAAAAAAAAAAKJldmFsdWVEAAAAAGZidWNrZXRQAAAAAAAAAAAAAAAAAAAAAKJldmFsdWVEAAAAAGZidWNrZXRQAAAAAAAAAAAAAAAAAAAAAKJldmFsdWVEAAAAAGZidWNrZXRQAAAAAAAAAAAAAAAAAAAAAKJldmFsdWVEAAAAAGZidWNrZXRQAAAAAAAAAAAAAAAAAAAAAKJldmFsdWVEAAAAAGZidWNrZXRQAAAAAAAAAAAAAAAAAAAAAKJldmFsdWVEAAAAAGZidWNrZXRQAAAAAAAAAAAAAAAAAAAAAKJldmFsdWVEAAAAAGZidWNrZXRQAAAAAAAAAAAAAAAAAAAAAKJldmFsdWVEAAAAAGZidWNrZXRQAAAAAAAAAAAAAAAAAAAAAKJldmFsdWVEAAAAAGZidWNrZXRQAAAAAAAAAAAAAAAAAAAAAKJldmFsdWVEAAAAAGZidWNrZXRQAAAAAAAAAAAAAAAAAAAAAKJldmFsdWVEAAAAAGZidWNrZXRQAAAAAAAAAAAAAAAAAAAAAKJldmFsdWVEAAAAAGZidWNrZXRQAAAAAAAAAAAAAAAAAAAAAKJldmFsdWVEAAAAAGZidWNrZXRQAAAAAAAAAAAAAAAAAAAAAKJldmFsdWVEAAAAAGZidWNrZXRQAAAAAAAAAAAAAAAAAAAAAKJldmFsdWVEAAAAAGZidWNrZXRQAAAAAAAAAAAAAAAAAAAAAGlvcGVyYXRpb25paGlzdG9ncmFt"
+]
\ No newline at end of file
diff --git a/device/BUILD.gn b/device/BUILD.gn
index 7c04da0a..fa72c574 100644
--- a/device/BUILD.gn
+++ b/device/BUILD.gn
@@ -209,10 +209,7 @@
       "//services/data_decoder/public/cpp:test_support",
     ]
 
-    data = [
-      "fido/enclave/verify/testdata/logentry.json",
-      "fido/enclave/verify/testdata/logentry_noverification.json",
-    ]
+    data = [ "fido/enclave/verify/testdata/" ]
 
     if (is_chromeos_lacros) {
       deps += [ "//chromeos/startup" ]
diff --git a/device/fido/BUILD.gn b/device/fido/BUILD.gn
index 8f0503f..9d5b32bd 100644
--- a/device/fido/BUILD.gn
+++ b/device/fido/BUILD.gn
@@ -173,6 +173,8 @@
         "enclave/enclave_protocol_utils.h",
         "enclave/enclave_websocket_client.cc",
         "enclave/enclave_websocket_client.h",
+        "enclave/metrics.cc",
+        "enclave/metrics.h",
         "enclave/transact.cc",
         "enclave/transact.h",
         "enclave/types.cc",
@@ -231,6 +233,8 @@
         "pin_internal.cc",
         "pin_internal.h",
         "platform_credential_store.h",
+        "platform_user_verification_policy.cc",
+        "platform_user_verification_policy.h",
         "reset_request_handler.cc",
         "reset_request_handler.h",
         "set_pin_request_handler.cc",
diff --git a/device/fido/enclave/enclave_authenticator.cc b/device/fido/enclave/enclave_authenticator.cc
index 09e8767..e1abf48f 100644
--- a/device/fido/enclave/enclave_authenticator.cc
+++ b/device/fido/enclave/enclave_authenticator.cc
@@ -19,6 +19,7 @@
 #include "crypto/random.h"
 #include "device/fido/discoverable_credential_metadata.h"
 #include "device/fido/enclave/constants.h"
+#include "device/fido/enclave/metrics.h"
 #include "device/fido/enclave/transact.h"
 #include "device/fido/enclave/types.h"
 #include "device/fido/fido_constants.h"
@@ -132,6 +133,8 @@
     return;
   }
 
+  RecordEvent(Event::kMakeCredential);
+
   Transact(network_context_factory_, GetEnclaveIdentity(),
            std::move(ui_request_->access_token),
            /*reauthentication_token=*/std::nullopt,
@@ -188,6 +191,8 @@
     return;
   }
 
+  RecordEvent(Event::kGetAssertion);
+
   Transact(network_context_factory_, GetEnclaveIdentity(),
            std::move(ui_request_->access_token),
            /*reauthentication_token=*/std::nullopt,
diff --git a/device/fido/enclave/metrics.cc b/device/fido/enclave/metrics.cc
new file mode 100644
index 0000000..5f57334
--- /dev/null
+++ b/device/fido/enclave/metrics.cc
@@ -0,0 +1,15 @@
+// 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 "device/fido/enclave/metrics.h"
+
+#include "base/metrics/histogram_functions.h"
+
+namespace device::enclave {
+
+void RecordEvent(Event event) {
+  base::UmaHistogramEnumeration("WebAuthentication.EnclaveEvent", event);
+}
+
+}  // namespace device::enclave
diff --git a/device/fido/enclave/metrics.h b/device/fido/enclave/metrics.h
new file mode 100644
index 0000000..085ed13
--- /dev/null
+++ b/device/fido/enclave/metrics.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 DEVICE_FIDO_ENCLAVE_METRICS_H_
+#define DEVICE_FIDO_ENCLAVE_METRICS_H_
+
+#include "base/component_export.h"
+
+namespace device::enclave {
+
+// A list of enclave-related events that are reported to UMA. Do not renumber
+// as the values are persisted.
+enum class Event {
+  kOnboarding = 0,
+  kOnboardingRejected = 1,
+  kOnboardingAccepted = 2,
+  kRecoveryShown = 3,
+  kRecoverySuccessful = 4,
+  kGetAssertion = 5,
+  kMakeCredential = 6,
+
+  kMaxValue = 6,
+};
+
+COMPONENT_EXPORT(DEVICE_FIDO) void RecordEvent(Event event);
+
+}  // namespace device::enclave
+
+#endif  // DEVICE_FIDO_ENCLAVE_METRICS_H_
diff --git a/device/fido/enclave/verify/hash.cc b/device/fido/enclave/verify/hash.cc
index b0104af..f199d8b 100644
--- a/device/fido/enclave/verify/hash.cc
+++ b/device/fido/enclave/verify/hash.cc
@@ -6,11 +6,44 @@
 
 #include <vector>
 
+#include "base/json/json_value_converter.h"
+
 namespace device::enclave {
 
+namespace {
+
+bool ConvertToBytes(const base::Value* s, std::vector<uint8_t>* result) {
+  if (!s->is_string()) {
+    return false;
+  }
+  std::vector<uint8_t> output(s->GetString().begin(), s->GetString().end());
+  *result = std::move(output);
+  return true;
+}
+
+bool CheckHashType(const base::Value* s, HashType* result) {
+  if (!s->is_string()) {
+    return false;
+  }
+  if (s->GetString() != "sha256") {
+    return false;
+  }
+  *result = kSHA256;
+  return true;
+}
+
+}  // namespace
+
 Hash::Hash(std::vector<uint8_t> bytes, HashType hash_type)
     : bytes(std::move(bytes)), hash_type(hash_type) {}
 Hash::Hash() = default;
 Hash::~Hash() = default;
+Hash::Hash(const Hash& hash) = default;
+
+void Hash::RegisterJSONConverter(base::JSONValueConverter<Hash>* converter) {
+  converter->RegisterCustomValueField("algorithm", &Hash::hash_type,
+                                      &CheckHashType);
+  converter->RegisterCustomValueField("value", &Hash::bytes, &ConvertToBytes);
+}
 
 }  // namespace device::enclave
diff --git a/device/fido/enclave/verify/hash.h b/device/fido/enclave/verify/hash.h
index 59bf7f7..eb700ce2 100644
--- a/device/fido/enclave/verify/hash.h
+++ b/device/fido/enclave/verify/hash.h
@@ -8,16 +8,22 @@
 #include <cstdint>
 #include <vector>
 
+#include "base/component_export.h"
+#include "base/json/json_value_converter.h"
+
 namespace device::enclave {
 
 enum HashType {
   kSHA256,
 };
 
-struct Hash {
+struct COMPONENT_EXPORT(DEVICE_FIDO) Hash {
   Hash(std::vector<uint8_t> bytes, HashType hash_type);
   Hash();
   ~Hash();
+  Hash(const Hash& hash);
+
+  static void RegisterJSONConverter(base::JSONValueConverter<Hash>* converter);
 
   std::vector<uint8_t> bytes;
   HashType hash_type = kSHA256;
diff --git a/device/fido/enclave/verify/rekor.cc b/device/fido/enclave/verify/rekor.cc
index de5f6ec4..a2085e7b 100644
--- a/device/fido/enclave/verify/rekor.cc
+++ b/device/fido/enclave/verify/rekor.cc
@@ -8,6 +8,7 @@
 #include <string>
 #include <string_view>
 
+#include "base/base64.h"
 #include "base/json/json_reader.h"
 #include "base/json/json_value_converter.h"
 #include "base/time/time.h"
@@ -87,6 +88,33 @@
                                  &LogEntryVerification::signed_entry_timestamp);
 }
 
+void PublicKey::RegisterJSONConverter(
+    base::JSONValueConverter<PublicKey>* converter) {
+  converter->RegisterStringField("content", &PublicKey::content);
+}
+
+void GenericSignature::RegisterJSONConverter(
+    base::JSONValueConverter<GenericSignature>* converter) {
+  converter->RegisterStringField("content", &GenericSignature::content);
+  converter->RegisterStringField("format", &GenericSignature::format);
+  converter->RegisterNestedField("publicKey", &GenericSignature::public_key);
+}
+
+void Data::RegisterJSONConverter(base::JSONValueConverter<Data>* converter) {
+  converter->RegisterNestedField("hash", &Data::hash);
+}
+
+void Spec::RegisterJSONConverter(base::JSONValueConverter<Spec>* converter) {
+  converter->RegisterNestedField("data", &Spec::data);
+  converter->RegisterNestedField("signature", &Spec::generic_signature);
+}
+
+void Body::RegisterJSONConverter(base::JSONValueConverter<Body>* converter) {
+  converter->RegisterStringField("apiVersion", &Body::api_version);
+  converter->RegisterStringField("kind", &Body::kind);
+  converter->RegisterNestedField("spec", &Body::spec);
+}
+
 std::optional<LogEntry> GetRekorLogEntry(base::span<const uint8_t> log_entry) {
   std::string_view json = reinterpret_cast<const char*>(log_entry.data());
   std::optional<base::Value> log_entry_json = base::JSONReader::Read(json);
@@ -101,4 +129,25 @@
   return log_entry_result;
 }
 
+std::optional<Body> GetRekorLogEntryBody(base::span<const uint8_t> log_entry) {
+  std::optional<LogEntry> log_entry_output = GetRekorLogEntry(log_entry);
+  if (!log_entry_output.has_value()) {
+    return std::nullopt;
+  }
+  std::string body;
+  if (!base::Base64Decode(log_entry_output->body, &body)) {
+    return std::nullopt;
+  }
+  std::optional<base::Value> body_json = base::JSONReader::Read(body);
+  if (!body_json.has_value()) {
+    return std::nullopt;
+  }
+  Body body_result;
+  base::JSONValueConverter<Body> converter;
+  if (!converter.Convert(body_json.value(), &body_result)) {
+    return std::nullopt;
+  }
+  return body_result;
+}
+
 }  // namespace device::enclave
diff --git a/device/fido/enclave/verify/rekor.h b/device/fido/enclave/verify/rekor.h
index aa19de2..4abd8db 100644
--- a/device/fido/enclave/verify/rekor.h
+++ b/device/fido/enclave/verify/rekor.h
@@ -62,17 +62,23 @@
 // Based on
 // <https://github.com/sigstore/rekor/blob/2978cdc26fdf8f5bfede8459afd9735f0f231a2a/pkg/generated/models/rekord_v001_schema.go#L551.>
 struct PublicKey {
+  static void RegisterJSONConverter(
+      base::JSONValueConverter<PublicKey>* converter);
+
   // Base64 content of a public key.
-  std::string_view content;
+  std::string content;
 };
 
 // Struct representing a signature in the body of a Rekor LogEntry.
 // Based on
 // <https://github.com/sigstore/rekor/blob/2978cdc26fdf8f5bfede8459afd9735f0f231a2a/pkg/generated/models/rekord_v001_schema.go#L383>
 struct GenericSignature {
+  static void RegisterJSONConverter(
+      base::JSONValueConverter<GenericSignature>* converter);
+
   // Base64 content that is signed.
-  std::string_view content;
-  std::string_view format;
+  std::string content;
+  std::string format;
   PublicKey public_key;
 };
 
@@ -80,6 +86,8 @@
 // Based on
 // <https://github.com/sigstore/rekor/blob/2978cdc26fdf8f5bfede8459afd9735f0f231a2a/pkg/generated/models/rekord_v001_schema.go#L179.>
 struct Data {
+  static void RegisterJSONConverter(base::JSONValueConverter<Data>* converter);
+
   Hash hash;
 };
 
@@ -87,14 +95,18 @@
 // Based on
 // <https://github.com/sigstore/rekor/blob/2978cdc26fdf8f5bfede8459afd9735f0f231a2a/pkg/generated/models/rekord_v001_schema.go#L39.>
 struct Spec {
+  static void RegisterJSONConverter(base::JSONValueConverter<Spec>* converter);
+
   Data data;
   GenericSignature generic_signature;
 };
 
 // Struct representing the body in a Rekor LogEntry.
 struct Body {
-  std::string_view api_version;
-  std::string_view kind;
+  static void RegisterJSONConverter(base::JSONValueConverter<Body>* converter);
+
+  std::string api_version;
+  std::string kind;
   Spec spec;
 };
 
@@ -134,7 +146,8 @@
 
 // Parses the given bytes into a Rekor `LogEntry` object, and returns its
 // `body` parsed into an instance of `Body`.
-std::optional<Body> GetRekorLogEntryBody(base::span<const uint8_t> log_entry);
+std::optional<Body> COMPONENT_EXPORT(DEVICE_FIDO)
+    GetRekorLogEntryBody(base::span<const uint8_t> log_entry);
 
 // Parses a blob into a Rekor log entry and verifies the signature in
 // `signed_entry_timestamp` using Rekor's public key.
diff --git a/device/fido/enclave/verify/rekor_unittest.cc b/device/fido/enclave/verify/rekor_unittest.cc
index c036316..b3b3390 100644
--- a/device/fido/enclave/verify/rekor_unittest.cc
+++ b/device/fido/enclave/verify/rekor_unittest.cc
@@ -11,6 +11,7 @@
 #include "base/files/file_util.h"
 #include "base/json/json_reader.h"
 #include "base/path_service.h"
+#include "device/fido/enclave/verify/hash.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 namespace device::enclave {
@@ -35,6 +36,25 @@
     "MEYCIQCd0RrIJrMSCBhTAwl+HOMU/9w81hs7xCXZRElft/"
     "jcCAIhAN07e5BrXL4xn8ZeZTAnfsCwyjO9e3NaTNt4zAFj96mV";
 
+constexpr char kApiVersion[] = "0.0.1";
+
+constexpr char kKind[] = "rekord";
+
+constexpr char kGenericSignatureContent[] =
+    "MEYCIQCyEHfQZxVza8oSg6GrPp9nZ3ETs+NWH6nXT7uHP2z/HgIhAJJGN56+pOSPF4gm/"
+    "J5As2306wOQD2KxIZmi9Gjyh6R9";
+
+constexpr char kGenericSignatureFormat[] = "x509";
+
+constexpr char kGenericSignaturePublicKeyContent[] =
+    "LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUZrd0V3WUhLb1pJemowQ0FRWUlLb1pJemowRE"
+    "FRY0RRZ0FFYndSY1FZMll2VWhwOFFwakJEakRZdGdyakFXSgphL2V3VUt1N1VSS1ZNYnppTjhJ"
+    "ZHp1N25lS2N2ZjJRS1BrWVhSZXBseTZmT3VmZFhaSitTUFZxWEJnPT0KLS0tLS1FTkQgUFVCTE"
+    "lDIEtFWS0tLS0tCg==";
+
+constexpr char kHashValue[] =
+    "961f60acb9e5723b7b301860481529bc3f1661e5086c29cd56242e321ba7f959";
+
 constexpr uint64_t kLogIndex = 74497915;
 
 constexpr base::Time kIntegratedTime = base::Time::FromTimeT(1709113639);
@@ -78,4 +98,24 @@
   EXPECT_FALSE(log_entry->verification.has_value());
 }
 
+TEST(RekorTest, GetRekorLogEntryBody) {
+  std::string json = GetJsonFromFile("logentry");
+  base::span<const uint8_t> span = base::make_span(
+      reinterpret_cast<const uint8_t*>(json.data()), json.size());
+  std::optional<Body> log_entry_body = GetRekorLogEntryBody(span);
+  EXPECT_TRUE(log_entry_body.has_value());
+  EXPECT_EQ(log_entry_body->api_version, kApiVersion);
+  EXPECT_EQ(log_entry_body->kind, kKind);
+  EXPECT_EQ(log_entry_body->spec.generic_signature.content,
+            kGenericSignatureContent);
+  EXPECT_EQ(log_entry_body->spec.generic_signature.format,
+            kGenericSignatureFormat);
+  EXPECT_EQ(log_entry_body->spec.generic_signature.public_key.content,
+            kGenericSignaturePublicKeyContent);
+  const std::vector<uint8_t>& bytes = log_entry_body->spec.data.hash.bytes;
+  std::string hash_value = std::string(bytes.begin(), bytes.end());
+  EXPECT_EQ(hash_value, kHashValue);
+  EXPECT_EQ(log_entry_body->spec.data.hash.hash_type, HashType::kSHA256);
+}
+
 }  // namespace device::enclave
diff --git a/device/fido/enclave/verify/testdata/test_digest b/device/fido/enclave/verify/testdata/test_digest
new file mode 100644
index 0000000..635c4de4
--- /dev/null
+++ b/device/fido/enclave/verify/testdata/test_digest
@@ -0,0 +1 @@
+æ8´xøð²Âª³Ûý?Ößâ×´H"AþXV~7®ö
\ No newline at end of file
diff --git a/device/fido/enclave/verify/testdata/test_pub_key.der b/device/fido/enclave/verify/testdata/test_pub_key.der
new file mode 100644
index 0000000..dd2c0cae
--- /dev/null
+++ b/device/fido/enclave/verify/testdata/test_pub_key.der
Binary files differ
diff --git a/device/fido/enclave/verify/testdata/test_pub_key.pem b/device/fido/enclave/verify/testdata/test_pub_key.pem
new file mode 100644
index 0000000..a80edb0
--- /dev/null
+++ b/device/fido/enclave/verify/testdata/test_pub_key.pem
@@ -0,0 +1,4 @@
+-----BEGIN PUBLIC KEY-----
+MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEW3MhBjYvdcDkc+HoYwJ3Z86gTSAC
+W6a1VcpPvhiGMR97rwOAAphMaz1Kl/+x5VCwqKkgTkG3S+U+GbAiFN/MSA==
+-----END PUBLIC KEY-----
diff --git a/device/fido/enclave/verify/testdata/test_signature b/device/fido/enclave/verify/testdata/test_signature
new file mode 100644
index 0000000..bc6c41d
--- /dev/null
+++ b/device/fido/enclave/verify/testdata/test_signature
Binary files differ
diff --git a/device/fido/enclave/verify/utils.cc b/device/fido/enclave/verify/utils.cc
index 030bbf9..1176906 100644
--- a/device/fido/enclave/verify/utils.cc
+++ b/device/fido/enclave/verify/utils.cc
@@ -9,9 +9,36 @@
 
 #include "base/base64.h"
 #include "base/strings/string_util.h"
+#include "third_party/boringssl/src/include/openssl/base.h"
+#include "third_party/boringssl/src/include/openssl/bytestring.h"
+#include "third_party/boringssl/src/include/openssl/ec.h"
+#include "third_party/boringssl/src/include/openssl/ec_key.h"
+#include "third_party/boringssl/src/include/openssl/ecdsa.h"
+#include "third_party/boringssl/src/include/openssl/evp.h"
+#include "third_party/boringssl/src/include/openssl/mem.h"
 
 namespace device::enclave {
 
+namespace {
+
+bssl::UniquePtr<EC_KEY> GetECKey(base::span<const uint8_t> public_key) {
+  CBS cbs;
+  CBS_init(&cbs, public_key.data(), public_key.size());
+  bssl::UniquePtr<EVP_PKEY> parsed_key(EVP_parse_public_key(&cbs));
+  if (!parsed_key || CBS_len(&cbs) != 0 ||
+      EVP_PKEY_id(parsed_key.get()) != EVP_PKEY_EC) {
+    return nullptr;
+  }
+  bssl::UniquePtr<EC_KEY> ec_key(EVP_PKEY_get1_EC_KEY(parsed_key.get()));
+  if (!ec_key || EC_GROUP_get_curve_name(EC_KEY_get0_group(ec_key.get())) !=
+                     NID_X9_62_prime256v1) {
+    return nullptr;
+  }
+  return ec_key;
+}
+
+}  // namespace
+
 constexpr std::string_view kPemHeader = "-----BEGIN PUBLIC KEY-----";
 constexpr std::string_view kPemFooter = "-----END PUBLIC KEY-----";
 
@@ -39,7 +66,7 @@
   return resultVector;
 }
 
-std::string ConvertRawToPem(std::vector<uint8_t> public_key) {
+std::string ConvertRawToPem(base::span<const uint8_t> public_key) {
   std::string before(public_key.begin(), public_key.end());
   std::string encoded = base::Base64Encode(before);
   std::vector<char> tempResult(kPemHeader.begin(), kPemHeader.end());
@@ -55,4 +82,21 @@
   return result;
 }
 
+base::expected<void, std::string> COMPONENT_EXPORT(DEVICE_FIDO)
+    VerifySignatureRaw(base::span<const uint8_t> signature,
+                       base::span<const uint8_t> contents,
+                       base::span<const uint8_t> public_key) {
+  bssl::UniquePtr<EC_KEY> ec_key(GetECKey(public_key));
+  if (!ec_key) {
+    return base::unexpected("Could not parse public_key");
+  }
+  bssl::UniquePtr<ECDSA_SIG> sig(
+      ECDSA_SIG_from_bytes(signature.data(), signature.size()));
+  if (!ECDSA_do_verify(contents.data(), contents.size(), sig.get(),
+                       ec_key.get())) {
+    return base::unexpected("Could not verify the signature");
+  }
+  return base::expected<void, std::string>();
+}
+
 }  // namespace device::enclave
diff --git a/device/fido/enclave/verify/utils.h b/device/fido/enclave/verify/utils.h
index e0c4bd2..21820797 100644
--- a/device/fido/enclave/verify/utils.h
+++ b/device/fido/enclave/verify/utils.h
@@ -6,9 +6,11 @@
 #define DEVICE_FIDO_ENCLAVE_VERIFY_UTILS_H_
 
 #include <string>
+#include <string_view>
 #include <vector>
 
 #include "base/component_export.h"
+#include "base/containers/span.h"
 #include "base/types/expected.h"
 
 namespace device::enclave {
@@ -22,7 +24,13 @@
 
 // Converts a raw public key to PEM format.
 std::string COMPONENT_EXPORT(DEVICE_FIDO)
-    ConvertRawToPem(std::vector<uint8_t> public_key);
+    ConvertRawToPem(base::span<const uint8_t> public_key);
+
+// Verifies the signature over the contents using the public key.
+base::expected<void, std::string> COMPONENT_EXPORT(DEVICE_FIDO)
+    VerifySignatureRaw(base::span<const uint8_t> signature,
+                       base::span<const uint8_t> contents,
+                       base::span<const uint8_t> public_key);
 
 }  // namespace device::enclave
 
diff --git a/device/fido/enclave/verify/utils_unittest.cc b/device/fido/enclave/verify/utils_unittest.cc
index 3c19dd0..3d6456b 100644
--- a/device/fido/enclave/verify/utils_unittest.cc
+++ b/device/fido/enclave/verify/utils_unittest.cc
@@ -4,28 +4,38 @@
 
 #include "device/fido/enclave/verify/utils.h"
 
+#include "base/base_paths.h"
+#include "base/containers/span.h"
+#include "base/files/file_util.h"
+#include "base/path_service.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 namespace device::enclave {
 
-constexpr std::string_view kTestPem =
-    "-----BEGIN PUBLIC KEY-----\n"
-    "YWtsc2Rqa2FidmprYW5idWlkcnR1aW93dGdodWlyYiBqaWtnYmFzamtsbmR2YXVp\n"
-    "ZXJ0YnVpd2Jlcmd1aSBhZGZ1amllZGZidWluYXNkamt2amtkYyAgZXJoZmdpb2py\n"
-    "d2Vpb2doaW9xYWJudmlvZmRudmlvZXdoZ2lvdXV2IGYgICAgICAgICAgd2Vpamhy\n"
-    "aWZvd2VuZmlvbndkZmlvbmlvd2Vub2Zub2l3ZW5maW4uMjM1NDM0NTY0MzZhc2xk\n"
-    "amZua2xkam5mMjM0MzUxMjUxNDA5NWtsbmFzZ2xuaW9ybmU=\n"
-    "-----END PUBLIC KEY-----\n";
+const base::FilePath::StringPieceType kTestDigestPath =
+    FILE_PATH_LITERAL("device/fido/enclave/verify/testdata/test_digest");
+const base::FilePath::StringPieceType kTestSignaturePath =
+    FILE_PATH_LITERAL("device/fido/enclave/verify/testdata/test_signature");
+const base::FilePath::StringPieceType kTestPemPath =
+    FILE_PATH_LITERAL("device/fido/enclave/verify/testdata/test_pub_key.pem");
+const base::FilePath::StringPieceType kTestRawPath =
+    FILE_PATH_LITERAL("device/fido/enclave/verify/testdata/test_pub_key.der");
+const uint8_t kInvalidSignature[] = {1, 2, 3, 4};
 
-constexpr std::string_view kTestRaw =
-    "aklsdjkabvjkanbuidrtuiowtghuirb jikgbasjklndvauiertbuiwbergui "
-    "adfujiedfbuinasdjkvjkdc  erhfgiojrweioghioqabnviofdnvioewhgiouuv f        "
-    "  "
-    "weijhrifowenfionwdfioniowenofnoiwenfin."
-    "23543456436asldjfnkldjnf2343512514095klnasglniorne";
+std::string ReadContentsOfFile(
+    base::FilePath::StringPieceType file_path_string) {
+  base::FilePath file_path;
+  base::PathService::Get(base::BasePathKey::DIR_SRC_TEST_DATA_ROOT, &file_path);
+  file_path = file_path.Append(file_path_string);
+  std::string result;
+  EXPECT_TRUE(base::ReadFileToString(file_path, &result));
+  return result;
+}
 
 TEST(UtilsTest, LooksLikePem_WithValidPem_ReturnsTrue) {
-  EXPECT_TRUE(device::enclave::LooksLikePem(kTestPem));
+  auto test_pem = ReadContentsOfFile(kTestPemPath);
+
+  EXPECT_TRUE(device::enclave::LooksLikePem(test_pem));
 }
 
 TEST(UtilsTest, LooksLikePem_WithInvalidPem_ReturnsFalse) {
@@ -33,20 +43,64 @@
 }
 
 TEST(UtilsTest, ConvertPemToRaw_WithValidPem_ReturnsRaw) {
-  auto res = device::enclave::ConvertPemToRaw(kTestPem);
+  auto test_pem = ReadContentsOfFile(kTestPemPath);
+  auto test_raw = ReadContentsOfFile(kTestRawPath);
+
+  auto res = device::enclave::ConvertPemToRaw(test_pem);
+
   EXPECT_TRUE(res.has_value());
-  EXPECT_EQ(std::string(res->begin(), res->end()), kTestRaw);
+  EXPECT_EQ(std::string(res->begin(), res->end()), test_raw);
 }
 
 TEST(UtilsTest, ConvertPemToRaw_WithInvalidPem_ReturnsError) {
   auto res = device::enclave::ConvertPemToRaw("Not a valid PEM");
+
   EXPECT_FALSE(res.has_value());
 }
 
 TEST(UtilsTest, ConvertRawToPem_ReturnsPem) {
-  std::vector<uint8_t> temp(kTestRaw.begin(), kTestRaw.end());
+  auto test_pem = ReadContentsOfFile(kTestPemPath);
+  auto test_raw = ReadContentsOfFile(kTestRawPath);
+  base::span<const uint8_t> temp = base::make_span(
+      static_cast<const uint8_t*>((uint8_t*)test_raw.data()), test_raw.size());
+
   auto res = device::enclave::ConvertRawToPem(temp);
-  EXPECT_EQ(res, kTestPem);
+
+  EXPECT_EQ(res, test_pem);
+}
+
+TEST(UtilsTest, VerifySignatureRaw_WithValidSignature_Succeeds) {
+  auto test_digest = ReadContentsOfFile(kTestDigestPath);
+  auto test_digest_signature = ReadContentsOfFile(kTestSignaturePath);
+  auto test_raw = ReadContentsOfFile(kTestRawPath);
+  base::span<const uint8_t> test_digest_span =
+      base::make_span(static_cast<const uint8_t*>((uint8_t*)test_digest.data()),
+                      test_digest.size());
+  base::span<const uint8_t> test_digest_sig_span = base::make_span(
+      static_cast<const uint8_t*>((uint8_t*)test_digest_signature.data()),
+      test_digest_signature.size());
+  base::span<const uint8_t> test_raw_span = base::make_span(
+      static_cast<const uint8_t*>((uint8_t*)test_raw.data()), test_raw.size());
+
+  auto res = device::enclave::VerifySignatureRaw(
+      test_digest_sig_span, test_digest_span, test_raw_span);
+
+  EXPECT_TRUE(res.has_value());
+}
+
+TEST(UtilsTest, VerifySignatureRaw_WithInvalidSignature_Fails) {
+  auto test_digest = ReadContentsOfFile(kTestDigestPath);
+  auto test_raw = ReadContentsOfFile(kTestRawPath);
+  base::span<const uint8_t> test_digest_span =
+      base::make_span(static_cast<const uint8_t*>((uint8_t*)test_digest.data()),
+                      test_digest.size());
+  base::span<const uint8_t> test_raw_span = base::make_span(
+      static_cast<const uint8_t*>((uint8_t*)test_raw.data()), test_raw.size());
+
+  auto res = device::enclave::VerifySignatureRaw(
+      kInvalidSignature, test_digest_span, test_raw_span);
+
+  EXPECT_FALSE(res.has_value());
 }
 
 }  // namespace device::enclave
diff --git a/device/fido/mac/get_assertion_operation.mm b/device/fido/mac/get_assertion_operation.mm
index 8bd5abd..13d2b714 100644
--- a/device/fido/mac/get_assertion_operation.mm
+++ b/device/fido/mac/get_assertion_operation.mm
@@ -4,11 +4,11 @@
 
 #include "device/fido/mac/get_assertion_operation.h"
 
+#import <Foundation/Foundation.h>
+
 #include <set>
 #include <string>
 
-#import <Foundation/Foundation.h>
-
 #include "base/apple/foundation_util.h"
 #include "base/apple/osstatus_logging.h"
 #include "base/apple/scoped_cftyperef.h"
@@ -21,6 +21,7 @@
 #include "device/fido/fido_types.h"
 #include "device/fido/mac/credential_metadata.h"
 #include "device/fido/mac/util.h"
+#include "device/fido/platform_user_verification_policy.h"
 #include "device/fido/public_key_credential_descriptor.h"
 #include "device/fido/public_key_credential_user_entity.h"
 #include "device/fido/strings/grit/fido_strings.h"
@@ -63,8 +64,7 @@
   }
 
   bool require_uv =
-      DeviceHasBiometricsAvailable() ||
-      request_.user_verification == UserVerificationRequirement::kRequired ||
+      PlatformWillDoUserVerification(request_.user_verification) ||
       std::any_of(credentials->begin(), credentials->end(),
                   [](const Credential& credential) {
                     return credential.RequiresUvForSignature();
diff --git a/device/fido/mac/make_credential_operation.mm b/device/fido/mac/make_credential_operation.mm
index 5723f90..1dbdeaa3f 100644
--- a/device/fido/mac/make_credential_operation.mm
+++ b/device/fido/mac/make_credential_operation.mm
@@ -4,10 +4,10 @@
 
 #include "device/fido/mac/make_credential_operation.h"
 
-#include <string>
-
 #import <Foundation/Foundation.h>
 
+#include <string>
+
 #include "base/apple/foundation_util.h"
 #include "base/apple/osstatus_logging.h"
 #include "base/apple/scoped_cftyperef.h"
@@ -26,6 +26,7 @@
 #include "device/fido/mac/credential_metadata.h"
 #include "device/fido/mac/credential_store.h"
 #include "device/fido/mac/util.h"
+#include "device/fido/platform_user_verification_policy.h"
 #include "device/fido/public_key.h"
 #include "device/fido/strings/grit/fido_strings.h"
 #include "ui/base/l10n/l10n_util.h"
@@ -54,8 +55,7 @@
   }
 
   const bool require_uv =
-      DeviceHasBiometricsAvailable() ||
-      request_.user_verification == UserVerificationRequirement::kRequired;
+      PlatformWillDoUserVerification(request_.user_verification);
   if (require_uv) {
     touch_id_context_->PromptTouchId(
         l10n_util::GetStringFUTF16(IDS_WEBAUTHN_TOUCH_ID_PROMPT_REASON,
diff --git a/device/fido/platform_user_verification_policy.cc b/device/fido/platform_user_verification_policy.cc
new file mode 100644
index 0000000..a8633a6a
--- /dev/null
+++ b/device/fido/platform_user_verification_policy.cc
@@ -0,0 +1,27 @@
+// 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 "device/fido/platform_user_verification_policy.h"
+
+#include "build/build_config.h"
+
+#if BUILDFLAG(IS_MAC)
+#include "device/fido/mac/util.h"
+#endif
+
+namespace device::fido {
+
+bool PlatformWillDoUserVerification(UserVerificationRequirement requirement) {
+#if BUILDFLAG(IS_WIN)
+  return true;
+#elif BUILDFLAG(IS_MAC)
+  return requirement == UserVerificationRequirement::kRequired ||
+         mac::DeviceHasBiometricsAvailable();
+#else
+  // This default is for unit tests.
+  return true;
+#endif
+}
+
+}  // namespace device::fido
diff --git a/device/fido/platform_user_verification_policy.h b/device/fido/platform_user_verification_policy.h
new file mode 100644
index 0000000..86df4f6
--- /dev/null
+++ b/device/fido/platform_user_verification_policy.h
@@ -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.
+
+#ifndef DEVICE_FIDO_PLATFORM_USER_VERIFICATION_POLICY_H_
+#define DEVICE_FIDO_PLATFORM_USER_VERIFICATION_POLICY_H_
+
+#include "base/component_export.h"
+#include "device/fido/fido_types.h"
+
+namespace device::fido {
+
+// Returns whether the platform norm is to do user verification for the given
+// requirement. The platform norm is taken from the native platform
+// authenticator, e.g. Windows Hello on Windows or iCloud Keychain on macOS.
+COMPONENT_EXPORT(DEVICE_FIDO)
+bool PlatformWillDoUserVerification(UserVerificationRequirement requirement);
+
+}  // namespace device::fido
+
+#endif  // DEVICE_FIDO_PLATFORM_USER_VERIFICATION_POLICY_H_
diff --git a/docs/website b/docs/website
index a91c462..fea62e9 160000
--- a/docs/website
+++ b/docs/website
@@ -1 +1 @@
-Subproject commit a91c462f4abff8bd8874a4b7cb5420647a0f0235
+Subproject commit fea62e99e8250d5596ecbb806298b2e2fec4a6de
diff --git a/google_apis/gaia/gaia_constants.cc b/google_apis/gaia/gaia_constants.cc
index 6ab6455..b2ee068 100644
--- a/google_apis/gaia/gaia_constants.cc
+++ b/google_apis/gaia/gaia_constants.cc
@@ -204,6 +204,9 @@
 const char kOptimizationGuideServiceModelExecutionOAuth2Scope[] =
     "https://www.googleapis.com/auth/chrome-model-execution";
 
+// OAuth2 scopes for Lens.
+const char kLensOAuth2Scope[] = "https://www.googleapis.com/auth/lens";
+
 // Used to build ClientOAuth requests.  These are the names of keys used when
 // building base::DictionaryValue that represent the json data that makes up
 // the ClientOAuth endpoint protocol.  The comment above each constant explains
diff --git a/google_apis/gaia/gaia_constants.h b/google_apis/gaia/gaia_constants.h
index 775aecd..f20d61a 100644
--- a/google_apis/gaia/gaia_constants.h
+++ b/google_apis/gaia/gaia_constants.h
@@ -98,6 +98,10 @@
 COMPONENT_EXPORT(GOOGLE_APIS)
 extern const char kOptimizationGuideServiceModelExecutionOAuth2Scope[];
 
+// OAuth2 scopes for Lens.
+COMPONENT_EXPORT(GOOGLE_APIS)
+extern const char kLensOAuth2Scope[];
+
 // Used by wallet sign in helper.
 COMPONENT_EXPORT(GOOGLE_APIS) extern const char kClientOAuthEmailKey[];
 
diff --git a/gpu/command_buffer/service/dawn_context_provider.cc b/gpu/command_buffer/service/dawn_context_provider.cc
index acc835e..996ab37 100644
--- a/gpu/command_buffer/service/dawn_context_provider.cc
+++ b/gpu/command_buffer/service/dawn_context_provider.cc
@@ -336,9 +336,11 @@
   }
   // The following toggles are all device-scoped toggles so it's not necessary
   // to pass them when creating the Instance above.
-#if DCHECK_IS_ON()
+#if DCHECK_IS_ON() || BUILDFLAG(IS_WIN)
   enabled_toggles.push_back("use_user_defined_labels_in_backend");
-#else
+#endif
+
+#if !DCHECK_IS_ON()
   if (features::kSkiaGraphiteDawnSkipValidation.Get()) {
     enabled_toggles.push_back("skip_validation");
   }
diff --git "a/infra/config/generated/builders/ci/Android arm Builder \050dbg\051/targets/chromium.android.json" "b/infra/config/generated/builders/ci/Android arm Builder \050dbg\051/targets/chromium.android.json"
index adc5939..70c488f4 100644
--- "a/infra/config/generated/builders/ci/Android arm Builder \050dbg\051/targets/chromium.android.json"
+++ "b/infra/config/generated/builders/ci/Android arm Builder \050dbg\051/targets/chromium.android.json"
@@ -9,6 +9,10 @@
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
         "name": "android_webview_junit_tests",
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
         "swarming": {
           "dimensions": {
             "cores": "8",
@@ -26,6 +30,10 @@
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
         "name": "base_junit_tests",
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
         "swarming": {
           "dimensions": {
             "cores": "8",
@@ -43,6 +51,10 @@
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
         "name": "build_junit_tests",
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
         "swarming": {
           "dimensions": {
             "cores": "8",
@@ -60,6 +72,10 @@
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
         "name": "chrome_java_test_pagecontroller_junit_tests",
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
         "swarming": {
           "dimensions": {
             "cores": "8",
@@ -77,6 +93,10 @@
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
         "name": "chrome_junit_tests",
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
         "swarming": {
           "dimensions": {
             "cores": "8",
@@ -94,6 +114,10 @@
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
         "name": "components_junit_tests",
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
         "swarming": {
           "dimensions": {
             "cores": "8",
@@ -111,6 +135,10 @@
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
         "name": "content_junit_tests",
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
         "swarming": {
           "dimensions": {
             "cores": "8",
@@ -128,6 +156,10 @@
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
         "name": "device_junit_tests",
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
         "swarming": {
           "dimensions": {
             "cores": "8",
@@ -145,6 +177,10 @@
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
         "name": "junit_unit_tests",
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
         "swarming": {
           "dimensions": {
             "cores": "8",
@@ -162,6 +198,10 @@
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
         "name": "keyboard_accessory_junit_tests",
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
         "swarming": {
           "dimensions": {
             "cores": "8",
@@ -179,6 +219,10 @@
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
         "name": "media_base_junit_tests",
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
         "swarming": {
           "dimensions": {
             "cores": "8",
@@ -196,6 +240,10 @@
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
         "name": "module_installer_junit_tests",
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
         "swarming": {
           "dimensions": {
             "cores": "8",
@@ -213,6 +261,10 @@
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
         "name": "net_junit_tests",
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
         "swarming": {
           "dimensions": {
             "cores": "8",
@@ -230,6 +282,10 @@
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
         "name": "paint_preview_junit_tests",
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
         "swarming": {
           "dimensions": {
             "cores": "8",
@@ -247,6 +303,10 @@
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
         "name": "password_check_junit_tests",
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
         "swarming": {
           "dimensions": {
             "cores": "8",
@@ -264,6 +324,10 @@
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
         "name": "password_manager_junit_tests",
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
         "swarming": {
           "dimensions": {
             "cores": "8",
@@ -281,6 +345,10 @@
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
         "name": "services_junit_tests",
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
         "swarming": {
           "dimensions": {
             "cores": "8",
@@ -298,6 +366,10 @@
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
         "name": "touch_to_fill_junit_tests",
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
         "swarming": {
           "dimensions": {
             "cores": "8",
@@ -315,6 +387,10 @@
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
         "name": "ui_junit_tests",
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
         "swarming": {
           "dimensions": {
             "cores": "8",
@@ -332,6 +408,10 @@
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
         "name": "webapk_client_junit_tests",
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
         "swarming": {
           "dimensions": {
             "cores": "8",
@@ -349,6 +429,10 @@
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
         "name": "webapk_shell_apk_h2o_junit_tests",
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
         "swarming": {
           "dimensions": {
             "cores": "8",
@@ -366,6 +450,10 @@
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
         "name": "webapk_shell_apk_junit_tests",
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
         "swarming": {
           "dimensions": {
             "cores": "8",
diff --git a/infra/config/generated/builders/try/android-arm-compile-dbg/targets/chromium.android.json b/infra/config/generated/builders/try/android-arm-compile-dbg/targets/chromium.android.json
index adc5939..70c488f4 100644
--- a/infra/config/generated/builders/try/android-arm-compile-dbg/targets/chromium.android.json
+++ b/infra/config/generated/builders/try/android-arm-compile-dbg/targets/chromium.android.json
@@ -9,6 +9,10 @@
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
         "name": "android_webview_junit_tests",
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
         "swarming": {
           "dimensions": {
             "cores": "8",
@@ -26,6 +30,10 @@
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
         "name": "base_junit_tests",
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
         "swarming": {
           "dimensions": {
             "cores": "8",
@@ -43,6 +51,10 @@
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
         "name": "build_junit_tests",
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
         "swarming": {
           "dimensions": {
             "cores": "8",
@@ -60,6 +72,10 @@
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
         "name": "chrome_java_test_pagecontroller_junit_tests",
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
         "swarming": {
           "dimensions": {
             "cores": "8",
@@ -77,6 +93,10 @@
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
         "name": "chrome_junit_tests",
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
         "swarming": {
           "dimensions": {
             "cores": "8",
@@ -94,6 +114,10 @@
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
         "name": "components_junit_tests",
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
         "swarming": {
           "dimensions": {
             "cores": "8",
@@ -111,6 +135,10 @@
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
         "name": "content_junit_tests",
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
         "swarming": {
           "dimensions": {
             "cores": "8",
@@ -128,6 +156,10 @@
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
         "name": "device_junit_tests",
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
         "swarming": {
           "dimensions": {
             "cores": "8",
@@ -145,6 +177,10 @@
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
         "name": "junit_unit_tests",
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
         "swarming": {
           "dimensions": {
             "cores": "8",
@@ -162,6 +198,10 @@
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
         "name": "keyboard_accessory_junit_tests",
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
         "swarming": {
           "dimensions": {
             "cores": "8",
@@ -179,6 +219,10 @@
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
         "name": "media_base_junit_tests",
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
         "swarming": {
           "dimensions": {
             "cores": "8",
@@ -196,6 +240,10 @@
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
         "name": "module_installer_junit_tests",
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
         "swarming": {
           "dimensions": {
             "cores": "8",
@@ -213,6 +261,10 @@
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
         "name": "net_junit_tests",
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
         "swarming": {
           "dimensions": {
             "cores": "8",
@@ -230,6 +282,10 @@
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
         "name": "paint_preview_junit_tests",
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
         "swarming": {
           "dimensions": {
             "cores": "8",
@@ -247,6 +303,10 @@
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
         "name": "password_check_junit_tests",
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
         "swarming": {
           "dimensions": {
             "cores": "8",
@@ -264,6 +324,10 @@
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
         "name": "password_manager_junit_tests",
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
         "swarming": {
           "dimensions": {
             "cores": "8",
@@ -281,6 +345,10 @@
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
         "name": "services_junit_tests",
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
         "swarming": {
           "dimensions": {
             "cores": "8",
@@ -298,6 +366,10 @@
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
         "name": "touch_to_fill_junit_tests",
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
         "swarming": {
           "dimensions": {
             "cores": "8",
@@ -315,6 +387,10 @@
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
         "name": "ui_junit_tests",
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
         "swarming": {
           "dimensions": {
             "cores": "8",
@@ -332,6 +408,10 @@
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
         "name": "webapk_client_junit_tests",
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
         "swarming": {
           "dimensions": {
             "cores": "8",
@@ -349,6 +429,10 @@
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
         "name": "webapk_shell_apk_h2o_junit_tests",
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
         "swarming": {
           "dimensions": {
             "cores": "8",
@@ -366,6 +450,10 @@
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
         "name": "webapk_shell_apk_junit_tests",
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
         "swarming": {
           "dimensions": {
             "cores": "8",
diff --git a/infra/config/generated/testing/variants.pyl b/infra/config/generated/testing/variants.pyl
index 3795057d..1296d575 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 126.0.6462.0',
+    'description': 'Run with ash-chrome version 126.0.6463.0',
     'args': [
-      '--ash-chrome-path-override=../../lacros_version_skew_tests_v126.0.6462.0/test_ash_chrome',
+      '--ash-chrome-path-override=../../lacros_version_skew_tests_v126.0.6463.0/test_ash_chrome',
     ],
     'swarming': {
       'cipd_packages': [
         {
           'cipd_package': 'chromium/testing/linux-ash-chromium/x86_64/ash.zip',
-          'location': 'lacros_version_skew_tests_v126.0.6462.0',
-          'revision': 'version:126.0.6462.0',
+          'location': 'lacros_version_skew_tests_v126.0.6463.0',
+          'revision': 'version:126.0.6463.0',
         },
       ],
     },
diff --git a/infra/config/subprojects/chromium/ci/chromium.android.star b/infra/config/subprojects/chromium/ci/chromium.android.star
index 37a0420..f9f779f7 100644
--- a/infra/config/subprojects/chromium/ci/chromium.android.star
+++ b/infra/config/subprojects/chromium/ci/chromium.android.star
@@ -240,6 +240,9 @@
         additional_compile_targets = [
             "all",
         ],
+        mixins = [
+            "has_native_resultdb_integration",
+        ],
     ),
     free_space = builders.free_space.high,
     tree_closing = True,
diff --git a/infra/config/targets/autoshard_exceptions.json b/infra/config/targets/autoshard_exceptions.json
index 6391346d..1d56ca7 100644
--- a/infra/config/targets/autoshard_exceptions.json
+++ b/infra/config/targets/autoshard_exceptions.json
@@ -159,18 +159,18 @@
             },
             "browser_tests": {
                 "debug": {
-                    "avg_num_builds_per_peak_hour": 96,
-                    "estimated_bot_hour_delta": 6.61,
-                    "prev_avg_pending_time_sec": 53.1,
-                    "prev_p50_pending_time_sec": 6.0,
-                    "prev_p90_pending_time_sec": 134.0,
-                    "prev_percentile_duration_minutes": 16.08,
-                    "prev_shard_count": 99,
-                    "simulated_max_shard_duration": 14.92,
-                    "test_overhead_min": 0.5166666666666667,
+                    "avg_num_builds_per_peak_hour": 99,
+                    "estimated_bot_hour_delta": -17.55,
+                    "prev_avg_pending_time_sec": 34.4,
+                    "prev_p50_pending_time_sec": 5.0,
+                    "prev_p90_pending_time_sec": 110.0,
+                    "prev_percentile_duration_minutes": 13.55,
+                    "prev_shard_count": 107,
+                    "simulated_max_shard_duration": 14.99,
+                    "test_overhead_min": 0.9666666666666667,
                     "try_builder": "linux-chromeos-rel"
                 },
-                "shards": 107
+                "shards": 96
             },
             "browser_tests_require_lacros": {
                 "debug": {
@@ -236,18 +236,18 @@
             },
             "interactive_ui_tests": {
                 "debug": {
-                    "avg_num_builds_per_peak_hour": 97,
-                    "estimated_bot_hour_delta": 0.94,
-                    "prev_avg_pending_time_sec": 52.3,
-                    "prev_p50_pending_time_sec": 2.0,
-                    "prev_p90_pending_time_sec": 140.0,
-                    "prev_percentile_duration_minutes": 16.0,
-                    "prev_shard_count": 6,
-                    "simulated_max_shard_duration": 13.8,
-                    "test_overhead_min": 0.5833333333333334,
+                    "avg_num_builds_per_peak_hour": 101,
+                    "estimated_bot_hour_delta": -2.36,
+                    "prev_avg_pending_time_sec": 23.7,
+                    "prev_p50_pending_time_sec": 1.0,
+                    "prev_p90_pending_time_sec": 76.0,
+                    "prev_percentile_duration_minutes": 12.98,
+                    "prev_shard_count": 7,
+                    "simulated_max_shard_duration": 14.91,
+                    "test_overhead_min": 1.4,
                     "try_builder": "linux-lacros-rel"
                 },
-                "shards": 7
+                "shards": 6
             },
             "interactive_ui_tests Lacros version skew testing ash beta": {
                 "debug": {
@@ -468,33 +468,33 @@
             },
             "browser_tests": {
                 "debug": {
-                    "avg_num_builds_per_peak_hour": 108,
-                    "estimated_bot_hour_delta": -2.97,
-                    "prev_avg_pending_time_sec": 58.9,
-                    "prev_p50_pending_time_sec": 4.0,
-                    "prev_p90_pending_time_sec": 188.0,
-                    "prev_percentile_duration_minutes": 13.63,
-                    "prev_shard_count": 32,
-                    "simulated_max_shard_duration": 14.98,
-                    "test_overhead_min": 0.55,
+                    "avg_num_builds_per_peak_hour": 99,
+                    "estimated_bot_hour_delta": -2.31,
+                    "prev_avg_pending_time_sec": 28.6,
+                    "prev_p50_pending_time_sec": 3.0,
+                    "prev_p90_pending_time_sec": 90.0,
+                    "prev_percentile_duration_minutes": 13.58,
+                    "prev_shard_count": 29,
+                    "simulated_max_shard_duration": 14.53,
+                    "test_overhead_min": 0.7,
                     "try_builder": "linux-rel"
                 },
-                "shards": 29
+                "shards": 27
             },
             "interactive_ui_tests": {
                 "debug": {
-                    "avg_num_builds_per_peak_hour": 93,
-                    "estimated_bot_hour_delta": -1.14,
-                    "prev_avg_pending_time_sec": 52.7,
-                    "prev_p50_pending_time_sec": 2.0,
-                    "prev_p90_pending_time_sec": 167.0,
-                    "prev_percentile_duration_minutes": 11.68,
-                    "prev_shard_count": 8,
-                    "simulated_max_shard_duration": 13.35,
-                    "test_overhead_min": 0.7333333333333333,
+                    "avg_num_builds_per_peak_hour": 99,
+                    "estimated_bot_hour_delta": -1.07,
+                    "prev_avg_pending_time_sec": 24.9,
+                    "prev_p50_pending_time_sec": 1.0,
+                    "prev_p90_pending_time_sec": 80.0,
+                    "prev_percentile_duration_minutes": 12.6,
+                    "prev_shard_count": 7,
+                    "simulated_max_shard_duration": 14.59,
+                    "test_overhead_min": 0.65,
                     "try_builder": "linux-rel"
                 },
-                "shards": 7
+                "shards": 6
             },
             "not_site_per_process_blink_wpt_tests": {
                 "debug": {
diff --git a/infra/config/targets/lacros-version-skew-variants.json b/infra/config/targets/lacros-version-skew-variants.json
index e0b9c76..dabc45b3 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_v126.0.6462.0/test_ash_chrome"
+      "--ash-chrome-path-override=../../lacros_version_skew_tests_v126.0.6463.0/test_ash_chrome"
     ],
-    "description": "Run with ash-chrome version 126.0.6462.0",
+    "description": "Run with ash-chrome version 126.0.6463.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_v126.0.6462.0",
-          "revision": "version:126.0.6462.0"
+          "location": "lacros_version_skew_tests_v126.0.6463.0",
+          "revision": "version:126.0.6463.0"
         }
       ]
     }
diff --git a/internal b/internal
index a4ccbea..596ba50 160000
--- a/internal
+++ b/internal
@@ -1 +1 @@
-Subproject commit a4ccbeaa41c7afd89c02c70fddd5dabaed66e29c
+Subproject commit 596ba505f7df80c737fbeed3216f75fffe7c4763
diff --git a/ios/chrome/browser/autofill/model/autofill_controller_unittest.mm b/ios/chrome/browser/autofill/model/autofill_controller_unittest.mm
index e329a1d..76159b02 100644
--- a/ios/chrome/browser/autofill/model/autofill_controller_unittest.mm
+++ b/ios/chrome/browser/autofill/model/autofill_controller_unittest.mm
@@ -19,6 +19,7 @@
 #import "components/autofill/core/browser/address_data_manager_test_api.h"
 #import "components/autofill/core/browser/browser_autofill_manager.h"
 #import "components/autofill/core/browser/form_structure.h"
+#import "components/autofill/core/browser/geo/alternative_state_name_map_updater.h"
 #import "components/autofill/core/browser/metrics/autofill_metrics.h"
 #import "components/autofill/core/browser/payments_data_manager.h"
 #import "components/autofill/core/browser/personal_data_manager.h"
diff --git a/ios/chrome/browser/prerender/model/prerender_egtest.mm b/ios/chrome/browser/prerender/model/prerender_egtest.mm
index f51769bb..021713e7 100644
--- a/ios/chrome/browser/prerender/model/prerender_egtest.mm
+++ b/ios/chrome/browser/prerender/model/prerender_egtest.mm
@@ -130,9 +130,10 @@
 - (AppLaunchConfiguration)appConfigurationForTestCase {
   AppLaunchConfiguration config;
   if ([self isRunningTest:@selector
-            (FLAKY_testLegacyOpenTabInTabStripBeforePrerenderedTab)] ||
-      [self isRunningTest:@selector(FLAKY_testMovePrerenderedTabInTabStrip)]) {
+            (testLegacyOpenTabInTabStripBeforePrerenderedTab)] ||
+      [self isRunningTest:@selector(testMovePrerenderedTabInTabStrip)]) {
     config.features_disabled.push_back(kModernTabStrip);
+    config.features_disabled.push_back(kTabGroupsIPad);
   } else {
     config.features_enabled.push_back(kModernTabStrip);
   }
@@ -289,8 +290,7 @@
 // Regression test for crbug.com/1477499. Tests that a pre-rendered tab doesn't
 // lead to an incorrect data source, as can be seen after moving it in the tab
 // strip.
-// TODO(b/324216491): This test is flaky on official bot.
-- (void)FLAKY_testMovePrerenderedTabInTabStrip {
+- (void)testMovePrerenderedTabInTabStrip {
   if (![ChromeEarlGrey isIPadIdiom]) {
     EARL_GREY_TEST_SKIPPED(
         @"Skipped for iPhone. The test makes use of the tab strip.");
@@ -348,9 +348,7 @@
 // Regression test for crbug.com/1482622. Tests that a pre-rendered tab doesn't
 // lead to an incorrect data source, as can be seen after opening a new tab in
 // the background before the pre-rendered tab.
-// TODO(crbug.com/40073670): Test fails on official builds.
-// TODO(crbug.com/332961713): Test is Flaky on iOS17+ devices.
-- (void)FLAKY_testLegacyOpenTabInTabStripBeforePrerenderedTab {
+- (void)testLegacyOpenTabInTabStripBeforePrerenderedTab {
   if (![ChromeEarlGrey isIPadIdiom]) {
     EARL_GREY_TEST_SKIPPED(
         @"Skipped for iPhone. The test makes use of the tab strip.");
diff --git a/ios/chrome/browser/ui/authentication/account_switching/BUILD.gn b/ios/chrome/browser/ui/authentication/account_switching/BUILD.gn
index fa4834c..ff6956f 100644
--- a/ios/chrome/browser/ui/authentication/account_switching/BUILD.gn
+++ b/ios/chrome/browser/ui/authentication/account_switching/BUILD.gn
@@ -19,12 +19,19 @@
 
 source_set("account_switching_ui") {
   sources = [
+    "account_switcher_presentation_controller.h",
+    "account_switcher_presentation_controller.mm",
+    "account_switcher_transition_delegate.h",
+    "account_switcher_transition_delegate.mm",
     "account_switcher_view_controller.h",
     "account_switcher_view_controller.mm",
   ]
   deps = [
+    "//base",
     "//ios/chrome/browser/shared/ui/table_view",
+    "//ios/chrome/browser/shared/ui/util",
     "//ios/chrome/common/ui/colors",
+    "//ios/chrome/common/ui/util",
   ]
   public_deps = [ "//ios/chrome/browser/shared/ui/table_view" ]
 }
diff --git a/ios/chrome/browser/ui/authentication/account_switching/account_switcher_coordinator.mm b/ios/chrome/browser/ui/authentication/account_switching/account_switcher_coordinator.mm
index 48928cfe..3def8d5e 100644
--- a/ios/chrome/browser/ui/authentication/account_switching/account_switcher_coordinator.mm
+++ b/ios/chrome/browser/ui/authentication/account_switching/account_switcher_coordinator.mm
@@ -6,6 +6,7 @@
 
 #import "base/check.h"
 #import "ios/chrome/browser/shared/ui/table_view/table_view_utils.h"
+#import "ios/chrome/browser/ui/authentication/account_switching/account_switcher_transition_delegate.h"
 #import "ios/chrome/browser/ui/authentication/account_switching/account_switcher_view_controller.h"
 
 @implementation AccountSwitcherCoordinator {
@@ -16,21 +17,19 @@
   [super start];
   _viewController = [[AccountSwitcherViewController alloc]
       initWithStyle:ChromeTableViewStyle()];
-  UINavigationController* navigationController = [[UINavigationController alloc]
-      initWithRootViewController:_viewController];
+  _viewController.modalPresentationStyle = UIModalPresentationCustom;
+  AccountSwitcherTransitionDelegate* transitionController =
+      [[AccountSwitcherTransitionDelegate alloc] init];
+  _viewController.transitioningDelegate = transitionController;
 
-  [self.baseViewController presentViewController:navigationController
+  [self.baseViewController presentViewController:_viewController
                                         animated:YES
                                       completion:nil];
 }
 
 - (void)stop {
   DCHECK(_viewController);
-  if (_viewController.presentingViewController) {
-    [_viewController.presentingViewController
-        dismissViewControllerAnimated:YES
-                           completion:nil];
-  }
+  [_viewController dismissViewControllerAnimated:YES completion:nil];
   _viewController = nil;
   [super stop];
 }
diff --git a/ios/chrome/browser/ui/authentication/account_switching/account_switcher_presentation_controller.h b/ios/chrome/browser/ui/authentication/account_switching/account_switcher_presentation_controller.h
new file mode 100644
index 0000000..66465e6
--- /dev/null
+++ b/ios/chrome/browser/ui/authentication/account_switching/account_switcher_presentation_controller.h
@@ -0,0 +1,16 @@
+// 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_UI_AUTHENTICATION_ACCOUNT_SWITCHING_ACCOUNT_SWITCHER_PRESENTATION_CONTROLLER_H_
+#define IOS_CHROME_BROWSER_UI_AUTHENTICATION_ACCOUNT_SWITCHING_ACCOUNT_SWITCHER_PRESENTATION_CONTROLLER_H_
+
+#import <UIKit/UIKit.h>
+
+// Presentation controller for presenting the AccountSwitcher. It is presenting
+// it as a Modal.
+@interface AccountSwitcherPresentationController : UIPresentationController
+
+@end
+
+#endif  // IOS_CHROME_BROWSER_UI_AUTHENTICATION_ACCOUNT_SWITCHING_ACCOUNT_SWITCHER_PRESENTATION_CONTROLLER_H_
diff --git a/ios/chrome/browser/ui/authentication/account_switching/account_switcher_presentation_controller.mm b/ios/chrome/browser/ui/authentication/account_switching/account_switcher_presentation_controller.mm
new file mode 100644
index 0000000..5b00c2b
--- /dev/null
+++ b/ios/chrome/browser/ui/authentication/account_switching/account_switcher_presentation_controller.mm
@@ -0,0 +1,65 @@
+// Copyright 2024 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#import "ios/chrome/browser/ui/authentication/account_switching/account_switcher_presentation_controller.h"
+
+#import "ios/chrome/browser/shared/ui/util/accessibility_close_menu_button.h"
+#import "ios/chrome/browser/shared/ui/util/uikit_ui_util.h"
+#import "ios/chrome/common/ui/util/constraints_ui_util.h"
+
+namespace {
+const CGFloat kMaxWidth = 350;
+const CGFloat kMaxHeight = 350;
+const CGFloat kMinimumMarginHorizontal = 25;
+const CGFloat kMinimumMarginVertical = 35;
+
+}  // namespace
+
+@interface AccountSwitcherPresentationController ()
+
+@property(nonatomic, strong) UIButton* closeButton;
+
+@end
+
+@implementation AccountSwitcherPresentationController
+
+- (CGRect)frameOfPresentedViewInContainerView {
+  CGRect safeAreaFrame = UIEdgeInsetsInsetRect(
+      self.containerView.bounds, self.containerView.safeAreaInsets);
+
+  CGFloat availableWidth = CGRectGetWidth(safeAreaFrame);
+  CGFloat availableHeight = CGRectGetHeight(safeAreaFrame);
+
+  CGFloat width = MIN(kMaxWidth, availableWidth - 2 * kMinimumMarginHorizontal);
+  CGFloat height =
+      MIN(kMaxHeight, availableHeight - 2 * kMinimumMarginVertical);
+
+  CGRect presentedViewFrame = safeAreaFrame;
+  presentedViewFrame.origin.x += (availableWidth - width) / 2;
+  presentedViewFrame.origin.y += (availableHeight - height) / 2;
+  presentedViewFrame.size.width = width;
+  presentedViewFrame.size.height = height;
+
+  return presentedViewFrame;
+}
+
+- (void)presentationTransitionWillBegin {
+  self.containerView.accessibilityViewIsModal = YES;
+
+  self.closeButton =
+      [AccessibilityCloseMenuButton buttonWithType:UIButtonTypeCustom];
+  [self.closeButton addTarget:self
+                       action:@selector(closeButtonAction)
+             forControlEvents:UIControlEventTouchUpInside];
+  self.closeButton.translatesAutoresizingMaskIntoConstraints = NO;
+  [self.containerView addSubview:self.closeButton];
+  AddSameConstraints(self.containerView, self.closeButton);
+}
+
+- (void)closeButtonAction {
+  [self.presentedViewController dismissViewControllerAnimated:YES
+                                                   completion:nil];
+}
+
+@end
diff --git a/ios/chrome/browser/ui/authentication/account_switching/account_switcher_transition_delegate.h b/ios/chrome/browser/ui/authentication/account_switching/account_switcher_transition_delegate.h
new file mode 100644
index 0000000..eee8767
--- /dev/null
+++ b/ios/chrome/browser/ui/authentication/account_switching/account_switcher_transition_delegate.h
@@ -0,0 +1,16 @@
+// 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_UI_AUTHENTICATION_ACCOUNT_SWITCHING_ACCOUNT_SWITCHER_TRANSITION_DELEGATE_H_
+#define IOS_CHROME_BROWSER_UI_AUTHENTICATION_ACCOUNT_SWITCHING_ACCOUNT_SWITCHER_TRANSITION_DELEGATE_H_
+
+#import <UIKit/UIKit.h>
+
+// Transition Delegate for the AccountSwitcher. It is presenting it as a modal.
+@interface AccountSwitcherTransitionDelegate
+    : NSObject <UIViewControllerTransitioningDelegate>
+
+@end
+
+#endif  // IOS_CHROME_BROWSER_UI_AUTHENTICATION_ACCOUNT_SWITCHING_ACCOUNT_SWITCHER_TRANSITION_DELEGATE_H_
diff --git a/ios/chrome/browser/ui/authentication/account_switching/account_switcher_transition_delegate.mm b/ios/chrome/browser/ui/authentication/account_switching/account_switcher_transition_delegate.mm
new file mode 100644
index 0000000..463424d
--- /dev/null
+++ b/ios/chrome/browser/ui/authentication/account_switching/account_switcher_transition_delegate.mm
@@ -0,0 +1,36 @@
+// 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 "ios/chrome/browser/ui/authentication/account_switching/account_switcher_transition_delegate.h"
+
+#import "ios/chrome/browser/ui/authentication/account_switching/account_switcher_presentation_controller.h"
+
+@implementation AccountSwitcherTransitionDelegate
+
+#pragma mark - UIViewControllerTransitioningDelegate
+
+- (UIPresentationController*)
+    presentationControllerForPresentedViewController:
+        (UIViewController*)presented
+                            presentingViewController:
+                                (UIViewController*)presenting
+                                sourceViewController:(UIViewController*)source {
+  return [[AccountSwitcherPresentationController alloc]
+      initWithPresentedViewController:presented
+             presentingViewController:presenting];
+}
+
+- (id<UIViewControllerAnimatedTransitioning>)
+    animationControllerForPresentedController:(UIViewController*)presented
+                         presentingController:(UIViewController*)presenting
+                             sourceController:(UIViewController*)source {
+  return nil;
+}
+
+- (id<UIViewControllerAnimatedTransitioning>)
+    animationControllerForDismissedController:(UIViewController*)dismissed {
+  return nil;
+}
+
+@end
diff --git a/ios/chrome/browser/ui/autofill/autofill_profile_edit_mediator_unittest.mm b/ios/chrome/browser/ui/autofill/autofill_profile_edit_mediator_unittest.mm
index 77f7289a..7afeb21 100644
--- a/ios/chrome/browser/ui/autofill/autofill_profile_edit_mediator_unittest.mm
+++ b/ios/chrome/browser/ui/autofill/autofill_profile_edit_mediator_unittest.mm
@@ -8,6 +8,7 @@
 #import "base/strings/sys_string_conversions.h"
 #import "components/autofill/core/browser/address_data_manager.h"
 #import "components/autofill/core/browser/autofill_test_utils.h"
+#import "components/autofill/core/browser/geo/alternative_state_name_map_updater.h"
 #import "components/autofill/core/browser/geo/autofill_country.h"
 #import "components/autofill/core/browser/personal_data_manager.h"
 #import "components/autofill/core/browser/ui/country_combobox_model.h"
diff --git a/ios/chrome/browser/ui/autofill/manual_fill/card_view_controller_egtest.mm b/ios/chrome/browser/ui/autofill/manual_fill/card_view_controller_egtest.mm
index 2ea7550..de9627c 100644
--- a/ios/chrome/browser/ui/autofill/manual_fill/card_view_controller_egtest.mm
+++ b/ios/chrome/browser/ui/autofill/manual_fill/card_view_controller_egtest.mm
@@ -25,6 +25,7 @@
 #import "url/gurl.h"
 
 using base::test::ios::kWaitForActionTimeout;
+using chrome_test_util::ButtonWithAccessibilityLabelId;
 using chrome_test_util::CancelButton;
 using chrome_test_util::ManualFallbackAddPaymentMethodMatcher;
 using chrome_test_util::ManualFallbackCreditCardIconMatcher;
@@ -141,6 +142,13 @@
                     grey_interactable(), nullptr);
 }
 
+// Matcher for the "Autofill Form" button shown in the payment method cells.
+id<GREYMatcher> AutofillFormButton() {
+  return grey_allOf(ButtonWithAccessibilityLabelId(
+                        IDS_IOS_MANUAL_FALLBACK_AUTOFILL_FORM_BUTTON_TITLE),
+                    grey_interactable(), nullptr);
+}
+
 // Opens the payment method manual fill view when there are no saved payment
 // methods and verifies that the card view controller is visible afterwards.
 // Only useful when the `kIOSKeyboardAccessoryUpgrade` feature is enabled.
@@ -855,6 +863,33 @@
   // TODO(crbug.com/326413453): Check that the card details opened.
 }
 
+// Tests that tapping the "Autofill Form" button fills the payment form with
+// the right data.
+- (void)testAutofillFormButtonFillsForm {
+  if (![AutofillAppInterface isKeyboardAccessoryUpgradeEnabled]) {
+    EARL_GREY_TEST_DISABLED(@"This test is not relevant when the Keyboard "
+                            @"Accessory Upgrade feature is disabled.")
+  }
+
+  // Save a card.
+  [AutofillAppInterface saveLocalCreditCard];
+
+  // Bring up the keyboard
+  [[EarlGrey selectElementWithMatcher:chrome_test_util::WebViewMatcher()]
+      performAction:TapWebElementWithId(kFormElementName)];
+  GREYAssertTrue([EarlGrey isKeyboardShownWithError:nil],
+                 @"Keyboard Should be Shown");
+
+  // Open the payment method manual fill view.
+  OpenPaymentMethodManualFillView();
+
+  [[EarlGrey selectElementWithMatcher:AutofillFormButton()]
+      assertWithMatcher:grey_sufficientlyVisible()];
+
+  // TODO(crbug.com/326413323): Perform tap on the button and assert that the
+  // form was filled.
+}
+
 #pragma mark - Private
 
 - (void)verifyCreditCardButtonWithTitle:(NSString*)title
diff --git a/ios/chrome/browser/ui/autofill/manual_fill/manual_fill_card_cell.mm b/ios/chrome/browser/ui/autofill/manual_fill/manual_fill_card_cell.mm
index 6c0f2a0..8aacf68 100644
--- a/ios/chrome/browser/ui/autofill/manual_fill/manual_fill_card_cell.mm
+++ b/ios/chrome/browser/ui/autofill/manual_fill/manual_fill_card_cell.mm
@@ -139,6 +139,9 @@
 // the rest of the cell.
 @property(nonatomic, strong) UIView* virtualCardInstructionsSeparator;
 
+// Button to autofill the current form with the card's data.
+@property(nonatomic, strong) UIButton* autofillFormButton;
+
 @end
 
 @implementation ManualFillCardCell
@@ -273,6 +276,11 @@
     [self.contentView addSubview:self.cardholderButton];
   }
 
+  if (IsKeyboardAccessoryUpgradeEnabled()) {
+    self.autofillFormButton = CreateAutofillFormButton();
+    [self.contentView addSubview:self.autofillFormButton];
+  }
+
   [self horizontallyArrangeViews:expirationDateSeparatorLabel];
 }
 
@@ -324,6 +332,11 @@
         AppendConstraintsHorizontalEqualOrSmallerThanGuide);
   }
 
+  if (IsKeyboardAccessoryUpgradeEnabled()) {
+    AppendHorizontalConstraintsForViews(
+        staticConstraints, @[ self.autofillFormButton ], self.layoutGuide);
+  }
+
   // Without this set, Voice Over will read the content vertically instead of
   // horizontally.
   self.contentView.shouldGroupAccessibilityChildren = YES;
@@ -474,6 +487,12 @@
   AddChipGroupsToVerticalLeadViews(@[ cardInfoGroupVerticalLeadChips ],
                                    verticalLeadViews);
 
+  if (IsKeyboardAccessoryUpgradeEnabled()) {
+    AddViewToVerticalLeadViews(self.autofillFormButton,
+                               ManualFillCellView::ElementType::kOther,
+                               verticalLeadViews);
+  }
+
   // Set and activate constraints.
   AppendVerticalConstraintsSpacingForViews(self.dynamicConstraints,
                                            verticalLeadViews, self.layoutGuide);
diff --git a/ios/chrome/browser/ui/settings/autofill/autofill_credit_card_table_view_controller_unittest.mm b/ios/chrome/browser/ui/settings/autofill/autofill_credit_card_table_view_controller_unittest.mm
index f7be3a2..b7daa0e 100644
--- a/ios/chrome/browser/ui/settings/autofill/autofill_credit_card_table_view_controller_unittest.mm
+++ b/ios/chrome/browser/ui/settings/autofill/autofill_credit_card_table_view_controller_unittest.mm
@@ -11,6 +11,7 @@
 #import "base/uuid.h"
 #import "components/autofill/core/browser/address_data_manager.h"
 #import "components/autofill/core/browser/data_model/credit_card.h"
+#import "components/autofill/core/browser/geo/alternative_state_name_map_updater.h"
 #import "components/autofill/core/browser/payments_data_manager.h"
 #import "components/autofill/core/browser/personal_data_manager.h"
 #import "components/autofill/core/browser/personal_data_manager_test_utils.h"
diff --git a/ios/chrome/browser/ui/settings/autofill/autofill_profile_table_view_controller_unittest.mm b/ios/chrome/browser/ui/settings/autofill/autofill_profile_table_view_controller_unittest.mm
index ef85c3b..931233f 100644
--- a/ios/chrome/browser/ui/settings/autofill/autofill_profile_table_view_controller_unittest.mm
+++ b/ios/chrome/browser/ui/settings/autofill/autofill_profile_table_view_controller_unittest.mm
@@ -10,6 +10,7 @@
 #import "base/uuid.h"
 #import "components/autofill/core/browser/address_data_manager.h"
 #import "components/autofill/core/browser/data_model/autofill_profile.h"
+#import "components/autofill/core/browser/geo/alternative_state_name_map_updater.h"
 #import "components/autofill/core/browser/personal_data_manager.h"
 #import "components/autofill/core/browser/personal_data_manager_test_utils.h"
 #import "components/autofill/core/common/autofill_features.h"
diff --git a/ios/chrome/browser/ui/tab_switcher/tab_grid/grid/tab_groups/create_tab_group_view_controller.mm b/ios/chrome/browser/ui/tab_switcher/tab_grid/grid/tab_groups/create_tab_group_view_controller.mm
index f222ce1..d1bc7d3 100644
--- a/ios/chrome/browser/ui/tab_switcher/tab_grid/grid/tab_groups/create_tab_group_view_controller.mm
+++ b/ios/chrome/browser/ui/tab_switcher/tab_grid/grid/tab_groups/create_tab_group_view_controller.mm
@@ -40,7 +40,6 @@
 // Group color selection constants.
 const CGFloat kColoredButtonSize = 24;
 const CGFloat kColoredButtonContentInset = 8;
-const CGFloat kColorSelectionImageSize = 13;
 const CGFloat kColoredButtonWidthAndHeight = 40;
 const CGFloat kColorListBottomMargin = 16;
 const CGFloat kColoredDotSize = 21;
@@ -423,38 +422,21 @@
         configurationWithPointSize:kColoredButtonSize
                             weight:UIImageSymbolWeightRegular
                              scale:UIImageSymbolScaleDefault];
-    UIImage* baseImage =
+
+    UIImage* normalSymbolImage =
         DefaultSymbolWithConfiguration(kCircleFillSymbol, configuration);
-    baseImage =
-        [baseImage imageWithTintColor:TabGroup::ColorForTabGroupColorId(colorID)
-                        renderingMode:UIImageRenderingModeAlwaysOriginal];
-
-    UIImageSymbolConfiguration* selectionConfiguration =
-        [UIImageSymbolConfiguration
-            configurationWithPointSize:kColorSelectionImageSize
-                                weight:UIImageSymbolWeightBold
-                                 scale:UIImageSymbolScaleLarge];
-    UIImage* selectionRingImage =
-        DefaultSymbolWithConfiguration(kCircleSymbol, selectionConfiguration);
-    selectionRingImage = [selectionRingImage
-        imageWithTintColor:[UIColor colorNamed:kGrey100Color]
+    normalSymbolImage = [normalSymbolImage
+        imageWithTintColor:TabGroup::ColorForTabGroupColorId(colorID)
              renderingMode:UIImageRenderingModeAlwaysOriginal];
-    UIGraphicsBeginImageContextWithOptions(baseImage.size, NO, 0.0f);
-    [baseImage drawInRect:CGRectMake(0, 0, baseImage.size.width,
-                                     baseImage.size.height)];
-    [selectionRingImage
-        drawInRect:CGRectMake(baseImage.size.width / 2 -
-                                  selectionRingImage.size.width / 2,
-                              baseImage.size.height / 2 -
-                                  selectionRingImage.size.height / 2,
-                              selectionRingImage.size.width,
-                              selectionRingImage.size.height)];
-    UIImage* selectionImage = UIGraphicsGetImageFromCurrentImageContext();
-    UIGraphicsEndImageContext();
 
-    [colorButton setImage:baseImage forState:UIControlStateNormal];
-    [colorButton setImage:selectionImage forState:UIControlStateSelected];
+    UIImage* selectedSymbolImage =
+        DefaultSymbolWithConfiguration(kCircleCircleFillSymbol, configuration);
+    selectedSymbolImage = [selectedSymbolImage
+        imageWithTintColor:TabGroup::ColorForTabGroupColorId(colorID)
+             renderingMode:UIImageRenderingModeAlwaysOriginal];
 
+    [colorButton setImage:normalSymbolImage forState:UIControlStateNormal];
+    [colorButton setImage:selectedSymbolImage forState:UIControlStateSelected];
     [colorButton addTarget:self
                     action:@selector(coloredButtonTapped:)
           forControlEvents:UIControlEventTouchUpInside];
diff --git a/media/base/media_switches.cc b/media/base/media_switches.cc
index 0cc394d..0c238a2 100644
--- a/media/base/media_switches.cc
+++ b/media/base/media_switches.cc
@@ -1005,11 +1005,6 @@
              "LiveCaptionWebAudio",
              base::FEATURE_ENABLED_BY_DEFAULT);
 
-// Live Caption runs system-wide on ChromeOS, as opposed to just in the browser.
-BASE_FEATURE(kLiveCaptionSystemWideOnChromeOS,
-             "LiveCaptionSystemWideOnChromeOS",
-             base::FEATURE_DISABLED_BY_DEFAULT);
-
 // Live Translate translates captions generated by Live Caption.
 BASE_FEATURE(kLiveTranslate,
              "LiveTranslate",
diff --git a/media/base/media_switches.h b/media/base/media_switches.h
index 441804e..c29cd4ee0 100644
--- a/media/base/media_switches.h
+++ b/media/base/media_switches.h
@@ -283,7 +283,6 @@
 MEDIA_EXPORT BASE_DECLARE_FEATURE(kLiveCaptionUseGreedyTextStabilizer);
 MEDIA_EXPORT BASE_DECLARE_FEATURE(kLiveCaptionUseWaitK);
 MEDIA_EXPORT BASE_DECLARE_FEATURE(kLiveCaptionWebAudio);
-MEDIA_EXPORT BASE_DECLARE_FEATURE(kLiveCaptionSystemWideOnChromeOS);
 MEDIA_EXPORT BASE_DECLARE_FEATURE(kLiveTranslate);
 MEDIA_EXPORT BASE_DECLARE_FEATURE(kLowDelayVideoRenderingOnLiveStream);
 #if BUILDFLAG(IS_MAC)
diff --git a/media/gpu/ipc/service/media_gpu_channel_manager.h b/media/gpu/ipc/service/media_gpu_channel_manager.h
index ad3c13e..b094866 100644
--- a/media/gpu/ipc/service/media_gpu_channel_manager.h
+++ b/media/gpu/ipc/service/media_gpu_channel_manager.h
@@ -30,8 +30,7 @@
 
 class MediaGpuChannel;
 
-class MediaGpuChannelManager
-    : public base::SupportsWeakPtr<MediaGpuChannelManager> {
+class MediaGpuChannelManager final {
  public:
   explicit MediaGpuChannelManager(gpu::GpuChannelManager* channel_manager);
   MediaGpuChannelManager(const MediaGpuChannelManager&) = delete;
@@ -51,6 +50,10 @@
 
   scoped_refptr<gpu::SharedContextState> GetSharedContextState();
 
+  base::WeakPtr<MediaGpuChannelManager> AsWeakPtr() {
+    return weak_ptr_factory_.GetWeakPtr();
+  }
+
  private:
   const raw_ptr<gpu::GpuChannelManager> channel_manager_;
   std::unordered_map<int32_t, std::unique_ptr<MediaGpuChannel>>
@@ -58,6 +61,7 @@
   std::map<base::UnguessableToken, int32_t> token_to_channel_;
   std::map<int32_t, base::UnguessableToken> channel_to_token_;
   AndroidOverlayMojoFactoryCB overlay_factory_cb_;
+  base::WeakPtrFactory<MediaGpuChannelManager> weak_ptr_factory_{this};
 };
 
 }  // namespace media
diff --git a/mojo/public/interfaces/bindings/tests/data/README.md b/mojo/public/interfaces/bindings/tests/data/README.md
new file mode 100644
index 0000000..13d964b9
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/README.md
@@ -0,0 +1,27 @@
+# Mojo conformance test data
+The files in [validations](validations) are test data for mojo conformance
+tests, which validate the memory layout of mojo messages.
+
+Run validation tests using the following command:
+
+```
+autoninja -C out/Default mojo_unittests
+out/Default/bin/run_mojo_unittests --gtest_filter=ValidationTest.*
+```
+
+Note that you do not need to rebuild after changing the test data.
+
+## Adding test data
+
+1. Add a test method if necessary in
+   [validation test interfaces](../validation_test_interfaces.mojom).
+1. Add a .data and .expected file in [validations](validations).
+   * The syntax for the .data file can be found in the
+     [validation test input parser](/mojo/public/cpp/bindings/tests/validation_test_input_parser.h).
+1. Run `ValidationTest.*` to ensure that tests work as expected.
+1. Update [validation_data_files.gni](../validation_data_files.gni) with your
+   test files.
+1. Use [python script](/build/ios/update_bundle_filelist.py) to update
+   [validation_unittest_bundle_data.filelist](../validation_unittest_bundle_data.filelist).
+   * The presubmit will give you a copy pastable command, so there is no need to
+     to figure out how to invoke the script.
diff --git a/net/base/features.cc b/net/base/features.cc
index 1af574a..30cc87c3 100644
--- a/net/base/features.cc
+++ b/net/base/features.cc
@@ -383,10 +383,6 @@
     &kEnableIpProtectionProxy, /*name=*/"IpPrivacyDirectOnly",
     /*default_value=*/false};
 
-const base::FeatureParam<std::string> kIpPrivacyProxyBPsk{
-    &kEnableIpProtectionProxy, /*name=*/"IpPrivacyProxyBPsk",
-    /*default_value=*/""};
-
 const base::FeatureParam<bool> kIpPrivacyIncludeOAuthTokenInGetProxyConfig{
     &kEnableIpProtectionProxy,
     /*name=*/"IpPrivacyIncludeOAuthTokenInGetProxyConfig",
diff --git a/net/base/features.h b/net/base/features.h
index 04b94501..e712ed4c 100644
--- a/net/base/features.h
+++ b/net/base/features.h
@@ -392,10 +392,6 @@
 // connections that _would_ have been proxied, but were not.
 NET_EXPORT extern const base::FeatureParam<bool> kIpPrivacyDirectOnly;
 
-// The PSK added to connections to proxyB with `Proxy-Authorization: Preshared
-// $PSK`.
-NET_EXPORT extern const base::FeatureParam<std::string> kIpPrivacyProxyBPsk;
-
 // If true, pass OAuth token to Phosphor in GetProxyConfig API for IP
 // Protection.
 NET_EXPORT extern const base::FeatureParam<bool>
diff --git a/net/http/http_util.cc b/net/http/http_util.cc
index d2483a1..c39a273 100644
--- a/net/http/http_util.cc
+++ b/net/http/http_util.cc
@@ -427,7 +427,11 @@
       "www-authenticate", "proxy-authenticate",
       // STS specifies that UAs must not process any STS headers after the first
       // one.
-      "strict-transport-security"};
+      "strict-transport-security",
+      // Attribution reporting registration header values are JSON, coalescing
+      // them changes the semantic. See https://crbug.com/40242261 for details.
+      "attribution-reporting-register-source",
+      "attribution-reporting-register-trigger"};
 
   for (std::string_view header : kNonCoalescingHeaders) {
     if (base::EqualsCaseInsensitiveASCII(name, header)) {
diff --git a/net/http/transport_security_state_static.json b/net/http/transport_security_state_static.json
index 5bb0d06..e354e2a 100644
--- a/net/http/transport_security_state_static.json
+++ b/net/http/transport_security_state_static.json
@@ -133214,7 +133214,6 @@
     { "name": "nikolajmackowski.dk", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "nivelo.io", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "njfog.org", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "nlead.gov", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "nmitaylor.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "nnas.org.uk", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "no-b.kiev.ua", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
@@ -142187,6 +142186,499 @@
     { "name": "westterrehautein.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
     { "name": "willoughbyhillsohio.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
     { "name": "woodsideca.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "36thdistrictcourtmi.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "abcks.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "acc.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "adaircomo.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "adaircosomo.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "addisonvt.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "alamedacountyca.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "altoonawater.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "americancanyon.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "americanclimatecorps.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "andesnewyork.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "anokawineandspirits.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "antrimcountymi.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "apcdca.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "ashlandoregon.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "ashmore.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "astoria.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "atchisoncountymo.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "atlanticcountynj.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "atmore.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "baldwinparkca.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "ballstonspa.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "baltimorecitycouncil.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "baltimorecitysheriff.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "barrycountye911.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "bciltransit.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "bcvwd.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "beavercityut.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "beckermn.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "bedfordboroughpa.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "bedfordcountyvaes.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "bergencountynj.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "beverlyhills.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "blissfieldmichigan.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "blueridgemanorky.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "bluffdale.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "boxeldercountyut.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "boyceville.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "branchcounty.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "breentwp.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "brentwoodpa.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "bridgesofcolorado.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "bringitnc.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "brinkhaven.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "brookshiretx.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "browncountymn.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "brownvillenebraska.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "brunswickme.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "brunswickohio.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "brysoncitync.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "buenavistava.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "buffalowypd.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "cachemosquito.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "caldwellcosdmo.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "calimesa.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "calistogaca.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "cameroncopa.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "carbondaleil.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "carnationwa.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "cayugacounty.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "cccera.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "cdfr-or.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "cedarhillsutah.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "census.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "cerrogordo.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "cfpd-il.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "chandleraz.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "chapelhillnc.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "chautauquacountyny.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "chemungtownshipil.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "childresstexas.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "chukchansihealthsafety.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "cityofabbotsfordwi.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "cityofbrea.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "cityofcamarillo.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "cityofcharlack.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "cityofcolusa.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "cityofdillonsc.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "cityofeyotamn.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "cityoffoley.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "cityoflamesa.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "cityoflaramiewy.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "cityofmeigs.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "cityofmooreok.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "cityofnewfranklinmo.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "cityofnisswa.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "cityofnorco.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "cityofnorforkar.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "cityofpilotgrovemo.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "cityofrockyfordco.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "cityofsavannail.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "cityofsoledad.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "cityofstonewoodwv.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "cityofvernonca.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "cityofwaupunwi.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "cityofwhitefish.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "cityofwooddaleil.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "claremontca.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "clarioncounty.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "claytonwinnebagowi.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "claytwp-hamin.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "clevelandmetroparks.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "climatecore.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "climatecorp.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "climatecorps.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "clintonairport.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "coalcountyok.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "columbustwpmi.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "commodorecoveimprovementdistrict.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "cooscountynh.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "cooscountyor.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "coralville.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "cordovapdak.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "cordovarpd.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "countyofcolusaca.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "cowetaga.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "crestviewky.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "croydon-nh.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "dadecountymo.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "daltonmi.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "daytonabeach.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "denvericmi.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "dgcoks.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "digitaltowpathny.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "divernonil.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "douglascoclerk.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "douglascountywi.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "downievillepudca.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "drewcountyar.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "durangoco.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "durantok.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "earth.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "eastarapmetdistco.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "eastcalntownship.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "ebci.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "ehpsmt.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "ekalaka.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "elburn.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "elginil.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "elkocountynv.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "emmauspd.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "enonvalleyboropa.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "eppingnh.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "faithnc.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "fbcbondprojectstx.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "fedsfeedfamilies.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "florencecountysc.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "fontanaks.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "foresthillspa.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "fountaincitywi.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "fremontcountyclerkco.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "fresnocountyjobs.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "fruitlandia.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "fullertonca.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "fultonsuperiorcourtga.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "gaithersburgmd.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "gallatinmt.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "gardengroveca.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "garfieldcountyco.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "garwiniowa.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "geneseeny.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "genoami.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "ghentmn.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "ghtmi.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "glotwpnjpd.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "gmdsc.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "grandislevt.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "grantcountynm.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "grantcountywv.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "grasslaketownship.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "greenesheriffny.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "greenislemn.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "grovelandfl.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "grovelandflpd.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "gustinetx.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "hamlintownshipeatonco.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "hamptontownshipmi.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "hamtramckcity.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "hanfordca.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "harrisburgnc.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "harriscountyesd11.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "harrodsburgky.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "hartcountyky.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "harveycounty.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "haskellar.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "havredegracepolicemd.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "healthycompetition.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "hfxtwppa.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "highspire.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "hillside-il.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "hillsvilleva.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "hinckleymn.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "hollandbrowncountywi.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "hoopa-nsn.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "hopewellpolicenj.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "hopkintonpoliceri.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "hotspringcountysoar.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "hotspringscountywy.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "houstoncountyga.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "hsi.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "hubbardtx.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "hughescountyok911.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "humphreystnsheriff.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "hylandhills.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "hyrumcity.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "idealga.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "ironcountyut.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "isanticountymn.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "islelamotte.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "ivinsutah.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "jacksoncountymn.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "jacksonvillega.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "jamestwpmi.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "jcvcd.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "jeffersoncitytn.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "jeffersoncountysheriffmt.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "jocomo911.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "johnsburgny.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "joinnjdoc.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "jordanny.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "jvwcd.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "kansaslabor.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "kansasui.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "kawcityok.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "keewatinmnpd.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "kempercountyms.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "kendallcountytx.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "kermittx.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "keystoneco.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "kinneymn.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "kirksville.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "kittcom.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "klamath911.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "kscourts.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "kscpost.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "kshub.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "ksleg.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "kslegislature.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "kslpa.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "lagrangeutilitiesky.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "lagunabeach.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "lakeelmo.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "lakesitetn.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "lakeviewpdtx.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "langleywa.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "laredo.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "lauriemo.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "lawrenceks.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "lcog-or.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "leecountyar.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "lewisborony.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "lewisboropd.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "limestonecounty-al.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "limestonecountyema-al.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "lincolncomo.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "lincolntonnc.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "littlecompton.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "littlesilvernj.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "livingstontx.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "loudouncountyva.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "lowerdrugcosts.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "loyaltown-wi.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "luca-appeals.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "lynntownshipmi.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "macoupincountypdil.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "madisonvilleky.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "magutah.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "mankato-mn.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "mankatomn.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "maplegrovewi.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "marionswcdfl.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "mariontownshipbeavercountypa.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "marlboroughmo.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "marshfieldmaine.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "martinsvilleva.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "mcdonaldcountymohealth.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "mdcfl.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "meredithnh.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "midvalleysewer.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "millersburgoregon.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "minervapark.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "missionviejo.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "moabcity.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "mokanemo.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "monautah.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "monheganplantation.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "mononacountyiowa.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "monroeut.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "monroviaca.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "monticellomn.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "morristownvt.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "moultoncityal.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "mounthollyvt.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "mountvernonny.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "mtcloudcommunications.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "muckleshoot.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "myvermont.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "myvi.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "nampa.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "nampapolice.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "nevadamo.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "newbrunswicknj.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "newflorencemo.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "newfoldenmn.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "newingtonnh.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "newportmn.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "newrichlandmn.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "nffiredistrictoh.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "nhsau107.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "ninetysixsc.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "njcsit.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "njmihia.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "nlead.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "nlrwu.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "nmdoj.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "nmslo.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "nokomiswi.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "northlogancity.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "northplains.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "northsalemny.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "nvup.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "obetz.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "ofallonmo.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "oglala.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "okawville.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "orcw.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "oregonstatetreasury.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "osagecountyok.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "otsegocountyny.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "ovaz.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "oxnard.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "palousewa.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "pamplicosc.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "paramuses.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "paramuspolice.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "parkersburgwv.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "parmatwp.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "pascopa.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "pekinil.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "pelhamma.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "pelhamny.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "pelicanbaytx.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "pendletonor.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "perry-mo.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "pettisclerk.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "pineislandmn.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "pinellassheriff.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "pinole.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "pittsvillewi.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "plainsboronj.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "pleasantscountywv.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "poncatribe-ne.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "portofbellingham.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "portofportisabeltx.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "portwentworthga.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "pottcountysheriff.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "pottia.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "poway.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "prairiedulongil.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "presqueislewi.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "prestonmn.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "providenceut.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "puercovalleyfireaz.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "raycountymo.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "rcflood.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "readysiskiyou.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "readysurrync.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "reddingct.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "redlands.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "redlandspd.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "redondo.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "redwoodcounty-mn.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "rhanc.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "ringgoldtownship.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "rioblanco.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "riversidesheriff.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "rockmn.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "rolandok.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "rollacity.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "rollingplainsgcd.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "rosscountyohiotaxlist.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "roywater.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "rushfordvillagemn.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "safgrandchallenge.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "salinas.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "sanbenitocountyca.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "sandag.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "sangabriel.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "sanleandro.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "santa-ana.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "santabarbaravote.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "sbristolme.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "sdmetrofire.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "selmer.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "seminolecityok.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "sfelections.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "sgcityutah.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "shawneeohio.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "sheridanarpd.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "shilohtownshipil.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "shippensburgpdpa.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "smfdmn.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "soaplakewa.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "somersetprosnj.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "southportland.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "spanishfortal.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "spokane.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "spokanesheriff.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "spotsy.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "stancounty.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "stcharlescounty.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "stjohnks.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "stoddardwi.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "sycuan.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "tacoma.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "taneycountyad.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "tangentor.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "tehama.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "templenh.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "thermopoliswy.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "tillamook911.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "tooeleco.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "torringtonwy.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "townofbartonvt.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "townofbeaver-clark-wi.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "townofbergenwi.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "townofcasselwi.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "townofclearcreek.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "townofdaywi.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "townofeaglewi.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "townofeastonwi.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "townofeasttroywi.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "townofeutawville.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "townofexeterny.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "townoffreedomnh.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "townofhalseywi.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "townoflisbonwi.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "townofmiltonnc.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "townofnunnco.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "townofoasiswi.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "townofportwashingtonwi.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "townofsomersetmd.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "townoftruckee.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "townofwinter.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "traillcountynd.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "trinidadhousing.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "troypolicemo.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "troytx.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "troyvt.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "txsmartbids.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "txsmartbuy.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "tylertexas.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "uintah.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "ukraineoversight.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "valleycountyid.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "vanburencountytn.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "vermilionsheriff.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "villageofalbanywi.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "villageofeastaltonil.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "villageofemersonne.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "villageofgrandview.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "villageoflongcreekil.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "villageofmoravia.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "villageofthebranchny.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "villageofwarrenil.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "virginiaworks.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "voslwi.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "voteindiana.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "waitsfieldvt.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "walkervillemt.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "wallowacounty.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "wasatchsheriff.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "washburnnd.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "watsonvillelibrary.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "wbcityut.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "wellsburgwvpd.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "wesleyville.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "westernriversidecog.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "westmifflin.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "westonlakestexas.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "westoverwv.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "whitecreekny.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "wilmington-il.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "wilmingtonohio.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "winfieldpa.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "winthropma.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "wvsdca.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "wyckoffnj.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "yatescenter.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "yavapaiready.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "yellowsprings.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "yorksc.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "yubacity.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
     // END OF ETLD-OWNER REQUESTED ENTRIES
 
     // To avoid trailing comma changes from showing up in diffs, we place a
diff --git a/net/http/transport_security_state_static.pins b/net/http/transport_security_state_static.pins
index aa54131..0ab24a73 100644
--- a/net/http/transport_security_state_static.pins
+++ b/net/http/transport_security_state_static.pins
@@ -43,9 +43,9 @@
 #   hash function for preloaded entries again (we have already done so once).
 #
 
-# Last updated: 2024-05-05 12:54 UTC
+# Last updated: 2024-05-06 12:54 UTC
 PinsListTimestamp
-1714913696
+1715000097
 
 TestSPKI
 sha256/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=
diff --git a/net/http/transport_security_state_static_pins.json b/net/http/transport_security_state_static_pins.json
index 1929c31..d053d264 100644
--- a/net/http/transport_security_state_static_pins.json
+++ b/net/http/transport_security_state_static_pins.json
@@ -31,7 +31,7 @@
 // the 'static_spki_hashes' and 'bad_static_spki_hashes' fields in 'pinsets'
 // refer to, and the timestamp at which the pins list was last updated.
 //
-// Last updated: 2024-05-05 12:54 UTC
+// Last updated: 2024-05-06 12:54 UTC
 //
 {
   "pinsets": [
diff --git a/net/url_request/url_request_http_job.cc b/net/url_request/url_request_http_job.cc
index 4df2949..be520e6 100644
--- a/net/url_request/url_request_http_job.cc
+++ b/net/url_request/url_request_http_job.cc
@@ -381,9 +381,6 @@
           IsSameSiteIgnoringWebSocketProtocol(request_initiator_site().value(),
                                               request()->url()));
 
-  UMA_HISTOGRAM_BOOLEAN("Net.HttpJob.CanIncludeCookies",
-                        ShouldAddCookieHeader());
-
   CookieStore* cookie_store = request()->context()->cookie_store();
   const CookieAccessDelegate* delegate =
       cookie_store ? cookie_store->cookie_access_delegate() : nullptr;
diff --git a/remoting/host/crash/crash_uploader_main.cc b/remoting/host/crash/crash_uploader_main.cc
index 68eb7b07..c0ad4d1 100644
--- a/remoting/host/crash/crash_uploader_main.cc
+++ b/remoting/host/crash/crash_uploader_main.cc
@@ -10,6 +10,7 @@
 #include "base/memory/scoped_refptr.h"
 #include "base/message_loop/message_pump_type.h"
 #include "base/run_loop.h"
+#include "base/strings/string_number_conversions.h"
 #include "base/task/single_thread_task_executor.h"
 #include "base/task/single_thread_task_runner.h"
 #include "base/task/thread_pool/thread_pool_instance.h"
@@ -25,6 +26,10 @@
 
 namespace remoting {
 
+namespace {
+constexpr char kTimeoutSwitchName[] = "timeout";
+}
+
 int CrashUploaderMain(int argc, char** argv) {
   base::AtExitManager exit_manager;
 
@@ -34,8 +39,22 @@
   auto task_runner = base::SingleThreadTaskRunner::GetCurrentDefault();
 
   base::CommandLine::Init(argc, argv);
+
   remoting::InitHostLogging();
 
+  base::TimeDelta timeout;
+  const base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
+  if (command_line->HasSwitch(kTimeoutSwitchName)) {
+    auto switch_value = command_line->GetSwitchValueASCII(kTimeoutSwitchName);
+    int parsed_value = 0;
+    if (base::StringToInt(switch_value, &parsed_value)) {
+      timeout = base::Seconds(std::max(0, parsed_value));
+    } else {
+      LOG(WARNING) << "Failed to parse timeout switch value: " << switch_value;
+      return 1;
+    }
+  }
+
   mojo::core::Init();
 
   auto url_request_context_getter =
@@ -54,7 +73,15 @@
       base::BindRepeating(&CrashFileUploader::Upload,
                           base::Unretained(&crash_file_uploader)));
 
-  base::RunLoop().Run();
+  base::RunLoop run_loop;
+
+  if (!timeout.is_zero()) {
+    LOG(INFO) << "Watching for crash dumps for " << timeout.InSeconds()
+              << " seconds...";
+    task_runner->PostDelayedTask(FROM_HERE, run_loop.QuitClosure(), timeout);
+  }
+
+  run_loop.Run();
 
   // Block until tasks blocking shutdown have completed their execution.
   base::ThreadPoolInstance::Get()->Shutdown();
diff --git a/remoting/protocol/webrtc_frame_scheduler_constant_rate.cc b/remoting/protocol/webrtc_frame_scheduler_constant_rate.cc
index 59a05192..4bd59e50 100644
--- a/remoting/protocol/webrtc_frame_scheduler_constant_rate.cc
+++ b/remoting/protocol/webrtc_frame_scheduler_constant_rate.cc
@@ -25,8 +25,8 @@
   // lower than the target. By adjusting the capture rate by ~2ms, the host will
   // generate frames at, or slightly above, the target frame rate. If a value of
   // 1ms is used, then the host will generate frames at, or slightly below, the
-  // target. They higher of the two was chosen for better performance on high-
-  // CPU machines.
+  // target. The higher of the two was chosen for better performance on high-CPU
+  // machines.
   return base::Milliseconds(2);
 }
 }  // namespace
diff --git a/services/network/cookie_settings.cc b/services/network/cookie_settings.cc
index da06ed49..1f7c390e 100644
--- a/services/network/cookie_settings.cc
+++ b/services/network/cookie_settings.cc
@@ -33,6 +33,7 @@
 #include "net/cookies/cookie_setting_override.h"
 #include "net/cookies/cookie_util.h"
 #include "net/cookies/static_cookie_policy.h"
+#include "net/first_party_sets/first_party_set_metadata.h"
 #include "services/network/tpcd/metadata/manager.h"
 #include "url/gurl.h"
 #include "url/origin.h"
@@ -271,39 +272,9 @@
   bool allowed = IsCookieAllowed(cookie, setting_with_metadata);
   bool is_third_party_request = IsThirdPartyRequest(url, site_for_cookies);
   if (cookie_inclusion_status) {
-    if (allowed) {
-      if (AffectedByThirdPartyCookiePhaseout(cookie.SameSite(),
-                                             is_third_party_request,
-                                             cookie.IsPartitioned())) {
-        if (ShouldBlockThirdPartyCookies()) {
-          cookie_inclusion_status->MaybeSetExemptionReason(GetExemptionReason(
-              setting_with_metadata.third_party_cookie_allow_mechanism()));
-        } else if (!setting_with_metadata.is_explicit_setting()) {
-          // The cookie should be allowed by default to have this warning
-          // reason.
-          cookie_inclusion_status->AddWarningReason(
-              net::CookieInclusionStatus::WARN_THIRD_PARTY_PHASEOUT);
-        }
-      }
-    } else {
-      if (IsThirdPartyPhaseoutEnabled() &&
-          AffectedByThirdPartyCookiePhaseout(cookie.SameSite(),
-                                             is_third_party_request,
-                                             cookie.IsPartitioned()) &&
-          !setting_with_metadata.is_explicit_setting()) {
-        cookie_inclusion_status->AddExclusionReason(
-            net::CookieInclusionStatus::EXCLUDE_THIRD_PARTY_PHASEOUT);
-
-        if (first_party_set_metadata.AreSitesInSameFirstPartySet()) {
-          cookie_inclusion_status->AddExclusionReason(
-              net::CookieInclusionStatus::
-                  EXCLUDE_THIRD_PARTY_BLOCKED_WITHIN_FIRST_PARTY_SET);
-        }
-      } else {
-        cookie_inclusion_status->AddExclusionReason(
-            net::CookieInclusionStatus::EXCLUDE_USER_PREFERENCES);
-      }
-    }
+    AugmentInclusionStatus(cookie, is_third_party_request,
+                           setting_with_metadata, first_party_set_metadata,
+                           *cookie_inclusion_status);
   }
   return allowed;
 }
@@ -377,52 +348,16 @@
   // that meets the conditions and add the `ExclusionReason` for cookies
   // that ought to be blocked.
   for (net::CookieWithAccessResult& cookie : maybe_included_cookies) {
-    if (IsCookieAllowed(cookie.cookie, setting_with_metadata)) {
-      is_any_allowed = true;
-      if (AffectedByThirdPartyCookiePhaseout(cookie.cookie.SameSite(),
-                                             is_third_party_request,
-                                             cookie.cookie.IsPartitioned())) {
-        if (ShouldBlockThirdPartyCookies()) {
-          cookie.access_result.status.MaybeSetExemptionReason(
-              GetExemptionReason(
-                  setting_with_metadata.third_party_cookie_allow_mechanism()));
-        } else if (!setting_with_metadata.is_explicit_setting()) {
-          // The cookie should be allowed by default to have this warning
-          // reason.
-          cookie.access_result.status.AddWarningReason(
-              net::CookieInclusionStatus::WARN_THIRD_PARTY_PHASEOUT);
-        }
-      }
-    } else {
-      // Use a different exclusion reason when the 3pc is blocked by browser.
-      if (IsThirdPartyPhaseoutEnabled() &&
-          AffectedByThirdPartyCookiePhaseout(cookie.cookie.SameSite(),
-                                             is_third_party_request,
-                                             cookie.cookie.IsPartitioned()) &&
-          !setting_with_metadata.is_explicit_setting()) {
-        cookie.access_result.status.AddExclusionReason(
-            net::CookieInclusionStatus::EXCLUDE_THIRD_PARTY_PHASEOUT);
-
-        if (first_party_set_metadata.AreSitesInSameFirstPartySet()) {
-          cookie.access_result.status.AddExclusionReason(
-              net::CookieInclusionStatus::
-                  EXCLUDE_THIRD_PARTY_BLOCKED_WITHIN_FIRST_PARTY_SET);
-        }
-      } else {
-        // User has a explicit setting to block 3pc.
-        cookie.access_result.status.AddExclusionReason(
-            net::CookieInclusionStatus::EXCLUDE_USER_PREFERENCES);
-      }
-    }
+    is_any_allowed =
+        is_any_allowed || IsCookieAllowed(cookie.cookie, setting_with_metadata);
+    AugmentInclusionStatus(cookie.cookie, is_third_party_request,
+                           setting_with_metadata, first_party_set_metadata,
+                           cookie.access_result.status);
   }
   for (net::CookieWithAccessResult& cookie : excluded_cookies) {
-    if (!IsCookieAllowed(cookie.cookie, setting_with_metadata)) {
-      if (!IsThirdPartyPhaseoutEnabled() ||
-          setting_with_metadata.is_explicit_setting()) {
-        cookie.access_result.status.AddExclusionReason(
-            net::CookieInclusionStatus::EXCLUDE_USER_PREFERENCES);
-      }
-    }
+    AugmentInclusionStatus(cookie.cookie, is_third_party_request,
+                           setting_with_metadata, first_party_set_metadata,
+                           cookie.access_result.status);
   }
   const auto to_be_moved = base::ranges::stable_partition(
       maybe_included_cookies, [](const net::CookieWithAccessResult& cookie) {
@@ -555,4 +490,47 @@
          mitigations_enabled_for_3pcd_;
 }
 
+void CookieSettings::AugmentInclusionStatus(
+    const net::CanonicalCookie& cookie,
+    bool is_third_party_request,
+    const CookieSettings::CookieSettingWithMetadata& setting_with_metadata,
+    const net::FirstPartySetMetadata& first_party_set_metadata,
+    net::CookieInclusionStatus& out_status) const {
+  if (IsCookieAllowed(cookie, setting_with_metadata)) {
+    if (AffectedByThirdPartyCookiePhaseout(cookie.SameSite(),
+                                           is_third_party_request,
+                                           cookie.IsPartitioned())) {
+      if (ShouldBlockThirdPartyCookies()) {
+        out_status.MaybeSetExemptionReason(GetExemptionReason(
+            setting_with_metadata.third_party_cookie_allow_mechanism()));
+      } else if (!setting_with_metadata.is_explicit_setting()) {
+        // The cookie should be allowed by default to have this warning
+        // reason.
+        out_status.AddWarningReason(
+            net::CookieInclusionStatus::WARN_THIRD_PARTY_PHASEOUT);
+      }
+    }
+  } else {
+    // Use a different exclusion reason when the 3pc is blocked by browser.
+    if (IsThirdPartyPhaseoutEnabled() &&
+        AffectedByThirdPartyCookiePhaseout(cookie.SameSite(),
+                                           is_third_party_request,
+                                           cookie.IsPartitioned()) &&
+        !setting_with_metadata.is_explicit_setting()) {
+      out_status.AddExclusionReason(
+          net::CookieInclusionStatus::EXCLUDE_THIRD_PARTY_PHASEOUT);
+
+      if (first_party_set_metadata.AreSitesInSameFirstPartySet()) {
+        out_status.AddExclusionReason(
+            net::CookieInclusionStatus::
+                EXCLUDE_THIRD_PARTY_BLOCKED_WITHIN_FIRST_PARTY_SET);
+      }
+    } else {
+      // User has a explicit setting to block 3pc.
+      out_status.AddExclusionReason(
+          net::CookieInclusionStatus::EXCLUDE_USER_PREFERENCES);
+    }
+  }
+}
+
 }  // namespace network
diff --git a/services/network/cookie_settings.h b/services/network/cookie_settings.h
index 1f4cf471..dc11ffa5 100644
--- a/services/network/cookie_settings.h
+++ b/services/network/cookie_settings.h
@@ -223,6 +223,15 @@
       const net::SiteForCookies& site_for_cookies,
       const url::Origin* top_frame_origin);
 
+  // Adds exclusion reasons, warnings, etc. as appropriate to `out_status` for
+  // the given cookie in the given context.
+  void AugmentInclusionStatus(
+      const net::CanonicalCookie& cookie,
+      bool is_third_party_request,
+      const CookieSettings::CookieSettingWithMetadata& setting_with_metadata,
+      const net::FirstPartySetMetadata& first_party_set_metadata,
+      net::CookieInclusionStatus& out_status) const;
+
   // Returns true if at least one content settings is session only.
   bool HasSessionOnlyOrigins() const;
 
diff --git a/services/network/cookie_settings_unittest.cc b/services/network/cookie_settings_unittest.cc
index 8e12e2b..2f64f29 100644
--- a/services/network/cookie_settings_unittest.cc
+++ b/services/network/cookie_settings_unittest.cc
@@ -1306,7 +1306,13 @@
        // The ExclusionReason below is irrelevant, as long as there is
        // one.
        net::CookieAccessResult(net::CookieInclusionStatus(
-           net::CookieInclusionStatus::ExclusionReason::EXCLUDE_SECURE_ONLY))}};
+           net::CookieInclusionStatus::ExclusionReason::EXCLUDE_SECURE_ONLY))},
+      {*MakeCanonicalCookie("excluded_samesitelax", kURL),
+       // The ExclusionReason below is irrelevant, as long as there is
+       // one.
+       net::CookieAccessResult(net::CookieInclusionStatus(
+           net::CookieInclusionStatus::ExclusionReason::EXCLUDE_SECURE_ONLY))},
+  };
   url::Origin origin = url::Origin::Create(GURL(kOtherURL));
 
   const bool expected_any_allowed = false;
@@ -1343,14 +1349,25 @@
                     _, _, _))));
     EXPECT_THAT(
         excluded_cookies,
-        UnorderedElementsAre(MatchesCookieWithAccessResult(
-            net::MatchesCookieWithName("excluded_other"),
-            MatchesCookieAccessResult(
-                HasExactlyExclusionReasonsForTesting(
-                    std::vector<net::CookieInclusionStatus::ExclusionReason>{
-                        net::CookieInclusionStatus::ExclusionReason::
-                            EXCLUDE_SECURE_ONLY}),
-                _, _, _))));
+        UnorderedElementsAre(
+            MatchesCookieWithAccessResult(
+                net::MatchesCookieWithName("excluded_other"),
+                MatchesCookieAccessResult(
+                    HasExactlyExclusionReasonsForTesting(
+                        std::vector<
+                            net::CookieInclusionStatus::ExclusionReason>{
+                            net::CookieInclusionStatus::ExclusionReason::
+                                EXCLUDE_SECURE_ONLY}),
+                    _, _, _)),
+            MatchesCookieWithAccessResult(
+                net::MatchesCookieWithName("excluded_samesitelax"),
+                MatchesCookieAccessResult(
+                    HasExactlyExclusionReasonsForTesting(
+                        std::vector<
+                            net::CookieInclusionStatus::ExclusionReason>{
+                            net::CookieInclusionStatus::ExclusionReason::
+                                EXCLUDE_SECURE_ONLY}),
+                    _, _, _))));
   } else {
     EXPECT_THAT(maybe_included_cookies, IsEmpty());
     EXPECT_THAT(
@@ -1396,6 +1413,17 @@
                                       EXCLUDE_THIRD_PARTY_PHASEOUT
                                 : net::CookieInclusionStatus::
                                       EXCLUDE_USER_PREFERENCES}),
+                    _, _, _)),
+            MatchesCookieWithAccessResult(
+                net::MatchesCookieWithName("excluded_samesitelax"),
+                MatchesCookieAccessResult(
+                    HasExactlyExclusionReasonsForTesting(
+                        std::vector<
+                            net::CookieInclusionStatus::ExclusionReason>{
+                            net::CookieInclusionStatus::EXCLUDE_SECURE_ONLY,
+                            net::CookieInclusionStatus::
+                                EXCLUDE_USER_PREFERENCES,
+                        }),
                     _, _, _))));
   }
 }
diff --git a/services/network/ip_protection/ip_protection_proxy_delegate.cc b/services/network/ip_protection/ip_protection_proxy_delegate.cc
index 6d2853e0..65570c7d 100644
--- a/services/network/ip_protection/ip_protection_proxy_delegate.cc
+++ b/services/network/ip_protection/ip_protection_proxy_delegate.cc
@@ -288,16 +288,6 @@
     VLOG(2) << "NSPD::OnBeforeTunnelRequest() - " << message;
   };
   if (proxy_chain.is_for_ip_protection()) {
-    // Temporarily support a pre-shared key for access to proxyB.
-    if (chain_index == 1) {
-      std::string proxy_b_psk = net::features::kIpPrivacyProxyBPsk.Get();
-      if (!proxy_b_psk.empty()) {
-        vlog("adding proxyB PSK");
-        extra_headers->SetHeader(net::HttpRequestHeaders::kProxyAuthorization,
-                                 base::StrCat({"Preshared ", proxy_b_psk}));
-        return net::OK;
-      }
-    }
     std::optional<network::mojom::BlindSignedAuthTokenPtr> token =
         ipp_config_cache_->GetAuthToken(chain_index);
     if (token) {
diff --git a/services/network/ip_protection/ip_protection_proxy_delegate_unittest.cc b/services/network/ip_protection/ip_protection_proxy_delegate_unittest.cc
index 7cd790d..da85d39 100644
--- a/services/network/ip_protection/ip_protection_proxy_delegate_unittest.cc
+++ b/services/network/ip_protection/ip_protection_proxy_delegate_unittest.cc
@@ -260,39 +260,6 @@
   EXPECT_THAT(headers, Contain("Authorization", "Bearer: a-token"));
 }
 
-TEST_F(IpProtectionProxyDelegateTest, AddsPskToTunnelRequest) {
-  std::map<std::string, std::string> parameters;
-  parameters[net::features::kIpPrivacyProxyBPsk.name] = "seekrit";
-  base::test::ScopedFeatureList scoped_feature_list;
-  scoped_feature_list.InitAndEnableFeatureWithParameters(
-      net::features::kEnableIpProtectionProxy, std::move(parameters));
-
-  auto network_service_proxy_allow_list =
-      NetworkServiceProxyAllowList::CreateForTesting(/*first_party_map=*/{});
-  auto ipp_config_cache = std::make_unique<MockIpProtectionConfigCache>();
-  ipp_config_cache->SetProxyList({MakeChain({"proxya", "proxyb"})});
-  ipp_config_cache->SetNextAuthToken(MakeAuthToken("Bearer: a-token"));
-  auto delegate = CreateDelegate(&network_service_proxy_allow_list,
-                                 std::move(ipp_config_cache));
-
-  net::HttpRequestHeaders headers;
-  auto ip_protection_proxy_chain = net::ProxyChain::ForIpProtection(
-      {net::ProxyServer::FromSchemeHostAndPort(net::ProxyServer::SCHEME_HTTPS,
-                                               "proxya", std::nullopt),
-       net::ProxyServer::FromSchemeHostAndPort(net::ProxyServer::SCHEME_HTTPS,
-                                               "proxyb", std::nullopt)});
-  EXPECT_THAT(delegate->OnBeforeTunnelRequest(ip_protection_proxy_chain,
-                                              /*chain_index=*/0, &headers),
-              IsOk());
-  EXPECT_THAT(headers, testing::Not(Contain("Proxy-Authorization",
-                                            "Preshared seekrit")));
-
-  EXPECT_THAT(delegate->OnBeforeTunnelRequest(ip_protection_proxy_chain,
-                                              /*chain_index=*/1, &headers),
-              IsOk());
-  EXPECT_THAT(headers, Contain("Proxy-Authorization", "Preshared seekrit"));
-}
-
 TEST_F(IpProtectionProxyDelegateTest, ErrorIfConnectionWithNoTokens) {
   auto network_service_proxy_allow_list =
       NetworkServiceProxyAllowList::CreateForTesting(/*first_party_map=*/{});
diff --git a/services/webnn/coreml/graph_builder.cc b/services/webnn/coreml/graph_builder.cc
index 740e4e1..ef04363 100644
--- a/services/webnn/coreml/graph_builder.cc
+++ b/services/webnn/coreml/graph_builder.cc
@@ -660,6 +660,10 @@
             AddOperationForLeakyRelu(*operation->get_leaky_relu(), block));
         break;
       }
+      case mojom::Operation::Tag::kLinear: {
+        RETURN_IF_ERROR(AddOperationForLinear(*operation->get_linear(), block));
+        break;
+      }
       case mojom::Operation::Tag::kMatmul: {
         RETURN_IF_ERROR(AddOperationForMatmul(*operation->get_matmul(), block));
         break;
@@ -729,7 +733,6 @@
       case mojom::Operation::Tag::kHardSwish:
       case mojom::Operation::Tag::kLayerNormalization:
       case mojom::Operation::Tag::kInstanceNormalization:
-      case mojom::Operation::Tag::kLinear:
       case mojom::Operation::Tag::kLstm:
       case mojom::Operation::Tag::kLstmCell:
       case mojom::Operation::Tag::kPad:
@@ -1700,6 +1703,49 @@
                                   operation.output_operand_id, block);
 }
 
+base::expected<void, mojom::ErrorPtr> GraphBuilder::AddOperationForLinear(
+    const mojom::Linear& operation,
+    CoreML::Specification::MILSpec::Block& block) {
+  const OperandInfo& input_operand_info =
+      GetOperandInfo(operation.input_operand_id);
+  CHECK(kFloatDataTypes.contains(input_operand_info.mil_data_type));
+
+  // WebNN's linear operator (alpha * a + beta) is far simpler than CoreML's
+  // "linear" operator (a fully connected layer), so just implement it as
+  //   add(mul(alpha, a), beta)
+
+  // Perform: mul(alpha, a)
+  //
+  // TODO: crbug.com/338667172 - Use float16 when the input is float16.
+  uint64_t alpha_operand_id = GenerateInternalOperandInfo(
+      CoreML::Specification::MILSpec::DataType::FLOAT32, /*dimensions=*/{});
+  RETURN_IF_ERROR(AddInternalConstantWithValue(
+      alpha_operand_id, CreateScalarImmediateValue(operation.alpha), block));
+
+  uint64_t mul_output = GenerateInternalOperandInfo(
+      input_operand_info.mil_data_type, input_operand_info.dimensions);
+  RETURN_IF_ERROR(AddOperationForElementwiseBinary(
+      /*lhs_operand_id=*/operation.input_operand_id,
+      /*rhs_operand_id=*/alpha_operand_id,
+      /*output_operand_id=*/mul_output, mojom::ElementWiseBinary::Kind::kMul,
+      block));
+
+  // Perform: add(mul_output, beta)
+  //
+  // TODO: crbug.com/338667172 - Use float16 when the input is float16.
+  uint64_t beta_operand_id = GenerateInternalOperandInfo(
+      CoreML::Specification::MILSpec::DataType::FLOAT32, /*dimensions=*/{});
+  RETURN_IF_ERROR(AddInternalConstantWithValue(
+      beta_operand_id, CreateScalarImmediateValue(operation.beta), block));
+
+  RETURN_IF_ERROR(AddOperationForElementwiseBinary(
+      /*lhs_operand_id=*/mul_output,
+      /*rhs_operand_id=*/beta_operand_id,
+      /*output_operand_id=*/operation.output_operand_id,
+      mojom::ElementWiseBinary::Kind::kAdd, block));
+  return base::ok();
+}
+
 [[nodiscard]] base::expected<void, mojom::ErrorPtr>
 GraphBuilder::AddOperationForMatmul(
     uint64_t input_x_operand_id,
diff --git a/services/webnn/coreml/graph_builder.h b/services/webnn/coreml/graph_builder.h
index 58378283..9162113 100644
--- a/services/webnn/coreml/graph_builder.h
+++ b/services/webnn/coreml/graph_builder.h
@@ -254,6 +254,9 @@
   [[nodiscard]] base::expected<void, mojom::ErrorPtr> AddOperationForLeakyRelu(
       const mojom::LeakyRelu& operation,
       CoreML::Specification::MILSpec::Block& block);
+  [[nodiscard]] base::expected<void, mojom::ErrorPtr> AddOperationForLinear(
+      const mojom::Linear& operation,
+      CoreML::Specification::MILSpec::Block& block);
   [[nodiscard]] base::expected<void, mojom::ErrorPtr> AddOperationForMatmul(
       uint64_t input_x_operand_id,
       uint64_t input_y_operand_id,
diff --git a/services/webnn/coreml/graph_impl.h b/services/webnn/coreml/graph_impl.h
index 222d772..bf304db 100644
--- a/services/webnn/coreml/graph_impl.h
+++ b/services/webnn/coreml/graph_impl.h
@@ -116,6 +116,11 @@
                   id<MLFeatureProvider> output_features,
                   NSError* error);
 
+  void DispatchImpl(
+      const base::flat_map<std::string_view, WebNNBufferImpl*>& named_inputs,
+      const base::flat_map<std::string_view, WebNNBufferImpl*>& named_outputs)
+      override;
+
  private:
   SEQUENCE_CHECKER(sequence_checker_);
 
diff --git a/services/webnn/coreml/graph_impl.mm b/services/webnn/coreml/graph_impl.mm
index e37962a3..270f142 100644
--- a/services/webnn/coreml/graph_impl.mm
+++ b/services/webnn/coreml/graph_impl.mm
@@ -500,6 +500,13 @@
   }
 }
 
+void GraphImpl::DispatchImpl(
+    const base::flat_map<std::string_view, WebNNBufferImpl*>& named_inputs,
+    const base::flat_map<std::string_view, WebNNBufferImpl*>& named_outputs) {
+  // TODO(crbug.com/1472888): Implement MLBuffer for CoreML.
+  NOTIMPLEMENTED();
+}
+
 GraphImpl::CompilationContext::CompilationContext(
     ComputeResourceInfo compute_resource_info,
     std::unique_ptr<CoreMLFeatureInfoMap> input_feature_info,
diff --git a/services/webnn/dml/graph_impl.cc b/services/webnn/dml/graph_impl.cc
index a8a1699..797f4398 100644
--- a/services/webnn/dml/graph_impl.cc
+++ b/services/webnn/dml/graph_impl.cc
@@ -27,6 +27,7 @@
 #include "components/ml/webnn/graph_validation_utils.h"
 #include "mojo/public/cpp/bindings/self_owned_associated_receiver.h"
 #include "services/webnn/dml/adapter.h"
+#include "services/webnn/dml/buffer_impl.h"
 #include "services/webnn/dml/command_queue.h"
 #include "services/webnn/dml/command_recorder.h"
 #include "services/webnn/dml/context_impl.h"
@@ -36,6 +37,7 @@
 #include "services/webnn/dml/utils.h"
 #include "services/webnn/error.h"
 #include "services/webnn/public/mojom/webnn_error.mojom.h"
+#include "services/webnn/webnn_context_impl.h"
 #include "services/webnn/webnn_utils.h"
 #include "third_party/abseil-cpp/absl/types/variant.h"
 #include "third_party/fp16/src/include/fp16.h"
@@ -4405,23 +4407,11 @@
 
 GraphImpl::PersistentResource::~PersistentResource() = default;
 
-GraphImpl::ComputeResources::ComputeResources(
+GraphImpl::GraphResources::GraphResources(
     ComPtr<ID3D12DescriptorHeap> descriptor_heap,
-    AlignedByteLength<std::string> input_aligned_byte_length,
-    ComPtr<ID3D12Resource> upload_buffer,
-    ComPtr<ID3D12Resource> input_buffer,
-    AlignedByteLength<std::string> output_aligned_byte_length,
-    ComPtr<ID3D12Resource> output_buffer,
-    ComPtr<ID3D12Resource> readback_buffer,
     uint64_t temporary_buffer_byte_length,
     ComPtr<ID3D12Resource> temporary_resource)
     : descriptor_heap(std::move(descriptor_heap)),
-      input_aligned_byte_length(std::move(input_aligned_byte_length)),
-      upload_buffer(std::move(upload_buffer)),
-      input_buffer(std::move(input_buffer)),
-      output_aligned_byte_length(std::move(output_aligned_byte_length)),
-      output_buffer(std::move(output_buffer)),
-      readback_buffer(std::move(readback_buffer)),
       temporary_buffer(std::move(temporary_resource)) {
   if (temporary_buffer_byte_length > 0) {
     CHECK_NE(temporary_buffer.Get(), nullptr);
@@ -4435,6 +4425,59 @@
   }
 }
 
+GraphImpl::GraphResources::~GraphResources() = default;
+
+// static
+base::expected<std::unique_ptr<GraphImpl::GraphResources>, HRESULT>
+GraphImpl::AllocateGraphResources(Adapter* adapter,
+                                  IDMLCompiledOperator* compiled_operator) {
+  TRACE_EVENT0("gpu", "GraphImpl::AllocateGraphResources");
+  // Create the descriptor heap.
+  DML_BINDING_PROPERTIES execution_binding_properties =
+      compiled_operator->GetBindingProperties();
+  ComPtr<ID3D12DescriptorHeap> descriptor_heap;
+  RETURN_UNEXPECTED_IF_FAILED(CreateDescriptorHeap(
+      adapter->d3d12_device(),
+      execution_binding_properties.RequiredDescriptorCount,
+      L"WebNN_Descriptor_Heap_For_Execution", descriptor_heap));
+
+  // Create and bind the temporary resource if the operator execution requires.
+  ComPtr<ID3D12Resource> temporary_buffer;
+  uint64_t temporary_buffer_byte_length =
+      execution_binding_properties.TemporaryResourceSize;
+  if (temporary_buffer_byte_length > 0) {
+    RETURN_UNEXPECTED_IF_FAILED(CreateDefaultBuffer(
+        adapter->d3d12_device(), temporary_buffer_byte_length,
+        L"WebNN_Temporary_Buffer_For_Execution", temporary_buffer));
+  }
+
+  return base::WrapUnique(new GraphResources(std::move(descriptor_heap),
+                                             temporary_buffer_byte_length,
+                                             std::move(temporary_buffer)));
+}
+
+GraphImpl::ComputeResources::ComputeResources(
+    ComPtr<ID3D12DescriptorHeap> descriptor_heap,
+    AlignedByteLength<std::string> input_aligned_byte_length,
+    ComPtr<ID3D12Resource> upload_buffer,
+    ComPtr<ID3D12Resource> input_buffer,
+    AlignedByteLength<std::string> output_aligned_byte_length,
+    ComPtr<ID3D12Resource> output_buffer,
+    ComPtr<ID3D12Resource> readback_buffer,
+    uint64_t temporary_buffer_byte_length,
+    ComPtr<ID3D12Resource> temporary_resource,
+    std::unique_ptr<CommandRecorder> command_recorder)
+    : input_aligned_byte_length(std::move(input_aligned_byte_length)),
+      upload_buffer(std::move(upload_buffer)),
+      input_buffer(std::move(input_buffer)),
+      output_aligned_byte_length(std::move(output_aligned_byte_length)),
+      output_buffer(std::move(output_buffer)),
+      readback_buffer(std::move(readback_buffer)),
+      graph_resources(std::move(descriptor_heap),
+                      temporary_buffer_byte_length,
+                      std::move(temporary_resource)),
+      command_recorder(std::move(command_recorder)) {}
+
 GraphImpl::ComputeResources::~ComputeResources() = default;
 
 // static
@@ -4541,25 +4584,33 @@
         L"WebNN_Temporary_Buffer_For_Execution", temporary_buffer));
   }
 
+  // Create a command recorder which may be re-used between compute() calls.
+  std::unique_ptr<CommandRecorder> command_recorder =
+      CommandRecorder::Create(adapter->command_queue(), adapter->dml_device());
+  if (!command_recorder) {
+    DLOG(ERROR) << "Failed to create a command recorder.";
+    return base::unexpected(E_FAIL);
+  }
+
   return base::WrapUnique(new ComputeResources(
       std::move(descriptor_heap),
       std::move(aligned_byte_length_of_inputs.value()),
       std::move(upload_buffer), std::move(input_buffer),
       std::move(aligned_byte_length_of_outputs.value()),
       std::move(output_buffer), std::move(readback_buffer),
-      temporary_buffer_byte_length, std::move(temporary_buffer)));
+      temporary_buffer_byte_length, std::move(temporary_buffer),
+      std::move(command_recorder)));
 }
 
 // static
 HRESULT GraphImpl::RecordGraphExecution(
     Adapter* adapter,
     IDMLCompiledOperator* compiled_operator,
-    CommandRecorder* command_recorder,
     const ComputeResources* compute_resources,
     const PersistentResource* persistent_resource,
     const GraphBufferBindingInfo& graph_buffer_binding_info) {
   // Open the command recorder for recording the graph execution commands.
-  RETURN_IF_FAILED(command_recorder->Open());
+  RETURN_IF_FAILED(compute_resources->command_recorder->Open());
 
   // Create the input buffer bindings for the graph execution.
   std::map<std::string, DML_BUFFER_BINDING>
@@ -4593,8 +4644,8 @@
   if (compute_resources->input_aligned_byte_length.total_byte_length > 0 &&
       !adapter->IsUMA()) {
     UploadBufferWithBarrier(
-        command_recorder, compute_resources->input_buffer,
-        compute_resources->upload_buffer,
+        compute_resources->command_recorder.get(),
+        compute_resources->input_buffer, compute_resources->upload_buffer,
         compute_resources->input_aligned_byte_length.total_byte_length);
   }
 
@@ -4631,31 +4682,32 @@
   }
 
   // Execute the graph with input, output and persistent buffer bindings.
-  RETURN_IF_FAILED(command_recorder->ExecuteOperator(
-      compiled_operator, compute_resources->descriptor_heap,
+  RETURN_IF_FAILED(compute_resources->command_recorder->ExecuteOperator(
+      compiled_operator, compute_resources->graph_resources.descriptor_heap,
       input_buffer_binding_desc, output_buffer_binding_desc,
       persistent_buffer_binding_desc,
-      compute_resources->temporary_buffer_binding_desc));
+      compute_resources->graph_resources.temporary_buffer_binding_desc));
 
   if (!adapter->IsUMA()) {
     ReadbackBufferWithBarrier(
-        command_recorder, compute_resources->readback_buffer,
-        compute_resources->output_buffer,
+        compute_resources->command_recorder.get(),
+        compute_resources->readback_buffer, compute_resources->output_buffer,
         compute_resources->output_aligned_byte_length.total_byte_length);
   }
 
-  RETURN_IF_FAILED(command_recorder->Close());
+  RETURN_IF_FAILED(compute_resources->command_recorder->Close());
   return S_OK;
 }
 
 GraphImpl::GraphImpl(scoped_refptr<Adapter> adapter,
+                     ContextImpl* context,
                      std::unique_ptr<CommandRecorder> command_recorder,
                      std::unique_ptr<PersistentResource> persistent_resource,
                      ComPtr<IDMLCompiledOperator> compiled_operator,
                      ComputeResourceInfo compute_resource_info,
                      GraphBufferBindingInfo graph_buffer_binding_info,
                      std::unique_ptr<ComputeResources> compute_resources)
-    : WebNNGraphImpl(std::move(compute_resource_info)),
+    : WebNNGraphImpl(context, std::move(compute_resource_info)),
       persistent_resource_(std::move(persistent_resource)),
       adapter_(std::move(adapter)),
       command_recorder_(std::move(command_recorder)),
@@ -4900,8 +4952,7 @@
   CHECK(compute_resources);
 
   hr = RecordGraphExecution(adapter.get(), compiled_operator.Get(),
-                            command_recorder.get(), compute_resources.get(),
-                            persistent_resource.get(),
+                            compute_resources.get(), persistent_resource.get(),
                             graph_buffer_binding_info);
   if (FAILED(hr)) {
     HandleGraphCreationFailure(
@@ -4924,7 +4975,7 @@
   context->OnWebNNGraphImplCreated(
       blink_remote.InitWithNewEndpointAndPassReceiver(),
       base::WrapUnique(new GraphImpl(
-          std::move(adapter), std::move(command_recorder),
+          std::move(adapter), context.get(), std::move(command_recorder),
           std::move(persistent_resource), std::move(compiled_operator),
           std::move(compute_resource_info),
           std::move(graph_buffer_binding_info), std::move(compute_resources))));
@@ -5360,7 +5411,7 @@
     const std::string& error_message,
     mojom::WebNNGraph::ComputeCallback callback) {
   DLOG(ERROR) << error_message;
-  command_recorder_.reset();
+  compute_resources_.reset();
   std::move(callback).Run(ComputeResult::NewError(
       CreateError(mojom::Error::Code::kUnknownError, error_message)));
 }
@@ -5370,7 +5421,7 @@
     HRESULT hr,
     mojom::WebNNGraph::ComputeCallback callback) {
   DLOG(ERROR) << error_message << " " << logging::SystemErrorCodeToString(hr);
-  command_recorder_.reset();
+  compute_resources_.reset();
   if (hr == E_OUTOFMEMORY) {
     DLOG(ERROR) << "No enough memory resources are available.";
     std::move(callback).Run(ComputeResult::NewError(CreateError(
@@ -5382,6 +5433,13 @@
   }
 }
 
+// TODO(crbug.com/41492165): generate error using context.
+void GraphImpl::HandleDispatchFailure(std::string_view error_message,
+                                      HRESULT hr) {
+  DLOG(ERROR) << error_message << " " << logging::SystemErrorCodeToString(hr);
+  command_recorder_.reset();
+}
+
 void GraphImpl::ComputeImpl(
     base::flat_map<std::string, mojo_base::BigBuffer> named_inputs,
     mojom::WebNNGraph::ComputeCallback callback) {
@@ -5389,27 +5447,10 @@
 
   // It indicates whether we need to record commands and bind resources again
   // for the graph execution by calling `RecordGraphExecution` method. If either
-  // the `compute_resources_` or `command_recorder_` is not available during the
-  // graph execution, it must be set to true.
+  // the `compute_resources_` is not available during the graph execution, it
+  // must be set to true.
   bool is_command_recording_needed = false;
 
-  // Recreate the command recorder if it has been released by last failed
-  // computation or it is unavailable due to still being occupied by last
-  // computation.
-  if (!command_recorder_) {
-    command_recorder_ = CommandRecorder::Create(adapter_->command_queue(),
-                                                adapter_->dml_device());
-    if (!command_recorder_) {
-      HandleComputationFailure("Failed to create the command recorder.",
-                               std::move(callback));
-      return;
-    }
-    is_command_recording_needed = true;
-  }
-
-  std::unique_ptr<CommandRecorder> command_recorder =
-      std::move(command_recorder_);
-
   // Use the existing compute resource if it is available, otherwise allocate
   // a new one.
   std::unique_ptr<ComputeResources> compute_resources =
@@ -5433,10 +5474,9 @@
   HRESULT hr = S_OK;
 
   if (is_command_recording_needed) {
-    hr = RecordGraphExecution(adapter_.get(), compiled_operator_.Get(),
-                              command_recorder.get(), compute_resources.get(),
-                              persistent_resource_.get(),
-                              graph_buffer_binding_info_);
+    hr = RecordGraphExecution(
+        adapter_.get(), compiled_operator_.Get(), compute_resources.get(),
+        persistent_resource_.get(), graph_buffer_binding_info_);
     if (FAILED(hr)) {
       HandleComputationFailure(
           "Failed to record and bind resources for execution.", hr,
@@ -5463,7 +5503,7 @@
   }
 
   // Submit the command list for execution.
-  hr = command_recorder->Execute();
+  hr = compute_resources->command_recorder->Execute();
   if (FAILED(hr)) {
     HandleComputationFailure("Failed to execute the command list.", hr,
                              std::move(callback));
@@ -5472,14 +5512,12 @@
 
   adapter_->command_queue()->WaitAsync(base::BindOnce(
       &GraphImpl::OnComputationComplete, weak_factory_.GetWeakPtr(),
-      std::move(callback), std::move(compute_resources),
-      std::move(command_recorder)));
+      std::move(callback), std::move(compute_resources)));
 }
 
 void GraphImpl::OnComputationComplete(
     mojom::WebNNGraph::ComputeCallback callback,
     std::unique_ptr<ComputeResources> compute_resources,
-    std::unique_ptr<CommandRecorder> command_recorder,
     HRESULT hr) {
   TRACE_EVENT0("gpu", "dml::GraphImpl::OnComputationComplete");
   if (FAILED(hr)) {
@@ -5522,15 +5560,150 @@
     compute_resources_ = std::move(compute_resources);
   }
 
-  // Similarly, if there is an existing available command_recorder, release
-  // it. Otherwise, recycle it for the next call.
-  if (!command_recorder_) {
-    command_recorder_ = std::move(command_recorder);
-  }
-
   adapter_->command_queue()->ReleaseCompletedResources();
   std::move(callback).Run(
       ComputeResult::NewNamedOutputs(std::move(named_outputs)));
 }
 
+void GraphImpl::DispatchImpl(
+    const base::flat_map<std::string_view, WebNNBufferImpl*>& named_inputs,
+    const base::flat_map<std::string_view, WebNNBufferImpl*>& named_outputs) {
+  TRACE_EVENT0("gpu", "dml::GraphImpl::DispatchImpl");
+
+  if (!command_recorder_) {
+    command_recorder_ = CommandRecorder::Create(adapter_->command_queue(),
+                                                adapter_->dml_device());
+    if (!command_recorder_) {
+      LOG(ERROR) << "Failed to create the command recorder.";
+      return;
+    }
+  }
+
+  // Use the existing graph resource if it is available, otherwise allocate
+  // a new one.
+  // TODO(crbug.com/1472888): pre-allocate graph resources in graph
+  // initialization.
+  std::unique_ptr<GraphResources> graph_resources = std::move(graph_resources_);
+  if (!graph_resources) {
+    base::expected<std::unique_ptr<GraphResources>, HRESULT> result =
+        AllocateGraphResources(adapter_.get(), compiled_operator_.Get());
+    if (!result.has_value()) {
+      HandleDispatchFailure("Failed to allocate graph resources.",
+                            std::move(result.error()));
+      return;
+    }
+    graph_resources = std::move(result.value());
+  }
+  CHECK(graph_resources);
+
+  // TODO(crbug.com/1472888): avoid re-recording commands between dispatches.
+  HRESULT hr = command_recorder_->Open();
+  if (FAILED(hr)) {
+    HandleDispatchFailure("Failed to open the command recorder.", hr);
+    return;
+  }
+
+  // Create the MLBuffer input bindings needed for graph execution.
+  std::vector<DML_BUFFER_BINDING> graph_input_buffer_bindings(
+      graph_buffer_binding_info_.input_buffer_binding_count,
+      DML_BUFFER_BINDING{.Buffer = nullptr, .Offset = 0, .SizeInBytes = 0});
+
+  // The graph input tensors must be bound to the binding table during the
+  // graph execution.
+  std::vector<DML_BINDING_DESC> input_buffer_binding_desc(
+      graph_buffer_binding_info_.input_buffer_binding_count,
+      DML_BINDING_DESC{.Type = DML_BINDING_TYPE_NONE, .Desc = nullptr});
+
+  for (auto& [name, input_buffer] : named_inputs) {
+    BufferImpl* input_buffer_impl = static_cast<BufferImpl*>(input_buffer);
+    // Get the graph input index for the name.
+    const size_t graph_input_index =
+        graph_buffer_binding_info_.graph_input_name_to_index_map.at(
+            std::string(name));
+    graph_input_buffer_bindings[graph_input_index] =
+        DML_BUFFER_BINDING{.Buffer = input_buffer_impl->buffer(),
+                           .Offset = 0,
+                           .SizeInBytes = input_buffer_impl->size()};
+    input_buffer_binding_desc[graph_input_index] = {
+        DML_BINDING_TYPE_BUFFER,
+        &graph_input_buffer_bindings[graph_input_index]};
+  }
+
+  // TODO(crbug.com/1472888): consider pre-computing the output binding count.
+  const size_t output_buffer_binding_count =
+      graph_buffer_binding_info_.graph_output_name_to_index_map.size();
+
+  // Create the MLBuffer output bindings needed for graph execution.
+  std::vector<DML_BUFFER_BINDING> graph_output_buffer_bindings(
+      output_buffer_binding_count,
+      DML_BUFFER_BINDING{.Buffer = nullptr, .Offset = 0, .SizeInBytes = 0});
+
+  // The graph output tensors must be bound to the binding table during the
+  // graph execution.
+  std::vector<DML_BINDING_DESC> output_buffer_binding_desc(
+      output_buffer_binding_count,
+      DML_BINDING_DESC{.Type = DML_BINDING_TYPE_NONE, .Desc = nullptr});
+
+  for (auto& [name, output_buffer] : named_outputs) {
+    BufferImpl* output_buffer_impl = static_cast<BufferImpl*>(output_buffer);
+    // Get the graph output index with the name.
+    const size_t graph_output_index =
+        graph_buffer_binding_info_.graph_output_name_to_index_map.at(
+            std::string(name));
+    graph_output_buffer_bindings[graph_output_index] =
+        DML_BUFFER_BINDING{.Buffer = output_buffer_impl->buffer(),
+                           .Offset = 0,
+                           .SizeInBytes = output_buffer_impl->size()};
+    output_buffer_binding_desc[graph_output_index] = {
+        DML_BINDING_TYPE_BUFFER,
+        &graph_output_buffer_bindings[graph_output_index]};
+  }
+
+  std::optional<DML_BINDING_DESC> persistent_buffer_binding_desc;
+  if (persistent_resource_) {
+    persistent_buffer_binding_desc =
+        persistent_resource_->persistent_buffer_binding_desc;
+  }
+
+  // Execute the graph with input, output, temporary, and persistent bindings.
+  hr = command_recorder_->ExecuteOperator(
+      compiled_operator_.Get(), graph_resources->descriptor_heap,
+      input_buffer_binding_desc, output_buffer_binding_desc,
+      persistent_buffer_binding_desc,
+      graph_resources->temporary_buffer_binding_desc);
+  if (FAILED(hr)) {
+    HandleDispatchFailure("Failed to record execute operator.", hr);
+    return;
+  }
+
+  // Submit the command list for execution.
+  hr = command_recorder_->CloseAndExecute();
+  if (FAILED(hr)) {
+    HandleDispatchFailure("Failed to open the command recorder.", hr);
+    return;
+  }
+
+  // Prepare for the next dispatch.
+  adapter_->command_queue()->WaitAsync(
+      base::BindOnce(&GraphImpl::OnDispatchComplete, weak_factory_.GetWeakPtr(),
+                     std::move(graph_resources)));
+}
+
+void GraphImpl::OnDispatchComplete(
+    std::unique_ptr<GraphResources> graph_resources,
+    HRESULT hr) {
+  TRACE_EVENT0("gpu", "dml::GraphImpl::OnDispatchComplete");
+  if (FAILED(hr)) {
+    HandleDispatchFailure("Failed to wait for the dispatch to complete.", hr);
+    return;
+  }
+
+  // If there is an existing available graph resources, release the graph
+  // resources. Otherwise, recycle the graph resources for the next call.
+  if (!graph_resources_) {
+    graph_resources_ = std::move(graph_resources);
+  }
+
+  adapter_->command_queue()->ReleaseCompletedResources();
+}
 }  // namespace webnn::dml
diff --git a/services/webnn/dml/graph_impl.h b/services/webnn/dml/graph_impl.h
index 8a6a1407..dfb3141 100644
--- a/services/webnn/dml/graph_impl.h
+++ b/services/webnn/dml/graph_impl.h
@@ -106,6 +106,34 @@
     DML_BINDING_DESC persistent_buffer_binding_desc;
   };
 
+  // Contains the GPU descriptor heap and temporary buffer for graph
+  // execution. These resources should be kept alive until the GPU has completed
+  // the execution. After that, the resources could be reused for next graph
+  // execution or be released.
+  struct GraphResources {
+    GraphResources(Microsoft::WRL::ComPtr<ID3D12DescriptorHeap> descriptor_heap,
+                   uint64_t temporary_buffer_byte_length,
+                   Microsoft::WRL::ComPtr<ID3D12Resource> temporary_resource);
+    ~GraphResources();
+    GraphResources(const GraphResources&) = delete;
+    GraphResources& operator=(const GraphResources&) = delete;
+    GraphResources(GraphResources&&) = delete;
+    GraphResources& operator=(GraphResources&&) = delete;
+
+    Microsoft::WRL::ComPtr<ID3D12DescriptorHeap> descriptor_heap;
+
+    // Temporary buffers can be reused between DML dispatches. However,
+    // they cannot be used between multiple queues at a time.
+    // https://learn.microsoft.com/en-us/windows/ai/directml/dml-binding
+    Microsoft::WRL::ComPtr<ID3D12Resource> temporary_buffer;
+    std::optional<DML_BUFFER_BINDING> temporary_buffer_binding;
+    std::optional<DML_BINDING_DESC> temporary_buffer_binding_desc;
+  };
+
+  static base::expected<std::unique_ptr<GraphResources>, HRESULT>
+  AllocateGraphResources(Adapter* adapter,
+                         IDMLCompiledOperator* compiled_operator);
+
   // Contains the GPU resources for a graph execution, including the descriptor
   // heap, upload buffer, input buffer, output buffer, read-back buffer and
   // temporary buffer if the graph needs. These resources should be kept alive
@@ -121,15 +149,14 @@
         Microsoft::WRL::ComPtr<ID3D12Resource> output_buffer,
         Microsoft::WRL::ComPtr<ID3D12Resource> readback_buffer,
         uint64_t temporary_buffer_byte_length,
-        Microsoft::WRL::ComPtr<ID3D12Resource> temporary_buffer);
+        Microsoft::WRL::ComPtr<ID3D12Resource> temporary_buffer,
+        std::unique_ptr<CommandRecorder> command_recorder);
     ~ComputeResources();
     ComputeResources(const ComputeResources&) = delete;
     ComputeResources& operator=(const ComputeResources&) = delete;
     ComputeResources(ComputeResources&&) = delete;
     ComputeResources& operator=(ComputeResources&&) = delete;
 
-    Microsoft::WRL::ComPtr<ID3D12DescriptorHeap> descriptor_heap;
-
     AlignedByteLength<std::string> input_aligned_byte_length;
     Microsoft::WRL::ComPtr<ID3D12Resource> upload_buffer;
     Microsoft::WRL::ComPtr<ID3D12Resource> input_buffer;
@@ -138,9 +165,8 @@
     Microsoft::WRL::ComPtr<ID3D12Resource> output_buffer;
     Microsoft::WRL::ComPtr<ID3D12Resource> readback_buffer;
 
-    Microsoft::WRL::ComPtr<ID3D12Resource> temporary_buffer;
-    std::optional<DML_BUFFER_BINDING> temporary_buffer_binding;
-    std::optional<DML_BINDING_DESC> temporary_buffer_binding_desc;
+    GraphResources graph_resources;
+    std::unique_ptr<CommandRecorder> command_recorder;
   };
 
   static base::expected<std::unique_ptr<ComputeResources>, HRESULT>
@@ -162,12 +188,12 @@
   static HRESULT RecordGraphExecution(
       Adapter* adapter,
       IDMLCompiledOperator* compiled_operator,
-      CommandRecorder* command_recorder,
       const ComputeResources* compute_resources,
       const PersistentResource* persistent_resource,
       const GraphBufferBindingInfo& graph_buffer_binding_info);
 
   GraphImpl(scoped_refptr<Adapter> adapter,
+            ContextImpl* context,
             std::unique_ptr<CommandRecorder> command_recorder,
             std::unique_ptr<PersistentResource> persistent_resource,
             Microsoft::WRL::ComPtr<IDMLCompiledOperator> compiled_operator,
@@ -234,9 +260,13 @@
   void OnComputationComplete(
       mojom::WebNNGraph::ComputeCallback callback,
       std::unique_ptr<ComputeResources> compute_resources,
-      std::unique_ptr<CommandRecorder> command_recorder,
       HRESULT hr);
 
+  // After the dispatch is completed, recycle the graph resources for another
+  // dispatch.
+  void OnDispatchComplete(std::unique_ptr<GraphResources> graph_resources,
+                          HRESULT hr);
+
   // If GraphImpl::ComputeImpl fails, report and log an error message and
   // release the command recorder since it may haven't been closed normally by
   // CommandRecorder::CloseAndExecute.
@@ -249,6 +279,11 @@
                                 HRESULT hr,
                                 mojom::WebNNGraph::ComputeCallback callback);
 
+  // If GraphImpl::DispatchImpl fails, report and log an error message and
+  // release the command recorder since it may haven't been closed normally by
+  // CommandRecorder::CloseAndExecute.
+  void HandleDispatchFailure(std::string_view error_message, HRESULT hr);
+
   // Execute the compiled platform graph asynchronously. The `named_inputs` was
   // validated in base class so we can use them to compute directly, the result
   // of execution will be returned to renderer process with the `callback`.
@@ -256,6 +291,11 @@
       base::flat_map<std::string, mojo_base::BigBuffer> named_inputs,
       mojom::WebNNGraph::ComputeCallback callback) override;
 
+  void DispatchImpl(
+      const base::flat_map<std::string_view, WebNNBufferImpl*>& named_inputs,
+      const base::flat_map<std::string_view, WebNNBufferImpl*>& named_outputs)
+      override;
+
   // The persistent resource is allocated after the compilation work is
   // completed for the graph initialization and will be used for the following
   // graph executions. It could be nullptr which means it isn't required by the
@@ -279,7 +319,17 @@
   Microsoft::WRL::ComPtr<IDMLCompiledOperator> compiled_operator_;
   GraphBufferBindingInfo graph_buffer_binding_info_;
 
-  // The compute resource is pre-allocated after graph initialization and
+  // Compute resources are allocated upon graph execution and
+  // recycled after graph execution has completed. It avoids the resource
+  // allocation overhead for the following executions when
+  // it is available. A graph execution takes its ownership during the execution
+  // and returns the ownership once the GPU has completed the execution. If it
+  // is unavailable, e.g., being taken by previous uncompleted execution, a
+  // graph execution will allocate a new one and release it after the execution
+  // is done.
+  std::unique_ptr<ComputeResources> compute_resources_;
+
+  // Graph resources are allocated after graph initialization and
   // recycled after graph execution has completed. It avoids the resource
   // allocation overhead for the first execution and following executions when
   // it is available. A graph execution takes its ownership during the execution
@@ -287,7 +337,7 @@
   // is unavailable, e.g., being taken by previous uncompleted execution, a
   // graph execution will allocate a new one and release it after the execution
   // is done.
-  std::unique_ptr<ComputeResources> compute_resources_;
+  std::unique_ptr<GraphResources> graph_resources_;
 
   base::WeakPtrFactory<GraphImpl> weak_factory_{this};
 };
diff --git a/services/webnn/public/mojom/webnn_graph.mojom b/services/webnn/public/mojom/webnn_graph.mojom
index ce8c5520a..13b4b59 100644
--- a/services/webnn/public/mojom/webnn_graph.mojom
+++ b/services/webnn/public/mojom/webnn_graph.mojom
@@ -5,6 +5,7 @@
 module webnn.mojom;
 
 import "mojo/public/mojom/base/big_buffer.mojom";
+import "mojo/public/mojom/base/unguessable_token.mojom";
 import "services/webnn/public/mojom/webnn_error.mojom";
 
 // Represents the `MLOperand` which describes not only input and constant
@@ -1071,4 +1072,12 @@
   // copy for inference.
   Compute(map<string, mojo_base.mojom.BigBuffer> named_inputs)
       => (ComputeResult result);
+
+  // Called by the renderer process to carry out the computational workload of
+  // the compiled graph. The key of map is the name of input/output to identify
+  // the tensor in the graph, the value is the MLBuffer containing the tensor
+  // data.
+  // TODO(crbug.com/331351967): remove Compute().
+  Dispatch(map<string, mojo_base.mojom.UnguessableToken> named_inputs,
+    map<string, mojo_base.mojom.UnguessableToken> named_outputs);
 };
diff --git a/services/webnn/tflite/graph_impl.cc b/services/webnn/tflite/graph_impl.cc
index ea52603..6e3d750 100644
--- a/services/webnn/tflite/graph_impl.cc
+++ b/services/webnn/tflite/graph_impl.cc
@@ -270,4 +270,12 @@
   std::move(callback).Run(std::move(result.first));
 }
 
+void GraphImpl::DispatchImpl(
+    const base::flat_map<std::string_view, WebNNBufferImpl*>& named_inputs,
+    const base::flat_map<std::string_view, WebNNBufferImpl*>& named_outputs) {
+  // TODO(crbug.com/1472888): Implement MLBuffer for TFLite. Involve
+  // an IPC security reviewer.
+  NOTIMPLEMENTED();
+}
+
 }  // namespace webnn::tflite
diff --git a/services/webnn/tflite/graph_impl.h b/services/webnn/tflite/graph_impl.h
index ae630555..4339a00bb 100644
--- a/services/webnn/tflite/graph_impl.h
+++ b/services/webnn/tflite/graph_impl.h
@@ -55,6 +55,11 @@
 
   void OnComputeComplete(ComputeCallback callback, AsyncComputeResult result);
 
+  void DispatchImpl(
+      const base::flat_map<std::string_view, WebNNBufferImpl*>& named_inputs,
+      const base::flat_map<std::string_view, WebNNBufferImpl*>& named_outputs)
+      override;
+
   // This class is owned by the `UniqueAssociatedReceiverSet` in `ContextImpl`.
   raw_ptr<ContextImpl> context_;
 
diff --git a/services/webnn/tflite/graph_impl_cros.cc b/services/webnn/tflite/graph_impl_cros.cc
index 7fcf58b..b8c8d227 100644
--- a/services/webnn/tflite/graph_impl_cros.cc
+++ b/services/webnn/tflite/graph_impl_cros.cc
@@ -105,4 +105,12 @@
           std::move(callback)));
 }
 
+void GraphImplCrOS::DispatchImpl(
+    const base::flat_map<std::string_view, WebNNBufferImpl*>& named_inputs,
+    const base::flat_map<std::string_view, WebNNBufferImpl*>& named_outputs) {
+  // TODO(crbug.com/1472888): Implement MLBuffer for TFLite. Involve
+  // an IPC security reviewer.
+  NOTIMPLEMENTED();
+}
+
 }  // namespace webnn::tflite
diff --git a/services/webnn/tflite/graph_impl_cros.h b/services/webnn/tflite/graph_impl_cros.h
index 04a0402..cf19856 100644
--- a/services/webnn/tflite/graph_impl_cros.h
+++ b/services/webnn/tflite/graph_impl_cros.h
@@ -40,6 +40,11 @@
       base::flat_map<std::string, mojo_base::BigBuffer> named_inputs,
       mojom::WebNNGraph::ComputeCallback callback) override;
 
+  void DispatchImpl(
+      const base::flat_map<std::string_view, WebNNBufferImpl*>& named_inputs,
+      const base::flat_map<std::string_view, WebNNBufferImpl*>& named_outputs)
+      override;
+
   mojo::Remote<ml::model_loader::mojom::Model> model_remote_;
 };
 
diff --git a/services/webnn/webnn_context_impl.cc b/services/webnn/webnn_context_impl.cc
index 4c6c5ef..9bfa86ce 100644
--- a/services/webnn/webnn_context_impl.cc
+++ b/services/webnn/webnn_context_impl.cc
@@ -86,4 +86,14 @@
   graph_impls_.Add(std::move(graph_impl), std::move(receiver));
 }
 
+base::optional_ref<WebNNBufferImpl> WebNNContextImpl::GetWebNNBufferImpl(
+    const base::UnguessableToken& buffer_handle) {
+  const auto it = buffer_impls_.find(buffer_handle);
+  if (it == buffer_impls_.end()) {
+    receiver_.ReportBadMessage(kBadMessageInvalidBuffer);
+    return std::nullopt;
+  }
+  return it->get();
+}
+
 }  // namespace webnn
diff --git a/services/webnn/webnn_context_impl.h b/services/webnn/webnn_context_impl.h
index 36336b5..4e40b90 100644
--- a/services/webnn/webnn_context_impl.h
+++ b/services/webnn/webnn_context_impl.h
@@ -7,6 +7,7 @@
 
 #include "base/component_export.h"
 #include "base/containers/flat_set.h"
+#include "base/types/optional_ref.h"
 #include "mojo/public/cpp/base/big_buffer.h"
 #include "mojo/public/cpp/bindings/pending_receiver.h"
 #include "mojo/public/cpp/bindings/receiver.h"
@@ -47,6 +48,11 @@
       mojo::PendingAssociatedReceiver<mojom::WebNNGraph> receiver,
       std::unique_ptr<WebNNGraphImpl> graph_impl);
 
+  // Retrieves a `WebNNBufferImpl` instance created from this context.
+  // Emits a bad message if a buffer with the given handle does not exist.
+  base::optional_ref<WebNNBufferImpl> GetWebNNBufferImpl(
+      const base::UnguessableToken& handle);
+
  protected:
   void OnConnectionError();
 
diff --git a/services/webnn/webnn_graph_impl.cc b/services/webnn/webnn_graph_impl.cc
index 3f6466e..88c2df60 100644
--- a/services/webnn/webnn_graph_impl.cc
+++ b/services/webnn/webnn_graph_impl.cc
@@ -15,7 +15,10 @@
 #include "base/ranges/algorithm.h"
 #include "base/types/expected.h"
 #include "components/ml/webnn/graph_validation_utils.h"
+#include "services/webnn/error.h"
 #include "services/webnn/public/mojom/webnn_error.mojom.h"
+#include "services/webnn/webnn_buffer_impl.h"
+#include "services/webnn/webnn_context_impl.h"
 #include "third_party/abseil-cpp/absl/types/variant.h"
 
 #if BUILDFLAG(IS_WIN)
@@ -2114,6 +2117,45 @@
       });
 }
 
+// Return false if the named buffers for dispatch don't match the built
+// graph's expectation.
+bool ValidateWebNNBuffers(
+    const base::flat_map<std::string_view, WebNNBufferImpl*>& named_buffers,
+    const base::flat_map<std::string, size_t>& name_to_byte_length_map) {
+  return base::ranges::equal(
+      named_buffers, name_to_byte_length_map,
+      [](const auto& named_buffer, const auto& buffer_spec) {
+        const auto& [buffer_name, buffer_impl] = named_buffer;
+        const auto& [buffer_spec_name, buffer_spec_byte_length] = buffer_spec;
+        return buffer_name == buffer_spec_name &&
+               buffer_impl->size() == buffer_spec_byte_length;
+      });
+}
+
+// Return false if the same buffer was specified in inputs and outputs.
+bool ValidateWebNNBuffersUsage(
+    const base::flat_map<std::string, base::UnguessableToken>& named_inputs,
+    const base::flat_map<std::string, base::UnguessableToken>& named_outputs) {
+  // Validate that output buffers are unique.
+  std::set<base::UnguessableToken> output_buffers;
+  for (const auto& named_output : named_outputs) {
+    output_buffers.insert(named_output.second);
+  }
+
+  if (output_buffers.size() != named_outputs.size()) {
+    return false;
+  }
+
+  // Validate buffers used for input and output are unique.
+  for (const auto& named_input : named_inputs) {
+    if (output_buffers.contains(named_input.second)) {
+      return false;
+    }
+  }
+
+  return true;
+}
+
 }  // namespace
 
 WebNNGraphImpl::ComputeResourceInfo::ComputeResourceInfo(
@@ -2134,6 +2176,13 @@
 WebNNGraphImpl::WebNNGraphImpl(ComputeResourceInfo compute_resource_info)
     : compute_resource_info_(std::move(compute_resource_info)) {}
 
+WebNNGraphImpl::WebNNGraphImpl(WebNNContextImpl* context,
+                               ComputeResourceInfo compute_resource_info)
+    : compute_resource_info_(std::move(compute_resource_info)),
+      context_(context) {
+  CHECK(context_);
+}
+
 WebNNGraphImpl::~WebNNGraphImpl() = default;
 
 bool WebNNGraphImpl::ValidateGraph(const mojom::GraphInfoPtr& graph_info) {
@@ -2274,4 +2323,61 @@
   ComputeImpl(std::move(named_inputs), std::move(callback));
 }
 
+void WebNNGraphImpl::Dispatch(
+    const base::flat_map<std::string, base::UnguessableToken>& named_inputs,
+    const base::flat_map<std::string, base::UnguessableToken>& named_outputs) {
+  if (!ValidateWebNNBuffersUsage(named_inputs, named_outputs)) {
+    mojo::ReportBadMessage(kBadMessageInvalidBuffer);
+    return;
+  }
+
+  // Resolve the token of a input MLBuffer to the corresponding `WebNNBuffer`
+  // instance.
+  std::vector<std::pair<std::string_view, WebNNBufferImpl*>>
+      name_to_input_buffers;
+  name_to_input_buffers.reserve(named_inputs.size());
+  for (const auto& [name, buffer_handle] : named_inputs) {
+    base::optional_ref<WebNNBufferImpl> input_buffer =
+        context_->GetWebNNBufferImpl(buffer_handle);
+    if (!input_buffer.has_value()) {
+      return;
+    }
+    name_to_input_buffers.emplace_back(name, input_buffer.as_ptr());
+  }
+  base::flat_map<std::string_view, WebNNBufferImpl*> name_to_input_buffer_map(
+      std::move(name_to_input_buffers));
+  if (!ValidateWebNNBuffers(
+          name_to_input_buffer_map,
+          compute_resource_info_.input_name_to_byte_length_map)) {
+    mojo::ReportBadMessage(kBadMessageInvalidBuffer);
+    return;
+  }
+
+  // Resolve the token of a output MLBuffer to the corresponding `WebNNBuffer`
+  // instance.
+  std::vector<std::pair<std::string_view, WebNNBufferImpl*>>
+      name_to_output_buffers;
+  name_to_output_buffers.reserve(named_outputs.size());
+  for (const auto& [name, buffer_handle] : named_outputs) {
+    base::optional_ref<WebNNBufferImpl> output_buffer =
+        context_->GetWebNNBufferImpl(buffer_handle);
+    if (!output_buffer.has_value()) {
+      return;
+    }
+    name_to_output_buffers.emplace_back(name, output_buffer.as_ptr());
+  }
+
+  base::flat_map<std::string_view, WebNNBufferImpl*> name_to_output_buffer_map(
+      std::move(name_to_output_buffers));
+  if (!ValidateWebNNBuffers(
+          name_to_output_buffer_map,
+          compute_resource_info_.output_name_to_byte_length_map)) {
+    mojo::ReportBadMessage(kBadMessageInvalidBuffer);
+    return;
+  }
+
+  // Call DispatchImpl() implemented by an `mojom::WebNNGraph` backend.
+  DispatchImpl(name_to_input_buffer_map, name_to_output_buffer_map);
+}
+
 }  // namespace webnn
diff --git a/services/webnn/webnn_graph_impl.h b/services/webnn/webnn_graph_impl.h
index 920eab89..649c7dfd 100644
--- a/services/webnn/webnn_graph_impl.h
+++ b/services/webnn/webnn_graph_impl.h
@@ -15,6 +15,9 @@
 
 namespace webnn {
 
+class WebNNBufferImpl;
+class WebNNContextImpl;
+
 class COMPONENT_EXPORT(WEBNN_SERVICE) WebNNGraphImpl
     : public mojom::WebNNGraph {
  public:
@@ -36,7 +39,15 @@
     base::flat_map<std::string, size_t> output_name_to_byte_length_map;
   };
 
+  // TODO(crbug.com/333188631): remove once no GraphImpls need to be created as
+  // self-receiver.
   explicit WebNNGraphImpl(ComputeResourceInfo compute_resource_info);
+
+  // Constructs a graph where the receiever and implementation is owned by the
+  // context upon calling WebNNContextImpl::OnWebNNGraphImplCreated.
+  WebNNGraphImpl(WebNNContextImpl* context,
+                 ComputeResourceInfo compute_resource_info);
+
   WebNNGraphImpl(const WebNNGraphImpl&) = delete;
   WebNNGraphImpl& operator=(const WebNNGraphImpl&) = delete;
   ~WebNNGraphImpl() override;
@@ -53,15 +64,30 @@
   // built graph's expected.
   ComputeResourceInfo compute_resource_info_;
 
+  // WebNNContextImpl owns this object.
+  const raw_ptr<WebNNContextImpl> context_;
+
   // mojom::WebNNGraph
   void Compute(base::flat_map<std::string, mojo_base::BigBuffer> named_inputs,
                mojom::WebNNGraph::ComputeCallback callback) override;
 
+  void Dispatch(
+      const base::flat_map<std::string, base::UnguessableToken>& named_inputs,
+      const base::flat_map<std::string, base::UnguessableToken>& named_outputs)
+      override;
+
   // An WebNNGraph backend should implement this method to execute the compiled
   // platform graph asynchronously.
   virtual void ComputeImpl(
       base::flat_map<std::string, mojo_base::BigBuffer> named_inputs,
       mojom::WebNNGraph::ComputeCallback callback) = 0;
+
+  // Execute the compiled platform graph. The `named_inputs` and `named_outputs`
+  // were validated in base class.
+  virtual void DispatchImpl(
+      const base::flat_map<std::string_view, WebNNBufferImpl*>& named_inputs,
+      const base::flat_map<std::string_view, WebNNBufferImpl*>&
+          named_outputs) = 0;
 };
 
 }  // namespace webnn
diff --git a/services/webnn/webnn_graph_impl_unittest.cc b/services/webnn/webnn_graph_impl_unittest.cc
index 2362f6e..bc2a308 100644
--- a/services/webnn/webnn_graph_impl_unittest.cc
+++ b/services/webnn/webnn_graph_impl_unittest.cc
@@ -20,8 +20,10 @@
 #include "mojo/public/cpp/bindings/remote.h"
 #include "mojo/public/cpp/bindings/self_owned_associated_receiver.h"
 #include "mojo/public/cpp/system/functions.h"
+#include "services/webnn/error.h"
 #include "services/webnn/public/mojom/webnn_context_provider.mojom.h"
 #include "services/webnn/public/mojom/webnn_graph.mojom.h"
+#include "services/webnn/webnn_buffer_impl.h"
 #include "services/webnn/webnn_context_impl.h"
 #include "services/webnn/webnn_context_provider_impl.h"
 #include "services/webnn/webnn_test_utils.h"
@@ -36,18 +38,21 @@
 // computing graph message.
 class FakeWebNNGraphImpl final : public WebNNGraphImpl {
  public:
-  explicit FakeWebNNGraphImpl(ComputeResourceInfo compute_resource_info)
-      : WebNNGraphImpl(std::move(compute_resource_info)) {}
+  explicit FakeWebNNGraphImpl(WebNNContextImpl* context,
+                              ComputeResourceInfo compute_resource_info)
+      : WebNNGraphImpl(context, std::move(compute_resource_info)) {}
   ~FakeWebNNGraphImpl() override = default;
 
   static void CreateAndBuild(
+      WebNNContextImpl* context,
       const mojom::GraphInfoPtr& graph_info,
       mojom::WebNNContext::CreateGraphCallback callback) {
     mojo::PendingAssociatedRemote<mojom::WebNNGraph> blink_remote;
     // The receiver bound to FakeWebNNGraphImpl.
-    mojo::MakeSelfOwnedAssociatedReceiver<mojom::WebNNGraph>(
-        std::make_unique<FakeWebNNGraphImpl>(ComputeResourceInfo(graph_info)),
-        blink_remote.InitWithNewEndpointAndPassReceiver());
+    context->OnWebNNGraphImplCreated(
+        blink_remote.InitWithNewEndpointAndPassReceiver(),
+        std::make_unique<FakeWebNNGraphImpl>(context,
+                                             ComputeResourceInfo(graph_info)));
     std::move(callback).Run(
         mojom::CreateGraphResult::NewGraphRemote(std::move(blink_remote)));
   }
@@ -61,6 +66,32 @@
     std::move(callback).Run(
         mojom::ComputeResult::NewNamedOutputs(std::move(named_outputs)));
   }
+
+  // Return nothing for testing the validation of inputs and outputs in
+  // `WebNNGraphImpl::Dispatch()` function.
+  void DispatchImpl(
+      const base::flat_map<std::string_view, WebNNBufferImpl*>& named_inputs,
+      const base::flat_map<std::string_view, WebNNBufferImpl*>& named_outputs)
+      override {}
+};
+
+// A fake WebNNBuffer Mojo interface implementation that binds a pipe for
+// buffer creation message.
+class FakeWebNNBufferImpl final : public WebNNBufferImpl {
+ public:
+  explicit FakeWebNNBufferImpl(
+      mojo::PendingAssociatedReceiver<mojom::WebNNBuffer> receiver,
+      WebNNContextImpl* context,
+      uint64_t size,
+      const base::UnguessableToken& buffer_handle)
+      : WebNNBufferImpl(std::move(receiver), context, size, buffer_handle) {}
+  ~FakeWebNNBufferImpl() override = default;
+
+ private:
+  // Read/write nothing for testing the validation of inputs and outputs in
+  // `WebNNGraphImpl::Dispatch()` function.
+  void ReadBufferImpl(ReadBufferCallback callback) override {}
+  void WriteBufferImpl(mojo_base::BigBuffer src_buffer) override {}
 };
 
 // A fake WebNNContext Mojo interface implementation that binds a pipe for
@@ -76,7 +107,7 @@
   void CreateGraphImpl(
       mojom::GraphInfoPtr graph_info,
       mojom::WebNNContext::CreateGraphCallback callback) override {
-    FakeWebNNGraphImpl::CreateAndBuild(std::move(graph_info),
+    FakeWebNNGraphImpl::CreateAndBuild(this, std::move(graph_info),
                                        std::move(callback));
   }
 
@@ -84,9 +115,8 @@
       mojo::PendingAssociatedReceiver<mojom::WebNNBuffer> receiver,
       mojom::BufferInfoPtr buffer_info,
       const base::UnguessableToken& buffer_handle) override {
-    // TODO(crbug.com/40278771): Implement MLBuffer support for graphs.
-    NOTIMPLEMENTED();
-    return {};
+    return std::make_unique<FakeWebNNBufferImpl>(
+        std::move(receiver), this, buffer_info->size, buffer_handle);
   }
 };
 
@@ -153,6 +183,94 @@
   return valid;
 }
 
+struct WebNNBufferInfo {
+  base::UnguessableToken buffer_handle;
+  uint64_t size;
+  bool create_buffer;
+};
+
+WebNNBufferInfo CreateWebNNBufferInfo(uint64_t size,
+                                      bool create_buffer = true) {
+  return {base::UnguessableToken::Create(), size, create_buffer};
+}
+
+// Converts inputs and outputs to MLBuffer then dispatches them.
+bool ValidateDispatch(mojom::GraphInfoPtr graph_info,
+                      base::flat_map<std::string, WebNNBufferInfo> inputs,
+                      base::flat_map<std::string, WebNNBufferInfo> outputs) {
+  // Creates WebNN Context mojo interface with the provider.
+  mojo::Remote<mojom::WebNNContextProvider> provider_remote;
+  WebNNContextProviderImpl::CreateForTesting(
+      provider_remote.BindNewPipeAndPassReceiver());
+
+  base::test::TestFuture<mojom::CreateContextResultPtr> create_context_future;
+  provider_remote->CreateWebNNContext(mojom::CreateContextOptions::New(),
+                                      create_context_future.GetCallback());
+  mojom::CreateContextResultPtr create_context_result =
+      create_context_future.Take();
+  mojo::Remote<mojom::WebNNContext> webnn_context;
+  webnn_context.Bind(std::move(create_context_result->get_context_remote()));
+
+  // Creates WebNN Graph mojo interface with the graph information which is
+  // validated before compiling.
+  base::test::TestFuture<mojom::CreateGraphResultPtr> create_graph_future;
+  webnn_context->CreateGraph(std::move(graph_info),
+                             create_graph_future.GetCallback());
+  mojom::CreateGraphResultPtr create_graph_result = create_graph_future.Take();
+  mojo::AssociatedRemote<mojom::WebNNGraph> webnn_graph;
+  webnn_graph.Bind(std::move(create_graph_result->get_graph_remote()));
+
+  // Validate the inputs in the `Dispatch` function.
+  bool valid = true;
+  // Set up the error handler for bad mojo messages.
+  mojo::SetDefaultProcessErrorHandler(
+      base::BindLambdaForTesting([&](const std::string& error_message) {
+        EXPECT_EQ(error_message, kBadMessageInvalidBuffer);
+        valid = false;
+      }));
+
+  // Create buffers for the inputs.
+  std::vector<mojo::AssociatedRemote<mojom::WebNNBuffer>> input_buffers(
+      inputs.size());
+  base::flat_map<std::string, base::UnguessableToken> dispatch_inputs;
+  for (const auto& [name, buffer_info] : inputs) {
+    if (buffer_info.create_buffer) {
+      mojo::AssociatedRemote<mojom::WebNNBuffer> webnn_buffer;
+      webnn_context->CreateBuffer(webnn_buffer.BindNewEndpointAndPassReceiver(),
+                                  mojom::BufferInfo::New(buffer_info.size),
+                                  buffer_info.buffer_handle);
+      input_buffers.push_back(std::move(webnn_buffer));
+    }
+    dispatch_inputs.emplace(name, buffer_info.buffer_handle);
+  }
+
+  // Create buffers for the outputs.
+  std::vector<mojo::AssociatedRemote<mojom::WebNNBuffer>> output_buffers(
+      outputs.size());
+  base::flat_map<std::string, base::UnguessableToken> dispatch_outputs;
+  for (const auto& [name, buffer_info] : outputs) {
+    if (buffer_info.create_buffer) {
+      mojo::AssociatedRemote<mojom::WebNNBuffer> webnn_buffer;
+      webnn_context->CreateBuffer(webnn_buffer.BindNewEndpointAndPassReceiver(),
+                                  mojom::BufferInfo::New(buffer_info.size),
+                                  buffer_info.buffer_handle);
+      output_buffers.push_back(std::move(webnn_buffer));
+    }
+    dispatch_outputs.emplace(name, buffer_info.buffer_handle);
+  }
+
+  // Ensure CreateBuffer messages have a chance to finish before calling
+  // Dispatch().
+  webnn_context.FlushForTesting();
+  webnn_graph->Dispatch(dispatch_inputs, dispatch_outputs);
+
+  // Ensure Dispatch message has a chance to finish before removing the error
+  // handler.
+  webnn_graph.FlushForTesting();
+  mojo::SetDefaultProcessErrorHandler(base::NullCallback());
+  return valid;
+}
+
 mojom::Operand::DataType kAllOperandDataTypes[] = {
     mojom::Operand::DataType::kFloat32, mojom::Operand::DataType::kFloat16,
     mojom::Operand::DataType::kInt32,   mojom::Operand::DataType::kInt8,
@@ -6834,6 +6952,192 @@
   }
 }
 
+TEST_F(WebNNGraphImplTest, ValidateDispatchTest) {
+  const std::vector<uint32_t> dimensions = {3, 5};
+  // Build the graph with mojo type.
+  GraphInfoBuilder builder;
+  const uint64_t lhs_operand_id =
+      builder.BuildInput("lhs", dimensions, mojom::Operand::DataType::kUint8);
+  const uint64_t rhs_operand_id =
+      builder.BuildInput("rhs", dimensions, mojom::Operand::DataType::kUint8);
+  const uint64_t output_1_operand_id = builder.BuildOutput(
+      "output1", dimensions, mojom::Operand::DataType::kUint8);
+  builder.BuildElementWiseBinary(mojom::ElementWiseBinary::Kind::kAdd,
+                                 lhs_operand_id, rhs_operand_id,
+                                 output_1_operand_id);
+  const uint64_t output_2_operand_id = builder.BuildOutput(
+      "output2", dimensions, mojom::Operand::DataType::kUint8);
+  builder.BuildElementWiseBinary(mojom::ElementWiseBinary::Kind::kAdd,
+                                 lhs_operand_id, rhs_operand_id,
+                                 output_2_operand_id);
+  EXPECT_TRUE(WebNNGraphImpl::ValidateGraph(builder.GetGraphInfo()));
+
+  const size_t byte_length =
+      ValidateAndCalculateByteLength(sizeof(uint8_t), dimensions).value();
+
+  {
+    // Validate the inputs match the expected.
+    base::flat_map<std::string, WebNNBufferInfo> inputs;
+    inputs["lhs"] = CreateWebNNBufferInfo(byte_length);
+    inputs["rhs"] = CreateWebNNBufferInfo(byte_length);
+    base::flat_map<std::string, WebNNBufferInfo> outputs;
+    outputs["output1"] = CreateWebNNBufferInfo(byte_length);
+    outputs["output2"] = CreateWebNNBufferInfo(byte_length);
+    EXPECT_TRUE(ValidateDispatch(builder.CloneGraphInfo(), std::move(inputs),
+                                 std::move(outputs)));
+  }
+  {
+    // Test the invalid inputs for invalid input size.
+    base::flat_map<std::string, WebNNBufferInfo> inputs;
+    inputs["lhs"] = CreateWebNNBufferInfo(byte_length);
+    base::flat_map<std::string, WebNNBufferInfo> outputs;
+    outputs["output1"] = CreateWebNNBufferInfo(byte_length);
+    outputs["output2"] = CreateWebNNBufferInfo(byte_length);
+    EXPECT_FALSE(ValidateDispatch(builder.CloneGraphInfo(), std::move(inputs),
+                                  std::move(outputs)));
+  }
+  {
+    // Test the invalid outputs for invalid output size.
+    base::flat_map<std::string, WebNNBufferInfo> inputs;
+    inputs["lhs"] = CreateWebNNBufferInfo(byte_length);
+    inputs["rhs"] = CreateWebNNBufferInfo(byte_length);
+    base::flat_map<std::string, WebNNBufferInfo> outputs;
+    outputs["output1"] = CreateWebNNBufferInfo(byte_length);
+    outputs["output2"] = CreateWebNNBufferInfo(byte_length);
+    outputs["a_different_output_name"] = CreateWebNNBufferInfo(byte_length);
+    EXPECT_FALSE(ValidateDispatch(builder.CloneGraphInfo(), std::move(inputs),
+                                  std::move(outputs)));
+  }
+  {
+    // Test the invalid inputs for invalid input name.
+    base::flat_map<std::string, WebNNBufferInfo> inputs;
+    inputs["a_different_input_name"] = {base::UnguessableToken::Create(),
+                                        byte_length};
+    inputs["rhs"] = CreateWebNNBufferInfo(byte_length);
+    base::flat_map<std::string, WebNNBufferInfo> outputs;
+    outputs["output1"] = CreateWebNNBufferInfo(byte_length);
+    outputs["output2"] = CreateWebNNBufferInfo(byte_length);
+    EXPECT_FALSE(ValidateDispatch(builder.CloneGraphInfo(), std::move(inputs),
+                                  std::move(outputs)));
+  }
+  {
+    // Test the invalid outputs for invalid input name.
+    base::flat_map<std::string, WebNNBufferInfo> inputs;
+    inputs["lhs"] = CreateWebNNBufferInfo(byte_length);
+    inputs["rhs"] = CreateWebNNBufferInfo(byte_length);
+    base::flat_map<std::string, WebNNBufferInfo> outputs;
+    outputs["a_different_output_name"] = CreateWebNNBufferInfo(byte_length);
+    outputs["output2"] = CreateWebNNBufferInfo(byte_length);
+    EXPECT_FALSE(ValidateDispatch(builder.CloneGraphInfo(), std::move(inputs),
+                                  std::move(outputs)));
+  }
+  {
+    // Test the invalid inputs for invalid first input byte length.
+    base::flat_map<std::string, WebNNBufferInfo> inputs;
+    inputs["lhs"] = CreateWebNNBufferInfo(/*size=*/20);
+    inputs["rhs"] = CreateWebNNBufferInfo(byte_length);
+    base::flat_map<std::string, WebNNBufferInfo> outputs;
+    outputs["output1"] = CreateWebNNBufferInfo(byte_length);
+    outputs["output2"] = CreateWebNNBufferInfo(byte_length);
+    EXPECT_FALSE(ValidateDispatch(builder.CloneGraphInfo(), std::move(inputs),
+                                  std::move(outputs)));
+  }
+  {
+    // Test the invalid outputs for invalid first output byte length.
+    base::flat_map<std::string, WebNNBufferInfo> inputs;
+    inputs["lhs"] = CreateWebNNBufferInfo(byte_length);
+    inputs["rhs"] = CreateWebNNBufferInfo(byte_length);
+    base::flat_map<std::string, WebNNBufferInfo> outputs;
+    outputs["output1"] = CreateWebNNBufferInfo(/*size=*/20);
+    outputs["output2"] = CreateWebNNBufferInfo(byte_length);
+    EXPECT_FALSE(ValidateDispatch(builder.CloneGraphInfo(), std::move(inputs),
+                                  std::move(outputs)));
+  }
+  {
+    // Test the invalid inputs for invalid second input byte length.
+    base::flat_map<std::string, WebNNBufferInfo> inputs;
+    inputs["lhs"] = CreateWebNNBufferInfo(byte_length);
+    inputs["rhs"] = CreateWebNNBufferInfo(/*size=*/20);
+    base::flat_map<std::string, WebNNBufferInfo> outputs;
+    outputs["output1"] = CreateWebNNBufferInfo(byte_length);
+    outputs["output2"] = CreateWebNNBufferInfo(byte_length);
+    EXPECT_FALSE(ValidateDispatch(builder.CloneGraphInfo(), std::move(inputs),
+                                  std::move(outputs)));
+  }
+  {
+    // Test the invalid outputs for invalid second output byte length.
+    base::flat_map<std::string, WebNNBufferInfo> inputs;
+    inputs["lhs"] = CreateWebNNBufferInfo(byte_length);
+    inputs["rhs"] = CreateWebNNBufferInfo(byte_length);
+    base::flat_map<std::string, WebNNBufferInfo> outputs;
+    outputs["output1"] = CreateWebNNBufferInfo(byte_length);
+    outputs["output2"] = CreateWebNNBufferInfo(/*size=*/20);
+    EXPECT_FALSE(ValidateDispatch(builder.CloneGraphInfo(), std::move(inputs),
+                                  std::move(outputs)));
+  }
+  {
+    // Test the inputs using the same buffer more than once.
+    base::flat_map<std::string, WebNNBufferInfo> inputs;
+    const WebNNBufferInfo& input_buffer = CreateWebNNBufferInfo(byte_length);
+    inputs["lhs"] = input_buffer;
+    inputs["rhs"] = {input_buffer.buffer_handle, byte_length,
+                     /*create_buffer=*/false};
+    base::flat_map<std::string, WebNNBufferInfo> outputs;
+    outputs["output1"] = CreateWebNNBufferInfo(byte_length);
+    outputs["output2"] = CreateWebNNBufferInfo(byte_length);
+    EXPECT_TRUE(ValidateDispatch(builder.CloneGraphInfo(), std::move(inputs),
+                                 std::move(outputs)));
+  }
+  {
+    // Test the invalid outputs when using the same buffer more than once.
+    base::flat_map<std::string, WebNNBufferInfo> inputs;
+    inputs["lhs"] = CreateWebNNBufferInfo(byte_length);
+    inputs["rhs"] = CreateWebNNBufferInfo(byte_length);
+    base::flat_map<std::string, WebNNBufferInfo> outputs;
+    const WebNNBufferInfo& output_buffer = CreateWebNNBufferInfo(byte_length);
+    outputs["output1"] = output_buffer;
+    outputs["output2"] = {output_buffer.buffer_handle, byte_length,
+                          /*create_buffer=*/false};
+    EXPECT_FALSE(ValidateDispatch(builder.CloneGraphInfo(), std::move(inputs),
+                                  std::move(outputs)));
+  }
+  {
+    // Test the inputs and outputs are invalid when using the same buffer.
+    const WebNNBufferInfo& input_and_output_buffer = {
+        base::UnguessableToken::Create(), byte_length};
+    base::flat_map<std::string, WebNNBufferInfo> inputs;
+    inputs["lhs"] = input_and_output_buffer;
+    inputs["rhs"] = CreateWebNNBufferInfo(byte_length);
+    base::flat_map<std::string, WebNNBufferInfo> outputs;
+    outputs["output1"] = input_and_output_buffer;
+    outputs["output2"] = CreateWebNNBufferInfo(byte_length);
+    EXPECT_FALSE(ValidateDispatch(builder.CloneGraphInfo(), std::move(inputs),
+                                  std::move(outputs)));
+  }
+  {
+    // Test the inputs are invalid when using a invalid buffer.
+    base::flat_map<std::string, WebNNBufferInfo> inputs;
+    inputs["lhs"] = CreateWebNNBufferInfo(byte_length, false);
+    inputs["rhs"] = CreateWebNNBufferInfo(byte_length);
+    base::flat_map<std::string, WebNNBufferInfo> outputs;
+    outputs["output1"] = CreateWebNNBufferInfo(byte_length);
+    outputs["output2"] = CreateWebNNBufferInfo(byte_length);
+    EXPECT_FALSE(ValidateDispatch(builder.CloneGraphInfo(), std::move(inputs),
+                                  std::move(outputs)));
+  }
+  {
+    // Test the outputs are invalid when using a invalid buffer.
+    base::flat_map<std::string, WebNNBufferInfo> inputs;
+    inputs["lhs"] = CreateWebNNBufferInfo(byte_length);
+    inputs["rhs"] = CreateWebNNBufferInfo(byte_length);
+    base::flat_map<std::string, WebNNBufferInfo> outputs;
+    outputs["output1"] = CreateWebNNBufferInfo(byte_length);
+    outputs["output2"] = CreateWebNNBufferInfo(byte_length, false);
+    EXPECT_FALSE(ValidateDispatch(builder.CloneGraphInfo(), std::move(inputs),
+                                  std::move(outputs)));
+  }
+}
+
 struct ConstantOperandTester {
   std::vector<uint8_t> values;
   bool expected;
diff --git a/testing/buildbot/chromium.android.json b/testing/buildbot/chromium.android.json
index 6429eabc..9e22c51 100644
--- a/testing/buildbot/chromium.android.json
+++ b/testing/buildbot/chromium.android.json
@@ -33,6 +33,10 @@
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
         "name": "cast_base_junit_tests",
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
         "swarming": {
           "dimensions": {
             "cores": "8",
@@ -50,6 +54,10 @@
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
         "name": "cast_shell_junit_tests",
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
         "swarming": {
           "dimensions": {
             "cores": "8",
@@ -2902,6 +2910,7 @@
       {
         "args": [
           "--avd-config=../../tools/android/avd/proto/android_30_google_apis_x86.textpb",
+          "--test-launcher-filter-file=../../testing/buildbot/filters/android.emulator_11.viz_unittests.filter",
           "--gs-results-bucket=chromium-result-details",
           "--recover-devices"
         ],
@@ -13375,6 +13384,7 @@
       {
         "args": [
           "--avd-config=../../tools/android/avd/proto/android_34_google_apis_x64.textpb",
+          "--test-launcher-filter-file=../../testing/buildbot/filters/android.emulator_14.base_unittests.filter",
           "--gs-results-bucket=chromium-result-details",
           "--recover-devices"
         ],
diff --git a/testing/buildbot/chromium.chromiumos.json b/testing/buildbot/chromium.chromiumos.json
index 6b9cd45..40b114b3 100644
--- a/testing/buildbot/chromium.chromiumos.json
+++ b/testing/buildbot/chromium.chromiumos.json
@@ -3430,7 +3430,7 @@
             "os": "Ubuntu-22.04"
           },
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
-          "shards": 107
+          "shards": 96
         },
         "test": "browser_tests",
         "test_id_prefix": "ninja://chrome/test:browser_tests/"
@@ -5449,7 +5449,7 @@
             "os": "Ubuntu-22.04"
           },
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
-          "shards": 7
+          "shards": 6
         },
         "test": "interactive_ui_tests",
         "test_id_prefix": "ninja://chrome/test:interactive_ui_tests/"
@@ -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_v126.0.6462.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v126.0.6463.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 126.0.6462.0",
+        "description": "Run with ash-chrome version 126.0.6463.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_v126.0.6462.0",
-              "revision": "version:126.0.6462.0"
+              "location": "lacros_version_skew_tests_v126.0.6463.0",
+              "revision": "version:126.0.6463.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_v126.0.6462.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v126.0.6463.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 126.0.6462.0",
+        "description": "Run with ash-chrome version 126.0.6463.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_v126.0.6462.0",
-              "revision": "version:126.0.6462.0"
+              "location": "lacros_version_skew_tests_v126.0.6463.0",
+              "revision": "version:126.0.6463.0"
             }
           ],
           "dimensions": {
diff --git a/testing/buildbot/chromium.coverage.json b/testing/buildbot/chromium.coverage.json
index cdc75d24..58c0824 100644
--- a/testing/buildbot/chromium.coverage.json
+++ b/testing/buildbot/chromium.coverage.json
@@ -19663,9 +19663,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_v126.0.6462.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v126.0.6463.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 126.0.6462.0",
+        "description": "Run with ash-chrome version 126.0.6463.0",
         "isolate_profile_data": true,
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -19675,8 +19675,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v126.0.6462.0",
-              "revision": "version:126.0.6462.0"
+              "location": "lacros_version_skew_tests_v126.0.6463.0",
+              "revision": "version:126.0.6463.0"
             }
           ],
           "dimensions": {
@@ -19819,9 +19819,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_v126.0.6462.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v126.0.6463.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 126.0.6462.0",
+        "description": "Run with ash-chrome version 126.0.6463.0",
         "isolate_profile_data": true,
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -19831,8 +19831,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v126.0.6462.0",
-              "revision": "version:126.0.6462.0"
+              "location": "lacros_version_skew_tests_v126.0.6463.0",
+              "revision": "version:126.0.6463.0"
             }
           ],
           "dimensions": {
diff --git a/testing/buildbot/chromium.fyi.json b/testing/buildbot/chromium.fyi.json
index 493262b..b8bf9776 100644
--- a/testing/buildbot/chromium.fyi.json
+++ b/testing/buildbot/chromium.fyi.json
@@ -41837,9 +41837,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_v126.0.6462.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v126.0.6463.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 126.0.6462.0",
+        "description": "Run with ash-chrome version 126.0.6463.0",
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
@@ -41848,8 +41848,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v126.0.6462.0",
-              "revision": "version:126.0.6462.0"
+              "location": "lacros_version_skew_tests_v126.0.6463.0",
+              "revision": "version:126.0.6463.0"
             }
           ],
           "dimensions": {
@@ -41987,9 +41987,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_v126.0.6462.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v126.0.6463.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 126.0.6462.0",
+        "description": "Run with ash-chrome version 126.0.6463.0",
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
@@ -41998,8 +41998,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v126.0.6462.0",
-              "revision": "version:126.0.6462.0"
+              "location": "lacros_version_skew_tests_v126.0.6463.0",
+              "revision": "version:126.0.6463.0"
             }
           ],
           "dimensions": {
@@ -43336,9 +43336,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_v126.0.6462.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v126.0.6463.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 126.0.6462.0",
+        "description": "Run with ash-chrome version 126.0.6463.0",
         "isolate_profile_data": true,
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -43348,8 +43348,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v126.0.6462.0",
-              "revision": "version:126.0.6462.0"
+              "location": "lacros_version_skew_tests_v126.0.6463.0",
+              "revision": "version:126.0.6463.0"
             }
           ],
           "dimensions": {
@@ -43492,9 +43492,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_v126.0.6462.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v126.0.6463.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 126.0.6462.0",
+        "description": "Run with ash-chrome version 126.0.6463.0",
         "isolate_profile_data": true,
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -43504,8 +43504,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v126.0.6462.0",
-              "revision": "version:126.0.6462.0"
+              "location": "lacros_version_skew_tests_v126.0.6463.0",
+              "revision": "version:126.0.6463.0"
             }
           ],
           "dimensions": {
@@ -44817,9 +44817,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_v126.0.6462.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v126.0.6463.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 126.0.6462.0",
+        "description": "Run with ash-chrome version 126.0.6463.0",
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
@@ -44828,8 +44828,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v126.0.6462.0",
-              "revision": "version:126.0.6462.0"
+              "location": "lacros_version_skew_tests_v126.0.6463.0",
+              "revision": "version:126.0.6463.0"
             }
           ],
           "dimensions": {
@@ -44967,9 +44967,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_v126.0.6462.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v126.0.6463.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 126.0.6462.0",
+        "description": "Run with ash-chrome version 126.0.6463.0",
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
@@ -44978,8 +44978,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v126.0.6462.0",
-              "revision": "version:126.0.6462.0"
+              "location": "lacros_version_skew_tests_v126.0.6463.0",
+              "revision": "version:126.0.6463.0"
             }
           ],
           "dimensions": {
diff --git a/testing/buildbot/chromium.linux.json b/testing/buildbot/chromium.linux.json
index cd7c36a5..ba46d2b 100644
--- a/testing/buildbot/chromium.linux.json
+++ b/testing/buildbot/chromium.linux.json
@@ -274,7 +274,7 @@
             "os": "Ubuntu-22.04"
           },
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
-          "shards": 29
+          "shards": 27
         },
         "test": "browser_tests",
         "test_id_prefix": "ninja://chrome/test:browser_tests/"
@@ -746,7 +746,7 @@
             "os": "Ubuntu-22.04"
           },
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
-          "shards": 7
+          "shards": 6
         },
         "test": "interactive_ui_tests",
         "test_id_prefix": "ninja://chrome/test:interactive_ui_tests/"
diff --git a/testing/buildbot/chromium.memory.json b/testing/buildbot/chromium.memory.json
index c679c5d..5d2cd20 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_v126.0.6462.0/test_ash_chrome",
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v126.0.6463.0/test_ash_chrome",
           "--test-launcher-print-test-stdio=always",
           "--combine-ash-logs-on-bots",
           "--asan-symbolize-output"
         ],
-        "description": "Run with ash-chrome version 126.0.6462.0",
+        "description": "Run with ash-chrome version 126.0.6463.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_v126.0.6462.0",
-              "revision": "version:126.0.6462.0"
+              "location": "lacros_version_skew_tests_v126.0.6463.0",
+              "revision": "version:126.0.6463.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_v126.0.6462.0/test_ash_chrome",
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v126.0.6463.0/test_ash_chrome",
           "--test-launcher-print-test-stdio=always",
           "--combine-ash-logs-on-bots",
           "--asan-symbolize-output"
         ],
-        "description": "Run with ash-chrome version 126.0.6462.0",
+        "description": "Run with ash-chrome version 126.0.6463.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_v126.0.6462.0",
-              "revision": "version:126.0.6462.0"
+              "location": "lacros_version_skew_tests_v126.0.6463.0",
+              "revision": "version:126.0.6463.0"
             }
           ],
           "dimensions": {
diff --git a/testing/buildbot/filters/BUILD.gn b/testing/buildbot/filters/BUILD.gn
index a1f78cc..127c41c 100644
--- a/testing/buildbot/filters/BUILD.gn
+++ b/testing/buildbot/filters/BUILD.gn
@@ -32,6 +32,7 @@
 
   data = [
     "//testing/buildbot/filters/android.emulator.base_unittests.filter",
+    "//testing/buildbot/filters/android.emulator_14.base_unittests.filter",
     "//testing/buildbot/filters/android.pie_tot.base_unittests.filter",
     "//testing/buildbot/filters/android.mte.base_unittests.filter",
     "//testing/buildbot/filters/fuchsia.lsan.base_unittests.filter",
@@ -425,6 +426,7 @@
   testonly = true
 
   data = [
+    "//testing/buildbot/filters/android.emulator_11.viz_unittests.filter",
     "//testing/buildbot/filters/fuchsia.debug.viz_unittests.filter",
     "//testing/buildbot/filters/fuchsia.viz_unittests.filter",
     "//testing/buildbot/filters/lacros-arm.viz_unittests.filter",
diff --git a/testing/buildbot/filters/android.emulator_11.viz_unittests.filter b/testing/buildbot/filters/android.emulator_11.viz_unittests.filter
new file mode 100644
index 0000000..34a9e18f
--- /dev/null
+++ b/testing/buildbot/filters/android.emulator_11.viz_unittests.filter
@@ -0,0 +1,4 @@
+# crbug/338436747
+-SkiaOutputSurfaceImplTest.CopyOutputBitmapSupportedColorSpace
+-SkiaOutputSurfaceImplTest.CopyOutputBitmapUnsupportedColorSpace
+-SkiaOutputSurfaceImplTest.EndPaint
diff --git a/testing/buildbot/filters/android.emulator_14.base_unittests.filter b/testing/buildbot/filters/android.emulator_14.base_unittests.filter
new file mode 100644
index 0000000..4ccffd2
--- /dev/null
+++ b/testing/buildbot/filters/android.emulator_14.base_unittests.filter
@@ -0,0 +1,11 @@
+# crbug.com/1260521
+-ModuleCacheTest.CheckAgainstProcMaps
+
+# crbug.com/1399972
+-CheckExitCodeAfterSignalHandlerDeathTest.CheckSIGSEGVNonCanonicalAddress
+
+# crbug/338426159
+-FileUtilTest.CopyFileContentsWithSendfileSeqFile
+
+# crbug/338429160
+-SystemMetricsTest.MeasureChildCpuUsage
diff --git a/testing/buildbot/filters/pixel_tests.filter b/testing/buildbot/filters/pixel_tests.filter
index bd680e1..173cc0c 100644
--- a/testing/buildbot/filters/pixel_tests.filter
+++ b/testing/buildbot/filters/pixel_tests.filter
@@ -82,6 +82,7 @@
 PasswordReuseModalWarningTest.*
 PaymentsWindowUserConsentDialogBrowserTest*
 PerformanceToolbarButtonInteractiveUiTest.*
+PerformanceInterventionInteractiveTest.InterventionToolbarButton
 PermissionRequestChipBrowserUiTest.*
 *PermissionPromptBubble*View*BrowserTest.InvokeUi_*
 PermissionPromptPreviewBrowserTest.InvokeUi_*
diff --git a/testing/buildbot/test_suite_exceptions.pyl b/testing/buildbot/test_suite_exceptions.pyl
index 6d52147..f7296c0e 100644
--- a/testing/buildbot/test_suite_exceptions.pyl
+++ b/testing/buildbot/test_suite_exceptions.pyl
@@ -249,6 +249,11 @@
           '--test-launcher-filter-file=../../testing/buildbot/filters/android.emulator.base_unittests.filter',
         ],
       },
+      'android-14-x64-rel': {
+        'args': [
+          '--test-launcher-filter-file=../../testing/buildbot/filters/android.emulator_14.base_unittests.filter',
+        ],
+      },
       'android-mte-arm64-rel': {
         'args': [
           '--test-launcher-filter-file=../../testing/buildbot/filters/android.mte.base_unittests.filter',
@@ -4645,6 +4650,11 @@
   },
   'viz_unittests': {
     'modifications': {
+      'android-11-x86-rel': {
+        'args': [
+          '--test-launcher-filter-file=../../testing/buildbot/filters/android.emulator_11.viz_unittests.filter',
+        ],
+      },
       'fuchsia-fyi-arm64-dbg': {
         'args': [
           '--test-launcher-filter-file=../../testing/buildbot/filters/fuchsia.debug.viz_unittests.filter',
diff --git a/testing/buildbot/variants.pyl b/testing/buildbot/variants.pyl
index 3795057d..1296d575 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 126.0.6462.0',
+    'description': 'Run with ash-chrome version 126.0.6463.0',
     'args': [
-      '--ash-chrome-path-override=../../lacros_version_skew_tests_v126.0.6462.0/test_ash_chrome',
+      '--ash-chrome-path-override=../../lacros_version_skew_tests_v126.0.6463.0/test_ash_chrome',
     ],
     'swarming': {
       'cipd_packages': [
         {
           'cipd_package': 'chromium/testing/linux-ash-chromium/x86_64/ash.zip',
-          'location': 'lacros_version_skew_tests_v126.0.6462.0',
-          'revision': 'version:126.0.6462.0',
+          'location': 'lacros_version_skew_tests_v126.0.6463.0',
+          'revision': 'version:126.0.6463.0',
         },
       ],
     },
diff --git a/testing/buildbot/waterfalls.pyl b/testing/buildbot/waterfalls.pyl
index a5b5a134..edcd053 100644
--- a/testing/buildbot/waterfalls.pyl
+++ b/testing/buildbot/waterfalls.pyl
@@ -453,6 +453,9 @@
         'test_suites': {
           'isolated_scripts': 'cast_junit_tests',
         },
+        'mixins': [
+            'has_native_resultdb_integration',
+        ],
       },
       'android-10-arm64-rel': {
         'swarming': {
diff --git a/testing/variations/fieldtrial_testing_config.json b/testing/variations/fieldtrial_testing_config.json
index 31a753b..02fb9fc 100644
--- a/testing/variations/fieldtrial_testing_config.json
+++ b/testing/variations/fieldtrial_testing_config.json
@@ -1821,31 +1821,6 @@
             ]
         }
     ],
-    "AutofillParsingPatternProvider": [
-        {
-            "platforms": [
-                "android",
-                "android_webview",
-                "chromeos",
-                "chromeos_lacros",
-                "ios",
-                "linux",
-                "mac",
-                "windows"
-            ],
-            "experiments": [
-                {
-                    "name": "Enabled",
-                    "params": {
-                        "prediction_source": "default"
-                    },
-                    "enable_features": [
-                        "AutofillParsingPatternProvider"
-                    ]
-                }
-            ]
-        }
-    ],
     "AutofillPreFilledFieldsCorrectly": [
         {
             "platforms": [
@@ -15544,6 +15519,26 @@
             ]
         }
     ],
+    "ProtectedAudiencesPermitCrossOriginTrustedSignals": [
+        {
+            "platforms": [
+                "android",
+                "chromeos",
+                "chromeos_lacros",
+                "linux",
+                "mac",
+                "windows"
+            ],
+            "experiments": [
+                {
+                    "name": "Enabled",
+                    "enable_features": [
+                        "FledgePermitCrossOriginTrustedSignals"
+                    ]
+                }
+            ]
+        }
+    ],
     "ProtectedAudiencesRealTimeReporting": [
         {
             "platforms": [
diff --git a/third_party/.gitignore b/third_party/.gitignore
index eb1f7d1..93d81d1 100644
--- a/third_party/.gitignore
+++ b/third_party/.gitignore
@@ -151,7 +151,7 @@
 /soda-win64
 /speex
 /sqlite4java/lib/
-/subresource-filter-ruleset/data/UnindexedRules
+/subresource-filter-ruleset/data/*
 /swift-format
 /swift-toolchain
 /swiftshader/
diff --git a/third_party/android_toolchain_canary/3pp/fetch.py b/third_party/android_toolchain_canary/3pp/fetch.py
index 9f017fd..3e59966 100755
--- a/third_party/android_toolchain_canary/3pp/fetch.py
+++ b/third_party/android_toolchain_canary/3pp/fetch.py
@@ -10,7 +10,7 @@
 
 
 # Update this when upgrading NDK.
-_URL = "https://ci.android.com/builds/submitted/10625055/linux/latest/raw/android-ndk-10625055-linux-x86_64.zip"
+_URL = "https://dl.google.com/android/repository/android-ndk-r27-beta1-linux.zip"
 
 def do_latest():
   # Change version time this file changes.
diff --git a/third_party/angle b/third_party/angle
index b0875f1..a951e0e 160000
--- a/third_party/angle
+++ b/third_party/angle
@@ -1 +1 @@
-Subproject commit b0875f124a2459318e974653517f87d8fcbb50c2
+Subproject commit a951e0e0823408ce2a5a827ac135c9454f6fc230
diff --git a/third_party/blink/common/features.cc b/third_party/blink/common/features.cc
index abd5538..81c77c9 100644
--- a/third_party/blink/common/features.cc
+++ b/third_party/blink/common/features.cc
@@ -617,6 +617,8 @@
              "CorrectFloatExtensionTestForWebGL",
              base::FEATURE_ENABLED_BY_DEFAULT);
 
+BASE_FEATURE(kCrabbyAvif, "CrabbyAvif", base::FEATURE_DISABLED_BY_DEFAULT);
+
 // When enabled, add a new option, {imageOrientation: 'none'}, to
 // createImageBitmap, which ignores the image orientation metadata of the source
 // and renders the image as encoded.
diff --git a/third_party/blink/common/shared_storage/shared_storage_utils.cc b/third_party/blink/common/shared_storage/shared_storage_utils.cc
index e1bcecc..cc56861 100644
--- a/third_party/blink/common/shared_storage/shared_storage_utils.cc
+++ b/third_party/blink/common/shared_storage/shared_storage_utils.cc
@@ -60,4 +60,20 @@
          base::IsStringUTF8AllowingNoncharacters(context_id);
 }
 
+bool IsValidPrivateAggregationFilteringIdMaxBytes(
+    size_t filtering_id_max_bytes) {
+  if (filtering_id_max_bytes ==
+      kPrivateAggregationApiDefaultFilteringIdMaxBytes) {
+    return true;
+  }
+
+  if (!base::FeatureList::IsEnabled(
+          features::kPrivateAggregationApiFilteringIds)) {
+    return false;
+  }
+
+  return filtering_id_max_bytes > 0 &&
+         filtering_id_max_bytes <= kPrivateAggregationApiMaxFilteringIdMaxBytes;
+}
+
 }  // namespace blink
diff --git a/third_party/blink/public/common/features.h b/third_party/blink/public/common/features.h
index ab41591..fb8f591 100644
--- a/third_party/blink/public/common/features.h
+++ b/third_party/blink/public/common/features.h
@@ -386,6 +386,9 @@
 BLINK_COMMON_EXPORT
 extern const base::FeatureParam<double> kCostReductionOfMultiplexedRequests;
 
+// Enables the use of CrabbyAvif for decoding AVIF images.
+BLINK_COMMON_EXPORT BASE_DECLARE_FEATURE(kCrabbyAvif);
+
 // Enables input IPC to directly target the renderer's compositor thread without
 // hopping through the IO thread first.
 BLINK_COMMON_EXPORT BASE_DECLARE_FEATURE(kDirectCompositorThreadIpc);
diff --git a/third_party/blink/public/common/shared_storage/shared_storage_utils.h b/third_party/blink/public/common/shared_storage/shared_storage_utils.h
index 2e2f744..617b555 100644
--- a/third_party/blink/public/common/shared_storage/shared_storage_utils.h
+++ b/third_party/blink/public/common/shared_storage/shared_storage_utils.h
@@ -89,9 +89,11 @@
   kSelectURLNonWebVisibleUnexpectedIndexReturned = 33,
   kSelectURLNonWebVisibleInsufficientBudget = 34,
   kSelectURLNonWebVisibleOther = 35,
+  kRunNonWebVisibleInvalidFilteringIdMaxBytes = 36,
+  kSelectURLNonWebVisibleInvalidFilteringIdMaxBytes = 37,
 
   // Keep this at the end and equal to the last entry.
-  kMaxValue = kSelectURLNonWebVisibleOther,
+  kMaxValue = kSelectURLNonWebVisibleInvalidFilteringIdMaxBytes,
 };
 
 // Whether or not there is sufficient budget for the `selectURL()` call, and if
@@ -137,7 +139,14 @@
     std::string_view context_id);
 
 // Maximum allowed length of the context_id string.
-constexpr int kPrivateAggregationApiContextIdMaxLength = 64;
+static constexpr int kPrivateAggregationApiContextIdMaxLength = 64;
+
+// Whether the `filtering_id_max_bytes` has a valid value.
+BLINK_COMMON_EXPORT bool IsValidPrivateAggregationFilteringIdMaxBytes(
+    size_t filtering_id_max_bytes);
+
+static constexpr size_t kPrivateAggregationApiDefaultFilteringIdMaxBytes = 1;
+static constexpr size_t kPrivateAggregationApiMaxFilteringIdMaxBytes = 8;
 
 }  // namespace blink
 
diff --git a/third_party/blink/public/devtools_protocol/browser_protocol.pdl b/third_party/blink/public/devtools_protocol/browser_protocol.pdl
index 5522734b..264628f 100644
--- a/third_party/blink/public/devtools_protocol/browser_protocol.pdl
+++ b/third_party/blink/public/devtools_protocol/browser_protocol.pdl
@@ -6459,7 +6459,7 @@
       optional string path
       # If specified, deletes only cookies with the the given name and partitionKey where domain
       # matches provided URL.
-      optional string partitionKey
+      experimental optional string partitionKey
 
   # Disables network tracking, prevents network events from being sent to the client.
   command disable
diff --git a/third_party/blink/public/mojom/shared_storage/shared_storage.mojom b/third_party/blink/public/mojom/shared_storage/shared_storage.mojom
index ec85795e..6343ae3 100644
--- a/third_party/blink/public/mojom/shared_storage/shared_storage.mojom
+++ b/third_party/blink/public/mojom/shared_storage/shared_storage.mojom
@@ -43,8 +43,9 @@
 // Bundles information passed to the worklet via the `privateAggregationConfig`
 // parameter on `sharedStorage.run()` or `sharedStorage.selectURL()`.
 struct PrivateAggregationConfig {
-  string? context_id;
   url.mojom.Origin? aggregation_coordinator_origin;
+  string? context_id;
+  uint32 filtering_id_max_bytes;
 };
 
 // Implemented by the browser and exposed to the renderer process on a
diff --git a/third_party/blink/public/mojom/shared_storage/shared_storage_worklet_service.mojom b/third_party/blink/public/mojom/shared_storage/shared_storage_worklet_service.mojom
index 2d6a3c9..0a4fddd 100644
--- a/third_party/blink/public/mojom/shared_storage/shared_storage_worklet_service.mojom
+++ b/third_party/blink/public/mojom/shared_storage/shared_storage_worklet_service.mojom
@@ -93,14 +93,19 @@
   RecordUseCounters(array<WebFeature> features);
 };
 
+// Per-operation parameters and pipes for Private Aggregation.
+struct PrivateAggregationOperationDetails {
+  pending_remote<PrivateAggregationHost> pa_host;
+  uint32 filtering_id_max_bytes;
+};
+
 // Used by the browser to load shared storage worklet script and run operations
 // in the worklet environment.
 // See https://github.com/pythagoraskitty/shared-storage/blob/main/README.md
 interface SharedStorageWorkletService {
-  // Binds `client` and, if not null, `pa_host`. Also initializes the
-  // "private-aggregation" permisisons policy's status to
-  // `private_aggregation_permissions_policy_allowed`. Must be the first call on
-  // this interface.
+  // Binds `client`. Also initializes the "private-aggregation" permissions
+  // policy's status to `private_aggregation_permissions_policy_allowed`. Must
+  // be the first call on this interface.
   Initialize(
     pending_associated_remote<SharedStorageWorkletServiceClient> client,
     bool private_aggregation_permissions_policy_allowed,
@@ -119,14 +124,14 @@
   // `name`. The size limit on `urls` should be checked at the Mojo boundary.
   // When the operation succeeds, the return value `index` will be set to the
   // uint32 value that the promise resolves to; otherwise it will be set to 0.
-  RunURLSelectionOperation(string name, array<url.mojom.Url> urls,
-                           CloneableMessage serialized_data,
-                           pending_remote<PrivateAggregationHost>? pa_host)
+  RunURLSelectionOperation(
+    string name, array<url.mojom.Url> urls, CloneableMessage serialized_data,
+    PrivateAggregationOperationDetails? pa_operation_details)
     => (bool success, string error_message, uint32 index);
 
   // Handle sharedStorage.runOperation(): run the operation previously
   // registered by registerOperation() with matching `name`.
   RunOperation(string name, CloneableMessage serialized_data,
-               pending_remote<PrivateAggregationHost>? pa_host)
+               PrivateAggregationOperationDetails? pa_operation_details)
     => (bool success, string error_message);
 };
diff --git a/third_party/blink/public/mojom/use_counter/metrics/web_feature.mojom b/third_party/blink/public/mojom/use_counter/metrics/web_feature.mojom
index 7cd3a5f..d0c5887 100644
--- a/third_party/blink/public/mojom/use_counter/metrics/web_feature.mojom
+++ b/third_party/blink/public/mojom/use_counter/metrics/web_feature.mojom
@@ -4360,6 +4360,8 @@
 
   kCSSLightDark = 4966,
 
+  kPrivateAggregationApiFilteringIds = 4967,
+
   // Add new features immediately above this line. Don't change assigned
   // numbers of any item, and don't reuse removed slots.
   // Also, run update_use_counter_feature_enum.py in
diff --git a/third_party/blink/renderer/build/scripts/core/css/properties/templates/style_builder_functions.tmpl b/third_party/blink/renderer/build/scripts/core/css/properties/templates/style_builder_functions.tmpl
index aed1816..48c66eb 100644
--- a/third_party/blink/renderer/build/scripts/core/css/properties/templates/style_builder_functions.tmpl
+++ b/third_party/blink/renderer/build/scripts/core/css/properties/templates/style_builder_functions.tmpl
@@ -226,9 +226,11 @@
     {% endcall %}
 
     {% call(property) apply_value(property) %}
+  const CSSValueList& list = To<CSSValueList>(value);
   CSS{{animation}}Data& data = state.StyleBuilder().Access{{animation}}s();
   data.{{vector}}.clear();
-  for (const CSSValue* list_value : To<CSSValueList>(value)) {
+  data.{{vector}}.reserve(list.length());
+  for (const CSSValue* list_value : list) {
     const auto& item = *list_value;
     data.{{vector}}.push_back(CSSToStyleMap::MapAnimation{{attribute}}(state, item));
   }
diff --git a/third_party/blink/renderer/core/animation/css/css_timing_data.h b/third_party/blink/renderer/core/animation/css/css_timing_data.h
index d88bf8d1..0c72b08 100644
--- a/third_party/blink/renderer/core/animation/css/css_timing_data.h
+++ b/third_party/blink/renderer/core/animation/css/css_timing_data.h
@@ -21,25 +21,23 @@
   USING_FAST_MALLOC(CSSTimingData);
 
  public:
+  using DelayVector = Vector<Timing::Delay, 1>;
+  using DurationVector = Vector<std::optional<double>, 1>;
+  using TimingFunctionVector = Vector<scoped_refptr<TimingFunction>, 1>;
+
   ~CSSTimingData() = default;
 
-  const Vector<Timing::Delay>& DelayStartList() const {
-    return delay_start_list_;
-  }
-  const Vector<Timing::Delay>& DelayEndList() const { return delay_end_list_; }
-  const Vector<std::optional<double>>& DurationList() const {
-    return duration_list_;
-  }
-  const Vector<scoped_refptr<TimingFunction>>& TimingFunctionList() const {
+  const DelayVector& DelayStartList() const { return delay_start_list_; }
+  const DelayVector& DelayEndList() const { return delay_end_list_; }
+  const DurationVector& DurationList() const { return duration_list_; }
+  const TimingFunctionVector& TimingFunctionList() const {
     return timing_function_list_;
   }
 
-  Vector<Timing::Delay>& DelayStartList() { return delay_start_list_; }
-  Vector<Timing::Delay>& DelayEndList() { return delay_end_list_; }
-  Vector<std::optional<double>>& DurationList() { return duration_list_; }
-  Vector<scoped_refptr<TimingFunction>>& TimingFunctionList() {
-    return timing_function_list_;
-  }
+  DelayVector& DelayStartList() { return delay_start_list_; }
+  DelayVector& DelayEndList() { return delay_end_list_; }
+  DurationVector& DurationList() { return duration_list_; }
+  TimingFunctionVector& TimingFunctionList() { return timing_function_list_; }
 
   bool HasSingleInitialDelayStart() const {
     return delay_start_list_.size() == 1u &&
@@ -58,8 +56,8 @@
         CubicBezierTimingFunction::EaseType::EASE);
   }
 
-  template <class T>
-  static const T& GetRepeated(const Vector<T>& v, size_t index) {
+  template <class T, wtf_size_t C>
+  static const T& GetRepeated(const Vector<T, C>& v, size_t index) {
     return v[index % v.size()];
   }
 
@@ -71,10 +69,10 @@
   bool TimingMatchForStyleRecalc(const CSSTimingData&) const;
 
  private:
-  Vector<Timing::Delay> delay_start_list_;
-  Vector<Timing::Delay> delay_end_list_;
-  Vector<std::optional<double>> duration_list_;
-  Vector<scoped_refptr<TimingFunction>> timing_function_list_;
+  DelayVector delay_start_list_;
+  DelayVector delay_end_list_;
+  DurationVector duration_list_;
+  TimingFunctionVector timing_function_list_;
 };
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/animation/css/css_transition_data.h b/third_party/blink/renderer/core/animation/css/css_transition_data.h
index ed82b64c..d6a2944e 100644
--- a/third_party/blink/renderer/core/animation/css/css_transition_data.h
+++ b/third_party/blink/renderer/core/animation/css/css_transition_data.h
@@ -55,6 +55,9 @@
     AtomicString property_string;
   };
 
+  using TransitionPropertyVector = Vector<TransitionProperty, 1>;
+  using TransitionBehaviorVector = Vector<TransitionBehavior, 1>;
+
   std::unique_ptr<CSSTransitionData> Clone() {
     return base::WrapUnique(new CSSTransitionData(*this));
   }
@@ -69,15 +72,15 @@
 
   Timing ConvertToTiming(size_t index) const;
 
-  const Vector<TransitionProperty>& PropertyList() const {
+  const TransitionPropertyVector& PropertyList() const {
     return property_list_;
   }
-  Vector<TransitionProperty>& PropertyList() { return property_list_; }
+  TransitionPropertyVector& PropertyList() { return property_list_; }
 
-  const Vector<TransitionBehavior>& BehaviorList() const {
+  const TransitionBehaviorVector& BehaviorList() const {
     return behavior_list_;
   }
-  Vector<TransitionBehavior>& BehaviorList() { return behavior_list_; }
+  TransitionBehaviorVector& BehaviorList() { return behavior_list_; }
 
   static std::optional<double> InitialDuration() { return 0; }
 
@@ -90,8 +93,8 @@
   }
 
  private:
-  Vector<TransitionProperty> property_list_;
-  Vector<TransitionBehavior> behavior_list_;
+  TransitionPropertyVector property_list_;
+  TransitionBehaviorVector behavior_list_;
 };
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/css/properties/computed_style_utils.cc b/third_party/blink/renderer/core/css/properties/computed_style_utils.cc
index c0ec505..37bbc04 100644
--- a/third_party/blink/renderer/core/css/properties/computed_style_utils.cc
+++ b/third_party/blink/renderer/core/css/properties/computed_style_utils.cc
@@ -2363,8 +2363,8 @@
 
 namespace {
 
-template <typename T, typename Func, typename... Args>
-CSSValue* CreateAnimationValueList(const Vector<T>& values,
+template <typename T, wtf_size_t C, typename Func, typename... Args>
+CSSValue* CreateAnimationValueList(const Vector<T, C>& values,
                                    Func item_func,
                                    Args&&... args) {
   CSSValueList* list = CSSValueList::CreateCommaSeparated();
@@ -2385,8 +2385,9 @@
 CSSValue* ComputedStyleUtils::ValueForAnimationDelayList(
     const CSSTimingData* timing_data) {
   return CreateAnimationValueList(
-      timing_data ? timing_data->DelayStartList()
-                  : Vector<Timing::Delay>{CSSTimingData::InitialDelayStart()},
+      timing_data
+          ? timing_data->DelayStartList()
+          : Vector<Timing::Delay, 1>{CSSTimingData::InitialDelayStart()},
       &ValueForAnimationDelay);
 }
 
@@ -2441,18 +2442,18 @@
       (phase == CSSValuePhase::kResolvedValue) &&
       (!animation_data || animation_data->HasSingleInitialTimeline());
   return CreateAnimationValueList(
-      animation_data
-          ? animation_data->DurationList()
-          : Vector<std::optional<double>>{CSSAnimationData::InitialDuration()},
+      animation_data ? animation_data->DurationList()
+                     : Vector<std::optional<double>,
+                              1>{CSSAnimationData::InitialDuration()},
       ValueForAnimationDuration, resolve_auto_to_zero);
 }
 
 CSSValue* ComputedStyleUtils::ValueForAnimationDurationList(
     const CSSTransitionData* transition_data) {
   return CreateAnimationValueList(
-      transition_data
-          ? transition_data->DurationList()
-          : Vector<std::optional<double>>{CSSTransitionData::InitialDuration()},
+      transition_data ? transition_data->DurationList()
+                      : Vector<std::optional<double>,
+                               1>{CSSTransitionData::InitialDuration()},
       ValueForAnimationDuration,
       /* resolve_auto_to_zero */ false);
 }
@@ -2634,10 +2635,9 @@
 CSSValue* ComputedStyleUtils::ValueForAnimationTimingFunctionList(
     const CSSTimingData* timing_data) {
   return CreateAnimationValueList(
-      timing_data
-          ? timing_data->TimingFunctionList()
-          : Vector<scoped_refptr<TimingFunction>>{CSSAnimationData::
-                                                      InitialTimingFunction()},
+      timing_data ? timing_data->TimingFunctionList()
+                  : Vector<scoped_refptr<TimingFunction>,
+                           1>{CSSAnimationData::InitialTimingFunction()},
       &ValueForAnimationTimingFunction);
 }
 
diff --git a/third_party/blink/renderer/core/css/properties/css_parsing_utils.cc b/third_party/blink/renderer/core/css/properties/css_parsing_utils.cc
index 6fda0f7..fe839e0 100644
--- a/third_party/blink/renderer/core/css/properties/css_parsing_utils.cc
+++ b/third_party/blink/renderer/core/css/properties/css_parsing_utils.cc
@@ -6136,6 +6136,9 @@
 template CSSIdentifierValue* ConsumeFontStretchKeywordOnly(
     CSSParserTokenStream& stream,
     const CSSParserContext& context);
+template CSSIdentifierValue* ConsumeFontStretchKeywordOnly(
+    CSSParserTokenRange& stream,
+    const CSSParserContext& context);
 
 template <typename T>
   requires std::is_same_v<T, CSSParserTokenStream> ||
diff --git a/third_party/blink/renderer/core/dom/document.cc b/third_party/blink/renderer/core/dom/document.cc
index 982b756..572e3ce 100644
--- a/third_party/blink/renderer/core/dom/document.cc
+++ b/third_party/blink/renderer/core/dom/document.cc
@@ -6064,8 +6064,10 @@
 void Document::EnqueueSnapChangedEvent(Node* target,
                                        Member<Node>& block_target,
                                        Member<Node>& inline_target) {
-  Event* snapchanged_event = SnapEvent::Create(event_type_names::kSnapchanged,
-                                               block_target, inline_target);
+  Event* snapchanged_event = SnapEvent::Create(
+      event_type_names::kSnapchanged,
+      (target->IsDocumentNode() ? Event::Bubbles::kYes : Event::Bubbles::kNo),
+      block_target, inline_target);
   snapchanged_event->SetTarget(target);
   scripted_animation_controller_->EnqueuePerFrameEvent(snapchanged_event);
 }
@@ -6073,10 +6075,12 @@
 void Document::EnqueueSnapChangingEvent(Node* target,
                                         Member<Node>& block_target,
                                         Member<Node>& inline_target) {
-  Event* snapchanged_event = SnapEvent::Create(event_type_names::kSnapchanging,
-                                               block_target, inline_target);
-  snapchanged_event->SetTarget(target);
-  scripted_animation_controller_->EnqueuePerFrameEvent(snapchanged_event);
+  Event* snapchanging_event = SnapEvent::Create(
+      event_type_names::kSnapchanging,
+      (target->IsDocumentNode() ? Event::Bubbles::kYes : Event::Bubbles::kNo),
+      block_target, inline_target);
+  snapchanging_event->SetTarget(target);
+  scripted_animation_controller_->EnqueuePerFrameEvent(snapchanging_event);
 }
 
 void Document::EnqueueMoveEvent() {
diff --git a/third_party/blink/renderer/core/dom/shadow_root.idl b/third_party/blink/renderer/core/dom/shadow_root.idl
index b78f12f..5b4271ca0 100644
--- a/third_party/blink/renderer/core/dom/shadow_root.idl
+++ b/third_party/blink/renderer/core/dom/shadow_root.idl
@@ -50,7 +50,7 @@
 
     // The serializable attribute controls whether the shadow root will be
     // serialized by getHTML({serializableShadowRoots:true}).
-    [RuntimeEnabled=ElementGetHTML] attribute boolean serializable;
+    [RuntimeEnabled=ElementGetHTML] readonly attribute boolean serializable;
 
     // The clonable attribute controls whether the shadow root will be
     // cloned by DOM cloning operations.
diff --git a/third_party/blink/renderer/core/frame/window.idl b/third_party/blink/renderer/core/frame/window.idl
index 4e342b9..27a29d25 100644
--- a/third_party/blink/renderer/core/frame/window.idl
+++ b/third_party/blink/renderer/core/frame/window.idl
@@ -184,7 +184,7 @@
     [RuntimeEnabled=AccessibilityObjectModel, CallWith=ScriptState] Promise<ComputedAccessibleNode> getComputedAccessibleNode(Element element);
 
     // https://dom.spec.whatwg.org/#interface-window-extensions
-    [Replaceable, GetterCallWith=ScriptState, MeasureAs=WindowEvent, NotEnumerable] readonly attribute any event;
+    [Replaceable, GetterCallWith=ScriptState, MeasureAs=WindowEvent] readonly attribute any event;
 
     // Non-standard APIs
     [MeasureAs=WindowClientInformation, Replaceable] readonly attribute Navigator clientInformation;
diff --git a/third_party/blink/renderer/core/html/forms/text_control_inner_elements.cc b/third_party/blink/renderer/core/html/forms/text_control_inner_elements.cc
index c7faf03..15fe6b7f 100644
--- a/third_party/blink/renderer/core/html/forms/text_control_inner_elements.cc
+++ b/third_party/blink/renderer/core/html/forms/text_control_inner_elements.cc
@@ -198,9 +198,7 @@
     style_builder.SetOverflowX(EOverflow::kScroll);
     // overflow-y:visible doesn't work because overflow-x:scroll makes a layer.
     style_builder.SetOverflowY(EOverflow::kScroll);
-    style_builder.SetPseudoElementStyles(
-        1 << (kPseudoIdScrollbar - kFirstPublicPseudoId));
-
+    style_builder.SetScrollbarWidth(EScrollbarWidth::kNone);
     style_builder.SetDisplay(EDisplay::kFlowRoot);
   }
 
@@ -210,18 +208,7 @@
   if (!is_visible_)
     style_builder.SetOpacity(0);
 
-  const ComputedStyle* style = style_builder.TakeStyle();
-
-  if (style->HasPseudoElementStyle(kPseudoIdScrollbar)) {
-    ComputedStyleBuilder no_scrollbar_style_builder =
-        GetDocument().GetStyleResolver().CreateComputedStyleBuilder();
-    no_scrollbar_style_builder.SetStyleType(kPseudoIdScrollbar);
-    no_scrollbar_style_builder.SetDisplay(EDisplay::kNone);
-    style->AddCachedPseudoElementStyle(no_scrollbar_style_builder.TakeStyle(),
-                                       kPseudoIdScrollbar, g_null_atom);
-  }
-
-  return style;
+  return style_builder.TakeStyle();
 }
 
 // ----------------------------
diff --git a/third_party/blink/renderer/core/html/html_image_element.h b/third_party/blink/renderer/core/html/html_image_element.h
index ee14eab..75eba5c 100644
--- a/third_party/blink/renderer/core/html/html_image_element.h
+++ b/third_party/blink/renderer/core/html/html_image_element.h
@@ -34,7 +34,6 @@
 #include "third_party/blink/renderer/core/html/forms/form_associated.h"
 #include "third_party/blink/renderer/core/html/html_element.h"
 #include "third_party/blink/renderer/core/html/html_image_loader.h"
-#include "third_party/blink/renderer/core/html/lazy_load_image_observer.h"
 #include "third_party/blink/renderer/core/resize_observer/resize_observer.h"
 #include "third_party/blink/renderer/platform/graphics/graphics_types.h"
 #include "third_party/blink/renderer/platform/graphics/image_orientation.h"
@@ -161,15 +160,6 @@
 
   bool ElementCreatedByParser() const { return element_created_by_parser_; }
 
-  LazyLoadImageObserver::VisibleLoadTimeMetrics&
-  EnsureVisibleLoadTimeMetrics() {
-    if (!visible_load_time_metrics_) {
-      visible_load_time_metrics_ =
-          std::make_unique<LazyLoadImageObserver::VisibleLoadTimeMetrics>();
-    }
-    return *visible_load_time_metrics_;
-  }
-
   // Updates if any optimized image policy is violated. When any policy is
   // violated, the image should be rendered as a placeholder image.
   void SetImagePolicyViolated() {
@@ -305,9 +295,6 @@
 
   HashSet<String> creator_scripts_;
 
-  std::unique_ptr<LazyLoadImageObserver::VisibleLoadTimeMetrics>
-      visible_load_time_metrics_;
-
   bool image_ad_use_counter_recorded_ = false;
 
   // The last rectangle reported to the `PageTimingMetricsSender`.
diff --git a/third_party/blink/renderer/core/html/lazy_load_image_observer.cc b/third_party/blink/renderer/core/html/lazy_load_image_observer.cc
index 8e8090b..b12da4e 100644
--- a/third_party/blink/renderer/core/html/lazy_load_image_observer.cc
+++ b/third_party/blink/renderer/core/html/lazy_load_image_observer.cc
@@ -6,9 +6,6 @@
 
 #include <limits>
 
-#include "base/metrics/histogram_functions.h"
-#include "base/metrics/histogram_macros.h"
-#include "base/strings/strcat.h"
 #include "third_party/blink/public/platform/web_effective_connection_type.h"
 #include "third_party/blink/renderer/core/dom/document.h"
 #include "third_party/blink/renderer/core/dom/element.h"
@@ -51,66 +48,6 @@
   return false;
 }
 
-void RecordVisibleLoadTimeForImage(
-    const LazyLoadImageObserver::VisibleLoadTimeMetrics&
-        visible_load_time_metrics) {
-  DCHECK(visible_load_time_metrics.has_initial_intersection_been_set);
-  DCHECK(!visible_load_time_metrics.time_when_first_visible.is_null());
-  DCHECK(!visible_load_time_metrics.time_when_first_load_finished.is_null());
-
-  base::TimeDelta visible_load_delay =
-      visible_load_time_metrics.time_when_first_load_finished -
-      visible_load_time_metrics.time_when_first_visible;
-  if (visible_load_delay.is_negative())
-    visible_load_delay = base::TimeDelta();
-
-  UMA_HISTOGRAM_MEDIUM_TIMES("Blink.VisibleLoadTime.LazyLoadImages",
-                             visible_load_delay);
-
-  if (visible_load_time_metrics.is_initially_intersecting) {
-    UMA_HISTOGRAM_MEDIUM_TIMES(
-        "Blink.VisibleLoadTime.LazyLoadImages.AboveTheFold3",
-        visible_load_delay);
-  } else {
-    UMA_HISTOGRAM_MEDIUM_TIMES(
-        "Blink.VisibleLoadTime.LazyLoadImages.BelowTheFold3",
-        visible_load_delay);
-  }
-
-  const char* network_type;
-  switch (GetNetworkStateNotifier().EffectiveType()) {
-    case WebEffectiveConnectionType::kTypeSlow2G:
-      network_type = "Slow2G";
-      break;
-    case WebEffectiveConnectionType::kType2G:
-      network_type = "2G";
-      break;
-    case WebEffectiveConnectionType::kType3G:
-      network_type = "3G";
-      break;
-    case WebEffectiveConnectionType::kType4G:
-      network_type = "4G";
-      break;
-    case WebEffectiveConnectionType::kTypeOffline:
-      network_type = "Offline";
-      break;
-    case WebEffectiveConnectionType::kTypeUnknown:
-      network_type = "Unknown";
-      break;
-    default:
-      NOTREACHED();
-  }
-
-  std::string uma_name = base::StrCat(
-      {"Blink.VisibleLoadTime.LazyLoadImages.",
-       visible_load_time_metrics.is_initially_intersecting ? "Above" : "Below",
-       "TheFold3.", network_type});
-  // Custom histogram times are used to exactly match the macro parameters of
-  // `UMA_HISTOGRAM_MEDIUM_TIMES` which is used for other metrics the area.
-  UmaHistogramCustomTimes(uma_name, visible_load_delay, base::Milliseconds(10),
-                          base::Minutes(3), 50);
-}
-
 bool IsDescendantOrSameDocument(Document& subject, Document& root) {
   for (Document* doc = &subject; doc; doc = doc->ParentDocument()) {
     if (doc == root) {
@@ -219,98 +156,8 @@
   }
 }
 
-void LazyLoadImageObserver::StartMonitoringVisibility(
-    Document* root_document,
-    HTMLImageElement* image_element) {
-  VisibleLoadTimeMetrics& visible_load_time_metrics =
-      image_element->EnsureVisibleLoadTimeMetrics();
-  if (!visible_load_time_metrics.time_when_first_visible.is_null()) {
-    // The time when the image first became visible has already been measured,
-    // so there's no need to monitor the visibility of the image any more.
-    return;
-  }
-  if (!visibility_metrics_observer_) {
-    visibility_metrics_observer_ = IntersectionObserver::Create(
-        *root_document,
-        WTF::BindRepeating(&LazyLoadImageObserver::OnVisibilityChanged,
-                           WrapWeakPersistent(this)),
-        LocalFrameUkmAggregator::kLazyLoadIntersectionObserver,
-        IntersectionObserver::Params{
-            .thresholds = {std::numeric_limits<float>::min()},
-            .needs_initial_observation_with_detached_target = false,
-        });
-  }
-  visibility_metrics_observer_->observe(image_element);
-}
-
-void LazyLoadImageObserver::OnLoadFinished(HTMLImageElement* image_element) {
-  VisibleLoadTimeMetrics& visible_load_time_metrics =
-      image_element->EnsureVisibleLoadTimeMetrics();
-
-  if (!visible_load_time_metrics.time_when_first_load_finished.is_null())
-    return;
-  visible_load_time_metrics.time_when_first_load_finished =
-      base::TimeTicks::Now();
-
-  if (visible_load_time_metrics.time_when_first_visible.is_null())
-    return;
-
-  RecordVisibleLoadTimeForImage(visible_load_time_metrics);
-}
-
-void LazyLoadImageObserver::OnVisibilityChanged(
-    const HeapVector<Member<IntersectionObserverEntry>>& entries) {
-  DCHECK(!entries.empty());
-
-  for (auto entry : entries) {
-    auto* image_element = DynamicTo<HTMLImageElement>(entry->target());
-    if (!image_element)
-      continue;
-
-    VisibleLoadTimeMetrics& visible_load_time_metrics =
-        image_element->EnsureVisibleLoadTimeMetrics();
-    // The image's visiblity shouldn't still be monitored if the time when the
-    // image first became visible has already been measured.
-    if (!visible_load_time_metrics.time_when_first_visible.is_null()) {
-      visibility_metrics_observer_->unobserve(image_element);
-      continue;
-    }
-
-    if (!visible_load_time_metrics.has_initial_intersection_been_set) {
-      visible_load_time_metrics.has_initial_intersection_been_set = true;
-      visible_load_time_metrics.is_initially_intersecting =
-          entry->isIntersecting();
-    }
-    if (!entry->isIntersecting())
-      continue;
-
-    visible_load_time_metrics.time_when_first_visible = base::TimeTicks::Now();
-    if (visible_load_time_metrics.time_when_first_load_finished.is_null()) {
-      // Note: If the WebEffectiveConnectionType enum ever gets out of sync
-      // with mojom::blink::EffectiveConnectionType, then both the AboveTheFold
-      // and BelowTheFold histograms here will have to be updated to record the
-      // sample in terms of mojom::blink::EffectiveConnectionType instead of
-      // WebEffectiveConnectionType.
-      if (visible_load_time_metrics.is_initially_intersecting) {
-        UMA_HISTOGRAM_ENUMERATION(
-            "Blink.VisibleBeforeLoaded.LazyLoadImages.AboveTheFold3",
-            GetNetworkStateNotifier().EffectiveType());
-      } else {
-        UMA_HISTOGRAM_ENUMERATION(
-            "Blink.VisibleBeforeLoaded.LazyLoadImages.BelowTheFold3",
-            GetNetworkStateNotifier().EffectiveType());
-      }
-    } else {
-      RecordVisibleLoadTimeForImage(visible_load_time_metrics);
-    }
-
-    visibility_metrics_observer_->unobserve(image_element);
-  }
-}
-
 void LazyLoadImageObserver::Trace(Visitor* visitor) const {
   visitor->Trace(lazy_load_intersection_observer_);
-  visitor->Trace(visibility_metrics_observer_);
 }
 
 int LazyLoadImageObserver::GetLazyLoadingImageMarginPx(
diff --git a/third_party/blink/renderer/core/html/lazy_load_image_observer.h b/third_party/blink/renderer/core/html/lazy_load_image_observer.h
index ed4ad8e..2ea378b8 100644
--- a/third_party/blink/renderer/core/html/lazy_load_image_observer.h
+++ b/third_party/blink/renderer/core/html/lazy_load_image_observer.h
@@ -5,7 +5,6 @@
 #ifndef THIRD_PARTY_BLINK_RENDERER_CORE_HTML_LAZY_LOAD_IMAGE_OBSERVER_H_
 #define THIRD_PARTY_BLINK_RENDERER_CORE_HTML_LAZY_LOAD_IMAGE_OBSERVER_H_
 
-#include "base/time/time.h"
 #include "third_party/blink/renderer/platform/heap/collection_support/heap_vector.h"
 #include "third_party/blink/renderer/platform/heap/forward.h"
 #include "third_party/blink/renderer/platform/heap/garbage_collected.h"
@@ -15,33 +14,17 @@
 
 class Document;
 class Element;
-class HTMLImageElement;
 class IntersectionObserver;
 class IntersectionObserverEntry;
 
 class LazyLoadImageObserver final
     : public GarbageCollected<LazyLoadImageObserver> {
  public:
-  struct VisibleLoadTimeMetrics {
-    // Keeps track of whether the image was initially intersecting the viewport.
-    bool is_initially_intersecting = false;
-    bool has_initial_intersection_been_set = false;
-
-    // Set when the image first becomes visible (i.e. appears in the viewport).
-    base::TimeTicks time_when_first_visible;
-
-    // Set when the first load event is dispatched for the image.
-    base::TimeTicks time_when_first_load_finished;
-  };
-
   LazyLoadImageObserver() = default;
 
   void StartMonitoringNearViewport(Document*, Element*);
   void StopMonitoring(Element*);
 
-  void StartMonitoringVisibility(Document*, HTMLImageElement*);
-  void OnLoadFinished(HTMLImageElement*);
-
   void Trace(Visitor*) const;
 
   // Loads all currently known lazy-loaded images. Returns whether any
@@ -51,17 +34,11 @@
  private:
   void LoadIfNearViewport(const HeapVector<Member<IntersectionObserverEntry>>&);
 
-  void OnVisibilityChanged(
-      const HeapVector<Member<IntersectionObserverEntry>>&);
-
   int GetLazyLoadingImageMarginPx(const Document& document);
 
   // The intersection observer responsible for loading the image once it's near
   // the viewport.
   Member<IntersectionObserver> lazy_load_intersection_observer_;
-
-  // The intersection observer used to track when the image becomes visible.
-  Member<IntersectionObserver> visibility_metrics_observer_;
 };
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/html/lazy_load_image_observer_test.cc b/third_party/blink/renderer/core/html/lazy_load_image_observer_test.cc
index b7cfc95e..48693cc 100644
--- a/third_party/blink/renderer/core/html/lazy_load_image_observer_test.cc
+++ b/third_party/blink/renderer/core/html/lazy_load_image_observer_test.cc
@@ -696,350 +696,6 @@
   EXPECT_TRUE(ConsoleMessages().Contains("unset onload"));
 }
 
-TEST_F(LazyLoadImagesTest, AboveTheFoldImageLoadedBeforeVisible) {
-  base::HistogramTester histogram_tester;
-
-  SimRequest main_resource("https://example.com/", "text/html");
-  SimSubresourceRequest image_resource("https://example.com/image.png",
-                                       "image/png");
-
-  LoadURL("https://example.com/");
-  // In-viewport lazy loaded images will be visible before being loaded because
-  // the intersection observer only starts loading them after they are visible.
-  // To work around this, we use a non-lazy image that synchronously loads which
-  // causes the in-viewport lazy image to also be loaded before it is visible.
-  main_resource.Complete(R"HTML(
-        <!doctype html>
-        <img src='https://example.com/image.png'/>
-        <img src='https://example.com/image.png' loading="lazy"/>
-      )HTML");
-  image_resource.Complete(TestImage());
-
-  // VisibleLoadTime should not have been recorded yet, since the image is not
-  // visible yet.
-  histogram_tester.ExpectTotalCount(
-      "Blink.VisibleLoadTime.LazyLoadImages.AboveTheFold3.4G", 0);
-
-  Compositor().BeginFrame();
-  test::RunPendingTasks();
-
-  histogram_tester.ExpectTotalCount(
-      "Blink.VisibleBeforeLoaded.LazyLoadImages.AboveTheFold3", 0);
-  histogram_tester.ExpectTotalCount(
-      "Blink.VisibleBeforeLoaded.LazyLoadImages.BelowTheFold3", 0);
-  histogram_tester.ExpectUniqueSample(
-      "Blink.VisibleLoadTime.LazyLoadImages.AboveTheFold3.4G", 0, 1);
-  histogram_tester.ExpectUniqueSample("Blink.VisibleLoadTime.LazyLoadImages", 0,
-                                      1);
-  histogram_tester.ExpectUniqueSample(
-      "Blink.VisibleLoadTime.LazyLoadImages.AboveTheFold3", 0, 1);
-  histogram_tester.ExpectTotalCount(
-      "Blink.VisibleLoadTime.LazyLoadImages.BelowTheFold3.4G", 0);
-  histogram_tester.ExpectTotalCount(
-      "Blink.VisibleLoadTime.LazyLoadImages.BelowTheFold3", 0);
-}
-
-// A fully above-the-fold cached image should not report below-the-fold metrics.
-TEST_F(LazyLoadImagesTest, AboveTheFoldCachedImageMetrics) {
-  base::HistogramTester histogram_tester;
-  SimRequest main_resource("https://example.com/", "text/html");
-  SimSubresourceRequest image_resource("https://example.com/image.png",
-                                       "image/png");
-  LoadURL("https://example.com/");
-  // Load a page with a non-lazy image that loads immediately, inserting the
-  // image into the cache.
-  main_resource.Complete(R"HTML(
-        <!doctype html>
-        <img id='image' src='https://example.com/image.png'/>
-        <div id='container'></div>
-      )HTML");
-  image_resource.Complete(TestImage());
-  Compositor().BeginFrame();
-  test::RunPendingTasks();
-
-  auto* image =
-      To<HTMLImageElement>(GetDocument().getElementById(AtomicString("image")));
-  EXPECT_TRUE(image->CachedImage()->IsLoaded());
-
-  // Insert a lazy loaded image with a src that is already cached.
-  auto* container = GetDocument().getElementById(AtomicString("container"));
-  container->setInnerHTML(R"HTML(
-    <img src='https://example.com/image.png' loading='lazy' id='lazy'/>
-  )HTML");
-
-  // The lazy image should have completed loading.
-  auto* lazy_image =
-      To<HTMLImageElement>(GetDocument().getElementById(AtomicString("lazy")));
-  EXPECT_TRUE(lazy_image->CachedImage()->IsLoaded());
-
-  // We should have a load time, but not yet `is_initially_intersecting`.
-  auto& visible_load_time = lazy_image->EnsureVisibleLoadTimeMetrics();
-  EXPECT_FALSE(visible_load_time.time_when_first_load_finished.is_null());
-  EXPECT_FALSE(visible_load_time.has_initial_intersection_been_set);
-
-  Compositor().BeginFrame();
-  test::RunPendingTasks();
-
-  // After a frame, the visibility metrics observer should fire and correctly
-  // set `is_initially_intersecting`.
-  EXPECT_TRUE(visible_load_time.has_initial_intersection_been_set);
-  EXPECT_TRUE(visible_load_time.is_initially_intersecting);
-
-  // Nothing was below the fold so no BelowTheFold metrics should be reported.
-  histogram_tester.ExpectTotalCount(
-      "Blink.VisibleBeforeLoaded.LazyLoadImages.BelowTheFold3", 0);
-  histogram_tester.ExpectTotalCount(
-      "Blink.VisibleLoadTime.LazyLoadImages.BelowTheFold3.4G", 0);
-}
-
-// An image that loads immediately due to being cached should not report
-// Blink.VisibleBeforeLoaded.LazyLoadImages metrics.
-TEST_F(LazyLoadImagesTest, CachedImageVisibleBeforeLoadedMetrics) {
-  base::HistogramTester histogram_tester;
-  SimRequest main_resource("https://a.com/", "text/html");
-  SimSubresourceRequest image_resource("https://a.com/image.png", "image/png");
-  LoadURL("https://a.com/");
-
-  // Load a page with a non-lazy image that loads immediately, inserting the
-  // image into the cache.
-  main_resource.Complete(
-      String::Format(R"HTML(
-        <!doctype html>
-        <div id='spacer' style='height: %dpx;'></div>
-        <div id='container'></div>
-        <!-- This non-lazy image will load immediately. -->
-        <img src='https://a.com/image.png' />
-      )HTML",
-                     kViewportHeight + kLoadingDistanceThreshold - 50));
-  image_resource.Complete(TestImage());
-  Compositor().BeginFrame();
-  test::RunPendingTasks();
-
-  // Insert a lazy loaded image with a src that is already cached.
-  auto* container = GetDocument().getElementById(AtomicString("container"));
-  container->setInnerHTML("<img src='https://a.com/image.png' loading='lazy'>");
-
-  Compositor().BeginFrame();
-  test::RunPendingTasks();
-
-  // VisibleBeforeLoaded should not be recorded since the image is not visible.
-  histogram_tester.ExpectTotalCount(
-      "Blink.VisibleBeforeLoaded.LazyLoadImages.AboveTheFold3", 0);
-  histogram_tester.ExpectTotalCount(
-      "Blink.VisibleBeforeLoaded.LazyLoadImages.BelowTheFold3", 0);
-
-  // Scroll down so that the image is in the viewport.
-  GetDocument().View()->LayoutViewport()->SetScrollOffset(
-      ScrollOffset(0, kViewportHeight + kLoadingDistanceThreshold + 50),
-      mojom::blink::ScrollType::kProgrammatic);
-
-  Compositor().BeginFrame();
-  test::RunPendingTasks();
-
-  // The image is now visible but loaded before being visible, so no
-  // VisibleBeforeLoaded metrics should have been recorded.
-  histogram_tester.ExpectTotalCount(
-      "Blink.VisibleBeforeLoaded.LazyLoadImages.AboveTheFold3", 0);
-  histogram_tester.ExpectTotalCount(
-      "Blink.VisibleBeforeLoaded.LazyLoadImages.BelowTheFold3", 0);
-}
-
-TEST_F(LazyLoadImagesTest, AboveTheFoldImageVisibleBeforeLoaded) {
-  base::HistogramTester histogram_tester;
-
-  SimRequest main_resource("https://example.com/", "text/html");
-  SimSubresourceRequest image_resource("https://example.com/image.png",
-                                       "image/png");
-
-  LoadURL("https://example.com/");
-  main_resource.Complete(
-      "<body><img src='https://example.com/image.png' loading='lazy'/></body>");
-
-  Compositor().BeginFrame();
-  test::RunPendingTasks();
-
-  // VisibleBeforeLoaded should have been recorded immediately when the image
-  // became visible.
-  histogram_tester.ExpectUniqueSample(
-      "Blink.VisibleBeforeLoaded.LazyLoadImages.AboveTheFold3",
-      static_cast<int>(WebEffectiveConnectionType::kType4G), 1);
-
-  // VisibleLoadTime should not have been recorded yet, since the image is not
-  // finished loading yet.
-  histogram_tester.ExpectTotalCount(
-      "Blink.VisibleLoadTime.LazyLoadImages.AboveTheFold3.4G", 0);
-
-  image_resource.Complete(TestImage());
-
-  Compositor().BeginFrame();
-  test::RunPendingTasks();
-
-  histogram_tester.ExpectUniqueSample(
-      "Blink.VisibleBeforeLoaded.LazyLoadImages.AboveTheFold3",
-      static_cast<int>(WebEffectiveConnectionType::kType4G), 1);
-  histogram_tester.ExpectTotalCount(
-      "Blink.VisibleBeforeLoaded.LazyLoadImages.BelowTheFold3", 0);
-  histogram_tester.ExpectTotalCount("Blink.VisibleLoadTime.LazyLoadImages", 1);
-  histogram_tester.ExpectTotalCount(
-      "Blink.VisibleLoadTime.LazyLoadImages.AboveTheFold3", 1);
-  histogram_tester.ExpectTotalCount(
-      "Blink.VisibleLoadTime.LazyLoadImages.BelowTheFold3", 0);
-  histogram_tester.ExpectTotalCount(
-      "Blink.VisibleLoadTime.LazyLoadImages.AboveTheFold3.4G", 1);
-  histogram_tester.ExpectTotalCount(
-      "Blink.VisibleLoadTime.LazyLoadImages.BelowTheFold3.4G", 0);
-}
-
-TEST_F(LazyLoadImagesTest, BelowTheFoldImageLoadedBeforeVisible) {
-  base::HistogramTester histogram_tester;
-
-  SimRequest main_resource("https://example.com/", "text/html");
-  LoadURL("https://example.com/");
-  main_resource.Complete(String::Format(
-      R"HTML(
-        <body>
-        <div style='height: %dpx;'></div>
-        <img src='https://example.com/image.png' loading="lazy"/>
-        </body>)HTML",
-      kViewportHeight + kLoadingDistanceThreshold + 100));
-
-  Compositor().BeginFrame();
-  test::RunPendingTasks();
-
-  SimSubresourceRequest image_resource("https://example.com/image.png",
-                                       "image/png");
-
-  // Scroll down such that the image is within kLoadingDistanceThreshold of the
-  // viewport, but isn't visible yet.
-  GetDocument().View()->LayoutViewport()->SetScrollOffset(
-      ScrollOffset(0, 200), mojom::blink::ScrollType::kProgrammatic);
-
-  Compositor().BeginFrame();
-  test::RunPendingTasks();
-
-  image_resource.Complete(TestImage());
-
-  Compositor().BeginFrame();
-  test::RunPendingTasks();
-
-  // VisibleLoadTime should not have been recorded yet, since the image is not
-  // visible yet.
-  histogram_tester.ExpectTotalCount(
-      "Blink.VisibleLoadTime.LazyLoadImages.BelowTheFold3.4G", 0);
-
-  // Scroll down such that the image is visible.
-  GetDocument().View()->LayoutViewport()->SetScrollOffset(
-      ScrollOffset(0, kViewportHeight + kLoadingDistanceThreshold),
-      mojom::blink::ScrollType::kProgrammatic);
-
-  Compositor().BeginFrame();
-  test::RunPendingTasks();
-
-  histogram_tester.ExpectTotalCount(
-      "Blink.VisibleBeforeLoaded.LazyLoadImages.AboveTheFold3", 0);
-  histogram_tester.ExpectTotalCount(
-      "Blink.VisibleBeforeLoaded.LazyLoadImages.BelowTheFold3", 0);
-  histogram_tester.ExpectUniqueSample("Blink.VisibleLoadTime.LazyLoadImages", 0,
-                                      1);
-  histogram_tester.ExpectTotalCount(
-      "Blink.VisibleLoadTime.LazyLoadImages.AboveTheFold3", 0);
-  histogram_tester.ExpectUniqueSample(
-      "Blink.VisibleLoadTime.LazyLoadImages.BelowTheFold3", 0, 1);
-  histogram_tester.ExpectTotalCount(
-      "Blink.VisibleLoadTime.LazyLoadImages.AboveTheFold3.4G", 0);
-  histogram_tester.ExpectUniqueSample(
-      "Blink.VisibleLoadTime.LazyLoadImages.BelowTheFold3.4G", 0, 1);
-}
-
-TEST_F(LazyLoadImagesTest, BelowTheFoldImageVisibleBeforeLoaded) {
-  base::HistogramTester histogram_tester;
-
-  SimRequest main_resource("https://example.com/", "text/html");
-  LoadURL("https://example.com/");
-  main_resource.Complete(String::Format(
-      R"HTML(
-        <body>
-        <div style='height: %dpx;'></div>
-        <img src='https://example.com/image.png' loading='lazy'/>
-        </body>)HTML",
-      kViewportHeight + kLoadingDistanceThreshold + 100));
-
-  Compositor().BeginFrame();
-  test::RunPendingTasks();
-
-  SimSubresourceRequest image_resource("https://example.com/image.png",
-                                       "image/png");
-
-  // Scroll down such that the image is visible.
-  GetDocument().View()->LayoutViewport()->SetScrollOffset(
-      ScrollOffset(0, kViewportHeight + kLoadingDistanceThreshold),
-      mojom::blink::ScrollType::kProgrammatic);
-
-  Compositor().BeginFrame();
-  test::RunPendingTasks();
-
-  // VisibleBeforeLoaded should have been recorded immediately when the image
-  // became visible.
-  histogram_tester.ExpectUniqueSample(
-      "Blink.VisibleBeforeLoaded.LazyLoadImages.BelowTheFold3",
-      static_cast<int>(WebEffectiveConnectionType::kType4G), 1);
-
-  // VisibleLoadTime should not have been recorded yet, since the image is not
-  // finished loading yet.
-  histogram_tester.ExpectTotalCount(
-      "Blink.VisibleLoadTime.LazyLoadImages.BelowTheFold3.4G", 0);
-
-  image_resource.Complete(TestImage());
-
-  Compositor().BeginFrame();
-  test::RunPendingTasks();
-
-  histogram_tester.ExpectTotalCount(
-      "Blink.VisibleBeforeLoaded.LazyLoadImages.AboveTheFold3", 0);
-  histogram_tester.ExpectUniqueSample(
-      "Blink.VisibleBeforeLoaded.LazyLoadImages.BelowTheFold3",
-      static_cast<int>(WebEffectiveConnectionType::kType4G), 1);
-  histogram_tester.ExpectTotalCount("Blink.VisibleLoadTime.LazyLoadImages", 1);
-  histogram_tester.ExpectTotalCount(
-      "Blink.VisibleLoadTime.LazyLoadImages.AboveTheFold3", 0);
-  histogram_tester.ExpectTotalCount(
-      "Blink.VisibleLoadTime.LazyLoadImages.BelowTheFold3", 1);
-  histogram_tester.ExpectTotalCount(
-      "Blink.VisibleLoadTime.LazyLoadImages.AboveTheFold3.4G", 0);
-  histogram_tester.ExpectTotalCount(
-      "Blink.VisibleLoadTime.LazyLoadImages.BelowTheFold3.4G", 1);
-}
-
-// LazyLoadImages metrics should not be recorded for non-lazy image loads.
-TEST_F(LazyLoadImagesTest, NonLazyIgnoredForLazyLoadImagesMetrics) {
-  base::HistogramTester histogram_tester;
-
-  SimRequest main_resource("https://aa.com/", "text/html");
-  SimSubresourceRequest above_resource("https://aa.com/above.png", "image/png");
-  SimSubresourceRequest below_resource("https://aa.com/below.png", "image/png");
-  LoadURL("https://aa.com/");
-  main_resource.Complete(
-      String::Format(R"HTML(
-        <!doctype html>
-        <img src='https://aa.com/above.png'/>
-        <div style='height: %dpx;'></div>
-        <img src='https://aa.com/below.png'/>)HTML",
-                     kViewportHeight + kLoadingDistanceThreshold + 100));
-  above_resource.Complete(TestImage());
-  below_resource.Complete(TestImage());
-
-  Compositor().BeginFrame();
-  test::RunPendingTasks();
-  histogram_tester.ExpectTotalCount(
-      "Blink.VisibleBeforeLoaded.LazyLoadImages.AboveTheFold3", 0);
-  histogram_tester.ExpectTotalCount(
-      "Blink.VisibleBeforeLoaded.LazyLoadImages.BelowTheFold3", 0);
-  histogram_tester.ExpectTotalCount(
-      "Blink.VisibleLoadTime.LazyLoadImages.AboveTheFold3.4G", 0);
-  histogram_tester.ExpectTotalCount(
-      "Blink.VisibleLoadTime.LazyLoadImages.BelowTheFold3.4G", 0);
-}
-
 // Allow lazy loading of file:/// urls.
 TEST_F(LazyLoadImagesTest, LazyLoadFileUrls) {
   SimRequest main_resource("file:///test.html", "text/html");
diff --git a/third_party/blink/renderer/core/layout/out_of_flow_layout_part.cc b/third_party/blink/renderer/core/layout/out_of_flow_layout_part.cc
index b00f846..d8129f79 100644
--- a/third_party/blink/renderer/core/layout/out_of_flow_layout_part.cc
+++ b/third_party/blink/renderer/core/layout/out_of_flow_layout_part.cc
@@ -2436,7 +2436,7 @@
     const PhysicalBoxFragment* new_fragmentainer;
     if (node.IsPaginatedRoot()) {
       new_fragmentainer = &PaginatedRootLayoutAlgorithm::CreateEmptyPage(
-          node, GetConstraintSpace(), previous_fragmentainer);
+          node, GetConstraintSpace(), index, previous_fragmentainer);
     } else {
       new_fragmentainer = &ColumnLayoutAlgorithm::CreateEmptyColumn(
           node, GetConstraintSpace(), previous_fragmentainer);
diff --git a/third_party/blink/renderer/core/layout/paginated_root_layout_algorithm.cc b/third_party/blink/renderer/core/layout/paginated_root_layout_algorithm.cc
index 9d3a1f24..e25e544 100644
--- a/third_party/blink/renderer/core/layout/paginated_root_layout_algorithm.cc
+++ b/third_party/blink/renderer/core/layout/paginated_root_layout_algorithm.cc
@@ -80,16 +80,14 @@
 const PhysicalBoxFragment& PaginatedRootLayoutAlgorithm::CreateEmptyPage(
     const BlockNode& node,
     const ConstraintSpace& parent_space,
+    wtf_size_t page_index,
     const PhysicalBoxFragment& previous_fragmentainer) {
   const BlockBreakToken* break_token = previous_fragmentainer.GetBreakToken();
   PageAreaLayoutParams page_area_params = {
       .break_token = break_token,
       .template_fragmentainer = &previous_fragmentainer};
-  // TODO(mstensho): We can do better than just using the size of the last page
-  // (figure out the correct page size by checking the page index and name), but
-  // there are other parts of the code that assume this behavior.
   PageContainerResult result =
-      LayoutPageContainer(node, parent_space, /*page_index=*/0,
+      LayoutPageContainer(node, parent_space, page_index,
                           previous_fragmentainer.PageName(), page_area_params);
   return *result.fragment;
 }
diff --git a/third_party/blink/renderer/core/layout/paginated_root_layout_algorithm.h b/third_party/blink/renderer/core/layout/paginated_root_layout_algorithm.h
index 608ca2a..61a1811a 100644
--- a/third_party/blink/renderer/core/layout/paginated_root_layout_algorithm.h
+++ b/third_party/blink/renderer/core/layout/paginated_root_layout_algorithm.h
@@ -96,6 +96,7 @@
   static const PhysicalBoxFragment& CreateEmptyPage(
       const BlockNode& node,
       const ConstraintSpace& parent_space,
+      wtf_size_t page_index,
       const PhysicalBoxFragment& previous_fragmentainer);
 
  private:
diff --git a/third_party/blink/renderer/core/loader/image_loader.cc b/third_party/blink/renderer/core/loader/image_loader.cc
index 6c7a04b..8ff42da 100644
--- a/third_party/blink/renderer/core/loader/image_loader.cc
+++ b/third_party/blink/renderer/core/loader/image_loader.cc
@@ -566,7 +566,6 @@
           lazy_image_load_state_ != LazyImageLoadState::kFullImage) {
         if (auto* html_image = DynamicTo<HTMLImageElement>(GetElement())) {
           if (LazyImageHelper::ShouldDeferImageLoad(*frame, html_image)) {
-            LazyImageHelper::StartMonitoringVisibilityMetrics(html_image);
             lazy_image_load_state_ = LazyImageLoadState::kDeferred;
             params.SetLazyImageDeferred();
           }
diff --git a/third_party/blink/renderer/core/loader/lazy_image_helper.cc b/third_party/blink/renderer/core/loader/lazy_image_helper.cc
index 84974ec..dcc8688 100644
--- a/third_party/blink/renderer/core/loader/lazy_image_helper.cc
+++ b/third_party/blink/renderer/core/loader/lazy_image_helper.cc
@@ -94,15 +94,6 @@
   return true;
 }
 
-// static
-void LazyImageHelper::StartMonitoringVisibilityMetrics(
-    HTMLImageElement* html_image) {
-  if (Document* root_document = GetRootDocumentOrNull(html_image)) {
-    root_document->EnsureLazyLoadImageObserver().StartMonitoringVisibility(
-        root_document, html_image);
-  }
-}
-
 void LazyImageHelper::RecordMetricsOnLoadFinished(
     HTMLImageElement* image_element) {
   // TODO(pdr): We should only report metrics for images that were actually lazy
@@ -125,8 +116,6 @@
                             response_size);
     }
   }
-
-  root_document->EnsureLazyLoadImageObserver().OnLoadFinished(image_element);
 }
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/loader/lazy_image_helper.h b/third_party/blink/renderer/core/loader/lazy_image_helper.h
index 588acacd..7d2b111 100644
--- a/third_party/blink/renderer/core/loader/lazy_image_helper.h
+++ b/third_party/blink/renderer/core/loader/lazy_image_helper.h
@@ -27,8 +27,6 @@
   static bool ShouldDeferImageLoad(LocalFrame& frame,
                                    HTMLImageElement* html_image);
 
-  static void StartMonitoringVisibilityMetrics(HTMLImageElement* html_image);
-
   static void RecordMetricsOnLoadFinished(HTMLImageElement* image_element);
 };
 
diff --git a/third_party/blink/renderer/core/paint/selection_bounds_recorder.cc b/third_party/blink/renderer/core/paint/selection_bounds_recorder.cc
index b434991..1455b131 100644
--- a/third_party/blink/renderer/core/paint/selection_bounds_recorder.cc
+++ b/third_party/blink/renderer/core/paint/selection_bounds_recorder.cc
@@ -203,9 +203,12 @@
   if (!layout_object || !layout_object->IsBox())
     return true;
 
-  const PhysicalOffset sample_point = GetSamplePointForVisibility(
+  PhysicalOffset sample_point = GetSamplePointForVisibility(
       edge_start, edge_end, rect_layout_object.GetFrame()->PageZoomFactor());
 
+  // Convert from paint coordinates to local layout coordinates.
+  sample_point -= layout_object->FirstFragment().PaintOffset();
+
   auto* const text_control_object = To<LayoutBox>(layout_object);
   const PhysicalOffset position_in_input =
       rect_layout_object.LocalToAncestorPoint(sample_point, text_control_object,
diff --git a/third_party/blink/renderer/core/scroll/snap_event.cc b/third_party/blink/renderer/core/scroll/snap_event.cc
index 6103066..c7edc89 100644
--- a/third_party/blink/renderer/core/scroll/snap_event.cc
+++ b/third_party/blink/renderer/core/scroll/snap_event.cc
@@ -11,15 +11,18 @@
 namespace blink {
 
 SnapEvent* SnapEvent::Create(const AtomicString& type,
+                             Bubbles bubbles,
                              Member<Node>& block_target,
                              Member<Node>& inline_target) {
-  return MakeGarbageCollected<SnapEvent>(type, block_target, inline_target);
+  return MakeGarbageCollected<SnapEvent>(type, bubbles, block_target,
+                                         inline_target);
 }
 
 SnapEvent::SnapEvent(const AtomicString& type,
+                     Bubbles bubbles,
                      Member<Node>& block_target,
                      Member<Node>& inline_target)
-    : Event(type, Bubbles::kNo, Cancelable::kNo),
+    : Event(type, bubbles, Cancelable::kNo),
       snap_target_block_(block_target),
       snap_target_inline_(inline_target) {}
 
diff --git a/third_party/blink/renderer/core/scroll/snap_event.h b/third_party/blink/renderer/core/scroll/snap_event.h
index 15e65903..e510747 100644
--- a/third_party/blink/renderer/core/scroll/snap_event.h
+++ b/third_party/blink/renderer/core/scroll/snap_event.h
@@ -24,9 +24,11 @@
 
  public:
   static SnapEvent* Create(const AtomicString& type,
+                           Bubbles bubbles,
                            Member<Node>& block_target,
                            Member<Node>& inline_target);
   SnapEvent(const AtomicString& type,
+            Bubbles bubbles,
             Member<Node>& block_target,
             Member<Node>& inline_target);
 
diff --git a/third_party/blink/renderer/core/timing/window_performance.cc b/third_party/blink/renderer/core/timing/window_performance.cc
index a6bedf1..9b224d67 100644
--- a/third_party/blink/renderer/core/timing/window_performance.cc
+++ b/third_party/blink/renderer/core/timing/window_performance.cc
@@ -33,10 +33,12 @@
 
 #include <optional>
 
+#include "base/feature_list.h"
 #include "base/trace_event/common/trace_event_common.h"
 #include "base/trace_event/trace_event.h"
 #include "components/viz/common/frame_timing_details.h"
 #include "services/metrics/public/cpp/ukm_builders.h"
+#include "services/network/public/mojom/load_timing_info.mojom-blink.h"
 #include "third_party/blink/public/common/features.h"
 #include "third_party/blink/public/common/frame/frame_owner_element_type.h"
 #include "third_party/blink/public/platform/platform.h"
@@ -270,9 +272,109 @@
   return memory_info;
 }
 
+namespace {
+
+BASE_FEATURE(kAdjustNavigationalPrefetchTiming,
+             "AdjustNavigationalPrefetchTiming",
+             base::FEATURE_ENABLED_BY_DEFAULT);
+
+enum class AdjustNavigationalPrefetchTimingBehavior {
+  kRemoveLoadTiming,
+  kClampToFetchStart,
+};
+
+constexpr base::FeatureParam<AdjustNavigationalPrefetchTimingBehavior>::Option
+    kAdjustNavigationalPrefetchTimingBehaviorOptions[] = {
+        {AdjustNavigationalPrefetchTimingBehavior::kRemoveLoadTiming,
+         "remove_load_timing"},
+        {AdjustNavigationalPrefetchTimingBehavior::kClampToFetchStart,
+         "clamp_to_fetch_start"},
+};
+
+constexpr base::FeatureParam<AdjustNavigationalPrefetchTimingBehavior>
+    kAdjustNavigationalPrefetchTimingBehavior{
+        &kAdjustNavigationalPrefetchTiming,
+        "adjust_navigational_prefetch_timing_behavior",
+        AdjustNavigationalPrefetchTimingBehavior::kClampToFetchStart,
+        &kAdjustNavigationalPrefetchTimingBehaviorOptions};
+
+network::mojom::blink::LoadTimingInfoPtr
+AdjustLoadTimingForNavigationalPrefetch(
+    const DocumentLoadTiming& document_load_timing,
+    network::mojom::blink::LoadTimingInfoPtr timing) {
+  if (!base::FeatureList::IsEnabled(kAdjustNavigationalPrefetchTiming)) {
+    return timing;
+  }
+
+  static const auto behavior = kAdjustNavigationalPrefetchTimingBehavior.Get();
+  switch (behavior) {
+    case AdjustNavigationalPrefetchTimingBehavior::kRemoveLoadTiming:
+      return nullptr;
+
+    case AdjustNavigationalPrefetchTimingBehavior::kClampToFetchStart:
+      break;
+  }
+
+  // Everything that happened before the fetch start (this is the value that
+  // will be exposed as fetchStart on PerformanceNavigationTiming).
+  using network::mojom::blink::LoadTimingInfo;
+  using network::mojom::blink::LoadTimingInfoConnectTiming;
+  const base::TimeTicks min_ticks = document_load_timing.FetchStart();
+  auto new_timing = LoadTimingInfo::New();
+  new_timing->socket_reused = timing->socket_reused;
+  new_timing->socket_log_id = timing->socket_log_id;
+
+  // Copy the basic members of LoadTimingInfo, and clamp them.
+  for (base::TimeTicks LoadTimingInfo::*ts :
+       {&LoadTimingInfo::request_start, &LoadTimingInfo::send_start,
+        &LoadTimingInfo::send_end, &LoadTimingInfo::receive_headers_start,
+        &LoadTimingInfo::receive_headers_end,
+        &LoadTimingInfo::receive_non_informational_headers_start,
+        &LoadTimingInfo::first_early_hints_time}) {
+    if (!((*timing).*ts).is_null()) {
+      (*new_timing).*ts = std::max((*timing).*ts, min_ticks);
+    }
+  }
+
+  // If connect timing is available, do the same to it.
+  if (auto* connect_timing = timing->connect_timing.get()) {
+    new_timing->connect_timing = LoadTimingInfoConnectTiming::New();
+    auto& new_connect_timing = *new_timing->connect_timing;
+    for (base::TimeTicks LoadTimingInfoConnectTiming::*ts : {
+             &LoadTimingInfoConnectTiming::domain_lookup_start,
+             &LoadTimingInfoConnectTiming::domain_lookup_end,
+             &LoadTimingInfoConnectTiming::connect_start,
+             &LoadTimingInfoConnectTiming::connect_end,
+             &LoadTimingInfoConnectTiming::ssl_start,
+             &LoadTimingInfoConnectTiming::ssl_end,
+         }) {
+      if (!(connect_timing->*ts).is_null()) {
+        new_connect_timing.*ts = std::max(connect_timing->*ts, min_ticks);
+      }
+    }
+  }
+
+  return new_timing;
+}
+
+}  // namespace
+
 void WindowPerformance::CreateNavigationTimingInstance(
     mojom::blink::ResourceTimingInfoPtr info) {
   DCHECK(DomWindow());
+
+  // If this is navigational prefetch, it may be necessary to partially redact
+  // the timings to avoid exposing when events that occurred during the prefetch
+  // happened. Instead, they look like they happened very fast.
+  DocumentLoader* loader = DomWindow()->document()->Loader();
+  if (loader &&
+      loader->GetNavigationDeliveryType() ==
+          network::mojom::NavigationDeliveryType::kNavigationalPrefetch &&
+      info->timing) {
+    info->timing = AdjustLoadTimingForNavigationalPrefetch(
+        loader->GetTiming(), std::move(info->timing));
+  }
+
   navigation_timing_ = MakeGarbageCollected<PerformanceNavigationTiming>(
       *DomWindow(), std::move(info), time_origin_);
 }
diff --git a/third_party/blink/renderer/core/view_transition/view_transition_supplement.cc b/third_party/blink/renderer/core/view_transition/view_transition_supplement.cc
index 75c744ba..0640ac8 100644
--- a/third_party/blink/renderer/core/view_transition/view_transition_supplement.cc
+++ b/third_party/blink/renderer/core/view_transition/view_transition_supplement.cc
@@ -176,7 +176,7 @@
 
   // If there is a transition in a parent frame, give that precedence over a
   // transition in a child frame.
-  if (!RuntimeEnabledFeatures::ConcurrentViewTransitionsEnabled() &&
+  if (!RuntimeEnabledFeatures::ConcurrentViewTransitionsSPAEnabled() &&
       HasActiveTransitionInAncestorFrame(document.GetFrame())) {
     auto skipped_transition = transition_;
     skipped_transition->SkipTransition();
@@ -187,7 +187,7 @@
 
   // Skip transitions in all frames associated with this widget. We can only
   // have one transition per widget/CC.
-  if (!RuntimeEnabledFeatures::ConcurrentViewTransitionsEnabled()) {
+  if (!RuntimeEnabledFeatures::ConcurrentViewTransitionsSPAEnabled()) {
     SkipTransitionInAllLocalFrames(document.GetFrame());
   }
   DCHECK(transition_);
diff --git a/third_party/blink/renderer/modules/accessibility/ax_object.cc b/third_party/blink/renderer/modules/accessibility/ax_object.cc
index 26282e49..1db6579 100644
--- a/third_party/blink/renderer/modules/accessibility/ax_object.cc
+++ b/third_party/blink/renderer/modules/accessibility/ax_object.cc
@@ -1764,6 +1764,7 @@
         IsOnlyChild()) {
       // The text of an only-child inline text box can be inferred directly
       // from the parent. No need to serialize redundant data.
+      node_data->SetNameFrom(ax::mojom::blink::NameFrom::kContents);
       return;
     }
   }
diff --git a/third_party/blink/renderer/modules/ml/ml_context.cc b/third_party/blink/renderer/modules/ml/ml_context.cc
index 7a5f7ed..8091b48 100644
--- a/third_party/blink/renderer/modules/ml/ml_context.cc
+++ b/third_party/blink/renderer/modules/ml/ml_context.cc
@@ -414,4 +414,31 @@
                                     "Not implemented");
 }
 
+void MLContext::dispatch(ScriptState* script_state,
+                         MLGraph* graph,
+                         const MLNamedBuffers& inputs,
+                         const MLNamedBuffers& outputs,
+                         ExceptionState& exception_state) {
+  ScopedMLTrace scoped_trace("MLContext::dispatch");
+  if (!script_state->ContextIsValid()) {
+    exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError,
+                                      "Invalid script state");
+    return;
+  }
+
+  if (graph->Context() != this) {
+    exception_state.ThrowTypeError(
+        "The graph isn't built within this context.");
+    return;
+  }
+
+  if (device_type_ == V8MLDeviceType::Enum::kGpu) {
+    return graph->Dispatch(std::move(scoped_trace), inputs, outputs,
+                           exception_state);
+  }
+
+  exception_state.ThrowDOMException(DOMExceptionCode::kNotSupportedError,
+                                    "Not implemented");
+}
+
 }  // namespace blink
diff --git a/third_party/blink/renderer/modules/ml/ml_context.h b/third_party/blink/renderer/modules/ml/ml_context.h
index dbc7a5f..c39b37e 100644
--- a/third_party/blink/renderer/modules/ml/ml_context.h
+++ b/third_party/blink/renderer/modules/ml/ml_context.h
@@ -112,6 +112,12 @@
                                            MLBuffer* src_buffer,
                                            ExceptionState& exception_state);
 
+  void dispatch(ScriptState* script_state,
+                MLGraph* graph,
+                const MLNamedBuffers& inputs,
+                const MLNamedBuffers& outputs,
+                ExceptionState& exception_state);
+
   // Creates a platform-specific compute graph described by `graph_info`.
   void CreateWebNNGraph(
       webnn::mojom::blink::GraphInfoPtr graph_info,
diff --git a/third_party/blink/renderer/modules/ml/ml_context.idl b/third_party/blink/renderer/modules/ml/ml_context.idl
index d2c0e96b..cac50a9b 100644
--- a/third_party/blink/renderer/modules/ml/ml_context.idl
+++ b/third_party/blink/renderer/modules/ml/ml_context.idl
@@ -19,6 +19,8 @@
   MLNamedArrayBufferViews outputs;
 };
 
+typedef record<DOMString, MLBuffer> MLNamedBuffers;
+
 [
   RuntimeEnabled=MachineLearningCommon,
   SecureContext,
@@ -68,4 +70,12 @@
     RaisesException
   ] Promise<ArrayBuffer> readBuffer(
         MLBuffer srcBuffer);
+
+  // TODO(crbug.com/1273291): enable partial buffer dispatches.
+  [
+    RuntimeEnabled=MachineLearningNeuralNetwork,
+    CallWith=ScriptState,
+    RaisesException
+  ] void dispatch(
+      MLGraph graph, MLNamedBuffers inputs, MLNamedBuffers outputs);
 };
diff --git a/third_party/blink/renderer/modules/ml/webnn/ml_buffer_mojo.h b/third_party/blink/renderer/modules/ml/webnn/ml_buffer_mojo.h
index 0a389e0..ea6688b 100644
--- a/third_party/blink/renderer/modules/ml/webnn/ml_buffer_mojo.h
+++ b/third_party/blink/renderer/modules/ml/webnn/ml_buffer_mojo.h
@@ -39,6 +39,8 @@
 
   const base::UnguessableToken& handle() const { return webnn_handle_; }
 
+  bool is_bound() const { return remote_buffer_.is_bound(); }
+
  protected:
   void DestroyImpl() override;
 
diff --git a/third_party/blink/renderer/modules/ml/webnn/ml_graph.cc b/third_party/blink/renderer/modules/ml/webnn/ml_graph.cc
index f7033c3..72181a1 100644
--- a/third_party/blink/renderer/modules/ml/webnn/ml_graph.cc
+++ b/third_party/blink/renderer/modules/ml/webnn/ml_graph.cc
@@ -4,6 +4,8 @@
 
 #include "third_party/blink/renderer/modules/ml/webnn/ml_graph.h"
 
+#include <cinttypes>
+
 #include "base/numerics/checked_math.h"
 #include "third_party/blink/renderer/bindings/core/v8/script_promise_resolver.h"
 #include "third_party/blink/renderer/core/dom/dom_exception.h"
@@ -12,6 +14,7 @@
 #include "third_party/blink/renderer/core/typed_arrays/dom_data_view.h"
 #include "third_party/blink/renderer/core/typed_arrays/dom_typed_array.h"
 #include "third_party/blink/renderer/modules/ml/ml_context.h"
+#include "third_party/blink/renderer/modules/ml/webnn/ml_buffer.h"
 #include "third_party/blink/renderer/modules/ml/webnn/ml_operand.h"
 #include "third_party/blink/renderer/modules/ml/webnn/ml_operator.h"
 #include "third_party/blink/renderer/platform/bindings/exception_state.h"
@@ -70,6 +73,71 @@
   return true;
 }
 
+// TODO(crbug.com/1472888): switch validation functions to use base::unexpected.
+bool ValidateNamedMLBuffers(
+    const MLContext* context,
+    const MLNamedBuffers& named_buffers,
+    const HashMap<String, MLGraph::ResourceInfo>& resources_info,
+    String& error_message) {
+  if (named_buffers.size() !=
+      base::checked_cast<wtf_size_t>(resources_info.size())) {
+    error_message = String::Format(
+        "The number (%u) of MLBuffer(s) doesn't match the "
+        "expectation (%u).",
+        named_buffers.size(), resources_info.size());
+    return false;
+  }
+  for (const auto& [name, buffer] : named_buffers) {
+    if (!resources_info.Contains(name)) {
+      error_message = String::Format("The name \"%s\" isn't part of the graph.",
+                                     name.Utf8().c_str());
+      return false;
+    }
+    const auto& info = resources_info.at(name);
+    if (buffer->size() != info.byte_length) {
+      error_message =
+          String::Format("The size %" PRIu64
+                         ", of the MLBuffer with name \"%s\" "
+                         "doesn't match the expected byte length (%zu).",
+                         buffer->size(), name.Utf8().c_str(), info.byte_length);
+      return false;
+    }
+    if (buffer->context() != context) {
+      error_message = String::Format(
+          "The context of MLGraph doesn't match the context of the MLBuffer "
+          "with name \"%s\".",
+          name.Utf8().c_str());
+      return false;
+    }
+  }
+  return true;
+}
+
+bool ValidateMLBufferUsage(const MLNamedBuffers& named_inputs,
+                           const MLNamedBuffers& named_outputs,
+                           String& error_message) {
+  // Validate that output buffers are unique.
+  HeapHashSet<Member<MLBuffer>> output_buffers;
+  for (const auto& named_output : named_outputs) {
+    output_buffers.insert(named_output.second);
+  }
+
+  if (output_buffers.size() != named_outputs.size()) {
+    error_message =
+        "The same MLBuffer cannot be used more than once as output.";
+    return false;
+  }
+
+  // Validate buffers used for input and output are unique.
+  for (const auto& named_input : named_inputs) {
+    if (output_buffers.Contains(named_input.second)) {
+      error_message = "The same MLBuffer cannot be used as input and output.";
+      return false;
+    }
+  }
+  return true;
+}
+
 }  // namespace
 
 MLGraph::MLGraph(MLContext* context) : ml_context_(context) {}
@@ -119,6 +187,35 @@
               exception_state);
 }
 
+void MLGraph::Dispatch(ScopedMLTrace scoped_trace,
+                       const MLNamedBuffers& inputs,
+                       const MLNamedBuffers& outputs,
+                       ExceptionState& exception_state) {
+  // The MLGraph object should be initialized before dispatching.
+  DCHECK(resources_info_initialized_);
+
+  // Validate the MLNamedBuffers.
+  String error_message;
+  if (!ValidateNamedMLBuffers(Context(), inputs, input_resources_info_,
+                              error_message)) {
+    exception_state.ThrowTypeError("Invalid inputs: " + error_message);
+    return;
+  }
+  if (!ValidateNamedMLBuffers(Context(), outputs, output_resources_info_,
+                              error_message)) {
+    exception_state.ThrowTypeError("Invalid outputs: " + error_message);
+    return;
+  }
+
+  if (!ValidateMLBufferUsage(inputs, outputs, error_message)) {
+    exception_state.ThrowTypeError("Invalid dispatch: " + error_message);
+    return;
+  }
+
+  // Call DispatchImpl() implemented by an MLGraph backend.
+  DispatchImpl(std::move(scoped_trace), inputs, outputs, exception_state);
+}
+
 void MLGraph::Build(ScopedMLTrace scoped_trace,
                     const MLNamedOperands& named_outputs,
                     ScriptPromiseResolver<MLGraph>* resolver) {
diff --git a/third_party/blink/renderer/modules/ml/webnn/ml_graph.h b/third_party/blink/renderer/modules/ml/webnn/ml_graph.h
index c890ae5..9e85072f 100644
--- a/third_party/blink/renderer/modules/ml/webnn/ml_graph.h
+++ b/third_party/blink/renderer/modules/ml/webnn/ml_graph.h
@@ -17,6 +17,7 @@
 #include "third_party/blink/renderer/platform/wtf/text/string_hash.h"
 
 namespace blink {
+class MLBuffer;
 class MLComputeResult;
 class MLContext;
 
@@ -25,6 +26,8 @@
 typedef HeapVector<std::pair<String, NotShared<DOMArrayBufferView>>>
     MLNamedArrayBufferViews;
 
+typedef HeapVector<std::pair<String, Member<MLBuffer>>> MLNamedBuffers;
+
 class MODULES_EXPORT MLGraph : public ScriptWrappable {
   DEFINE_WRAPPERTYPEINFO();
 
@@ -62,6 +65,17 @@
                ScriptPromiseResolver<MLComputeResult>* resolver,
                ExceptionState& exception_state);
 
+  // This method validates the input and output MLNamedBuffers against
+  // the graph's input and output resources info. If there are no errors, it
+  // passes the buffers to DispatchImpl() implemented by an MLGraph backend that
+  // binds the buffers and executes the compiled platform graph.
+  // This method is called by MLContext to implement MLContext.dispatch()
+  // method.
+  void Dispatch(ScopedMLTrace scoped_trace,
+                const MLNamedBuffers& inputs,
+                const MLNamedBuffers& outputs,
+                ExceptionState& exception_state);
+
   const MLContext* Context() const;
 
  protected:
@@ -104,6 +118,11 @@
                            ScriptPromiseResolver<MLComputeResult>* resolver,
                            ExceptionState& exception_state) = 0;
 
+  virtual void DispatchImpl(ScopedMLTrace scoped_trace,
+                            const MLNamedBuffers& inputs,
+                            const MLNamedBuffers& outputs,
+                            ExceptionState& exception_state) = 0;
+
   Member<MLContext> ml_context_;
   bool resources_info_initialized_{false};
   HashMap<String, ResourceInfo> input_resources_info_;
diff --git a/third_party/blink/renderer/modules/ml/webnn/ml_graph_builder_test.cc b/third_party/blink/renderer/modules/ml/webnn/ml_graph_builder_test.cc
index 729417f..64cbe664 100644
--- a/third_party/blink/renderer/modules/ml/webnn/ml_graph_builder_test.cc
+++ b/third_party/blink/renderer/modules/ml/webnn/ml_graph_builder_test.cc
@@ -1261,6 +1261,15 @@
                    ExceptionState& exception_state) override {
     resolver->Resolve();
   }
+
+  // Just return for testing the validation of inputs and outputs in
+  // MLGraph::Dispatch().
+  void DispatchImpl(ScopedMLTrace scoped_trace,
+                    const MLNamedBuffers& inputs,
+                    const MLNamedBuffers& outputs,
+                    ExceptionState& exception_state) override {
+    return;
+  }
 };
 
 FakeMLGraphBackend* ToFakeMLGraphBackend(V8TestingScope* scope,
diff --git a/third_party/blink/renderer/modules/ml/webnn/ml_graph_mojo.cc b/third_party/blink/renderer/modules/ml/webnn/ml_graph_mojo.cc
index ef5d264a..d9b5d4b0 100644
--- a/third_party/blink/renderer/modules/ml/webnn/ml_graph_mojo.cc
+++ b/third_party/blink/renderer/modules/ml/webnn/ml_graph_mojo.cc
@@ -10,6 +10,7 @@
 #include "third_party/blink/renderer/bindings/modules/v8/v8_ml_compute_result.h"
 #include "third_party/blink/renderer/core/execution_context/execution_context.h"
 #include "third_party/blink/renderer/modules/ml/ml.h"
+#include "third_party/blink/renderer/modules/ml/webnn/ml_buffer_mojo.h"
 #include "third_party/blink/renderer/modules/ml/webnn/ml_error_mojo.h"
 #include "third_party/blink/renderer/modules/ml/webnn/ml_graph_type_converter.h"
 #include "third_party/blink/renderer/modules/ml/webnn/ml_graph_utils.h"
@@ -196,6 +197,51 @@
                     std::move(inputs_info), std::move(outputs_info)));
 }
 
+void MLGraphMojo::DispatchImpl(ScopedMLTrace scoped_trace,
+                               const MLNamedBuffers& inputs,
+                               const MLNamedBuffers& outputs,
+                               ExceptionState& exception_state) {
+  // Remote graph gets automatically unbound when the execution context
+  // destructs.
+  if (!remote_graph_.is_bound()) {
+    exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError,
+                                      "Invalid graph state");
+    return;
+  }
+
+  // The inputs and outputs were already verified in the base class so we can
+  // pass the buffer directly with the input and output tensors.
+  HashMap<String, base::UnguessableToken> mojo_inputs;
+  for (const auto& [name, input_buffer] : inputs) {
+    // Remote buffer gets automatically unbound when the execution context
+    // destructs.
+    MLBufferMojo* ml_buffer_mojo =
+        static_cast<MLBufferMojo*>(input_buffer.Get());
+    if (!ml_buffer_mojo->is_bound()) {
+      exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError,
+                                        "Invalid input buffer state");
+      return;
+    }
+
+    mojo_inputs.insert(name, ml_buffer_mojo->handle());
+  }
+
+  HashMap<String, base::UnguessableToken> mojo_outputs;
+  for (const auto& [name, output_buffer] : outputs) {
+    MLBufferMojo* ml_buffer_mojo =
+        static_cast<MLBufferMojo*>(output_buffer.Get());
+    if (!ml_buffer_mojo->is_bound()) {
+      exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError,
+                                        "Invalid output buffer state");
+      return;
+    }
+
+    mojo_outputs.insert(name, ml_buffer_mojo->handle());
+  }
+
+  remote_graph_->Dispatch(std::move(mojo_inputs), std::move(mojo_outputs));
+}
+
 void MLGraphMojo::OnDidCompute(
     ScopedMLTrace scoped_trace,
     ScriptPromiseResolver<MLComputeResult>* resolver,
diff --git a/third_party/blink/renderer/modules/ml/webnn/ml_graph_mojo.h b/third_party/blink/renderer/modules/ml/webnn/ml_graph_mojo.h
index 153bc05..2fd59c4c 100644
--- a/third_party/blink/renderer/modules/ml/webnn/ml_graph_mojo.h
+++ b/third_party/blink/renderer/modules/ml/webnn/ml_graph_mojo.h
@@ -48,6 +48,12 @@
                    const MLNamedArrayBufferViews& outputs,
                    ScriptPromiseResolver<MLComputeResult>* resolver,
                    ExceptionState& exception_state) override;
+
+  void DispatchImpl(ScopedMLTrace scoped_trace,
+                    const MLNamedBuffers& inputs,
+                    const MLNamedBuffers& outputs,
+                    ExceptionState& exception_state) override;
+
   // The callback of computing `WebNNGraph` by calling hardware accelerated OS
   // machine learning APIs.
   void OnDidCompute(
diff --git a/third_party/blink/renderer/modules/ml/webnn/ml_graph_mojo_test.cc b/third_party/blink/renderer/modules/ml/webnn/ml_graph_mojo_test.cc
index d42c0fe0..5de8f14f 100644
--- a/third_party/blink/renderer/modules/ml/webnn/ml_graph_mojo_test.cc
+++ b/third_party/blink/renderer/modules/ml/webnn/ml_graph_mojo_test.cc
@@ -130,6 +130,12 @@
         blink_mojom::ComputeResult::NewNamedOutputs(std::move(mojo_outputs)));
   }
 
+  // Just return for testing the validation of inputs and outputs.
+  void Dispatch(
+      const HashMap<WTF::String, base::UnguessableToken>& named_inputs,
+      const HashMap<WTF::String, base::UnguessableToken>& named_outputs)
+      override {}
+
   const raw_ref<MLGraphTestMojo, DanglingUntriaged> helper_;
 };
 
@@ -355,6 +361,39 @@
   return IsBufferDataEqual(array_buffer, expected_data);
 }
 
+MLBuffer* CreateMLBufferForOperand(V8TestingScope& scope,
+                                   MLContext* ml_context,
+                                   const MLOperand* operand) {
+  auto array_buffer_view = CreateArrayBufferViewForOperand(operand);
+  auto* desc = MLBufferDescriptor::Create();
+  desc->setSize(array_buffer_view->byteLength());
+
+  MLBuffer* ml_buffer = ml_context->createBuffer(scope.GetScriptState(), desc,
+                                                 scope.GetExceptionState());
+  ml_context->writeBuffer(
+      scope.GetScriptState(), ml_buffer,
+      MaybeShared<DOMArrayBufferView>(array_buffer_view.Get()),
+      /*src_element_offset=*/0, scope.GetExceptionState());
+  return ml_buffer;
+}
+
+Vector<uint8_t> GetMLBufferValues(V8TestingScope& scope,
+                                  MLContext* ml_context,
+                                  MLBuffer* ml_buffer) {
+  ScriptPromiseTester tester(
+      scope.GetScriptState(),
+      ml_context->readBuffer(scope.GetScriptState(), ml_buffer,
+                             scope.GetExceptionState()));
+  tester.WaitUntilSettled();
+  if (tester.IsRejected()) {
+    return {};
+  }
+  auto* array_buffer = V8ToObject<DOMArrayBuffer>(&scope, tester.Value());
+  return GetArrayBufferViewValues<uint8_t>(
+      NotShared<DOMArrayBufferView>(blink::DOMUint8Array::Create(
+          array_buffer, /*byte_offset=*/0, ml_buffer->size())));
+}
+
 TEST_P(MLGraphTestMojo, CreateWebNNBufferTest) {
   V8TestingScope scope;
   // Bind fake WebNN Context in the service for testing.
@@ -556,6 +595,76 @@
   EXPECT_TRUE(buffer_tester.IsRejected());
 }
 
+TEST_P(MLGraphTestMojo, WebNNGraphDispatchTest) {
+  V8TestingScope scope;
+  // Bind fake WebNN Context in the service for testing.
+  ScopedWebNNServiceBinder scoped_setup_binder(*this, scope);
+
+  auto* options = MLContextOptions::Create();
+  // Create WebNN Context with GPU device type.
+  options->setDeviceType(V8MLDeviceType::Enum::kGpu);
+  auto* builder = CreateGraphBuilder(scope, options);
+  ASSERT_THAT(builder, testing::NotNull());
+  const Vector<uint32_t> dimensions = {3, 5};
+  const wtf_size_t number_of_elements = base::checked_cast<wtf_size_t>(
+      webnn::ValidateAndCalculateElementsNumber(dimensions).value());
+
+  // Build the graph.
+  auto* lhs_operand =
+      BuildInput(builder, "lhs", dimensions, V8MLOperandDataType::Enum::kUint8,
+                 scope.GetExceptionState());
+  auto* rhs_operand =
+      BuildInput(builder, "rhs", dimensions, V8MLOperandDataType::Enum::kUint8,
+                 scope.GetExceptionState());
+  auto* output_operand = BuildElementWiseBinary(
+      scope, builder, webnn::mojom::blink::ElementWiseBinary::Kind::kAdd,
+      lhs_operand, rhs_operand);
+  auto [graph, error_message, build_exception] =
+      BuildGraph(scope, builder, {{"output", output_operand}});
+  ASSERT_THAT(graph, testing::NotNull());
+
+  MLContext* ml_context = builder->GetContext();
+
+  // Check if MLBuffer is supported.
+  auto* desc = MLBufferDescriptor::Create();
+  desc->setSize(4ull);
+
+  MLBuffer* ml_buffer = ml_context->createBuffer(scope.GetScriptState(), desc,
+                                                 scope.GetExceptionState());
+
+  if (scope.GetExceptionState().Code() ==
+      ToExceptionCode(DOMExceptionCode::kNotSupportedError)) {
+    GTEST_SKIP() << "MLBuffer has not been implemented on this platform.";
+  }
+
+  ASSERT_THAT(ml_buffer, testing::NotNull());
+
+  MLNamedBuffers inputs(
+      {{"lhs", CreateMLBufferForOperand(scope, ml_context, lhs_operand)},
+       {"rhs", CreateMLBufferForOperand(scope, ml_context, rhs_operand)}});
+  MLNamedBuffers outputs({{"output", CreateMLBufferForOperand(
+                                         scope, ml_context, output_operand)}});
+
+  {
+    // Dispatch successfully.
+    ml_context->dispatch(scope.GetScriptState(), graph, inputs, outputs,
+                         scope.GetExceptionState());
+    EXPECT_EQ(scope.GetExceptionState().Code(),
+              ToExceptionCode(DOMExceptionCode::kNoError));
+    Vector<uint8_t> results =
+        GetMLBufferValues(scope, ml_context, outputs[0].second);
+    EXPECT_EQ(results, Vector<uint8_t>(number_of_elements, 0));
+
+    // Dispatch again successfully.
+    ml_context->dispatch(scope.GetScriptState(), graph, inputs, outputs,
+                         scope.GetExceptionState());
+    EXPECT_EQ(scope.GetExceptionState().Code(),
+              ToExceptionCode(DOMExceptionCode::kNoError));
+    results = GetMLBufferValues(scope, ml_context, outputs[0].second);
+    EXPECT_EQ(results, Vector<uint8_t>(number_of_elements, 0));
+  }
+}
+
 struct OperandInfoMojo {
   blink_mojom::Operand::DataType data_type;
   Vector<uint32_t> dimensions;
diff --git a/third_party/blink/renderer/modules/ml/webnn/ml_graph_xnnpack.cc b/third_party/blink/renderer/modules/ml/webnn/ml_graph_xnnpack.cc
index 0704c93c0..4cb8e35 100644
--- a/third_party/blink/renderer/modules/ml/webnn/ml_graph_xnnpack.cc
+++ b/third_party/blink/renderer/modules/ml/webnn/ml_graph_xnnpack.cc
@@ -2192,6 +2192,14 @@
                           resolver_task_runner_));
 }
 
+void MLGraphXnnpack::DispatchImpl(ScopedMLTrace scoped_trace,
+                                  const MLNamedBuffers& inputs,
+                                  const MLNamedBuffers& outputs,
+                                  ExceptionState& exception_state) {
+  exception_state.ThrowDOMException(DOMExceptionCode::kNotSupportedError,
+                                    "Not implemented");
+}
+
 // static
 void MLGraphXnnpack::ComputeOnBackgroundThread(
     ScopedMLTrace scoped_trace,
diff --git a/third_party/blink/renderer/modules/ml/webnn/ml_graph_xnnpack.h b/third_party/blink/renderer/modules/ml/webnn/ml_graph_xnnpack.h
index ff53cf3e8..efc434ce 100644
--- a/third_party/blink/renderer/modules/ml/webnn/ml_graph_xnnpack.h
+++ b/third_party/blink/renderer/modules/ml/webnn/ml_graph_xnnpack.h
@@ -141,6 +141,11 @@
                    ScriptPromiseResolver<MLComputeResult>* resolver,
                    ExceptionState& exception_state) override;
 
+  void DispatchImpl(ScopedMLTrace scoped_trace,
+                    const MLNamedBuffers& inputs,
+                    const MLNamedBuffers& outputs,
+                    ExceptionState& exception_state) override;
+
   // Invoking an XNNPACK Runtime object can be time-consuming. Calling this
   // method in a background thread avoids blocking the main thread. The
   // ownership of `input_buffers`, `external_values`, `inputs_info` and
diff --git a/third_party/blink/renderer/modules/shared_storage/private_aggregation.cc b/third_party/blink/renderer/modules/shared_storage/private_aggregation.cc
index 262e035..5ec350b 100644
--- a/third_party/blink/renderer/modules/shared_storage/private_aggregation.cc
+++ b/third_party/blink/renderer/modules/shared_storage/private_aggregation.cc
@@ -6,12 +6,14 @@
 
 #include <stdint.h>
 
+#include <bit>
 #include <iterator>
 #include <memory>
 #include <optional>
 #include <utility>
 
 #include "base/check.h"
+#include "base/feature_list.h"
 #include "base/ranges/algorithm.h"
 #include "third_party/abseil-cpp/absl/numeric/int128.h"
 #include "third_party/blink/public/common/features.h"
@@ -44,6 +46,8 @@
     "The \"private-aggregation\" Permissions Policy denied the method on "
     "privateAggregation";
 
+constexpr size_t kBitsPerByte = 8;
+
 }  // namespace
 
 PrivateAggregation::PrivateAggregation(
@@ -78,6 +82,7 @@
     return;
   }
 
+  // TODO(alexmt): Align error types with Protected Audience implementation.
   std::optional<absl::uint128> bucket = contribution->bucket().ToUInt128();
   if (!bucket) {
     exception_state.ThrowDOMException(
@@ -93,12 +98,42 @@
     return;
   }
 
+  std::optional<uint64_t> filtering_id;
+  if (contribution->hasFilteringId() &&
+      base::FeatureList::IsEnabled(
+          features::kPrivateAggregationApiFilteringIds)) {
+    EnsureFilteringIdUseCounterIsRecorded();
+    std::optional<absl::uint128> filtering_id_128 =
+        contribution->filteringId().ToUInt128();
+    if (!filtering_id_128 || absl::Uint128High64(*filtering_id_128) != 0) {
+      exception_state.ThrowDOMException(
+          DOMExceptionCode::kDataError,
+          "contribution['filteringId'] is negative or does not fit in byte "
+          "size");
+      return;
+    }
+    filtering_id = absl::Uint128Low64(*filtering_id_128);
+
+    int64_t operation_id = global_scope_->GetCurrentOperationId();
+    CHECK(base::Contains(operation_states_, operation_id));
+    OperationState* operation_state = operation_states_.at(operation_id);
+
+    if (static_cast<size_t>(std::bit_width(*filtering_id)) >
+        kBitsPerByte * operation_state->filtering_id_max_bytes) {
+      exception_state.ThrowDOMException(
+          DOMExceptionCode::kDataError,
+          "contribution['filteringId'] is negative or does not fit in byte "
+          "size");
+      return;
+    }
+  }
+
   // TODO(crbug.com/330744610): Allow filtering ID to be set.
   Vector<mojom::blink::AggregatableReportHistogramContributionPtr>
       mojom_contribution_vector;
   mojom_contribution_vector.push_back(
       mojom::blink::AggregatableReportHistogramContribution::New(
-          bucket.value(), value, /*filtering_id=*/std::nullopt));
+          bucket.value(), value, filtering_id));
 
   int64_t operation_id = global_scope_->GetCurrentOperationId();
   CHECK(operation_states_.Contains(operation_id));
@@ -169,13 +204,14 @@
 
 void PrivateAggregation::OnOperationStarted(
     int64_t operation_id,
-    mojo::PendingRemote<mojom::blink::PrivateAggregationHost>
-        private_aggregation_host) {
+    mojom::blink::PrivateAggregationOperationDetailsPtr pa_operation_details) {
   CHECK(!operation_states_.Contains(operation_id));
   auto map_it = operation_states_.insert(
-      operation_id, MakeGarbageCollected<OperationState>(global_scope_));
+      operation_id,
+      MakeGarbageCollected<OperationState>(
+          global_scope_, pa_operation_details->filtering_id_max_bytes));
   map_it.stored_value->value->private_aggregation_host.Bind(
-      std::move(private_aggregation_host),
+      std::move(pa_operation_details->pa_host),
       global_scope_->GetTaskRunner(blink::TaskType::kMiscPlatformAPI));
 }
 
@@ -217,4 +253,12 @@
   }
 }
 
+void PrivateAggregation::EnsureFilteringIdUseCounterIsRecorded() {
+  if (!has_recorded_filtering_id_use_counter_) {
+    has_recorded_filtering_id_use_counter_ = true;
+    global_scope_->GetSharedStorageWorkletServiceClient()->RecordUseCounters(
+        {mojom::blink::WebFeature::kPrivateAggregationApiFilteringIds});
+  }
+}
+
 }  // namespace blink
diff --git a/third_party/blink/renderer/modules/shared_storage/private_aggregation.h b/third_party/blink/renderer/modules/shared_storage/private_aggregation.h
index 0c4f03b..1de01d6f 100644
--- a/third_party/blink/renderer/modules/shared_storage/private_aggregation.h
+++ b/third_party/blink/renderer/modules/shared_storage/private_aggregation.h
@@ -12,6 +12,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-forward.h"
 #include "third_party/blink/renderer/modules/modules_export.h"
+#include "third_party/blink/renderer/modules/shared_storage/util.h"
 #include "third_party/blink/renderer/platform/bindings/script_wrappable.h"
 #include "third_party/blink/renderer/platform/context_lifecycle_notifier.h"
 #include "third_party/blink/renderer/platform/heap/collection_support/heap_hash_map.h"
@@ -32,10 +33,15 @@
 
  public:
   struct OperationState : public GarbageCollected<OperationState> {
-    explicit OperationState(ContextLifecycleNotifier* notifier)
-        : private_aggregation_host(notifier) {}
+    explicit OperationState(ContextLifecycleNotifier* notifier,
+                            size_t filtering_id_max_bytes)
+        : filtering_id_max_bytes(filtering_id_max_bytes),
+          private_aggregation_host(notifier) {
+      CHECK_LE(filtering_id_max_bytes, kMaximumFilteringIdMaxBytes);
+    }
 
     bool enable_debug_mode_called = false;
+    size_t filtering_id_max_bytes;
 
     // No need to be associated as message ordering (relative to shared storage
     // operations) is unimportant.
@@ -64,8 +70,7 @@
 
   void OnOperationStarted(
       int64_t operation_id,
-      mojo::PendingRemote<mojom::blink::PrivateAggregationHost>
-          private_aggregation_host);
+      mojom::blink::PrivateAggregationOperationDetailsPtr pa_operation_details);
   void OnOperationFinished(int64_t operation_id);
 
   void OnWorkletDestroyed();
@@ -73,9 +78,11 @@
  private:
   void EnsureGeneralUseCountersAreRecorded();
   void EnsureEnableDebugModeUseCounterIsRecorded();
+  void EnsureFilteringIdUseCounterIsRecorded();
 
   bool has_recorded_general_use_counters_ = false;
   bool has_recorded_enable_debug_mode_use_counter_ = false;
+  bool has_recorded_filtering_id_use_counter_ = false;
 
   Member<SharedStorageWorkletGlobalScope> global_scope_;
   HeapHashMap<int64_t,
diff --git a/third_party/blink/renderer/modules/shared_storage/private_aggregation_histogram_contribution.idl b/third_party/blink/renderer/modules/shared_storage/private_aggregation_histogram_contribution.idl
index a7563042..535f231 100644
--- a/third_party/blink/renderer/modules/shared_storage/private_aggregation_histogram_contribution.idl
+++ b/third_party/blink/renderer/modules/shared_storage/private_aggregation_histogram_contribution.idl
@@ -5,4 +5,5 @@
 dictionary PrivateAggregationHistogramContribution {
   required bigint bucket;
   required long value;
+  [RuntimeEnabled=PrivateAggregationApiFilteringIds] bigint filteringId;
 };
diff --git a/third_party/blink/renderer/modules/shared_storage/shared_storage_private_aggregation_config.idl b/third_party/blink/renderer/modules/shared_storage/shared_storage_private_aggregation_config.idl
index 0d2a9121..cd362e0a 100644
--- a/third_party/blink/renderer/modules/shared_storage/shared_storage_private_aggregation_config.idl
+++ b/third_party/blink/renderer/modules/shared_storage/shared_storage_private_aggregation_config.idl
@@ -1,4 +1,6 @@
 dictionary SharedStoragePrivateAggregationConfig {
   USVString aggregationCoordinatorOrigin;
   USVString contextId;
+  [EnforceRange, RuntimeEnabled=PrivateAggregationApiFilteringIds]
+  unsigned long long filteringIdMaxBytes;
 };
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 b49fb1b..46ea370 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
@@ -21,6 +21,7 @@
 #include "third_party/blink/public/common/shared_storage/module_script_downloader.h"
 #include "third_party/blink/public/common/shared_storage/shared_storage_utils.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-blink.h"
 #include "third_party/blink/public/platform/cross_variant_mojo_util.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"
@@ -361,8 +362,7 @@
     const String& name,
     const Vector<KURL>& urls,
     BlinkCloneableMessage serialized_data,
-    mojo::PendingRemote<mojom::blink::PrivateAggregationHost>
-        private_aggregation_host,
+    mojom::blink::PrivateAggregationOperationDetailsPtr pa_operation_details,
     RunURLSelectionOperationCallback callback) {
   String error_message;
   SharedStorageOperationDefinition* operation_definition = nullptr;
@@ -375,7 +375,7 @@
   }
 
   base::OnceClosure operation_completion_cb =
-      StartOperation(std::move(private_aggregation_host));
+      StartOperation(std::move(pa_operation_details));
   RunURLSelectionOperationCallback combined_operation_completion_cb =
       std::move(callback).Then(std::move(operation_completion_cb));
 
@@ -442,8 +442,7 @@
 void SharedStorageWorkletGlobalScope::RunOperation(
     const String& name,
     BlinkCloneableMessage serialized_data,
-    mojo::PendingRemote<mojom::blink::PrivateAggregationHost>
-        private_aggregation_host,
+    mojom::blink::PrivateAggregationOperationDetailsPtr pa_operation_details,
     RunOperationCallback callback) {
   String error_message;
   SharedStorageOperationDefinition* operation_definition = nullptr;
@@ -455,7 +454,7 @@
   }
 
   base::OnceClosure operation_completion_cb =
-      StartOperation(std::move(private_aggregation_host));
+      StartOperation(std::move(pa_operation_details));
   mojom::blink::SharedStorageWorkletService::RunOperationCallback
       combined_operation_completion_cb =
           std::move(callback).Then(std::move(operation_completion_cb));
@@ -678,10 +677,9 @@
 }
 
 base::OnceClosure SharedStorageWorkletGlobalScope::StartOperation(
-    mojo::PendingRemote<mojom::blink::PrivateAggregationHost>
-        private_aggregation_host) {
+    mojom::blink::PrivateAggregationOperationDetailsPtr pa_operation_details) {
   CHECK(add_module_finished_);
-  CHECK_EQ(!!private_aggregation_host,
+  CHECK_EQ(!!pa_operation_details,
            ShouldDefinePrivateAggregationInSharedStorage());
 
   int64_t operation_id = operation_counter_++;
@@ -697,7 +695,7 @@
 
   if (ShouldDefinePrivateAggregationInSharedStorage()) {
     GetOrCreatePrivateAggregation()->OnOperationStarted(
-        operation_id, std::move(private_aggregation_host));
+        operation_id, std::move(pa_operation_details));
   }
 
   return WTF::BindOnce(&SharedStorageWorkletGlobalScope::FinishOperation,
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 4da4116..6bbaaf09 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
@@ -109,14 +109,13 @@
       const String& name,
       const Vector<KURL>& urls,
       BlinkCloneableMessage serialized_data,
-      mojo::PendingRemote<mojom::blink::PrivateAggregationHost>
-          private_aggregation_host,
+      mojom::blink::PrivateAggregationOperationDetailsPtr pa_operation_details,
       RunURLSelectionOperationCallback callback) override;
-  void RunOperation(const String& name,
-                    BlinkCloneableMessage serialized_data,
-                    mojo::PendingRemote<mojom::blink::PrivateAggregationHost>
-                        private_aggregation_host,
-                    RunOperationCallback callback) override;
+  void RunOperation(
+      const String& name,
+      BlinkCloneableMessage serialized_data,
+      mojom::blink::PrivateAggregationOperationDetailsPtr pa_operation_details,
+      RunOperationCallback callback) override;
 
   // SharedStorageWorkletGlobalScope IDL
   SharedStorage* sharedStorage(ScriptState*, ExceptionState&);
@@ -169,8 +168,7 @@
   // particular operation invocation later, even after asynchronous operations.
   // Returns a closure that should be run when the operation finishes.
   base::OnceClosure StartOperation(
-      mojo::PendingRemote<mojom::blink::PrivateAggregationHost>
-          private_aggregation_host);
+      mojom::blink::PrivateAggregationOperationDetailsPtr pa_operation_details);
 
   // Notifies the `private_aggregation_` that the operation with the given ID
   // has finished.
@@ -232,7 +230,7 @@
 
   // Whether the "private-aggregation" permissions policy is enabled in the
   // worklet.
-  bool private_aggregation_permissions_policy_allowed_ = false;
+  bool private_aggregation_permissions_policy_allowed_;
 
   const SharedStorageWorkletToken token_;
 };
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 5246a69..c4d5462 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
@@ -2,6 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#include <limits>
 #include <memory>
 #include <optional>
 #include <string>
@@ -41,6 +42,7 @@
 #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"
 #include "third_party/blink/public/mojom/worker/worklet_global_scope_creation_params.mojom-blink.h"
+#include "third_party/blink/public/platform/cross_variant_mojo_util.h"
 #include "third_party/blink/public/platform/web_runtime_features.h"
 #include "third_party/blink/renderer/bindings/core/v8/v8_binding_for_core.h"
 #include "third_party/blink/renderer/core/messaging/blink_cloneable_message_mojom_traits.h"
@@ -49,6 +51,7 @@
 #include "third_party/blink/renderer/modules/shared_storage/shared_storage_worklet_messaging_proxy.h"
 #include "third_party/blink/renderer/modules/shared_storage/shared_storage_worklet_thread.h"
 #include "third_party/blink/renderer/platform/scheduler/public/post_cross_thread_task.h"
+#include "third_party/blink/renderer/platform/testing/runtime_enabled_features_test_helpers.h"
 #include "v8/include/v8-isolate.h"
 
 namespace blink {
@@ -215,8 +218,10 @@
   }
 
   void RecordUseCounters(
-      const std::vector<blink::mojom::WebFeature>& features) override {
-    observed_use_counters_.push_back(features);
+      const std::vector<mojom::WebFeature>& features) override {
+    base::ranges::for_each(features, [&](mojom::WebFeature feature) {
+      observed_use_counters_.push_back(feature);
+    });
   }
 
   mojo::Remote<blink::mojom::SharedStorageEntriesListener>
@@ -255,7 +260,7 @@
   size_t observed_length_count_ = 0;
   size_t observed_remaining_budget_count_ = 0;
   std::vector<std::string> observed_console_log_messages_;
-  std::vector<std::vector<blink::mojom::WebFeature>> observed_use_counters_;
+  std::vector<mojom::WebFeature> observed_use_counters_;
 
   // Default results to be returned for corresponding operations. They can be
   // overridden.
@@ -374,45 +379,52 @@
 
     base::test::TestFuture<bool, const std::string&, uint32_t> future;
     shared_storage_worklet_service_->RunURLSelectionOperation(
-        name, urls, std::move(serialized_data), MaybeInitNewRemotePAHost(),
+        name, urls, std::move(serialized_data), MaybeInitPAOperationDetails(),
         future.GetCallback());
 
     return {future.Get<0>(), future.Get<1>(), future.Get<2>()};
   }
 
   RunResult Run(const std::string& name,
-                blink::CloneableMessage serialized_data) {
+                blink::CloneableMessage serialized_data,
+                int filtering_id_max_bytes = 1) {
     InitializeWorkletServiceOnce();
 
     base::test::TestFuture<bool, const std::string&> future;
     shared_storage_worklet_service_->RunOperation(
-        name, std::move(serialized_data), MaybeInitNewRemotePAHost(),
+        name, std::move(serialized_data),
+        MaybeInitPAOperationDetails(filtering_id_max_bytes),
         future.GetCallback());
 
     return {future.Get<0>(), future.Get<1>()};
   }
 
-  CrossVariantMojoRemote<mojom::blink::PrivateAggregationHostInterfaceBase>
-  MaybeInitNewRemotePAHost() {
+  // CrossVariantMojoRemote<mojom::blink::PrivateAggregationHostInterfaceBase>
+  mojom::PrivateAggregationOperationDetailsPtr MaybeInitPAOperationDetails(
+      int filtering_id_max_bytes =
+          kPrivateAggregationApiDefaultFilteringIdMaxBytes) {
     CHECK_EQ(ShouldDefinePrivateAggregationInSharedStorage(),
              !!mock_private_aggregation_host_);
 
-    mojo::PendingRemote<mojom::blink::PrivateAggregationHost>
-        pending_pa_host_remote;
-
-    if (ShouldDefinePrivateAggregationInSharedStorage()) {
-      mojo::PendingReceiver<mojom::blink::PrivateAggregationHost>
-          pending_pa_host_receiver =
-              pending_pa_host_remote.InitWithNewPipeAndPassReceiver();
-
-      mock_private_aggregation_host_->receiver_set().Add(
-          mock_private_aggregation_host_.get(),
-          std::move(pending_pa_host_receiver));
+    if (!ShouldDefinePrivateAggregationInSharedStorage()) {
+      return nullptr;
     }
 
-    return CrossVariantMojoRemote<
-        mojom::blink::PrivateAggregationHostInterfaceBase>(
-        std::move(pending_pa_host_remote));
+    mojo::PendingRemote<mojom::blink::PrivateAggregationHost>
+        pending_pa_host_remote;
+    mojo::PendingReceiver<mojom::blink::PrivateAggregationHost>
+        pending_pa_host_receiver =
+            pending_pa_host_remote.InitWithNewPipeAndPassReceiver();
+
+    mock_private_aggregation_host_->receiver_set().Add(
+        mock_private_aggregation_host_.get(),
+        std::move(pending_pa_host_receiver));
+
+    return mojom::PrivateAggregationOperationDetails::New(
+        CrossVariantMojoRemote<
+            mojom::blink::PrivateAggregationHostInterfaceBase>(
+            std::move(pending_pa_host_remote)),
+        filtering_id_max_bytes);
   }
 
   CloneableMessage CreateSerializedUndefined() {
@@ -2289,8 +2301,8 @@
 
   base::test::TestFuture<bool, const std::string&> run_future;
   shared_storage_worklet_service_->RunOperation(
-      "test-operation", CreateSerializedUndefined(), MaybeInitNewRemotePAHost(),
-      run_future.GetCallback());
+      "test-operation", CreateSerializedUndefined(),
+      MaybeInitPAOperationDetails(), run_future.GetCallback());
   shared_storage_worklet_service_.FlushForTesting();
 
   EXPECT_FALSE(run_future.IsReady());
@@ -2323,8 +2335,8 @@
 
   base::test::TestFuture<bool, const std::string&> run_future;
   shared_storage_worklet_service_->RunOperation(
-      "test-operation", CreateSerializedUndefined(), MaybeInitNewRemotePAHost(),
-      run_future.GetCallback());
+      "test-operation", CreateSerializedUndefined(),
+      MaybeInitPAOperationDetails(), run_future.GetCallback());
   shared_storage_worklet_service_.FlushForTesting();
 
   EXPECT_FALSE(run_future.IsReady());
@@ -2359,8 +2371,8 @@
 
   base::test::TestFuture<bool, const std::string&> run_future;
   shared_storage_worklet_service_->RunOperation(
-      "test-operation", CreateSerializedUndefined(), MaybeInitNewRemotePAHost(),
-      run_future.GetCallback());
+      "test-operation", CreateSerializedUndefined(),
+      MaybeInitPAOperationDetails(), run_future.GetCallback());
   shared_storage_worklet_service_.FlushForTesting();
 
   EXPECT_FALSE(run_future.IsReady());
@@ -2406,8 +2418,8 @@
 
   base::test::TestFuture<bool, const std::string&> run_future;
   shared_storage_worklet_service_->RunOperation(
-      "test-operation", CreateSerializedUndefined(), MaybeInitNewRemotePAHost(),
-      run_future.GetCallback());
+      "test-operation", CreateSerializedUndefined(),
+      MaybeInitPAOperationDetails(), run_future.GetCallback());
   shared_storage_worklet_service_.FlushForTesting();
 
   EXPECT_FALSE(run_future.IsReady());
@@ -2452,8 +2464,8 @@
 
   base::test::TestFuture<bool, const std::string&> run_future;
   shared_storage_worklet_service_->RunOperation(
-      "test-operation", CreateSerializedUndefined(), MaybeInitNewRemotePAHost(),
-      run_future.GetCallback());
+      "test-operation", CreateSerializedUndefined(),
+      MaybeInitPAOperationDetails(), run_future.GetCallback());
   shared_storage_worklet_service_.FlushForTesting();
 
   EXPECT_FALSE(run_future.IsReady());
@@ -2502,8 +2514,8 @@
 
   base::test::TestFuture<bool, const std::string&> run_future;
   shared_storage_worklet_service_->RunOperation(
-      "test-operation", CreateSerializedUndefined(), MaybeInitNewRemotePAHost(),
-      run_future.GetCallback());
+      "test-operation", CreateSerializedUndefined(),
+      MaybeInitPAOperationDetails(), run_future.GetCallback());
   shared_storage_worklet_service_.FlushForTesting();
 
   EXPECT_FALSE(run_future.IsReady());
@@ -2575,8 +2587,8 @@
 
   base::test::TestFuture<bool, const std::string&> run_future;
   shared_storage_worklet_service_->RunOperation(
-      "test-operation", CreateSerializedUndefined(), MaybeInitNewRemotePAHost(),
-      run_future.GetCallback());
+      "test-operation", CreateSerializedUndefined(),
+      MaybeInitPAOperationDetails(), run_future.GetCallback());
   shared_storage_worklet_service_.FlushForTesting();
 
   EXPECT_FALSE(run_future.IsReady());
@@ -3097,6 +3109,8 @@
       int expected_value,
       mojom::blink::DebugModeDetailsPtr expected_debug_mode_details =
           mojom::blink::DebugModeDetails::New(),
+      std::optional<uint64_t> filtering_id = std::nullopt,
+      int filtering_id_max_bytes = 1,
       std::string* error_message = nullptr) {
     AddModuleResult add_module_result =
         AddModule(/*script_content=*/base::StrCat(
@@ -3119,7 +3133,8 @@
           }));
     }
 
-    RunResult run_result = Run("test-operation", CreateSerializedUndefined());
+    RunResult run_result = Run("test-operation", CreateSerializedUndefined(),
+                               filtering_id_max_bytes);
 
     EXPECT_EQ(run_result.success, (error_message == nullptr));
 
@@ -3127,31 +3142,28 @@
       *error_message = run_result.error_message;
     }
 
-    // Use counters are recorded.
+    std::vector<mojom::WebFeature> expected_use_counters = {
+        mojom::WebFeature::kPrivateAggregationApiAll,
+        mojom::WebFeature::kPrivateAggregationApiSharedStorage};
     if (expected_debug_mode_details->is_enabled) {
-      EXPECT_THAT(test_client_->observed_use_counters_,
-                  testing::UnorderedElementsAre(
-                      testing::UnorderedElementsAre(
-                          blink::mojom::WebFeature::kPrivateAggregationApiAll,
-                          blink::mojom::WebFeature::
-                              kPrivateAggregationApiSharedStorage),
-                      testing::UnorderedElementsAre(
-                          blink::mojom::WebFeature::
-                              kPrivateAggregationApiEnableDebugMode)));
-    } else {
-      EXPECT_EQ(test_client_->observed_use_counters_.size(), 1u);
-      EXPECT_THAT(
-          test_client_->observed_use_counters_[0],
-          testing::UnorderedElementsAre(
-              blink::mojom::WebFeature::kPrivateAggregationApiAll,
-              blink::mojom::WebFeature::kPrivateAggregationApiSharedStorage));
+      expected_use_counters.push_back(
+          mojom::WebFeature::kPrivateAggregationApiEnableDebugMode);
     }
+    if (filtering_id.has_value()) {
+      expected_use_counters.push_back(
+          mojom::WebFeature::kPrivateAggregationApiFilteringIds);
+    }
+
+    EXPECT_THAT(test_client_->observed_use_counters_,
+                testing::UnorderedElementsAreArray(expected_use_counters));
 
     mock_private_aggregation_host_->FlushForTesting();
   }
 
-  std::string ExecuteScriptReturningError(const std::string& script_body,
-                                          bool expect_use_counter) {
+  std::string ExecuteScriptReturningError(
+      const std::string& script_body,
+      std::vector<mojom::WebFeature> expect_use_counters = {},
+      int filtering_id_max_bytes = 1) {
     AddModuleResult add_module_result =
         AddModule(/*script_content=*/base::StrCat(
             {"class TestClass { async run() {", script_body,
@@ -3166,19 +3178,12 @@
       EXPECT_CALL(*mock_private_aggregation_host_, EnableDebugMode).Times(0);
     }
 
-    RunResult run_result = Run("test-operation", CreateSerializedUndefined());
+    RunResult run_result = Run("test-operation", CreateSerializedUndefined(),
+                               filtering_id_max_bytes);
     EXPECT_FALSE(run_result.success);
 
-    if (expect_use_counter) {
-      EXPECT_EQ(test_client_->observed_use_counters_.size(), 1u);
-      EXPECT_THAT(
-          test_client_->observed_use_counters_[0],
-          testing::UnorderedElementsAre(
-              blink::mojom::WebFeature::kPrivateAggregationApiAll,
-              blink::mojom::WebFeature::kPrivateAggregationApiSharedStorage));
-    } else {
-      EXPECT_EQ(test_client_->observed_use_counters_.size(), 0u);
-    }
+    EXPECT_THAT(test_client_->observed_use_counters_,
+                testing::UnorderedElementsAreArray(expect_use_counters));
 
     if (mock_private_aggregation_host_) {
       mock_private_aggregation_host_->FlushForTesting();
@@ -3297,7 +3302,9 @@
 
   std::string error_str = ExecuteScriptReturningError(
       "privateAggregation.contributeToHistogram({bucket: 1n, value: 2});",
-      /*expect_use_counter=*/true);
+      /*expect_use_counters=*/{
+          mojom::WebFeature::kPrivateAggregationApiAll,
+          mojom::WebFeature::kPrivateAggregationApiSharedStorage});
 
   EXPECT_THAT(error_str, testing::HasSubstr(
                              "The \"private-aggregation\" Permissions Policy "
@@ -3308,7 +3315,9 @@
   std::string error_str = ExecuteScriptReturningError(
       "privateAggregation.contributeToHistogram({bucket: "
       "340282366920938463463374607431768211456n, value: 2});",
-      /*expect_use_counter=*/true);
+      /*expect_use_counters=*/{
+          mojom::WebFeature::kPrivateAggregationApiAll,
+          mojom::WebFeature::kPrivateAggregationApiSharedStorage});
 
   EXPECT_THAT(
       error_str,
@@ -3319,7 +3328,9 @@
 TEST_F(SharedStoragePrivateAggregationTest, NegativeBucket_Rejected) {
   std::string error_str = ExecuteScriptReturningError(
       "privateAggregation.contributeToHistogram({bucket: -1n, value: 2});",
-      /*expect_use_counter=*/true);
+      /*expect_use_counters=*/{
+          mojom::WebFeature::kPrivateAggregationApiAll,
+          mojom::WebFeature::kPrivateAggregationApiSharedStorage});
 
   EXPECT_THAT(
       error_str,
@@ -3330,7 +3341,7 @@
 TEST_F(SharedStoragePrivateAggregationTest, NonBigIntBucket_Rejected) {
   std::string error_str = ExecuteScriptReturningError(
       "privateAggregation.contributeToHistogram({bucket: 1, value: 2});",
-      /*expect_use_counter=*/false);
+      /*expect_use_counters=*/{});
 
   EXPECT_THAT(error_str, testing::HasSubstr("Cannot convert 1 to a BigInt"));
 }
@@ -3338,7 +3349,9 @@
 TEST_F(SharedStoragePrivateAggregationTest, NegativeValue_Rejected) {
   std::string error_str = ExecuteScriptReturningError(
       "privateAggregation.contributeToHistogram({bucket: 1n, value: -1});",
-      /*expect_use_counter=*/true);
+      /*expect_use_counters=*/{
+          mojom::WebFeature::kPrivateAggregationApiAll,
+          mojom::WebFeature::kPrivateAggregationApiSharedStorage});
 
   EXPECT_THAT(error_str,
               testing::HasSubstr("contribution['value'] is negative"));
@@ -3349,7 +3362,7 @@
   // The debug key is not wrapped in a dictionary.
   std::string error_str =
       ExecuteScriptReturningError("privateAggregation.enableDebugMode(1234n);",
-                                  /*expect_use_counter=*/false);
+                                  /*expect_use_counters=*/{});
 
   EXPECT_THAT(error_str,
               testing::HasSubstr("The provided value is not of type "
@@ -3380,7 +3393,8 @@
       mojom::blink::DebugModeDetails::New(
           /*is_enabled=*/true,
           /*debug_key=*/mojom::blink::DebugKey::New(1234u)),
-      &error_str);
+      /*filtering_id=*/std::nullopt,
+      /*filtering_id_max_bytes=*/1, &error_str);
 
   EXPECT_THAT(error_str,
               testing::HasSubstr("enableDebugMode may be called at most once"));
@@ -3526,8 +3540,8 @@
           }));
 
   shared_storage_worklet_service_->RunOperation(
-      "test-operation", CreateSerializedUndefined(), MaybeInitNewRemotePAHost(),
-      base::DoNothing());
+      "test-operation", CreateSerializedUndefined(),
+      MaybeInitPAOperationDetails(), base::DoNothing());
 
   // Trigger the disconnect handler.
   shared_storage_worklet_service_.reset();
@@ -3538,6 +3552,215 @@
   run_loop.Run();
 }
 
+class SharedStoragePrivateAggregationFilteringIdTest
+    : public SharedStoragePrivateAggregationTest {
+ public:
+  SharedStoragePrivateAggregationFilteringIdTest() = default;
+
+ private:
+  // The features are not necessarily synchronized in the unit test, so we
+  // enable both.
+  base::test::ScopedFeatureList scoped_base_feature_{
+      features::kPrivateAggregationApiFilteringIds};
+  ScopedPrivateAggregationApiFilteringIdsForTest scoped_rte_feature{
+      /*enabled=*/true};
+};
+
+TEST_F(SharedStoragePrivateAggregationFilteringIdTest, BasicFilteringId) {
+  ExecuteScriptAndValidateContribution(
+      "privateAggregation.contributeToHistogram("
+      "{bucket: 1n, value: 2, filteringId: 3n});",
+      /*expected_bucket=*/1, /*expected_value=*/2,
+      /*expected_debug_mode_details=*/mojom::blink::DebugModeDetails::New(),
+      /*filtering_id=*/3);
+}
+
+TEST_F(SharedStoragePrivateAggregationFilteringIdTest,
+       FilteringIdWithDebugMode) {
+  ExecuteScriptAndValidateContribution(
+      R"(privateAggregation.enableDebugMode();
+         privateAggregation.contributeToHistogram(
+             {bucket: 1n, value: 2, filteringId: 3n});)",
+      /*expected_bucket=*/1, /*expected_value=*/2,
+      /*expected_debug_mode_details=*/
+      mojom::blink::DebugModeDetails::New(/*is_enabled=*/true,
+                                          /*debug_key=*/nullptr),
+      /*filtering_id=*/3);
+}
+
+TEST_F(SharedStoragePrivateAggregationFilteringIdTest,
+       NoFilteringIdSpecified_FilteringIdNull) {
+  ExecuteScriptAndValidateContribution(
+      "privateAggregation.contributeToHistogram({bucket: 1n, value: 2});",
+      /*expected_bucket=*/1, /*expected_value=*/2,
+      /*expected_debug_mode_details=*/mojom::blink::DebugModeDetails::New(),
+      /*filtering_id=*/std::nullopt);
+}
+
+TEST_F(SharedStoragePrivateAggregationFilteringIdTest,
+       ExplicitDefaultFilteringId_FilteringIdNotNull) {
+  ExecuteScriptAndValidateContribution(
+      "privateAggregation.contributeToHistogram("
+      "{bucket: 1n, value: 2, filteringId: 0n});",
+      /*expected_bucket=*/1, /*expected_value=*/2,
+      /*expected_debug_mode_details=*/mojom::blink::DebugModeDetails::New(),
+      /*filtering_id=*/0);
+}
+
+TEST_F(SharedStoragePrivateAggregationFilteringIdTest,
+       MaxFilteringIdForByteSize_Success) {
+  ExecuteScriptAndValidateContribution(
+      "privateAggregation.contributeToHistogram("
+      "{bucket: 1n, value: 2, filteringId: 255n});",
+      /*expected_bucket=*/1, /*expected_value=*/2,
+      /*expected_debug_mode_details=*/mojom::blink::DebugModeDetails::New(),
+      /*filtering_id=*/255);
+}
+
+TEST_F(SharedStoragePrivateAggregationFilteringIdTest,
+       FilteringIdTooBigForByteSize_Error) {
+  std::string error_str = ExecuteScriptReturningError(
+      "privateAggregation.contributeToHistogram("
+      "{bucket: 1n, value: 2, filteringId: 256n});",
+      /*expect_use_counters=*/{
+          mojom::WebFeature::kPrivateAggregationApiAll,
+          mojom::WebFeature::kPrivateAggregationApiSharedStorage,
+          mojom::WebFeature::kPrivateAggregationApiFilteringIds});
+
+  EXPECT_THAT(error_str,
+              testing::HasSubstr("contribution['filteringId'] is negative or "
+                                 "does not fit in byte size"));
+}
+
+TEST_F(SharedStoragePrivateAggregationFilteringIdTest,
+       FilteringIdNegative_Error) {
+  std::string error_str = ExecuteScriptReturningError(
+      "privateAggregation.contributeToHistogram("
+      "{bucket: 1n, value: 2, filteringId: -1n});",
+      /*expect_use_counters=*/{
+          mojom::WebFeature::kPrivateAggregationApiAll,
+          mojom::WebFeature::kPrivateAggregationApiSharedStorage,
+          mojom::WebFeature::kPrivateAggregationApiFilteringIds});
+
+  EXPECT_THAT(error_str,
+              testing::HasSubstr("contribution['filteringId'] is negative or "
+                                 "does not fit in byte size"));
+}
+
+TEST_F(SharedStoragePrivateAggregationFilteringIdTest,
+       NoFilteringIdWithCustomByteSize) {
+  ExecuteScriptAndValidateContribution(
+      "privateAggregation.contributeToHistogram({bucket: 1n, value: 2});",
+      /*expected_bucket=*/1, /*expected_value=*/2,
+      /*expected_debug_mode_details=*/mojom::blink::DebugModeDetails::New(),
+      /*filtering_id=*/std::nullopt, /*filtering_id_max_bytes=*/3);
+}
+
+TEST_F(SharedStoragePrivateAggregationFilteringIdTest,
+       FilteringIdWithCustomByteSize_Success) {
+  ExecuteScriptAndValidateContribution(
+      "privateAggregation.contributeToHistogram("
+      "{bucket: 1n, value: 2, filteringId: 3n});",
+      /*expected_bucket=*/1, /*expected_value=*/2,
+      /*expected_debug_mode_details=*/mojom::blink::DebugModeDetails::New(),
+      /*filtering_id=*/3, /*filtering_id_max_bytes=*/3);
+}
+
+TEST_F(SharedStoragePrivateAggregationFilteringIdTest,
+       MaxFilteringIdWithCustomByteSize_Success) {
+  ExecuteScriptAndValidateContribution(
+      "privateAggregation.contributeToHistogram("
+      "{bucket: 1n, value: 2, filteringId: 16777215n});",
+      /*expected_bucket=*/1, /*expected_value=*/2,
+      /*expected_debug_mode_details=*/mojom::blink::DebugModeDetails::New(),
+      /*filtering_id=*/16777215, /*filtering_id_max_bytes=*/3);
+}
+
+TEST_F(SharedStoragePrivateAggregationFilteringIdTest,
+       TooBigFilteringIdWithCustomByteSize_Error) {
+  std::string error_str = ExecuteScriptReturningError(
+      "privateAggregation.contributeToHistogram("
+      "{bucket: 1n, value: 2, filteringId: 16777216n});",
+      /*expect_use_counters=*/
+      {mojom::WebFeature::kPrivateAggregationApiAll,
+       mojom::WebFeature::kPrivateAggregationApiSharedStorage,
+       mojom::WebFeature::kPrivateAggregationApiFilteringIds},
+      /*filtering_id_max_bytes=*/3);
+
+  EXPECT_THAT(error_str,
+              testing::HasSubstr("contribution['filteringId'] is negative or "
+                                 "does not fit in byte size"));
+}
+
+TEST_F(SharedStoragePrivateAggregationFilteringIdTest, MaxPossibleFilteringId) {
+  ExecuteScriptAndValidateContribution(
+      "privateAggregation.contributeToHistogram("
+      "{bucket: 1n, value: 2, filteringId: (1n << 64n) - 1n});",
+      /*expected_bucket=*/1, /*expected_value=*/2,
+      /*expected_debug_mode_details=*/mojom::blink::DebugModeDetails::New(),
+      /*filtering_id=*/std::numeric_limits<uint64_t>::max(),
+      /*filtering_id_max_bytes=*/8);
+}
+
+TEST_F(SharedStoragePrivateAggregationFilteringIdTest,
+       TooBigFilteringIdWithMaxByteSize_Error) {
+  std::string error_str = ExecuteScriptReturningError(
+      "privateAggregation.contributeToHistogram("
+      "{bucket: 1n, value: 2, filteringId: (1n << 64n)});",
+      /*expect_use_counters=*/
+      {mojom::WebFeature::kPrivateAggregationApiAll,
+       mojom::WebFeature::kPrivateAggregationApiSharedStorage,
+       mojom::WebFeature::kPrivateAggregationApiFilteringIds},
+      /*filtering_id_max_bytes=*/8);
+
+  EXPECT_THAT(error_str,
+              testing::HasSubstr("contribution['filteringId'] is negative or "
+                                 "does not fit in byte size"));
+}
+
+class SharedStoragePrivateAggregationFilteringIdDisabledTest
+    : public SharedStoragePrivateAggregationTest {
+ public:
+  SharedStoragePrivateAggregationFilteringIdDisabledTest() {
+    scoped_base_feature_.InitAndDisableFeature(
+        features::kPrivateAggregationApiFilteringIds);
+  }
+
+ private:
+  // The features are not necessarily synchronized in the unit test, so we
+  // disable both.
+  base::test::ScopedFeatureList scoped_base_feature_;
+  ScopedPrivateAggregationApiFilteringIdsForTest scoped_rte_feature{
+      /*enabled=*/false};
+};
+
+TEST_F(SharedStoragePrivateAggregationFilteringIdDisabledTest,
+       ValidFilteringId_Ignored) {
+  ExecuteScriptAndValidateContribution(
+      "privateAggregation.contributeToHistogram("
+      "{bucket: 1n, value: 2, filteringId: 3n});",
+      /*expected_bucket=*/1, /*expected_value=*/2,
+      /*expected_debug_mode_details=*/mojom::blink::DebugModeDetails::New(),
+      /*filtering_id=*/std::nullopt);
+}
+TEST_F(SharedStoragePrivateAggregationFilteringIdDisabledTest,
+       InvalidFilteringId_Ignored) {
+  ExecuteScriptAndValidateContribution(
+      "privateAggregation.contributeToHistogram("
+      "{bucket: 1n, value: 2, filteringId: -1});",
+      /*expected_bucket=*/1, /*expected_value=*/2,
+      /*expected_debug_mode_details=*/mojom::blink::DebugModeDetails::New(),
+      /*filtering_id=*/std::nullopt);
+}
+TEST_F(SharedStoragePrivateAggregationFilteringIdDisabledTest,
+       CustomFilteringIdMaxBytes_Ignored) {
+  ExecuteScriptAndValidateContribution(
+      "privateAggregation.contributeToHistogram({bucket: 1n, value: 2});",
+      /*expected_bucket=*/1, /*expected_value=*/2,
+      /*expected_debug_mode_details=*/mojom::blink::DebugModeDetails::New(),
+      /*filtering_id=*/std::nullopt, /*filtering_id_max_bytes=*/3);
+}
+
 class SharedStorageWorkletThreadTest : public testing::Test {};
 
 // Assert that each `SharedStorageWorkletThread` owns a dedicated
diff --git a/third_party/blink/renderer/modules/shared_storage/util.cc b/third_party/blink/renderer/modules/shared_storage/util.cc
index b9799a4..f8c642d 100644
--- a/third_party/blink/renderer/modules/shared_storage/util.cc
+++ b/third_party/blink/renderer/modules/shared_storage/util.cc
@@ -4,6 +4,7 @@
 
 #include "third_party/blink/renderer/modules/shared_storage/util.h"
 
+#include "base/feature_list.h"
 #include "base/memory/scoped_refptr.h"
 #include "components/aggregation_service/aggregation_coordinator_utils.h"
 #include "components/aggregation_service/features.h"
@@ -97,6 +98,10 @@
   WTF::String& out_context_id = out_private_aggregation_config->context_id;
   scoped_refptr<const SecurityOrigin>& out_aggregation_coordinator_origin =
       out_private_aggregation_config->aggregation_coordinator_origin;
+  uint32_t& out_filtering_id_max_bytes =
+      out_private_aggregation_config->filtering_id_max_bytes;
+
+  out_filtering_id_max_bytes = kPrivateAggregationApiDefaultFilteringIdMaxBytes;
 
   if (!options.hasPrivateAggregationConfig()) {
     return true;
@@ -138,6 +143,26 @@
     out_aggregation_coordinator_origin = parsed_coordinator;
   }
 
+  if (options.privateAggregationConfig()->hasFilteringIdMaxBytes() &&
+      base::FeatureList::IsEnabled(
+          features::kPrivateAggregationApiFilteringIds)) {
+    if (options.privateAggregationConfig()->filteringIdMaxBytes() < 1) {
+      resolver.Reject(V8ThrowDOMException::CreateOrEmpty(
+          script_state.GetIsolate(), DOMExceptionCode::kDataError,
+          "filteringIdMaxBytes must be positive"));
+      return false;
+    }
+    if (options.privateAggregationConfig()->filteringIdMaxBytes() >
+        kMaximumFilteringIdMaxBytes) {
+      resolver.Reject(V8ThrowDOMException::CreateOrEmpty(
+          script_state.GetIsolate(), DOMExceptionCode::kDataError,
+          "filteringIdMaxBytes is too big"));
+      return false;
+    }
+    out_filtering_id_max_bytes = static_cast<uint32_t>(
+        options.privateAggregationConfig()->filteringIdMaxBytes());
+  }
+
   return true;
 }
 
diff --git a/third_party/blink/renderer/modules/shared_storage/util.h b/third_party/blink/renderer/modules/shared_storage/util.h
index 7805ba4..9e251afd 100644
--- a/third_party/blink/renderer/modules/shared_storage/util.h
+++ b/third_party/blink/renderer/modules/shared_storage/util.h
@@ -21,6 +21,8 @@
 class ScriptPromiseResolverBase;
 class SharedStorageRunOperationMethodOptions;
 
+static constexpr size_t kMaximumFilteringIdMaxBytes = 8;
+
 // Helper method to convert v8 string to WTF::String.
 bool StringFromV8(v8::Isolate* isolate,
                   v8::Local<v8::Value> val,
@@ -48,7 +50,9 @@
 // `out_private_aggregation_config->context_id` is populated with it; otherwise,
 // it's left default (a null String). If a valid aggregation coordinator is
 // provided, `out_private_aggregation_config->aggregation_coodinator_origin` is
-// populated with it; otherwise, it's left default (nullptr).
+// populated with it; otherwise, it's left default (nullptr). If a valid
+// filteringIdMaxBytes is provided, `out_filtering_id_max_bytes` is populated
+// with it; otherwise, it's populated with the default of 1.
 bool CheckPrivateAggregationConfig(
     const SharedStorageRunOperationMethodOptions& options,
     ScriptState& script_state,
diff --git a/third_party/blink/renderer/platform/BUILD.gn b/third_party/blink/renderer/platform/BUILD.gn
index 6c576bf4..30dea9b 100644
--- a/third_party/blink/renderer/platform/BUILD.gn
+++ b/third_party/blink/renderer/platform/BUILD.gn
@@ -2700,6 +2700,17 @@
   libfuzzer_options = [ "rss_limit_mb=8192" ]
 }
 
+# Fuzzer for blink::CrabbyAVIFImageDecoder
+fuzzer_test("blink_crabbyavif_decoder_fuzzer") {
+  sources = [ "image-decoders/avif/crabbyavif_image_decoder_fuzzer.cc" ]
+  deps = [
+    ":blink_fuzzer_test_support",
+    ":platform",
+  ]
+  seed_corpuses = [ "//third_party/blink/web_tests/images/resources/avif" ]
+  libfuzzer_options = [ "rss_limit_mb=8192" ]
+}
+
 # Fuzzer for blink::ICOImageDecoder.
 fuzzer_test("blink_ico_decoder_fuzzer") {
   sources = [ "image-decoders/ico/ico_image_decoder_fuzzer.cc" ]
diff --git a/third_party/blink/renderer/platform/exported/web_url_response.cc b/third_party/blink/renderer/platform/exported/web_url_response.cc
index 5f7b520..4951a4b 100644
--- a/third_party/blink/renderer/platform/exported/web_url_response.cc
+++ b/third_party/blink/renderer/platform/exported/web_url_response.cc
@@ -217,24 +217,7 @@
   // If there's no received headers end time, don't set load timing.  This is
   // the case for non-HTTP requests, requests that don't go over the wire, and
   // certain error cases.
-  //
-  // https://crbug.com/1382255: Because the resource-fetching request of
-  // prefetch occurs before the navigation, both `requestStart` and
-  // `responseStart` are negative, measured with respect to `startTime` of the
-  // navigation. Do not set the `ResourceLoadTiming` for the prefetch navigation
-  // response; then `PerformanceResourceTiming` won't be able to retrieve the
-  // timing info, resulting in setting `requestStart` and `responseStart` to
-  // their respective previous timeline event.
-  //
-  // Not setting the `ResourceLoadTiming` does not affect the DNS and TCP
-  // timings (domainLookupStart, domainLookupEnd, connectStart,
-  // secureConnectionStart and connectEnd) because these values are null for the
-  // prefetch navigation. See
-  // https://docs.google.com/document/d/1XbLImIqGoHgxJZnscWoZIX8IlIiHOlXvBW81B_3HScc
-  // (Chromium org access) for the navigation timing events timeline.
-  if (!head.load_timing.receive_headers_end.is_null() &&
-      head.navigation_delivery_type !=
-          network::mojom::NavigationDeliveryType::kNavigationalPrefetch) {
+  if (!head.load_timing.receive_headers_end.is_null()) {
     response.SetLoadTiming(ToMojoLoadTiming(head.load_timing));
   }
 
diff --git a/third_party/blink/renderer/platform/fonts/mac/font_cache_mac.mm b/third_party/blink/renderer/platform/fonts/mac/font_cache_mac.mm
index a6a4140..25a256d 100644
--- a/third_party/blink/renderer/platform/fonts/mac/font_cache_mac.mm
+++ b/third_party/blink/renderer/platform/fonts/mac/font_cache_mac.mm
@@ -259,6 +259,21 @@
   return !font_name.empty() && font_name[0] == '.';
 }
 
+inline bool IsAppKitFontWeightBold(NSInteger app_kit_font_weight) {
+  return app_kit_font_weight >= 7;
+}
+
+void FontCacheRegisteredFontsChangedNotificationCallback(
+    CFNotificationCenterRef,
+    void* observer,
+    CFStringRef name,
+    const void*,
+    CFDictionaryRef) {
+  DCHECK_EQ(observer, &FontCache::Get());
+  DCHECK(CFEqual(name, kCTFontManagerRegisteredFontsChangedNotification));
+  FontCache::InvalidateFromAnyThread();
+}
+
 }  // namespace
 
 const char kColorEmojiFontMac[] = "Apple Color Emoji";
@@ -280,17 +295,6 @@
   FontCache::Get().Invalidate();
 }
 
-static void FontCacheRegisteredFontsChangedNotificationCallback(
-    CFNotificationCenterRef,
-    void* observer,
-    CFStringRef name,
-    const void*,
-    CFDictionaryRef) {
-  DCHECK_EQ(observer, &FontCache::Get());
-  DCHECK(CFEqual(name, kCTFontManagerRegisteredFontsChangedNotification));
-  FontCache::InvalidateFromAnyThread();
-}
-
 void FontCache::PlatformInit() {
   CFNotificationCenterAddObserver(
       CFNotificationCenterGetLocalCenter(), this,
@@ -299,10 +303,6 @@
       CFNotificationSuspensionBehaviorDeliverImmediately);
 }
 
-static inline bool IsAppKitFontWeightBold(NSInteger app_kit_font_weight) {
-  return app_kit_font_weight >= 7;
-}
-
 const SimpleFontData* FontCache::PlatformFallbackFontForCharacter(
     const FontDescription& font_description,
     UChar32 character,
diff --git a/third_party/blink/renderer/platform/image-decoders/BUILD.gn b/third_party/blink/renderer/platform/image-decoders/BUILD.gn
index 3f45cc71..946802a 100644
--- a/third_party/blink/renderer/platform/image-decoders/BUILD.gn
+++ b/third_party/blink/renderer/platform/image-decoders/BUILD.gn
@@ -76,9 +76,12 @@
     sources += [
       "avif/avif_image_decoder.cc",
       "avif/avif_image_decoder.h",
+      "avif/crabbyavif_image_decoder.cc",
+      "avif/crabbyavif_image_decoder.h",
     ]
 
     deps += [
+      "//third_party/crabbyavif",
       "//third_party/libavif",
       "//third_party/libavifinfo",
     ]
@@ -120,7 +123,10 @@
   }
 
   if (enable_av1_decoder) {
-    sources += [ "avif/avif_image_decoder_test.cc" ]
+    sources += [
+      "avif/avif_image_decoder_test.cc",
+      "avif/crabbyavif_image_decoder_test.cc",
+    ]
   }
 }
 
diff --git a/third_party/blink/renderer/platform/image-decoders/avif/OWNERS b/third_party/blink/renderer/platform/image-decoders/avif/OWNERS
index 6be2ef7..e0fd7c89 100644
--- a/third_party/blink/renderer/platform/image-decoders/avif/OWNERS
+++ b/third_party/blink/renderer/platform/image-decoders/avif/OWNERS
@@ -1,2 +1,3 @@
 dalecurtis@chromium.org
+vigneshv@google.com
 wtc@google.com
diff --git a/third_party/blink/renderer/platform/image-decoders/avif/crabbyavif_image_decoder.cc b/third_party/blink/renderer/platform/image-decoders/avif/crabbyavif_image_decoder.cc
index 2ed8168..cd120aa9 100644
--- a/third_party/blink/renderer/platform/image-decoders/avif/crabbyavif_image_decoder.cc
+++ b/third_party/blink/renderer/platform/image-decoders/avif/crabbyavif_image_decoder.cc
@@ -1,8 +1,10 @@
-// Copyright 2020 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.
+// WARNING: Auto-generated by gen_crabbyavif_wrapper.py.
+// Do not modify manually.
 
-#include "third_party/blink/renderer/platform/image-decoders/avif/avif_image_decoder.h"
+#include "third_party/blink/renderer/platform/image-decoders/avif/crabbyavif_image_decoder.h"
 
 #include <stdint.h>
 #include <string.h>
@@ -30,7 +32,7 @@
 #include "third_party/blink/renderer/platform/image-decoders/image_animation.h"
 #include "third_party/blink/renderer/platform/image-decoders/image_decoder.h"
 #include "third_party/blink/renderer/platform/image-decoders/rw_buffer.h"
-#include "third_party/libavif/src/include/avif/avif.h"
+#include "third_party/crabbyavif/src/include/avif/avif.h"
 #include "third_party/libavifinfo/src/avifinfo.h"
 #include "third_party/libyuv/include/libyuv.h"
 #include "third_party/skia/include/core/SkColorSpace.h"
@@ -52,7 +54,7 @@
 // known.
 constexpr uint64_t kMaxAvifFileSize = 0x10000000;  // 256 MB
 
-const char* AvifDecoderErrorMessage(const avifDecoder* decoder) {
+const char* AvifDecoderErrorMessage(const crabbyavif::avifDecoder* decoder) {
   // decoder->diag.error is a char array that stores a null-terminated C string.
   return *decoder->diag.error != '\0' ? decoder->diag.error
                                       : "(no error message)";
@@ -60,10 +62,10 @@
 
 // Builds a gfx::ColorSpace from the ITU-T H.273 (CICP) color description.
 gfx::ColorSpace GetColorSpace(
-    avifColorPrimaries color_primaries,
-    avifTransferCharacteristics transfer_characteristics,
-    avifMatrixCoefficients matrix_coefficients,
-    avifRange yuv_range,
+    crabbyavif::avifColorPrimaries color_primaries,
+    crabbyavif::avifTransferCharacteristics transfer_characteristics,
+    crabbyavif::avifMatrixCoefficients matrix_coefficients,
+    crabbyavif::avifRange yuv_range,
     bool grayscale) {
   // (As of ISO/IEC 23000-22:2019 Amendment 2) MIAF Section 7.3.6.4 says:
   //   If a coded image has no associated colour property, the default property
@@ -75,9 +77,9 @@
   //     and
   //   - full_range_flag equal to 1.
   //   ...
-  // These values correspond to AVIF_COLOR_PRIMARIES_BT709,
-  // AVIF_TRANSFER_CHARACTERISTICS_SRGB, and AVIF_MATRIX_COEFFICIENTS_BT601,
-  // respectively.
+  // These values correspond to crabbyavif::AVIF_COLOR_PRIMARIES_BT709,
+  // crabbyavif::AVIF_TRANSFER_CHARACTERISTICS_SRGB, and
+  // crabbyavif::AVIF_MATRIX_COEFFICIENTS_BT601, respectively.
   //
   // Note that this only specifies the default color property when the color
   // property is absent. It does not really specify the default values for
@@ -87,18 +89,21 @@
   // and these are the most reasonable defaults to choose. We also advocate that
   // all AVIF decoders choose these defaults:
   // https://github.com/AOMediaCodec/av1-avif/issues/84
-  const auto primaries = color_primaries == AVIF_COLOR_PRIMARIES_UNSPECIFIED
-                             ? AVIF_COLOR_PRIMARIES_BT709
-                             : color_primaries;
+  const auto primaries =
+      color_primaries == crabbyavif::AVIF_COLOR_PRIMARIES_UNSPECIFIED
+          ? crabbyavif::AVIF_COLOR_PRIMARIES_BT709
+          : color_primaries;
   const auto transfer =
-      transfer_characteristics == AVIF_TRANSFER_CHARACTERISTICS_UNSPECIFIED
-          ? AVIF_TRANSFER_CHARACTERISTICS_SRGB
+      transfer_characteristics ==
+              crabbyavif::AVIF_TRANSFER_CHARACTERISTICS_UNSPECIFIED
+          ? crabbyavif::AVIF_TRANSFER_CHARACTERISTICS_SRGB
           : transfer_characteristics;
   const auto matrix =
-      (grayscale || matrix_coefficients == AVIF_MATRIX_COEFFICIENTS_UNSPECIFIED)
-          ? AVIF_MATRIX_COEFFICIENTS_BT601
+      (grayscale ||
+       matrix_coefficients == crabbyavif::AVIF_MATRIX_COEFFICIENTS_UNSPECIFIED)
+          ? crabbyavif::AVIF_MATRIX_COEFFICIENTS_BT601
           : matrix_coefficients;
-  const auto range = yuv_range == AVIF_RANGE_FULL
+  const auto range = yuv_range == crabbyavif::AVIF_RANGE_FULL
                          ? gfx::ColorSpace::RangeID::FULL
                          : gfx::ColorSpace::RangeID::LIMITED;
   media::VideoColorSpace color_space(primaries, transfer, matrix, range);
@@ -109,7 +114,7 @@
   // MatrixCoefficients 12, 13, 14.
   DCHECK_GE(matrix, 12);
   DCHECK_LE(matrix, 14);
-  if (yuv_range == AVIF_RANGE_FULL) {
+  if (yuv_range == crabbyavif::AVIF_RANGE_FULL) {
     return gfx::ColorSpace::CreateJpeg();
   }
   return gfx::ColorSpace::CreateREC709();
@@ -117,8 +122,9 @@
 
 // Builds a gfx::ColorSpace from the ITU-T H.273 (CICP) color description in the
 // image.
-gfx::ColorSpace GetColorSpace(const avifImage* image) {
-  const bool grayscale = image->yuvFormat == AVIF_PIXEL_FORMAT_YUV400;
+gfx::ColorSpace GetColorSpace(const crabbyavif::avifImage* image) {
+  const bool grayscale =
+      image->yuvFormat == crabbyavif::AVIF_PIXEL_FORMAT_YUV400;
   return GetColorSpace(image->colorPrimaries, image->transferCharacteristics,
                        image->matrixCoefficients, image->yuvRange, grayscale);
 }
@@ -241,8 +247,8 @@
 // need to specify it in SkGainmapInfo, and using the base image's color space
 // may be more accurate if the profile cannot be exactly represented as a
 // SkColorSpace object.
-sk_sp<SkColorSpace> GetAltImageColorSpace(const avifImage& image) {
-  const avifGainMap* gain_map = image.gainMap;
+sk_sp<SkColorSpace> GetAltImageColorSpace(const crabbyavif::avifImage& image) {
+  const crabbyavif::avifGainMap* gain_map = image.gainMap;
   if (!gain_map) {
     return nullptr;
   }
@@ -276,7 +282,8 @@
       skcms_SetTransferFunction(&with_srgb, skcms_sRGB_TransferFunction());
       color_space = SkColorSpace::Make(with_srgb);
     }
-  } else if (gain_map->altColorPrimaries != AVIF_COLOR_PRIMARIES_UNSPECIFIED) {
+  } else if (gain_map->altColorPrimaries !=
+             crabbyavif::AVIF_COLOR_PRIMARIES_UNSPECIFIED) {
     if (image.icc.size == 0 &&
         image.colorPrimaries == gain_map->altColorPrimaries) {
       // Same as base image, no need to specify it.
@@ -298,30 +305,31 @@
 
 }  // namespace
 
-AVIFImageDecoder::AVIFImageDecoder(AlphaOption alpha_option,
-                                   HighBitDepthDecodingOption hbd_option,
-                                   ColorBehavior color_behavior,
-                                   wtf_size_t max_decoded_bytes,
-                                   AnimationOption animation_option)
+CrabbyAVIFImageDecoder::CrabbyAVIFImageDecoder(
+    AlphaOption alpha_option,
+    HighBitDepthDecodingOption hbd_option,
+    ColorBehavior color_behavior,
+    wtf_size_t max_decoded_bytes,
+    AnimationOption animation_option)
     : ImageDecoder(alpha_option, hbd_option, color_behavior, max_decoded_bytes),
       animation_option_(animation_option) {}
 
-AVIFImageDecoder::~AVIFImageDecoder() = default;
+CrabbyAVIFImageDecoder::~CrabbyAVIFImageDecoder() = default;
 
-String AVIFImageDecoder::FilenameExtension() const {
+String CrabbyAVIFImageDecoder::FilenameExtension() const {
   return "avif";
 }
 
-const AtomicString& AVIFImageDecoder::MimeType() const {
+const AtomicString& CrabbyAVIFImageDecoder::MimeType() const {
   DEFINE_STATIC_LOCAL(const AtomicString, avif_mime_type, ("image/avif"));
   return avif_mime_type;
 }
 
-bool AVIFImageDecoder::ImageIsHighBitDepth() {
+bool CrabbyAVIFImageDecoder::ImageIsHighBitDepth() {
   return bit_depth_ > 8;
 }
 
-void AVIFImageDecoder::OnSetData(scoped_refptr<SegmentReader> data) {
+void CrabbyAVIFImageDecoder::OnSetData(scoped_refptr<SegmentReader> data) {
   have_parsed_current_data_ = false;
   const bool all_data_received = IsAllDataReceived();
   avif_io_data_.reader = data_;
@@ -336,30 +344,30 @@
   }
 }
 
-cc::YUVSubsampling AVIFImageDecoder::GetYUVSubsampling() const {
+cc::YUVSubsampling CrabbyAVIFImageDecoder::GetYUVSubsampling() const {
   switch (avif_yuv_format_) {
-    case AVIF_PIXEL_FORMAT_YUV420:
+    case crabbyavif::AVIF_PIXEL_FORMAT_YUV420:
       return cc::YUVSubsampling::k420;
-    case AVIF_PIXEL_FORMAT_YUV422:
+    case crabbyavif::AVIF_PIXEL_FORMAT_YUV422:
       return cc::YUVSubsampling::k422;
-    case AVIF_PIXEL_FORMAT_YUV444:
+    case crabbyavif::AVIF_PIXEL_FORMAT_YUV444:
       return cc::YUVSubsampling::k444;
-    case AVIF_PIXEL_FORMAT_YUV400:
+    case crabbyavif::AVIF_PIXEL_FORMAT_YUV400:
       return cc::YUVSubsampling::kUnknown;
-    case AVIF_PIXEL_FORMAT_NONE:
-      // avif_yuv_format_ is initialized to AVIF_PIXEL_FORMAT_NONE in the
-      // constructor. If we have called SetSize() successfully at the end
+    case crabbyavif::AVIF_PIXEL_FORMAT_NONE:
+      // avif_yuv_format_ is initialized to crabbyavif::AVIF_PIXEL_FORMAT_NONE
+      // in the constructor. If we have called SetSize() successfully at the end
       // of UpdateDemuxer(), avif_yuv_format_ cannot possibly be
-      // AVIF_PIXEL_FORMAT_NONE.
+      // crabbyavif::AVIF_PIXEL_FORMAT_NONE.
       CHECK(!IsDecodedSizeAvailable());
       return cc::YUVSubsampling::kUnknown;
-    case AVIF_PIXEL_FORMAT_COUNT:
+    case crabbyavif::AVIF_PIXEL_FORMAT_COUNT:
       break;
   }
   NOTREACHED_NORETURN() << "Invalid YUV format: " << avif_yuv_format_;
 }
 
-gfx::Size AVIFImageDecoder::DecodedYUVSize(cc::YUVIndex index) const {
+gfx::Size CrabbyAVIFImageDecoder::DecodedYUVSize(cc::YUVIndex index) const {
   DCHECK(IsDecodedSizeAvailable());
   if (index == cc::YUVIndex::kU || index == cc::YUVIndex::kV) {
     return gfx::Size(UVSize(Size().width(), chroma_shift_x_),
@@ -368,7 +376,8 @@
   return Size();
 }
 
-wtf_size_t AVIFImageDecoder::DecodedYUVWidthBytes(cc::YUVIndex index) const {
+wtf_size_t CrabbyAVIFImageDecoder::DecodedYUVWidthBytes(
+    cc::YUVIndex index) const {
   DCHECK(IsDecodedSizeAvailable());
   // Try to return the same width bytes as used by the dav1d library. This will
   // allow DecodeToYUV() to copy each plane with a single memcpy() call.
@@ -397,22 +406,22 @@
   return aligned_width;
 }
 
-SkYUVColorSpace AVIFImageDecoder::GetYUVColorSpace() const {
+SkYUVColorSpace CrabbyAVIFImageDecoder::GetYUVColorSpace() const {
   DCHECK(CanDecodeToYUV());
   DCHECK_NE(yuv_color_space_, SkYUVColorSpace::kIdentity_SkYUVColorSpace);
   return yuv_color_space_;
 }
 
-uint8_t AVIFImageDecoder::GetYUVBitDepth() const {
+uint8_t CrabbyAVIFImageDecoder::GetYUVBitDepth() const {
   DCHECK(CanDecodeToYUV());
   return bit_depth_;
 }
 
-std::optional<gfx::HDRMetadata> AVIFImageDecoder::GetHDRMetadata() const {
+std::optional<gfx::HDRMetadata> CrabbyAVIFImageDecoder::GetHDRMetadata() const {
   return hdr_metadata_;
 }
 
-void AVIFImageDecoder::DecodeToYUV() {
+void CrabbyAVIFImageDecoder::DecodeToYUV() {
   DCHECK(image_planes_);
   DCHECK(CanDecodeToYUV());
 
@@ -428,24 +437,30 @@
   // complete scan will not be updated.
   const int frame_index = progressive_ ? (decoder_->imageCount - 1) : 0;
   // TODO(crbug.com/943519): Implement YUV incremental decoding as in Decode().
-  decoder_->allowIncremental = AVIF_FALSE;
+  decoder_->allowIncremental = crabbyavif::CRABBY_AVIF_FALSE;
 
   // libavif cannot decode to an external buffer. So we need to copy from
   // libavif's internal buffer to |image_planes_|.
   // TODO(crbug.com/1099825): Enhance libavif to decode to an external buffer.
   auto ret = DecodeImage(frame_index);
-  if (ret != AVIF_RESULT_OK) {
-    if (ret != AVIF_RESULT_WAITING_ON_IO) {
+  if (ret != crabbyavif::AVIF_RESULT_OK) {
+    if (ret != crabbyavif::AVIF_RESULT_WAITING_ON_IO) {
       SetFailed();
     }
     return;
   }
-  const avifImage* image = decoded_image_;
+  const crabbyavif::avifImage* image = decoded_image_;
 
   DCHECK(!image->alphaPlane);
-  static_assert(cc::YUVIndex::kY == static_cast<cc::YUVIndex>(AVIF_CHAN_Y), "");
-  static_assert(cc::YUVIndex::kU == static_cast<cc::YUVIndex>(AVIF_CHAN_U), "");
-  static_assert(cc::YUVIndex::kV == static_cast<cc::YUVIndex>(AVIF_CHAN_V), "");
+  static_assert(
+      cc::YUVIndex::kY == static_cast<cc::YUVIndex>(crabbyavif::AVIF_CHAN_Y),
+      "");
+  static_assert(
+      cc::YUVIndex::kU == static_cast<cc::YUVIndex>(crabbyavif::AVIF_CHAN_U),
+      "");
+  static_assert(
+      cc::YUVIndex::kV == static_cast<cc::YUVIndex>(crabbyavif::AVIF_CHAN_V),
+      "");
 
   // Disable subnormal floats which can occur when converting to half float.
   std::unique_ptr<cc::ScopedSubnormalFloatDisabler> disable_subnormals;
@@ -506,12 +521,12 @@
   image_planes_->SetHasCompleteScan();
 }
 
-int AVIFImageDecoder::RepetitionCount() const {
+int CrabbyAVIFImageDecoder::RepetitionCount() const {
   if (decoded_frame_count_ > 1) {
     switch (decoder_->repetitionCount) {
-      case AVIF_REPETITION_COUNT_INFINITE:
+      case crabbyavif::CRABBY_AVIF_REPETITION_COUNT_INFINITE:
         return kAnimationLoopInfinite;
-      case AVIF_REPETITION_COUNT_UNKNOWN:
+      case crabbyavif::CRABBY_AVIF_REPETITION_COUNT_UNKNOWN:
         // The AVIF file does not have repetitions specified using an EditList
         // box. Loop infinitely for backward compatibility with older versions
         // of Chrome.
@@ -523,7 +538,7 @@
   return kAnimationNone;
 }
 
-bool AVIFImageDecoder::FrameIsReceivedAtIndex(wtf_size_t index) const {
+bool CrabbyAVIFImageDecoder::FrameIsReceivedAtIndex(wtf_size_t index) const {
   if (!IsDecodedSizeAvailable()) {
     return false;
   }
@@ -536,64 +551,66 @@
   if (IsAllDataReceived()) {
     return true;
   }
-  avifExtent data_extent;
-  if (avifDecoderNthImageMaxExtent(decoder_.get(), index, &data_extent) !=
-      AVIF_RESULT_OK) {
+  crabbyavif::avifExtent data_extent;
+  if (crabbyavif::crabby_avifDecoderNthImageMaxExtent(
+          decoder_.get(), index, &data_extent) != crabbyavif::AVIF_RESULT_OK) {
     return false;
   }
   return data_extent.size == 0 ||
          data_extent.offset + data_extent.size <= data_->size();
 }
 
-std::optional<base::TimeDelta> AVIFImageDecoder::FrameTimestampAtIndex(
+std::optional<base::TimeDelta> CrabbyAVIFImageDecoder::FrameTimestampAtIndex(
     wtf_size_t index) const {
   return index < frame_buffer_cache_.size()
              ? frame_buffer_cache_[index].Timestamp()
              : std::nullopt;
 }
 
-base::TimeDelta AVIFImageDecoder::FrameDurationAtIndex(wtf_size_t index) const {
+base::TimeDelta CrabbyAVIFImageDecoder::FrameDurationAtIndex(
+    wtf_size_t index) const {
   return index < frame_buffer_cache_.size()
              ? frame_buffer_cache_[index].Duration()
              : base::TimeDelta();
 }
 
-bool AVIFImageDecoder::ImageHasBothStillAndAnimatedSubImages() const {
+bool CrabbyAVIFImageDecoder::ImageHasBothStillAndAnimatedSubImages() const {
   // Per MIAF, all animated AVIF files must have a still image, even if it's
   // just a pointer to the first frame of the animation.
   return decoder_ && decoder_->imageSequenceTrackPresent;
 }
 
 // static
-bool AVIFImageDecoder::MatchesAVIFSignature(
+bool CrabbyAVIFImageDecoder::MatchesAVIFSignature(
     const FastSharedBufferReader& fast_reader) {
-  // avifPeekCompatibleFileType() clamps compatible brands at 32 when reading in
-  // the ftyp box in ISO BMFF for the 'avif' or 'avis' brand. So the maximum
-  // number of bytes read is 144 bytes (size 4 bytes, type 4 bytes, major brand
-  // 4 bytes, minor version 4 bytes, and 4 bytes * 32 compatible brands).
+  // crabbyavif::crabby_avifPeekCompatibleFileType() clamps compatible brands at
+  // 32 when reading in the ftyp box in ISO BMFF for the 'avif' or 'avis' brand.
+  // So the maximum number of bytes read is 144 bytes (size 4 bytes, type 4
+  // bytes, major brand 4 bytes, minor version 4 bytes, and 4 bytes * 32
+  // compatible brands).
   char buffer[144];
-  avifROData input;
+  crabbyavif::avifROData input;
   input.size = std::min(sizeof(buffer), fast_reader.size());
   input.data = reinterpret_cast<const uint8_t*>(
       fast_reader.GetConsecutiveData(0, input.size, buffer));
-  return avifPeekCompatibleFileType(&input);
+  return crabbyavif::crabby_avifPeekCompatibleFileType(&input);
 }
 
-gfx::ColorSpace AVIFImageDecoder::GetColorSpaceForTesting() const {
+gfx::ColorSpace CrabbyAVIFImageDecoder::GetColorSpaceForTesting() const {
   return GetColorSpace(decoder_->image);
 }
 
-void AVIFImageDecoder::ParseMetadata() {
+void CrabbyAVIFImageDecoder::ParseMetadata() {
   if (!UpdateDemuxer()) {
     SetFailed();
   }
 }
 
-void AVIFImageDecoder::DecodeSize() {
+void CrabbyAVIFImageDecoder::DecodeSize() {
   ParseMetadata();
 }
 
-wtf_size_t AVIFImageDecoder::DecodeFrameCount() {
+wtf_size_t CrabbyAVIFImageDecoder::DecodeFrameCount() {
   if (!Failed()) {
     ParseMetadata();
   }
@@ -601,7 +618,7 @@
                                   : frame_buffer_cache_.size();
 }
 
-void AVIFImageDecoder::InitializeNewFrame(wtf_size_t index) {
+void CrabbyAVIFImageDecoder::InitializeNewFrame(wtf_size_t index) {
   auto& buffer = frame_buffer_cache_[index];
   if (decode_to_half_float_) {
     buffer.SetPixelFormat(ImageFrame::PixelFormat::kRGBA_F16);
@@ -610,14 +627,15 @@
   // For AVIFs, the frame always fills the entire image.
   buffer.SetOriginalFrameRect(gfx::Rect(Size()));
 
-  avifImageTiming timing;
-  auto ret = avifDecoderNthImageTiming(decoder_.get(), index, &timing);
-  DCHECK_EQ(ret, AVIF_RESULT_OK);
+  crabbyavif::avifImageTiming timing;
+  auto ret = crabbyavif::crabby_avifDecoderNthImageTiming(decoder_.get(), index,
+                                                          &timing);
+  DCHECK_EQ(ret, crabbyavif::AVIF_RESULT_OK);
   buffer.SetTimestamp(base::Seconds(timing.pts));
   buffer.SetDuration(base::Seconds(timing.duration));
 }
 
-void AVIFImageDecoder::Decode(wtf_size_t index) {
+void CrabbyAVIFImageDecoder::Decode(wtf_size_t index) {
   if (Failed()) {
     return;
   }
@@ -636,13 +654,14 @@
     DCHECK_LT(decoder_->imageIndex + 1, decoder_->imageCount);
     for (frame_index = decoder_->imageIndex + 1;
          frame_index + 1 < decoder_->imageCount; ++frame_index) {
-      avifExtent data_extent;
-      auto rv = avifDecoderNthImageMaxExtent(decoder_.get(), frame_index + 1,
-                                             &data_extent);
-      if (rv != AVIF_RESULT_OK) {
-        DVLOG(1) << "avifDecoderNthImageMaxExtent(" << frame_index + 1
-                 << ") failed: " << avifResultToString(rv) << ": "
-                 << AvifDecoderErrorMessage(decoder_.get());
+      crabbyavif::avifExtent data_extent;
+      auto rv = crabbyavif::crabby_avifDecoderNthImageMaxExtent(
+          decoder_.get(), frame_index + 1, &data_extent);
+      if (rv != crabbyavif::AVIF_RESULT_OK) {
+        DVLOG(1) << "crabbyavif::crabby_avifDecoderNthImageMaxExtent("
+                 << frame_index + 1
+                 << ") failed: " << crabbyavif::crabby_avifResultToString(rv)
+                 << ": " << AvifDecoderErrorMessage(decoder_.get());
         SetFailed();
         return;
       }
@@ -660,16 +679,17 @@
   decoder_->allowIncremental = (decoder_->imageCount == 1);
 
   auto ret = DecodeImage(frame_index);
-  if (ret != AVIF_RESULT_OK && ret != AVIF_RESULT_WAITING_ON_IO) {
+  if (ret != crabbyavif::AVIF_RESULT_OK &&
+      ret != crabbyavif::AVIF_RESULT_WAITING_ON_IO) {
     SetFailed();
     return;
   }
-  const avifImage* image = decoded_image_;
+  const crabbyavif::avifImage* image = decoded_image_;
 
   // ImageDecoder::SizeCalculationMayOverflow(), called by UpdateDemuxer()
   // before being here, made sure the image height fits in an int.
-  int displayable_height =
-      static_cast<int>(avifDecoderDecodedRowCount(decoder_.get()));
+  int displayable_height = static_cast<int>(
+      crabbyavif::crabby_avifDecoderDecodedRowCount(decoder_.get()));
   if (image == cropped_image_.get()) {
     displayable_height -= clap_origin_.y();
     displayable_height =
@@ -731,7 +751,8 @@
   }
 }
 
-bool AVIFImageDecoder::CanReusePreviousFrameBuffer(wtf_size_t index) const {
+bool CrabbyAVIFImageDecoder::CanReusePreviousFrameBuffer(
+    wtf_size_t index) const {
   // (a) Technically we can reuse the bitmap of the previous frame because the
   // AVIF decoder handles frame dependence internally and we never need to
   // preserve previous frames to decode later ones, and (b) since this function
@@ -741,14 +762,15 @@
 }
 
 // static
-avifResult AVIFImageDecoder::ReadFromSegmentReader(avifIO* io,
-                                                   uint32_t read_flags,
-                                                   uint64_t offset,
-                                                   size_t size,
-                                                   avifROData* out) {
+crabbyavif::avifResult CrabbyAVIFImageDecoder::ReadFromSegmentReader(
+    crabbyavif::avifIO* io,
+    uint32_t read_flags,
+    uint64_t offset,
+    size_t size,
+    crabbyavif::avifROData* out) {
   if (read_flags != 0) {
     // Unsupported read_flags
-    return AVIF_RESULT_IO_ERROR;
+    return crabbyavif::AVIF_RESULT_IO_ERROR;
   }
 
   AvifIOData* io_data = static_cast<AvifIOData*>(io->data);
@@ -756,8 +778,8 @@
   // Sanitize/clamp incoming request
   if (offset > io_data->reader->size()) {
     // The offset is past the end of the buffer or available data.
-    return io_data->all_data_received ? AVIF_RESULT_IO_ERROR
-                                      : AVIF_RESULT_WAITING_ON_IO;
+    return io_data->all_data_received ? crabbyavif::AVIF_RESULT_IO_ERROR
+                                      : crabbyavif::AVIF_RESULT_WAITING_ON_IO;
   }
 
   // It is more convenient to work with a variable of the size_t type. Since
@@ -766,7 +788,7 @@
   const size_t available_size = io_data->reader->size() - position;
   if (size > available_size) {
     if (!io_data->all_data_received) {
-      return AVIF_RESULT_WAITING_ON_IO;
+      return crabbyavif::AVIF_RESULT_WAITING_ON_IO;
     }
     size = available_size;
   }
@@ -776,7 +798,7 @@
   size_t data_size = io_data->reader->GetSomeData(data, position);
   if (data_size >= size) {
     out->data = reinterpret_cast<const uint8_t*>(data);
-    return AVIF_RESULT_OK;
+    return crabbyavif::AVIF_RESULT_OK;
   }
 
   io_data->buffer.clear();
@@ -790,10 +812,10 @@
   }
 
   out->data = io_data->buffer.data();
-  return AVIF_RESULT_OK;
+  return crabbyavif::AVIF_RESULT_OK;
 }
 
-bool AVIFImageDecoder::UpdateDemuxer() {
+bool CrabbyAVIFImageDecoder::UpdateDemuxer() {
   DCHECK(!Failed());
   if (IsDecodedSizeAvailable()) {
     return true;
@@ -805,7 +827,7 @@
   have_parsed_current_data_ = true;
 
   if (!decoder_) {
-    decoder_.reset(avifDecoderCreate());
+    decoder_.reset(crabbyavif::crabby_avifDecoderCreate());
     if (!decoder_) {
       return false;
     }
@@ -813,62 +835,65 @@
     // For simplicity, use a hardcoded maxThreads of 2, independent of the image
     // size and processor count. Note: even if we want maxThreads to depend on
     // the image size, it is impossible to do so because maxThreads is passed to
-    // dav1d_open() inside avifDecoderParse(), but the image size is not known
-    // until avifDecoderParse() returns successfully. See
-    // https://github.com/AOMediaCodec/libavif/issues/636.
+    // dav1d_open() inside crabbyavif::crabby_avifDecoderParse(), but the image
+    // size is not known until crabbyavif::crabby_avifDecoderParse() returns
+    // successfully. See https://github.com/AOMediaCodec/libavif/issues/636.
     decoder_->maxThreads = 2;
 
     if (animation_option_ != AnimationOption::kUnspecified &&
-        avifDecoderSetSource(
+        crabbyavif::crabby_avifDecoderSetSource(
             decoder_.get(),
             animation_option_ == AnimationOption::kPreferAnimation
-                ? AVIF_DECODER_SOURCE_TRACKS
-                : AVIF_DECODER_SOURCE_PRIMARY_ITEM) != AVIF_RESULT_OK) {
+                ? crabbyavif::AVIF_DECODER_SOURCE_TRACKS
+                : crabbyavif::AVIF_DECODER_SOURCE_PRIMARY_ITEM) !=
+            crabbyavif::AVIF_RESULT_OK) {
       return false;
     }
 
     // Chrome doesn't use XMP and Exif metadata. Ignoring XMP and Exif will
-    // ensure avifDecoderParse() isn't waiting for some tiny Exif payload hiding
-    // at the end of a file.
-    decoder_->ignoreXMP = AVIF_TRUE;
-    decoder_->ignoreExif = AVIF_TRUE;
+    // ensure crabbyavif::crabby_avifDecoderParse() isn't waiting for some tiny
+    // Exif payload hiding at the end of a file.
+    decoder_->ignoreXMP = crabbyavif::CRABBY_AVIF_TRUE;
+    decoder_->ignoreExif = crabbyavif::CRABBY_AVIF_TRUE;
 
     // Turn off libavif's 'clap' (clean aperture) property validation. We
     // validate 'clap' ourselves and ignore invalid 'clap' properties.
-    decoder_->strictFlags &= ~AVIF_STRICT_CLAP_VALID;
+    decoder_->strictFlags &= ~crabbyavif::AVIF_STRICT_CLAP_VALID;
     // Allow the PixelInformationProperty ('pixi') to be missing in AV1 image
     // items. libheif v1.11.0 or older does not add the 'pixi' item property to
     // AV1 image items. (This issue has been corrected in libheif v1.12.0.) See
     // crbug.com/1198455.
-    decoder_->strictFlags &= ~AVIF_STRICT_PIXI_REQUIRED;
+    decoder_->strictFlags &= ~crabbyavif::AVIF_STRICT_PIXI_REQUIRED;
 
     if (base::FeatureList::IsEnabled(features::kGainmapHdrImages) &&
         base::FeatureList::IsEnabled(features::kAvifGainmapHdrImages)) {
-      decoder_->enableParsingGainMapMetadata = AVIF_TRUE;
+      decoder_->enableParsingGainMapMetadata = crabbyavif::CRABBY_AVIF_TRUE;
     }
 
     avif_io_.destroy = nullptr;
     avif_io_.read = ReadFromSegmentReader;
     avif_io_.write = nullptr;
-    avif_io_.persistent = AVIF_FALSE;
+    avif_io_.persistent = crabbyavif::CRABBY_AVIF_FALSE;
     avif_io_.data = &avif_io_data_;
-    avifDecoderSetIO(decoder_.get(), &avif_io_);
+    crabbyavif::crabby_avifDecoderSetIO(decoder_.get(), &avif_io_);
   }
 
   // If all data is received, there is no point in decoding progressively.
   decoder_->allowProgressive = !IsAllDataReceived();
 
-  auto ret = avifDecoderParse(decoder_.get());
-  if (ret == AVIF_RESULT_WAITING_ON_IO) {
+  auto ret = crabbyavif::crabby_avifDecoderParse(decoder_.get());
+  if (ret == crabbyavif::AVIF_RESULT_WAITING_ON_IO) {
     return true;
   }
-  if (ret != AVIF_RESULT_OK) {
-    DVLOG(1) << "avifDecoderParse failed: " << avifResultToString(ret);
+  if (ret != crabbyavif::AVIF_RESULT_OK) {
+    DVLOG(1) << "crabbyavif::crabby_avifDecoderParse failed: "
+             << crabbyavif::crabby_avifResultToString(ret);
     return false;
   }
 
-  // Image metadata is available in decoder_->image after avifDecoderParse()
-  // even though decoder_->imageIndex is invalid (-1).
+  // Image metadata is available in decoder_->image after
+  // crabbyavif::crabby_avifDecoderParse() even though decoder_->imageIndex is
+  // invalid (-1).
   DCHECK_EQ(decoder_->imageIndex, -1);
   // This variable is named |container| to emphasize the fact that the current
   // contents of decoder_->image come from the container, not any frame.
@@ -890,7 +915,8 @@
   }
 
   DCHECK_GT(decoder_->imageCount, 0);
-  progressive_ = decoder_->progressiveState == AVIF_PROGRESSIVE_STATE_ACTIVE;
+  progressive_ =
+      decoder_->progressiveState == crabbyavif::AVIF_PROGRESSIVE_STATE_ACTIVE;
   // If the image is progressive, decoder_->imageCount is the number of
   // progressive frames, but there is only one still image.
   decoded_frame_count_ = progressive_ ? 1 : decoder_->imageCount;
@@ -901,20 +927,23 @@
       ImageIsHighBitDepth() &&
       high_bit_depth_decoding_option_ == kHighBitDepthToHalfFloat;
 
-  // Verify that AVIF_PIXEL_FORMAT_{YUV444,YUV422,YUV420,YUV400} are
+  // Verify that crabbyavif::AVIF_PIXEL_FORMAT_{YUV444,YUV422,YUV420,YUV400} are
   // consecutive.
-  static_assert(AVIF_PIXEL_FORMAT_YUV422 == AVIF_PIXEL_FORMAT_YUV444 + 1);
-  static_assert(AVIF_PIXEL_FORMAT_YUV420 == AVIF_PIXEL_FORMAT_YUV422 + 1);
-  static_assert(AVIF_PIXEL_FORMAT_YUV400 == AVIF_PIXEL_FORMAT_YUV420 + 1);
-  // Assert that after avifDecoderParse() returns AVIF_RESULT_OK,
-  // decoder_->image->yuvFormat (the same as container->yuvFormat) is one of the
-  // four YUV formats in AV1.
-  CHECK(container->yuvFormat >= AVIF_PIXEL_FORMAT_YUV444 &&
-        container->yuvFormat <= AVIF_PIXEL_FORMAT_YUV400)
+  static_assert(crabbyavif::AVIF_PIXEL_FORMAT_YUV422 ==
+                crabbyavif::AVIF_PIXEL_FORMAT_YUV444 + 1);
+  static_assert(crabbyavif::AVIF_PIXEL_FORMAT_YUV420 ==
+                crabbyavif::AVIF_PIXEL_FORMAT_YUV422 + 1);
+  static_assert(crabbyavif::AVIF_PIXEL_FORMAT_YUV400 ==
+                crabbyavif::AVIF_PIXEL_FORMAT_YUV420 + 1);
+  // Assert that after crabbyavif::crabby_avifDecoderParse() returns
+  // crabbyavif::AVIF_RESULT_OK, decoder_->image->yuvFormat (the same as
+  // container->yuvFormat) is one of the four YUV formats in AV1.
+  CHECK(container->yuvFormat >= crabbyavif::AVIF_PIXEL_FORMAT_YUV444 &&
+        container->yuvFormat <= crabbyavif::AVIF_PIXEL_FORMAT_YUV400)
       << "Invalid YUV format: " << container->yuvFormat;
   avif_yuv_format_ = container->yuvFormat;
-  avifPixelFormatInfo format_info;
-  avifGetPixelFormatInfo(container->yuvFormat, &format_info);
+  crabbyavif::avifPixelFormatInfo format_info;
+  crabbyavif::crabby_avifGetPixelFormatInfo(container->yuvFormat, &format_info);
   chroma_shift_x_ = format_info.chromaShiftX;
   chroma_shift_y_ = format_info.chromaShiftY;
 
@@ -940,7 +969,8 @@
         return false;
       }
       uint32_t data_color_space = profile->GetProfile()->data_color_space;
-      const bool is_mono = container->yuvFormat == AVIF_PIXEL_FORMAT_YUV400;
+      const bool is_mono =
+          container->yuvFormat == crabbyavif::AVIF_PIXEL_FORMAT_YUV400;
       if (is_mono) {
         if (data_color_space != skcms_Signature_Gray &&
             data_color_space != skcms_Signature_RGB) {
@@ -957,9 +987,10 @@
         return false;
       }
       SetEmbeddedColorProfile(std::move(profile));
-    } else if (container->colorPrimaries != AVIF_COLOR_PRIMARIES_UNSPECIFIED ||
+    } else if (container->colorPrimaries !=
+                   crabbyavif::AVIF_COLOR_PRIMARIES_UNSPECIFIED ||
                container->transferCharacteristics !=
-                   AVIF_TRANSFER_CHARACTERISTICS_UNSPECIFIED) {
+                   crabbyavif::AVIF_TRANSFER_CHARACTERISTICS_UNSPECIFIED) {
       gfx::ColorSpace frame_cs = GetColorSpace(container);
 
       sk_sp<SkColorSpace> sk_color_space =
@@ -978,7 +1009,7 @@
   // |angle| * 90 specifies the angle of anti-clockwise rotation in degrees.
   // Legal values: [0-3].
   int angle = 0;
-  if (container->transformFlags & AVIF_TRANSFORM_IROT) {
+  if (container->transformFlags & crabbyavif::AVIF_TRANSFORM_IROT) {
     angle = container->irot.angle;
     CHECK_LT(angle, 4);
   }
@@ -987,7 +1018,7 @@
   //    0: The top and bottom parts of the image are exchanged.
   //    1: The left and right parts of the image are exchanged.
   int axis = -1;
-  if (container->transformFlags & AVIF_TRANSFORM_IMIR) {
+  if (container->transformFlags & crabbyavif::AVIF_TRANSFORM_IMIR) {
     axis = container->imir.axis;
     CHECK_LT(axis, 2);
   }
@@ -1021,8 +1052,8 @@
   // * Multi-frame images (animations) are not supported. (The DecodeToYUV()
   //   method does not have an 'index' parameter.)
   allow_decode_to_yuv_ =
-      avif_yuv_format_ != AVIF_PIXEL_FORMAT_YUV400 && !decoder_->alphaPresent &&
-      decoded_frame_count_ == 1 &&
+      avif_yuv_format_ != crabbyavif::AVIF_PIXEL_FORMAT_YUV400 &&
+      !decoder_->alphaPresent && decoded_frame_count_ == 1 &&
       GetColorSpace(container).ToSkYUVColorSpace(container->depth,
                                                  &yuv_color_space_) &&
       // TODO(crbug.com/911246): Support color space transforms for YUV decodes.
@@ -1030,7 +1061,8 @@
 
   // Record bpp information only for 8-bit, color, still images that do not have
   // alpha.
-  if (container->depth == 8 && avif_yuv_format_ != AVIF_PIXEL_FORMAT_YUV400 &&
+  if (container->depth == 8 &&
+      avif_yuv_format_ != crabbyavif::AVIF_PIXEL_FORMAT_YUV400 &&
       !decoder_->alphaPresent && decoded_frame_count_ == 1) {
     static constexpr char kType[] = "Avif";
     update_bpp_histogram_callback_ = base::BindOnce(&UpdateBppHistogram<kType>);
@@ -1040,13 +1072,14 @@
   unsigned height = container->height;
   // If the image is cropped, pass the size of the cropped image (the clean
   // aperture) to SetSize().
-  if (container->transformFlags & AVIF_TRANSFORM_CLAP) {
+  if (container->transformFlags & crabbyavif::AVIF_TRANSFORM_CLAP) {
     AVIFCleanApertureType clap_type;
-    avifCropRect crop_rect;
-    avifDiagnostics diag;
-    avifBool valid_clap = avifCropRectConvertCleanApertureBox(
-        &crop_rect, &container->clap, container->width, container->height,
-        container->yuvFormat, &diag);
+    crabbyavif::avifCropRect crop_rect;
+    crabbyavif::avifDiagnostics diag;
+    crabbyavif::avifBool valid_clap =
+        crabbyavif::crabby_avifCropRectConvertCleanApertureBox(
+            &crop_rect, &container->clap, container->width, container->height,
+            container->yuvFormat, &diag);
     if (!valid_clap) {
       DVLOG(1) << "Invalid 'clap' property: " << diag.error
                << "; showing the full image.";
@@ -1072,15 +1105,17 @@
   return SetSize(width, height);
 }
 
-avifResult AVIFImageDecoder::DecodeImage(wtf_size_t index) {
-  const auto ret = avifDecoderNthImage(decoder_.get(), index);
+crabbyavif::avifResult CrabbyAVIFImageDecoder::DecodeImage(wtf_size_t index) {
+  const auto ret =
+      crabbyavif::crabby_avifDecoderNthImage(decoder_.get(), index);
   // |index| should be less than what DecodeFrameCount() returns, so we should
-  // not get the AVIF_RESULT_NO_IMAGES_REMAINING error.
-  DCHECK_NE(ret, AVIF_RESULT_NO_IMAGES_REMAINING);
-  if (ret != AVIF_RESULT_OK && ret != AVIF_RESULT_WAITING_ON_IO) {
-    DVLOG(1) << "avifDecoderNthImage(" << index
-             << ") failed: " << avifResultToString(ret) << ": "
-             << AvifDecoderErrorMessage(decoder_.get());
+  // not get the crabbyavif::AVIF_RESULT_NO_IMAGES_REMAINING error.
+  DCHECK_NE(ret, crabbyavif::AVIF_RESULT_NO_IMAGES_REMAINING);
+  if (ret != crabbyavif::AVIF_RESULT_OK &&
+      ret != crabbyavif::AVIF_RESULT_WAITING_ON_IO) {
+    DVLOG(1) << "crabbyavif::crabby_avifDecoderNthImage(" << index
+             << ") failed: " << crabbyavif::crabby_avifResultToString(ret)
+             << ": " << AvifDecoderErrorMessage(decoder_.get());
     return ret;
   }
 
@@ -1090,25 +1125,26 @@
     DVLOG(1) << "Frame size " << image->width << "x" << image->height
              << " differs from container size " << container_width_ << "x"
              << container_height_;
-    return AVIF_RESULT_UNKNOWN_ERROR;
+    return crabbyavif::AVIF_RESULT_UNKNOWN_ERROR;
   }
   // Frame bit depth must be equal to container bit depth.
   if (image->depth != bit_depth_) {
     DVLOG(1) << "Frame bit depth must be equal to container bit depth";
-    return AVIF_RESULT_UNKNOWN_ERROR;
+    return crabbyavif::AVIF_RESULT_UNKNOWN_ERROR;
   }
   // Frame YUV format must be equal to container YUV format.
   if (image->yuvFormat != avif_yuv_format_) {
     DVLOG(1) << "Frame YUV format must be equal to container YUV format";
-    return AVIF_RESULT_UNKNOWN_ERROR;
+    return crabbyavif::AVIF_RESULT_UNKNOWN_ERROR;
   }
 
   decoded_image_ = image;
-  if ((image->transformFlags & AVIF_TRANSFORM_CLAP) && !ignore_clap_) {
+  if ((image->transformFlags & crabbyavif::AVIF_TRANSFORM_CLAP) &&
+      !ignore_clap_) {
     CropDecodedImage();
   }
 
-  if (ret == AVIF_RESULT_OK) {
+  if (ret == crabbyavif::AVIF_RESULT_OK) {
     if (IsAllDataReceived() && update_bpp_histogram_callback_) {
       std::move(update_bpp_histogram_callback_).Run(Size(), data_->size());
     }
@@ -1122,26 +1158,26 @@
   return ret;
 }
 
-void AVIFImageDecoder::CropDecodedImage() {
+void CrabbyAVIFImageDecoder::CropDecodedImage() {
   DCHECK_NE(decoded_image_, cropped_image_.get());
   if (!cropped_image_) {
-    cropped_image_.reset(avifImageCreateEmpty());
+    cropped_image_.reset(crabbyavif::crabby_avifImageCreateEmpty());
   }
-  avifCropRect rect;
+  crabbyavif::avifCropRect rect;
   rect.x = clap_origin_.x();
   rect.y = clap_origin_.y();
   rect.width = Size().width();
   rect.height = Size().height();
-  const avifResult result =
-      avifImageSetViewRect(cropped_image_.get(), decoded_image_, &rect);
-  CHECK_EQ(result, AVIF_RESULT_OK);
+  const crabbyavif::avifResult result = crabbyavif::crabby_avifImageSetViewRect(
+      cropped_image_.get(), decoded_image_, &rect);
+  CHECK_EQ(result, crabbyavif::AVIF_RESULT_OK);
   decoded_image_ = cropped_image_.get();
 }
 
-bool AVIFImageDecoder::RenderImage(const avifImage* image,
-                                   int from_row,
-                                   int* to_row,
-                                   ImageFrame* buffer) {
+bool CrabbyAVIFImageDecoder::RenderImage(const crabbyavif::avifImage* image,
+                                         int from_row,
+                                         int* to_row,
+                                         ImageFrame* buffer) {
   DCHECK_LT(from_row, *to_row);
 
   // libavif uses libyuv for the YUV 4:2:0 to RGB upsampling and/or conversion
@@ -1174,7 +1210,8 @@
   //                                           6 (*to_row)
 
   const bool use_libyuv_bilinear_upsampling =
-      !decode_to_half_float_ && image->yuvFormat == AVIF_PIXEL_FORMAT_YUV420;
+      !decode_to_half_float_ &&
+      image->yuvFormat == crabbyavif::AVIF_PIXEL_FORMAT_YUV420;
   const bool save_top_row = use_libyuv_bilinear_upsampling && from_row > 0;
   const bool postpone_bottom_row =
       use_libyuv_bilinear_upsampling &&
@@ -1197,29 +1234,32 @@
   }
 
   // Focus |image| on rows [from_row, *to_row).
-  std::unique_ptr<avifImage, decltype(&avifImageDestroy)> view(
-      nullptr, avifImageDestroy);
+  std::unique_ptr<crabbyavif::avifImage,
+                  decltype(&crabbyavif::crabby_avifImageDestroy)>
+      view(nullptr, crabbyavif::crabby_avifImageDestroy);
   if (from_row > 0 || static_cast<uint32_t>(*to_row) < image->height) {
-    const avifCropRect rect = {0, static_cast<uint32_t>(from_row), image->width,
-                               static_cast<uint32_t>(*to_row - from_row)};
-    view.reset(avifImageCreateEmpty());
-    const avifResult result = avifImageSetViewRect(view.get(), image, &rect);
-    CHECK_EQ(result, AVIF_RESULT_OK);
+    const crabbyavif::avifCropRect rect = {
+        0, static_cast<uint32_t>(from_row), image->width,
+        static_cast<uint32_t>(*to_row - from_row)};
+    view.reset(crabbyavif::crabby_avifImageCreateEmpty());
+    const crabbyavif::avifResult result =
+        crabbyavif::crabby_avifImageSetViewRect(view.get(), image, &rect);
+    CHECK_EQ(result, crabbyavif::AVIF_RESULT_OK);
     image = view.get();
   }
 
-  avifRGBImage rgb_image;
-  avifRGBImageSetDefaults(&rgb_image, image);
+  crabbyavif::avifRGBImage rgb_image;
+  crabbyavif::crabby_avifRGBImageSetDefaults(&rgb_image, image);
 
   if (decode_to_half_float_) {
     rgb_image.depth = 16;
-    rgb_image.isFloat = AVIF_TRUE;
+    rgb_image.isFloat = crabbyavif::CRABBY_AVIF_TRUE;
     rgb_image.pixels =
         reinterpret_cast<uint8_t*>(buffer->GetAddrF16(0, from_row));
     rgb_image.rowBytes = image->width * sizeof(uint64_t);
     // When decoding to half float, the pixel ordering is always RGBA on all
     // platforms.
-    rgb_image.format = AVIF_RGB_FORMAT_RGBA;
+    rgb_image.format = crabbyavif::AVIF_RGB_FORMAT_RGBA;
   } else {
     rgb_image.depth = 8;
     rgb_image.pixels = reinterpret_cast<uint8_t*>(buffer->GetAddr(0, from_row));
@@ -1230,9 +1270,9 @@
     static_assert(SK_G32_SHIFT == 8);
     static_assert(SK_A32_SHIFT == 24);
 #if SK_B32_SHIFT
-    rgb_image.format = AVIF_RGB_FORMAT_RGBA;
+    rgb_image.format = crabbyavif::AVIF_RGB_FORMAT_RGBA;
 #else
-    rgb_image.format = AVIF_RGB_FORMAT_BGRA;
+    rgb_image.format = crabbyavif::AVIF_RGB_FORMAT_BGRA;
 #endif
   }
   rgb_image.alphaPremultiplied = buffer->PremultiplyAlpha();
@@ -1243,17 +1283,18 @@
     memcpy(previous_last_decoded_row_.data(), rgb_image.pixels,
            rgb_image.rowBytes);
   }
-  const avifResult result = avifImageYUVToRGB(image, &rgb_image);
+  const crabbyavif::avifResult result =
+      crabbyavif::crabby_avifImageYUVToRGB(image, &rgb_image);
   if (save_top_row) {
     memcpy(rgb_image.pixels, previous_last_decoded_row_.data(),
            rgb_image.rowBytes);
   }
-  return result == AVIF_RESULT_OK;
+  return result == crabbyavif::AVIF_RESULT_OK;
 }
 
-void AVIFImageDecoder::ColorCorrectImage(int from_row,
-                                         int to_row,
-                                         ImageFrame* buffer) {
+void CrabbyAVIFImageDecoder::ColorCorrectImage(int from_row,
+                                               int to_row,
+                                               ImageFrame* buffer) {
   // Postprocess the image data according to the profile.
   const ColorProfileTransform* const transform = ColorTransform();
   if (!transform) {
@@ -1283,7 +1324,7 @@
   }
 }
 
-bool AVIFImageDecoder::GetGainmapInfoAndData(
+bool CrabbyAVIFImageDecoder::GetGainmapInfoAndData(
     SkGainmapInfo& out_gainmap_info,
     scoped_refptr<SegmentReader>& out_gainmap_data) const {
   CHECK(base::FeatureList::IsEnabled(features::kGainmapHdrImages));
@@ -1308,8 +1349,8 @@
   // If libavif detected a gain map, it already parsed the metadata from the
   // 'tmap' box.
   if (decoder_->gainMapPresent) {
-    const avifGainMap& gain_map = *decoder_->image->gainMap;
-    const avifGainMapMetadata& metadata = gain_map.metadata;
+    const crabbyavif::avifGainMap& gain_map = *decoder_->image->gainMap;
+    const crabbyavif::avifGainMapMetadata& metadata = gain_map.metadata;
     if (metadata.baseHdrHeadroomD == 0 || metadata.alternateHdrHeadroomD == 0) {
       DVLOG(1) << "Invalid gainmap metadata: a denominator value is zero";
       return false;
@@ -1376,25 +1417,29 @@
   // Parse the gainmap image to get the gainmap XMP.
   AvifIOData gainmap_avif_io_data(out_gainmap_data.get(), IsAllDataReceived());
 
-  avifIO gainmap_avif_io = {.destroy = nullptr,
-                            .read = ReadFromSegmentReader,
-                            .write = nullptr,
-                            .sizeHint = gainmap_avif_io_data.all_data_received
-                                            ? out_gainmap_data->size()
-                                            : kMaxAvifFileSize,
-                            .persistent = AVIF_FALSE,
-                            .data = &gainmap_avif_io_data};
-  auto decoder = std::unique_ptr<avifDecoder, void (*)(avifDecoder*)>(
-      avifDecoderCreate(), avifDecoderDestroy);
+  crabbyavif::avifIO gainmap_avif_io = {
+      .destroy = nullptr,
+      .read = ReadFromSegmentReader,
+      .write = nullptr,
+      .sizeHint = gainmap_avif_io_data.all_data_received
+                      ? out_gainmap_data->size()
+                      : kMaxAvifFileSize,
+      .persistent = crabbyavif::CRABBY_AVIF_FALSE,
+      .data = &gainmap_avif_io_data};
+  auto decoder = std::unique_ptr<crabbyavif::avifDecoder,
+                                 void (*)(crabbyavif::avifDecoder*)>(
+      crabbyavif::crabby_avifDecoderCreate(),
+      crabbyavif::crabby_avifDecoderDestroy);
   if (!decoder) {
     return false;
   }
-  avifDecoderSetIO(decoder.get(), &gainmap_avif_io);
-  const avifResult gainmap_parse_result = avifDecoderParse(decoder.get());
-  if (gainmap_parse_result == AVIF_RESULT_WAITING_ON_IO) {
+  crabbyavif::crabby_avifDecoderSetIO(decoder.get(), &gainmap_avif_io);
+  const crabbyavif::avifResult gainmap_parse_result =
+      crabbyavif::crabby_avifDecoderParse(decoder.get());
+  if (gainmap_parse_result == crabbyavif::AVIF_RESULT_WAITING_ON_IO) {
     return false;  // Not enough data.
   }
-  if (gainmap_parse_result != AVIF_RESULT_OK) {
+  if (gainmap_parse_result != crabbyavif::AVIF_RESULT_OK) {
     DVLOG(1) << "Failed to parse AVIF gainmap image";
     return false;
   }
@@ -1418,11 +1463,11 @@
   return true;
 }
 
-AVIFImageDecoder::AvifIOData::AvifIOData() = default;
-AVIFImageDecoder::AvifIOData::AvifIOData(
+CrabbyAVIFImageDecoder::AvifIOData::AvifIOData() = default;
+CrabbyAVIFImageDecoder::AvifIOData::AvifIOData(
     scoped_refptr<const SegmentReader> reader,
     bool all_data_received)
     : reader(std::move(reader)), all_data_received(all_data_received) {}
-AVIFImageDecoder::AvifIOData::~AvifIOData() = default;
+CrabbyAVIFImageDecoder::AvifIOData::~AvifIOData() = default;
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/platform/image-decoders/avif/crabbyavif_image_decoder.h b/third_party/blink/renderer/platform/image-decoders/avif/crabbyavif_image_decoder.h
index 333d9a0c..2467a0d 100644
--- a/third_party/blink/renderer/platform/image-decoders/avif/crabbyavif_image_decoder.h
+++ b/third_party/blink/renderer/platform/image-decoders/avif/crabbyavif_image_decoder.h
@@ -1,6 +1,8 @@
-// Copyright 2020 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.
+// WARNING: Auto-generated by gen_crabbyavif_wrapper.py.
+// Do not modify manually.
 
 #ifndef THIRD_PARTY_BLINK_RENDERER_PLATFORM_IMAGE_DECODERS_AVIF_CRABBYAVIF_IMAGE_DECODER_H_
 #define THIRD_PARTY_BLINK_RENDERER_PLATFORM_IMAGE_DECODERS_AVIF_CRABBYAVIF_IMAGE_DECODER_H_
@@ -12,7 +14,7 @@
 #include "third_party/blink/renderer/platform/allow_discouraged_type.h"
 #include "third_party/blink/renderer/platform/image-decoders/image_decoder.h"
 #include "third_party/blink/renderer/platform/wtf/vector.h"
-#include "third_party/libavif/src/include/avif/avif.h"
+#include "third_party/crabbyavif/src/include/avif/avif.h"
 #include "third_party/skia/include/core/SkImageInfo.h"
 #include "ui/gfx/color_space.h"
 #include "ui/gfx/geometry/point.h"
@@ -21,16 +23,16 @@
 
 class FastSharedBufferReader;
 
-class PLATFORM_EXPORT AVIFImageDecoder final : public ImageDecoder {
+class PLATFORM_EXPORT CrabbyAVIFImageDecoder final : public ImageDecoder {
  public:
-  AVIFImageDecoder(AlphaOption,
-                   HighBitDepthDecodingOption,
-                   ColorBehavior,
-                   wtf_size_t max_decoded_bytes,
-                   AnimationOption);
-  AVIFImageDecoder(const AVIFImageDecoder&) = delete;
-  AVIFImageDecoder& operator=(const AVIFImageDecoder&) = delete;
-  ~AVIFImageDecoder() override;
+  CrabbyAVIFImageDecoder(AlphaOption,
+                         HighBitDepthDecodingOption,
+                         ColorBehavior,
+                         wtf_size_t max_decoded_bytes,
+                         AnimationOption);
+  CrabbyAVIFImageDecoder(const CrabbyAVIFImageDecoder&) = delete;
+  CrabbyAVIFImageDecoder& operator=(const CrabbyAVIFImageDecoder&) = delete;
+  ~CrabbyAVIFImageDecoder() override;
 
   // ImageDecoder:
   String FilenameExtension() const override;
@@ -94,12 +96,14 @@
   void Decode(wtf_size_t) override;
   bool CanReusePreviousFrameBuffer(wtf_size_t) const override;
 
-  // Implements avifIOReadFunc, the |read| function in the avifIO struct.
-  static avifResult ReadFromSegmentReader(avifIO* io,
-                                          uint32_t read_flags,
-                                          uint64_t offset,
-                                          size_t size,
-                                          avifROData* out);
+  // Implements crabbyavif::avifIOReadFunc, the |read| function in the
+  // crabbyavif::avifIO struct.
+  static crabbyavif::avifResult ReadFromSegmentReader(
+      crabbyavif::avifIO* io,
+      uint32_t read_flags,
+      uint64_t offset,
+      size_t size,
+      crabbyavif::avifROData* out);
 
   // Creates |decoder_| if not yet created and decodes the size and frame count.
   bool UpdateDemuxer();
@@ -107,7 +111,7 @@
   // Decodes the frame at index |index| and checks if the frame's size, bit
   // depth, and YUV format matches those reported by the container. The decoded
   // frame is available in decoded_image_.
-  avifResult DecodeImage(wtf_size_t index);
+  crabbyavif::avifResult DecodeImage(wtf_size_t index);
 
   // Crops |decoded_image_|.
   void CropDecodedImage();
@@ -116,7 +120,7 @@
   // whether |image| was rendered successfully. On return, the in/out argument
   // |*to_row| may be decremented in case of subsampled chroma needing more
   // data.
-  bool RenderImage(const avifImage* image,
+  bool RenderImage(const crabbyavif::avifImage* image,
                    int from_row,
                    int* to_row,
                    ImageFrame* buffer);
@@ -143,7 +147,8 @@
   // Number of displayed rows for a non-progressive still image.
   int incrementally_displayed_height_ = 0;
   // The YUV format from the container.
-  avifPixelFormat avif_yuv_format_ = AVIF_PIXEL_FORMAT_NONE;
+  crabbyavif::avifPixelFormat avif_yuv_format_ =
+      crabbyavif::AVIF_PIXEL_FORMAT_NONE;
   wtf_size_t decoded_frame_count_ = 0;
   SkYUVColorSpace yuv_color_space_ = SkYUVColorSpace::kIdentity_SkYUVColorSpace;
   // Used to call UpdateBppHistogram<"Avif">() at most once to record the
@@ -159,15 +164,18 @@
   // A copy of decoder_->image with the width, height, and plane buffers
   // adjusted to those of the clean aperture. Used only when the image has a
   // 'clap' (clean aperture) property.
-  std::unique_ptr<avifImage, decltype(&avifImageDestroy)> cropped_image_{
-      nullptr, avifImageDestroy};
+  std::unique_ptr<crabbyavif::avifImage,
+                  decltype(&crabbyavif::crabby_avifImageDestroy)>
+      cropped_image_{nullptr, crabbyavif::crabby_avifImageDestroy};
   // Set by a successful DecodeImage() call to either decoder_->image or
   // cropped_image_.get() depending on whether the image has a 'clap' (clean
   // aperture) property.
-  raw_ptr<const avifImage, DanglingUntriaged> decoded_image_ = nullptr;
-  std::unique_ptr<avifDecoder, decltype(&avifDecoderDestroy)> decoder_{
-      nullptr, avifDecoderDestroy};
-  avifIO avif_io_ = {};
+  raw_ptr<const crabbyavif::avifImage, DanglingUntriaged> decoded_image_ =
+      nullptr;
+  std::unique_ptr<crabbyavif::avifDecoder,
+                  decltype(&crabbyavif::crabby_avifDecoderDestroy)>
+      decoder_{nullptr, crabbyavif::crabby_avifDecoderDestroy};
+  crabbyavif::avifIO avif_io_ = {};
   AvifIOData avif_io_data_;
 
   const AnimationOption animation_option_;
diff --git a/third_party/blink/renderer/platform/image-decoders/avif/crabbyavif_image_decoder_fuzzer.cc b/third_party/blink/renderer/platform/image-decoders/avif/crabbyavif_image_decoder_fuzzer.cc
index a11c4819..f6f150c 100644
--- a/third_party/blink/renderer/platform/image-decoders/avif/crabbyavif_image_decoder_fuzzer.cc
+++ b/third_party/blink/renderer/platform/image-decoders/avif/crabbyavif_image_decoder_fuzzer.cc
@@ -1,6 +1,8 @@
 // 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.
+// WARNING: Auto-generated by gen_crabbyavif_wrapper.py.
+// Do not modify manually.
 
 #include <stddef.h>
 #include <stdint.h>
@@ -8,7 +10,7 @@
 #include <memory>
 
 #include "third_party/blink/renderer/platform/graphics/color_behavior.h"
-#include "third_party/blink/renderer/platform/image-decoders/avif/avif_image_decoder.h"
+#include "third_party/blink/renderer/platform/image-decoders/avif/crabbyavif_image_decoder.h"
 #include "third_party/blink/renderer/platform/image-decoders/image_decoder.h"
 #include "third_party/blink/renderer/platform/testing/blink_fuzzer_test_support.h"
 #include "third_party/blink/renderer/platform/wtf/shared_buffer.h"
@@ -19,7 +21,7 @@
 std::unique_ptr<ImageDecoder> CreateAVIFDecoder() {
   // TODO(crbug.com/323934468): Initialize decoder settings dynamically using
   // fuzzer input.
-  return std::make_unique<AVIFImageDecoder>(
+  return std::make_unique<CrabbyAVIFImageDecoder>(
       ImageDecoder::kAlphaPremultiplied, ImageDecoder::kDefaultBitDepth,
       ColorBehavior::kTransformToSRGB, ImageDecoder::kNoDecodedImageByteLimit,
       ImageDecoder::AnimationOption::kPreferAnimation);
diff --git a/third_party/blink/renderer/platform/image-decoders/avif/crabbyavif_image_decoder_test.cc b/third_party/blink/renderer/platform/image-decoders/avif/crabbyavif_image_decoder_test.cc
index fbf5c1fe..e8a5d618 100644
--- a/third_party/blink/renderer/platform/image-decoders/avif/crabbyavif_image_decoder_test.cc
+++ b/third_party/blink/renderer/platform/image-decoders/avif/crabbyavif_image_decoder_test.cc
@@ -1,8 +1,10 @@
-// Copyright 2020 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.
+// WARNING: Auto-generated by gen_crabbyavif_wrapper.py.
+// Do not modify manually.
 
-#include "third_party/blink/renderer/platform/image-decoders/avif/avif_image_decoder.h"
+#include "third_party/blink/renderer/platform/image-decoders/avif/crabbyavif_image_decoder.h"
 
 #include <cmath>
 #include <memory>
@@ -40,7 +42,7 @@
     ImageDecoder::HighBitDepthDecodingOption high_bit_depth_option,
     ColorBehavior color_behavior,
     ImageDecoder::AnimationOption animation_option) {
-  return std::make_unique<AVIFImageDecoder>(
+  return std::make_unique<CrabbyAVIFImageDecoder>(
       alpha_option, high_bit_depth_option, color_behavior,
       ImageDecoder::kNoDecodedImageByteLimit, animation_option);
 }
@@ -739,7 +741,7 @@
   options.src_bit_depth = bit_depth;
   options.dst_bit_depth = bit_depth;
   auto transform = gfx::ColorTransform::NewColorTransform(
-      reinterpret_cast<AVIFImageDecoder*>(decoder.get())
+      reinterpret_cast<CrabbyAVIFImageDecoder*>(decoder.get())
           ->GetColorSpaceForTesting(),
       gfx::ColorSpace(), options);
   transform->Transform(rgb_pixel, 1);
@@ -912,30 +914,30 @@
     {"/images/resources/avif/small-with-gainmap-iso.avif", 1, kAnimationNone},
 };
 
-using AVIFValidImagesTest = ::testing::TestWithParam<AVIFImageParam>;
+using CrabbyAVIFValidImagesTest = ::testing::TestWithParam<AVIFImageParam>;
 
 INSTANTIATE_TEST_SUITE_P(AnimatedAVIF,
-                         AVIFValidImagesTest,
+                         CrabbyAVIFValidImagesTest,
                          ::testing::ValuesIn(kAnimatedTestParams));
 
 INSTANTIATE_TEST_SUITE_P(StaticAVIF,
-                         AVIFValidImagesTest,
+                         CrabbyAVIFValidImagesTest,
                          ::testing::ValuesIn(kStaticTestParams));
 
-TEST_P(AVIFValidImagesTest, ByteByByteDecode) {
+TEST_P(CrabbyAVIFValidImagesTest, ByteByByteDecode) {
   TestByteByByteDecode(&CreateAVIFDecoder, GetParam().path,
                        GetParam().expected_frame_count,
                        GetParam().expected_repetition_count);
 }
 
-TEST(AnimatedAVIFTests, HasMultipleSubImages) {
+TEST(CrabbyAnimatedAVIFTests, HasMultipleSubImages) {
   std::unique_ptr<ImageDecoder> decoder = CreateAVIFDecoder();
   decoder->SetData(ReadFile("/images/resources/avif/star-animated-8bpc.avif"),
                    true);
   EXPECT_TRUE(decoder->ImageHasBothStillAndAnimatedSubImages());
 }
 
-TEST(StaticAVIFTests, DoesNotHaveMultipleSubImages) {
+TEST(CrabbyStaticAVIFTests, DoesNotHaveMultipleSubImages) {
   std::unique_ptr<ImageDecoder> decoder = CreateAVIFDecoder();
   decoder->SetData(ReadFile("/images/resources/avif/"
                             "red-at-12-oclock-with-color-profile-8bpc.avif"),
@@ -943,7 +945,7 @@
   EXPECT_FALSE(decoder->ImageHasBothStillAndAnimatedSubImages());
 }
 
-TEST(StaticAVIFTests, HasTimingInformation) {
+TEST(CrabbyStaticAVIFTests, HasTimingInformation) {
   std::unique_ptr<ImageDecoder> decoder = CreateAVIFDecoder();
   decoder->SetData(ReadFile("/images/resources/avif/"
                             "red-at-12-oclock-with-color-profile-8bpc.avif"),
@@ -955,7 +957,7 @@
   EXPECT_EQ(base::TimeDelta(), decoder->FrameTimestampAtIndex(0));
 }
 
-TEST(AnimatedAVIFTests, HasTimingInformation) {
+TEST(CrabbyAnimatedAVIFTests, HasTimingInformation) {
   std::unique_ptr<ImageDecoder> decoder = CreateAVIFDecoder();
   decoder->SetData(ReadFile("/images/resources/avif/star-animated-8bpc.avif"),
                    true);
@@ -971,7 +973,7 @@
   EXPECT_EQ(kDuration, decoder->FrameDurationAtIndex(1));
 }
 
-TEST(StaticAVIFTests, NoCrashWhenCheckingForMultipleSubImages) {
+TEST(CrabbyStaticAVIFTests, NoCrashWhenCheckingForMultipleSubImages) {
   std::unique_ptr<ImageDecoder> decoder = CreateAVIFDecoder();
   constexpr char kHeader[] = {0x00, 0x00, 0x00, 0x20, 0x66, 0x74, 0x79, 0x70};
   auto buffer = SharedBuffer::Create();
@@ -982,7 +984,7 @@
 
 // TODO(ryoh): Add corrupted video tests.
 
-TEST(StaticAVIFTests, invalidImages) {
+TEST(CrabbyStaticAVIFTests, invalidImages) {
   // Image data is truncated.
   TestInvalidStaticImage(
       "/images/resources/avif/"
@@ -995,7 +997,7 @@
       ErrorPhase::kDecode);
 }
 
-TEST(StaticAVIFTests, GetAdobeGainmapInfoAndData) {
+TEST(CrabbyStaticAVIFTests, GetAdobeGainmapInfoAndData) {
   base::test::ScopedFeatureList scoped_feature_list;
   scoped_feature_list.InitWithFeatures(
       /*enabled_features=*/{features::kGainmapHdrImages,
@@ -1051,7 +1053,7 @@
   EXPECT_TRUE(gainmap_frame);
 }
 
-TEST(StaticAVIFTests, GetIsoGainmapInfoAndData) {
+TEST(CrabbyStaticAVIFTests, GetIsoGainmapInfoAndData) {
   base::test::ScopedFeatureList scoped_feature_list;
   scoped_feature_list.InitWithFeatures(
       /*enabled_features=*/{features::kGainmapHdrImages,
@@ -1107,7 +1109,7 @@
   EXPECT_TRUE(gainmap_frame);
 }
 
-TEST(StaticAVIFTests, GetIsoGainmapInfoAndDataHdrToSdr) {
+TEST(CrabbyStaticAVIFTests, GetIsoGainmapInfoAndDataHdrToSdr) {
   base::test::ScopedFeatureList scoped_feature_list;
   scoped_feature_list.InitWithFeatures(
       /*enabled_features=*/{features::kGainmapHdrImages,
@@ -1161,7 +1163,7 @@
   EXPECT_TRUE(gainmap_frame);
 }
 
-TEST(StaticAVIFTests, GetIsoGainmapColorSpaceSameICC) {
+TEST(CrabbyStaticAVIFTests, GetIsoGainmapColorSpaceSameICC) {
   base::test::ScopedFeatureList scoped_feature_list;
   scoped_feature_list.InitWithFeatures(
       /*enabled_features=*/{features::kGainmapHdrImages,
@@ -1194,7 +1196,7 @@
   }
 }
 
-TEST(StaticAVIFTests, GetIsoGainmapColorSpaceDifferentICC) {
+TEST(CrabbyStaticAVIFTests, GetIsoGainmapColorSpaceDifferentICC) {
   base::test::ScopedFeatureList scoped_feature_list;
   scoped_feature_list.InitWithFeatures(
       /*enabled_features=*/{features::kGainmapHdrImages,
@@ -1225,7 +1227,7 @@
   ExpectMatrixNear(matrix, SkNamedGamut::kDisplayP3, 0.001);
 }
 
-TEST(StaticAVIFTests, GetIsoGainmapColorSpaceDifferentCICP) {
+TEST(CrabbyStaticAVIFTests, GetIsoGainmapColorSpaceDifferentCICP) {
   base::test::ScopedFeatureList scoped_feature_list;
   scoped_feature_list.InitWithFeatures(
       /*enabled_features=*/{features::kGainmapHdrImages,
@@ -1254,7 +1256,7 @@
   ExpectMatrixNear(matrix, SkNamedGamut::kRec2020, 0.0001);
 }
 
-TEST(StaticAVIFTests, GetGainmapInfoAndDataWithFeatureDisabled) {
+TEST(CrabbyStaticAVIFTests, GetGainmapInfoAndDataWithFeatureDisabled) {
   base::test::ScopedFeatureList scoped_feature_list;
   scoped_feature_list.InitWithFeatures(
       /*enabled_features=*/{features::kGainmapHdrImages},
@@ -1274,7 +1276,7 @@
   }
 }
 
-TEST(StaticAVIFTests, GetGainmapInfoAndDataWithTruncatedData) {
+TEST(CrabbyStaticAVIFTests, GetGainmapInfoAndDataWithTruncatedData) {
   base::test::ScopedFeatureList scoped_feature_list;
   scoped_feature_list.InitWithFeatures(
       /*enabled_features=*/{features::kGainmapHdrImages,
@@ -1299,7 +1301,7 @@
   }
 }
 
-TEST(StaticAVIFTests, YUV) {
+TEST(CrabbyStaticAVIFTests, YUV) {
   // 3x3, YUV 4:2:0
   constexpr gfx::Size kUVSize420(2, 2);
   TestYUVRed("red-limited-range-420-8bpc.avif", kUVSize420);
@@ -1343,7 +1345,7 @@
   }
 }
 
-TEST(StaticAVIFTests, SizeAvailableBeforeAllDataReceived) {
+TEST(CrabbyStaticAVIFTests, SizeAvailableBeforeAllDataReceived) {
   scoped_refptr<SharedBuffer> stream_buffer = WTF::SharedBuffer::Create();
   scoped_refptr<SegmentReader> segment_reader =
       SegmentReader::CreateFromSharedBuffer(stream_buffer);
@@ -1368,7 +1370,7 @@
   EXPECT_TRUE(decoder->IsSizeAvailable());
 }
 
-TEST(StaticAVIFTests, ProgressiveDecoding) {
+TEST(CrabbyStaticAVIFTests, ProgressiveDecoding) {
   base::HistogramTester histogram_tester;
   scoped_refptr<SharedBuffer> stream_buffer = WTF::SharedBuffer::Create();
   scoped_refptr<SegmentReader> segment_reader =
@@ -1436,7 +1438,7 @@
               testing::ContainerEq(expected_counts));
 }
 
-TEST(StaticAVIFTests, IncrementalDecoding) {
+TEST(CrabbyStaticAVIFTests, IncrementalDecoding) {
   base::HistogramTester histogram_tester;
   scoped_refptr<SharedBuffer> stream_buffer = WTF::SharedBuffer::Create();
   scoped_refptr<SegmentReader> segment_reader =
@@ -1512,7 +1514,7 @@
 // Reproduces crbug.com/1402841. Decodes a large AVIF image 104 times in
 // parallel from base::ThreadPool. Should not cause temporary deadlock of
 // base::ThreadPool.
-TEST(StaticAVIFTests, ParallelDecoding) {
+TEST(CrabbyStaticAVIFTests, ParallelDecoding) {
   // The base::test::TaskEnvironment constructor creates a base::ThreadPool
   // instance with 4 foreground threads. The number 4 comes from the
   // test::TaskEnvironment::kNumForegroundThreadPoolThreads constant.
@@ -1521,7 +1523,8 @@
   // This test image is fast to decode (all neutral gray pixels) and its
   // allocation size is large enough to cause
   // media::PaintCanvasVideoRenderer::ConvertVideoFrameToRGBPixels() to pick
-  // n_tasks > 1 if AVIFImageDecoder did not pass disable_threading=true to it.
+  // n_tasks > 1 if CrabbyAVIFImageDecoder did not pass disable_threading=true
+  // to it.
   scoped_refptr<SharedBuffer> data =
       ReadFile("/images/resources/avif/gray1024x704.avif");
   ASSERT_TRUE(data.get());
@@ -1546,7 +1549,7 @@
   event.Wait();
 }
 
-TEST(StaticAVIFTests, AlphaHasNoIspeProperty) {
+TEST(CrabbyStaticAVIFTests, AlphaHasNoIspeProperty) {
   std::unique_ptr<ImageDecoder> decoder = CreateAVIFDecoder();
   decoder->SetData(ReadFile("/images/resources/avif/green-no-alpha-ispe.avif"),
                    true);
@@ -1554,7 +1557,7 @@
   EXPECT_TRUE(decoder->Failed());
 }
 
-TEST(StaticAVIFTests, UnsupportedTransferFunctionInColrProperty) {
+TEST(CrabbyStaticAVIFTests, UnsupportedTransferFunctionInColrProperty) {
   std::unique_ptr<ImageDecoder> decoder = CreateAVIFDecoder();
   decoder->SetData(
       ReadFile("/images/resources/avif/red-unsupported-transfer.avif"), true);
@@ -1562,7 +1565,7 @@
   EXPECT_TRUE(decoder->Failed());
 }
 
-TEST(StaticAVIFTests, ClapPropertyZeroOrigin) {
+TEST(CrabbyStaticAVIFTests, ClapPropertyZeroOrigin) {
   constexpr int kClapWidth = 200;
   constexpr int kClapHeight = 50;
   std::unique_ptr<ImageDecoder> decoder1 = CreateAVIFDecoder();
@@ -1603,7 +1606,7 @@
 
 // Verifies that an invalid 'clap' (clean aperture) image property is handled by
 // ignoring the 'clap' property and showing the full image.
-TEST(StaticAVIFTests, InvalidClapPropertyHandling) {
+TEST(CrabbyStaticAVIFTests, InvalidClapPropertyHandling) {
   // The first image has a valid 'clap' property. The full image has size
   // 320x280. The clean aperture has size 180x100, located at (40, 80) of the
   // full image.
@@ -1648,7 +1651,7 @@
   }
 }
 
-TEST(StaticAVIFTests, BppHistogramSmall) {
+TEST(CrabbyStaticAVIFTests, BppHistogramSmall) {
   constexpr int kImageArea = 768 * 512;  // = 393216
   constexpr int kFileSize = 25724;
   constexpr int kSample =
@@ -1657,7 +1660,7 @@
                        "Blink.DecodedImage.AvifDensity.Count.0.4MP", kSample);
 }
 
-TEST(StaticAVIFTests, BppHistogramSmall3x3) {
+TEST(CrabbyStaticAVIFTests, BppHistogramSmall3x3) {
   // The centi bpp = 318 * 100 * 8 / (3 * 3) ~= 28267, which is greater than the
   // histogram's max value (1000), so this sample goes into the overflow bucket.
   constexpr int kSample = 1000;
@@ -1665,7 +1668,7 @@
                        "Blink.DecodedImage.AvifDensity.Count.0.1MP", kSample);
 }
 
-TEST(StaticAVIFTests, BppHistogramSmall900000) {
+TEST(CrabbyStaticAVIFTests, BppHistogramSmall900000) {
   constexpr int kImageArea = 1200 * 750;  // = 900000
   constexpr int kFileSize = 8144;
   constexpr int kSample =
@@ -1674,7 +1677,7 @@
                        "Blink.DecodedImage.AvifDensity.Count.0.9MP", kSample);
 }
 
-TEST(StaticAVIFTests, BppHistogramBig) {
+TEST(CrabbyStaticAVIFTests, BppHistogramBig) {
   constexpr int kImageArea = 4032 * 3024;  // = 12192768
   constexpr int kFileSize = 88692;
   constexpr int kSample =
@@ -1683,7 +1686,7 @@
                        "Blink.DecodedImage.AvifDensity.Count.13MP", kSample);
 }
 
-TEST(StaticAVIFTests, BppHistogramBig13000000) {
+TEST(CrabbyStaticAVIFTests, BppHistogramBig13000000) {
   constexpr int kImageArea = 4000 * 3250;  // = 13000000
   constexpr int kFileSize = 16725;
   constexpr int kSample =
@@ -1692,7 +1695,7 @@
                        "Blink.DecodedImage.AvifDensity.Count.13MP", kSample);
 }
 
-TEST(StaticAVIFTests, BppHistogramHuge) {
+TEST(CrabbyStaticAVIFTests, BppHistogramHuge) {
   constexpr int kImageArea = 4624 * 3472;  // = 16054528
   constexpr int kFileSize = 20095;
   constexpr int kSample =
@@ -1701,7 +1704,7 @@
                        "Blink.DecodedImage.AvifDensity.Count.14+MP", kSample);
 }
 
-TEST(StaticAVIFTests, BppHistogramHuge13000002) {
+TEST(CrabbyStaticAVIFTests, BppHistogramHuge13000002) {
   constexpr int kImageArea = 3961 * 3282;  // = 13000002
   constexpr int kFileSize = 16379;
   constexpr int kSample =
@@ -1710,7 +1713,7 @@
                        "Blink.DecodedImage.AvifDensity.Count.14+MP", kSample);
 }
 
-TEST(StaticAVIFTests, BppHistogramInvalid) {
+TEST(CrabbyStaticAVIFTests, BppHistogramInvalid) {
   base::HistogramTester histogram_tester;
   std::unique_ptr<ImageDecoder> decoder = CreateAVIFDecoder();
   decoder->SetData(
@@ -1731,33 +1734,34 @@
               testing::ContainerEq(empty_counts));
 }
 
-TEST(StaticAVIFTests, BppHistogram10bit) {
+TEST(CrabbyStaticAVIFTests, BppHistogram10bit) {
   TestAvifBppHistogram("/images/resources/avif/red-full-range-420-10bpc.avif");
 }
 
-TEST(StaticAVIFTests, BppHistogramMonochrome) {
+TEST(CrabbyStaticAVIFTests, BppHistogramMonochrome) {
   TestAvifBppHistogram("/images/resources/avif/silver-400-matrix-6.avif");
 }
 
-TEST(StaticAVIFTests, BppHistogramAlpha) {
+TEST(CrabbyStaticAVIFTests, BppHistogramAlpha) {
   TestAvifBppHistogram("/images/resources/avif/red-with-alpha-8bpc.avif");
 }
 
-TEST(StaticAVIFTests, BppHistogramAnimated) {
+TEST(CrabbyStaticAVIFTests, BppHistogramAnimated) {
   TestAvifBppHistogram("/images/resources/avif/star-animated-8bpc.avif");
 }
 
-using StaticAVIFColorTests = ::testing::TestWithParam<StaticColorCheckParam>;
+using CrabbyStaticAVIFColorTests =
+    ::testing::TestWithParam<StaticColorCheckParam>;
 
 INSTANTIATE_TEST_SUITE_P(Parameterized,
-                         StaticAVIFColorTests,
+                         CrabbyStaticAVIFColorTests,
                          ::testing::ValuesIn(kTestParams));
 
-TEST_P(StaticAVIFColorTests, InspectImage) {
+TEST_P(CrabbyStaticAVIFColorTests, InspectImage) {
   InspectImage(GetParam(), ImageDecoder::kDefaultBitDepth);
 }
 
-TEST_P(StaticAVIFColorTests, InspectImageHalfFloat) {
+TEST_P(CrabbyStaticAVIFColorTests, InspectImageHalfFloat) {
   InspectImage(GetParam(), ImageDecoder::kHighBitDepthToHalfFloat);
 }
 
diff --git a/third_party/blink/renderer/platform/image-decoders/avif/gen_crabbyavif_wrapper.py b/third_party/blink/renderer/platform/image-decoders/avif/gen_crabbyavif_wrapper.py
new file mode 100644
index 0000000..b3c9f0d
--- /dev/null
+++ b/third_party/blink/renderer/platform/image-decoders/avif/gen_crabbyavif_wrapper.py
@@ -0,0 +1,162 @@
+# 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.
+"""Script to generate crabbyavif wrapper using the libavif wrapper as the base.
+
+When the libavif wrapper files (any of avif_image_decoder*) are changed, this
+script must be run to update the crabbyavif wrappers
+(crabbyavif_image_decoder*).
+"""
+
+import os
+import re
+
+
+def _read_file(filename):
+    with open(filename) as file:
+        return file.read()
+
+
+def _write_file(filename, contents):
+    with open(filename, "w") as file:
+        file.write(contents)
+
+
+def _apply_replacements(contents, replacements):
+    for find, replace in replacements:
+        contents = re.sub(find, replace, contents)
+    return contents
+
+
+_COMMON_REPLACEMENTS = (
+    (r"Copyright 2020", "Copyright 2024"),
+    (r"third_party/libavif/src", "third_party/crabbyavif/src"),
+    (r"avif_image_decoder.h", "crabbyavif_image_decoder.h"),
+    (r"AVIFImageDecoder", "CrabbyAVIFImageDecoder"),
+    (r"AVIF_TRUE", "CRABBY_AVIF_TRUE"),
+    (r"AVIF_FALSE", "CRABBY_AVIF_FALSE"),
+    (r"AVIF_REPETITION_COUNT_", "CRABBY_AVIF_REPETITION_COUNT_"),
+)
+
+_NOTICE = """// WARNING: Auto-generated by gen_crabbyavif_wrapper.py.
+// Do not modify manually.
+"""
+
+
+def _generate_crabbyavif_file(source_file, replacements):
+    contents = _read_file(source_file)
+    contents = _apply_replacements(contents, _COMMON_REPLACEMENTS)
+    contents = _apply_replacements(contents, replacements)
+    contents = contents.split("\n")
+    # Copyright notice is 3 lines, insert the notice as the 4th line.
+    contents.insert(3, _NOTICE)
+    contents = "\n".join(contents)
+    crabby_source_file = "crabby%(source_file)s" % locals()
+    _write_file(crabby_source_file, contents)
+    os.system("clang-format -style chromium -i %(crabby_source_file)s" %
+              locals())
+
+
+_HEADER_REPLACEMENTS = (
+    (r"AVIF_AVIF_IMAGE_DECODER_H_", "AVIF_CRABBYAVIF_IMAGE_DECODER_H_"),
+    (r"AVIF_PIXEL_FORMAT_NONE", "crabbyavif::AVIF_PIXEL_FORMAT_NONE"),
+    # Functions
+    (r"avifDecoderDestroy", "crabbyavif::crabby_avifDecoderDestroy"),
+    (r"avifImageDestroy", "crabbyavif::crabby_avifImageDestroy"),
+    # Types
+    (r"avifIO", "crabbyavif::avifIO"),
+    (r"avifPixelFormat", "crabbyavif::avifPixelFormat"),
+    (r"avifROData", "crabbyavif::avifROData"),
+    (r"avifResult", "crabbyavif::avifResult"),
+    (r"\bavifDecoder\b", "crabbyavif::avifDecoder"),
+    (r"\bavifImage\b", "crabbyavif::avifImage"),
+)
+
+_CC_REPLACEMENTS = (
+    # Functions (to be namespaced and prefixed with "crabby_")
+    (
+        r"\bavifCropRectConvertCleanApertureBox\b",
+        "crabbyavif::crabby_avifCropRectConvertCleanApertureBox",
+    ),
+    (r"\bavifDecoderCreate\b", "crabbyavif::crabby_avifDecoderCreate"),
+    (
+        r"\bavifDecoderDecodedRowCount\b",
+        "crabbyavif::crabby_avifDecoderDecodedRowCount",
+    ),
+    (r"\bavifDecoderDestroy\b", "crabbyavif::crabby_avifDecoderDestroy"),
+    (r"\bavifDecoderNthImage\b", "crabbyavif::crabby_avifDecoderNthImage"),
+    (
+        r"\bavifDecoderNthImageMaxExtent\b",
+        "crabbyavif::crabby_avifDecoderNthImageMaxExtent",
+    ),
+    (
+        r"\bavifDecoderNthImageMaxExtent\b",
+        "crabbyavif::crabby_avifDecoderNthImageMaxExtent",
+    ),
+    (
+        r"\bavifDecoderNthImageTiming\b",
+        "crabbyavif::crabby_avifDecoderNthImageTiming",
+    ),
+    (r"\bavifDecoderParse\b", "crabbyavif::crabby_avifDecoderParse"),
+    (r"\bavifDecoderSetIO\b", "crabbyavif::crabby_avifDecoderSetIO"),
+    (r"\bavifDecoderSetSource\b", "crabbyavif::crabby_avifDecoderSetSource"),
+    (
+        r"\bavifGetPixelFormatInfo\b",
+        "crabbyavif::crabby_avifGetPixelFormatInfo",
+    ),
+    (r"\bavifImageCreateEmpty\b", "crabbyavif::crabby_avifImageCreateEmpty"),
+    (r"\bavifImageDestroy\b", "crabbyavif::crabby_avifImageDestroy"),
+    (r"\bavifImageSetViewRect\b", "crabbyavif::crabby_avifImageSetViewRect"),
+    (r"\bavifImageYUVToRGB\b", "crabbyavif::crabby_avifImageYUVToRGB"),
+    (
+        r"\bavifPeekCompatibleFileType\b",
+        "crabbyavif::crabby_avifPeekCompatibleFileType",
+    ),
+    (
+        r"\bavifRGBImageSetDefaults\b",
+        "crabbyavif::crabby_avifRGBImageSetDefaults",
+    ),
+    (r"\bavifResultToString\b", "crabbyavif::crabby_avifResultToString"),
+    # Symbols (to be namespaced).
+    (r"avifBool\b", "crabbyavif::avifBool"),
+    (r"avifColorPrimaries\b", "crabbyavif::avifColorPrimaries"),
+    (r"avifCropRect\b", "crabbyavif::avifCropRect"),
+    (r"avifDecoder\b", "crabbyavif::avifDecoder"),
+    (r"avifDiagnostics\b", "crabbyavif::avifDiagnostics"),
+    (r"avifExtent\b", "crabbyavif::avifExtent"),
+    (r"avifGainMap\b", "crabbyavif::avifGainMap"),
+    (r"avifGainMapMetadata\b", "crabbyavif::avifGainMapMetadata"),
+    (r"avifIO\b", "crabbyavif::avifIO"),
+    (r"avifImage\b", "crabbyavif::avifImage"),
+    (r"avifImageTiming\b", "crabbyavif::avifImageTiming"),
+    (r"avifMatrixCoefficients\b", "crabbyavif::avifMatrixCoefficients"),
+    (r"avifPixelFormatInfo\b", "crabbyavif::avifPixelFormatInfo"),
+    (r"avifRGBImage\b", "crabbyavif::avifRGBImage"),
+    (r"avifROData\b", "crabbyavif::avifROData"),
+    (r"avifRange\b", "crabbyavif::avifRange"),
+    (r"avifResult\b", "crabbyavif::avifResult"),
+    (
+        r"avifTransferCharacteristics\b",
+        "crabbyavif::avifTransferCharacteristics",
+    ),
+    (r"\bAVIF_", "crabbyavif::AVIF_"),
+    (r"\bCRABBY_AVIF_", "crabbyavif::CRABBY_AVIF_"),
+)
+
+_TEST_REPLACEMENTS = (
+    (r"\bAVIFValidImagesTest\b", "CrabbyAVIFValidImagesTest"),
+    (r"\bAnimatedAVIFTests\b", "CrabbyAnimatedAVIFTests"),
+    (r"\bStaticAVIFColorTests\b", "CrabbyStaticAVIFColorTests"),
+    (r"\bStaticAVIFTests\b", "CrabbyStaticAVIFTests"),
+)
+
+
+def main():
+    _generate_crabbyavif_file("avif_image_decoder.h", _HEADER_REPLACEMENTS)
+    _generate_crabbyavif_file("avif_image_decoder.cc", _CC_REPLACEMENTS)
+    _generate_crabbyavif_file("avif_image_decoder_test.cc", _TEST_REPLACEMENTS)
+    _generate_crabbyavif_file("avif_image_decoder_fuzzer.cc", [])
+
+
+if __name__ == "__main__":
+    main()
diff --git a/third_party/blink/renderer/platform/image-decoders/image_decoder.cc b/third_party/blink/renderer/platform/image-decoders/image_decoder.cc
index d22cd5c0..dcd0573 100644
--- a/third_party/blink/renderer/platform/image-decoders/image_decoder.cc
+++ b/third_party/blink/renderer/platform/image-decoders/image_decoder.cc
@@ -31,6 +31,7 @@
 #include "media/media_buildflags.h"
 #include "skia/ext/cicp.h"
 #include "third_party/blink/public/common/buildflags.h"
+#include "third_party/blink/public/common/features.h"
 #include "third_party/blink/public/platform/platform.h"
 #include "third_party/blink/renderer/platform/image-decoders/bmp/bmp_image_decoder.h"
 #include "third_party/blink/renderer/platform/image-decoders/exif_reader.h"
@@ -46,6 +47,7 @@
 
 #if BUILDFLAG(ENABLE_AV1_DECODER)
 #include "third_party/blink/renderer/platform/image-decoders/avif/avif_image_decoder.h"
+#include "third_party/blink/renderer/platform/image-decoders/avif/crabbyavif_image_decoder.h"
 #endif
 
 namespace blink {
@@ -188,7 +190,9 @@
     return "image/bmp";
   }
 #if BUILDFLAG(ENABLE_AV1_DECODER)
-  if (AVIFImageDecoder::MatchesAVIFSignature(fast_reader)) {
+  if (base::FeatureList::IsEnabled(blink::features::kCrabbyAvif)
+          ? CrabbyAVIFImageDecoder::MatchesAVIFSignature(fast_reader)
+          : AVIFImageDecoder::MatchesAVIFSignature(fast_reader)) {
     return "image/avif";
   }
 #endif
@@ -294,9 +298,15 @@
                                                 max_decoded_bytes);
 #if BUILDFLAG(ENABLE_AV1_DECODER)
   } else if (mime_type == "image/avif") {
-    decoder = std::make_unique<AVIFImageDecoder>(
-        alpha_option, high_bit_depth_decoding_option, color_behavior,
-        max_decoded_bytes, animation_option);
+    if (base::FeatureList::IsEnabled(blink::features::kCrabbyAvif)) {
+      decoder = std::make_unique<CrabbyAVIFImageDecoder>(
+          alpha_option, high_bit_depth_decoding_option, color_behavior,
+          max_decoded_bytes, animation_option);
+    } else {
+      decoder = std::make_unique<AVIFImageDecoder>(
+          alpha_option, high_bit_depth_decoding_option, color_behavior,
+          max_decoded_bytes, animation_option);
+    }
 #endif
   }
 
diff --git a/third_party/blink/renderer/platform/runtime_enabled_features.json5 b/third_party/blink/renderer/platform/runtime_enabled_features.json5
index 722991b7..ed21360f 100644
--- a/third_party/blink/renderer/platform/runtime_enabled_features.json5
+++ b/third_party/blink/renderer/platform/runtime_enabled_features.json5
@@ -685,9 +685,8 @@
       status: "stable",
     },
     {
-      name: "ConcurrentViewTransitions",
+      name: "ConcurrentViewTransitionsSPA",
       status: "test",
-      implied_by: ["ViewTransitionOnNavigation"],
     },
     {
       name: "ContactsManager",
@@ -1373,7 +1372,6 @@
       // https://chromestatus.com/feature/5113053598711808
       name: "DocumentRenderBlocking",
       status: "stable",
-      implied_by: ["ViewTransitionOnNavigation"],
     },
     {
       name: "DocumentWrite",
@@ -2868,17 +2866,15 @@
     {
       name: "PageRevealEvent",
       status: "stable",
-      implied_by: ["ViewTransitionOnNavigation"],
     },
     {
       name: "PageSwapEvent",
       status: "stable",
-      implied_by: ["ViewTransitionOnNavigation"],
     },
     {
       name: "PaintHoldingForIframes",
       status: "test",
-      implied_by: ["ViewTransitionOnNavigation"],
+      implied_by: ["ViewTransitionOnNavigationForIframes"],
     },
     {
       name: "PaintHoldingForLocalIframes",
@@ -3065,6 +3061,12 @@
       origin_trial_allows_third_party: true,
     },
     {
+      // Controls whether filtering IDs can be specified for Private Aggregation
+      // contributions. If disabled, any IDs will be ignored.
+      name: "PrivateAggregationApiFilteringIds",
+      status: "experimental",
+    },
+    {
       name: "PrivateAggregationAuctionReportBuyerDebugModeConfig",
       status: "stable",
     },
@@ -4124,6 +4126,11 @@
       // cross-document transitions.
       // See https://drafts.csswg.org/css-view-transitions-1/.
       name: "ViewTransitionOnNavigation",
+      status: "stable",
+    },
+    {
+      name: "ViewTransitionOnNavigationForIframes",
+      status: "test",
     },
     {
       // https://chromestatus.com/feature/5089552511533056
diff --git a/third_party/blink/web_tests/NeverFixTests b/third_party/blink/web_tests/NeverFixTests
index c8d8aa1..9214ef9 100644
--- a/third_party/blink/web_tests/NeverFixTests
+++ b/third_party/blink/web_tests/NeverFixTests
@@ -2703,6 +2703,8 @@
 # WebNN feature work in-progress.
 crbug.com/1472888 [ Mac ] virtual/webnn-service-with-gpu/external/wpt/webnn/conformance_tests/buffer.https.any.html?gpu [ Skip ]
 crbug.com/1472888 [ Mac ] virtual/webnn-service-with-gpu/external/wpt/webnn/conformance_tests/buffer.https.any.worker.html?gpu [ Skip ]
+crbug.com/1472888 [ Linux ] virtual/webnn-service-without-gpu/external/wpt/webnn/conformance_tests/buffer.https.any.html?gpu [ Skip ]
+crbug.com/1472888 [ Linux ] virtual/webnn-service-without-gpu/external/wpt/webnn/conformance_tests/buffer.https.any.worker.html?gpu [ 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 56af60c..696ab13 100644
--- a/third_party/blink/web_tests/TestExpectations
+++ b/third_party/blink/web_tests/TestExpectations
@@ -279,10 +279,23 @@
 # These tests are flaky with BFCache, which is enabled by default.
 crbug.com/1311546 http/tests/test-runner/back-forward.html [ Failure Pass ]
 
+crbug.com/1078927 fragmentation/border-spacing-break-before-unbreakable-row.html [ Failure ]
+crbug.com/1396218 fragmentation/fragmented-rowspan-alignment.html [ Failure ]
+crbug.com/1396218 fragmentation/fragmented-rowspan.html [ Failure ]
+crbug.com/1078927 fragmentation/no-repeating-table-header-after-sections.html [ Failure ]
+crbug.com/1352931 fragmentation/repeating-thead-under-repeating-thead.html [ Failure ]
+crbug.com/1347984 fragmentation/table-in-subpixel-fragmentainer.html [ Failure ]
+crbug.com/1396218 fragmentation/table-overlapping-rowspan.html [ Failure ]
+crbug.com/1347984 fragmentation/table-row-dimensions-break-freely.html [ Failure ]
+crbug.com/1347984 fragmentation/table-row-dimensions-with-thead.html [ Failure ]
+crbug.com/1347984 fragmentation/table-row-dimensions.html [ Failure ]
+
+crbug.com/1225630 fast/multicol/flexbox/doubly-nested-with-zero-width-flexbox-and-forced-break-crash.html [ Skip Timeout ]
 crbug.com/1225630 fast/multicol/infinite-height-causing-fractional-row-height-crash.html [ Skip Timeout ]
 crbug.com/1225630 fast/multicol/infinitely-tall-content-in-outer-crash.html [ Skip Timeout ]
 crbug.com/807395 fast/multicol/mixed-opacity-test.html [ Failure ]
 crbug.com/1225630 fast/multicol/nested-very-tall-inside-short-crash.html [ Skip Timeout ]
+crbug.com/1078927 fast/multicol/table-row-height-increase.html [ Failure ]
 
 # View transition failures
 crbug.com/1351556 virtual/view-transition/view-transition/capture-callback-exception.html [ Failure ]
@@ -989,16 +1002,19 @@
 # `text-decoration-skip-ink: all` not implemented yet.
 crbug.com/1054656 external/wpt/css/css-text-decor/text-decoration-skip-ink-005.html [ Crash Failure Pass Timeout ]
 
+crbug.com/338842128 fast/table/border-collapsing/composited-cell-collapsed-border.html [ Failure ]
+crbug.com/338842128 fast/table/border-collapsing/composited-row-collapsed-border.html [ Failure ]
 
 ### external/wpt/css/css-tables/
+crbug.com/694374 external/wpt/css/css-tables/anonymous-table-ws-001.html [ Failure ]
 crbug.com/708345 external/wpt/css/css-tables/height-distribution/extra-height-given-to-all-row-groups-004.html [ Failure ]
 crbug.com/174167 external/wpt/css/css-tables/visibility-collapse-colspan-003.html [ Failure ]
 crbug.com/1179497 external/wpt/css/css-tables/col-definite-max-size-001.html [ Failure ]
 crbug.com/1179497 external/wpt/css/css-tables/col-definite-min-size-001.html [ Failure ]
 crbug.com/1179497 external/wpt/css/css-tables/col-definite-size-001.html [ Failure ]
+crbug.com/1171616 external/wpt/css/css-tables/height-distribution/percentage-sizing-of-table-cell-children.html [ Failure ]
 crbug.com/910725 external/wpt/css/css-tables/tentative/paint/overflow-hidden-table.html [ Failure ]
-
-crbug.com/694374 external/wpt/css/css-tables/anonymous-table-ws-001.html [ Failure ]
+crbug.com/40736545 external/wpt/css/css-tables/visibility-collapse-rowspan-005.html [ Failure ]
 crbug.com/694374 external/wpt/css/css-tables/whitespace-001.html [ Failure ]
 
 # [css-align]
@@ -1217,6 +1233,7 @@
 crbug.com/591099 virtual/text-antialias/font-format-support-color-cff2-vertical.html [ Failure ]
 crbug.com/591099 virtual/text-antialias/whitespace/018.html [ Failure ]
 crbug.com/591099 fast/writing-mode/auto-sizing-orthogonal-flows.html [ Failure ]
+crbug.com/982194 [ Win10.20h2 ] fast/writing-mode/border-image-vertical-lr.html [ Failure Pass ]
 crbug.com/591099 fast/writing-mode/percentage-height-orthogonal-writing-modes.html [ Failure ]
 crbug.com/835484 paint/invalidation/outline/inline-focus.html [ Failure ]
 crbug.com/591099 paint/invalidation/scroll/fixed-under-composited-fixed-scrolled.html [ Failure ]
@@ -1319,47 +1336,9 @@
 crbug.com/1512722 external/wpt/html/semantics/permission-element/negative-offset-and-margin.html [ Crash Failure Pass ]
 crbug.com/1512722 external/wpt/html/semantics/permission-element/no-end-tag-no-contents.html [ Crash Failure Pass ]
 
-### With LayoutNGFragmentItem enabled
-crbug.com/982194 [ Win10.20h2 ] fast/writing-mode/border-image-vertical-lr.html [ Failure Pass ]
-
-# Known issues with offsetTop/offsetLeft in a fragmented context, see crbug.com/1347984
-crbug.com/1347984 fragmentation/table-in-subpixel-fragmentainer.html [ Failure ]
-crbug.com/1347984 fragmentation/table-row-dimensions-break-freely.html [ Failure ]
-crbug.com/1347984 fragmentation/table-row-dimensions-with-thead.html [ Failure ]
-crbug.com/1347984 fragmentation/table-row-dimensions.html [ Failure ]
-
-# Rowspan cells need to grow for fragmentation expansion.
-crbug.com/1396218 fragmentation/fragmented-rowspan-alignment.html [ Failure ]
-crbug.com/1396218 fragmentation/fragmented-rowspan.html [ Failure ]
-crbug.com/1396218 fragmentation/table-overlapping-rowspan.html [ Failure ]
-
-### Tests failing with LayoutNGFlexFragmentation enabled:
-crbug.com/1367912 external/wpt/css/css-break/flexbox/multi-line-row-flex-fragmentation-058.html [ Failure ]
-crbug.com/1367912 external/wpt/css/css-break/flexbox/single-line-column-flex-fragmentation-043.html [ Failure ]
-crbug.com/1509077 external/wpt/css/css-break/flexbox/single-line-column-flex-fragmentation-065-print.html [ Failure ]
-crbug.com/1509077 external/wpt/css/css-break/flexbox/single-line-row-flex-fragmentation-045-print.html [ Failure ]
-crbug.com/1225630 fast/multicol/flexbox/doubly-nested-with-zero-width-flexbox-and-forced-break-crash.html [ Skip Timeout ]
-
-### Tests failing with LayoutNGTableFragmentation enabled:
-crbug.com/1078927 external/wpt/css/css-multicol/table/balance-table-with-border-spacing.html [ Failure ]
-crbug.com/1078927 fast/multicol/table-row-height-increase.html [ Failure ]
-crbug.com/1078927 fragmentation/border-spacing-break-before-unbreakable-row.html [ Failure ]
-crbug.com/1078927 fragmentation/no-repeating-table-header-after-sections.html [ Failure ]
-crbug.com/1352931 fragmentation/repeating-thead-under-repeating-thead.html [ Failure ]
-
 ### TablesNG
 # crbug.com/958381
 
-# TODO fails because cell size with only input element is 18px, not 15. line-height: 0px fixes it.
-crbug.com/1171616 external/wpt/css/css-tables/height-distribution/percentage-sizing-of-table-cell-children.html [ Failure ]
-
-# Composited background painting leaves gaps.
-crbug.com/958381 fast/table/border-collapsing/composited-cell-collapsed-border.html [ Failure ]
-crbug.com/958381 fast/table/border-collapsing/composited-row-collapsed-border.html [ Failure ]
-
-# Rowspanned cell overflows TD visible rect.
-crbug.com/958381 external/wpt/css/css-tables/visibility-collapse-rowspan-005.html [ Failure ]
-
 # Mac failures - antialiasing of ref
 crbug.com/958381 [ Mac ] virtual/text-antialias/hyphen-min-preferred-width.html [ Failure ]
 crbug.com/1449411 [ Mac13 ] virtual/text-antialias/word-break-soft-hyphen.html [ Failure Pass ]
@@ -3389,7 +3368,11 @@
 crbug.com/481431 external/wpt/css/css-break/box-decoration-break-clone-001.html [ Failure ]
 crbug.com/481431 external/wpt/css/css-break/box-decoration-break-clone-002.html [ Failure ]
 crbug.com/481431 external/wpt/css/css-break/box-decoration-break-clone-004.html [ Failure ]
+crbug.com/1367912 external/wpt/css/css-break/flexbox/multi-line-row-flex-fragmentation-058.html [ Failure ]
+crbug.com/1367912 external/wpt/css/css-break/flexbox/single-line-column-flex-fragmentation-043.html [ Failure ]
+crbug.com/1509077 external/wpt/css/css-break/flexbox/single-line-column-flex-fragmentation-065-print.html [ Failure ]
 crbug.com/338576660 external/wpt/css/css-break/flexbox/single-line-column-flex-fragmentation-069b-print.html [ Failure ]
+crbug.com/1509077 external/wpt/css/css-break/flexbox/single-line-row-flex-fragmentation-045-print.html [ Failure ]
 crbug.com/614667 external/wpt/css/css-break/grid/grid-item-fragmentation-039.html [ Failure ]
 crbug.com/328483408 external/wpt/css/css-break/table/table-fragmentation-003a-print.html [ Failure ]
 crbug.com/328483408 external/wpt/css/css-break/table/table-fragmentation-003b-print.html [ Failure ]
@@ -3407,6 +3390,7 @@
 crbug.com/481431 external/wpt/css/css-multicol/multicol-zero-height-003.html [ Failure ]
 crbug.com/1191124 external/wpt/css/css-multicol/spanner-fragmentation-012.html [ Failure ]
 crbug.com/1224888 external/wpt/css/css-multicol/spanner-in-opacity.html [ Failure ]
+crbug.com/1078927 external/wpt/css/css-multicol/table/balance-table-with-border-spacing.html [ Failure ]
 crbug.com/1494847 external/wpt/css/css-page/layers-003-print.html [ Failure ]
 crbug.com/1494858 external/wpt/css/css-page/page-name-abspos-002-print.html [ Failure ]
 crbug.com/1494838 external/wpt/css/css-page/page-name-canvas-003-print.html [ Failure ]
@@ -7107,9 +7091,47 @@
 [ Mac14-arm64 ] webaudio/internals/audioworkletprocessor-gc.https.html [ Crash ]
 [ Mac14-arm64 ] wpt_internal/webxr/ar/iframe-oopif.sub.https.html [ Timeout ]
 [ Debug Mac14 ] accessibility/notification-listeners.html [ Failure ]
+[ Debug Mac14 ] scrollbars/scrollbar-iframe-click-does-not-blur-content.html [ Failure Pass ]
+[ Debug Mac14 ] external/wpt/css/css-contain/content-visibility/content-visibility-with-top-layer-008.html [ Failure Pass ]
+[ Debug Mac14 ] external/wpt/clipboard-apis/async-html-script-removal.https.html [ Failure Pass ]
+[ Debug Mac14 ] fast/peerconnection/RTCPeerConnection-iframe-gc.html [ Failure Pass ]
+[ Debug Mac14 ] http/tests/webfont/font-display-intervention.html [ Failure Pass ]
+[ Debug Mac14 ] virtual/shared-storage-fenced-frame-mparch/http/tests/inspector-protocol/shared-storage/shared-storage.js [ Failure Pass ]
+[ Debug Mac14 ] external/wpt/clipboard-apis/async-write-blobs-read-blobs.https.html [ Failure Pass ]
+[ Debug Mac14 ] external/wpt/css/css-fonts/font-display/font-display.html [ Failure Pass ]
+[ Debug Mac14 ] virtual/fenced-frame-mparch/external/wpt/fenced-frame/disallowed-navigations-dangling-markup-urn.https.html [ Failure Pass ]
+[ Debug Mac14 ] accessibility/aria-activedescendant-events.html [ Failure Pass ]
+[ Debug Mac14 ] virtual/unified-autoplay/external/wpt/feature-policy/feature-policy-frame-policy-timing.https.sub.html [ Failure Pass ]
+[ Debug Mac14 ] external/wpt/upgrade-insecure-requests/link-upgrade.sub.https.html [ Failure Pass ]
+[ Debug Mac14 ] media/controls/closed-captions-dynamic-update.html [ Failure Pass ]
+[ Debug Mac14 ] editing/selection/editable-links.html [ Failure Pass ]
+[ Debug Mac14 ] virtual/prefer_compositing_to_lcd_text/scrollbars/custom-scrollbar-thumb-inactive-pseudo.html [ Failure Pass ]
+[ Debug Mac14 ] virtual/raster-inducing-scroll/scrollbars/custom-scrollbar-thumb-focus-iframe-inactive-pseudo.html [ Failure Pass ]
+[ Debug Mac14 ] fast/canvas/synchronous-create-pattern.html [ Failure Pass ]
+[ Debug Mac14 ] fast/peerconnection/RTCPeerConnection-addMultipleTracks.html [ Failure Pass ]
+[ Debug Mac14 ] editing/selection/arrow_key_in_vertical_writing_mode.html [ Failure Pass ]
+[ Debug Mac14 ] fast/events/autoscroll-should-not-stop-on-keypress.html [ Failure Pass ]
+[ Debug Mac14 ] external/wpt/fledge/tentative/auction-config.https.window.html?50-last [ Failure Pass ]
+[ Debug Mac14 ] fast/peerconnection/RTCPeerConnection-addMultipleTransceivers.html [ Failure Pass ]
+[ Debug Mac14 ] http/tests/devtools/resource-tree/resource-tree-non-unique-url.js [ Failure Pass ]
+[ Debug Mac14 ] http/tests/security/filesystem-iframe-from-remote.html [ Failure Pass ]
 [ Debug Mac14-arm64 ] accessibility/notification-listeners.html [ Failure ]
 [ Debug Mac14-arm64 ] external/wpt/webxr/xrSession_requestReferenceSpace_features.https.html [ Failure ]
 [ Debug Mac14-arm64 ] media/controls/doubletap-to-jump-backwards.html [ Failure Pass Timeout ]
+[ Debug Mac14-arm64 ] fast/peerconnection/RTCPeerConnection-iframe-gc.html [ Failure Pass ]
+[ Debug Mac14-arm64 ] virtual/fenced-frame-mparch/external/wpt/fenced-frame/sandbox-attribute.https.html [ Failure Pass ]
+[ Debug Mac14-arm64 ] external/wpt/html/semantics/embedded-content/the-img-element/invisible-image.html [ Failure Pass ]
+[ Debug Mac14-arm64 ] external/wpt/webtransport/streams-close.https.any.serviceworker.html [ Failure Pass ]
+[ Debug Mac14-arm64 ] editing/selection/drag-drop-events.html [ Failure Pass ]
+[ Debug Mac14-arm64 ] external/wpt/clipboard-apis/async-write-blobs-read-blobs.https.html [ Failure Pass ]
+[ Debug Mac14-arm64 ] scrollbars/custom-scrollbar-inactive-only-on-windowinactive-selector.html [ Failure Pass ]
+[ Debug Mac14-arm64 ] scrollbars/scrollbar-iframe-click-does-not-blur-content.html [ Failure Pass ]
+[ Debug Mac14-arm64 ] virtual/shared-storage-fenced-frame-mparch/http/tests/inspector-protocol/shared-storage/shared-storage.js [ Failure Pass ]
+[ Debug Mac14-arm64 ] virtual/raster-inducing-scroll/scrollbars/custom-scrollbar-thumb-width-changed-on-inactive-pseudo.html [ Failure Pass ]
+[ Debug Mac14-arm64 ] virtual/disable-intersection-optimization/external/wpt/html/semantics/embedded-content/the-img-element/invisible-image.html [ Failure Pass ]
+[ Debug Mac14-arm64 ] fast/peerconnection/RTCPeerConnection-undelivered-events-gc.html [ Failure Pass ]
+[ Debug Mac14-arm64 ] virtual/stable/http/tests/navigation/form-targets-cross-site-frame-get.html [ Failure Pass ]
+[ Debug Mac14-arm64 ] virtual/fenced-frame-mparch/external/wpt/fenced-frame/disallowed-navigations-dangling-markup-urn.https.html [ Failure Pass ]
 crbug.com/40261737 [ Mac14-arm64 ] external/wpt/webtransport/streams-close.https.any.sharedworker.html [ Failure Pass ]
 crbug.com/40261737 [ Mac14-arm64 ] external/wpt/webtransport/streams-close.https.any.worker.html [ Failure Pass ]
 crbug.com/40912755 [ Mac14-arm64 ] http/tests/devtools/a11y-axe-core/sources/scope-pane-a11y-test.js [ Failure Pass ]
diff --git a/third_party/blink/web_tests/VirtualTestSuites b/third_party/blink/web_tests/VirtualTestSuites
index 2a717a4..5d25691 100644
--- a/third_party/blink/web_tests/VirtualTestSuites
+++ b/third_party/blink/web_tests/VirtualTestSuites
@@ -636,8 +636,7 @@
   {
     "prefix": "direct-sockets",
     "platforms": ["Linux", "Mac", "Win"],
-    "bases": ["external/wpt/direct-sockets",
-              "wpt_internal/direct-sockets"],
+    "bases": ["external/wpt/direct-sockets"],
     "exclusive_tests": "ALL",
     "args": ["--isolated-context-origins=https://web-platform.test",
              "--disable-threaded-compositing", "--disable-threaded-animation"],
@@ -1378,7 +1377,6 @@
     "owners": ["view-transitions-api@chromium.org"],
     "bases": ["external/wpt/css/css-view-transitions",
               "wpt_internal/view-transition",
-              "wpt_internal/view-transition-types",
               "view-transition",
               "inspector-protocol/css/css-get-styles-for-view-transition.js",
               "inspector-protocol/css/css-view-transition-set-style-text.js",
@@ -1395,7 +1393,7 @@
     "owners": ["view-transitions-api@chromium.org"],
     "bases": ["wpt_internal/view-transition-on-navigation"],
     "exclusive_tests": "ALL",
-    "args": ["--enable-features=ViewTransitionOnNavigation,PageSwapEvent",
+    "args": ["--enable-features=ViewTransitionOnNavigation, ViewTransitionOnNavigationForIframes",
              "--enable-threaded-compositing"],
     "expires": "December 1, 2024"
   },
@@ -1953,7 +1951,7 @@
     "exclusive_tests": "ALL",
     "args": [
       "--enable-blink-features=SpeculationRulesFetchFromHeader,SpeculationRulesDocumentRules,NoVarySearchPrefetch,SpeculationRulesNoVarySearchHint,SpeculationRulesEagerness,SpeculationRulesDocumentRulesSelectorMatches",
-      "--enable-features=PrefetchUseContentRefactor:block_until_head_eager_prefetch/true,PrefetchRedirects",
+      "--enable-features=PrefetchUseContentRefactor:block_until_head_eager_prefetch/true/block_until_head_timeout_eager_prefetch/0,PrefetchRedirects",
       "--bypass-prefetch-proxy-for-host=not-web-platform.test",
       "--disable-threaded-compositing", "--disable-threaded-animation"
     ],
@@ -1986,7 +1984,7 @@
     "exclusive_tests": "ALL",
     "args": [
       "--enable-blink-features=SpeculationRulesFetchFromHeader,SpeculationRulesDocumentRules,NoVarySearchPrefetch,SpeculationRulesNoVarySearchHint,SpeculationRulesEagerness,SpeculationRulesDocumentRulesSelectorMatches",
-      "--enable-features=PrefetchUseContentRefactor:block_until_head_eager_prefetch/true,PrefetchRedirects,PrefetchReusable",
+      "--enable-features=PrefetchUseContentRefactor:block_until_head_eager_prefetch/true/block_until_head_timeout_eager_prefetch/0,PrefetchRedirects,PrefetchReusable",
       "--bypass-prefetch-proxy-for-host=not-web-platform.test",
       "--disable-threaded-compositing", "--disable-threaded-animation"
     ],
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 c4a97e9..5a9e357 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
@@ -152593,6 +152593,19 @@
         {}
        ]
       ],
+      "mask-image-svg-viewport-changed.html": [
+       "156317b2517d639b8521d510b7e5bf3c793273ed",
+       [
+        null,
+        [
+         [
+          "/css/css-masking/clip-path/reference/green-100x100.html",
+          "=="
+         ]
+        ],
+        {}
+       ]
+      ],
       "mask-image-url-image-hash.html": [
        "b1efc90818bec85d5022a9b908a14c2e0c35ff3b",
        [
@@ -344179,6 +344192,10 @@
       "e86302f50f838a96441640c0f9a090d03ae6e900",
       []
      ],
+     "CSSStyleSheet-constructable-baseURL-expected.txt": [
+      "e0e6df8d61f13e901716a16769483e32fd922043",
+      []
+     ],
      "CSSStyleSheet-constructable-baseURL.tentative-expected.txt": [
       "e0e6df8d61f13e901716a16769483e32fd922043",
       []
@@ -382830,6 +382847,10 @@
      "d7b1f473416baf9f060e2d3d843f7943df0f25e0",
      []
     ],
+    "WEB_FEATURES.yml": [
+     "384fd1a3e54bc7784406de3651be747a69c99bad",
+     []
+    ],
     "avoid-prefetching-on-text-plain-inner.html": [
      "518e2465418ad6aaecc5a6fb3957eeef38259240",
      []
@@ -387481,6 +387502,10 @@
        ]
       }
      },
+     "WEB_FEATURES.yml": [
+      "9ddc5b400dc0e116b9c3c8a14203d3a475422c69",
+      []
+     ],
      "about-blank-replacement.https-expected.txt": [
       "329f457fa2222f17c030be4aa02bb826a36f49a7",
       []
@@ -402293,6 +402318,10 @@
      }
     },
     "modules": {
+     "WEB_FEATURES.yml": [
+      "ab73efc0d041258d2e778479391523c2960156e8",
+      []
+     ],
      "dedicated-worker-import-failure-expected.txt": [
       "5e21ea7c0e8df406b5469f3c6b7831f47ce8c721",
       []
@@ -462597,7 +462626,7 @@
       ]
      ],
      "invalidation-part-pseudo.html": [
-      "fca4a964dce5c5c61b8f4fed4ef25510a9099298",
+      "66df33a4b7019768d29ea64fda7265d63d9436fc",
       [
        null,
        {
@@ -474917,8 +474946,8 @@
        {}
       ]
      ],
-     "CSSStyleSheet-constructable-baseURL.tentative.html": [
-      "8997a59e9c18238b661c5b3592bdfd86e266b754",
+     "CSSStyleSheet-constructable-baseURL.html": [
+      "d0f0f828b03ead6083ca8e3aad6a9070bd2a1f97",
       [
        null,
        {}
diff --git a/third_party/blink/web_tests/external/wpt/css/css-page/page-margin-007-print-ref.html b/third_party/blink/web_tests/external/wpt/css/css-page/page-margin-007-print-ref.html
new file mode 100644
index 0000000..20d16df
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-page/page-margin-007-print-ref.html
@@ -0,0 +1,78 @@
+<!DOCTYPE html>
+<link rel="author" title="Morten Stenshorne" href="mailto:mstensho@chromium.org">
+<style>
+  @page {
+    size: 400px 200px;
+    margin: 0;
+  }
+  body {
+    margin: 0;
+  }
+  .pagebox {
+    display: flow-root;
+  }
+  .pagebox > div {
+    width: 300px;
+    background: gray;
+  }
+  .pagebox > div > div {
+    box-sizing: border-box;
+    border: solid;
+    width: 250px;
+    height: 100%;
+  }
+  .left {
+    margin: 50px;
+    height: 100px;
+  }
+  .left > div {
+    background: hotpink;
+  }
+  .left > div::before {
+    content: "Margins on every side.";
+  }
+  .right {
+    height: 200px;
+  }
+  .right > div {
+    background: cyan;
+  }
+  .right > div::before {
+    content: "No page margins.";
+  }
+  .first {
+    height: 200px;
+    margin-left: 50px;
+  }
+  .first > div {
+    background: yellow;
+  }
+</style>
+
+<div class="pagebox">
+  <div class="first">
+    <div>
+      Every page should have a colored box as tall as the page area (gray area).<br>
+      This particular page should have a left-margin.<br>
+      There should be 7 pages.
+    </div>
+  </div>
+</div>
+<div class="pagebox">
+  <div class="left"><div></div></div>
+</div>
+<div class="pagebox">
+  <div class="right"><div></div></div>
+</div>
+<div class="pagebox">
+  <div class="left"><div></div></div>
+</div>
+<div class="pagebox">
+  <div class="right"><div></div></div>
+</div>
+<div class="pagebox">
+  <div class="left"><div></div></div>
+</div>
+<div class="pagebox">
+  <div class="right"><div></div></div>
+</div>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-page/page-margin-007-print.html b/third_party/blink/web_tests/external/wpt/css/css-page/page-margin-007-print.html
new file mode 100644
index 0000000..c2045d0
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-page/page-margin-007-print.html
@@ -0,0 +1,62 @@
+<!DOCTYPE html>
+<link rel="author" title="Morten Stenshorne" href="mailto:mstensho@chromium.org">
+<link rel="help" href="https://drafts.csswg.org/css-page-3/#at-page-rule">
+<link rel="match" href="page-margin-007-print-ref.html">
+<style>
+  @page {
+    size: 400px 200px;
+    margin: 0;
+  }
+  @page :first {
+    margin-left: 50px;
+  }
+  @page :left {
+    margin: 50px;
+  }
+  body {
+    margin: 0;
+  }
+  .container {
+    width: 300px;
+    background: gray;
+  }
+  .container > div {
+    box-sizing: border-box;
+    border: solid;
+    width: 250px;
+  }
+  .left {
+    height: 100px;
+    background: hotpink;
+  }
+  .left::before {
+    content: "Margins on every side.";
+  }
+  .right {
+    height: 200px;
+    background: cyan;
+  }
+  .right::before {
+    content: "No page margins.";
+  }
+  .first {
+    height: 200px;
+    background: yellow;
+  }
+</style>
+
+<div class="container">
+  <div class="first">
+    Every page should have a colored box as tall as the page area (gray area).<br>
+    This particular page should have a left-margin.<br>
+    There should be 7 pages.
+  </div>
+  <div class="left"></div>
+  <div class="right"></div>
+  <div class="left"></div>
+</div>
+<div class="container" style="position:absolute;">
+  <div class="right"></div>
+  <div class="left"></div>
+  <div class="right"></div>
+</div>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-scroll-snap-2/resources/common.js b/third_party/blink/web_tests/external/wpt/css/css-scroll-snap-2/resources/common.js
index 8dce294..d95b6051 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-scroll-snap-2/resources/common.js
+++ b/third_party/blink/web_tests/external/wpt/css/css-scroll-snap-2/resources/common.js
@@ -9,7 +9,8 @@
 }
 
 function assertSnapEvent(evt, expected_ids) {
-  assert_equals(evt.bubbles, false, "snap events don't bubble");
+  assert_equals(evt.bubbles, evt.target == document,
+    "snap events don't bubble except when fired at the document");
   assert_false(evt.cancelable, "snap events are not cancelable.");
   assert_equals(evt.snapTargetBlock, expected_ids.block,
     "snap event supplied expected target in block axis");
diff --git a/third_party/blink/web_tests/external/wpt/css/css-scroll-snap-2/snapevents-at-document-bubble-to-window.html b/third_party/blink/web_tests/external/wpt/css/css-scroll-snap-2/snapevents-at-document-bubble-to-window.html
new file mode 100644
index 0000000..e50be0b
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-scroll-snap-2/snapevents-at-document-bubble-to-window.html
@@ -0,0 +1,96 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+  <meta charset="utf-8">
+  <title> CSS Scroll Snap 2 Test: snapchanged event on the document bubbles</title>
+  <link rel="help" href="https://drafts.csswg.org/css-scroll-snap-2/#snap-events">
+  <script src="/resources/testharness.js"></script>
+  <script src="/resources/testharnessreport.js"></script>
+  <script src="/css/css-scroll-snap-2/resources/common.js"></script>
+  <script src="/dom/events/scrolling/scroll_support.js"></script>
+</head>
+
+<body>
+  <style>
+    :root {
+      margin: 0;
+      padding: 0;
+      scroll-snap-type: y mandatory;
+    }
+
+    div {
+      position: absolute;
+      margin: 0px;
+    }
+
+    #spacer {
+      width: 200vw;
+      height: 200vh;
+    }
+
+    .snap_point {
+      width: 40vw;
+      height: 40vh;
+      scroll-snap-align: start;
+    }
+
+    #snap_point_1 {
+      left: 0px;
+      top: 0px;
+      background-color: red;
+    }
+
+    #snap_point_2 {
+      top: 40vh;
+      left: 40vw;
+      background-color: orange;
+    }
+
+    #snap_point_3 {
+      left: 80vw;
+      top: 80vh;
+      background-color: blue;
+    }
+  </style>
+  <div id="spacer"></div>
+  <div id="snap_point_1" class="snap_point"></div>
+  <div id="snap_point_2" class="snap_point"></div>
+  <div id="snap_point_3" class="snap_point"></div>
+
+  <script>
+
+    promise_test(async(t) => {
+      await waitForCompositorCommit();
+
+      let snapchanging_promise = waitForSnapEvent(window, "snapchanging");
+      let snapchanged_promise = waitForSnapEvent(window, "snapchanged");
+      document.scrollingElement.scrollTo(0, snap_point_2.offsetTop);
+      let snapchanging_evt = await snapchanging_promise;
+      let snapchanged_evt = await snapchanged_promise;
+
+      assertSnapEvent(snapchanging_evt, { inline: null, block: snap_point_2 });
+      assertSnapEvent(snapchanged_evt, { inline: null, block: snap_point_2 });
+    }, "snapchanged bubbles when fired at the document (addEventListener).");
+
+    promise_test(async(t) => {
+      await waitForScrollReset(t, document.scrollingElement);
+      await waitForCompositorCommit();
+
+      let snapchanging_promise = waitForSnapEvent(window, "snapchanging",
+                                    /*scroll_happens=*/true,
+                                    /*use_onsnap_member=*/true);
+      let snapchanged_promise = waitForSnapEvent(window, "snapchanged",
+                                    /*scroll_happens=*/true,
+                                    /*use_onsnap_member=*/true);
+      document.scrollingElement.scrollTo(0, snap_point_2.offsetTop);
+      let snapchanging_evt = await snapchanging_promise;
+      let snapchanged_evt = await snapchanged_promise;
+
+      assertSnapEvent(snapchanging_evt, { inline: null, block: snap_point_2 });
+      assertSnapEvent(snapchanged_evt, { inline: null, block: snap_point_2 });
+    }, "snapchanged bubbles when fired at the document (onsnapchanged).");
+  </script>
+</body>
+
+</html>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-shadow-parts/invalidation-part-pseudo.html b/third_party/blink/web_tests/external/wpt/css/css-shadow-parts/invalidation-part-pseudo.html
index fca4a96..66df33a 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-shadow-parts/invalidation-part-pseudo.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-shadow-parts/invalidation-part-pseudo.html
@@ -3,7 +3,6 @@
 <link rel="help" href="https://drafts.csswg.org/css-shadow-parts" >
 <link rel="help" href="https://drafts.csswg.org/selectors/#matches">
 <link href="https://drafts.csswg.org/selectors/#matches" rel="help">
-<link rel="match" href="interaction-with-nested-pseudo-class-ref.html">
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <script src="/resources/testdriver.js"></script>
diff --git a/third_party/blink/web_tests/wpt_internal/view-transition-types/view-transition-types-match-early-mutation.html b/third_party/blink/web_tests/external/wpt/css/css-view-transitions/view-transition-types-match-early-mutation.html
similarity index 100%
rename from third_party/blink/web_tests/wpt_internal/view-transition-types/view-transition-types-match-early-mutation.html
rename to third_party/blink/web_tests/external/wpt/css/css-view-transitions/view-transition-types-match-early-mutation.html
diff --git a/third_party/blink/web_tests/wpt_internal/view-transition-types/view-transition-types-match-early.html b/third_party/blink/web_tests/external/wpt/css/css-view-transitions/view-transition-types-match-early.html
similarity index 100%
rename from third_party/blink/web_tests/wpt_internal/view-transition-types/view-transition-types-match-early.html
rename to third_party/blink/web_tests/external/wpt/css/css-view-transitions/view-transition-types-match-early.html
diff --git a/third_party/blink/web_tests/wpt_internal/view-transition-types/view-transition-types-match-late-mutation.html b/third_party/blink/web_tests/external/wpt/css/css-view-transitions/view-transition-types-match-late-mutation.html
similarity index 100%
rename from third_party/blink/web_tests/wpt_internal/view-transition-types/view-transition-types-match-late-mutation.html
rename to third_party/blink/web_tests/external/wpt/css/css-view-transitions/view-transition-types-match-late-mutation.html
diff --git a/third_party/blink/web_tests/wpt_internal/view-transition-types/view-transition-types-matches-ref.html b/third_party/blink/web_tests/external/wpt/css/css-view-transitions/view-transition-types-matches-ref.html
similarity index 100%
rename from third_party/blink/web_tests/wpt_internal/view-transition-types/view-transition-types-matches-ref.html
rename to third_party/blink/web_tests/external/wpt/css/css-view-transitions/view-transition-types-matches-ref.html
diff --git a/third_party/blink/web_tests/wpt_internal/view-transition-types/view-transition-types-matches.html b/third_party/blink/web_tests/external/wpt/css/css-view-transitions/view-transition-types-matches.html
similarity index 100%
rename from third_party/blink/web_tests/wpt_internal/view-transition-types/view-transition-types-matches.html
rename to third_party/blink/web_tests/external/wpt/css/css-view-transitions/view-transition-types-matches.html
diff --git a/third_party/blink/web_tests/wpt_internal/view-transition-types/view-transition-types-mutable-no-document-element-crashtest.html b/third_party/blink/web_tests/external/wpt/css/css-view-transitions/view-transition-types-mutable-no-document-element-crashtest.html
similarity index 100%
rename from third_party/blink/web_tests/wpt_internal/view-transition-types/view-transition-types-mutable-no-document-element-crashtest.html
rename to third_party/blink/web_tests/external/wpt/css/css-view-transitions/view-transition-types-mutable-no-document-element-crashtest.html
diff --git a/third_party/blink/web_tests/wpt_internal/view-transition-types/view-transition-types-mutable.html b/third_party/blink/web_tests/external/wpt/css/css-view-transitions/view-transition-types-mutable.html
similarity index 100%
rename from third_party/blink/web_tests/wpt_internal/view-transition-types/view-transition-types-mutable.html
rename to third_party/blink/web_tests/external/wpt/css/css-view-transitions/view-transition-types-mutable.html
diff --git a/third_party/blink/web_tests/wpt_internal/view-transition-types/view-transition-types-one-green-square-ref.html b/third_party/blink/web_tests/external/wpt/css/css-view-transitions/view-transition-types-one-green-square-ref.html
similarity index 100%
rename from third_party/blink/web_tests/wpt_internal/view-transition-types/view-transition-types-one-green-square-ref.html
rename to third_party/blink/web_tests/external/wpt/css/css-view-transitions/view-transition-types-one-green-square-ref.html
diff --git a/third_party/blink/web_tests/wpt_internal/view-transition-types/view-transition-types-removed.html b/third_party/blink/web_tests/external/wpt/css/css-view-transitions/view-transition-types-removed.html
similarity index 100%
rename from third_party/blink/web_tests/wpt_internal/view-transition-types/view-transition-types-removed.html
rename to third_party/blink/web_tests/external/wpt/css/css-view-transitions/view-transition-types-removed.html
diff --git a/third_party/blink/web_tests/wpt_internal/view-transition-types/view-transition-types-reserved-mutation.html b/third_party/blink/web_tests/external/wpt/css/css-view-transitions/view-transition-types-reserved-mutation.html
similarity index 100%
rename from third_party/blink/web_tests/wpt_internal/view-transition-types/view-transition-types-reserved-mutation.html
rename to third_party/blink/web_tests/external/wpt/css/css-view-transitions/view-transition-types-reserved-mutation.html
diff --git a/third_party/blink/web_tests/wpt_internal/view-transition-types/view-transition-types-reserved-ref.html b/third_party/blink/web_tests/external/wpt/css/css-view-transitions/view-transition-types-reserved-ref.html
similarity index 100%
rename from third_party/blink/web_tests/wpt_internal/view-transition-types/view-transition-types-reserved-ref.html
rename to third_party/blink/web_tests/external/wpt/css/css-view-transitions/view-transition-types-reserved-ref.html
diff --git a/third_party/blink/web_tests/wpt_internal/view-transition-types/view-transition-types-reserved.html b/third_party/blink/web_tests/external/wpt/css/css-view-transitions/view-transition-types-reserved.html
similarity index 100%
rename from third_party/blink/web_tests/wpt_internal/view-transition-types/view-transition-types-reserved.html
rename to third_party/blink/web_tests/external/wpt/css/css-view-transitions/view-transition-types-reserved.html
diff --git a/third_party/blink/web_tests/wpt_internal/view-transition-types/view-transition-types-stay.html b/third_party/blink/web_tests/external/wpt/css/css-view-transitions/view-transition-types-stay.html
similarity index 100%
rename from third_party/blink/web_tests/wpt_internal/view-transition-types/view-transition-types-stay.html
rename to third_party/blink/web_tests/external/wpt/css/css-view-transitions/view-transition-types-stay.html
diff --git a/third_party/blink/web_tests/wpt_internal/view-transition-types/view-transition-types-universal-match.html b/third_party/blink/web_tests/external/wpt/css/css-view-transitions/view-transition-types-universal-match.html
similarity index 100%
rename from third_party/blink/web_tests/wpt_internal/view-transition-types/view-transition-types-universal-match.html
rename to third_party/blink/web_tests/external/wpt/css/css-view-transitions/view-transition-types-universal-match.html
diff --git a/third_party/blink/web_tests/external/wpt/css/cssom/CSSStyleSheet-constructable-baseURL-expected.txt b/third_party/blink/web_tests/external/wpt/css/cssom/CSSStyleSheet-constructable-baseURL-expected.txt
new file mode 100644
index 0000000..e0e6df8
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/cssom/CSSStyleSheet-constructable-baseURL-expected.txt
@@ -0,0 +1,9 @@
+This is a testharness.js-based test.
+[FAIL] Constructing sheet with custom base URL ueses that URL for CSS rules
+  assert_equals: expected "url(\\"http://web-platform.test:8001/custom/path/example.png\\")" but got "url(\\"http://web-platform.test:8001/css/cssom/example.png\\")"
+[FAIL] Constructing sheet with relative URL adds to the constructor document's base URL
+  assert_equals: expected "url(\\"http://web-platform.test:8001/css/cssom/custom/path/example.png\\")" but got "url(\\"http://web-platform.test:8001/css/cssom/example.png\\")"
+[FAIL] Constructing sheet with invalid base URL throws a NotAllowedError
+  assert_throws_dom: function "() => { new CSSStyleSheet({ baseURL: "https://test:test/"}) }" did not throw
+Harness: the test ran to completion.
+
diff --git a/third_party/blink/web_tests/external/wpt/css/cssom/CSSStyleSheet-constructable-baseURL.tentative.html b/third_party/blink/web_tests/external/wpt/css/cssom/CSSStyleSheet-constructable-baseURL.html
similarity index 95%
rename from third_party/blink/web_tests/external/wpt/css/cssom/CSSStyleSheet-constructable-baseURL.tentative.html
rename to third_party/blink/web_tests/external/wpt/css/cssom/CSSStyleSheet-constructable-baseURL.html
index 8997a59..d0f0f82 100644
--- a/third_party/blink/web_tests/external/wpt/css/cssom/CSSStyleSheet-constructable-baseURL.tentative.html
+++ b/third_party/blink/web_tests/external/wpt/css/cssom/CSSStyleSheet-constructable-baseURL.html
@@ -1,7 +1,7 @@
 <!DOCTYPE html>
 <title>CSSStyleSheet baseURL</title>
 <link rel="author" title="Erik Nordin" href="mailto:enordin@mozilla.com">
-<link rel="help" href="https://github.com/WICG/construct-stylesheets/issues/95#issuecomment-593545252">
+<link rel="help" href="https://drafts.csswg.org/cssom-1/#dom-cssstylesheetinit-baseurl">
 <div id="target"></div>
 <script src='/resources/testharness.js'></script>
 <script src='/resources/testharnessreport.js'></script>
diff --git a/third_party/blink/web_tests/external/wpt/direct-sockets/META.yml b/third_party/blink/web_tests/external/wpt/direct-sockets/META.yml
index 85c05e8c..ace0341 100644
--- a/third_party/blink/web_tests/external/wpt/direct-sockets/META.yml
+++ b/third_party/blink/web_tests/external/wpt/direct-sockets/META.yml
@@ -1,5 +1,3 @@
 spec: https://github.com/WICG/direct-sockets/blob/main/docs/explainer.md
 suggested_reviewers:
-  - ewilligers
-  - mgiuca
-  - phoglenix
+  - greengrape
diff --git a/third_party/blink/web_tests/external/wpt/direct-sockets/disabled-by-permissions-policy.https.sub.html b/third_party/blink/web_tests/external/wpt/direct-sockets/disabled-by-permissions-policy.https.sub.html
index f2f6e50be..cc2f58b 100644
--- a/third_party/blink/web_tests/external/wpt/direct-sockets/disabled-by-permissions-policy.https.sub.html
+++ b/third_party/blink/web_tests/external/wpt/direct-sockets/disabled-by-permissions-policy.https.sub.html
@@ -1,14 +1,26 @@
 <!DOCTYPE html>
-<html>
-  <head>
-    <meta charset="utf-8" />
-    <title>Sockets test: Can be disabled by permissions policy</title>
-    <script src="/resources/testharness.js"></script>
-    <script src="/resources/testharnessreport.js"></script>
-    <script src="/resources/testdriver.js"></script>
-    <script src="/resources/testdriver-vendor.js"></script>
-  </head>
-  <body>
-    <script src="disabled-by-permissions-policy.js"></script>
-  </body>
-</html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+
+'use strict';
+
+test(() => {
+  assert_throws_dom("NotAllowedError", () => {
+    new TCPSocket("127.0.0.1", 53);
+  }, "constructor should throw");
+}, "TCPSocket disabled by permissions-policy");
+
+test(() => {
+  assert_throws_dom("NotAllowedError", () => {
+    new UDPSocket({ remoteAddress: "127.0.0.1", remotePort: 53 });
+  }, "constructor should throw");
+}, "UDPSocket disabled by permissions-policy");
+
+test(() => {
+  assert_throws_dom("NotAllowedError", () => {
+    new TCPServerSocket("127.0.0.1");
+  }, "constructor should throw");
+}, "TCPServerSocket disabled by permissions-policy");
+
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/direct-sockets/disabled-by-permissions-policy.https.sub.html.headers b/third_party/blink/web_tests/external/wpt/direct-sockets/disabled-by-permissions-policy.https.sub.html.headers
index ba6f09f5..661a357 100644
--- a/third_party/blink/web_tests/external/wpt/direct-sockets/disabled-by-permissions-policy.https.sub.html.headers
+++ b/third_party/blink/web_tests/external/wpt/direct-sockets/disabled-by-permissions-policy.https.sub.html.headers
@@ -1,3 +1,3 @@
 Cross-Origin-Opener-Policy: same-origin
 Cross-Origin-Embedder-Policy: require-corp
-Permissions-Policy: direct-sockets=()
\ No newline at end of file
+Permissions-Policy: direct-sockets=()
diff --git a/third_party/blink/web_tests/external/wpt/direct-sockets/disabled-by-permissions-policy.js b/third_party/blink/web_tests/external/wpt/direct-sockets/disabled-by-permissions-policy.js
deleted file mode 100644
index a27d1dd..0000000
--- a/third_party/blink/web_tests/external/wpt/direct-sockets/disabled-by-permissions-policy.js
+++ /dev/null
@@ -1,8 +0,0 @@
-'use strict';
-
-test(() => {
-  assert_throws_dom("NotAllowedError", () => new TCPSocket("address.com", 53), "constructor should throw");
-}, "tcp disabled by permissions-policy");
-test(() => {
-  assert_throws_dom("NotAllowedError", () => new UDPSocket({ remoteAddress: "address.com", remotePort: 53 }), "constructor should throw");
-}, "udp disabled by permissions-policy");
diff --git a/third_party/blink/web_tests/external/wpt/direct-sockets/open-securecontext.http.html b/third_party/blink/web_tests/external/wpt/direct-sockets/open-securecontext.http.html
deleted file mode 100644
index c3a2a42..0000000
--- a/third_party/blink/web_tests/external/wpt/direct-sockets/open-securecontext.http.html
+++ /dev/null
@@ -1,21 +0,0 @@
-<!DOCTYPE html>
-<html>
-  <head>
-    <meta charset="utf-8">
-    <title>Sockets test: Open from non-secure context</title>
-    <link rel="help" href="https://github.com/WICG/direct-sockets/blob/main/docs/explainer.md#security-considerations">
-    <script src="/resources/testharness.js"></script>
-    <script src="/resources/testharnessreport.js"></script>
-  </head>
-  <body>
-    <script>
-        test(() => {
-          assert_false(typeof TCPSocket !== 'undefined', 'TCPSocket is present');
-        }, 'TCPSocket must be undefined in non-secure context');
-
-        test(() => {
-          assert_false(typeof UDPSocket !== 'undefined', 'UDPSocket is present');
-        }, 'UDPSocket must be undefined in non-secure context');
-    </script>
-  </body>
-</html>
diff --git a/third_party/blink/web_tests/external/wpt/direct-sockets/tcp_socket.https.html b/third_party/blink/web_tests/external/wpt/direct-sockets/tcp_socket.https.html
new file mode 100644
index 0000000..10d21ce
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/direct-sockets/tcp_socket.https.html
@@ -0,0 +1,68 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/test-only-api.js"></script>
+<script>
+'use strict';
+
+promise_test(async () => {
+  const kPacket = "I'm a netcat. Meow-meow!"
+
+  const serverSocket = new TCPServerSocket("127.0.0.1");
+  const { localPort } = await serverSocket.opened;
+
+  const clientSocket = new TCPSocket("127.0.0.1", localPort);
+  const acceptedSocket = await (async () => {
+    const { readable } = await serverSocket.opened;
+    const reader = readable.getReader();
+    const { value: acceptedSocket, done } = await reader.read();
+    assert_false(done);
+    reader.releaseLock();
+    return acceptedSocket;
+  })();
+
+  const encoder = new TextEncoder();
+  const decoder = new TextDecoder();
+
+  const readPacket = async socket => {
+    const { readable } = await socket.opened;
+    const reader = readable.getReader();
+    let result = "";
+    while (result.length < kPacket.length) {
+      const { value, done } = await reader.read();
+      assert_false(done);
+      result += decoder.decode(value);
+    }
+    assert_equals(result, kPacket);
+    reader.releaseLock();
+  };
+
+  const sendPacket = async socket => {
+    const { writable } = await socket.opened;
+    const writer = writable.getWriter();
+    await writer.ready;
+    await writer.write(encoder.encode(kPacket));
+    writer.releaseLock();
+  };
+
+  const acceptedSocketEcho = (async () => {
+    await readPacket(acceptedSocket);
+    await sendPacket(acceptedSocket);
+  })();
+
+  const clientSocketSend = (async () => {
+    await sendPacket(clientSocket);
+  })();
+
+  const clientSocketReceive = (async () => {
+    await readPacket(clientSocket);
+  })();
+
+  await clientSocketReceive;
+
+  await clientSocket.close();
+  await acceptedSocket.close();
+  await serverSocket.close();
+
+}, 'TCPSocket exchanges packets with TCPServerSocket');
+</script>
diff --git a/third_party/blink/web_tests/wpt_internal/direct-sockets/enabled-on-self-origin-by-permissions-policy.https.sub.html.headers b/third_party/blink/web_tests/external/wpt/direct-sockets/tcp_socket.https.html.headers
similarity index 100%
rename from third_party/blink/web_tests/wpt_internal/direct-sockets/enabled-on-self-origin-by-permissions-policy.https.sub.html.headers
rename to third_party/blink/web_tests/external/wpt/direct-sockets/tcp_socket.https.html.headers
diff --git a/third_party/blink/web_tests/external/wpt/direct-sockets/udp_socket.https.html b/third_party/blink/web_tests/external/wpt/direct-sockets/udp_socket.https.html
new file mode 100644
index 0000000..c95d41e
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/direct-sockets/udp_socket.https.html
@@ -0,0 +1,95 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/test-only-api.js"></script>
+<script>
+'use strict';
+
+promise_test(async () => {
+  const kRequiredDatagrams = 150;
+  const kRequiredBytes =
+    kRequiredDatagrams * (kRequiredDatagrams + 1) / 2;
+
+  const boundSocket = new UDPSocket({ localAddress: "127.0.0.1" });
+  const { localPort: boundLocalPort } = await boundSocket.opened;
+
+  const connectedSocket = new UDPSocket({
+    remoteAddress: "127.0.0.1",
+    remotePort: boundLocalPort,
+  });
+
+  const {
+    localAddress: clientAddress,
+    localPort: clientPort
+  } = await connectedSocket.opened;
+
+  const boundEchoLoop = (async() => {
+    let bytesEchoed = 0;
+
+    const { readable, writable } = await boundSocket.opened;
+    const reader = readable.getReader();
+    const writer = writable.getWriter();
+
+    while (bytesEchoed < kRequiredBytes) {
+      const { value: { data, remoteAddress, remotePort }, done } = await reader.read();
+      assert_false(done);
+      assert_equals(remoteAddress, clientAddress);
+      assert_equals(remotePort, clientPort);
+      for (let index = 0; index < data.length; index++) {
+        assert_equals(data[index], bytesEchoed % 256);
+        bytesEchoed++;
+      }
+      await writer.write({ data, remoteAddress, remotePort });
+    }
+
+    assert_equals(bytesEchoed, kRequiredBytes);
+    reader.releaseLock();
+    writer.releaseLock();
+  })();
+
+  const connectedSendLoop = (async () => {
+    let bytesWritten = 0;
+    let chunkLength = 0;
+
+    const { writable } = await connectedSocket.opened;
+    const writer = writable.getWriter();
+
+    while (bytesWritten < kRequiredBytes) {
+      chunkLength = Math.min(chunkLength + 1, kRequiredBytes - bytesWritten);
+      let chunk = new Uint8Array(chunkLength);
+      for (let index = 0; index < chunkLength; index++) {
+        chunk[index] = bytesWritten % 256;
+        bytesWritten++;
+      }
+      await writer.ready;
+      await writer.write({ data: chunk });
+    }
+    assert_equals(bytesWritten, kRequiredBytes);
+    writer.releaseLock();
+  })();
+
+  const connectedReadLoop = (async () => {
+    let bytesRead = 0;
+
+    const { readable } = await connectedSocket.opened;
+    const reader = readable.getReader();
+
+    while (bytesRead < kRequiredBytes) {
+      const { value: { data }, done } = await reader.read();
+      assert_false(done);
+      for (let index = 0; index < data.length; index++) {
+        assert_equals(data[index], bytesRead % 256);
+        bytesRead++;
+      }
+    }
+    assert_equals(bytesRead, kRequiredBytes);
+
+    reader.releaseLock();
+  })();
+
+  await connectedReadLoop;
+
+  await boundSocket.close();
+  await connectedSocket.close();
+}, "UDPSocket (connected) exchanges datagrams with UDPSocket (bound)");
+</script>
diff --git a/third_party/blink/web_tests/wpt_internal/direct-sockets/enabled-on-self-origin-by-permissions-policy.https.sub.html.headers b/third_party/blink/web_tests/external/wpt/direct-sockets/udp_socket.https.html.headers
similarity index 100%
copy from third_party/blink/web_tests/wpt_internal/direct-sockets/enabled-on-self-origin-by-permissions-policy.https.sub.html.headers
copy to third_party/blink/web_tests/external/wpt/direct-sockets/udp_socket.https.html.headers
diff --git a/third_party/blink/web_tests/external/wpt/dom/idlharness.window_exclude=Node-expected.txt b/third_party/blink/web_tests/external/wpt/dom/idlharness.window_exclude=Node-expected.txt
deleted file mode 100644
index fb4296ce..0000000
--- a/third_party/blink/web_tests/external/wpt/dom/idlharness.window_exclude=Node-expected.txt
+++ /dev/null
@@ -1,8 +0,0 @@
-This is a testharness.js-based test.
-Found 2 FAIL, 0 TIMEOUT, 0 NOTRUN.
-[FAIL] ShadowRoot interface: attribute serializable
-  assert_equals: setter must be undefined for readonly attributes expected (undefined) undefined but got (function) function "function set serializable() { [native code] }"
-[FAIL] Window interface: attribute event
-  assert_true: property should be enumerable expected true got false
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/external/wpt/fledge/tentative/auction-config.https.window.js b/third_party/blink/web_tests/external/wpt/fledge/tentative/auction-config.https.window.js
index 057b4d7..b5f5514 100644
--- a/third_party/blink/web_tests/external/wpt/fledge/tentative/auction-config.https.window.js
+++ b/third_party/blink/web_tests/external/wpt/fledge/tentative/auction-config.https.window.js
@@ -242,9 +242,12 @@
   }
 });
 
+// Cross-origin trustedScoringSignalsURL is fine, but it needs extra
+// headers to actually make it work. The auction here doesn't actually
+// care if the signals don't load.
 makeTest({
   name: 'trustedScoringSignalsURL is cross-origin with seller',
-  expect: EXPECT_EXCEPTION(TypeError),
+  expect: EXPECT_WINNER,
   auctionConfigOverrides: { trustedScoringSignalsURL: "https://example.com" },
 });
 
diff --git a/third_party/blink/web_tests/external/wpt/fledge/tentative/network.https.window.js b/third_party/blink/web_tests/external/wpt/fledge/tentative/network.https.window.js
index fe28776..0b41662 100644
--- a/third_party/blink/web_tests/external/wpt/fledge/tentative/network.https.window.js
+++ b/third_party/blink/web_tests/external/wpt/fledge/tentative/network.https.window.js
@@ -6,7 +6,8 @@
 // META: timeout=long
 // META: variant=?1-5
 // META: variant=?6-10
-// META: variant=?11-last
+// META: variant=?11-15
+// META: variant=?16-last
 
 "use strict";
 
@@ -37,22 +38,6 @@
   return url.toString();
 }
 
-// Delete all cookies. Separate function so that can be replaced with
-// something else for testing outside of a WPT environment.
-async function deleteAllCookies() {
-  await test_driver.delete_all_cookies();
-}
-
-// Deletes all cookies (to avoid pre-existing cookies causing inconsistent
-// output on failure) and sets a cookie with name "cookie" and a value of
-// "cookie". Adds a cleanup task to delete all cookies again when the test
-// is done.
-async function setCookie(test) {
-  await deleteAllCookies();
-  document.cookie = 'cookie=cookie; path=/'
-  test.add_cleanup(deleteAllCookies);
-}
-
 // Assert that "headers" has a single header with "name", whose value is "value".
 function assertHasHeader(headers, name, value) {
   assert_equals(JSON.stringify(headers[name]), JSON.stringify([value]),
@@ -213,7 +198,7 @@
                    }
                    checkHeader("accept", "application/json");
                    checkHeader("sec-fetch-dest", "empty");
-                   checkHeader("sec-fetch-mode", "no-cors");
+                   checkHeader("sec-fetch-mode", "cors");
                    checkHeader("sec-fetch-site", "same-origin");
                    if (headers.cookie !== undefined)
                      throw "Unexpected cookie: " + JSON.stringify(headers.cookie);
@@ -226,6 +211,40 @@
 
 subsetTest(promise_test, async test => {
   const uuid = generateUuid(test);
+  let cookieFrame = await createFrame(test, OTHER_ORIGIN1);
+  await runInFrame(test, cookieFrame, `await setCookie(test_instance)`);
+
+  await joinGroupAndRunBasicFledgeTestExpectingWinner(
+      test,
+      { uuid: uuid,
+        interestGroupOverrides: {
+          trustedBiddingSignalsURL: CROSS_ORIGIN_TRUSTED_BIDDING_SIGNALS_URL,
+          trustedBiddingSignalsKeys: ['headers', 'cors'],
+          biddingLogicURL: createBiddingScriptURL({
+              generateBid:
+                  `let headers = crossOriginTrustedBiddingSignals[
+                       '${OTHER_ORIGIN1}'].headers;
+                   function checkHeader(name, value) {
+                     jsonActualValue = JSON.stringify(headers[name]);
+                     if (jsonActualValue !== JSON.stringify([value]))
+                       throw "Unexpected " + name + ": " + jsonActualValue;
+                   }
+                   checkHeader("accept", "application/json");
+                   checkHeader("sec-fetch-dest", "empty");
+                   checkHeader("sec-fetch-mode", "cors");
+                   checkHeader("sec-fetch-site", "cross-site");
+                   checkHeader("origin", "${window.location.origin}");
+                   if (headers.cookie !== undefined)
+                     throw "Unexpected cookie: " + JSON.stringify(headers.cookie);
+                   if (headers.referer !== undefined)
+                     throw "Unexpected referer: " + JSON.stringify(headers.referer);`,
+          })
+        }
+      });
+}, 'cross-origin trustedBiddingSignalsURL request headers.');
+
+subsetTest(promise_test, async test => {
+  const uuid = generateUuid(test);
   await deleteAllCookies();
 
   await joinGroupAndRunBasicFledgeTestExpectingWinner(
@@ -282,7 +301,7 @@
                    }
                    checkHeader("accept", "application/json");
                    checkHeader("sec-fetch-dest", "empty");
-                   checkHeader("sec-fetch-mode", "no-cors");
+                   checkHeader("sec-fetch-mode", "cors");
                    checkHeader("sec-fetch-site", "same-origin");
                    if (headers.cookie !== undefined)
                      throw "Unexpected cookie: " + JSON.stringify(headers.cookie);
@@ -295,6 +314,46 @@
 
 subsetTest(promise_test, async test => {
   const uuid = generateUuid(test);
+  let cookieFrame = await createFrame(test, OTHER_ORIGIN1);
+  await runInFrame(test, cookieFrame, `await setCookie(test_instance)`);
+
+  let renderURL = createRenderURL(uuid, /*script=*/null, /*signalsParam=*/'headers,cors');
+
+  await joinGroupAndRunBasicFledgeTestExpectingWinner(
+      test,
+      { uuid: uuid,
+        interestGroupOverrides: {
+          ads: [{ renderURL: renderURL }]
+        },
+        auctionConfigOverrides: {
+          trustedScoringSignalsURL: CROSS_ORIGIN_TRUSTED_SCORING_SIGNALS_URL,
+          decisionLogicURL: createDecisionScriptURL(uuid,
+            {
+              permitCrossOriginTrustedSignals: `"${OTHER_ORIGIN1}"`,
+              scoreAd:
+                  `let headers = crossOriginTrustedScoringSignals[
+                      '${OTHER_ORIGIN1}'].renderURL["${renderURL}"];
+                   function checkHeader(name, value) {
+                     jsonActualValue = JSON.stringify(headers[name]);
+                     if (jsonActualValue !== JSON.stringify([value]))
+                       throw "Unexpected " + name + ": " + jsonActualValue;
+                   }
+                   checkHeader("accept", "application/json");
+                   checkHeader("sec-fetch-dest", "empty");
+                   checkHeader("sec-fetch-mode", "cors");
+                   checkHeader("sec-fetch-site", "cross-site");
+                   checkHeader("origin", "${window.location.origin}");
+                   if (headers.cookie !== undefined)
+                     throw "Unexpected cookie: " + JSON.stringify(headers.cookie);
+                   if (headers.referer !== undefined)
+                     throw "Unexpected referer: " + JSON.stringify(headers.referer);`,
+            })
+        }
+      });
+}, 'cross-origin trustedScoringSignalsURL request headers.');
+
+subsetTest(promise_test, async test => {
+  const uuid = generateUuid(test);
   await deleteAllCookies();
 
   await joinGroupAndRunBasicFledgeTestExpectingWinner(
diff --git a/third_party/blink/web_tests/external/wpt/fledge/tentative/resources/bidding-logic.sub.py b/third_party/blink/web_tests/external/wpt/fledge/tentative/resources/bidding-logic.sub.py
index e17f2c2c..8c0539d 100644
--- a/third_party/blink/web_tests/external/wpt/fledge/tentative/resources/bidding-logic.sub.py
+++ b/third_party/blink/web_tests/external/wpt/fledge/tentative/resources/bidding-logic.sub.py
@@ -58,7 +58,8 @@
       body += f"""
           function generateBid(interestGroup, auctionSignals, perBuyerSignals,
                               trustedBiddingSignals, browserSignals,
-                              directFromSellerSignals) {{
+                              directFromSellerSignals,
+                              crossOriginTrustedBiddingSignals) {{
             {{{{GET[generateBid]}}}};
             return {{
               bid: {bid},
diff --git a/third_party/blink/web_tests/external/wpt/fledge/tentative/resources/decision-logic.sub.py b/third_party/blink/web_tests/external/wpt/fledge/tentative/resources/decision-logic.sub.py
index 3a23f98..545c7e5 100644
--- a/third_party/blink/web_tests/external/wpt/fledge/tentative/resources/decision-logic.sub.py
+++ b/third_party/blink/web_tests/external/wpt/fledge/tentative/resources/decision-logic.sub.py
@@ -35,11 +35,18 @@
     if error == b"no-body":
         return b''
 
+    permitCrossOriginTrustedSignals = request.GET.get(
+        b"permit-cross-origin-trusted-signals", None)
+    if permitCrossOriginTrustedSignals != None:
+        response.headers.set(b"Ad-Auction-Allow-Trusted-Scoring-Signals-From",
+                             permitCrossOriginTrustedSignals)
+
     body = (Path(__file__).parent.resolve() / 'worklet-helpers.js').read_text().encode("ASCII")
     if error != b"no-scoreAd":
         body += b"""
             function scoreAd(adMetadata, bid, auctionConfig, trustedScoringSignals,
-                             browserSignals, directFromSellerSignals) {
+                             browserSignals, directFromSellerSignals,
+                             crossOriginTrustedScoringSignals) {
               // Don't bid on interest group with the wrong uuid. This is to prevent
               // left over interest groups from other tests from affecting auction
               // results.
diff --git a/third_party/blink/web_tests/external/wpt/fledge/tentative/resources/fledge-util.sub.js b/third_party/blink/web_tests/external/wpt/fledge/tentative/resources/fledge-util.sub.js
index a7d0f63..148613e 100644
--- a/third_party/blink/web_tests/external/wpt/fledge/tentative/resources/fledge-util.sub.js
+++ b/third_party/blink/web_tests/external/wpt/fledge/tentative/resources/fledge-util.sub.js
@@ -26,6 +26,12 @@
 const OTHER_ORIGIN6 = 'https://{{hosts[alt][www]}}:{{ports[https][0]}}';
 const OTHER_ORIGIN7 = 'https://{{hosts[alt][www]}}:{{ports[https][1]}}';
 
+// Trusted signals hosted on OTHER_ORIGIN1
+const CROSS_ORIGIN_TRUSTED_BIDDING_SIGNALS_URL = OTHER_ORIGIN1 + BASE_PATH +
+    'resources/trusted-bidding-signals.py';
+const CROSS_ORIGIN_TRUSTED_SCORING_SIGNALS_URL = OTHER_ORIGIN1 + BASE_PATH +
+    'resources/trusted-scoring-signals.py';
+
 // Creates a URL that will be sent to the URL request tracker script.
 // `uuid` is used to identify the stash shard to use.
 // `dispatch` affects what the tracker script does.
@@ -232,6 +238,10 @@
     url.searchParams.append('reportResult', params.reportResult);
   if (params.error != null)
     url.searchParams.append('error', params.error);
+  if (params.permitCrossOriginTrustedSignals != null) {
+    url.searchParams.append('permit-cross-origin-trusted-signals',
+                            params.permitCrossOriginTrustedSignals);
+  }
   return url.toString();
 }
 
@@ -859,3 +869,19 @@
   }
   return { beforeReplacements, afterReplacements };
 }
+
+// Delete all cookies. Separate function so that can be replaced with
+// something else for testing outside of a WPT environment.
+async function deleteAllCookies() {
+  await test_driver.delete_all_cookies();
+}
+
+// Deletes all cookies (to avoid pre-existing cookies causing inconsistent
+// output on failure) and sets a cookie with name "cookie" and a value of
+// "cookie". Adds a cleanup task to delete all cookies again when the test
+// is done.
+async function setCookie(test) {
+  await deleteAllCookies();
+  document.cookie = 'cookie=cookie; path=/'
+  test.add_cleanup(deleteAllCookies);
+}
diff --git a/third_party/blink/web_tests/external/wpt/fledge/tentative/resources/subordinate-frame.sub.html b/third_party/blink/web_tests/external/wpt/fledge/tentative/resources/subordinate-frame.sub.html
index f5b1ef9..ea1f0c4 100644
--- a/third_party/blink/web_tests/external/wpt/fledge/tentative/resources/subordinate-frame.sub.html
+++ b/third_party/blink/web_tests/external/wpt/fledge/tentative/resources/subordinate-frame.sub.html
@@ -5,6 +5,8 @@
 <!--- Allow injected scripts to use functions in fledge-util.sub.js --->
 <base href="..">
 <script src="/resources/testharness.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
 <script src="/common/utils.js"></script>
 <script src="resources/fledge-util.sub.js"></script>
 </head>
diff --git a/third_party/blink/web_tests/external/wpt/fledge/tentative/resources/trusted-bidding-signals.py b/third_party/blink/web_tests/external/wpt/fledge/tentative/resources/trusted-bidding-signals.py
index 955a7c0b..5a89e3b 100644
--- a/third_party/blink/web_tests/external/wpt/fledge/tentative/resources/trusted-bidding-signals.py
+++ b/third_party/blink/web_tests/external/wpt/fledge/tentative/resources/trusted-bidding-signals.py
@@ -39,6 +39,13 @@
             continue
         return fail(response, "Unexpected query parameter: " + param)
 
+    # If trusted signal keys are passed in, and one of them is "cors",
+    # add appropriate Access-Control-* headers to normal requests, and handle
+    # CORS preflights.
+    if keys and "cors" in keys and fledge_http_server_util.handle_cors_headers_and_preflight(
+            request, response):
+        return
+
     # "interestGroupNames" and "hostname" are mandatory.
     if not hostname:
         return fail(response, "hostname missing")
diff --git a/third_party/blink/web_tests/external/wpt/fledge/tentative/resources/trusted-scoring-signals.py b/third_party/blink/web_tests/external/wpt/fledge/tentative/resources/trusted-scoring-signals.py
index ce53e762..fd0a81f 100644
--- a/third_party/blink/web_tests/external/wpt/fledge/tentative/resources/trusted-scoring-signals.py
+++ b/third_party/blink/web_tests/external/wpt/fledge/tentative/resources/trusted-scoring-signals.py
@@ -62,6 +62,7 @@
     contentType = "application/json"
     adAuctionAllowed = "true"
     dataVersion = None
+    cors = False
     for urlList in urlLists:
         for renderUrl in urlList["urls"]:
             value = "default value"
@@ -124,11 +125,19 @@
                         value = fledge_http_server_util.headers_to_ascii(request.headers)
                     elif signalsParam == "url":
                         value = request.url
+                    elif signalsParam == "cors":
+                        cors = True
             if addValue:
                 if urlList["type"] not in responseBody:
                     responseBody[urlList["type"]] = {}
                 responseBody[urlList["type"]][renderUrl] = value
 
+    # If the signalsParam embedded inside a render URL calls for CORS, add
+    # appropriate response headers, and fully handle preflights.
+    if cors and fledge_http_server_util.handle_cors_headers_and_preflight(
+            request, response):
+        return
+
     if contentType:
         response.headers.set("Content-Type", contentType)
     if adAuctionAllowed:
diff --git a/third_party/blink/web_tests/external/wpt/fledge/tentative/trusted-bidding-signals.https.window.js b/third_party/blink/web_tests/external/wpt/fledge/tentative/trusted-bidding-signals.https.window.js
index 905abf8..205c689 100644
--- a/third_party/blink/web_tests/external/wpt/fledge/tentative/trusted-bidding-signals.https.window.js
+++ b/third_party/blink/web_tests/external/wpt/fledge/tentative/trusted-bidding-signals.https.window.js
@@ -329,6 +329,34 @@
 }, 'Trusted bidding signals receives hostname field.');
 
 /////////////////////////////////////////////////////////////////////////////
+// Cross-origin trusted bidding signals tests
+/////////////////////////////////////////////////////////////////////////////
+
+subsetTest(promise_test, async test => {
+  await runTrustedBiddingSignalsTest(
+      test,
+      `trustedBiddingSignals === null &&
+       !('dataVersion' in browserSignals) &&
+       crossOriginTrustedBiddingSignals['${OTHER_ORIGIN1}']['num-value'] === 1 &&
+       browserSignals.crossOriginDataVersion === 4`,
+      { name: 'data-version',
+        trustedBiddingSignalsKeys: ['num-value', 'cors'],
+        trustedBiddingSignalsURL: CROSS_ORIGIN_TRUSTED_BIDDING_SIGNALS_URL });
+}, 'Basic cross-origin trusted bidding signals');
+
+subsetTest(promise_test, async test => {
+  await runTrustedBiddingSignalsTest(
+      test,
+      `trustedBiddingSignals === null &&
+       !('dataVersion' in browserSignals) &&
+       crossOriginTrustedBiddingSignals === null &&
+       !('crossOriginDataVersion' in browserSignals)`,
+      { name: 'data-version',
+        trustedBiddingSignalsKeys: ['num-value'],
+        trustedBiddingSignalsURL: CROSS_ORIGIN_TRUSTED_BIDDING_SIGNALS_URL });
+}, 'Cross-origin trusted bidding signals w/o CORS authorization');
+
+/////////////////////////////////////////////////////////////////////////////
 // Data-Version tests
 /////////////////////////////////////////////////////////////////////////////
 
diff --git a/third_party/blink/web_tests/external/wpt/fledge/tentative/trusted-scoring-signals.https.window.js b/third_party/blink/web_tests/external/wpt/fledge/tentative/trusted-scoring-signals.https.window.js
index 105b09fb..4dd4f96 100644
--- a/third_party/blink/web_tests/external/wpt/fledge/tentative/trusted-scoring-signals.https.window.js
+++ b/third_party/blink/web_tests/external/wpt/fledge/tentative/trusted-scoring-signals.https.window.js
@@ -21,17 +21,20 @@
 // to worklet scripts, and handling the Data-Version header.
 
 // Helper for trusted scoring signals tests. Runs an auction with
-// TRUSTED_SCORING_SIGNALS_URL and a single interest group, failing the test
-// if there's no winner. "scoreAdCheck" is an expression that should be true
+// trustedSignalsURL and a single interest group, failing the test if there's no
+// winner. "scoreAdCheck" is an expression that should be true
 // when evaluated in scoreAd(). "renderURL" can be used to control the response
-// given for TRUSTED_SCORING_SIGNALS_URL.
+// given for trustedSignalsURL.
 async function runTrustedScoringSignalsTest(test, uuid, renderURL, scoreAdCheck,
-                                            additionalInterestGroupOverrides) {
+                                            additionalInterestGroupOverrides,
+                                            trustedSignalsURL = TRUSTED_SCORING_SIGNALS_URL,
+                                            decisionScriptParamOverrides = {}) {
   const auctionConfigOverrides = {
-      trustedScoringSignalsURL: TRUSTED_SCORING_SIGNALS_URL,
+      trustedScoringSignalsURL: trustedSignalsURL,
       decisionLogicURL:
           createDecisionScriptURL(uuid, {
-                  scoreAd: `if (!(${scoreAdCheck})) throw "error";` })};
+                  scoreAd: `if (!(${scoreAdCheck})) throw "error";`,
+                  ...decisionScriptParamOverrides})};
   await joinGroupAndRunBasicFledgeTestExpectingWinner(
       test,
       {
@@ -306,6 +309,70 @@
 }, 'Trusted scoring signals multiple renderURLs.');
 
 /////////////////////////////////////////////////////////////////////////////
+// Cross-origin trusted scoring signals tests
+/////////////////////////////////////////////////////////////////////////////
+subsetTest(promise_test, async test => {
+  const uuid = generateUuid(test);
+  const renderURL = createRenderURL(uuid, /*script=*/null,
+      /*signalsParam=*/'string-value,data-version:3,cors');
+  await runTrustedScoringSignalsTest(
+      test, uuid, renderURL,
+      `trustedScoringSignals === null &&
+       !('dataVersion' in browserSignals) &&
+       crossOriginTrustedScoringSignals['${OTHER_ORIGIN1}'].renderURL[
+           "${renderURL}"] === "1" &&
+       browserSignals.crossOriginDataVersion === 3`,
+       /*additionalInterestGroupOverrides=*/ {},
+      CROSS_ORIGIN_TRUSTED_SCORING_SIGNALS_URL,
+      {permitCrossOriginTrustedSignals: `"${OTHER_ORIGIN1}"`});
+}, 'Basic cross-origin trusted scoring signals.');
+
+subsetTest(promise_test, async test => {
+  const uuid = generateUuid(test);
+  const renderURL = createRenderURL(uuid, /*script=*/null,
+      /*signalsParam=*/'string-value,data-version:3');
+  await runTrustedScoringSignalsTest(
+      test, uuid, renderURL,
+      `trustedScoringSignals === null &&
+       !('dataVersion' in browserSignals) &&
+       crossOriginTrustedScoringSignals === null &&
+       !('crossOriginDataVersion' in browserSignals)`,
+       /*additionalInterestGroupOverrides=*/ {},
+      CROSS_ORIGIN_TRUSTED_SCORING_SIGNALS_URL,
+      {permitCrossOriginTrustedSignals: `"${OTHER_ORIGIN1}"`});
+}, 'Cross-origin trusted scoring signals w/o CORS authorization.');
+
+subsetTest(promise_test, async test => {
+  const uuid = generateUuid(test);
+  const renderURL = createRenderURL(uuid, /*script=*/null,
+      /*signalsParam=*/'string-value,data-version:3, cors');
+  await runTrustedScoringSignalsTest(
+      test, uuid, renderURL,
+      `trustedScoringSignals === null &&
+       !('dataVersion' in browserSignals) &&
+       crossOriginTrustedScoringSignals === null &&
+       !('crossOriginDataVersion' in browserSignals)`,
+       /*additionalInterestGroupOverrides=*/ {},
+      CROSS_ORIGIN_TRUSTED_SCORING_SIGNALS_URL);
+}, 'Cross-origin trusted scoring signals w/o script allow header.');
+
+subsetTest(promise_test, async test => {
+  const uuid = generateUuid(test);
+  const renderURL = createRenderURL(uuid, /*script=*/null,
+      /*signalsParam=*/'string-value,data-version:3');
+  await runTrustedScoringSignalsTest(
+      test, uuid, renderURL,
+      `trustedScoringSignals === null &&
+       !('dataVersion' in browserSignals) &&
+       crossOriginTrustedScoringSignals === null &&
+       !('crossOriginDataVersion' in browserSignals)`,
+       /*additionalInterestGroupOverrides=*/ {},
+      CROSS_ORIGIN_TRUSTED_SCORING_SIGNALS_URL,
+      {permitCrossOriginTrustedSignals:
+          `"${OTHER_ORIGIN2}", "${window.location.origin}"`});
+}, 'Cross-origin trusted scoring signals with wrong script allow header.');
+
+/////////////////////////////////////////////////////////////////////////////
 // Data-Version tests
 /////////////////////////////////////////////////////////////////////////////
 
@@ -658,4 +725,4 @@
   );
 
   runBasicFledgeTestExpectingWinner(test, uuid, auctionConfigOverrides);
-}, 'Trusted scoring signals splits the request if the combined URL length exceeds the limit of small value.');
\ No newline at end of file
+}, 'Trusted scoring signals splits the request if the combined URL length exceeds the limit of small value.');
diff --git a/third_party/blink/web_tests/external/wpt/lint.ignore b/third_party/blink/web_tests/external/wpt/lint.ignore
index 61bca89dd..34a32ec 100644
--- a/third_party/blink/web_tests/external/wpt/lint.ignore
+++ b/third_party/blink/web_tests/external/wpt/lint.ignore
@@ -616,7 +616,6 @@
 MISSING DEPENDENCY: resources/chromium/fake-hid.js
 MISSING DEPENDENCY: resources/chromium/mock-battery-monitor.js
 MISSING DEPENDENCY: resources/chromium/mock-barcodedetection.js
-MISSING DEPENDENCY: resources/chromium/mock-direct-sockets.js
 MISSING DEPENDENCY: resources/chromium/mock-facedetection.js
 MISSING DEPENDENCY: resources/chromium/mock-idle-detection.js
 MISSING DEPENDENCY: resources/chromium/mock-imagecapture.js
diff --git a/third_party/blink/web_tests/external/wpt/mediasession/mediametadata.html b/third_party/blink/web_tests/external/wpt/mediasession/mediametadata.html
index f87e71d..d73a4312 100644
--- a/third_party/blink/web_tests/external/wpt/mediasession/mediametadata.html
+++ b/third_party/blink/web_tests/external/wpt/mediasession/mediametadata.html
@@ -50,8 +50,27 @@
 test(function() {
   var image1 = { src: 'http://example.com/1', sizes: 'sizes1', type: 'type1' };
   var image2 = { src: 'http://example.com/2', sizes: 'sizes2', type: 'type2' };
+  var chapter1_image1 = { src: 'http://chapterexample.com/1', sizes: '128x128', type: 'image/png' };
+  var chapter1_image2 = { src: 'http://chapterexample.com/2', sizes: '512x512', type: 'image/png' };
+  var chapter2_image1 = { src: 'http://chapterexample.com/3', sizes: '128x128', type: 'image/png' };
+  var chapter2_image2 = { src: 'http://chapterexample.com/4', sizes: '512x512', type: 'image/png' };
   var metadata = new MediaMetadata({
-      title: 'foo', album: 'bar', artist: 'plop', artwork: [ image1, image2 ]
+    title: 'foo', album: 'bar', artist: 'plop', artwork: [image1, image2],
+    chapterInfo: [{
+      title: 'Chapter 1',
+      startTime: 0,
+      artwork: [
+        chapter1_image1,
+        chapter1_image2
+      ]
+    }, {
+      title: 'Chapter 2',
+      startTime: 16,
+      artwork: [
+        chapter2_image1,
+        chapter2_image2,
+      ]
+    }]
   });
 
   assert_equals(metadata.title, 'foo');
@@ -64,6 +83,22 @@
   assert_equals(metadata.artwork[1].src, image2.src);
   assert_equals(metadata.artwork[1].sizes, image2.sizes);
   assert_equals(metadata.artwork[1].type, image2.type);
+  assert_equals(metadata.chapterInfo[0].title, 'Chapter 1');
+  assert_equals(metadata.chapterInfo[0].startTime, 0);
+  assert_equals(metadata.chapterInfo[0].artwork[0].src, chapter1_image1.src);
+  assert_equals(metadata.chapterInfo[0].artwork[1].src, chapter1_image2.src);
+  assert_equals(metadata.chapterInfo[0].artwork[0].sizes, chapter1_image1.sizes);
+  assert_equals(metadata.chapterInfo[0].artwork[1].sizes, chapter1_image2.sizes);
+  assert_equals(metadata.chapterInfo[0].artwork[0].type, chapter1_image1.type);
+  assert_equals(metadata.chapterInfo[0].artwork[1].type, chapter1_image2.type);
+  assert_equals(metadata.chapterInfo[1].title, 'Chapter 2');
+  assert_equals(metadata.chapterInfo[1].startTime, 16);
+  assert_equals(metadata.chapterInfo[1].artwork[0].src, chapter2_image1.src);
+  assert_equals(metadata.chapterInfo[1].artwork[1].src, chapter2_image2.src);
+  assert_equals(metadata.chapterInfo[1].artwork[0].sizes, chapter2_image1.sizes);
+  assert_equals(metadata.chapterInfo[1].artwork[1].sizes, chapter2_image2.sizes);
+  assert_equals(metadata.chapterInfo[1].artwork[0].type, chapter2_image1.type);
+  assert_equals(metadata.chapterInfo[1].artwork[1].type, chapter2_image2.type);
 }, 'Test the different values allowed in MediaMetadata init dictionary');
 
 test(function() {
@@ -72,6 +107,7 @@
   assert_equals(metadata.artist, '');
   assert_equals(metadata.album, '');
   assert_equals(0, metadata.artwork.length);
+  assert_equals(0, metadata.chapterInfo.length);
 }, 'Test the default values for MediaMetadata with empty init dictionary');
 
 test(function() {
@@ -80,6 +116,7 @@
   assert_equals(metadata.artist, '');
   assert_equals(metadata.album, '');
   assert_equals(0, metadata.artwork.length);
+  assert_equals(0, metadata.chapterInfo.length);
 }, 'Test the default values for MediaMetadata with no init dictionary');
 
 test(function() {
@@ -90,8 +127,28 @@
 test(function() {
   var image1 = { src: 'http://example.com/1', sizes: 'sizes1', type: 'type1' };
   var image2 = { src: 'http://example.com/2', sizes: 'sizes2', type: 'type2' };
+  var chapter1_image1 = { src: 'http://chapterexample.com/1', sizes: '128x128', type: 'image/png' };
+  var chapter1_image2 = { src: 'http://chapterexample.com/2', sizes: '512x512', type: 'image/png' };
+  var chapter2_image1 = { src: 'http://chapterexample.com/3', sizes: '128x128', type: 'image/png' };
+  var chapter2_image2 = { src: 'http://chapterexample.com/4', sizes: '512x512', type: 'image/png' };
+
   var metadata = new MediaMetadata({
-    title: 'foo', album: 'bar', artist: 'plop', artwork: [ image1, image2 ]
+    title: 'foo', album: 'bar', artist: 'plop', artwork: [image1, image2],
+    chapterInfo: [{
+      title: 'Chapter 1',
+      startTime: 0,
+      artwork: [
+        chapter1_image1,
+        chapter1_image2
+      ]
+    }, {
+      title: 'Chapter 2',
+      startTime: 16,
+      artwork: [
+        chapter2_image1,
+        chapter2_image2,
+      ]
+    }]
   });
 
   metadata.title = 'something else';
@@ -109,6 +166,18 @@
   assert_equals(metadata.artwork[0].src, 'http://example.com/');
   assert_equals(metadata.artwork[0].sizes, '40x40');
   assert_equals(metadata.artwork[0].type, 'image/png');
+
+  // The chapterInfo cannot be modified.
+  var chapter_image = { src: 'http://example.com/', sizes: '40x40', type: 'image/png' };
+  var chapter = {
+    title: 'Chapter 3',
+    startTime: 22,
+    artwork: [chapter_image]
+  };
+  metadata.chapterInfo = [chapter];
+  assert_equals(metadata.chapterInfo[0].title, 'Chapter 1');
+  assert_equals(metadata.chapterInfo[0].startTime, 0);
+  assert_equals(metadata.chapterInfo.length, 2);
 }, "Test that MediaMetadata is read/write");
 
 test(function() {
@@ -121,7 +190,7 @@
 
   metadata.artwork[0].src = 'bar';
   assert_equals(metadata.artwork[0].src, 'http://foo.com/');
-}, "Test that MediaMetadat.artwork can't be modified");
+}, "Test that MediaMetadata.artwork can't be modified");
 
 test(function() {
   var metadata = new MediaMetadata({ artwork: [{
@@ -149,6 +218,34 @@
 }, "Test that MediaMetadata.artwork is Frozen");
 
 test(function() {
+  var chapter1_image1 = { src: 'http://chapterexample.com/1', sizes: '128x128', type: 'image/png' };
+  var chapter1_image2 = { src: 'http://chapterexample.com/2', sizes: '512x512', type: 'image/png' };
+  var chapter2_image1 = { src: 'http://chapterexample.com/3', sizes: '128x128', type: 'image/png' };
+  var chapter2_image2 = { src: 'http://chapterexample.com/4', sizes: '512x512', type: 'image/png' };
+  var metadata = new MediaMetadata({
+    chapterInfo: [{
+      title: 'Chapter 1',
+      startTime: 0,
+      artwork: [
+        chapter1_image1,
+        chapter1_image2
+      ]
+    }, {
+      title: 'Chapter 2',
+      startTime: 16,
+      artwork: [
+        chapter2_image1,
+        chapter2_image2,
+      ]
+    }]
+  });
+
+  assert_true(Object.isFrozen(metadata.chapterInfo));
+  for (var i = 0; i < metadata.chapterInfo.length; ++i)
+    assert_true(Object.isFrozen(metadata.chapterInfo[i]));
+}, "Test that MediaMetadata.chapterInfo is Frozen");
+
+test(function() {
   var metadata = new MediaMetadata({ artwork: [
     { src: 'http://example.com', sizes: '40x40', type: 'image/png' },
     { src: '../foo', sizes: '40x40', type: 'image/png' },
@@ -161,6 +258,39 @@
 }, "Test that MediaMetadata.artwork returns parsed urls");
 
 test(function() {
+  var chapter1_image1 = { src: 'http://chapterexample.com/1', sizes: '128x128', type: 'image/png' };
+  var chapter1_image2 = { src: 'http://chapterexample.com/2', sizes: '512x512', type: 'image/png' };
+  var chapter2_image1 = { src: 'http://chapterexample.com/3', sizes: '128x128', type: 'image/png' };
+  var chapter2_image2 = { src: 'http://chapterexample.com/4', sizes: '512x512', type: 'image/png' };
+  var metadata = new MediaMetadata({
+    chapterInfo: [{
+      title: 'Chapter 1',
+      startTime: 0,
+      artwork: [
+        chapter1_image1,
+        chapter1_image2
+      ]
+    }, {
+      title: 'Chapter 2',
+      startTime: 16,
+      artwork: [
+        chapter2_image1,
+        chapter2_image2,
+      ]
+    }]
+  });
+
+  assert_equals(metadata.chapterInfo[0].artwork[0].src,
+    new URL('http://chapterexample.com/1', document.URL).href)
+  assert_equals(metadata.chapterInfo[0].artwork[1].src,
+    new URL('http://chapterexample.com/2', document.URL).href)
+  assert_equals(metadata.chapterInfo[1].artwork[0].src,
+    new URL('http://chapterexample.com/3', document.URL).href)
+  assert_equals(metadata.chapterInfo[1].artwork[1].src,
+    new URL('http://chapterexample.com/4', document.URL).href)
+}, "Test that MediaMetadata.chapterInfo's artwork returns parsed urls");
+
+test(function() {
   var metadata = 42;
 
   assert_throws_js(TypeError, _ => {
@@ -180,6 +310,22 @@
   });
   assert_equals(metadata.artwork.length, 0);
 
+  assert_throws_js(TypeError, _ => {
+    metadata
+    new MediaMetadata({
+      chapterInfo: [{
+        title: 'Chapter 1',
+        startTime: 0,
+        artwork: [
+          // Valid url.
+          { src: 'http://example.com' },
+          // Invalid url.
+          { src: 'http://example.com:demo' },
+        ]
+      }]
+    });
+  });
+  assert_equals(metadata.chapterInfo.length, 0);
 }, "Test that MediaMetadata throws when setting an invalid url");
 
 test(function() {
@@ -190,13 +336,24 @@
 
 test(function() {
   assert_throws_js(TypeError, _ => {
-    new MediaMetadata({ artwork: [ {} ] });
+    new MediaMetadata({ artwork: [ {} ] })
   });
 
   var metadata = new MediaMetadata();
   assert_throws_js(TypeError, _ => {
     metadata.artwork = [ { type: 'image/png', sizes: '40x40' } ];
   });
+
+  assert_throws_js(TypeError, _ => {
+    metadata
+    new MediaMetadata({
+      chapterInfo: [{
+        title: 'Chapter 1',
+        startTime: 0,
+        artwork: [{ type: 'image/png', sizes: '40x40' }]
+      }]
+    });
+  });
 }, "Test that MediaImage.src is required")
 
 promise_test(async t => {
diff --git a/third_party/blink/web_tests/external/wpt/preload/WEB_FEATURES.yml b/third_party/blink/web_tests/external/wpt/preload/WEB_FEATURES.yml
new file mode 100644
index 0000000..384fd1a
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/preload/WEB_FEATURES.yml
@@ -0,0 +1,4 @@
+features:
+- name: modulepreload
+  files:
+  - "*modulepreload*"
diff --git a/third_party/blink/web_tests/external/wpt/resources/chromium/mock-direct-sockets.js b/third_party/blink/web_tests/external/wpt/resources/chromium/mock-direct-sockets.js
deleted file mode 100644
index 6d557f7a..0000000
--- a/third_party/blink/web_tests/external/wpt/resources/chromium/mock-direct-sockets.js
+++ /dev/null
@@ -1,94 +0,0 @@
-'use strict';
-
-import {DirectSocketsService, DirectSocketsServiceReceiver} from '/gen/third_party/blink/public/mojom/direct_sockets/direct_sockets.mojom.m.js';
-
-self.DirectSocketsServiceTest = (() => {
-  // Class that mocks DirectSocketsService interface defined in
-  // https://source.chromium.org/chromium/chromium/src/third_party/blink/public/mojom/direct_sockets/direct_sockets.mojom
-  class MockDirectSocketsService {
-    constructor() {
-      this.interceptor_ = new MojoInterfaceInterceptor(DirectSocketsService.$interfaceName);
-      this.receiver_ = new DirectSocketsServiceReceiver(this);
-      this.interceptor_.oninterfacerequest = e =>
-          this.receiver_.$.bindHandle(e.handle);
-      this.interceptor_.start();
-    }
-
-    reset() {
-      this.receiver_.$.close();
-      this.interceptor_.stop();
-    }
-
-    openTCPSocket(
-      options,
-      receiver,
-      observer) {
-      return Promise.resolve({
-        // return result = net:Error::NOT_IMPLEMENTED (code -11)
-        result: -11
-      });
-    }
-
-    openConnectedUDPSocket(
-      options,
-      receiver,
-      listener) {
-      return Promise.resolve({
-        // return result = net:Error::NOT_IMPLEMENTED (code -11)
-        result: -11
-      });
-    }
-
-    openBoundUDPSocket(
-      options,
-      receiver,
-      listener) {
-      return Promise.resolve({
-        // return result = net:Error::NOT_IMPLEMENTED (code -11)
-        result: -11
-      });
-    }
-
-    openTCPServerSocket(
-      options,
-      receiver) {
-      return Promise.resolve({
-        // return result = net:Error::NOT_IMPLEMENTED (code -11)
-        result: -11
-      });
-    }
-  }
-
-  let testInternal = {
-    initialized: false,
-    mockDirectSocketsService: null
-  }
-
-  class DirectSocketsServiceTestChromium {
-    constructor() {
-      Object.freeze(this);  // Make it immutable.
-    }
-
-    initialize() {
-      if (!testInternal.initialized) {
-        testInternal = {
-          mockDirectSocketsService: new MockDirectSocketsService(),
-          initialized: true
-        };
-      }
-    }
-
-    async reset() {
-      if (testInternal.initialized) {
-        testInternal.mockDirectSocketsService.reset();
-        testInternal = {
-          mockDirectSocketsService: null,
-          initialized: false
-        };
-        await new Promise(resolve => setTimeout(resolve, 0));
-      }
-    }
-  }
-
-  return DirectSocketsServiceTestChromium;
-})();
diff --git a/third_party/blink/web_tests/external/wpt/service-workers/service-worker/WEB_FEATURES.yml b/third_party/blink/web_tests/external/wpt/service-workers/service-worker/WEB_FEATURES.yml
new file mode 100644
index 0000000..9ddc5b4
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/service-workers/service-worker/WEB_FEATURES.yml
@@ -0,0 +1,5 @@
+features:
+- name: js-modules-service-workers
+  files:
+  - registration-script-module.https.html
+  - update-registration-with-type.https.html
diff --git a/third_party/blink/web_tests/external/wpt/shadow-dom/focus-navigation/resources/focus-utils.js b/third_party/blink/web_tests/external/wpt/shadow-dom/focus-navigation/resources/focus-utils.js
index f4056dc..ef517c5 100644
--- a/third_party/blink/web_tests/external/wpt/shadow-dom/focus-navigation/resources/focus-utils.js
+++ b/third_party/blink/web_tests/external/wpt/shadow-dom/focus-navigation/resources/focus-utils.js
@@ -7,7 +7,10 @@
 async function navigateFocusForward() {
   await waitForRender();
   const kTab = '\uE004';
-  await new test_driver.send_keys(document.documentElement,kTab);
+  await new test_driver.Actions()
+    .keyDown(kTab)
+    .keyUp(kTab)
+    .send();
   await waitForRender();
 }
 
diff --git a/third_party/blink/web_tests/external/wpt/shared-storage/resources/shared-storage-writable-iframe-request-in-sandboxed-iframe-inner.https.sub.html b/third_party/blink/web_tests/external/wpt/shared-storage/resources/shared-storage-writable-iframe-request-in-sandboxed-iframe-inner.https.sub.html
new file mode 100644
index 0000000..4d1b7ab198d
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/shared-storage/resources/shared-storage-writable-iframe-request-in-sandboxed-iframe-inner.https.sub.html
@@ -0,0 +1,47 @@
+<!doctype html>
+<body>
+  <script src=/resources/testharness.js></script>
+  <script src=/shared-storage/resources/util.js></script>
+  <script>
+
+async function init() {
+  // Create iframe that sets (expectedKey, expectedValue) to shared storage
+  // via response header.
+  let {expectedKey, expectedValue} = parseExpectedKeyAndValueData();
+  const rawWriteHeader = `set;key=${expectedKey};value=${expectedValue}`;
+  const writeHeader = encodeURIComponent(rawWriteHeader);
+  const iframeSrc =
+    `/shared-storage/resources/shared-storage-write-notify-parent.py` +
+    `?write=${writeHeader}`;
+  let frame = document.createElement('iframe');
+  frame.src = iframeSrc;
+  frame.sharedStorageWritable = true;
+
+  // We pass the message on to the parent/opener.
+  const promise = new Promise((resolve, reject) => {
+    window.addEventListener('message', async function handler(evt) {
+      if (evt.source === frame.contentWindow &&
+          evt.data.sharedStorageWritableHeader) {
+        assert_equals(evt.data.sharedStorageWritableHeader, '?1');
+        let parentOrOpener = window.opener || window.parent;
+        parentOrOpener.postMessage({sharedStorageWritableHeader:
+                                    evt.data.sharedStorageWritableHeader},
+                                   "*");
+        document.body.removeChild(frame);
+        window.removeEventListener('message', handler);
+        resolve();
+      }
+    });
+    window.addEventListener('error', () => {
+      reject(new Error('Navigation error'));
+    });
+  });
+
+  // Navigate and wait for notification.
+  document.body.appendChild(frame);
+  await promise;
+}
+
+init();
+  </script>
+</body>
diff --git a/third_party/blink/web_tests/external/wpt/shared-storage/shared-storage-writable-iframe-request-in-sandboxed-frame.tentative.https.html b/third_party/blink/web_tests/external/wpt/shared-storage/shared-storage-writable-iframe-request-in-sandboxed-frame.tentative.https.html
new file mode 100644
index 0000000..d2d07fcc
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/shared-storage/shared-storage-writable-iframe-request-in-sandboxed-frame.tentative.https.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>
+    async function
+      test_shared_storage_writable_iframe_request_in_sandboxed_iframe(
+      test, key, value, sandbox_flags) {
+      // Create sandboxed iframe.
+      let frame = document.createElement('iframe');
+      frame.sandbox = sandbox_flags;
+      let url = new URL(
+        '/shared-storage/resources/'
+          + 'shared-storage-writable-iframe-request-'
+          + 'in-sandboxed-iframe-inner.https.sub.html',
+        location.href);
+      url = appendExpectedKeyAndValue(url, key, value);
+      frame.src = url;
+
+      // We expect a message to bubble up via the sandboxed iframe.
+      const promise = new Promise((resolve, reject) => {
+        window.addEventListener('message', async function handler(evt) {
+          if (evt.source === frame.contentWindow &&
+              evt.data.sharedStorageWritableHeader) {
+            assert_equals(evt.data.sharedStorageWritableHeader, '?1');
+            document.body.removeChild(frame);
+            window.removeEventListener('message', handler);
+            resolve();
+          }
+        });
+        window.addEventListener('error', () => {
+          reject(new Error('Navigation error'));
+        });
+      });
+
+      // Navigate and wait for notification.
+      document.body.appendChild(frame);
+      await promise;
+
+      // Verify that the value has been set.
+      await verifyKeyValueForOrigin(key, value, location.origin);
+
+      // Clean up and finish.
+      await sharedStorage.delete(key);
+      test.done();
+    }
+
+    async_test(t => {
+      test_shared_storage_writable_iframe_request_in_sandboxed_iframe(
+        t,
+        /*key=*/'a',
+        /*value=*/'b',
+        /*sandbox_flags=*/'allow-scripts allow-same-origin');
+    }, 'test sharedStorageWritable iframe request in sandboxed iframe with '
+         + '"allow-same-origin"');
+
+    async_test(t => {
+      test_shared_storage_writable_iframe_request_in_sandboxed_iframe(
+        t,
+        /*key=*/'c',
+        /*value=*/'d',
+        /*sandbox_flags=*/'allow-scripts');
+    }, 'test sharedStorageWritable iframe request in sandboxed iframe without '
+         + '"allow-same-origin"');
+  </script>
+</body>
diff --git a/third_party/blink/web_tests/external/wpt/speculation-rules/prefetch/navigation-timing-requestStart-responseStart.https.html b/third_party/blink/web_tests/external/wpt/speculation-rules/prefetch/navigation-timing-requestStart-responseStart.https.html
index 9c9371c..2687b5bf 100644
--- a/third_party/blink/web_tests/external/wpt/speculation-rules/prefetch/navigation-timing-requestStart-responseStart.https.html
+++ b/third_party/blink/web_tests/external/wpt/speculation-rules/prefetch/navigation-timing-requestStart-responseStart.https.html
@@ -5,48 +5,92 @@
 <script src="/common/utils.js"></script>
 <script src="../resources/utils.js"></script>
 <script src="resources/utils.sub.js"></script>
+<script src="/common/subset-tests-by-key.js"></script>
 
-<meta name="variant" content="?default">
-<meta name="variant" content="?prefetch=true">
+<meta name="variant" content="?include=noPrefetch">
+<meta name="variant" content="?include=afterResponse">
+<meta name="variant" content="?include=waitingForResponse">
+<meta name="variant" content="?include=waitingForRedirect">
 
 <script>
-setup(() => assertSpeculationRulesIsSupported());
+const setupProperties = {};
+if (shouldRunSubTest('waitingForResponse') || shouldRunSubTest('waitingForRedirect')) {
+  // These tests rely on the relationship between these timeouts, the delays
+  // on the server, and any vendor specific timeouts. Force a multiplier of 1,
+  // since using setTimeout directly incurs the wrath of `wpt lint`.
+  setupProperties.timeout_multiplier = 1;
+}
+setup(() => assertSpeculationRulesIsSupported(), setupProperties);
 
-const searchParams = new URLSearchParams(location.search);
-const prefetchEnabled = searchParams.has('prefetch');
+subsetTestByKey('noPrefetch', promise_test, async t => {
+  const agent = await spawnWindow(t);
+  const landingUrl = agent.getExecutorURL({page: 2});
 
-promise_test(async t => {
-    const agent = await spawnWindow(t);
-    // Some meaningless query param to avoid cached response.
-    const prefetchUrl = agent.getExecutorURL({ a: "b" });
+  await agent.navigate(landingUrl);
+  assert_not_prefetched(await agent.getRequestHeaders(), `${landingUrl} should not be prefetched.`);
 
-    if (prefetchEnabled)
-      await agent.forceSinglePrefetch(prefetchUrl);
-
-    await agent.navigate(prefetchUrl);
-
-    if (prefetchEnabled) {
-      assert_prefetched(await agent.getRequestHeaders(),
-        `Prefetch ${prefetchUrl.href} should work.`);
-    } else {
-      assert_not_prefetched(await agent.getRequestHeaders(),
-        `${prefetchUrl.href} should not be prefetched.`);
-    }
-
-    const entries = await agent.execute_script(
+  // It's generally expected that events occur in this order:
+  //   ... -> connectEnd --> requestStart --> responseStart --> ...
+  const [entry] = await agent.execute_script(
       () => performance.getEntriesByType('navigation'));
-    assert_equals(entries.length, 1, 'Wrong number of navigation entries');
-    const entry = entries[0];
+  assert_less_than_equal(entry.connectEnd, entry.requestStart);
+  assert_less_than_equal(entry.requestStart, entry.responseStart);
+}, "PerformanceNavigationTiming data should be in the correct order (no prefetch)");
 
-    // Events timeline:
-    //   ... -> connectEnd --> requestStart --> responseStart --> ...
-    if (prefetchEnabled) {
-      assert_equals(entry.connectEnd, entry.requestStart);
-      assert_equals(entry.requestStart, entry.responseStart);
-    } else {
-      assert_less_than_equal(entry.connectEnd, entry.requestStart);
-      assert_less_than_equal(entry.requestStart, entry.responseStart);
-    }
+subsetTestByKey('afterResponse', promise_test, async t => {
+  const agent = await spawnWindow(t);
+  const landingUrl = agent.getExecutorURL({page: 2});
+  await agent.forceSinglePrefetch(landingUrl);
 
-  }, "PerformanceNavigationTiming.requestStart/responseStart test, same origin prefetch.");
+  await agent.navigate(landingUrl);
+  assert_prefetched(await agent.getRequestHeaders(), `${landingUrl} should have been prefetched.`);
+
+  // Since the response should have started before the navigation, these should
+  // all have the same value (i.e., the start of the fetch).
+  const [entry] = await agent.execute_script(
+      () => performance.getEntriesByType('navigation'));
+  assert_equals(entry.connectEnd, entry.requestStart);
+  assert_equals(entry.requestStart, entry.responseStart);
+}, "PerformanceNavigationTiming data should show 'instantaneous' events completed beforehand");
+
+subsetTestByKey('waitingForResponse', promise_test, async t => {
+  const agent = await spawnWindow(t);
+  const landingUrl = agent.getExecutorURL({executor: 'slow-executor.py', delay: '4', page: 2});
+  await agent.forceSinglePrefetch(landingUrl, {}, /*wait_for_completion=*/false);
+  await new Promise(resolve => t.step_timeout(resolve, 1000));
+
+  await agent.navigate(landingUrl);
+  assert_prefetched(await agent.getRequestHeaders(), `${landingUrl} should have been prefetched.`);
+
+  // We should have to wait for this response. While timing is going to be
+  // somewhat variable here, it's probably wrong for the response to seem
+  // to take less than 1 second (since we only waited for 1 second).
+  // Regardless, these events should be normally ordered.
+  const [entry] = await agent.execute_script(
+      () => performance.getEntriesByType('navigation'));
+  assert_less_than_equal(entry.connectEnd, entry.requestStart);
+  assert_less_than_equal(entry.requestStart + 1000, entry.responseStart);
+  assert_greater_than(entry.responseStart, 1000);
+}, "PerformanceNavigationTiming data should show noticeable TTFB if the response is slow");
+
+subsetTestByKey('waitingForRedirect', promise_test, async t => {
+  const agent = await spawnWindow(t);
+  const landingUrl = agent.getExecutorURL({page: 2});
+  const slowRedirectUrl = new URL(`/common/slow-redirect.py?delay=4&location=${encodeURIComponent(landingUrl)}`, document.baseURI);
+  await agent.forceSinglePrefetch(slowRedirectUrl, {}, /*wait_for_completion=*/false);
+  await new Promise(resolve => t.step_timeout(resolve, 1000));
+
+  await agent.navigate(slowRedirectUrl, {expectedDestinationUrl: landingUrl});
+  assert_prefetched(await agent.getRequestHeaders(), `${landingUrl} should have been prefetched.`);
+
+  // We should have to wait for this response. While timing is going to be
+  // somewhat variable here, it's probably wrong for the response to seem
+  // to take less than 1 second (since we only waited for 1 second).
+  // Regardless, these events should be normally ordered.
+  const [entry] = await agent.execute_script(
+      () => performance.getEntriesByType('navigation'));
+  assert_less_than_equal(entry.connectEnd, entry.requestStart);
+  assert_less_than_equal(entry.requestStart, entry.responseStart);
+  assert_greater_than(entry.responseStart, 1000);
+}, "PerformanceNavigationTiming data should show noticeable TTFB if the response is slow");
 </script>
diff --git a/third_party/blink/web_tests/external/wpt/speculation-rules/prefetch/resources/slow-executor.py b/third_party/blink/web_tests/external/wpt/speculation-rules/prefetch/resources/slow-executor.py
new file mode 100644
index 0000000..379ab552
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/speculation-rules/prefetch/resources/slow-executor.py
@@ -0,0 +1,13 @@
+import os.path
+import time
+
+from wptserve.pipes import template
+
+def main(request, response):
+    time.sleep(float(request.GET.first(b"delay")))
+    response.headers.set(b"Content-Type", b"text/html")
+    response.headers.set(b"Cache-Control", b"no-store")
+    response.content = template(
+        request,
+        open(os.path.join(os.path.dirname(__file__), "executor.sub.html"), "rb").read())
+
diff --git a/third_party/blink/web_tests/external/wpt/webnn/conformance_tests/buffer.https.any.js b/third_party/blink/web_tests/external/wpt/webnn/conformance_tests/buffer.https.any.js
index 5a09b05..51804e72 100644
--- a/third_party/blink/web_tests/external/wpt/webnn/conformance_tests/buffer.https.any.js
+++ b/third_party/blink/web_tests/external/wpt/webnn/conformance_tests/buffer.https.any.js
@@ -14,6 +14,7 @@
   testDestroyWebNNBuffer('destroyTwice');
   testReadWebNNBuffer('read');
   testWriteWebNNBuffer('write');
+  testDispatchWebNNBuffer('dispatch');
 } else {
   test(() => assert_implements(navigator.ml, 'missing navigator.ml'));
 }
diff --git a/third_party/blink/web_tests/external/wpt/webnn/resources/utils.js b/third_party/blink/web_tests/external/wpt/webnn/resources/utils.js
index e5b80ae..c231f46 100644
--- a/third_party/blink/web_tests/external/wpt/webnn/resources/utils.js
+++ b/third_party/blink/web_tests/external/wpt/webnn/resources/utils.js
@@ -880,8 +880,19 @@
 const contextOptions = kContextOptionsForVariant[variant];
 
 /**
+ * Checks if MLBuffer is implemented or not.
+ * @param {MLContext} ml_context - A ML context to test for MLBuffer support.
+ * @returns {Boolean} True if MLBuffer is supported; otherwise, False.
+ */
+const isMLBufferSupported =
+    (ml_context) => {
+      return (createBuffer(ml_context, 4) !== undefined);
+    }
+
+/**
  * Run WebNN operation tests.
- * @param {(String[]|String)} operationName - An operation name array or an operation name
+ * @param {(String[]|String)} operationName - An operation name array or an
+ *     operation name
  * @param {Function} buildFunc - A build function for an operation
  */
 const testWebNNOperation = (operationName, buildFunc) => {
@@ -1384,3 +1395,428 @@
         t, TypeError, another_ml_context.readBuffer(ml_buffer));
   }, `${testName} / context_mismatch`);
 };
+
+/**
+ * WebNN dispatch buffer operation test.
+ * @param {String} testName - The name of the test operation.
+ */
+const testDispatchWebNNBuffer = (testName) => {
+  let ml_context;
+  let ml_graph;
+  const shape = [3, 5];
+  let inputs = {};
+  let outputs = {};
+  promise_setup(async () => {
+    let supported = false;
+    try {
+      ml_context = await navigator.ml.createContext(contextOptions);
+      supported = true;
+    } catch (e) {
+    }
+    assert_implements(
+        supported, `Unable to create context for ${variant} variant`);
+    // Construct a simple graph: A = B + C, with two outputs.
+    const builder = new MLGraphBuilder(ml_context);
+    const operandType = {dataType: 'float32', dimensions: shape};
+    const lhs_operand = builder.input('lhs', operandType);
+    const rhs_operand = builder.input('rhs', operandType);
+    const output_1_operand = builder.add(lhs_operand, rhs_operand);
+    const output_2_operand = builder.add(lhs_operand, rhs_operand);
+    ml_graph = await builder.build(
+        {'output1': output_1_operand, 'output2': output_2_operand});
+    const ml_buffer_size =
+        TypedArrayDict['float32'].BYTES_PER_ELEMENT * sizeOfShape(shape);
+    // MLBuffer was unsupported for the deviceType.
+    if (!isMLBufferSupported(ml_context)) {
+      return;
+    }
+    inputs = {
+      'lhs': ml_context.createBuffer({size: ml_buffer_size}),
+      'rhs': ml_context.createBuffer({size: ml_buffer_size}),
+    };
+    outputs = {
+      'output1': ml_context.createBuffer({size: ml_buffer_size}),
+      'output2': ml_context.createBuffer({size: ml_buffer_size}),
+    };
+  });
+
+  promise_test(async () => {
+    // MLBuffer was unsupported for the deviceType.
+    if (!isMLBufferSupported(ml_context)) {
+      return;
+    }
+
+    let another_ml_context = await navigator.ml.createContext(contextOptions);
+
+    // Control case, same context.
+    ml_context.dispatch(ml_graph, inputs, outputs);
+
+    // Test the wrong context being used for inputs.
+    assert_throws_js(
+        TypeError,
+        () => ml_context.dispatch(
+            ml_graph, {
+              'lhs':
+                  another_ml_context.createBuffer({size: inputs['lhs'].size()}),
+              'rhs': inputs['rhs'],
+            },
+            outputs));
+
+    // Test the wrong context being used for outputs.
+    assert_throws_js(TypeError, () => ml_context.dispatch(ml_graph, inputs, {
+      'output1':
+          another_ml_context.createBuffer({size: outputs['output1'].size()}),
+      'output2': outputs['output2'],
+    }));
+  }, `${testName} / context_mismatch`);
+
+  promise_test(async () => {
+    // MLBuffer was unsupported for the deviceType.
+    if (!isMLBufferSupported(ml_context)) {
+      return;
+    }
+
+    // Control case, valid size.
+    ml_context.dispatch(ml_graph, inputs, outputs);
+
+    // Input is too large.
+    assert_throws_js(
+        TypeError,
+        () => ml_context.dispatch(
+            ml_graph, {
+              'lhs': ml_context.createBuffer({size: inputs['lhs'].size() + 1}),
+              'rhs': inputs['rhs'],
+            },
+            outputs));
+
+    assert_throws_js(
+        TypeError,
+        () => ml_context.dispatch(
+            ml_graph, {
+              'lhs': inputs['lhs'],
+              'rhs': ml_context.createBuffer({size: inputs['rhs'].size() + 1}),
+            },
+            outputs));
+
+    // Output is too large.
+    assert_throws_js(TypeError, () => ml_context.dispatch(ml_graph, inputs, {
+      'output1': ml_context.createBuffer({size: outputs['output1'].size() + 1}),
+      'output2': outputs['output2'],
+    }));
+
+    assert_throws_js(TypeError, () => ml_context.dispatch(ml_graph, inputs, {
+      'output1': outputs['output1'],
+      'output2': ml_context.createBuffer({size: outputs['output2'].size() + 1}),
+    }));
+  }, `${testName} / invalid_size`);
+
+  promise_test(async () => {
+    // MLBuffer was unsupported for the deviceType.
+    if (!isMLBufferSupported(ml_context)) {
+      return;
+    }
+
+    // Control case, valid names.
+    ml_context.dispatch(ml_graph, inputs, outputs);
+
+    // No names is invalid.
+    assert_throws_js(TypeError, () => ml_context.dispatch(ml_graph, {}, {}));
+
+    // Input name is invalid.
+    assert_throws_js(
+        TypeError,
+        () => ml_context.dispatch(
+            ml_graph, {
+              'a_different_input_name': inputs['lhs'],
+              'rhs': inputs['rhs'],
+            },
+            outputs));
+
+    assert_throws_js(
+        TypeError,
+        () => ml_context.dispatch(
+            ml_graph, {
+              'lhs': inputs['lhs'],
+              'a_different_input_name': inputs['rhs'],
+            },
+            outputs));
+
+    // Output name is invalid.
+    assert_throws_js(TypeError, () => ml_context.dispatch(ml_graph, inputs, {
+      'a_different_output_name': outputs['output1'],
+      'output2': outputs['output2'],
+    }));
+
+    assert_throws_js(TypeError, () => ml_context.dispatch(ml_graph, inputs, {
+      'output1': outputs['output1'],
+      'a_different_output_name': outputs['output2'],
+    }));
+
+    // Too few named inputs is invalid.
+    assert_throws_js(
+        TypeError,
+        () => ml_context.dispatch(
+            ml_graph, {
+              'lhs': inputs['lhs'],
+            },
+            outputs));
+
+    // Too many named inputs is invalid.
+    assert_throws_js(
+        TypeError,
+        () => ml_context.dispatch(
+            ml_graph, {
+              'lhs': inputs['lhs'],
+              'rhs': inputs['rhs'],
+              'a_different_input_name':
+                  ml_context.createBuffer({size: inputs['rhs'].size()}),
+            },
+            outputs));
+
+    // Too few named outputs is invalid.
+    assert_throws_js(TypeError, () => ml_context.dispatch(ml_graph, inputs, {
+      'output1': outputs['output1']
+    }));
+
+    // Too many named outputs is invalid.
+    assert_throws_js(TypeError, () => ml_context.dispatch(ml_graph, inputs, {
+      'output1': outputs['output1'],
+      'output2': outputs['output2'],
+      'a_different_output_name':
+          ml_context.createBuffer({size: outputs['output2'].size()}),
+    }));
+  }, `${testName} / invalid_name`);
+
+  promise_test(async () => {
+    // MLBuffer was unsupported for the deviceType.
+    if (!isMLBufferSupported(ml_context)) {
+      return;
+    }
+
+    // Control case, valid buffers.
+    ml_context.dispatch(ml_graph, inputs, outputs);
+
+    // Same buffer used as outputs more than once is invalid.
+    assert_throws_js(TypeError, () => ml_context.dispatch(ml_graph, inputs, {
+      'output1': outputs['output1'],
+      'output2': outputs['output1'],
+    }));
+
+    // Same buffer used as input and output is invalid.
+    assert_throws_js(TypeError, () => ml_context.dispatch(ml_graph, inputs, {
+      'output1': inputs['lhs'],
+      'output2': outputs['output2'],
+    }));
+
+    assert_throws_js(
+        TypeError,
+        () => ml_context.dispatch(
+            ml_graph, {
+              'lhs': outputs['output1'],
+              'rhs': inputs['rhs'],
+            },
+            outputs));
+
+    // Buffer that does not exist is invalid.
+    assert_throws_js(
+        TypeError,
+        () => ml_context.dispatch(
+            ml_graph, {
+              'lhs': undefined,
+              'rhs': inputs['rhs'],
+            },
+            outputs));
+
+    assert_throws_js(TypeError, () => ml_context.dispatch(ml_graph, inputs, {
+      'output1': undefined,
+      'output2': outputs['output2'],
+    }));
+  }, `${testName} / invalid_buffer`);
+
+  promise_test(async () => {
+    // MLBuffer was unsupported for the deviceType.
+    if (!isMLBufferSupported(ml_context)) {
+      return;
+    }
+
+    const dispatch_inputs = {
+      'lhs': ml_context.createBuffer({size: inputs['lhs'].size}),
+      'rhs': ml_context.createBuffer({size: inputs['rhs'].size}),
+    };
+
+    const dispatch_1_outputs = {
+      'output1': ml_context.createBuffer({size: outputs['output1'].size}),
+      'output2': ml_context.createBuffer({size: outputs['output2'].size}),
+    };
+
+    const dispatch_2_outputs = {
+      'output1': ml_context.createBuffer({size: outputs['output1'].size}),
+      'output2': ml_context.createBuffer({size: outputs['output2'].size}),
+    };
+
+    // Initialize inputs
+    const input_data =
+        new TypedArrayDict['float32'](sizeOfShape(shape)).fill(1.0);
+    ml_context.writeBuffer(dispatch_inputs['lhs'], input_data);
+    ml_context.writeBuffer(dispatch_inputs['rhs'], input_data);
+
+    // Output_1 = LHS + RHS = 1 + 1 = 2
+    ml_context.dispatch(ml_graph, dispatch_inputs, dispatch_1_outputs);
+
+    // Output_2 = LHS + RHS = 1 + 1 = 2
+    ml_context.dispatch(ml_graph, dispatch_inputs, dispatch_2_outputs);
+
+    await assert_buffer_data_equals(
+        ml_context, dispatch_1_outputs['output1'],
+        new Float32Array(sizeOfShape(shape)).fill(2.0));
+
+    await assert_buffer_data_equals(
+        ml_context, dispatch_1_outputs['output2'],
+        new Float32Array(sizeOfShape(shape)).fill(2.0));
+
+    await assert_buffer_data_equals(
+        ml_context, dispatch_2_outputs['output1'],
+        new Float32Array(sizeOfShape(shape)).fill(2.0));
+
+    await assert_buffer_data_equals(
+        ml_context, dispatch_2_outputs['output2'],
+        new Float32Array(sizeOfShape(shape)).fill(2.0));
+  }, `${testName} / same_inputs`);
+
+  promise_test(async () => {
+    // MLBuffer was unsupported for the deviceType.
+    if (!isMLBufferSupported(ml_context)) {
+      return;
+    }
+
+    const dispatch_1_inputs = {
+      'lhs': ml_context.createBuffer({size: inputs['lhs'].size}),
+      'rhs': ml_context.createBuffer({size: inputs['rhs'].size}),
+    };
+
+    const dispatch_2_inputs = {
+      'lhs': ml_context.createBuffer({size: inputs['lhs'].size}),
+      'rhs': ml_context.createBuffer({size: inputs['rhs'].size}),
+    };
+
+    const dispatch_outputs = {
+      'output1': ml_context.createBuffer({size: outputs['output1'].size}),
+      'output2': ml_context.createBuffer({size: outputs['output2'].size}),
+    };
+
+    // Initialize inputs
+    const input_1_data =
+        new TypedArrayDict['float32'](sizeOfShape(shape)).fill(1.0);
+    ml_context.writeBuffer(dispatch_1_inputs['lhs'], input_1_data);
+    ml_context.writeBuffer(dispatch_1_inputs['rhs'], input_1_data);
+
+    const input_2_data =
+        new TypedArrayDict['float32'](sizeOfShape(shape)).fill(2.0);
+    ml_context.writeBuffer(dispatch_2_inputs['lhs'], input_2_data);
+    ml_context.writeBuffer(dispatch_2_inputs['rhs'], input_2_data);
+
+    // Output = LHS_1 + RHS_1 = 1 + 1 = 2
+    ml_context.dispatch(ml_graph, dispatch_1_inputs, dispatch_outputs);
+
+    // Output = LHS_2 + RHS_2 = 2 + 2 = 4
+    ml_context.dispatch(ml_graph, dispatch_2_inputs, dispatch_outputs);
+
+    await assert_buffer_data_equals(
+        ml_context, dispatch_outputs['output1'],
+        new Float32Array(sizeOfShape(shape)).fill(4.0));
+
+    await assert_buffer_data_equals(
+        ml_context, dispatch_outputs['output2'],
+        new Float32Array(sizeOfShape(shape)).fill(4.0));
+  }, `${testName} / same_outputs`);
+
+  promise_test(async () => {
+    // MLBuffer was unsupported for the deviceType.
+    if (!isMLBufferSupported(ml_context)) {
+      return;
+    }
+
+    const dispatch_inputs = {
+      'lhs': ml_context.createBuffer({size: inputs['lhs'].size}),
+      'rhs': ml_context.createBuffer({size: inputs['rhs'].size}),
+    };
+
+    const dispatch_outputs = {
+      'output1': ml_context.createBuffer({size: outputs['output1'].size}),
+      'output2': ml_context.createBuffer({size: outputs['output2'].size}),
+    };
+
+    // Initialize inputs
+    const input_data =
+        new TypedArrayDict['float32'](sizeOfShape(shape)).fill(1.0);
+    ml_context.writeBuffer(dispatch_inputs['lhs'], input_data);
+    ml_context.writeBuffer(dispatch_inputs['rhs'], input_data);
+
+    // Output = LHS + RHS = 1 + 1 = 2
+    ml_context.dispatch(ml_graph, dispatch_inputs, dispatch_outputs);
+    ml_context.dispatch(ml_graph, dispatch_inputs, dispatch_outputs);
+
+    await assert_buffer_data_equals(
+        ml_context, dispatch_outputs['output1'],
+        new Float32Array(sizeOfShape(shape)).fill(2.0));
+
+    await assert_buffer_data_equals(
+        ml_context, dispatch_outputs['output2'],
+        new Float32Array(sizeOfShape(shape)).fill(2.0));
+  }, `${testName} / same_inputs_and_outputs`);
+
+  promise_test(async () => {
+    // MLBuffer was unsupported for the deviceType.
+    if (!isMLBufferSupported(ml_context)) {
+      return;
+    }
+
+    const dispatch_inputs = {
+      'lhs': ml_context.createBuffer({size: inputs['lhs'].size}),
+      'rhs': ml_context.createBuffer({size: inputs['rhs'].size}),
+    };
+
+    const dispatch_1_outputs = {
+      'output1': ml_context.createBuffer({size: outputs['output1'].size}),
+      'output2': ml_context.createBuffer({size: outputs['output2'].size}),
+    };
+
+    const dispatch_2_outputs = {
+      'output1': ml_context.createBuffer({size: outputs['output1'].size}),
+      'output2': ml_context.createBuffer({size: outputs['output2'].size}),
+    };
+
+    // Initialize inputs
+    const input_data =
+        new TypedArrayDict['float32'](sizeOfShape(shape)).fill(1.0);
+    ml_context.writeBuffer(dispatch_inputs['lhs'], input_data);
+    ml_context.writeBuffer(dispatch_inputs['rhs'], input_data);
+
+    // Output_1 = LHS + RHS = 1 + 1 = 2
+    ml_context.dispatch(ml_graph, dispatch_inputs, dispatch_1_outputs);
+
+    // Output_2 = Output_1_LHS + Output_1_RHS = 2 + 2 = 4
+    ml_context.dispatch(
+        ml_graph, {
+          'lhs': dispatch_1_outputs['output1'],
+          'rhs': dispatch_1_outputs['output2'],
+        },
+        dispatch_2_outputs);
+
+    // Output_1 = Output_2_LHS + Output_2_RHS = 4 + 4 = 8
+    ml_context.dispatch(
+        ml_graph, {
+          'lhs': dispatch_2_outputs['output1'],
+          'rhs': dispatch_2_outputs['output2'],
+        },
+        dispatch_1_outputs);
+
+    await assert_buffer_data_equals(
+        ml_context, dispatch_1_outputs['output1'],
+        new Float32Array(sizeOfShape(shape)).fill(8));
+
+    await assert_buffer_data_equals(
+        ml_context, dispatch_1_outputs['output2'],
+        new Float32Array(sizeOfShape(shape)).fill(8));
+  }, `${testName} / outputs_as_inputs`);
+};
diff --git a/third_party/blink/web_tests/external/wpt/workers/modules/WEB_FEATURES.yml b/third_party/blink/web_tests/external/wpt/workers/modules/WEB_FEATURES.yml
new file mode 100644
index 0000000..ab73efc0
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/workers/modules/WEB_FEATURES.yml
@@ -0,0 +1,7 @@
+features:
+- name: js-modules-workers
+  files:
+  - dedicated-worker-*
+- name: js-modules-shared-workers
+  files:
+  - shared-worker-*
diff --git a/third_party/blink/web_tests/fast/dom/Window/event-non-enumerable-expected.txt b/third_party/blink/web_tests/fast/dom/Window/event-non-enumerable-expected.txt
deleted file mode 100644
index 195e62aa..0000000
--- a/third_party/blink/web_tests/fast/dom/Window/event-non-enumerable-expected.txt
+++ /dev/null
@@ -1,6 +0,0 @@
-PASS "event" in window is true
-PASS window.propertyIsEnumerable("event") is false
-PASS successfullyParsed is true
-
-TEST COMPLETE
-
diff --git a/third_party/blink/web_tests/fast/dom/Window/event-non-enumerable.html b/third_party/blink/web_tests/fast/dom/Window/event-non-enumerable.html
deleted file mode 100644
index db22e70..0000000
--- a/third_party/blink/web_tests/fast/dom/Window/event-non-enumerable.html
+++ /dev/null
@@ -1,8 +0,0 @@
-<!DOCTYPE html>
-<script src="../../../resources/js-test.js"></script>
-<script>
-
-shouldBeTrue('"event" in window');
-shouldBeFalse('window.propertyIsEnumerable("event")');
-
-</script>
diff --git a/third_party/blink/web_tests/flag-specific/disable-site-isolation-trials/virtual/direct-sockets/external/wpt/direct-sockets/disabled-by-permissions-policy.https.sub-expected.txt b/third_party/blink/web_tests/flag-specific/disable-site-isolation-trials/virtual/direct-sockets/external/wpt/direct-sockets/disabled-by-permissions-policy.https.sub-expected.txt
index e6c71b9c..039793f 100644
--- a/third_party/blink/web_tests/flag-specific/disable-site-isolation-trials/virtual/direct-sockets/external/wpt/direct-sockets/disabled-by-permissions-policy.https.sub-expected.txt
+++ b/third_party/blink/web_tests/flag-specific/disable-site-isolation-trials/virtual/direct-sockets/external/wpt/direct-sockets/disabled-by-permissions-policy.https.sub-expected.txt
@@ -1,7 +1,9 @@
 This is a testharness.js-based test.
-[FAIL] tcp disabled by permissions-policy
-  assert_throws_dom: constructor should throw function "() => new TCPSocket("address.com", 53)" threw object "ReferenceError: TCPSocket is not defined" that is not a DOMException NotAllowedError: property "code" is equal to undefined, expected 0
-[FAIL] udp disabled by permissions-policy
-  assert_throws_dom: constructor should throw function "() => new UDPSocket({ remoteAddress: "address.com", remotePort: 53 })" threw object "ReferenceError: UDPSocket is not defined" that is not a DOMException NotAllowedError: property "code" is equal to undefined, expected 0
+[FAIL] TCPSocket disabled by permissions-policy
+  assert_throws_dom: constructor should throw function "() => {\n    new TCPSocket("127.0.0.1", 53);\n  }" threw object "ReferenceError: TCPSocket is not defined" that is not a DOMException NotAllowedError: property "code" is equal to undefined, expected 0
+[FAIL] UDPSocket disabled by permissions-policy
+  assert_throws_dom: constructor should throw function "() => {\n    new UDPSocket({ remoteAddress: "127.0.0.1", remotePort: 53 });\n  }" threw object "ReferenceError: UDPSocket is not defined" that is not a DOMException NotAllowedError: property "code" is equal to undefined, expected 0
+[FAIL] TCPServerSocket disabled by permissions-policy
+  assert_throws_dom: constructor should throw function "() => {\n    new TCPServerSocket("127.0.0.1");\n  }" threw object "ReferenceError: TCPServerSocket is not defined" that is not a DOMException NotAllowedError: property "code" is equal to undefined, expected 0
 Harness: the test ran to completion.
 
diff --git a/third_party/blink/web_tests/flag-specific/disable-site-isolation-trials/virtual/direct-sockets/external/wpt/direct-sockets/tcp_socket.https-expected.txt b/third_party/blink/web_tests/flag-specific/disable-site-isolation-trials/virtual/direct-sockets/external/wpt/direct-sockets/tcp_socket.https-expected.txt
new file mode 100644
index 0000000..3f8040e
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/disable-site-isolation-trials/virtual/direct-sockets/external/wpt/direct-sockets/tcp_socket.https-expected.txt
@@ -0,0 +1,5 @@
+This is a testharness.js-based test.
+[FAIL] TCPSocket exchanges packets with TCPServerSocket
+  promise_test: Unhandled rejection with value: object "ReferenceError: TCPServerSocket is not defined"
+Harness: the test ran to completion.
+
diff --git a/third_party/blink/web_tests/flag-specific/disable-site-isolation-trials/virtual/direct-sockets/external/wpt/direct-sockets/udp_socket.https-expected.txt b/third_party/blink/web_tests/flag-specific/disable-site-isolation-trials/virtual/direct-sockets/external/wpt/direct-sockets/udp_socket.https-expected.txt
new file mode 100644
index 0000000..c4f96b7
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/disable-site-isolation-trials/virtual/direct-sockets/external/wpt/direct-sockets/udp_socket.https-expected.txt
@@ -0,0 +1,5 @@
+This is a testharness.js-based test.
+[FAIL] UDPSocket (connected) exchanges datagrams with UDPSocket (bound)
+  promise_test: Unhandled rejection with value: object "ReferenceError: UDPSocket is not defined"
+Harness: the test ran to completion.
+
diff --git a/third_party/blink/web_tests/flag-specific/disable-site-isolation-trials/virtual/direct-sockets/wpt_internal/direct-sockets/enabled-on-self-origin-by-permissions-policy.https.sub-expected.txt b/third_party/blink/web_tests/flag-specific/disable-site-isolation-trials/virtual/direct-sockets/wpt_internal/direct-sockets/enabled-on-self-origin-by-permissions-policy.https.sub-expected.txt
deleted file mode 100644
index 31c037d..0000000
--- a/third_party/blink/web_tests/flag-specific/disable-site-isolation-trials/virtual/direct-sockets/wpt_internal/direct-sockets/enabled-on-self-origin-by-permissions-policy.https.sub-expected.txt
+++ /dev/null
@@ -1,7 +0,0 @@
-This is a testharness.js-based test.
-[FAIL] direct sockets (TCP) do not get blocked on permissions policy direct-sockets=(self)
-  promise_test: Unhandled rejection with value: object "ReferenceError: TCPSocket is not defined"
-[FAIL] direct sockets (UDP) do not get blocked on permissions policy direct-sockets=(self)
-  promise_test: Unhandled rejection with value: object "ReferenceError: UDPSocket is not defined"
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/flag-specific/enable-skia-graphite/transforms/transformed-focused-text-input-expected.png b/third_party/blink/web_tests/flag-specific/enable-skia-graphite/transforms/transformed-focused-text-input-expected.png
index c0938fa..3e25e30 100644
--- a/third_party/blink/web_tests/flag-specific/enable-skia-graphite/transforms/transformed-focused-text-input-expected.png
+++ b/third_party/blink/web_tests/flag-specific/enable-skia-graphite/transforms/transformed-focused-text-input-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/paint/invalidation/clip/caret-ancestor-clip-change-expected.txt b/third_party/blink/web_tests/paint/invalidation/clip/caret-ancestor-clip-change-expected.txt
index 75c9404..584be9c 100644
--- a/third_party/blink/web_tests/paint/invalidation/clip/caret-ancestor-clip-change-expected.txt
+++ b/third_party/blink/web_tests/paint/invalidation/clip/caret-ancestor-clip-change-expected.txt
@@ -11,27 +11,15 @@
     },
     {
       "name": "Caret",
-      "position": [0, 2],
+      "position": [12, 3],
       "bounds": [1, 112],
-      "contentsOpaque": true,
-      "transform": 1
+      "contentsOpaque": true
     },
     {
       "name": "LayoutTextControlSingleLine (inline) INPUT id='target'",
       "position": [8, 8],
       "bounds": [109, 112]
     }
-  ],
-  "transforms": [
-    {
-      "id": 1,
-      "transform": [
-        [1, 0, 0, 0],
-        [0, 1, 0, 0],
-        [0, 0, 1, 0],
-        [12, 1, 0, 1]
-      ]
-    }
   ]
 }
 
diff --git a/third_party/blink/web_tests/platform/linux/transforms/transformed-focused-text-input-expected.png b/third_party/blink/web_tests/platform/linux/transforms/transformed-focused-text-input-expected.png
index d577e6c3..8a37edca5 100644
--- a/third_party/blink/web_tests/platform/linux/transforms/transformed-focused-text-input-expected.png
+++ b/third_party/blink/web_tests/platform/linux/transforms/transformed-focused-text-input-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac14-arm64/virtual/webnn-service-with-gpu/external/wpt/webnn/conformance_tests/linear.https.any.worker_gpu-expected.txt b/third_party/blink/web_tests/platform/mac-mac14-arm64/virtual/webnn-service-with-gpu/external/wpt/webnn/conformance_tests/linear.https.any.worker_gpu-expected.txt
new file mode 100644
index 0000000..5b37deb
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac-mac14-arm64/virtual/webnn-service-with-gpu/external/wpt/webnn/conformance_tests/linear.https.any.worker_gpu-expected.txt
@@ -0,0 +1,4 @@
+This is a testharness.js-based test.
+All subtests passed and are omitted for brevity.
+See https://chromium.googlesource.com/chromium/src/+/HEAD/docs/testing/writing_web_tests.md#Text-Test-Baselines for details.
+Harness: the test ran to completion.
diff --git a/third_party/blink/web_tests/platform/mac-mac14-arm64/virtual/webnn-service-with-gpu/external/wpt/webnn/conformance_tests/linear.https.any_gpu-expected.txt b/third_party/blink/web_tests/platform/mac-mac14-arm64/virtual/webnn-service-with-gpu/external/wpt/webnn/conformance_tests/linear.https.any_gpu-expected.txt
new file mode 100644
index 0000000..5b37deb
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac-mac14-arm64/virtual/webnn-service-with-gpu/external/wpt/webnn/conformance_tests/linear.https.any_gpu-expected.txt
@@ -0,0 +1,4 @@
+This is a testharness.js-based test.
+All subtests passed and are omitted for brevity.
+See https://chromium.googlesource.com/chromium/src/+/HEAD/docs/testing/writing_web_tests.md#Text-Test-Baselines for details.
+Harness: the test ran to completion.
diff --git a/third_party/blink/web_tests/platform/mac/paint/invalidation/caret-subpixel-expected.txt b/third_party/blink/web_tests/platform/mac/paint/invalidation/caret-subpixel-expected.txt
index 4a71c00d..8edcfae 100644
--- a/third_party/blink/web_tests/platform/mac/paint/invalidation/caret-subpixel-expected.txt
+++ b/third_party/blink/web_tests/platform/mac/paint/invalidation/caret-subpixel-expected.txt
@@ -11,21 +11,9 @@
     },
     {
       "name": "Caret",
-      "position": [200, 0],
+      "position": [212, 11],
       "bounds": [1, 15],
-      "contentsOpaque": true,
-      "transform": 1
-    }
-  ],
-  "transforms": [
-    {
-      "id": 1,
-      "transform": [
-        [1, 0, 0, 0],
-        [0, 1, 0, 0],
-        [0, 0, 1, 0],
-        [12, 11, 0, 1]
-      ]
+      "contentsOpaque": true
     }
   ]
 }
diff --git a/third_party/blink/web_tests/platform/mac/paint/invalidation/scroll/caret-with-composited-scroll-expected.txt b/third_party/blink/web_tests/platform/mac/paint/invalidation/scroll/caret-with-composited-scroll-expected.txt
index 45d66838..6f9dc02 100644
--- a/third_party/blink/web_tests/platform/mac/paint/invalidation/scroll/caret-with-composited-scroll-expected.txt
+++ b/third_party/blink/web_tests/platform/mac/paint/invalidation/scroll/caret-with-composited-scroll-expected.txt
@@ -28,9 +28,10 @@
     },
     {
       "name": "Caret",
+      "position": [3, 1003],
       "bounds": [1, 15],
       "contentsOpaque": true,
-      "transform": 3
+      "transform": 2
     },
     {
       "name": "LayoutTextControlSingleLine (inline) INPUT id='text'",
@@ -61,16 +62,6 @@
         [0, 0, 1, 0],
         [0, -921, 0, 1]
       ]
-    },
-    {
-      "id": 3,
-      "parent": 2,
-      "transform": [
-        [1, 0, 0, 0],
-        [0, 1, 0, 0],
-        [0, 0, 1, 0],
-        [3, 1003, 0, 1]
-      ]
     }
   ]
 }
diff --git a/third_party/blink/web_tests/platform/mac/transforms/transformed-focused-text-input-expected.png b/third_party/blink/web_tests/platform/mac/transforms/transformed-focused-text-input-expected.png
index 0ea851b0..934495f 100644
--- a/third_party/blink/web_tests/platform/mac/transforms/transformed-focused-text-input-expected.png
+++ b/third_party/blink/web_tests/platform/mac/transforms/transformed-focused-text-input-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/virtual/webnn-service-with-gpu/external/wpt/webnn/conformance_tests/linear.https.any.worker_gpu-expected.txt b/third_party/blink/web_tests/platform/mac/virtual/webnn-service-with-gpu/external/wpt/webnn/conformance_tests/linear.https.any.worker_gpu-expected.txt
index 61de2a7..41b47c2c 100644
--- a/third_party/blink/web_tests/platform/mac/virtual/webnn-service-with-gpu/external/wpt/webnn/conformance_tests/linear.https.any.worker_gpu-expected.txt
+++ b/third_party/blink/web_tests/platform/mac/virtual/webnn-service-with-gpu/external/wpt/webnn/conformance_tests/linear.https.any.worker_gpu-expected.txt
@@ -1,27 +1,27 @@
 This is a testharness.js-based test.
 [FAIL] linear float32 1D constant tensor default options
-  promise_test: Unhandled rejection with value: object "NotSupportedError: Failed to execute 'build' on 'MLGraphBuilder': This operator is not implemented."
+  assert_true: assert_array_approx_equals_ulp: test linear float32 actual -1.1220703125 should be close enough to expected -1.12251615524292 by the acceptable 2 ULP distance, but they have 3740 ULP distance expected true got false
 [FAIL] linear float32 1D tensor default options
-  promise_test: Unhandled rejection with value: object "NotSupportedError: Failed to execute 'build' on 'MLGraphBuilder': This operator is not implemented."
+  assert_true: assert_array_approx_equals_ulp: test linear float32 actual -1.1220703125 should be close enough to expected -1.12251615524292 by the acceptable 2 ULP distance, but they have 3740 ULP distance expected true got false
 [FAIL] linear float32 2D tensor default options
-  promise_test: Unhandled rejection with value: object "NotSupportedError: Failed to execute 'build' on 'MLGraphBuilder': This operator is not implemented."
+  assert_true: assert_array_approx_equals_ulp: test linear float32 actual -1.1220703125 should be close enough to expected -1.12251615524292 by the acceptable 2 ULP distance, but they have 3740 ULP distance expected true got false
 [FAIL] linear float32 3D tensor default options
-  promise_test: Unhandled rejection with value: object "NotSupportedError: Failed to execute 'build' on 'MLGraphBuilder': This operator is not implemented."
+  assert_true: assert_array_approx_equals_ulp: test linear float32 actual -1.1220703125 should be close enough to expected -1.12251615524292 by the acceptable 2 ULP distance, but they have 3740 ULP distance expected true got false
 [FAIL] linear float32 4D tensor default options
-  promise_test: Unhandled rejection with value: object "NotSupportedError: Failed to execute 'build' on 'MLGraphBuilder': This operator is not implemented."
+  assert_true: assert_array_approx_equals_ulp: test linear float32 actual -1.1220703125 should be close enough to expected -1.12251615524292 by the acceptable 2 ULP distance, but they have 3740 ULP distance expected true got false
 [FAIL] linear float32 5D tensor default options
-  promise_test: Unhandled rejection with value: object "NotSupportedError: Failed to execute 'build' on 'MLGraphBuilder': This operator is not implemented."
+  assert_true: assert_array_approx_equals_ulp: test linear float32 actual -1.1220703125 should be close enough to expected -1.12251615524292 by the acceptable 2 ULP distance, but they have 3740 ULP distance expected true got false
 [FAIL] linear float32 4D tensor specified options.alpha and default options.beta
-  promise_test: Unhandled rejection with value: object "NotSupportedError: Failed to execute 'build' on 'MLGraphBuilder': This operator is not implemented."
+  assert_true: assert_array_approx_equals_ulp: test linear float32 actual -8.296875 should be close enough to expected -8.305265426635742 by the acceptable 2 ULP distance, but they have 8798 ULP distance expected true got false
 [FAIL] linear float32 positive 4D tensor specified positive options.beta and default options.alpha
-  promise_test: Unhandled rejection with value: object "NotSupportedError: Failed to execute 'build' on 'MLGraphBuilder': This operator is not implemented."
+  assert_true: assert_array_approx_equals_ulp: test linear float32 actual 11.015625 should be close enough to expected 11.017641067504883 by the acceptable 2 ULP distance, but they have 2114 ULP distance expected true got false
 [FAIL] linear float32 negative 4D tensor specified negative options.beta and default options.alpha
-  promise_test: Unhandled rejection with value: object "NotSupportedError: Failed to execute 'build' on 'MLGraphBuilder': This operator is not implemented."
+  assert_true: assert_array_approx_equals_ulp: test linear float32 actual -11.015625 should be close enough to expected -11.017641067504883 by the acceptable 2 ULP distance, but they have 2114 ULP distance expected true got false
 [FAIL] linear float32 positive 4D tensor all options (positive options.alpha and positive options.beta)
-  promise_test: Unhandled rejection with value: object "NotSupportedError: Failed to execute 'build' on 'MLGraphBuilder': This operator is not implemented."
+  assert_true: assert_array_approx_equals_ulp: test linear float32 actual 43.59375 should be close enough to expected 43.64218521118164 by the acceptable 2 ULP distance, but they have 12697 ULP distance expected true got false
 [FAIL] linear float32 positive 4D tensor all options (negative options.alpha and negative options.beta)
-  promise_test: Unhandled rejection with value: object "NotSupportedError: Failed to execute 'build' on 'MLGraphBuilder': This operator is not implemented."
+  assert_true: assert_array_approx_equals_ulp: test linear float32 actual -43.59375 should be close enough to expected -43.64218521118164 by the acceptable 2 ULP distance, but they have 12697 ULP distance expected true got false
 [FAIL] linear float32 negative 4D tensor all options (positive options.alpha and negative options.beta)
-  promise_test: Unhandled rejection with value: object "NotSupportedError: Failed to execute 'build' on 'MLGraphBuilder': This operator is not implemented."
+  assert_true: assert_array_approx_equals_ulp: test linear float32 actual -43.59375 should be close enough to expected -43.64218521118164 by the acceptable 2 ULP distance, but they have 12697 ULP distance expected true got false
 Harness: the test ran to completion.
 
diff --git a/third_party/blink/web_tests/platform/mac/virtual/webnn-service-with-gpu/external/wpt/webnn/conformance_tests/linear.https.any_gpu-expected.txt b/third_party/blink/web_tests/platform/mac/virtual/webnn-service-with-gpu/external/wpt/webnn/conformance_tests/linear.https.any_gpu-expected.txt
index 61de2a7..41b47c2c 100644
--- a/third_party/blink/web_tests/platform/mac/virtual/webnn-service-with-gpu/external/wpt/webnn/conformance_tests/linear.https.any_gpu-expected.txt
+++ b/third_party/blink/web_tests/platform/mac/virtual/webnn-service-with-gpu/external/wpt/webnn/conformance_tests/linear.https.any_gpu-expected.txt
@@ -1,27 +1,27 @@
 This is a testharness.js-based test.
 [FAIL] linear float32 1D constant tensor default options
-  promise_test: Unhandled rejection with value: object "NotSupportedError: Failed to execute 'build' on 'MLGraphBuilder': This operator is not implemented."
+  assert_true: assert_array_approx_equals_ulp: test linear float32 actual -1.1220703125 should be close enough to expected -1.12251615524292 by the acceptable 2 ULP distance, but they have 3740 ULP distance expected true got false
 [FAIL] linear float32 1D tensor default options
-  promise_test: Unhandled rejection with value: object "NotSupportedError: Failed to execute 'build' on 'MLGraphBuilder': This operator is not implemented."
+  assert_true: assert_array_approx_equals_ulp: test linear float32 actual -1.1220703125 should be close enough to expected -1.12251615524292 by the acceptable 2 ULP distance, but they have 3740 ULP distance expected true got false
 [FAIL] linear float32 2D tensor default options
-  promise_test: Unhandled rejection with value: object "NotSupportedError: Failed to execute 'build' on 'MLGraphBuilder': This operator is not implemented."
+  assert_true: assert_array_approx_equals_ulp: test linear float32 actual -1.1220703125 should be close enough to expected -1.12251615524292 by the acceptable 2 ULP distance, but they have 3740 ULP distance expected true got false
 [FAIL] linear float32 3D tensor default options
-  promise_test: Unhandled rejection with value: object "NotSupportedError: Failed to execute 'build' on 'MLGraphBuilder': This operator is not implemented."
+  assert_true: assert_array_approx_equals_ulp: test linear float32 actual -1.1220703125 should be close enough to expected -1.12251615524292 by the acceptable 2 ULP distance, but they have 3740 ULP distance expected true got false
 [FAIL] linear float32 4D tensor default options
-  promise_test: Unhandled rejection with value: object "NotSupportedError: Failed to execute 'build' on 'MLGraphBuilder': This operator is not implemented."
+  assert_true: assert_array_approx_equals_ulp: test linear float32 actual -1.1220703125 should be close enough to expected -1.12251615524292 by the acceptable 2 ULP distance, but they have 3740 ULP distance expected true got false
 [FAIL] linear float32 5D tensor default options
-  promise_test: Unhandled rejection with value: object "NotSupportedError: Failed to execute 'build' on 'MLGraphBuilder': This operator is not implemented."
+  assert_true: assert_array_approx_equals_ulp: test linear float32 actual -1.1220703125 should be close enough to expected -1.12251615524292 by the acceptable 2 ULP distance, but they have 3740 ULP distance expected true got false
 [FAIL] linear float32 4D tensor specified options.alpha and default options.beta
-  promise_test: Unhandled rejection with value: object "NotSupportedError: Failed to execute 'build' on 'MLGraphBuilder': This operator is not implemented."
+  assert_true: assert_array_approx_equals_ulp: test linear float32 actual -8.296875 should be close enough to expected -8.305265426635742 by the acceptable 2 ULP distance, but they have 8798 ULP distance expected true got false
 [FAIL] linear float32 positive 4D tensor specified positive options.beta and default options.alpha
-  promise_test: Unhandled rejection with value: object "NotSupportedError: Failed to execute 'build' on 'MLGraphBuilder': This operator is not implemented."
+  assert_true: assert_array_approx_equals_ulp: test linear float32 actual 11.015625 should be close enough to expected 11.017641067504883 by the acceptable 2 ULP distance, but they have 2114 ULP distance expected true got false
 [FAIL] linear float32 negative 4D tensor specified negative options.beta and default options.alpha
-  promise_test: Unhandled rejection with value: object "NotSupportedError: Failed to execute 'build' on 'MLGraphBuilder': This operator is not implemented."
+  assert_true: assert_array_approx_equals_ulp: test linear float32 actual -11.015625 should be close enough to expected -11.017641067504883 by the acceptable 2 ULP distance, but they have 2114 ULP distance expected true got false
 [FAIL] linear float32 positive 4D tensor all options (positive options.alpha and positive options.beta)
-  promise_test: Unhandled rejection with value: object "NotSupportedError: Failed to execute 'build' on 'MLGraphBuilder': This operator is not implemented."
+  assert_true: assert_array_approx_equals_ulp: test linear float32 actual 43.59375 should be close enough to expected 43.64218521118164 by the acceptable 2 ULP distance, but they have 12697 ULP distance expected true got false
 [FAIL] linear float32 positive 4D tensor all options (negative options.alpha and negative options.beta)
-  promise_test: Unhandled rejection with value: object "NotSupportedError: Failed to execute 'build' on 'MLGraphBuilder': This operator is not implemented."
+  assert_true: assert_array_approx_equals_ulp: test linear float32 actual -43.59375 should be close enough to expected -43.64218521118164 by the acceptable 2 ULP distance, but they have 12697 ULP distance expected true got false
 [FAIL] linear float32 negative 4D tensor all options (positive options.alpha and negative options.beta)
-  promise_test: Unhandled rejection with value: object "NotSupportedError: Failed to execute 'build' on 'MLGraphBuilder': This operator is not implemented."
+  assert_true: assert_array_approx_equals_ulp: test linear float32 actual -43.59375 should be close enough to expected -43.64218521118164 by the acceptable 2 ULP distance, but they have 12697 ULP distance expected true got false
 Harness: the test ran to completion.
 
diff --git a/third_party/blink/web_tests/platform/win/paint/invalidation/caret-subpixel-expected.txt b/third_party/blink/web_tests/platform/win/paint/invalidation/caret-subpixel-expected.txt
index 1471bef..c3a1db6 100644
--- a/third_party/blink/web_tests/platform/win/paint/invalidation/caret-subpixel-expected.txt
+++ b/third_party/blink/web_tests/platform/win/paint/invalidation/caret-subpixel-expected.txt
@@ -11,21 +11,9 @@
     },
     {
       "name": "Caret",
-      "position": [200, 0],
+      "position": [212, 11],
       "bounds": [1, 16],
-      "contentsOpaque": true,
-      "transform": 1
-    }
-  ],
-  "transforms": [
-    {
-      "id": 1,
-      "transform": [
-        [1, 0, 0, 0],
-        [0, 1, 0, 0],
-        [0, 0, 1, 0],
-        [12, 11, 0, 1]
-      ]
+      "contentsOpaque": true
     }
   ]
 }
diff --git a/third_party/blink/web_tests/platform/win/paint/invalidation/scroll/caret-with-composited-scroll-expected.txt b/third_party/blink/web_tests/platform/win/paint/invalidation/scroll/caret-with-composited-scroll-expected.txt
index 35403fc..dffe2a6 100644
--- a/third_party/blink/web_tests/platform/win/paint/invalidation/scroll/caret-with-composited-scroll-expected.txt
+++ b/third_party/blink/web_tests/platform/win/paint/invalidation/scroll/caret-with-composited-scroll-expected.txt
@@ -28,9 +28,10 @@
     },
     {
       "name": "Caret",
+      "position": [3, 1003],
       "bounds": [1, 16],
       "contentsOpaque": true,
-      "transform": 3
+      "transform": 2
     },
     {
       "name": "LayoutTextControlSingleLine (inline) INPUT id='text'",
@@ -61,16 +62,6 @@
         [0, 0, 1, 0],
         [0, -922, 0, 1]
       ]
-    },
-    {
-      "id": 3,
-      "parent": 2,
-      "transform": [
-        [1, 0, 0, 0],
-        [0, 1, 0, 0],
-        [0, 0, 1, 0],
-        [3, 1003, 0, 1]
-      ]
     }
   ]
 }
diff --git a/third_party/blink/web_tests/platform/win/transforms/transformed-focused-text-input-expected.png b/third_party/blink/web_tests/platform/win/transforms/transformed-focused-text-input-expected.png
index ad847f9..aea948b5 100644
--- a/third_party/blink/web_tests/platform/win/transforms/transformed-focused-text-input-expected.png
+++ b/third_party/blink/web_tests/platform/win/transforms/transformed-focused-text-input-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/virtual/direct-sockets/wpt_internal/direct-sockets/README.txt b/third_party/blink/web_tests/virtual/direct-sockets/wpt_internal/direct-sockets/README.txt
deleted file mode 100644
index 076c633..0000000
--- a/third_party/blink/web_tests/virtual/direct-sockets/wpt_internal/direct-sockets/README.txt
+++ /dev/null
@@ -1,2 +0,0 @@
-# This suite runs wpt_internal/direct-sockets/ with
-# --isolated-context-origins=https://web-platform.test
diff --git a/third_party/blink/web_tests/virtual/stable/webexposed/css-properties-as-js-properties-expected.txt b/third_party/blink/web_tests/virtual/stable/webexposed/css-properties-as-js-properties-expected.txt
index 7cc3993c..b35b5e4f 100644
--- a/third_party/blink/web_tests/virtual/stable/webexposed/css-properties-as-js-properties-expected.txt
+++ b/third_party/blink/web_tests/virtual/stable/webexposed/css-properties-as-js-properties-expected.txt
@@ -291,6 +291,7 @@
 minInlineSize
 minWidth
 mixBlendMode
+navigation
 negative
 objectFit
 objectPosition
@@ -463,6 +464,7 @@
 transitionProperty
 transitionTimingFunction
 translate
+types
 unicodeBidi
 unicodeRange
 userSelect
diff --git a/third_party/blink/web_tests/virtual/stable/webexposed/global-interface-listing-expected.txt b/third_party/blink/web_tests/virtual/stable/webexposed/global-interface-listing-expected.txt
index 3ef25a4..7cbe0b1d 100644
--- a/third_party/blink/web_tests/virtual/stable/webexposed/global-interface-listing-expected.txt
+++ b/third_party/blink/web_tests/virtual/stable/webexposed/global-interface-listing-expected.txt
@@ -957,6 +957,11 @@
     getter variable
     method constructor
     setter variable
+interface CSSViewTransitionRule : CSSRule
+    attribute @@toStringTag
+    getter navigation
+    getter types
+    method constructor
 interface Cache
     attribute @@toStringTag
     method add
@@ -6252,10 +6257,12 @@
     method constructor
 interface PageRevealEvent : Event
     attribute @@toStringTag
+    getter viewTransition
     method constructor
 interface PageSwapEvent : Event
     attribute @@toStringTag
     getter activation
+    getter viewTransition
     method constructor
 interface PageTransitionEvent : Event
     attribute @@toStringTag
@@ -8582,7 +8589,6 @@
     setter fullscreenElement
     setter innerHTML
     setter onslotchange
-    setter serializable
 interface SharedWorker : EventTarget
     attribute @@toStringTag
     getter onerror
diff --git a/third_party/blink/web_tests/webexposed/global-interface-listing-dedicated-worker-expected.txt b/third_party/blink/web_tests/webexposed/global-interface-listing-dedicated-worker-expected.txt
index 7fcea7c..49f2e09 100644
--- a/third_party/blink/web_tests/webexposed/global-interface-listing-dedicated-worker-expected.txt
+++ b/third_party/blink/web_tests/webexposed/global-interface-listing-dedicated-worker-expected.txt
@@ -1184,6 +1184,7 @@
 [Worker]     method compute
 [Worker]     method constructor
 [Worker]     method createBuffer
+[Worker]     method dispatch
 [Worker]     method readBuffer
 [Worker]     method writeBuffer
 [Worker] interface MLGraph
diff --git a/third_party/blink/web_tests/webexposed/global-interface-listing-expected.txt b/third_party/blink/web_tests/webexposed/global-interface-listing-expected.txt
index 7cd64ed..7666147 100644
--- a/third_party/blink/web_tests/webexposed/global-interface-listing-expected.txt
+++ b/third_party/blink/web_tests/webexposed/global-interface-listing-expected.txt
@@ -5851,6 +5851,7 @@
     method compute
     method constructor
     method createBuffer
+    method dispatch
     method readBuffer
     method writeBuffer
 interface MLGraph
@@ -9621,7 +9622,6 @@
     setter fullscreenElement
     setter innerHTML
     setter onslotchange
-    setter serializable
 interface SharedStorage
     attribute @@toStringTag
     getter worklet
diff --git a/third_party/blink/web_tests/wpt_internal/direct-sockets/DIR_METADATA b/third_party/blink/web_tests/wpt_internal/direct-sockets/DIR_METADATA
deleted file mode 100644
index e168aa3..0000000
--- a/third_party/blink/web_tests/wpt_internal/direct-sockets/DIR_METADATA
+++ /dev/null
@@ -1,6 +0,0 @@
-monorail: {
-  component: "Blink>Network"
-}
-buganizer_public: {
-  component_id: 1456852
-}
diff --git a/third_party/blink/web_tests/wpt_internal/direct-sockets/META.yml b/third_party/blink/web_tests/wpt_internal/direct-sockets/META.yml
deleted file mode 100644
index 85c05e8c..0000000
--- a/third_party/blink/web_tests/wpt_internal/direct-sockets/META.yml
+++ /dev/null
@@ -1,5 +0,0 @@
-spec: https://github.com/WICG/direct-sockets/blob/main/docs/explainer.md
-suggested_reviewers:
-  - ewilligers
-  - mgiuca
-  - phoglenix
diff --git a/third_party/blink/web_tests/wpt_internal/direct-sockets/OWNERS b/third_party/blink/web_tests/wpt_internal/direct-sockets/OWNERS
deleted file mode 100644
index 561efe7..0000000
--- a/third_party/blink/web_tests/wpt_internal/direct-sockets/OWNERS
+++ /dev/null
@@ -1,2 +0,0 @@
-# This directory already inherits owner '*', so these owners are informational.
-greengrape@google.com
diff --git a/third_party/blink/web_tests/wpt_internal/direct-sockets/README.md b/third_party/blink/web_tests/wpt_internal/direct-sockets/README.md
deleted file mode 100644
index 71a19de..0000000
--- a/third_party/blink/web_tests/wpt_internal/direct-sockets/README.md
+++ /dev/null
@@ -1,2 +0,0 @@
-These tests are for the TCP and UDP sockets API proposed in
-https://github.com/WICG/direct-sockets/blob/main/docs/explainer.md
diff --git a/third_party/blink/web_tests/wpt_internal/direct-sockets/enabled-on-self-origin-by-permissions-policy.https.sub.html b/third_party/blink/web_tests/wpt_internal/direct-sockets/enabled-on-self-origin-by-permissions-policy.https.sub.html
deleted file mode 100644
index de433bf1..0000000
--- a/third_party/blink/web_tests/wpt_internal/direct-sockets/enabled-on-self-origin-by-permissions-policy.https.sub.html
+++ /dev/null
@@ -1,15 +0,0 @@
-<!DOCTYPE html>
-<html>
-  <head>
-    <meta charset="utf-8" />
-    <title>Sockets test: can be enabled by default permissions policy (self)</title>
-    <script src="/resources/testharness.js"></script>
-    <script src="/resources/testharnessreport.js"></script>
-    <script src="/resources/testdriver.js"></script>
-    <script src="/resources/testdriver-vendor.js"></script>
-    <script src="resources/direct-sockets-helpers.js"></script>
-  </head>
-  <body>
-    <script src="enabled-on-self-origin-by-permissions-policy.js"></script>
-  </body>
-</html>
diff --git a/third_party/blink/web_tests/wpt_internal/direct-sockets/enabled-on-self-origin-by-permissions-policy.js b/third_party/blink/web_tests/wpt_internal/direct-sockets/enabled-on-self-origin-by-permissions-policy.js
deleted file mode 100644
index a771c3c8..0000000
--- a/third_party/blink/web_tests/wpt_internal/direct-sockets/enabled-on-self-origin-by-permissions-policy.js
+++ /dev/null
@@ -1,11 +0,0 @@
-'use strict';
-
-direct_sockets_test(async (t, mockDirectSocketsService) => {
-  const socket = new TCPSocket('127.0.0.1', 53);
-  await promise_rejects_dom(t, "NetworkError", socket.opened);
-}, "direct sockets (TCP) do not get blocked on permissions policy direct-sockets=(self)");
-
-direct_sockets_test(async (t, mockDirectSocketsService) => {
-  const socket = new UDPSocket({ remoteAddress: '127.0.0.1', remotePort: 53 });
-  await promise_rejects_dom(t, "NetworkError", socket.opened);
-}, "direct sockets (UDP) do not get blocked on permissions policy direct-sockets=(self)");
diff --git a/third_party/blink/web_tests/wpt_internal/direct-sockets/resources/direct-sockets-helpers.js b/third_party/blink/web_tests/wpt_internal/direct-sockets/resources/direct-sockets-helpers.js
deleted file mode 100644
index cd163c79..0000000
--- a/third_party/blink/web_tests/wpt_internal/direct-sockets/resources/direct-sockets-helpers.js
+++ /dev/null
@@ -1,35 +0,0 @@
-'use strict';
-
-// This mock provides a way to intercept renderer <> browser mojo messages for
-// openTCPSocket(...) and openUDPSocket(...) eliminating the need for an actual
-// browser.
-// See https://wicg.github.io/direct-sockets/
-
-async function loadChromiumResources() {
-    await import ('/resources/chromium/mock-direct-sockets.js');
-}
-
-let mockDirectSocketsService = null;
-
-async function createMockDirectSocketsService() {
-    if (typeof DirectSocketsServiceTest === 'undefined') {
-        await loadChromiumResources();
-    }
-    assert_implements(DirectSocketsServiceTest, 'DirectSocketsServiceTest is not loaded properly.');
-
-    if (mockDirectSocketsService === null) {
-        mockDirectSocketsService = new DirectSocketsServiceTest();
-    } else {
-        mockDirectSocketsService.reset();
-    }
-    mockDirectSocketsService.initialize();
-
-    return mockDirectSocketsService;
-}
-
-function direct_sockets_test(func, description) {
-    promise_test(async test => {
-        const directSocketsServiceTest = await createMockDirectSocketsService();
-        await func(test, mockDirectSocketsService);
-    }, description);
-}
diff --git a/third_party/blink/web_tests/wpt_internal/view-transition-types/shared-element-types-syntax-ref.html b/third_party/blink/web_tests/wpt_internal/view-transition-types/shared-element-types-syntax-ref.html
deleted file mode 100644
index 5db5382..0000000
--- a/third_party/blink/web_tests/wpt_internal/view-transition-types/shared-element-types-syntax-ref.html
+++ /dev/null
@@ -1,28 +0,0 @@
-<!DOCTYPE html>
-<title>View transitions: use dictionary syntax smoketest (ref)</title>
-<link rel="help" href="https://drafts.csswg.org/css-view-transitions-2/">
-<link rel="author" href="mailto:vmpstr@chromium.org">
-
-<style>
-div { contain: paint; }
-#left {
-  background: blue;
-  width: 100px;
-  height: 100px;
-  position: absolute;
-  top: 50px;
-  left: 50px;
-}
-#right {
-  width: 50px;
-  height: 50px;
-  background: green;
-  position: absolute;
-  top: 50px;
-  left: 250px;
-}
-body { background: lightpink; }
-</style>
-
-<div id=left></div>
-<div id=right></div>
diff --git a/third_party/blink/web_tests/wpt_internal/view-transition-types/shared-element-types-syntax.html b/third_party/blink/web_tests/wpt_internal/view-transition-types/shared-element-types-syntax.html
deleted file mode 100644
index cd610f1..0000000
--- a/third_party/blink/web_tests/wpt_internal/view-transition-types/shared-element-types-syntax.html
+++ /dev/null
@@ -1,81 +0,0 @@
-<!DOCTYPE html>
-<html class=reftest-wait>
-<title>View transitions: use dictionary syntax smoketest</title>
-<link rel="help" href="https://drafts.csswg.org/css-view-transitions-2/">
-<link rel="author" href="mailto:vmpstr@chromium.org">
-<link rel="match" href="shared-element-types-syntax-ref.html">
-
-<script src="/common/reftest-wait.js"></script>
-<style>
-div { contain: paint; }
-#left {
-  background: green;
-  width: 100px;
-  height: 100px;
-  position: absolute;
-  top: 50px;
-  left: 50px;
-}
-#right {
-  width: 50px;
-  height: 50px;
-  background: blue;
-  position: absolute;
-  top: 50px;
-  left: 250px;
-}
-.left-tag {
-  view-transition-name: left-element;
-}
-.right-tag {
-  view-transition-name: right-element;
-}
-
-.hidden {
-  background: pink;
-  width: 10px;
-  height: 10px;
-  view-transition-name: hidden;
-}
-
-html::view-transition-group(hidden) { animation-duration: 300s; }
-html::view-transition-image-pair(hidden) { animation: unset; opacity: 0; }
-
-html::view-transition-group(left-element),
-html::view-transition-group(right-element) { animation-duration: 0s; }
-
-html::view-transition-new(left-element),
-html::view-transition-new(right-element) { animation: unset; opacity: 0; }
-
-html::view-transition-old(left-element),
-html::view-transition-old(right-element) { animation: unset; opacity: 1; }
-
-html::view-transition-group(root) { animation: unset; opacity: 0; }
-html::view-transition { background: lightpink; }
-
-</style>
-
-<div id=left class="left-tag"></div>
-<div id=right class="right-tag"></div>
-
-<div id=hidden class=hidden></div>
-
-<script>
-failIfNot(document.startViewTransition, "Missing document.startViewTransition");
-
-async function runTest() {
-  document.startViewTransition({
-    update: () => {
-      left.classList.remove("left-tag");
-      left.classList.add("right-tag");
-
-      right.classList.remove("right-tag");
-      right.classList.add("left-tag");
-
-      requestAnimationFrame(() => requestAnimationFrame(takeScreenshot))
-    }
-  });
-}
-onload = () => requestAnimationFrame(() => requestAnimationFrame(runTest));
-</script>
-
diff --git a/third_party/dawn b/third_party/dawn
index d3bebe6..a87c533 160000
--- a/third_party/dawn
+++ b/third_party/dawn
@@ -1 +1 @@
-Subproject commit d3bebe6cf996439ed197d1c83e323baf8a9a7135
+Subproject commit a87c5333bf91c8c1eec2518c01235f311d7ccb0c
diff --git a/third_party/devtools-frontend-internal b/third_party/devtools-frontend-internal
index b905e0b..6307112 160000
--- a/third_party/devtools-frontend-internal
+++ b/third_party/devtools-frontend-internal
@@ -1 +1 @@
-Subproject commit b905e0bc023bf72eaccbb36735adc6873ceff486
+Subproject commit 6307112179b9bb77d936d8d433cca67d0bf64ba1
diff --git a/third_party/devtools-frontend/src b/third_party/devtools-frontend/src
index 9cc73b5..674f95a 160000
--- a/third_party/devtools-frontend/src
+++ b/third_party/devtools-frontend/src
@@ -1 +1 @@
-Subproject commit 9cc73b5e0315d6e6bc9b7d47458b076c94ed1cc0
+Subproject commit 674f95aa61785d51f0cbec824cf16d8cbf6f5a8a
diff --git a/third_party/freetype/README.chromium b/third_party/freetype/README.chromium
index ee54cf9..ea0672c 100644
--- a/third_party/freetype/README.chromium
+++ b/third_party/freetype/README.chromium
@@ -1,7 +1,7 @@
 Name: FreeType
 URL: http://www.freetype.org/
-Version: VER-2-13-2-133-g7bd887f17
-Revision: 7bd887f177b765165ecf7c443b8d0bab88034df0
+Version: VER-2-13-2-135-gd0e3239f3
+Revision: d0e3239f32a0fe71c234cf2c1ce7a90cb11b64bb
 CPEPrefix: cpe:/a:freetype:freetype:2.13.2
 License: Custom license "inspired by the BSD, Artistic, and IJG (Independent
          JPEG Group) licenses"
diff --git a/third_party/freetype/src b/third_party/freetype/src
index 7bd887f..d0e3239 160000
--- a/third_party/freetype/src
+++ b/third_party/freetype/src
@@ -1 +1 @@
-Subproject commit 7bd887f177b765165ecf7c443b8d0bab88034df0
+Subproject commit d0e3239f32a0fe71c234cf2c1ce7a90cb11b64bb
diff --git a/third_party/skia b/third_party/skia
index 3bc48d1..01f5db0 160000
--- a/third_party/skia
+++ b/third_party/skia
@@ -1 +1 @@
-Subproject commit 3bc48d128d4b081bb75a917c51c38f93d5a0e719
+Subproject commit 01f5db01210ff282f1ccb7c6e3cf53a8bc2453d3
diff --git a/third_party/subresource-filter-ruleset/data/.gitignore b/third_party/subresource-filter-ruleset/data/.gitignore
deleted file mode 100644
index c4c8391..0000000
--- a/third_party/subresource-filter-ruleset/data/.gitignore
+++ /dev/null
@@ -1,2 +0,0 @@
-# Ignore downloaded binaries
-/.*
diff --git a/third_party/subresource-filter-ruleset/data/UnindexedRules.sha1 b/third_party/subresource-filter-ruleset/data/UnindexedRules.sha1
deleted file mode 100644
index 4f678341..0000000
--- a/third_party/subresource-filter-ruleset/data/UnindexedRules.sha1
+++ /dev/null
@@ -1 +0,0 @@
-e4d1c702ca1b5497a3abcdd9495a5d0758f19ffc
\ No newline at end of file
diff --git a/third_party/webgpu-cts/src b/third_party/webgpu-cts/src
index 31fc42c..c032a34 160000
--- a/third_party/webgpu-cts/src
+++ b/third_party/webgpu-cts/src
@@ -1 +1 @@
-Subproject commit 31fc42ca8a0518b64668cad45139ec9c3cac87ae
+Subproject commit c032a348d7aac5b5bfa9146fb0702c069306231c
diff --git a/third_party/webrtc b/third_party/webrtc
index a2e33ed..89679bf 160000
--- a/third_party/webrtc
+++ b/third_party/webrtc
@@ -1 +1 @@
-Subproject commit a2e33ed8808384ed46adb5ee3e0f8a2932645655
+Subproject commit 89679bfd02dad86938b401da3e61f699ddc6b13b
diff --git a/tools/bisect/bisect_builds.py b/tools/bisect/bisect_builds.py
index 8a24ce1..e966901a 100755
--- a/tools/bisect/bisect_builds.py
+++ b/tools/bisect/bisect_builds.py
@@ -40,6 +40,8 @@
 CATAPULT_DIR = os.environ.get('CATAPULT_DIR', DEFAULT_CATAPULT_DIR)
 CATAPULT_REPO = 'https://github.com/catapult-project/catapult.git'
 DEVIL_PATH = os.path.abspath(os.path.join(CATAPULT_DIR, 'devil'))
+sys.path.append(DEVIL_PATH)
+from devil.android.sdk import version_codes
 
 # The base URL for stored build archives.
 CHROMIUM_BASE_URL = ('http://commondatastorage.googleapis.com'
@@ -176,8 +178,14 @@
     'official': {
         'android-arm': {
             'binary_name': None,
-            'listing_platform_dir': 'Android Builder/',
-            'archive_name': 'chrome-perf-arm.zip',
+            'listing_platform_dir': 'android-builder-perf/',
+            'archive_name': 'full-build-linux.zip',
+            'archive_extract_dir': 'full-build-linux'
+        },
+        'android-arm64': {
+            'binary_name': None,
+            'listing_platform_dir': 'android_arm64-builder-perf/',
+            'archive_name': 'full-build-linux.zip',
             'archive_extract_dir': 'full-build-linux'
         },
         'linux64': {
@@ -299,12 +307,41 @@
     }
 }
 
-# Currently we support only ChromePublic.apk produced by
-# perf builders.
 CHROME_APK_FILENAMES = {
-  'chromium': 'ChromePublic.apk',
+    'chrome': 'Chrome.apk',
+    'chrome_beta': 'ChromeBeta.apk',
+    'chrome_canary': 'ChromeCanary.apk',
+    'chrome_dev': 'ChromeDev.apk',
+    'chrome_stable': 'ChromeStable.apk',
+    'chromium': 'ChromePublic.apk',
 }
 
+CHROME_MODERN_APK_FILENAMES = {
+    'chrome': 'ChromeModern.apk',
+    'chrome_beta': 'ChromeModernBeta.apk',
+    'chrome_canary': 'ChromeModernCanary.apk',
+    'chrome_dev': 'ChromeModernDev.apk',
+    'chrome_stable': 'ChromeModernStable.apk',
+    'chromium': 'ChromePublic.apk',
+}
+
+MONOCHROME_APK_FILENAMES = {
+    'chrome': 'Monochrome.apk',
+    'chrome_beta': 'MonochromeBeta.apk',
+    'chrome_canary': 'MonochromeCanary.apk',
+    'chrome_dev': 'MonochromeDev.apk',
+    'chrome_stable': 'MonochromeStable.apk',
+    'chromium': 'ChromePublic.apk',
+}
+
+WEBVIEW_APK_FILENAMES = {
+    # clank release
+    'android_webview': 'AndroidWebview.apk',
+    # clank official
+    'system_webview_google': 'SystemWebViewGoogle.apk',
+    # upstream
+    'system_webview': 'SystemWebView.apk',
+}
 
 # Old storage locations for per CL builds
 OFFICIAL_BACKUP_BUILDS = {
@@ -1004,7 +1041,28 @@
   try:
     tempdir = tempfile.mkdtemp(prefix='bisect_tmp')
     UnzipFilenameToDir(zip_file, tempdir)
-    apk_path = os.path.join(tempdir, CHROME_APK_FILENAMES[context.apk])
+    sdk = context.device.build_version_sdk
+    if 'webview' in context.apk:
+      apk_filename = WEBVIEW_APK_FILENAMES[context.apk]
+    # Need these logic to bisect very old build. Release binaries are stored
+    # forever and occasionally there are requests to bisect issues introduced
+    # in very old versions.
+    elif sdk < version_codes.LOLLIPOP:
+      apk_filename = CHROME_APK_FILENAMES[context.apk]
+    elif sdk < version_codes.NOUGAT:
+      apk_filename = CHROME_MODERN_APK_FILENAMES[context.apk]
+    else:
+      apk_filename = MONOCHROME_APK_FILENAMES[context.apk]
+
+    apk_dir = os.path.join(tempdir, context._archive_extract_dir, 'apks')
+    apk_path = os.path.join(apk_dir, apk_filename)
+    if not os.path.exists(apk_path):
+      print('%s does not exist.' % apk_path)
+      if os.path.exists(apk_dir):
+        print('Are you using the correct apk? The list of available apks:')
+        apk_files = [f for f in os.listdir(apk_dir) if f.endswith('.apk')]
+        print(apk_files)
+      exit(1)
     InstallonAndroid(context.device, apk_path)
     LaunchOnAndroid(context.device, context.apk)
   finally:
@@ -1692,6 +1750,9 @@
 
 def LaunchOnAndroid(device, apk):
   """Launches the chromium build on a given device."""
+  if 'webview' in apk:
+    return
+
   print('Launching  chrome on android device...')
   device.StartActivity(
       intent.Intent(
@@ -1816,11 +1877,15 @@
                     help='Test the first and last revisions in the range ' +
                          'before proceeding with the bisect.')
   parser.add_option('--apk',
-                      choices=list(CHROME_APK_FILENAMES.keys()),
-                      dest='apk',
-                      default='chromium',
-                      help='Apk you want to bisect.')
-  parser.add_option('-d', '--device-id',
+                    choices=list(set().union(CHROME_APK_FILENAMES,
+                                             CHROME_MODERN_APK_FILENAMES,
+                                             MONOCHROME_APK_FILENAMES,
+                                             WEBVIEW_APK_FILENAMES)),
+                    dest='apk',
+                    default='chromium',
+                    help='Apk you want to bisect.')
+  parser.add_option('-d',
+                    '--device-id',
                     dest='device_id',
                     type='str',
                     help='Device to run the bisect on.')
diff --git a/tools/clang/scripts/package.py b/tools/clang/scripts/package.py
index 3b80e5f..72debb7 100755
--- a/tools/clang/scripts/package.py
+++ b/tools/clang/scripts/package.py
@@ -117,7 +117,7 @@
                 gcs_platform,
                 extra_gsutil_args=[]):
   gsutil_args = ['cp'] + extra_gsutil_args + [
-      '-n', '-a', 'public-read', filename,
+      '-n', filename,
       'gs://%s/%s/' % (gcs_bucket, gcs_platform)
   ]
   if do_upload:
diff --git a/tools/metrics/actions/actions.xml b/tools/metrics/actions/actions.xml
index b5aa832..01a976f 100644
--- a/tools/metrics/actions/actions.xml
+++ b/tools/metrics/actions/actions.xml
@@ -43324,6 +43324,7 @@
   <suffix name="AIBackgroundButton" label="AI background details page button"/>
   <suffix name="AIPremiumPlanButton"
       label="Google One AI Premium Plan details page button"/>
+  <suffix name="AutoUpdateButton" label="Auto Update details page button"/>
   <suffix name="BatteryButton" label="Battery details page button"/>
   <suffix name="BuiltInSecurityButton"
       label="Built in security details page button"/>
@@ -43332,15 +43333,21 @@
       label="Display (Entertainment) details page button"/>
   <suffix name="DisplayPerformanceButton"
       label="Display (Performance) details page button"/>
+  <suffix name="EasySetupButton" label="Easy setup details page button"/>
   <suffix name="EntertainmentAppsButton"
       label="Entertainment Apps details page button"/>
+  <suffix name="FastBootButton" label="Fast boot details page button"/>
   <suffix name="GameDashboardButton"
       label="Game Dashboard details page button"/>
   <suffix name="GeminiForAllButton" label="Gemini for All details page button"/>
   <suffix name="GeminiForWorkSpaceButton"
       label="Gemini for Work space details page button"/>
   <suffix name="GoogleAppsButton" label="Google Apps details page button"/>
+  <suffix name="GoogleToolsBuiltInButton"
+      label="Google tools built in details page button"/>
   <suffix name="HelpMeWriteButton" label="Help me write page button"/>
+  <suffix name="LauncherSearchButton"
+      label="Launcher search details page button"/>
   <suffix name="LumaFusionButton" label="LumaFusion details page button"/>
   <suffix name="MessagingButton" label="Messaging details page button"/>
   <suffix name="MobileGamingButton" label="Mobile Gaming details page button"/>
@@ -43352,9 +43359,12 @@
   <suffix name="PCConsoleGamingButton"
       label="PC/Console Gaming details page button"/>
   <suffix name="PhotosButton" label="Photos details page button"/>
+  <suffix name="PlayStoreButton" label="Play Store details page button"/>
   <suffix name="ProcessorButton" label="Processor details page button"/>
+  <suffix name="ProductivityButton" label="Productivity details page button"/>
   <suffix name="StorageButton" label="Storage details page button"/>
   <suffix name="SwitchingButton" label="Switching details page button"/>
+  <suffix name="TitanC2Button" label="Titan C2 details page button"/>
   <suffix name="VideoCallButton" label="Video Call details page button"/>
   <suffix name="WebcamButton" label="Webcam details page button"/>
   <affected-action name="DemoMode_Highlights_DetailsPage_Clicked"/>
diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml
index de43803..6e83507 100644
--- a/tools/metrics/histograms/enums.xml
+++ b/tools/metrics/histograms/enums.xml
@@ -11545,6 +11545,7 @@
   <int value="4964" label="ScriptingMediaFeature"/>
   <int value="4965" label="InvertedColorsMediaFeature"/>
   <int value="4966" label="CSSLightDark"/>
+  <int value="4967" label="PrivateAggregationApiFilteringIds"/>
 </enum>
 
 <enum name="FeaturePolicyFeature">
@@ -20610,6 +20611,7 @@
   <int value="-295753272" label="CaptivePortalPopupWindow:disabled"/>
   <int value="-295237704" label="EnableRemovingAllThirdPartyCookies:enabled"/>
   <int value="-293546827" label="HideIncognitoMediaMetadata:enabled"/>
+  <int value="-292546921" label="CrabbyAvif:disabled"/>
   <int value="-292450031"
       label="EnableWebUsbOnExtensionServiceWorker:disabled"/>
   <int value="-291936879" label="UsernameFirstFlowFilling:disabled"/>
@@ -23839,6 +23841,7 @@
   <int value="1139226452" label="enable-nacl-debug"/>
   <int value="1139363314" label="disable-supervised-user-blacklist"/>
   <int value="1139756271" label="WebOTPCrossDevice:enabled"/>
+  <int value="1140375528" label="CrabbyAvif:enabled"/>
   <int value="1140513364" label="IdentityStatusConsistency:disabled"/>
   <int value="1140541604" label="WinrtGeolocationImplementation:enabled"/>
   <int value="1140776731" label="WebViewForceDarkModeMatchTheme:disabled"/>
diff --git a/tools/metrics/histograms/metadata/android/histograms.xml b/tools/metrics/histograms/metadata/android/histograms.xml
index 8233a62..5ba4b005c 100644
--- a/tools/metrics/histograms/metadata/android/histograms.xml
+++ b/tools/metrics/histograms/metadata/android/histograms.xml
@@ -2354,6 +2354,44 @@
   </summary>
 </histogram>
 
+<histogram name="Android.MultiInstance.NumActivities.DesktopWindow"
+    units="activities" expires_after="2025-05-02">
+  <owner>aishwaryarj@google.com</owner>
+  <owner>wenyufu@chromium.org</owner>
+  <owner>clank-large-form-factors@google.com</owner>
+  <summary>
+    Records the number of running ChromeTabbedActivity's on every
+    ChromeTabbedActivity startup in a desktop window. This includes activities
+    that may not be visible on screen (i.e. it includes ChromeTabbedActivity's
+    in the &quot;stopped&quot; state). This does not include
+    ChromeTabbedActivity's that have been destroyed regardless of whether their
+    owning Android Task may be retrieved in Android Recents. Applicable to
+    Android V+ only.
+  </summary>
+</histogram>
+
+<histogram
+    name="Android.MultiInstance.NumActivities.DesktopWindow.{InstanceAllocationType}"
+    units="activities" expires_after="2025-05-02">
+  <owner>aishwaryarj@google.com</owner>
+  <owner>wenyufu@chromium.org</owner>
+  <owner>clank-large-form-factors@google.com</owner>
+  <summary>
+    Records the number of running ChromeTabbedActivity's when a new
+    ChromeTabbedActivity is started in a desktop window, as
+    {InstanceAllocationType}. This includes activities that may not be visible
+    on screen (i.e. it includes ChromeTabbedActivity's in the
+    &quot;stopped&quot; state). This does not include ChromeTabbedActivity's
+    that have been destroyed regardless of whether their owning Android Task may
+    be retrieved in Android Recents. Applicable to Android V+ only.
+  </summary>
+  <token key="InstanceAllocationType">
+    <variant name="ExistingInstance"
+        summary="a restoration of a persisted instance"/>
+    <variant name="NewInstance" summary="a new instance"/>
+  </token>
+</histogram>
+
 <histogram name="Android.MultiInstance.NumInstances" units="instances"
     expires_after="2024-09-22">
   <owner>jinsukkim@chromium.org</owner>
diff --git a/tools/metrics/histograms/metadata/blink/enums.xml b/tools/metrics/histograms/metadata/blink/enums.xml
index a79ab84c..ec2e9d2 100644
--- a/tools/metrics/histograms/metadata/blink/enums.xml
+++ b/tools/metrics/histograms/metadata/blink/enums.xml
@@ -525,16 +525,6 @@
   <int value="2" label="keyboardUnlock()"/>
 </enum>
 
-<enum name="NQEEffectiveConnectionType">
-  <int value="0" label="Unknown"/>
-  <int value="1" label="Offline"/>
-  <int value="2" label="Slow 2G"/>
-  <int value="3" label="2G"/>
-  <int value="4" label="3G"/>
-  <int value="5" label="4G"/>
-  <int value="6" label="(Obsolete) Broadband. Deprecated as of 01/2017."/>
-</enum>
-
 <enum name="RaceTaskPriority">
   <int value="0" label="LowerPriority"/>
   <int value="1" label="NormalPriority"/>
diff --git a/tools/metrics/histograms/metadata/blink/histograms.xml b/tools/metrics/histograms/metadata/blink/histograms.xml
index c5e8836..9f43e31e 100644
--- a/tools/metrics/histograms/metadata/blink/histograms.xml
+++ b/tools/metrics/histograms/metadata/blink/histograms.xml
@@ -52,16 +52,6 @@
   <variant name="xslt"/>
 </variants>
 
-<variants name="BlinkVisibleLoadTimeSuffixes">
-  <variant name="" summary="Aggregated across all connection types"/>
-  <variant name=".2G" summary="2G effective connection type"/>
-  <variant name=".3G" summary="3G effective connection type"/>
-  <variant name=".4G" summary="4G effective connection type"/>
-  <variant name=".Offline" summary="Offline effective connection type"/>
-  <variant name=".Slow2G" summary="Slow-2G effective connection type"/>
-  <variant name=".Unknown" summary="Unknown effective connection type"/>
-</variants>
-
 <variants name="PreloadTrigger">
   <variant name="" summary="All preloads"/>
   <variant name=".LinkPreload"
@@ -212,7 +202,7 @@
 </histogram>
 
 <histogram base="true" name="Blink.Animate.UpdateTime" units="microseconds"
-    expires_after="2024-06-01">
+    expires_after="2025-06-01">
 <!-- Name completed by histogram_suffixes name="BlinkUpdateTimeSuffixes" -->
 
   <owner>pdr@chromium.org</owner>
@@ -2260,7 +2250,7 @@
 </histogram>
 
 <histogram base="true" name="Blink.HandleInputEvents.UpdateTime"
-    units="microseconds" expires_after="2024-06-01">
+    units="microseconds" expires_after="2025-06-01">
 <!-- Name completed by histogram_suffixes name="BlinkUpdateTimeSuffixes" -->
 
   <owner>pdr@chromium.org</owner>
@@ -2280,7 +2270,7 @@
 </histogram>
 
 <histogram base="true" name="Blink.HitTestDocumentUpdate.UpdateTime"
-    units="microseconds" expires_after="2024-06-01">
+    units="microseconds" expires_after="2025-06-01">
 <!-- Name completed by histogram_suffixes name="BlinkUpdateTimeSuffixes" -->
 
   <owner>pdr@chromium.org</owner>
@@ -4598,70 +4588,6 @@
   </summary>
 </histogram>
 
-<histogram name="Blink.VisibleBeforeLoaded.LazyLoadImages.AboveTheFold3"
-    enum="NQEEffectiveConnectionType" expires_after="2024-02-27">
-  <owner>pdr@chromium.org</owner>
-  <owner>paint-dev@chromium.org</owner>
-  <summary>
-    Records the effective connection type whenever a lazily loaded image that
-    was initially above the fold becomes visible before it finishes loading.
-  </summary>
-</histogram>
-
-<histogram name="Blink.VisibleBeforeLoaded.LazyLoadImages.BelowTheFold3"
-    enum="NQEEffectiveConnectionType" expires_after="2024-09-01">
-  <owner>pdr@chromium.org</owner>
-  <owner>paint-dev@chromium.org</owner>
-  <summary>
-    Records the effective connection type whenever a lazily loaded image that
-    was initially below the fold becomes visible before it finishes loading.
-  </summary>
-</histogram>
-
-<histogram name="Blink.VisibleLoadTime.LazyLoadImages" units="ms"
-    expires_after="2024-09-01">
-  <owner>pdr@chromium.org</owner>
-  <owner>paint-dev@chromium.org</owner>
-  <summary>
-    Milliseconds spent waiting for a lazily loaded image to load. 0ms is
-    recorded if the image is already loaded by the time it is visible.
-
-    See also the AboveTheFold3 and BelowTheFold3 versions of this metric.
-  </summary>
-</histogram>
-
-<histogram
-    name="Blink.VisibleLoadTime.LazyLoadImages.AboveTheFold3{BlinkVisibleLoadTimeSuffixes}"
-    units="ms" expires_after="2024-09-01">
-  <owner>pdr@chromium.org</owner>
-  <owner>paint-dev@chromium.org</owner>
-  <summary>
-    Milliseconds spent waiting for an initially above-the-fold lazily loaded
-    image to load. 0ms is recorded if the image is already loaded by the time it
-    is visible. EffectiveConnectionType (2G, 3G, etc) is recorded as suffix to
-    this histogram. View the base histogram to see results aggregated across all
-    connection types. {BlinkVisibleLoadTimeSuffixes}
-  </summary>
-  <token key="BlinkVisibleLoadTimeSuffixes"
-      variants="BlinkVisibleLoadTimeSuffixes"/>
-</histogram>
-
-<histogram
-    name="Blink.VisibleLoadTime.LazyLoadImages.BelowTheFold3{BlinkVisibleLoadTimeSuffixes}"
-    units="ms" expires_after="2024-09-01">
-  <owner>pdr@chromium.org</owner>
-  <owner>paint-dev@chromium.org</owner>
-  <summary>
-    Milliseconds spent waiting for an initially below-the-fold lazily loaded
-    image to load. 0ms is recorded if the image is already loaded by the it is
-    visible. EffectiveConnectionType (2G, 3G, etc) is recorded as suffix to this
-    histogram. View the base histogram to see results aggregated across all
-    connection types. {BlinkVisibleLoadTimeSuffixes}
-  </summary>
-  <token key="BlinkVisibleLoadTimeSuffixes"
-      variants="BlinkVisibleLoadTimeSuffixes"/>
-</histogram>
-
 <histogram base="true" name="Blink.VisualUpdateDelay.UpdateTime"
     units="microseconds" expires_after="2024-10-06">
 <!-- Name completed by histogram_suffixes name="BlinkUpdateTimeSuffixes" -->
diff --git a/tools/metrics/histograms/metadata/compose/enums.xml b/tools/metrics/histograms/metadata/compose/enums.xml
index 46b7e76..8494606 100644
--- a/tools/metrics/histograms/metadata/compose/enums.xml
+++ b/tools/metrics/histograms/metadata/compose/enums.xml
@@ -59,6 +59,14 @@
              suggestion"/>
 </enum>
 
+<enum name="ComposeProactiveNudgeCtrEvent">
+  <int value="0" label="Nudge shown"/>
+  <int value="1" label="Dialog opened from nudge"/>
+  <int value="2" label="Globally disabled from nudge"/>
+  <int value="3" label="Site disabled from nudge"/>
+  <int value="4" label="Open Settings from nudge"/>
+</enum>
+
 <enum name="ComposeRequestFeedback">
   <int value="0" label="No feedback given"/>
   <int value="1" label="Postivie feedback given"/>
@@ -158,17 +166,22 @@
   <int value="5" label="Blocked for user language not supported"/>
   <int value="6"
       label="Blocked for form element appearing in cross-origin subframe"/>
-  <int value="7"
-      label="Blocked by Optimization Guide Result for main frame URL"/>
-  <int value="8"
-      label="Blocked for user was not allowed by optimization guide"/>
+  <int value="7" label="Compose disabled by optimization guide"/>
+  <int value="8" label="User not allowed by optimization guide"/>
   <int value="9" label="Feature not eligible, remotely disabled"/>
   <int value="10" label="Incorrect scheme, should be HTTP/HTTPS"/>
   <int value="11" label="Blocked for element appearing inside a fenced frame"/>
-  <int value="12"
-      label="Blocked for feature flag for Chrome Compose not enabled"/>
-  <int value="13"
-      label="Blocked for not being supported on this ChromeOS device"/>
+  <int value="12" label="Compose feature disabled"/>
+  <int value="13" label="Blocked for unsupported ChromeOS device"/>
+  <int value="14" label="Proactive nudge blocked for autocomplete=off"/>
+  <int value="15" label="Proactive nudge blocked for writingsuggestions=false"/>
+  <int value="16" label="Proactive nudge feature disabled"/>
+  <int value="17" label="Proactive nudge gloabally disabled by user"/>
+  <int value="18" label="Proactive nudge disabled for site by user"/>
+  <int value="19" label="Proactive nudge disabled by optimization guide"/>
+  <int value="20"
+      label="Proactive nudge disabled for unset optimization guide"/>
+  <int value="21" label="Proactive nudge disabled randomly"/>
 </enum>
 
 <enum name="OpenComposeDialogResult">
diff --git a/tools/metrics/histograms/metadata/compose/histograms.xml b/tools/metrics/histograms/metadata/compose/histograms.xml
index eeaea8c9..e821b6d 100644
--- a/tools/metrics/histograms/metadata/compose/histograms.xml
+++ b/tools/metrics/histograms/metadata/compose/histograms.xml
@@ -130,6 +130,28 @@
   </summary>
 </histogram>
 
+<histogram name="Compose.ProactiveNudge.CTR"
+    enum="ComposeProactiveNudgeCtrEvent" expires_after="2024-11-01">
+  <owner>perrier@chromium.org</owner>
+  <owner>chrome-compose-frontend@google.com</owner>
+  <summary>
+    Histogram for calculating the click-through rate (CTR) of the Compose
+    proactive nudge. Logged each time the nudge is displayed and when it's
+    clicked by the user. CTR is calculated through `clicked/displayed`.
+  </summary>
+</histogram>
+
+<histogram name="Compose.ProactiveNudge.ShowStatus" enum="ComposeShowStatus"
+    expires_after="2024-11-01">
+  <owner>perrier@chromium.org</owner>
+  <owner>chrome-compose-frontend@google.com</owner>
+  <summary>
+    Histogram that records the show status of the Compose proactive nudge.
+    Logged each time the nudge is displayed or blocked from displaying (with a
+    reason why).
+  </summary>
+</histogram>
+
 <histogram name="Compose.Session.Duration.OverOneDay" enum="BooleanYesNo"
     expires_after="2024-11-01">
   <owner>perrier@chromium.org</owner>
diff --git a/tools/metrics/histograms/metadata/content/histograms.xml b/tools/metrics/histograms/metadata/content/histograms.xml
index 415b714..4a0822e3 100644
--- a/tools/metrics/histograms/metadata/content/histograms.xml
+++ b/tools/metrics/histograms/metadata/content/histograms.xml
@@ -164,7 +164,7 @@
 </histogram>
 
 <histogram name="ContentNotifications.Promo.Prompt.Action"
-    enum="ContentNotificationPromptAction" expires_after="2024-06-20">
+    enum="ContentNotificationPromptAction" expires_after="2024-12-20">
   <owner>tinazwang@chromium.org</owner>
   <owner>chrome-sherlock@google.com</owner>
   <summary>
@@ -176,7 +176,7 @@
 <histogram
     name="ContentNotifications.Promo.ProvisionalNotifications.Entrypoint"
     enum="ContentNotificationPromoProvisionalEntrypoint"
-    expires_after="2024-06-20">
+    expires_after="2024-12-20">
   <owner>guiperez@google.com</owner>
   <owner>chrome-sherlock@google.com</owner>
   <summary>
@@ -195,14 +195,14 @@
 </histogram>
 
 <histogram name="ContentNotifications.Promo.SetUpList.Event"
-    enum="ContentNotificationSetUpListPromoEvent" expires_after="2024-06-20">
+    enum="ContentNotificationSetUpListPromoEvent" expires_after="2024-12-20">
   <owner>tinazwang@chromium.org</owner>
   <owner>chrome-sherlock@google.com</owner>
   <summary>Log the Set Up List Content Notification promo events.</summary>
 </histogram>
 
 <histogram name="ContentNotifications.Promo.Snackbar.Event"
-    enum="ContentNotificationSnackbarEvent" expires_after="2024-06-20">
+    enum="ContentNotificationSnackbarEvent" expires_after="2024-12-20">
   <owner>tinazwang@chromium.org</owner>
   <owner>chrome-sherlock@google.com</owner>
   <summary>
@@ -221,7 +221,7 @@
 </histogram>
 
 <histogram name="ContentNotifications.Promo.TopOfFeed.Event"
-    enum="ContentNotificationTopOfFeedPromoAction" expires_after="2024-06-20">
+    enum="ContentNotificationTopOfFeedPromoAction" expires_after="2024-12-20">
   <owner>guiperez@google.com</owner>
   <owner>chrome-sherlock@google.com</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/histogram_suffixes_list.xml b/tools/metrics/histograms/metadata/histogram_suffixes_list.xml
index 58bd5e4..60e95fd 100644
--- a/tools/metrics/histograms/metadata/histogram_suffixes_list.xml
+++ b/tools/metrics/histograms/metadata/histogram_suffixes_list.xml
@@ -1286,6 +1286,7 @@
   <suffix name="OfflinePage" label="OfflinePage."/>
   <suffix name="Retry" label="Retry."/>
   <suffix name="RetryFromBubble" label="RetryFromBubble."/>
+  <suffix name="ToolbarMenu" label="ToolbarMenu."/>
   <suffix name="UnknownSource" label="UnknownSource."/>
   <suffix name="WebContentsAPI" label="WebContentsAPI."/>
   <affected-histogram name="Download.Counts"/>
diff --git a/tools/metrics/histograms/metadata/net/histograms.xml b/tools/metrics/histograms/metadata/net/histograms.xml
index 06de7c83..2ec0d75 100644
--- a/tools/metrics/histograms/metadata/net/histograms.xml
+++ b/tools/metrics/histograms/metadata/net/histograms.xml
@@ -1790,17 +1790,6 @@
   </summary>
 </histogram>
 
-<histogram name="Net.HttpJob.CanIncludeCookies" enum="Boolean"
-    expires_after="2024-06-18">
-  <owner>cfredric@chromium.org</owner>
-  <owner>kaustubhag@chromium.org</owner>
-  <summary>
-    Counts the HTTP requests which involve reading from the cookie jar to
-    construct a Cookie header vs. the HTTP requests which are disallowed from
-    including a Cookie header.
-  </summary>
-</histogram>
-
 <histogram name="Net.HttpJob.IpProtection.AllowListMatch.BytesReceived2"
     units="bytes" expires_after="2024-09-15">
   <owner>ashleynewson@chromium.org</owner>
diff --git a/tools/metrics/histograms/metadata/new_tab_page/enums.xml b/tools/metrics/histograms/metadata/new_tab_page/enums.xml
index 14f3e41..77a62e69e 100644
--- a/tools/metrics/histograms/metadata/new_tab_page/enums.xml
+++ b/tools/metrics/histograms/metadata/new_tab_page/enums.xml
@@ -309,6 +309,10 @@
   <int value="22" label="Custom color clicked"/>
 </enum>
 
+<enum name="NTPCustomizeChromeSidePanelImpression">
+  <int value="0" label="Extensions card section displayed"/>
+</enum>
+
 <enum name="NTPCustomizedFeatures">
   <int value="0" label="Background customized"/>
   <int value="1" label="Shortcut customized"/>
diff --git a/tools/metrics/histograms/metadata/new_tab_page/histograms.xml b/tools/metrics/histograms/metadata/new_tab_page/histograms.xml
index ba3b7f7..a78ef42 100644
--- a/tools/metrics/histograms/metadata/new_tab_page/histograms.xml
+++ b/tools/metrics/histograms/metadata/new_tab_page/histograms.xml
@@ -482,6 +482,18 @@
   </summary>
 </histogram>
 
+<histogram name="NewTabPage.CustomizeChromeSidePanelImpression"
+    enum="NTPCustomizeChromeSidePanelImpression" expires_after="2025-05-03">
+  <owner>howardcha@google.com</owner>
+  <owner>tiborg@chromium.org</owner>
+  <owner>webstore-eng@google.com</owner>
+  <owner>chrome-desktop-ntp@google.com</owner>
+  <summary>
+    Captures the impressions for customizing Chrome sections within the
+    Customize Chrome Side Panel on the New Tab Page.
+  </summary>
+</histogram>
+
 <histogram name="NewTabPage.Customized" enum="NTPCustomizedFeatures"
     expires_after="2024-09-01">
   <owner>tiborg@chromium.org</owner>
diff --git a/tools/metrics/histograms/metadata/oobe/histograms.xml b/tools/metrics/histograms/metadata/oobe/histograms.xml
index f0f08c7f..8d178267 100644
--- a/tools/metrics/histograms/metadata/oobe/histograms.xml
+++ b/tools/metrics/histograms/metadata/oobe/histograms.xml
@@ -555,7 +555,7 @@
 </histogram>
 
 <histogram name="OOBE.ConsumerUpdateScreen.UpdateTime.{ConsumerUpdateType}"
-    units="ms" expires_after="2024-05-12">
+    units="ms" expires_after="2024-11-12">
   <owner>osamafathy@google.com</owner>
   <owner>cros-oobe@google.com</owner>
   <summary>
@@ -1124,7 +1124,7 @@
 </histogram>
 
 <histogram name="OOBE.SyncConsentScreen.ReviewFollowingSetup"
-    enum="BooleanChecked" expires_after="2024-05-05">
+    enum="BooleanChecked" expires_after="2024-11-05">
   <owner>dkuzmin@google.com</owner>
   <owner>cros-oac@google.com</owner>
   <summary>
@@ -1134,7 +1134,7 @@
 </histogram>
 
 <histogram name="OOBE.SyncConsentScreen.SyncEnabled" enum="BooleanEnabled"
-    expires_after="2024-05-05">
+    expires_after="2024-11-05">
   <owner>osamafathy@google.com</owner>
   <owner>cros-oac@google.com</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/others/histograms.xml b/tools/metrics/histograms/metadata/others/histograms.xml
index 8c88902..f19da70 100644
--- a/tools/metrics/histograms/metadata/others/histograms.xml
+++ b/tools/metrics/histograms/metadata/others/histograms.xml
@@ -48,6 +48,7 @@
       summary="Highlights app AI background detail page"/>
   <variant name="AIPremiumPlanPage"
       summary="Highlights app Google One AI Premium Plan detail page"/>
+  <variant name="AutoUpdatePage" summary="Auto Update detail page"/>
   <variant name="BatteryPage" summary="Highlights app Battery detail page"/>
   <variant name="BuiltInSecurityPage"
       summary="Highlights app Built In Security detail page"/>
@@ -57,8 +58,10 @@
       summary="Highlights app Display (Entertainment) detail page"/>
   <variant name="DisplayPerformancePage"
       summary="Highlights app Display (Performance) detail page"/>
+  <variant name="EasySetupPage" summary="Easy setup detail page"/>
   <variant name="EntertainmentAppsPage"
       summary="Highlights app Entertainment Apps detail page"/>
+  <variant name="FastBootPage" summary="Fast boot detail page"/>
   <variant name="GameDashboardPage"
       summary="Highlights app Game Dashboard detail page"/>
   <variant name="GeminiForAllPage"
@@ -67,8 +70,11 @@
       summary="Highlights app Gemini for Work space detail page"/>
   <variant name="GoogleAppsPage"
       summary="Highlights app Google Apps detail page"/>
+  <variant name="GoogleToolsBuiltInPage"
+      summary="Google tools built in detail page"/>
   <variant name="HelpMeWritePage"
       summary="Highlights app Help me write detail page"/>
+  <variant name="LauncherSearchPage" summary="Launcher search detail page"/>
   <variant name="LumaFusionPage"
       summary="Highlights app LumaFusion detail page"/>
   <variant name="MessagingPage" summary="Highlights app Messaging detail page"/>
@@ -84,9 +90,14 @@
   <variant name="PCConsoleGamingPage"
       summary="Highlights app PC/Console Gaming detail page"/>
   <variant name="PhotosPage" summary="Highlights app Photos detail page"/>
+  <variant name="PlayStorePage" summary="Play Store detail page"/>
   <variant name="ProcessorPage" summary="Highlights app Processor detail page"/>
+  <variant name="ProductivityPage" summary="Productivity detail page"/>
   <variant name="StoragePage" summary="Highlights app Storage detail page"/>
   <variant name="SwitchingPage" summary="Highlights app Switching detail page"/>
+  <variant name="SwitchToChromeBookPage"
+      summary="Switch to ChromeBook detail page"/>
+  <variant name="TitanC2Page" summary="Titan C2 details page"/>
   <variant name="VideoCallPage"
       summary="Highlights app Video Call detail page"/>
   <variant name="WebcamPage" summary="Highlights app Webcam detail page"/>
diff --git a/tools/metrics/histograms/metadata/performance_manager/histograms.xml b/tools/metrics/histograms/metadata/performance_manager/histograms.xml
index 19d26f19..9e5649ba 100644
--- a/tools/metrics/histograms/metadata/performance_manager/histograms.xml
+++ b/tools/metrics/histograms/metadata/performance_manager/histograms.xml
@@ -83,6 +83,22 @@
     same time as the Memory.Browser.PrivateMemoryFootprint. This slice is the
     memory used to store {ResourceContextType} that are {ResourceContextState}.
   </summary>
+  <token key="ResourceContextType">
+    <variant name="AllContexts" summary="all ResourceContexts"/>
+    <variant name="FrameContexts"/>
+    <variant name="OpaqueOriginContexts"
+        summary="OriginInPageContexts with opaque origins"/>
+    <variant name="OriginInPageContexts"/>
+    <variant name="PageContexts"/>
+    <variant name="ProcessContexts"/>
+    <variant name="WorkerContexts"/>
+  </token>
+  <token key="ResourceContextState">
+    <variant name="Dead"
+        summary="associated with a resource that no longer exists"/>
+    <variant name="Live" summary="associated with an existing resource"/>
+    <variant name="Total" summary="in any state"/>
+  </token>
 </histogram>
 
 <histogram name="PerformanceManager.Experimental.AccessibilityModeFlag"
diff --git a/tools/metrics/histograms/metadata/quota/histograms.xml b/tools/metrics/histograms/metadata/quota/histograms.xml
index e53e16a..cb4e43d4 100644
--- a/tools/metrics/histograms/metadata/quota/histograms.xml
+++ b/tools/metrics/histograms/metadata/quota/histograms.xml
@@ -63,7 +63,7 @@
 </histogram>
 
 <histogram name="Quota.DatabaseMigration{VersionUpgrades}"
-    enum="BooleanSuccess" expires_after="2024-05-24">
+    enum="BooleanSuccess" expires_after="2025-05-24">
   <owner>ayui@chromium.org</owner>
   <owner>chrome-owp-storage@google.com</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/storage/enums.xml b/tools/metrics/histograms/metadata/storage/enums.xml
index f750208..5ebb7f3 100644
--- a/tools/metrics/histograms/metadata/storage/enums.xml
+++ b/tools/metrics/histograms/metadata/storage/enums.xml
@@ -354,6 +354,14 @@
   <int value="35" label="SelectURLNonWebVisibleOther">
     Other errors in `selectURL()` not visible to the document.
   </int>
+  <int value="36" label="RunNonWebVisibleInvalidFilteringIdMaxBytes">
+    Error in `run()` not visible to the document: browser received message from
+    renderer with invalid filtering ID byte size.
+  </int>
+  <int value="37" label="SelectURLNonWebVisibleInvalidFilteringIdMaxBytes">
+    Error in `selectURL()` not visible to the document: browser received message
+    from renderer with invalid filtering ID byte size.
+  </int>
 </enum>
 
 <enum name="SqlRecoveryResult">
diff --git a/tools/metrics/histograms/metadata/webauthn/enums.xml b/tools/metrics/histograms/metadata/webauthn/enums.xml
index 66e39df..afc82ff1 100644
--- a/tools/metrics/histograms/metadata/webauthn/enums.xml
+++ b/tools/metrics/histograms/metadata/webauthn/enums.xml
@@ -180,6 +180,18 @@
   <int value="8" label="Canceled"/>
 </enum>
 
+<enum name="WebAuthenticationEnclaveEvent">
+  <int value="0" label="Onboarding sheet was shown to the user"/>
+  <int value="1"
+      label="User rejected the onboarding sheet by requesting to save the
+             passkey in a different authenticator"/>
+  <int value="2" label="User accepted the onboarding sheet"/>
+  <int value="3" label="A MagicArch window was shown"/>
+  <int value="4" label="MagicArch provided the security domain secret"/>
+  <int value="5" label="A create() request was sent to the enclave"/>
+  <int value="6" label="A get() request was sent to the enclave"/>
+</enum>
+
 <enum name="WebAuthenticationFidoTransport">
   <int value="0" label="USB HID"/>
   <int value="1" label="Near Field Communication"/>
diff --git a/tools/metrics/histograms/metadata/webauthn/histograms.xml b/tools/metrics/histograms/metadata/webauthn/histograms.xml
index 12faec2c..6a1db32 100644
--- a/tools/metrics/histograms/metadata/webauthn/histograms.xml
+++ b/tools/metrics/histograms/metadata/webauthn/histograms.xml
@@ -196,6 +196,16 @@
   </summary>
 </histogram>
 
+<histogram name="WebAuthentication.EnclaveEvent"
+    enum="WebAuthenticationEnclaveEvent" expires_after="2024-12-31">
+  <owner>agl@chromium.org</owner>
+  <owner>chrome-webauthn@google.com</owner>
+  <summary>
+    Records events related to the enclave (i.e. Google Password Manager passkeys
+    on the desktop).
+  </summary>
+</histogram>
+
 <histogram name="WebAuthentication.GetAssertion.Result"
     enum="WebAuthenticationGetAssertionResult" expires_after="2024-10-01">
   <owner>nsatragno@chromium.org</owner>
diff --git a/tools/metrics/structured/sync/structured.xml b/tools/metrics/structured/sync/structured.xml
index 9b587588..a5aa7877 100644
--- a/tools/metrics/structured/sync/structured.xml
+++ b/tools/metrics/structured/sync/structured.xml
@@ -2507,6 +2507,59 @@
     </metric>
   </event>
 
+  <event name="QuickStart.GetWifiCredentials">
+    <summary>
+      Recorded when the Wi-Fi credentials are received during Quick Start, or when
+      an error occurs attempting to receive them.
+    </summary>
+    <metric name="Success" type="int">
+      <summary>
+        Boolean indicating whether the Wi-Fi credentials were successfully received.
+      </summary>
+    </metric>
+  </event>
+
+  <event name="QuickStart.InstallForcedUpdate">
+    <summary>
+      Recorded when the Quick Start user begins to install a forced update.
+    </summary>
+
+  </event>
+
+  <event name="QuickStart.InstallConsumerUpdate">
+    <summary>
+      Recorded when the Quick Start user begins to install a consumer update.
+    </summary>
+
+  </event>
+
+  <event name="QuickStart.ConsumerUpdateCancelled">
+    <summary>
+      Recorded when the Quick Start user cancels after beginning to install a
+      consumer update.
+    </summary>
+
+  </event>
+
+  <event name="QuickStart.AccountTransferStarted">
+    <summary>
+      Recorded when the Quick Start account transfer process begins.
+    </summary>
+
+  </event>
+
+  <event name="QuickStart.AccountTransferComplete">
+    <summary>
+      Recorded when the Quick Start account transfer process finishes or an error
+      occurs during.
+    </summary>
+    <metric name="Success" type="int">
+      <summary>
+        Boolean indicating whether the account transfer was successful.
+      </summary>
+    </metric>
+  </event>
+
   <event name="UserLogin">
     <summary>
       An event to signify a user is using the system.
diff --git a/tools/utr/run.py b/tools/utr/run.py
index 97938a55..a65831b 100755
--- a/tools/utr/run.py
+++ b/tools/utr/run.py
@@ -73,7 +73,10 @@
       '--recipe-path',
       '-r',
       type=pathlib.Path,
-      help='Path to override the recipe bundle with a local checkout.')
+      help='Path to override the recipe bundle with a local bundle. To create '
+      'a bundle locally, run `./recipes.py bundle` in your desired recipe '
+      'checkout. This creates a dir called "bundle" that can be pointed to '
+      'with this arg.')
 
 
 def add_compile_args(parser):
@@ -161,7 +164,7 @@
   if not args.recipe_dir:
     recipes_path = cipd.fetch_recipe_bundle(args.verbosity).joinpath('recipes')
   else:
-    recipes_path = args.recipe_dir.joinpath('recipes', 'recipes.py')
+    recipes_path = args.recipe_dir.joinpath('recipes')
 
   builder_props, swarming_server = builders.find_builder_props(
       args.bucket, args.builder)
diff --git a/tools/v8_context_snapshot/v8_context_snapshot.gni b/tools/v8_context_snapshot/v8_context_snapshot.gni
index d3f2426a..561f74a5 100644
--- a/tools/v8_context_snapshot/v8_context_snapshot.gni
+++ b/tools/v8_context_snapshot/v8_context_snapshot.gni
@@ -15,7 +15,10 @@
 declare_args() {
   # If set, both snapshots are included. At this time, this only applicable to
   # android. In other words, it will not work correctly on other platforms.
-  include_both_v8_snapshots = false
+  # Building the context snapshots on android requires building blink multiple
+  # times. To avoid impacting developer productivity the context snapshot is
+  # only built for official builds.
+  include_both_v8_snapshots = is_official_build && target_os == "android"
 }
 
 declare_args() {
diff --git a/ui/accelerated_widget_mac/ca_layer_tree_coordinator.mm b/ui/accelerated_widget_mac/ca_layer_tree_coordinator.mm
index 4efcff1..b99461d5 100644
--- a/ui/accelerated_widget_mac/ca_layer_tree_coordinator.mm
+++ b/ui/accelerated_widget_mac/ca_layer_tree_coordinator.mm
@@ -95,8 +95,8 @@
 }
 
 void CALayerTreeCoordinator::ApplyBackpressure() {
-  // Nothing was just committed.
-  if (presented_frames_.empty() || presented_frames_.front()->has_committed) {
+  // No frame has been committed yet - this is the first frame being presented.
+  if (presented_frames_.empty() || !presented_frames_.front()->has_committed) {
     return;
   }
 
diff --git a/ui/accessibility/ax_computed_node_data.cc b/ui/accessibility/ax_computed_node_data.cc
index e0a6ab4..c6419fa 100644
--- a/ui/accessibility/ax_computed_node_data.cc
+++ b/ui/accessibility/ax_computed_node_data.cc
@@ -110,6 +110,13 @@
       // The value attribute could be computed on the browser for content
       // editables and ARIA text/search boxes.
       return owner_->data().IsNonAtomicTextField();
+
+    case ax::mojom::StringAttribute::kName:
+      return ::features::IsAccessibilityPruneRedundantInlineTextEnabled() &&
+             owner_->data().role == ax::mojom::Role::kInlineTextBox &&
+             owner_->GetParent()->data().HasStringAttribute(
+                 ax::mojom::StringAttribute::kName);
+
     default:
       return false;
   }
@@ -151,6 +158,16 @@
       // such controls on the browser. The same for all other controls, other
       // than non-atomic text fields.
       return base::EmptyString();
+
+    case ax::mojom::StringAttribute::kName:
+      // The name may be suppressed when serializing an AXInlineTextBox if it
+      // can be inferred from the parent.
+      if (::features::IsAccessibilityPruneRedundantInlineTextEnabled() &&
+          owner_->data().role == ax::mojom::Role::kInlineTextBox) {
+        return GetOrComputeTextContentUTF8();
+      }
+      return base::EmptyString();
+
     default:
       return base::EmptyString();
   }
@@ -463,7 +480,6 @@
   // set and kNone if the text content is to be inferred from the parent.
   if (::features::IsAccessibilityPruneRedundantInlineTextEnabled()) {
     if (owner_->data().role == ax::mojom::Role::kInlineTextBox &&
-        owner_->data().GetNameFrom() == ax::mojom::NameFrom::kNone &&
         !owner_->data().HasStringAttribute(ax::mojom::StringAttribute::kName)) {
       return owner_->GetParent()->data().GetStringAttribute(
           ax::mojom::StringAttribute::kName);
diff --git a/ui/android/java/src/org/chromium/ui/widget/LoadingView.java b/ui/android/java/src/org/chromium/ui/widget/LoadingView.java
index 471d16c..c1f3af07 100644
--- a/ui/android/java/src/org/chromium/ui/widget/LoadingView.java
+++ b/ui/android/java/src/org/chromium/ui/widget/LoadingView.java
@@ -99,14 +99,28 @@
         super(context, attrs);
     }
 
-    /** Show loading UI. It shows the loading animation 500ms after. */
+    /** Shows loading UI with a delay by calling showLoadingUI(false). */
     public void showLoadingUI() {
+        showLoadingUI(/* skipDelay= */ false);
+    }
+
+    /**
+     * Show the loading UI. If skipDelay is set to true, the delay before the loading animation will
+     * be skipped. If skipDelay is set to false, the loading animation will be shown after a delay
+     * based on LOADING_ANIMATION_DELAY_MS (500ms).
+     */
+    public void showLoadingUI(boolean skipDelay) {
         removeCallbacks(mDelayedShow);
         removeCallbacks(mDelayedHide);
         mShouldShow = true;
 
         setVisibility(GONE);
-        postDelayed(mDelayedShow, LOADING_ANIMATION_DELAY_MS);
+
+        if (skipDelay) {
+            mDelayedShow.run();
+        } else {
+            postDelayed(mDelayedShow, LOADING_ANIMATION_DELAY_MS);
+        }
     }
 
     /**
diff --git a/ui/android/junit/src/org/chromium/ui/widget/LoadingViewTest.java b/ui/android/junit/src/org/chromium/ui/widget/LoadingViewTest.java
index 42d3b71..290a84a1 100644
--- a/ui/android/junit/src/org/chromium/ui/widget/LoadingViewTest.java
+++ b/ui/android/junit/src/org/chromium/ui/widget/LoadingViewTest.java
@@ -164,4 +164,18 @@
                 1,
                 mTestObserver2.hideLoadingCallback.getCallCount());
     }
+
+    @Test
+    @SmallTest
+    public void testLoadingSkipDelay() {
+        mLoadingView.showLoadingUI(/* skipDelay= */ true);
+        Assert.assertEquals(
+                "showLoadingCallback1 should be executed as soon as showLoadingUI is called.",
+                1,
+                mTestObserver1.showLoadingCallback.getCallCount());
+        Assert.assertEquals(
+                "showLoadingCallback2 should be executed as soon as showLoadingUI is called.",
+                1,
+                mTestObserver2.showLoadingCallback.getCallCount());
+    }
 }
diff --git a/ui/display/types/display_color_management.cc b/ui/display/types/display_color_management.cc
index 4289300..ed924f0 100644
--- a/ui/display/types/display_color_management.cc
+++ b/ui/display/types/display_color_management.cc
@@ -143,6 +143,12 @@
   out_b = static_cast<uint16_t>(std::round(65535.f * b));
 }
 
+void GammaCurve::Evaluate(float rgb[3]) const {
+  for (size_t c = 0; c < 3; ++c) {
+    rgb[c] = Evaluate(rgb[c], c);
+  }
+}
+
 std::string GammaCurve::ToString() const {
   std::string str = "[";
   for (size_t i = 0; i < lut_.size(); ++i) {
diff --git a/ui/display/types/display_color_management.h b/ui/display/types/display_color_management.h
index d67ddd91..4142fed 100644
--- a/ui/display/types/display_color_management.h
+++ b/ui/display/types/display_color_management.h
@@ -43,6 +43,10 @@
   // function will clamp `x` to [0, 1].
   void Evaluate(float x, uint16_t& r, uint16_t& g, uint16_t& b) const;
 
+  // Evaluate at the specified RGB values. Input values will be clamped to the
+  // [0, 1] interval.
+  void Evaluate(float rgb[3]) const;
+
   // Display as a string for debugging.
   std::string ToString() const;
 
diff --git a/ui/ozone/platform/drm/gpu/drm_gpu_util.cc b/ui/ozone/platform/drm/gpu/drm_gpu_util.cc
index 4aa531b..ff22879 100644
--- a/ui/ozone/platform/drm/gpu/drm_gpu_util.cc
+++ b/ui/ozone/platform/drm/gpu/drm_gpu_util.cc
@@ -95,6 +95,12 @@
 
   return permutations;
 }
+
+// Constants for parsing CTM values.
+constexpr uint64_t kCtmSignMask = (1ull << 63);
+constexpr uint64_t kCtmValueMask = ~(1ull << 63);
+constexpr float kCtmValueScale = static_cast<float>(1ull << 32);
+
 }  // namespace
 
 ControllerConfigParams::ControllerConfigParams(
@@ -201,6 +207,25 @@
   return lut;
 }
 
+bool ParseLutBlob(const void* data, size_t size, display::GammaCurve& result) {
+  // LUT blobs are an array of drm_color_lut entries, and so the size of the
+  // blob must be a multiple of the size of drm_color_lut.
+  if (size % sizeof(drm_color_lut) != 0) {
+    LOG(ERROR) << "Invalid size for LUT blob.";
+    return false;
+  }
+  size_t entry_count = size / sizeof(drm_color_lut);
+  const drm_color_lut* entries = reinterpret_cast<const drm_color_lut*>(data);
+  std::vector<display::GammaRampRGBEntry> lut(entry_count);
+  for (size_t i = 0; i < entry_count; ++i) {
+    lut[i].r = entries[i].red;
+    lut[i].g = entries[i].green;
+    lut[i].b = entries[i].blue;
+  }
+  result = display::GammaCurve(lut);
+  return true;
+}
+
 ScopedDrmColorCtmPtr CreateCTMBlob(const skcms_Matrix3x3& color_matrix,
                                    bool negative_values_broken) {
   ScopedDrmColorCtmPtr ctm(
@@ -211,16 +236,34 @@
       if (negative_values_broken) {
         ctm->matrix[i] = 0;
       } else {
-        ctm->matrix[i] = static_cast<uint64_t>(-value * (1ull << 32));
-        ctm->matrix[i] |= static_cast<uint64_t>(1) << 63;
+        ctm->matrix[i] =
+            static_cast<uint64_t>(-value * kCtmValueScale) & kCtmValueMask;
+        ctm->matrix[i] |= kCtmSignMask;
       }
     } else {
-      ctm->matrix[i] = static_cast<uint64_t>(value * (1ull << 32));
+      ctm->matrix[i] =
+          static_cast<uint64_t>(value * kCtmValueScale) & kCtmValueMask;
     }
   }
   return ctm;
 }
 
+bool ParseCTMBlob(const void* data, size_t size, skcms_Matrix3x3& result) {
+  // CTM blobs must contain exactly 9 (3x3) numbers which are encoded in
+  // uint64_ts.
+  if (size != 9 * sizeof(uint64_t)) {
+    LOG(ERROR) << "Invalid size for CTM blob.";
+    return false;
+  }
+  const uint64_t* data_u64 = reinterpret_cast<const uint64_t*>(data);
+  for (size_t i = 0; i < 9; ++i) {
+    float sign = (data_u64[i] & kCtmSignMask) ? -1.f : 1.f;
+    float value = (data_u64[i] & kCtmValueMask) / kCtmValueScale;
+    result.vals[i / 3][i % 3] = sign * value;
+  }
+  return true;
+}
+
 ScopedDrmModeRectPtr CreateDCBlob(const gfx::Rect& rect) {
   // Damage rect can be empty, but sending empty or negative rects can result in
   // artifacting and black screens. Filter them out here.
@@ -289,4 +332,59 @@
 
   return permutations;
 }
+
+void ApplyCrtcColorSpaceConversion(DrmWrapper* drm,
+                                   uint32_t crtc_id,
+                                   float rgb[3]) {
+  // Look up all properties on this CRTC and create a helper lambda to look up
+  // their blobs.
+  ScopedDrmObjectPropertyPtr props(
+      drm->GetObjectProperties(crtc_id, DRM_MODE_OBJECT_CRTC));
+  if (!props) {
+    return;
+  }
+  auto get_blob_by_name = [&](const char* name) {
+    DrmDevice::Property property;
+    if (!GetDrmPropertyForName(drm, props.get(), name, &property)) {
+      return ScopedDrmPropertyBlobPtr(nullptr);
+    }
+    return drm->GetPropertyBlob(property.value);
+  };
+
+  // Apply DEGAMMA.
+  ScopedDrmPropertyBlobPtr degamma_blob = get_blob_by_name("DEGAMMA_LUT");
+  if (degamma_blob) {
+    display::GammaCurve curve;
+    if (ParseLutBlob(degamma_blob->data, degamma_blob->length, curve)) {
+      curve.Evaluate(rgb);
+    }
+  }
+
+  // Apply CTM.
+  ScopedDrmPropertyBlobPtr ctm_blob = get_blob_by_name("CTM");
+  if (ctm_blob) {
+    skcms_Matrix3x3 ctm;
+    if (ParseCTMBlob(ctm_blob->data, ctm_blob->length, ctm)) {
+      float temp[3] = {0, 0, 0};
+      for (int i = 0; i < 3; ++i) {
+        for (int j = 0; j < 3; ++j) {
+          temp[i] += ctm.vals[i][j] * rgb[j];
+        }
+      }
+      for (int i = 0; i < 3; ++i) {
+        rgb[i] = temp[i];
+      }
+    }
+  }
+
+  // Apply GAMMA.
+  ScopedDrmPropertyBlobPtr gamma_blob = get_blob_by_name("GAMMA_LUT");
+  if (gamma_blob) {
+    display::GammaCurve curve;
+    if (ParseLutBlob(gamma_blob->data, gamma_blob->length, curve)) {
+      curve.Evaluate(rgb);
+    }
+  }
+}
+
 }  // namespace ui
diff --git a/ui/ozone/platform/drm/gpu/drm_gpu_util.h b/ui/ozone/platform/drm/gpu/drm_gpu_util.h
index 6923372..d520014 100644
--- a/ui/ozone/platform/drm/gpu/drm_gpu_util.h
+++ b/ui/ozone/platform/drm/gpu/drm_gpu_util.h
@@ -67,12 +67,18 @@
 ScopedDrmColorLutPtr CreateLutBlob(const display::GammaCurve& source,
                                    size_t size);
 
+// Parse a Lut blob to retrieve a display::GammaCurve.
+bool ParseLutBlob(const void* data, size_t size, display::GammaCurve& result);
+
 // Converts |color_matrix| to a drm_color_ctm in U31.32 format where the most
 // significant bit is the sign. If `negative_values_broken` is true, then
 // clamp all negative values to 0.
 ScopedDrmColorCtmPtr CreateCTMBlob(const skcms_Matrix3x3& color_matrix,
                                    bool negative_values_broken);
 
+// Parse a CTM blob to retrieve a skcms_Matrix3x3.
+bool ParseCTMBlob(const void* data, size_t size, skcms_Matrix3x3& result);
+
 // Creates a FB Damage Clip Blob
 ScopedDrmModeRectPtr CreateDCBlob(const gfx::Rect& rect);
 
@@ -93,6 +99,12 @@
     const DrmDevice& drm,
     const std::vector<ControllerConfigParams>& controllers_params);
 
+// Apply the color space conversion of `crtc_id` in-place on the specified RGB
+// triple.
+void ApplyCrtcColorSpaceConversion(DrmWrapper* drm,
+                                   uint32_t crtc_id,
+                                   float rgb[3]);
+
 }  // namespace ui
 
 #endif  // UI_OZONE_PLATFORM_DRM_GPU_DRM_GPU_UTIL_H_
diff --git a/ui/ozone/platform/drm/gpu/fake_drm_device.cc b/ui/ozone/platform/drm/gpu/fake_drm_device.cc
index d5acb9c..da49563 100644
--- a/ui/ozone/platform/drm/gpu/fake_drm_device.cc
+++ b/ui/ozone/platform/drm/gpu/fake_drm_device.cc
@@ -754,6 +754,8 @@
                                       uint32_t object_type,
                                       uint32_t property_id,
                                       uint32_t property_value) {
+  UpdateProperty(object_id, property_id, property_value,
+                 /*add_property_if_needed=*/false);
   set_object_property_count_++;
   return true;
 }
diff --git a/ui/ozone/platform/drm/gpu/hardware_display_plane_manager.cc b/ui/ozone/platform/drm/gpu/hardware_display_plane_manager.cc
index 0ed91c5e..c5c8e1b 100644
--- a/ui/ozone/platform/drm/gpu/hardware_display_plane_manager.cc
+++ b/ui/ozone/platform/drm/gpu/hardware_display_plane_manager.cc
@@ -42,9 +42,6 @@
                    crop_rect.width() << 16, crop_rect.height() << 16);
 }
 
-// TODO(b/335542790): The values that are written to the CTM blob are not
-// tested, and so the values written by this function are also not tested.
-// Add tests for these.
 skcms_Matrix3x3 PlaneToOutputMatrix(
     const HardwareDisplayPlaneManager::CrtcState& crtc_state) {
   skcms_Matrix3x3 plane_to_xyzd50;
diff --git a/ui/ozone/platform/drm/gpu/hardware_display_plane_manager_unittest.cc b/ui/ozone/platform/drm/gpu/hardware_display_plane_manager_unittest.cc
index 6a609e0d..8dbc13b 100644
--- a/ui/ozone/platform/drm/gpu/hardware_display_plane_manager_unittest.cc
+++ b/ui/ozone/platform/drm/gpu/hardware_display_plane_manager_unittest.cc
@@ -1084,7 +1084,9 @@
   }
 }
 
-TEST_P(HardwareDisplayPlaneManagerTest, ColorManagement_CtmColorManagement) {
+// The effect of color temperature adjustment (night light) on the CTM.
+TEST_P(HardwareDisplayPlaneManagerTest,
+       CtmColorManagement_ColorTemperatureAdjustment) {
   base::test::ScopedFeatureList scoped_feature_list;
   scoped_feature_list.InitWithFeatures({display::features::kCtmColorManagement},
                                        {});
@@ -1092,38 +1094,261 @@
   fake_drm_->ResetStateWithDefaultObjects(
       /*crtc_count=*/1, /*planes_per_crtc=*/1);
 
+  uint32_t crtc_id = fake_drm_->crtc_property(0).id;
+
   // This test has full CTM, DEGAMMA, and GAMMA.
-  fake_drm_->AddProperty(fake_drm_->crtc_property(0).id,
-                         {.id = kCtmPropId, .value = 0});
-  fake_drm_->AddProperty(fake_drm_->crtc_property(0).id,
-                         {.id = kDegammaLutSizePropId, .value = 1});
-  fake_drm_->AddProperty(fake_drm_->crtc_property(0).id,
-                         {.id = kDegammaLutPropId, .value = 0});
-  fake_drm_->AddProperty(fake_drm_->crtc_property(0).id,
-                         {.id = kGammaLutSizePropId, .value = 1});
-  fake_drm_->AddProperty(fake_drm_->crtc_property(0).id,
-                         {.id = kGammaLutPropId, .value = 0});
+  fake_drm_->AddProperty(crtc_id, {.id = kCtmPropId, .value = 0});
+  fake_drm_->AddProperty(crtc_id, {.id = kDegammaLutSizePropId, .value = 33});
+  fake_drm_->AddProperty(crtc_id, {.id = kDegammaLutPropId, .value = 0});
+  fake_drm_->AddProperty(crtc_id, {.id = kGammaLutSizePropId, .value = 33});
+  fake_drm_->AddProperty(crtc_id, {.id = kGammaLutPropId, .value = 0});
 
   // Color profile change will set all properties.
   fake_drm_->InitializeState(use_atomic_);
 
-  fake_drm_->plane_manager()->SetOutputColorSpace(
-      fake_drm_->crtc_property(0).id, SkNamedPrimariesExt::kP3);
-  fake_drm_->plane_manager()->SetColorSpaceForAllPlanes(
-      fake_drm_->crtc_property(0).id, SkNamedPrimariesExt::kRec2020);
+  // Apply color temperature adjustment. The CTM should be updated
+  // immediately.
+  display::ColorTemperatureAdjustment cta;
+  cta.srgb_matrix.vals[0][0] = 0.1f;
+  cta.srgb_matrix.vals[1][1] = 0.2f;
+  cta.srgb_matrix.vals[2][2] = 0.3f;
+  fake_drm_->plane_manager()->SetColorTemperatureAdjustment(
+      fake_drm_->crtc_property(0).id, cta);
 
-  if (use_atomic_) {
-    // We should not have committed the CTM yet.
-    EXPECT_EQ(0, fake_drm_->get_commit_count());
-    EXPECT_EQ(0u, GetCrtcPropertyValue(fake_drm_->crtc_property(0).id, "CTM"));
+  {
+    constexpr float kEpsilon = 0.001f;
+    float rgb[3] = {0.4f, 0.5f, 0.6f};
+    ApplyCrtcColorSpaceConversion(fake_drm_.get(), crtc_id, rgb);
+    EXPECT_NEAR(rgb[0], 0.1f * 0.4f, kEpsilon);
+    EXPECT_NEAR(rgb[1], 0.2f * 0.5f, kEpsilon);
+    EXPECT_NEAR(rgb[2], 0.3f * 0.6f, kEpsilon);
+  }
+}
 
-    // The CTM commit should happen at flip time.
+// The effect of gamma adjustment on the CTM.
+TEST_P(HardwareDisplayPlaneManagerTest, CtmColorManagement_GammaAdjustment) {
+  base::test::ScopedFeatureList scoped_feature_list;
+  scoped_feature_list.InitWithFeatures({display::features::kCtmColorManagement},
+                                       {});
+
+  fake_drm_->ResetStateWithDefaultObjects(
+      /*crtc_count=*/1, /*planes_per_crtc=*/1);
+
+  uint32_t crtc_id = fake_drm_->crtc_property(0).id;
+
+  // This test has full CTM, DEGAMMA, and GAMMA.
+  fake_drm_->AddProperty(crtc_id, {.id = kCtmPropId, .value = 0});
+  fake_drm_->AddProperty(crtc_id, {.id = kDegammaLutSizePropId, .value = 33});
+  fake_drm_->AddProperty(crtc_id, {.id = kDegammaLutPropId, .value = 0});
+  fake_drm_->AddProperty(crtc_id, {.id = kGammaLutSizePropId, .value = 33});
+  fake_drm_->AddProperty(crtc_id, {.id = kGammaLutPropId, .value = 0});
+
+  // Color profile change will set all properties.
+  fake_drm_->InitializeState(use_atomic_);
+
+  // Apply gamma adjustment.
+  display::GammaAdjustment gamma_adjustment;
+  gamma_adjustment.curve = display::GammaCurve::MakeScale(0.9, 0.8, 0.7);
+  fake_drm_->plane_manager()->SetGammaAdjustment(crtc_id, gamma_adjustment);
+
+  {
+    constexpr float kEpsilon = 0.001f;
+    float rgb[3] = {0.6f, 0.5f, 0.4f};
+    ApplyCrtcColorSpaceConversion(fake_drm_.get(), crtc_id, rgb);
+    EXPECT_NEAR(rgb[0], 0.9f * 0.6f, kEpsilon);
+    EXPECT_NEAR(rgb[1], 0.8f * 0.5f, kEpsilon);
+    EXPECT_NEAR(rgb[2], 0.7f * 0.4f, kEpsilon);
+  }
+}
+
+// The effect of color conversion (from input plane space to output space) on
+// the CTM.
+TEST_P(HardwareDisplayPlaneManagerAtomicTest,
+       CtmColorManagement_ColorConversion) {
+  base::test::ScopedFeatureList scoped_feature_list;
+  scoped_feature_list.InitWithFeatures({display::features::kCtmColorManagement},
+                                       {});
+
+  fake_drm_->ResetStateWithDefaultObjects(
+      /*crtc_count=*/1, /*planes_per_crtc=*/1);
+
+  uint32_t crtc_id = fake_drm_->crtc_property(0).id;
+
+  // This test has full CTM, DEGAMMA, and GAMMA.
+  fake_drm_->AddProperty(crtc_id, {.id = kCtmPropId, .value = 0});
+  fake_drm_->AddProperty(crtc_id, {.id = kDegammaLutSizePropId, .value = 33});
+  fake_drm_->AddProperty(crtc_id, {.id = kDegammaLutPropId, .value = 0});
+  fake_drm_->AddProperty(crtc_id, {.id = kGammaLutSizePropId, .value = 33});
+  fake_drm_->AddProperty(crtc_id, {.id = kGammaLutPropId, .value = 0});
+
+  // Color profile change will set all properties.
+  fake_drm_->InitializeState(use_atomic_);
+
+  fake_drm_->plane_manager()->SetOutputColorSpace(crtc_id,
+                                                  SkNamedPrimariesExt::kP3);
+
+  // We should not have committed the CTM yet.
+  EXPECT_EQ(0, fake_drm_->get_commit_count());
+  EXPECT_EQ(0u, GetCrtcPropertyValue(crtc_id, "CTM"));
+
+  // Commit a plane that is sRGB. Colors should be converted.
+  {
     HardwareDisplayPlaneList state;
-    PerformPageFlip(/*crtc_idx=*/0, &state);
+    auto buffer = CreateBuffer(kDefaultBufferSize);
+    DrmOverlayPlaneList planes;
+    planes.push_back(DrmOverlayPlane::TestPlane(buffer));
+    planes[0].color_space = gfx::ColorSpace::CreateSRGB();
+
+    PerformPageFlip(/*crtc_idx=*/0, &state, planes);
+  }
+
+  // This is the conversion of color(--display-p3-linear 0.25 0.5 0.75) to
+  // srgb-linear using https://colorjs.io/apps/convert/.
+  {
+    constexpr float kEpsilon = 0.001f;
+    float rgb[3] = {0.1937649f, 0.51051424f, 0.77947779f};
+    ApplyCrtcColorSpaceConversion(fake_drm_.get(), crtc_id, rgb);
+    EXPECT_NEAR(rgb[0], 0.25f, kEpsilon);
+    EXPECT_NEAR(rgb[1], 0.5f, kEpsilon);
+    EXPECT_NEAR(rgb[2], 0.75f, kEpsilon);
+  }
+}
+
+// The combined effects of color conversion, color temperature adjustment, and
+// gamma adjustment, on the CTM.
+TEST_P(HardwareDisplayPlaneManagerAtomicTest, CtmColorManagement_Combined) {
+  constexpr float kEpsilon = 0.001f;
+
+  base::test::ScopedFeatureList scoped_feature_list;
+  scoped_feature_list.InitWithFeatures({display::features::kCtmColorManagement},
+                                       {});
+
+  fake_drm_->ResetStateWithDefaultObjects(
+      /*crtc_count=*/1, /*planes_per_crtc=*/1);
+
+  uint32_t crtc_id = fake_drm_->crtc_property(0).id;
+
+  // This test has full CTM, DEGAMMA, and GAMMA.
+  fake_drm_->AddProperty(crtc_id, {.id = kCtmPropId, .value = 0});
+  fake_drm_->AddProperty(crtc_id, {.id = kDegammaLutSizePropId, .value = 33});
+  fake_drm_->AddProperty(crtc_id, {.id = kDegammaLutPropId, .value = 0});
+  fake_drm_->AddProperty(crtc_id, {.id = kGammaLutSizePropId, .value = 33});
+  fake_drm_->AddProperty(crtc_id, {.id = kGammaLutPropId, .value = 0});
+
+  // Color profile change will set all properties.
+  fake_drm_->InitializeState(use_atomic_);
+
+  fake_drm_->plane_manager()->SetOutputColorSpace(crtc_id,
+                                                  SkNamedPrimariesExt::kP3);
+
+  // We should not have committed the CTM yet.
+  EXPECT_EQ(0, fake_drm_->get_commit_count());
+  EXPECT_EQ(0u, GetCrtcPropertyValue(crtc_id, "CTM"));
+  HardwareDisplayPlaneList state;
+
+  auto buffer = CreateBuffer(kDefaultBufferSize);
+
+  // Constants for a test color value in P3 and sRGB. This is the conversion of
+  // color(--display-p3-linear 0.25 0.5 0.75) to srgb-linear using
+  // https://colorjs.io/apps/convert/.
+  const float kColorP3[3] = {0.25, 0.5, 0.75};
+  const float kColorSRGB[3] = {0.1937649f, 0.51051424f, 0.77947779f};
+
+  // Commit a plane that is P3. Color conversion should be a no-op.
+  {
+    DrmOverlayPlaneList planes;
+    planes.push_back(DrmOverlayPlane::TestPlane(buffer));
+    planes[0].color_space = gfx::ColorSpace::CreateDisplayP3D65();
+
+    PerformPageFlip(/*crtc_idx=*/0, &state, planes);
     EXPECT_EQ(1, fake_drm_->get_commit_count());
-    EXPECT_NE(0u, GetCrtcPropertyValue(fake_drm_->crtc_property(0).id, "CTM"));
-  } else {
-    EXPECT_EQ(0, fake_drm_->get_set_object_property_count());
+    EXPECT_NE(0u, GetCrtcPropertyValue(crtc_id, "CTM"));
+
+    float rgb[3] = {kColorP3[0], kColorP3[1], kColorP3[2]};
+    ApplyCrtcColorSpaceConversion(fake_drm_.get(), crtc_id, rgb);
+    EXPECT_NEAR(rgb[0], kColorP3[0], kEpsilon);
+    EXPECT_NEAR(rgb[1], kColorP3[1], kEpsilon);
+    EXPECT_NEAR(rgb[2], kColorP3[2], kEpsilon);
+  }
+
+  // Commit a plane that is sRGB. Colors should be converted.
+  {
+    DrmOverlayPlaneList planes;
+    planes.push_back(DrmOverlayPlane::TestPlane(buffer));
+    planes[0].color_space = gfx::ColorSpace::CreateSRGB();
+
+    PerformPageFlip(/*crtc_idx=*/0, &state, planes);
+    EXPECT_NE(0u, GetCrtcPropertyValue(crtc_id, "CTM"));
+
+    // This is the conversion of color(--display-p3-linear 0.25 0.5 0.75) to
+    // srgb-linear using https://colorjs.io/apps/convert/.
+    float rgb[3] = {kColorSRGB[0], kColorSRGB[1], kColorSRGB[2]};
+    ApplyCrtcColorSpaceConversion(fake_drm_.get(), crtc_id, rgb);
+    EXPECT_NEAR(rgb[0], kColorP3[0], kEpsilon);
+    EXPECT_NEAR(rgb[1], kColorP3[1], kEpsilon);
+    EXPECT_NEAR(rgb[2], kColorP3[2], kEpsilon);
+  }
+
+  // Apply color temperature adjustment. The CTM should be updated
+  // immediately.
+  {
+    display::ColorTemperatureAdjustment cta;
+    cta.srgb_matrix.vals[0][0] = 0.5;
+    cta.srgb_matrix.vals[1][1] = 1.0;
+    cta.srgb_matrix.vals[2][2] = 1.0;
+    fake_drm_->plane_manager()->SetColorTemperatureAdjustment(
+        fake_drm_->crtc_property(0).id, cta);
+
+    float rgb[3] = {2.f * kColorSRGB[0], kColorSRGB[1], kColorSRGB[2]};
+    ApplyCrtcColorSpaceConversion(fake_drm_.get(), crtc_id, rgb);
+    EXPECT_NEAR(rgb[0], kColorP3[0], kEpsilon);
+    EXPECT_NEAR(rgb[1], kColorP3[1], kEpsilon);
+    EXPECT_NEAR(rgb[2], kColorP3[2], kEpsilon);
+  }
+
+  // Change the output color space. Nothing should change yet.
+  {
+    fake_drm_->plane_manager()->SetOutputColorSpace(crtc_id,
+                                                    SkNamedPrimariesExt::kSRGB);
+
+    float rgb[3] = {2.f * kColorSRGB[0], kColorSRGB[1], kColorSRGB[2]};
+    ApplyCrtcColorSpaceConversion(fake_drm_.get(), crtc_id, rgb);
+    EXPECT_NEAR(rgb[0], kColorP3[0], kEpsilon);
+    EXPECT_NEAR(rgb[1], kColorP3[1], kEpsilon);
+    EXPECT_NEAR(rgb[2], kColorP3[2], kEpsilon);
+  }
+
+  // Commit the plane with the same color space as the last flip. The CTM
+  // should change now.
+  {
+    DrmOverlayPlaneList planes;
+    planes.push_back(
+        DrmOverlayPlane::TestPlane(CreateBuffer(kDefaultBufferSize)));
+    planes[0].color_space = gfx::ColorSpace::CreateSRGB();
+    planes[0].z_order = 1;
+
+    PerformPageFlip(/*crtc_idx=*/0, &state, planes);
+    EXPECT_NE(0u, GetCrtcPropertyValue(crtc_id, "CTM"));
+
+    float rgb[3] = {1.0f, 0.75f, 0.25f};
+    ApplyCrtcColorSpaceConversion(fake_drm_.get(), crtc_id, rgb);
+    EXPECT_NEAR(rgb[0], 0.5f, kEpsilon);
+    EXPECT_NEAR(rgb[1], 0.75f, kEpsilon);
+    EXPECT_NEAR(rgb[2], 0.25f, kEpsilon);
+  }
+
+  // Apply gamma adjustment.
+  {
+    display::GammaAdjustment gamma_adjustment;
+    gamma_adjustment.curve =
+        display::GammaCurve::MakeScale(0.5, 0.25 / 0.75, 0.1 / 0.25);
+    fake_drm_->plane_manager()->SetGammaAdjustment(crtc_id, gamma_adjustment);
+
+    float rgb[3] = {1.0f, 0.75f, 0.25f};
+    ApplyCrtcColorSpaceConversion(fake_drm_.get(), crtc_id, rgb);
+    EXPECT_NEAR(rgb[0], 0.25f, kEpsilon);
+    EXPECT_NEAR(rgb[1], 0.25f, kEpsilon);
+    EXPECT_NEAR(rgb[2], 0.1f, kEpsilon);
   }
 }
 
diff --git a/v8 b/v8
index c61acc7..5385579 160000
--- a/v8
+++ b/v8
@@ -1 +1 @@
-Subproject commit c61acc78d87d6796b8555c28ee5026281f543073
+Subproject commit 5385579a7cf01f8fe63ce2b417fdee0a18be3351