diff --git a/DEPS b/DEPS
index 35ba0b24..27ad84b 100644
--- a/DEPS
+++ b/DEPS
@@ -248,7 +248,7 @@
   # luci-go CIPD package version.
   # Make sure the revision is uploaded by infra-packagers builder.
   # https://ci.chromium.org/p/infra-internal/g/infra-packagers/console
-  'luci_go': 'git_revision:eef34c694bdab861ded59a7e34ddf4d97e013c90',
+  'luci_go': 'git_revision:cd0142e4e2f376bb0bd1b34a3b91167aff5226bf',
 
   # This can be overridden, e.g. with custom_vars, to build clang from HEAD
   # instead of downloading the prebuilt pinned revision.
@@ -300,19 +300,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': '063254c1cbbe2562ca28802b862f9d4162ff25f4',
+  'src_internal_revision': 'a4e46326699b3f0b96d8071f454f5b762710a6eb',
   # 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': '5342f227267ef899adb5da0473fe3b513f3c6790',
+  'skia_revision': 'c106d7831592fbc7245b6e032164506db3038f2a',
   # 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': '86883747d3c5aba2f21b1963c6c24931f3d00067',
+  'v8_revision': 'ebd5dc00c889238ea48d84b19c1bc0a8233d41b1',
   # 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': 'b64aa6d5c5bdf4acf7de56dcb52594027f29a3ec',
+  'angle_revision': 'f56c8e02ce8b75bba76782d1b13200f0bde6cc63',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling SwiftShader
   # and whatever else without interference from each other.
@@ -324,7 +324,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling BoringSSL
   # and whatever else without interference from each other.
-  'boringssl_revision': 'c86127e656ae8b95622c79ba3d0fb35415502d53',
+  'boringssl_revision': 'f0ab91129d1a234dc127335765b794d62dc9ed4d',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling Fuchsia sdk
   # and whatever else without interference from each other.
@@ -420,7 +420,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': '85717040cc75f7b29ca714b18a31087d2a2b6382',
+  'dawn_revision': 'ad166239934f9105fdfbbfe8ea46f8e05ca43181',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
@@ -1158,7 +1158,7 @@
   },
 
   'src/chrome/release_scripts': {
-      'url': Var('chrome_git') + '/chrome/tools/release/scripts' + '@' + 'dd1c8ec0edb90fc7d5860a50b690f3bd8026ce14',
+      'url': Var('chrome_git') + '/chrome/tools/release/scripts' + '@' + '4f61c195e5c4e456130a2de1ad5362a125ea7a61',
       'condition': 'checkout_chrome_release_scripts',
   },
 
@@ -1501,7 +1501,7 @@
 
   'src/clank': {
     'url': Var('chrome_git') + '/clank/internal/apps.git' + '@' +
-    '0b6dec35eaa57d814798237cad2cd20a7453d50d',
+    '75c07728388055f0e7b7a0496693ee75827e374d',
     'condition': 'checkout_android and checkout_src_internal',
   },
 
@@ -2855,7 +2855,7 @@
       'dep_type': 'cipd',
   },
 
-  'src/third_party/vulkan-deps': '{chromium_git}/vulkan-deps@a7952ef72c943513045046f4f1ab0f91998a9307',
+  'src/third_party/vulkan-deps': '{chromium_git}/vulkan-deps@07e305fc66d3132b515ed22bc78dee79bf7eb9fc',
   'src/third_party/glslang/src': '{chromium_git}/external/github.com/KhronosGroup/glslang@e57f993cff981c8c3ffd38967e030f04d13781a9',
   'src/third_party/spirv-cross/src': '{chromium_git}/external/github.com/KhronosGroup/SPIRV-Cross@b8fcf307f1f347089e3c46eb4451d27f32ebc8d3',
   'src/third_party/spirv-headers/src': '{chromium_git}/external/github.com/KhronosGroup/SPIRV-Headers@8e82b7cfeca98baae9a01a53511483da7194f854',
@@ -2864,7 +2864,7 @@
   'src/third_party/vulkan-loader/src': '{chromium_git}/external/github.com/KhronosGroup/Vulkan-Loader@723d6b4aa35853315c6e021ec86388b3a2559fae',
   'src/third_party/vulkan-tools/src': '{chromium_git}/external/github.com/KhronosGroup/Vulkan-Tools@289efccc7560f2b970e2b4e0f50349da87669311',
   'src/third_party/vulkan-utility-libraries/src': '{chromium_git}/external/github.com/KhronosGroup/Vulkan-Utility-Libraries@551221d913cc56218fcaddce086ae293d375ac28',
-  'src/third_party/vulkan-validation-layers/src': '{chromium_git}/external/github.com/KhronosGroup/Vulkan-ValidationLayers@7c7fd77a6ba3ff7699d042ee1d396aa5803850df',
+  'src/third_party/vulkan-validation-layers/src': '{chromium_git}/external/github.com/KhronosGroup/Vulkan-ValidationLayers@fcc588f5171b007d433bf7c02184708dbc7d9d89',
 
   'src/third_party/vulkan_memory_allocator':
     Var('chromium_git') + '/external/github.com/GPUOpen-LibrariesAndSDKs/VulkanMemoryAllocator.git' + '@' + '56300b29fbfcc693ee6609ddad3fdd5b7a449a21',
@@ -2903,7 +2903,7 @@
     Var('chromium_git') + '/external/khronosgroup/webgl.git' + '@' + 'c01b768bce4a143e152c1870b6ba99ea6267d2b0',
 
   'src/third_party/webgpu-cts/src':
-    Var('chromium_git') + '/external/github.com/gpuweb/cts.git' + '@' + 'eb1d526134f6789e9ce6d5e3b654fbfc10043267',
+    Var('chromium_git') + '/external/github.com/gpuweb/cts.git' + '@' + '5aba8ba55eb2496852d622854a99fac74f074c65',
 
   'src/third_party/webpagereplay':
     Var('chromium_git') + '/webpagereplay.git' + '@' + Var('webpagereplay_revision'),
@@ -2987,7 +2987,7 @@
       'packages': [
         {
           'package': 'skia/tools/goldctl/mac-arm64',
-          'version': 'Sfu8dXj0tJ8uWFOZr90L_rVRcp8Kcy8zLsfelpk6K2YC',
+          'version': 'zQOaSHuVEWVro28b0D6FdKiq5f8KhkMG_XOFqAdgoIUC',
         },
       ],
       'dep_type': 'cipd',
@@ -3113,7 +3113,7 @@
       'packages': [
           {
               'package': 'chromium/third_party/android_deps/autorolled',
-              'version': '-kw2ioUuVwgaTHjBYvcyYBT3SZ24bRuwH_rIhsiZ24IC',
+              'version': 'TFiDa5VbziMUligPbDLeDXpPmLBgBdhqIj5CslOiRLMC',
           },
       ],
       'condition': 'checkout_android and non_git_source',
@@ -4701,7 +4701,7 @@
 
   'src/ios_internal':  {
       'url': Var('chrome_git') + '/chrome/ios_internal.git' + '@' +
-        'ab1e6c45e8b0ceb3f070f0a39c5271766fc7aa12',
+        'a4b8373a27c133f175d81ed8044474c23cfb164b',
       'condition': 'checkout_ios and checkout_src_internal',
   },
 
diff --git a/PRESUBMIT.py b/PRESUBMIT.py
index 42fa7c4..ced056f 100644
--- a/PRESUBMIT.py
+++ b/PRESUBMIT.py
@@ -2470,7 +2470,7 @@
                     'skylab-test-cros-roller', 'infra-try-recipes-tester',
                     'chrome-automated-expectation',
                     'chromium-automated-expectation', 'chrome-branch-day',
-                    'chromium-autosharder')
+                    'chrome-cherry-picker', 'chromium-autosharder')
   ) | set('%s@skia-public.iam.gserviceaccount.com' % s
           for s in ('chromium-autoroll', 'chromium-release-autoroll')
   ) | set('%s@skia-corp.google.com.iam.gserviceaccount.com' % s
diff --git a/android_webview/browser/aw_feature_map.cc b/android_webview/browser/aw_feature_map.cc
index 7b5e56e7..dd0a78a 100644
--- a/android_webview/browser/aw_feature_map.cc
+++ b/android_webview/browser/aw_feature_map.cc
@@ -37,7 +37,6 @@
     &features::kWebViewXRequestedWithHeaderControl,
     &metrics::kAndroidMetricsAsyncMetricLogging,
     &safe_browsing::kHashPrefixRealTimeLookups,
-    &features::kWebViewSupervisedUserSiteBlock,
     &base::features::kCollectAndroidFrameTimelineMetrics,
     &features::kWebViewMediaIntegrityApiBlinkExtension,
     &features::kWebViewSeparateResourceContext,
diff --git a/android_webview/common/aw_features.cc b/android_webview/common/aw_features.cc
index 1799c8d..20199c2 100644
--- a/android_webview/common/aw_features.cc
+++ b/android_webview/common/aw_features.cc
@@ -101,12 +101,6 @@
              "WebViewRecordAppDataDirectorySize",
              base::FEATURE_DISABLED_BY_DEFAULT);
 
-// Enable blocking the loading of mature sites (according to Google SafeSearch)
-// on WebViews running on supervised user accounts.
-BASE_FEATURE(kWebViewSupervisedUserSiteBlock,
-             "WebViewSupervisedUserSiteBlock",
-             base::FEATURE_ENABLED_BY_DEFAULT);
-
 // A Feature used for WebView variations tests. Not used in production. Please
 // do not clean up this stale feature: we intentionally keep this feature flag
 // around for testing purposes.
diff --git a/android_webview/common/aw_features.h b/android_webview/common/aw_features.h
index 5f88340..d33e154c 100644
--- a/android_webview/common/aw_features.h
+++ b/android_webview/common/aw_features.h
@@ -32,7 +32,6 @@
 BASE_DECLARE_FEATURE(kWebViewMuteAudio);
 BASE_DECLARE_FEATURE(kWebViewRecordAppDataDirectorySize);
 BASE_DECLARE_FEATURE(kWebViewRenderDocument);
-BASE_DECLARE_FEATURE(kWebViewSupervisedUserSiteBlock);
 BASE_DECLARE_FEATURE(kWebViewTestFeature);
 BASE_DECLARE_FEATURE(kWebViewUseMetricsUploadService);
 BASE_DECLARE_FEATURE(kWebViewUseMetricsUploadServiceOnlySdkRuntime);
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 1d826da..e66a69b 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
@@ -666,10 +666,6 @@
                 AwFeatures.WEBVIEW_AUTO_SAA,
                 "Enable auto granting storage access API requests. This will be done "
                         + "if a relationship is detected between the app and the website."),
-        Flag.baseFeature(
-                AwFeatures.WEBVIEW_SUPERVISED_USER_SITE_BLOCK,
-                "Enable blocking the loading of mature sites on "
-                        + "WebViews running on supervised user accounts"),
         Flag.baseFeature(GwpAsanFeatures.GWP_ASAN_MALLOC, "GWP-ASan for `malloc()`."),
         Flag.baseFeature(GwpAsanFeatures.GWP_ASAN_PARTITION_ALLOC, "GWP-ASan for PartitionAlloc."),
         Flag.baseFeature(
@@ -939,10 +935,6 @@
                 "Redact sensitive content during screen sharing, screen recording, and similar"
                         + " actions"),
         Flag.baseFeature(
-                BlinkFeatures.PLZ_DEDICATED_WORKER,
-                "Enable PlzDedicatedWorker. This affects how some URLs are sent to"
-                        + " WebViewClient.shouldInterceptRequest()"),
-        Flag.baseFeature(
                 "BlinkUseLargeEmptySlotSpanRingForBufferRoot",
                 "Tuning memory allocator for speed - large empty slot span ring for Blink buffer"
                         + " root"),
diff --git a/android_webview/java/src/org/chromium/android_webview/supervised_user/AwSupervisedUserUrlClassifier.java b/android_webview/java/src/org/chromium/android_webview/supervised_user/AwSupervisedUserUrlClassifier.java
index c0ed9cb..1c8420e 100644
--- a/android_webview/java/src/org/chromium/android_webview/supervised_user/AwSupervisedUserUrlClassifier.java
+++ b/android_webview/java/src/org/chromium/android_webview/supervised_user/AwSupervisedUserUrlClassifier.java
@@ -10,8 +10,6 @@
 import org.jni_zero.JNINamespace;
 import org.jni_zero.NativeMethods;
 
-import org.chromium.android_webview.AwFeatureMap;
-import org.chromium.android_webview.common.AwFeatures;
 import org.chromium.android_webview.common.AwSupervisedUserUrlClassifierDelegate;
 import org.chromium.android_webview.common.PlatformServiceBridge;
 import org.chromium.base.ContextUtils;
@@ -51,12 +49,10 @@
 
         synchronized (sInstanceLock) {
             if (!sInitialized) {
-                if (AwFeatureMap.isEnabled(AwFeatures.WEBVIEW_SUPERVISED_USER_SITE_BLOCK)) {
-                    AwSupervisedUserUrlClassifierDelegate delegate =
-                            PlatformServiceBridge.getInstance().getUrlClassifierDelegate();
-                    if (delegate != null) {
-                        sInstance = new AwSupervisedUserUrlClassifier(delegate);
-                    }
+                AwSupervisedUserUrlClassifierDelegate delegate =
+                        PlatformServiceBridge.getInstance().getUrlClassifierDelegate();
+                if (delegate != null) {
+                    sInstance = new AwSupervisedUserUrlClassifier(delegate);
                 }
                 sInitialized = true;
             }
diff --git a/android_webview/javatests/src/org/chromium/android_webview/test/AwContentsClientShouldInterceptRequestTest.java b/android_webview/javatests/src/org/chromium/android_webview/test/AwContentsClientShouldInterceptRequestTest.java
index be93c173..c4ebffd 100644
--- a/android_webview/javatests/src/org/chromium/android_webview/test/AwContentsClientShouldInterceptRequestTest.java
+++ b/android_webview/javatests/src/org/chromium/android_webview/test/AwContentsClientShouldInterceptRequestTest.java
@@ -1997,7 +1997,6 @@
     @Test
     @SmallTest
     @Feature({"AndroidWebView"})
-    @CommandLineFlags.Add({"enable-features=PlzDedicatedWorker"})
     public void testDedicatedWorkerSubresourceIntercepted() throws Throwable {
         final String importScriptJs = addJavaScriptToTestServer(mWebServer, "/test-worker.js", "");
         final String workerJs =
diff --git a/android_webview/javatests/src/org/chromium/android_webview/test/AwSupervisedUserTest.java b/android_webview/javatests/src/org/chromium/android_webview/test/AwSupervisedUserTest.java
index 91d178b..184c0e5 100644
--- a/android_webview/javatests/src/org/chromium/android_webview/test/AwSupervisedUserTest.java
+++ b/android_webview/javatests/src/org/chromium/android_webview/test/AwSupervisedUserTest.java
@@ -37,10 +37,8 @@
 import org.junit.runners.Parameterized.UseParametersRunnerFactory;
 
 import org.chromium.android_webview.AwContents;
-import org.chromium.android_webview.AwFeatureMap;
 import org.chromium.android_webview.JsReplyProxy;
 import org.chromium.android_webview.WebMessageListener;
-import org.chromium.android_webview.common.AwFeatures;
 import org.chromium.android_webview.common.AwSupervisedUserUrlClassifierDelegate;
 import org.chromium.android_webview.common.BackgroundThreadExecutor;
 import org.chromium.android_webview.common.PlatformServiceBridge;
@@ -50,7 +48,6 @@
 import org.chromium.base.ThreadUtils;
 import org.chromium.base.test.util.Batch;
 import org.chromium.base.test.util.CallbackHelper;
-import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.Criteria;
 import org.chromium.base.test.util.CriteriaHelper;
 import org.chromium.base.test.util.Feature;
@@ -153,7 +150,6 @@
     @Test
     @SmallTest
     @Feature({"AndroidWebView"})
-    @CommandLineFlags.Add("enable-features=" + AwFeatures.WEBVIEW_SUPERVISED_USER_SITE_BLOCK)
     public void testAllowedSiteIsLoaded() throws Throwable {
         String embeddedUrl = setUpWebPage(SAFE_SITE_IFRAME_PATH, SAFE_SITE_IFRAME_TITLE, null);
         String requestUrl = setUpWebPage(SAFE_SITE_PATH, SAFE_SITE_TITLE, embeddedUrl);
@@ -167,7 +163,6 @@
     @Test
     @SmallTest
     @Feature({"AndroidWebView"})
-    @CommandLineFlags.Add("enable-features=" + AwFeatures.WEBVIEW_SUPERVISED_USER_SITE_BLOCK)
     public void testDisallowedSiteIsBlocked() throws Throwable {
         String requestUrl = setUpWebPage(MATURE_SITE_PATH, MATURE_SITE_TITLE, null);
 
@@ -179,7 +174,6 @@
     @Test
     @SmallTest
     @Feature({"AndroidWebView"})
-    @CommandLineFlags.Add("enable-features=" + AwFeatures.WEBVIEW_SUPERVISED_USER_SITE_BLOCK)
     public void testDisallowedEmbeddedSiteIsBlocked() throws Throwable {
         String embeddedUrl = setUpWebPage(MATURE_SITE_IFRAME_PATH, MATURE_SITE_IFRAME_TITLE, null);
         String requestUrl = setUpWebPage(SAFE_SITE_PATH, SAFE_SITE_TITLE, embeddedUrl);
@@ -193,7 +187,6 @@
     @Test
     @SmallTest
     @Feature({"AndroidWebView"})
-    @CommandLineFlags.Add("enable-features=" + AwFeatures.WEBVIEW_SUPERVISED_USER_SITE_BLOCK)
     public void testDisallowedSiteRedirectIsBlocked() throws Throwable {
         String requestUrl = mWebServer.setRedirect(MATURE_SITE_PATH, SAFE_SITE_PATH);
 
@@ -205,7 +198,6 @@
     @Test
     @SmallTest
     @Feature({"AndroidWebView"})
-    @CommandLineFlags.Add("enable-features=" + AwFeatures.WEBVIEW_SUPERVISED_USER_SITE_BLOCK)
     public void testDisallowedRedirectIsBlocked() throws Throwable {
         String requestUrl = mWebServer.setRedirect(SAFE_SITE_PATH, MATURE_SITE_PATH);
 
@@ -217,21 +209,6 @@
     @Test
     @SmallTest
     @Feature({"AndroidWebView"})
-    @CommandLineFlags.Add("disable-features=" + AwFeatures.WEBVIEW_SUPERVISED_USER_SITE_BLOCK)
-    public void testDisallowedSiteIsLoadedFeatureOff() throws Throwable {
-        String embeddedUrl = setUpWebPage(MATURE_SITE_IFRAME_PATH, MATURE_SITE_IFRAME_TITLE, null);
-        String requestUrl = setUpWebPage(MATURE_SITE_PATH, MATURE_SITE_TITLE, embeddedUrl);
-
-        loadUrl(requestUrl);
-
-        assertPageTitle(MATURE_SITE_TITLE);
-        assertIframeTitle(MATURE_SITE_IFRAME_TITLE);
-    }
-
-    @Test
-    @SmallTest
-    @Feature({"AndroidWebView"})
-    @CommandLineFlags.Add("enable-features=" + AwFeatures.WEBVIEW_SUPERVISED_USER_SITE_BLOCK)
     public void testBlocksContentOnlyIfRestrctionRequired() throws Throwable {
         String embeddedUrl = setUpWebPage(MATURE_SITE_IFRAME_PATH, MATURE_SITE_IFRAME_TITLE, null);
         String requestUrl = setUpWebPage(MATURE_SITE_PATH, MATURE_SITE_TITLE, embeddedUrl);
@@ -263,7 +240,6 @@
     @Test
     @SmallTest
     @Feature({"AndroidWebView"})
-    @CommandLineFlags.Add("enable-features=" + AwFeatures.WEBVIEW_SUPERVISED_USER_SITE_BLOCK)
     public void testClickLearnMoreLink() throws Throwable {
         String requestUrl = setUpWebPage(MATURE_SITE_PATH, MATURE_SITE_TITLE, null);
         try {
@@ -293,7 +269,6 @@
     @Test
     @SmallTest
     @Feature({"AndroidWebView"})
-    @CommandLineFlags.Add("enable-features=" + AwFeatures.WEBVIEW_SUPERVISED_USER_SITE_BLOCK)
     public void testSafeMode() throws Throwable {
         String embeddedUrl = setUpWebPage(MATURE_SITE_IFRAME_PATH, MATURE_SITE_IFRAME_TITLE, null);
         String requestUrl = setUpWebPage(MATURE_SITE_PATH, MATURE_SITE_TITLE, embeddedUrl);
@@ -415,10 +390,6 @@
     }
 
     private void resetNeedsRestriction(boolean value) throws Exception {
-        if (!AwFeatureMap.isEnabled(AwFeatures.WEBVIEW_SUPERVISED_USER_SITE_BLOCK)) {
-            // Nothing we need to do if the feature is disabled.
-            return;
-        }
         mDelegate.setNeedsRestrictedContentBlockingResponse(value);
         int count = mDelegate.getNeedsRestrictionHelper().getCallCount();
         AwSupervisedUserUrlClassifier classifier = AwSupervisedUserUrlClassifier.getInstance();
diff --git a/android_webview/test/data/web_tests/virtual/stable/webexposed/global-interface-listing-expected.txt b/android_webview/test/data/web_tests/virtual/stable/webexposed/global-interface-listing-expected.txt
index 17b244c..869feefa 100644
--- a/android_webview/test/data/web_tests/virtual/stable/webexposed/global-interface-listing-expected.txt
+++ b/android_webview/test/data/web_tests/virtual/stable/webexposed/global-interface-listing-expected.txt
@@ -8302,6 +8302,7 @@
     getter anchorOffset
     getter baseNode
     getter baseOffset
+    getter direction
     getter extentNode
     getter extentOffset
     getter focusNode
@@ -8318,6 +8319,7 @@
     method deleteFromDocument
     method empty
     method extend
+    method getComposedRanges
     method getRangeAt
     method modify
     method removeAllRanges
diff --git a/ash/root_window_controller.cc b/ash/root_window_controller.cc
index 0e2396f..4548d9b 100644
--- a/ash/root_window_controller.cc
+++ b/ash/root_window_controller.cc
@@ -1176,11 +1176,9 @@
                   non_lock_screen_containers);
 
   aura::Window* shutdown_screenshot_container = non_lock_screen_containers;
-  if (features::IsForestFeatureEnabled()) {
-    shutdown_screenshot_container = CreateContainer(
-        kShellWindowId_ShutdownScreenshotContainer,
-        "ShutdownScreenshotContainer", non_lock_screen_containers);
-  }
+  shutdown_screenshot_container = CreateContainer(
+      kShellWindowId_ShutdownScreenshotContainer, "ShutdownScreenshotContainer",
+      non_lock_screen_containers);
 
   for (const auto& id : desks_util::GetDesksContainersIds()) {
     aura::Window* container =
diff --git a/ash/system/unified/quick_settings_footer_unittest.cc b/ash/system/unified/quick_settings_footer_unittest.cc
index b846f2ff..6a516259 100644
--- a/ash/system/unified/quick_settings_footer_unittest.cc
+++ b/ash/system/unified/quick_settings_footer_unittest.cc
@@ -16,6 +16,7 @@
 #include "base/memory/raw_ptr.h"
 #include "base/memory/scoped_refptr.h"
 #include "base/test/metrics/histogram_tester.h"
+#include "base/test/run_until.h"
 #include "components/user_manager/user_type.h"
 #include "ui/views/test/views_test_utils.h"
 #include "ui/views/view_utils.h"
@@ -249,11 +250,6 @@
 }
 
 TEST_F(QuickSettingsFooterTest, SignOutButtonRecordsUmaAndSignsOut) {
-  // TODO(minch): Re-enable this test.
-  if (features::IsForestFeatureEnabled()) {
-    GTEST_SKIP() << "Skipping test body for forest feature.";
-  }
-
   GetSessionControllerClient()->set_existing_users_count(2);
   SimulateUserLogin(kRegularUserLoginInfo);
   SetUpView();
@@ -267,7 +263,9 @@
                                      QsButtonCatalogName::kSignOutButton,
                                      /*expected_count=*/1);
 
-  EXPECT_EQ(1, GetSessionControllerClient()->request_sign_out_count());
+  EXPECT_TRUE(base::test::RunUntil([&]() {
+    return GetSessionControllerClient()->request_sign_out_count() == 1;
+  }));
 }
 
 // Settings button is disabled when kSettingsIconDisabled is set.
diff --git a/ash/webui/recorder_app_ui/resources/platforms/swa/on_device_model.ts b/ash/webui/recorder_app_ui/resources/platforms/swa/on_device_model.ts
index 992c8c7..f73621d9 100644
--- a/ash/webui/recorder_app_ui/resources/platforms/swa/on_device_model.ts
+++ b/ash/webui/recorder_app_ui/resources/platforms/swa/on_device_model.ts
@@ -160,6 +160,7 @@
         maxOutputTokens: 0,
         topK: null,
         temperature: null,
+        responseJsonSchema: '',
       },
       responseRouter.$.bindNewPipeAndPassRemote(),
     );
diff --git a/ash/wm/lock_state_controller.cc b/ash/wm/lock_state_controller.cc
index 4dbfe953..e9bc5139 100644
--- a/ash/wm/lock_state_controller.cc
+++ b/ash/wm/lock_state_controller.cc
@@ -17,7 +17,6 @@
 #include "ash/app_list/app_list_controller_impl.h"
 #include "ash/cancel_mode.h"
 #include "ash/capture_mode/capture_mode_controller.h"
-#include "ash/constants/ash_features.h"
 #include "ash/constants/ash_pref_names.h"
 #include "ash/public/cpp/saved_desk_delegate.h"
 #include "ash/public/cpp/shell_window_ids.h"
@@ -465,11 +464,7 @@
   }
 
   HideAndMaybeLockCursor(/*lock=*/true);
-  if (features::IsForestFeatureEnabled()) {
-    SessionStateChangeWithInformedRestore(RequestedSessionState::kShutdown);
-  } else {
-    StartSessionStateChange(RequestedSessionState::kShutdown);
-  }
+  SessionStateChangeWithInformedRestore(RequestedSessionState::kShutdown);
 }
 
 void LockStateController::RequestCancelableShutdown(ShutdownReason reason) {
@@ -477,12 +472,8 @@
   shutdown_canceled_ = false;
 
   HideAndMaybeLockCursor(/*lock=*/false);
-  if (features::IsForestFeatureEnabled()) {
-    SessionStateChangeWithInformedRestore(
-        RequestedSessionState::kCancelableShutdown);
-  } else {
-    StartSessionStateChange(RequestedSessionState::kCancelableShutdown);
-  }
+  SessionStateChangeWithInformedRestore(
+      RequestedSessionState::kCancelableShutdown);
 }
 
 bool LockStateController::ShutdownRequested() const {
@@ -499,12 +490,10 @@
       SessionStateAnimator::ANIMATION_UNDO_GRAYSCALE_BRIGHTNESS,
       SessionStateAnimator::ANIMATION_SPEED_REVERT_SHUTDOWN);
   shutdown_canceled_ = true;
-  if (features::IsForestFeatureEnabled()) {
-    // Shutdown maybe canceled before or after image saved. So we need to delete
-    // both here and `OnImageSaved`.
-    DeleteInformedRestoreImage(informed_restore_image_callback_for_test_,
-                               GetInformedRestoreImagePath());
-  }
+  // Shutdown maybe canceled before or after image saved. So we need to delete
+  // both here and `OnImageSaved`.
+  DeleteInformedRestoreImage(informed_restore_image_callback_for_test_,
+                             GetInformedRestoreImagePath());
   cancelable_shutdown_timer_.Stop();
   return true;
 }
@@ -512,23 +501,15 @@
 void LockStateController::RequestRestart(
     power_manager::RequestRestartReason reason,
     const std::string& description) {
-  if (features::IsForestFeatureEnabled()) {
-    HideAndMaybeLockCursor(/*lock=*/false);
-    restart_callback_ =
-        base::BindOnce(&LockStateController::DoRestart, base::Unretained(this),
-                       reason, description);
-    SessionStateChangeWithInformedRestore(RequestedSessionState::kRestart);
-  } else {
-    chromeos::PowerManagerClient::Get()->RequestRestart(reason, description);
-  }
+  HideAndMaybeLockCursor(/*lock=*/false);
+  restart_callback_ =
+      base::BindOnce(&LockStateController::DoRestart, base::Unretained(this),
+                     reason, description);
+  SessionStateChangeWithInformedRestore(RequestedSessionState::kRestart);
 }
 
 void LockStateController::RequestSignOut() {
-  if (features::IsForestFeatureEnabled()) {
-    SessionStateChangeWithInformedRestore(RequestedSessionState::kSignOut);
-  } else {
-    Shell::Get()->session_controller()->RequestSignOut();
-  }
+  SessionStateChangeWithInformedRestore(RequestedSessionState::kSignOut);
 }
 
 void LockStateController::OnHostCloseRequested(aura::WindowTreeHost* host) {
diff --git a/base/BUILD.gn b/base/BUILD.gn
index c4b8c65..0c24c7f 100644
--- a/base/BUILD.gn
+++ b/base/BUILD.gn
@@ -1519,8 +1519,6 @@
   # TODO(crbug.com/40511454) Use only boringssl when NaCl is removed.
   sources += [
     "hash/md5.h",
-    "hash/md5_constexpr.h",
-    "hash/md5_constexpr_internal.h",
     "hash/sha1.h",
   ]
   if (is_nacl) {
@@ -3381,7 +3379,6 @@
     "gmock_unittest.cc",
     "hash/hash_unittest.cc",
     "hash/legacy_hash_unittest.cc",
-    "hash/md5_constexpr_unittest.cc",
     "hash/md5_unittest.cc",
     "hash/sha1_unittest.cc",
     "i18n/break_iterator_unittest.cc",
diff --git a/base/hash/md5_constexpr.h b/base/hash/md5_constexpr.h
deleted file mode 100644
index dd239598..0000000
--- a/base/hash/md5_constexpr.h
+++ /dev/null
@@ -1,27 +0,0 @@
-// Copyright 2019 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef BASE_HASH_MD5_CONSTEXPR_H_
-#define BASE_HASH_MD5_CONSTEXPR_H_
-
-#include <string_view>
-
-#include "base/hash/md5_constexpr_internal.h"  // IWYU pragma: export
-
-namespace base {
-
-// Calculates the first 32/64 bits of the MD5 digest of the provided data,
-// returned as a uint32_t/uint64_t. When passing |string| with no explicit
-// length the terminating null will not be processed. This abstracts away
-// endianness so that the integer will read as the first 4 or 8 bytes of the
-// MD5 sum, ensuring that the following outputs are equivalent for
-// convenience:
-//
-// printf("%08x\n", MD5Hash32Constexpr("foo"));
-constexpr uint64_t MD5Hash64Constexpr(std::string_view string);
-constexpr uint32_t MD5Hash32Constexpr(std::string_view string);
-
-}  // namespace base
-
-#endif  // BASE_HASH_MD5_CONSTEXPR_H_
diff --git a/base/hash/md5_constexpr_unittest.cc b/base/hash/md5_constexpr_unittest.cc
deleted file mode 100644
index bb6d553..0000000
--- a/base/hash/md5_constexpr_unittest.cc
+++ /dev/null
@@ -1,25 +0,0 @@
-// Copyright 2019 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "base/hash/md5_constexpr.h"
-
-namespace base {
-
-// Ensure that everything works at compile-time by comparing to a few
-// reference hashes.
-constexpr char kMessage0[] = "message digest";
-static_assert(MD5Hash64Constexpr(kMessage0) == 0xF96B697D7CB7938Dull,
-              "incorrect MD5Hash64 implementation");
-
-static_assert(MD5Hash32Constexpr(kMessage0) == 0xF96B697Dul,
-              "incorrect MD5Hash32 implementation");
-
-constexpr char kMessage1[] = "The quick brown fox jumps over the lazy dog";
-static_assert(MD5Hash64Constexpr(kMessage1) == 0x9E107D9D372BB682ull,
-              "incorrect MD5Hash64 implementation");
-
-static_assert(MD5Hash32Constexpr(kMessage1) == 0x9E107D9Dul,
-              "incorrect MD5Hash32 implementation");
-
-}  // namespace base
diff --git a/build/android/gyp/util/jar_utils.py b/build/android/gyp/util/jar_utils.py
index 0bd4137..1649eab6 100644
--- a/build/android/gyp/util/jar_utils.py
+++ b/build/android/gyp/util/jar_utils.py
@@ -15,7 +15,7 @@
 _IGNORED_JAR_PATHS = [
     # This matches org_ow2_asm_asm_commons and org_ow2_asm_asm_analysis, both of
     # which fail jdeps (not sure why), see: https://crbug.com/348423879
-    'third_party/android_deps/cipd/libs/org_ow2_asm_asm',
+    'cipd/libs/org_ow2_asm_asm',
 ]
 
 def _should_ignore(jar_path: pathlib.Path) -> bool:
diff --git a/build/config/unsafe_buffers_paths.txt b/build/config/unsafe_buffers_paths.txt
index 80abde7..29db2f9 100644
--- a/build/config/unsafe_buffers_paths.txt
+++ b/build/config/unsafe_buffers_paths.txt
@@ -5,7 +5,7 @@
 # See `docs/unsafe_buffers.md`.
 
 # Checks to enable
-.buffers
+.buffers,libc
 
 # Directories to exempt from checks
 -base/allocator/
diff --git a/chrome/android/features/keyboard_accessory/internal/java/src/org/chromium/chrome/browser/keyboard_accessory/AutofillKeyboardAccessoryViewBridge.java b/chrome/android/features/keyboard_accessory/internal/java/src/org/chromium/chrome/browser/keyboard_accessory/AutofillKeyboardAccessoryViewBridge.java
index 5ec0d0c9..5a71adf 100644
--- a/chrome/android/features/keyboard_accessory/internal/java/src/org/chromium/chrome/browser/keyboard_accessory/AutofillKeyboardAccessoryViewBridge.java
+++ b/chrome/android/features/keyboard_accessory/internal/java/src/org/chromium/chrome/browser/keyboard_accessory/AutofillKeyboardAccessoryViewBridge.java
@@ -184,7 +184,6 @@
                 .setLabel(label)
                 .setSubLabel(sublabel)
                 .setIconId(drawableId)
-                .setIsIconAtStart(false)
                 .setSuggestionType(suggestionType)
                 .setIsDeletable(isDeletable)
                 .setFeatureForIph(featureForIph)
diff --git a/chrome/android/features/tab_ui/BUILD.gn b/chrome/android/features/tab_ui/BUILD.gn
index 7c41afdd..56d8e7c 100644
--- a/chrome/android/features/tab_ui/BUILD.gn
+++ b/chrome/android/features/tab_ui/BUILD.gn
@@ -29,6 +29,8 @@
     "java/res/anim/iph_touch_point_background_animation.xml",
     "java/res/anim/tab_switcher_context_menu_animation_in.xml",
     "java/res/anim/tab_switcher_context_menu_animation_out.xml",
+    "java/res/color-night/tab_grid_dialog_bg_color.xml",
+    "java/res/color/tab_grid_dialog_bg_color.xml",
     "java/res/drawable/archived_tab_icon.xml",
     "java/res/drawable/chevron_right.xml",
     "java/res/drawable/color_picker_color_icon.xml",
diff --git a/chrome/android/features/tab_ui/java/res/color-night/tab_grid_dialog_bg_color.xml b/chrome/android/features/tab_ui/java/res/color-night/tab_grid_dialog_bg_color.xml
new file mode 100644
index 0000000..4c9a1ac
--- /dev/null
+++ b/chrome/android/features/tab_ui/java/res/color-night/tab_grid_dialog_bg_color.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+Copyright 2025 The Chromium Authors
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+  <item android:color="?attr/colorSurfaceContainerLow" />
+</selector>
diff --git a/chrome/android/features/tab_ui/java/res/color/tab_grid_dialog_bg_color.xml b/chrome/android/features/tab_ui/java/res/color/tab_grid_dialog_bg_color.xml
new file mode 100644
index 0000000..8a60d84
--- /dev/null
+++ b/chrome/android/features/tab_ui/java/res/color/tab_grid_dialog_bg_color.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+Copyright 2025 The Chromium Authors
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+  <item android:color="?attr/colorSurface" />
+</selector>
diff --git a/chrome/android/features/tab_ui/java/res/values/colors.xml b/chrome/android/features/tab_ui/java/res/values/colors.xml
index 2dab8d7..460a3ead5 100644
--- a/chrome/android/features/tab_ui/java/res/values/colors.xml
+++ b/chrome/android/features/tab_ui/java/res/values/colors.xml
@@ -26,7 +26,7 @@
     <color name="incognito_tab_tile_number_color">@color/baseline_neutral_90</color>
     <color name="incognito_tab_tile_number_selected_color">@color/baseline_primary_20</color>
 
-    <color name="incognito_tab_grid_dialog_background_color">@color/default_bg_color_dark</color>
+    <color name="incognito_tab_grid_dialog_background_color">@color/gm3_baseline_surface_container_low_dark</color>
 
     <color name="incognito_tab_grid_dialog_ungroup_bar_bg_hovered_color">@color/baseline_primary_80</color>
 
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabUiThemeProvider.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabUiThemeProvider.java
index 1299edbe..264a78e 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabUiThemeProvider.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabUiThemeProvider.java
@@ -204,7 +204,7 @@
         if (isIncognito) {
             return context.getColor(R.color.incognito_tab_grid_dialog_background_color);
         } else {
-            return SemanticColorUtils.getDialogBgColor(context);
+            return ContextCompat.getColor(context, R.color.tab_grid_dialog_bg_color);
         }
     }
 
diff --git a/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherListEditorPTTest.java b/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherListEditorPTTest.java
index 0b50f81..1ed16775 100644
--- a/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherListEditorPTTest.java
+++ b/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherListEditorPTTest.java
@@ -4,20 +4,27 @@
 
 package org.chromium.chrome.browser.tasks.tab_management;
 
+import static org.mockito.Mockito.when;
+
 import static org.chromium.base.test.transit.TransitAsserts.assertFinalDestination;
 import static org.chromium.chrome.test.util.TabBinningUtil.group;
 
 import androidx.test.filters.MediumTest;
 
+import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
 
 import org.chromium.base.ThreadUtils;
 import org.chromium.base.test.util.Batch;
 import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.Features;
 import org.chromium.base.test.util.RequiresRestart;
+import org.chromium.chrome.browser.collaboration.CollaborationServiceFactory;
 import org.chromium.chrome.browser.flags.ChromeFeatureList;
 import org.chromium.chrome.browser.flags.ChromeSwitches;
 import org.chromium.chrome.browser.tab.Tab;
@@ -38,6 +45,8 @@
 import org.chromium.chrome.test.transit.ntp.RegularNewTabPageStation;
 import org.chromium.chrome.test.transit.page.WebPageStation;
 import org.chromium.chrome.test.util.TabBinningUtil;
+import org.chromium.components.collaboration.CollaborationService;
+import org.chromium.components.collaboration.ServiceStatus;
 import org.chromium.components.tab_groups.TabGroupColorId;
 
 import java.util.List;
@@ -48,12 +57,28 @@
 @Batch(Batch.PER_CLASS)
 // TODO(https://crbug.com/392634251): Fix line height when elegant text height is used with Roboto
 // or enable Google Sans (Text) in //chrome/ tests on Android T+.
-@Features.DisableFeatures(ChromeFeatureList.ANDROID_ELEGANT_TEXT_HEIGHT)
+@Features.DisableFeatures({
+    ChromeFeatureList.DATA_SHARING,
+    ChromeFeatureList.ANDROID_ELEGANT_TEXT_HEIGHT
+})
 public class TabSwitcherListEditorPTTest {
     @Rule
     public AutoResetCtaTransitTestRule mCtaTestRule =
             ChromeTransitTestRules.autoResetCtaActivityRule();
 
+    @Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule();
+
+    @Mock private CollaborationService mCollaborationService;
+    @Mock private ServiceStatus mServiceStatus;
+
+    @Before
+    public void setUp() {
+        CollaborationServiceFactory.setForTesting(mCollaborationService);
+        when(mCollaborationService.getServiceStatus()).thenReturn(mServiceStatus);
+        when(mServiceStatus.isAllowedToCreate()).thenReturn(false);
+        when(mServiceStatus.isAllowedToJoin()).thenReturn(false);
+    }
+
     @Test
     @MediumTest
     public void testLeaveEditorViaBackPress() {
diff --git a/chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/tab_management/ArchivedTabsCardViewBinderUnitTest.java b/chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/tab_management/ArchivedTabsCardViewBinderUnitTest.java
index c9d68699..e3c4cfd48 100644
--- a/chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/tab_management/ArchivedTabsCardViewBinderUnitTest.java
+++ b/chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/tab_management/ArchivedTabsCardViewBinderUnitTest.java
@@ -16,17 +16,19 @@
 import android.view.View;
 import android.widget.TextView;
 
+import androidx.test.ext.junit.rules.ActivityScenarioRule;
+
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.junit.MockitoJUnit;
 import org.mockito.junit.MockitoRule;
-import org.robolectric.Robolectric;
 import org.robolectric.annotation.Config;
 
 import org.chromium.base.test.BaseRobolectricTestRunner;
 import org.chromium.base.test.util.CallbackHelper;
+import org.chromium.ui.base.TestActivity;
 import org.chromium.ui.modelutil.PropertyModel;
 import org.chromium.ui.modelutil.PropertyModelChangeProcessor;
 
@@ -43,6 +45,10 @@
 
     @Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule();
 
+    @Rule
+    public final ActivityScenarioRule<TestActivity> mActivityScenarioRule =
+            new ActivityScenarioRule<>(TestActivity.class);
+
     private Activity mActivity;
     private View mArchivedTabsCardView;
     private PropertyModel mModel;
@@ -50,7 +56,7 @@
 
     @Before
     public void setUp() throws Exception {
-        mActivity = Robolectric.buildActivity(Activity.class).setup().get();
+        mActivityScenarioRule.getScenario().onActivity(activity -> mActivity = activity);
         mArchivedTabsCardView =
                 LayoutInflater.from(mActivity)
                         .inflate(R.layout.archived_tabs_message_card_view, /* root= */ null);
diff --git a/chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/tab_management/ArchivedTabsMessageServiceUnitTest.java b/chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/tab_management/ArchivedTabsMessageServiceUnitTest.java
index 83f8c464..80a148a 100644
--- a/chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/tab_management/ArchivedTabsMessageServiceUnitTest.java
+++ b/chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/tab_management/ArchivedTabsMessageServiceUnitTest.java
@@ -21,6 +21,8 @@
 import android.view.ViewGroup;
 import android.widget.FrameLayout;
 
+import androidx.test.ext.junit.rules.ActivityScenarioRule;
+
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
@@ -31,7 +33,6 @@
 import org.mockito.junit.MockitoJUnit;
 import org.mockito.junit.MockitoRule;
 import org.mockito.quality.Strictness;
-import org.robolectric.Robolectric;
 import org.robolectric.annotation.Config;
 import org.robolectric.shadows.ShadowLooper;
 
@@ -50,6 +51,7 @@
 import org.chromium.chrome.browser.ui.edge_to_edge.EdgeToEdgeController;
 import org.chromium.chrome.browser.ui.messages.snackbar.SnackbarManager;
 import org.chromium.components.feature_engagement.Tracker;
+import org.chromium.ui.base.TestActivity;
 import org.chromium.ui.modaldialog.ModalDialogManager;
 import org.chromium.ui.modelutil.PropertyModel;
 
@@ -61,6 +63,10 @@
 
     @Rule public MockitoRule mMockitoRule = MockitoJUnit.rule().strictness(Strictness.LENIENT);
 
+    @Rule
+    public ActivityScenarioRule<TestActivity> mActivityScenarioRule =
+            new ActivityScenarioRule<>(TestActivity.class);
+
     @Mock private ArchivedTabModelOrchestrator mArchivedTabModelOrchestrator;
     @Mock private TabArchiveSettings mTabArchiveSettings;
     @Mock private TabModel mArchivedTabModel;
@@ -90,7 +96,7 @@
 
     @Before
     public void setUp() throws Exception {
-        mActivity = Robolectric.buildActivity(Activity.class).setup().get();
+        mActivityScenarioRule.getScenario().onActivity(activity -> mActivity = activity);
         mRootView = new FrameLayout(mActivity);
 
         doReturn(TIME_DELTA_DAYS).when(mTabArchiveSettings).getArchiveTimeDeltaDays();
diff --git a/chrome/android/java/res/layout/recent_tabs_group_item.xml b/chrome/android/java/res/layout/recent_tabs_group_item.xml
index 7c648a3..086dd62 100644
--- a/chrome/android/java/res/layout/recent_tabs_group_item.xml
+++ b/chrome/android/java/res/layout/recent_tabs_group_item.xml
@@ -10,8 +10,7 @@
     xmlns:tools="http://schemas.android.com/tools"
     android:id="@+id/recent_tabs_group_view"
     android:layout_height="wrap_content"
-    android:layout_width="match_parent"
-    android:background="?attr/selectableItemBackground" >
+    android:layout_width="match_parent" >
 
     <LinearLayout
         android:layout_width="match_parent"
diff --git a/chrome/android/java/res/layout/recent_tabs_list_item.xml b/chrome/android/java/res/layout/recent_tabs_list_item.xml
index 993f571..ede144b 100644
--- a/chrome/android/java/res/layout/recent_tabs_list_item.xml
+++ b/chrome/android/java/res/layout/recent_tabs_list_item.xml
@@ -13,7 +13,6 @@
     android:layout_height="wrap_content"
     android:layout_width="match_parent"
     android:minHeight="@dimen/recent_tabs_foreign_session_group_item_height"
-    android:background="?attr/selectableItemBackground"
     android:paddingStart="@dimen/recent_tabs_list_item_side_padding"
     android:paddingEnd="@dimen/recent_tabs_list_item_side_padding">
 
diff --git a/chrome/android/java/res/layout/recent_tabs_page.xml b/chrome/android/java/res/layout/recent_tabs_page.xml
index 63c4153c..8bc81f65 100644
--- a/chrome/android/java/res/layout/recent_tabs_page.xml
+++ b/chrome/android/java/res/layout/recent_tabs_page.xml
@@ -27,11 +27,10 @@
             android:paddingTop="@dimen/recent_tabs_group_view_vertical_padding"
             android:layout_height="match_parent"
             android:layout_width="match_parent"
-            android:cacheColorHint="@macro/default_bg_color"
             android:childDivider="@null"
             android:divider="@null"
             android:background="@macro/default_bg_color"
-            android:listSelector="@android:color/transparent"
+            android:listSelector="?attr/selectableItemBackground"
             android:scrollbarStyle="outsideOverlay" />
 
     </org.chromium.chrome.browser.ntp.NativePageRootFrameLayout>
diff --git a/chrome/android/java/res/values-sw600dp-v30/styles.xml b/chrome/android/java/res/values-sw600dp-v30/styles.xml
index 80d39fdb..096724e4 100644
--- a/chrome/android/java/res/values-sw600dp-v30/styles.xml
+++ b/chrome/android/java/res/values-sw600dp-v30/styles.xml
@@ -6,6 +6,10 @@
 -->
 
 <resources>
+    <style name="Theme.Chromium.TabbedMode" parent="Base.Theme.Chromium.TabbedMode">
+        <item name="android:statusBarColor">@macro/default_bg_color</item>
+        <item name="android:navigationBarColor">@macro/default_bg_color</item>
+    </style>
     <style name="Theme.Chromium.Settings" parent="Base.Theme.Chromium.Settings">
         <item name="android:statusBarColor">@macro/default_bg_color</item>
         <item name="android:windowLightStatusBar">@bool/window_light_status_bar</item>
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/compositor/overlays/strip/StripLayoutHelper.java b/chrome/android/java/src/org/chromium/chrome/browser/compositor/overlays/strip/StripLayoutHelper.java
index 926d769d..358d8d3 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/compositor/overlays/strip/StripLayoutHelper.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/compositor/overlays/strip/StripLayoutHelper.java
@@ -1179,7 +1179,7 @@
 
     private boolean shouldEnableGroupSharing() {
         Profile profile = mTabGroupModelFilter.getTabModel().getProfile();
-        if (profile == null || profile.isOffTheRecord()) {
+        if (profile == null || profile.isOffTheRecord() || mTabGroupSyncService == null) {
             return false;
         }
         CollaborationService collaborationService =
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/crash/MinidumpUploadServiceImpl.java b/chrome/android/java/src/org/chromium/chrome/browser/crash/MinidumpUploadServiceImpl.java
index 68fc1a7..5b486cbc 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/crash/MinidumpUploadServiceImpl.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/crash/MinidumpUploadServiceImpl.java
@@ -7,7 +7,6 @@
 import android.app.job.JobInfo;
 import android.content.ComponentName;
 import android.content.Intent;
-import android.os.Build;
 import android.os.PersistableBundle;
 import android.os.Process;
 
@@ -362,16 +361,14 @@
      * Attempts to upload the specified {@param minidumpFile} directly. If Android doesn't allow a
      * direct upload, then fallback to JobScheduler.
      *
-     * Note that the preferred way to upload minidump is only through JobScheduler, use this
+     * <p>Note that the preferred way to upload minidump is only through JobScheduler, use this
      * function if you need to upload it urgently.
      */
     static void tryUploadCrashDumpNow(File minidumpFile) {
-        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S
-                && !(ApplicationStatus.isInitialized()
-                        && ApplicationStatus.hasVisibleActivities())) {
-            // If we are on API 31+, Android does not allow us to start services from the
-            // background. If we are in the background, then go through the JobScheduler path
-            // instead. See crbug.com/1433529 for more details.
+        if (!(ApplicationStatus.isInitialized() && ApplicationStatus.hasVisibleActivities())) {
+            // Android does not allow us to start services from the background. If we are in the
+            // background, then go through the JobScheduler path instead. See crbug.com/1433529
+            // and crbug.com/407575680 for crashes caused by not doing this.
             scheduleUploadJob();
             return;
         }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/BaseCustomTabRootUiCoordinator.java b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/BaseCustomTabRootUiCoordinator.java
index 8be39acc..461e4853 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/BaseCustomTabRootUiCoordinator.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/BaseCustomTabRootUiCoordinator.java
@@ -66,6 +66,7 @@
 import org.chromium.chrome.browser.lifecycle.ActivityLifecycleDispatcher;
 import org.chromium.chrome.browser.page_info.ChromePageInfo;
 import org.chromium.chrome.browser.page_info.ChromePageInfoHighlight;
+import org.chromium.chrome.browser.pdf.PdfPageIphController;
 import org.chromium.chrome.browser.privacy_sandbox.ActivityTypeMapper;
 import org.chromium.chrome.browser.privacy_sandbox.PrivacySandboxBridge;
 import org.chromium.chrome.browser.privacy_sandbox.PrivacySandboxDialogController;
@@ -127,6 +128,7 @@
     private @Nullable BrandingController mBrandingController;
 
     private @Nullable DesktopSiteSettingsIphController mDesktopSiteSettingsIphController;
+    private @Nullable PdfPageIphController mPdfPageIphController;
     private @Nullable CustomTabHistoryIphController mCustomTabHistoryIphController;
     private @Nullable ReadAloudIphController mReadAloudIphController;
     private @Nullable GoogleBottomBarCoordinator mGoogleBottomBarCoordinator;
@@ -740,6 +742,11 @@
             mDesktopSiteSettingsIphController = null;
         }
 
+        if (mPdfPageIphController != null) {
+            mPdfPageIphController.destroy();
+            mPdfPageIphController = null;
+        }
+
         if (mReadAloudIphController != null) {
             mReadAloudIphController.destroy();
             mReadAloudIphController = null;
@@ -865,6 +872,15 @@
                                                 regularProfile,
                                                 getToolbarManager().getMenuButtonView(),
                                                 mAppMenuCoordinator.getAppMenuHandler());
+                                mPdfPageIphController =
+                                        PdfPageIphController.create(
+                                                mActivity,
+                                                mWindowAndroid,
+                                                mActivityTabProvider,
+                                                profile,
+                                                getToolbarManager().getMenuButtonView(),
+                                                mAppMenuCoordinator.getAppMenuHandler(),
+                                                /* isBrowserApp= */ false);
                             }
 
                             if (!didShowPrompt
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/pdf/PdfPageIphController.java b/chrome/android/java/src/org/chromium/chrome/browser/pdf/PdfPageIphController.java
index 22666028..20ff7ec 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/pdf/PdfPageIphController.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/pdf/PdfPageIphController.java
@@ -4,37 +4,89 @@
 
 package org.chromium.chrome.browser.pdf;
 
+import android.app.Activity;
+import android.content.Context;
+import android.os.Handler;
+import android.os.Looper;
+import android.view.View;
+
 import org.chromium.chrome.R;
 import org.chromium.chrome.browser.ActivityTabProvider;
 import org.chromium.chrome.browser.ActivityTabProvider.ActivityTabTabObserver;
+import org.chromium.chrome.browser.feature_engagement.TrackerFactory;
+import org.chromium.chrome.browser.flags.ChromeFeatureList;
+import org.chromium.chrome.browser.profiles.Profile;
 import org.chromium.chrome.browser.tab.Tab;
+import org.chromium.chrome.browser.ui.appmenu.AppMenuHandler;
+import org.chromium.chrome.browser.user_education.IphCommandBuilder;
+import org.chromium.chrome.browser.user_education.UserEducationHelper;
+import org.chromium.components.feature_engagement.FeatureConstants;
+import org.chromium.components.feature_engagement.Tracker;
 import org.chromium.ui.base.DeviceFormFactor;
 import org.chromium.ui.base.WindowAndroid;
 import org.chromium.url.GURL;
 
-// TODO(crbug.com/405813894): Placeholder class to be implemented.
 /** Controller to manage PDF page in-product-help messages to users. */
 public class PdfPageIphController {
+    private final UserEducationHelper mUserEducationHelper;
     private final WindowAndroid mWindowAndroid;
+    private final AppMenuHandler mAppMenuHandler;
+    private final View mToolbarMenuButton;
     private final ActivityTabProvider mActivityTabProvider;
     private ActivityTabTabObserver mActivityTabTabObserver;
+    private final Context mContext;
+    private final boolean mIsBrowserApp;
 
     /**
      * Creates and initializes the controller. Registers an {@link ActivityTabTabObserver} that
      * attempts to show the pdf download IPH when the download button is not in the omnibox.
      *
+     * @param activity The current activity.
      * @param windowAndroid The window associated with the activity.
      * @param activityTabProvider The provider of the current activity tab.
+     * @param profile The current {@link Profile}.
+     * @param toolbarMenuButton The toolbar menu button to which the IPH will be anchored.
+     * @param appMenuHandler The app menu handler.
+     * @param isBrowserApp Whether the current activity is ChromeTabbedActivity.
      */
     public static PdfPageIphController create(
-            WindowAndroid windowAndroid, ActivityTabProvider activityTabProvider) {
-        return new PdfPageIphController(windowAndroid, activityTabProvider);
+            Activity activity,
+            WindowAndroid windowAndroid,
+            ActivityTabProvider activityTabProvider,
+            Profile profile,
+            View toolbarMenuButton,
+            AppMenuHandler appMenuHandler,
+            boolean isBrowserApp) {
+        if (!PdfUtils.shouldOpenPdfInline(profile.isOffTheRecord())) {
+            return null;
+        }
+        return new PdfPageIphController(
+                windowAndroid,
+                activityTabProvider,
+                profile,
+                toolbarMenuButton,
+                appMenuHandler,
+                new UserEducationHelper(activity, profile, new Handler(Looper.getMainLooper())),
+                isBrowserApp);
     }
 
-    PdfPageIphController(WindowAndroid windowAndroid, ActivityTabProvider activityTabProvider) {
+    PdfPageIphController(
+            WindowAndroid windowAndroid,
+            ActivityTabProvider activityTabProvider,
+            Profile profile,
+            View toolbarMenuButton,
+            AppMenuHandler appMenuHandler,
+            UserEducationHelper userEducationHelper,
+            boolean isBrowserApp) {
         mWindowAndroid = windowAndroid;
+        mToolbarMenuButton = toolbarMenuButton;
+        mContext = mToolbarMenuButton.getContext();
+        mAppMenuHandler = appMenuHandler;
+        mUserEducationHelper = userEducationHelper;
         mActivityTabProvider = activityTabProvider;
-        createActivityTabTabObserver();
+        mIsBrowserApp = isBrowserApp;
+
+        createActivityTabTabObserver(profile);
     }
 
     public void destroy() {
@@ -43,7 +95,7 @@
         }
     }
 
-    private void createActivityTabTabObserver() {
+    private void createActivityTabTabObserver(Profile profile) {
         mActivityTabTabObserver =
                 new ActivityTabTabObserver(mActivityTabProvider) {
                     @Override
@@ -51,21 +103,56 @@
                         if (tab == null || !tab.isNativePage() || !tab.getNativePage().isPdf()) {
                             return;
                         }
-                        showDownloadIph();
+                        showDownloadIph(profile);
                     }
                 };
     }
 
-    private void showDownloadIph() {
-        @SuppressWarnings("UnusedVariable")
+    ActivityTabTabObserver getActiveTabObserverForTesting() {
+        return mActivityTabTabObserver;
+    }
+
+    private void showDownloadIph(Profile profile) {
+        boolean isTablet = DeviceFormFactor.isWindowOnTablet(mWindowAndroid);
+        // Do now show IPH if the download button is shown in the toolbar.
+        if (isTablet
+                && mIsBrowserApp
+                && !ChromeFeatureList.sHideTabletToolbarDownloadButton.isEnabled()) {
+            return;
+        }
+        Tracker tracker = TrackerFactory.getTrackerForProfile(profile);
+        String featureName = FeatureConstants.IPH_PDF_PAGE_DOWNLOAD;
+        if (!tracker.wouldTriggerHelpUi(featureName)) {
+            return;
+        }
+
         int highlightMenuItemId;
-        if (DeviceFormFactor.isWindowOnTablet(mWindowAndroid)) {
+        if (isTablet && mIsBrowserApp) {
             highlightMenuItemId = R.id.download_page_id;
         } else {
             highlightMenuItemId = R.id.offline_page_id;
         }
+        requestShowDownloadIph(featureName, highlightMenuItemId);
+    }
 
-        @SuppressWarnings("UnusedVariable")
-        int textId = R.string.pdf_page_download_iph_text;
+    private void requestShowDownloadIph(String featureName, int highlightMenuItemId) {
+        mUserEducationHelper.requestShowIph(
+                new IphCommandBuilder(
+                                mContext.getResources(),
+                                featureName,
+                                R.string.pdf_page_download_iph_text,
+                                R.string.pdf_page_download_iph_text)
+                        .setAnchorView(mToolbarMenuButton)
+                        .setOnShowCallback(() -> turnOnHighlightForMenuItem(highlightMenuItemId))
+                        .setOnDismissCallback(this::turnOffHighlightForMenuItem)
+                        .build());
+    }
+
+    private void turnOnHighlightForMenuItem(int highlightMenuItemId) {
+        mAppMenuHandler.setMenuHighlight(highlightMenuItemId);
+    }
+
+    private void turnOffHighlightForMenuItem() {
+        mAppMenuHandler.clearMenuHighlight();
     }
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tabbed_mode/TabbedRootUiCoordinator.java b/chrome/android/java/src/org/chromium/chrome/browser/tabbed_mode/TabbedRootUiCoordinator.java
index 0158196..364f123d 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/tabbed_mode/TabbedRootUiCoordinator.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/tabbed_mode/TabbedRootUiCoordinator.java
@@ -114,6 +114,7 @@
 import org.chromium.chrome.browser.offlinepages.indicator.OfflineIndicatorControllerV2;
 import org.chromium.chrome.browser.offlinepages.indicator.OfflineIndicatorInProductHelpController;
 import org.chromium.chrome.browser.omnibox.UrlFocusChangeListener;
+import org.chromium.chrome.browser.pdf.PdfPageIphController;
 import org.chromium.chrome.browser.preferences.ChromePreferenceKeys;
 import org.chromium.chrome.browser.preferences.ChromeSharedPreferences;
 import org.chromium.chrome.browser.privacy_sandbox.ActivityTypeMapper;
@@ -224,6 +225,7 @@
     private ReadAloudIphController mReadAloudIphController;
     private ReadLaterIphController mReadLaterIphController;
     private DesktopSiteSettingsIphController mDesktopSiteSettingsIphController;
+    private PdfPageIphController mPdfPageIphController;
     private RtlGestureNavIphController mRtlGestureNavIphController;
     private WebFeedFollowIntroController mWebFeedFollowIntroController;
     private UrlFocusChangeListener mUrlFocusChangeListener;
@@ -609,6 +611,11 @@
             mDesktopSiteSettingsIphController = null;
         }
 
+        if (mPdfPageIphController != null) {
+            mPdfPageIphController.destroy();
+            mPdfPageIphController = null;
+        }
+
         if (mRtlGestureNavIphController != null) {
             mRtlGestureNavIphController.destroy();
             mRtlGestureNavIphController = null;
@@ -1096,13 +1103,23 @@
                         mAppMenuCoordinator.getAppMenuHandler(),
                         R.id.manage_all_windows_menu_id);
             }
-            DesktopSiteSettingsIphController.create(
-                    mActivity,
-                    mWindowAndroid,
-                    mActivityTabProvider,
-                    profile,
-                    getToolbarManager().getMenuButtonView(),
-                    mAppMenuCoordinator.getAppMenuHandler());
+            mDesktopSiteSettingsIphController =
+                    DesktopSiteSettingsIphController.create(
+                            mActivity,
+                            mWindowAndroid,
+                            mActivityTabProvider,
+                            profile,
+                            getToolbarManager().getMenuButtonView(),
+                            mAppMenuCoordinator.getAppMenuHandler());
+            mPdfPageIphController =
+                    PdfPageIphController.create(
+                            mActivity,
+                            mWindowAndroid,
+                            mActivityTabProvider,
+                            profile,
+                            getToolbarManager().getMenuButtonView(),
+                            mAppMenuCoordinator.getAppMenuHandler(),
+                            /* isBrowserApp= */ true);
         }
         mPromoShownOneshotSupplier.set(didTriggerPromo);
 
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/settings/MainSettingsFragmentTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/settings/MainSettingsFragmentTest.java
index 94f5491..b9598984 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/settings/MainSettingsFragmentTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/settings/MainSettingsFragmentTest.java
@@ -158,6 +158,7 @@
 @RunWith(ChromeJUnit4ClassRunner.class)
 @CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE, "show-autofill-signatures"})
 @DoNotBatch(reason = "Tests cannot run batched because they launch a Settings activity.")
+@DisableFeatures(ChromeFeatureList.DATA_SHARING)
 public class MainSettingsFragmentTest {
     private static final String SEARCH_ENGINE_SHORT_NAME = "Google";
 
diff --git a/chrome/android/junit/BUILD.gn b/chrome/android/junit/BUILD.gn
index f5c633f..b72181cb 100644
--- a/chrome/android/junit/BUILD.gn
+++ b/chrome/android/junit/BUILD.gn
@@ -1284,6 +1284,7 @@
       "src/org/chromium/chrome/browser/night_mode/NightModeUtilsTest.java",
       "src/org/chromium/chrome/browser/payments/AddressEditorTest.java",
       "src/org/chromium/chrome/browser/payments/ContactEditorTest.java",
+      "src/org/chromium/chrome/browser/pdf/PdfPageIphControllerUnitTest.java",
       "src/org/chromium/chrome/browser/privacy/settings/PrivacyPreferencesManagerImplTest.java",
       "src/org/chromium/chrome/browser/quickactionsearchwidget/QuickActionSearchWidgetProviderTest.java",
       "src/org/chromium/chrome/browser/read_later/ReadLaterIphControllerUnitTest.java",
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/pdf/OWNERS b/chrome/android/junit/src/org/chromium/chrome/browser/pdf/OWNERS
new file mode 100644
index 0000000..ef38d56
--- /dev/null
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/pdf/OWNERS
@@ -0,0 +1 @@
+file://chrome/android/java/src/org/chromium/chrome/browser/pdf/OWNERS
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/pdf/PdfPageIphControllerUnitTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/pdf/PdfPageIphControllerUnitTest.java
new file mode 100644
index 0000000..218e8bd
--- /dev/null
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/pdf/PdfPageIphControllerUnitTest.java
@@ -0,0 +1,213 @@
+// Copyright 2025 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.chrome.browser.pdf;
+
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.view.View;
+
+import androidx.test.core.app.ApplicationProvider;
+
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+import org.robolectric.annotation.Config;
+
+import org.chromium.base.test.BaseRobolectricTestRunner;
+import org.chromium.base.test.util.Features.DisableFeatures;
+import org.chromium.chrome.R;
+import org.chromium.chrome.browser.ActivityTabProvider;
+import org.chromium.chrome.browser.ActivityTabProvider.ActivityTabTabObserver;
+import org.chromium.chrome.browser.feature_engagement.TrackerFactory;
+import org.chromium.chrome.browser.flags.ChromeFeatureList;
+import org.chromium.chrome.browser.profiles.Profile;
+import org.chromium.chrome.browser.tab.Tab;
+import org.chromium.chrome.browser.ui.appmenu.AppMenuHandler;
+import org.chromium.chrome.browser.ui.native_page.NativePage;
+import org.chromium.chrome.browser.user_education.IphCommand;
+import org.chromium.chrome.browser.user_education.UserEducationHelper;
+import org.chromium.components.feature_engagement.FeatureConstants;
+import org.chromium.components.feature_engagement.Tracker;
+import org.chromium.ui.base.WindowAndroid;
+import org.chromium.url.JUnitTestGURLs;
+
+import java.lang.ref.WeakReference;
+
+/** Unit tests for {@link PdfPageIphController}. */
+@RunWith(BaseRobolectricTestRunner.class)
+public class PdfPageIphControllerUnitTest {
+    @Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule();
+    @Mock private WindowAndroid mWindowAndroid;
+    @Mock private ActivityTabProvider mActivityTabProvider;
+    @Mock private View mToolbarMenuButton;
+    @Mock private AppMenuHandler mAppMenuHandler;
+    @Mock private UserEducationHelper mUserEducationHelper;
+    @Mock private WeakReference<Context> mWeakReferenceContext;
+    @Mock private Tab mTab;
+    @Mock private NativePage mNativePage;
+    @Mock private Tracker mTracker;
+    @Mock private Profile mProfile;
+
+    @Captor private ArgumentCaptor<IphCommand> mIphCommandCaptor;
+
+    private PdfPageIphController mController;
+
+    @Before
+    public void setUp() {
+        PdfUtils.setShouldOpenPdfInlineForTesting(true);
+        Context context = ApplicationProvider.getApplicationContext();
+        doReturn(mWeakReferenceContext).when(mWindowAndroid).getContext();
+        doReturn(context).when(mWeakReferenceContext).get();
+        doReturn(context).when(mToolbarMenuButton).getContext();
+
+        TrackerFactory.setTrackerForTests(mTracker);
+        when(mTracker.wouldTriggerHelpUi(FeatureConstants.IPH_PDF_PAGE_DOWNLOAD)).thenReturn(true);
+
+        when(mTab.isNativePage()).thenReturn(true);
+        when(mTab.getNativePage()).thenReturn(mNativePage);
+        when(mNativePage.isPdf()).thenReturn(true);
+    }
+
+    @After
+    public void tearDown() {
+        TrackerFactory.setTrackerForTests(null);
+    }
+
+    @Test
+    @Config(qualifiers = "sw320dp")
+    public void testDownloadIph() {
+        initializeController(true);
+        ActivityTabTabObserver activityTabTabObserver =
+                mController.getActiveTabObserverForTesting();
+        activityTabTabObserver.onPageLoadFinished(mTab, JUnitTestGURLs.EXAMPLE_URL);
+        verify(mUserEducationHelper).requestShowIph(mIphCommandCaptor.capture());
+        // The download button in CTA appears in the icon row for phone.
+        verifyIphCommand(true);
+    }
+
+    @Test
+    @Config(qualifiers = "sw320dp")
+    public void testDownloadIph_CCT() {
+        initializeController(false);
+        ActivityTabTabObserver activityTabTabObserver =
+                mController.getActiveTabObserverForTesting();
+        activityTabTabObserver.onPageLoadFinished(mTab, JUnitTestGURLs.EXAMPLE_URL);
+        verify(mUserEducationHelper).requestShowIph(mIphCommandCaptor.capture());
+        // The download button in CCT appears in the icon row regardless of whether it is on a phone
+        // or tablet.
+        verifyIphCommand(true);
+    }
+
+    @Test
+    @Config(qualifiers = "sw320dp")
+    public void testDownloadIph_NullTab() {
+        initializeController(true);
+        ActivityTabTabObserver activityTabTabObserver =
+                mController.getActiveTabObserverForTesting();
+        activityTabTabObserver.onPageLoadFinished(null, JUnitTestGURLs.EXAMPLE_URL);
+        verify(mUserEducationHelper, never()).requestShowIph(mIphCommandCaptor.capture());
+    }
+
+    @Test
+    @Config(qualifiers = "sw320dp")
+    public void testDownloadIph_NotPdf() {
+        initializeController(true);
+        when(mNativePage.isPdf()).thenReturn(false);
+        ActivityTabTabObserver activityTabTabObserver =
+                mController.getActiveTabObserverForTesting();
+        activityTabTabObserver.onPageLoadFinished(mTab, JUnitTestGURLs.EXAMPLE_URL);
+        verify(mUserEducationHelper, never()).requestShowIph(mIphCommandCaptor.capture());
+    }
+
+    @Test
+    @Config(qualifiers = "sw600dp")
+    public void testDownloadIph_Tablet() {
+        initializeController(true);
+        ActivityTabTabObserver activityTabTabObserver =
+                mController.getActiveTabObserverForTesting();
+        activityTabTabObserver.onPageLoadFinished(mTab, JUnitTestGURLs.EXAMPLE_URL);
+        verify(mUserEducationHelper).requestShowIph(mIphCommandCaptor.capture());
+        // The download button in CTA appears in the menu row for tablet.
+        verifyIphCommand(false);
+    }
+
+    @Test
+    @Config(qualifiers = "sw600dp")
+    public void testDownloadIph_Tablet_CCT() {
+        initializeController(false);
+        ActivityTabTabObserver activityTabTabObserver =
+                mController.getActiveTabObserverForTesting();
+        activityTabTabObserver.onPageLoadFinished(mTab, JUnitTestGURLs.EXAMPLE_URL);
+        verify(mUserEducationHelper).requestShowIph(mIphCommandCaptor.capture());
+        // The download button in CCT appears in the icon row regardless of whether it is on a phone
+        // or tablet.
+        verifyIphCommand(true);
+    }
+
+    @Test
+    @DisableFeatures(ChromeFeatureList.HIDE_TABLET_TOOLBAR_DOWNLOAD_BUTTON)
+    @Config(qualifiers = "sw600dp")
+    public void testDownloadIph_Tablet_DownloadButtonInToolbar() {
+        initializeController(true);
+        ActivityTabTabObserver activityTabTabObserver =
+                mController.getActiveTabObserverForTesting();
+        activityTabTabObserver.onPageLoadFinished(mTab, JUnitTestGURLs.EXAMPLE_URL);
+        verify(mUserEducationHelper, never()).requestShowIph(mIphCommandCaptor.capture());
+    }
+
+    @Test
+    @Config(qualifiers = "sw320dp")
+    public void testDownloadIph_TrackerWouldNotTrigger() {
+        initializeController(true);
+        when(mTracker.wouldTriggerHelpUi(FeatureConstants.IPH_PDF_PAGE_DOWNLOAD)).thenReturn(false);
+        ActivityTabTabObserver activityTabTabObserver =
+                mController.getActiveTabObserverForTesting();
+        activityTabTabObserver.onPageLoadFinished(mTab, JUnitTestGURLs.EXAMPLE_URL);
+        verify(mUserEducationHelper, never()).requestShowIph(mIphCommandCaptor.capture());
+    }
+
+    private void initializeController(boolean isBrowserApp) {
+        mController =
+                new PdfPageIphController(
+                        mWindowAndroid,
+                        mActivityTabProvider,
+                        mProfile,
+                        mToolbarMenuButton,
+                        mAppMenuHandler,
+                        mUserEducationHelper,
+                        isBrowserApp);
+    }
+
+    private void verifyIphCommand(boolean isIconRow) {
+        IphCommand command = mIphCommandCaptor.getValue();
+        Assert.assertEquals(
+                "IphCommand feature should match.",
+                FeatureConstants.IPH_PDF_PAGE_DOWNLOAD,
+                command.featureName);
+        Assert.assertEquals(
+                "IphCommand stringId should match.",
+                R.string.pdf_page_download_iph_text,
+                command.stringId);
+
+        command.onShowCallback.run();
+        verify(mAppMenuHandler)
+                .setMenuHighlight(isIconRow ? R.id.offline_page_id : R.id.download_page_id);
+
+        command.onDismissCallback.run();
+        verify(mAppMenuHandler).clearMenuHighlight();
+    }
+}
diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn
index 7d2f5044..2eddbe5 100644
--- a/chrome/browser/BUILD.gn
+++ b/chrome/browser/BUILD.gn
@@ -3745,6 +3745,8 @@
       "enterprise/signals/context_info_fetcher.h",
       "enterprise/signals/device_info_fetcher.cc",
       "enterprise/signals/device_info_fetcher.h",
+      "enterprise/signals/profile_signals_collector.cc",
+      "enterprise/signals/profile_signals_collector.h",
       "first_run/first_run.cc",
       "first_run/first_run.h",
       "first_run/first_run_dialog.h",
diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc
index 3d9837a8..72f6d9c 100644
--- a/chrome/browser/about_flags.cc
+++ b/chrome/browser/about_flags.cc
@@ -756,6 +756,17 @@
          std::size(kOptimizationGuidePersonalizedFetchingAllowPageInsights),
          nullptr}};
 
+const FeatureEntry::FeatureParam kFeedHeaderRemovalParam1 = {
+    feed::kFeedHeaderRemovalTreatmentParam,
+    feed::kFeedHeaderRemovalTreatmentValue1};
+const FeatureEntry::FeatureParam kFeedHeaderRemovalParam2 = {
+    feed::kFeedHeaderRemovalTreatmentParam,
+    feed::kFeedHeaderRemovalTreatmentValue2};
+const FeatureEntry::FeatureVariation kFeedHeaderRemovalVariations[] = {
+    {"1", &kFeedHeaderRemovalParam1, 1, nullptr},
+    {"2", &kFeedHeaderRemovalParam2, 1, nullptr},
+};
+
 #endif  // BUILDFLAG(IS_ANDROID)
 
 #if !BUILDFLAG(IS_CHROMEOS)
@@ -4746,6 +4757,9 @@
      flag_descriptions::kAshEnableUnifiedDesktopName,
      flag_descriptions::kAshEnableUnifiedDesktopDescription, kOsCrOS,
      SINGLE_VALUE_TYPE(switches::kEnableUnifiedDesktop)},
+    {"disable-system-blur", flag_descriptions::kDisableSystemBlur,
+     flag_descriptions::kDisableSystemBlurDescription, kOsCrOS,
+     FEATURE_VALUE_TYPE(chromeos::features::kDisableSystemBlur)},
     {"rounded-windows", flag_descriptions::kRoundedWindows,
      flag_descriptions::kRoundedWindowsDescription, kOsCrOS,
      FEATURE_WITH_PARAMS_VALUE_TYPE(chromeos::features::kRoundedWindows,
@@ -5809,6 +5823,11 @@
     {"refresh-feed-on-start", flag_descriptions::kRefreshFeedOnRestartName,
      flag_descriptions::kRefreshFeedOnRestartDescription, kOsAndroid,
      FEATURE_VALUE_TYPE(feed::kRefreshFeedOnRestart)},
+    {"feed-header-removal", flag_descriptions::kFeedHeaderRemovalName,
+     flag_descriptions::kFeedHeaderRemovalDescription, kOsAndroid,
+     FEATURE_WITH_PARAMS_VALUE_TYPE(feed::kFeedHeaderRemoval,
+                                    kFeedHeaderRemovalVariations,
+                                    "FeedHeaderRemoval")},
 #endif  // BUILDFLAG(IS_ANDROID)
     {"enable-force-dark", flag_descriptions::kAutoWebContentsDarkModeName,
      flag_descriptions::kAutoWebContentsDarkModeDescription, kOsAll,
diff --git a/chrome/browser/accessibility/tree_fixing/BUILD.gn b/chrome/browser/accessibility/tree_fixing/BUILD.gn
index 3b81e16..1ef531d8 100644
--- a/chrome/browser/accessibility/tree_fixing/BUILD.gn
+++ b/chrome/browser/accessibility/tree_fixing/BUILD.gn
@@ -53,6 +53,8 @@
   sources = [
     "internal/ax_tree_fixing_screen_ai_service.cc",
     "internal/ax_tree_fixing_screen_ai_service.h",
+    "internal/ax_tree_fixing_screenshotter.cc",
+    "internal/ax_tree_fixing_screenshotter.h",
   ]
 
   public_deps = [ "//ui/accessibility:ax_base" ]
@@ -61,6 +63,9 @@
     "//base",
     "//chrome/browser/profiles:profile",
     "//chrome/browser/screen_ai:screen_ai_service_router_factory",
+    "//components/paint_preview/browser",
+    "//components/paint_preview/common",
+    "//components/paint_preview/public",
     "//content/public/browser:browser",
     "//services/screen_ai/public/mojom:mojom",
   ]
diff --git a/chrome/browser/accessibility/tree_fixing/internal/DEPS b/chrome/browser/accessibility/tree_fixing/internal/DEPS
new file mode 100644
index 0000000..20c72e0b
--- /dev/null
+++ b/chrome/browser/accessibility/tree_fixing/internal/DEPS
@@ -0,0 +1,6 @@
+specific_include_rules = {
+  'ax_tree_fixing_screenshotter.*': [
+    "+components/paint_preview/common/recording_map.h",
+    "+components/paint_preview/public/paint_preview_compositor_service.h",
+  ]
+}
\ No newline at end of file
diff --git a/chrome/browser/accessibility/tree_fixing/internal/ax_tree_fixing_screenshotter.cc b/chrome/browser/accessibility/tree_fixing/internal/ax_tree_fixing_screenshotter.cc
new file mode 100644
index 0000000..d590863
--- /dev/null
+++ b/chrome/browser/accessibility/tree_fixing/internal/ax_tree_fixing_screenshotter.cc
@@ -0,0 +1,120 @@
+// Copyright 2025 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/accessibility/tree_fixing/internal/ax_tree_fixing_screenshotter.h"
+
+#include <string>
+
+#include "base/base_paths.h"
+#include "base/files/file_path.h"
+#include "base/functional/callback_helpers.h"
+#include "base/path_service.h"
+#include "base/strings/stringprintf.h"
+#include "base/task/task_traits.h"
+#include "base/task/thread_pool.h"
+#include "components/paint_preview/browser/compositor_utils.h"
+#include "components/paint_preview/browser/paint_preview_base_service.h"
+#include "components/paint_preview/common/recording_map.h"
+#include "third_party/skia/include/core/SkStream.h"
+#include "third_party/skia/include/encode/SkPngEncoder.h"
+#include "ui/gfx/geometry/rect.h"
+
+constexpr size_t kMaxPerCaptureSizeBytes = 50 * 1000L * 1000L;  // 50 MB.
+
+AXTreeFixingScreenshotter::AXTreeFixingScreenshotter()
+    : paint_preview::PaintPreviewBaseService(
+          /*file_mixin=*/nullptr,  // in-memory captures
+          /*policy=*/nullptr,      // all content is deemed amenable
+          /*is_off_the_record=*/false),
+      compositor_service_(nullptr, base::OnTaskRunnerDeleter(nullptr)),
+      compositor_client_(nullptr, base::OnTaskRunnerDeleter(nullptr)) {
+  compositor_service_ = paint_preview::StartCompositorService(base::BindOnce(
+      &AXTreeFixingScreenshotter::OnCompositorServiceDisconnected,
+      weak_ptr_factory_.GetWeakPtr()));
+  CHECK(compositor_service_);
+}
+
+AXTreeFixingScreenshotter::~AXTreeFixingScreenshotter() = default;
+
+void AXTreeFixingScreenshotter::RequestScreenshot(
+    const raw_ptr<content::WebContents> web_contents) {
+  if (!web_contents) {
+    return;
+  }
+
+  CaptureParams capture_params;
+  capture_params.web_contents = web_contents;
+  capture_params.persistence =
+      paint_preview::RecordingPersistence::kMemoryBuffer;
+  capture_params.max_per_capture_size = kMaxPerCaptureSizeBytes;
+  CapturePaintPreview(
+      capture_params,
+      base::BindOnce(&AXTreeFixingScreenshotter::OnScreenshotCaptured,
+                     weak_ptr_factory_.GetWeakPtr()));
+}
+
+void AXTreeFixingScreenshotter::OnCompositorServiceDisconnected() {
+  compositor_client_.reset();
+  compositor_service_.reset();
+}
+
+void AXTreeFixingScreenshotter::OnScreenshotCaptured(
+    paint_preview::PaintPreviewBaseService::CaptureStatus status,
+    std::unique_ptr<paint_preview::CaptureResult> result) {
+  if (status != PaintPreviewBaseService::CaptureStatus::kOk ||
+      !result->capture_success) {
+    return;
+  }
+  if (!compositor_client_) {
+    compositor_client_ = compositor_service_->CreateCompositor(
+        base::BindOnce(&AXTreeFixingScreenshotter::SendCompositeRequest,
+                       weak_ptr_factory_.GetWeakPtr(), std::move(result)));
+    return;
+  }
+  SendCompositeRequest(std::move(result));
+}
+
+void AXTreeFixingScreenshotter::SendCompositeRequest(
+    std::unique_ptr<paint_preview::CaptureResult> result) {
+  paint_preview::mojom::PaintPreviewBeginCompositeRequestPtr request =
+      paint_preview::mojom::PaintPreviewBeginCompositeRequest::New();
+  std::pair<paint_preview::RecordingMap, paint_preview::PaintPreviewProto>
+      map_and_proto =
+          paint_preview::RecordingMapFromCaptureResult(std::move(*result));
+  request->recording_map = std::move(map_and_proto.first);
+  request->preview = mojo_base::ProtoWrapper(std::move(map_and_proto.second));
+  compositor_client_->BeginMainFrameComposite(
+      std::move(request),
+      base::BindOnce(&AXTreeFixingScreenshotter::OnCompositeFinished,
+                     weak_ptr_factory_.GetWeakPtr()));
+}
+
+void AXTreeFixingScreenshotter::OnCompositeFinished(
+    paint_preview::mojom::PaintPreviewCompositor::BeginCompositeStatus status,
+    paint_preview::mojom::PaintPreviewBeginCompositeResponsePtr response) {
+  if (status != paint_preview::mojom::PaintPreviewCompositor::
+                    BeginCompositeStatus::kSuccess &&
+      status != paint_preview::mojom::PaintPreviewCompositor::
+                    BeginCompositeStatus::kPartialSuccess) {
+    return;
+  }
+
+  // Pass an empty `gfx::Rect` allows us to get a bitmap for the full page.
+  compositor_client_->BitmapForMainFrame(
+      gfx::Rect(), /*scale_factor=*/1.0,
+      base::BindOnce(&AXTreeFixingScreenshotter::OnBitmapReceived,
+                     weak_ptr_factory_.GetWeakPtr()));
+}
+
+void AXTreeFixingScreenshotter::OnBitmapReceived(
+    paint_preview::mojom::PaintPreviewCompositor::BitmapStatus status,
+    const SkBitmap& bitmap) {
+  if (status != paint_preview::mojom::PaintPreviewCompositor::BitmapStatus::
+                    kSuccess ||
+      bitmap.empty()) {
+    return;
+  }
+
+  // TODO: Save bitmap.
+}
diff --git a/chrome/browser/accessibility/tree_fixing/internal/ax_tree_fixing_screenshotter.h b/chrome/browser/accessibility/tree_fixing/internal/ax_tree_fixing_screenshotter.h
new file mode 100644
index 0000000..c0cdf4a
--- /dev/null
+++ b/chrome/browser/accessibility/tree_fixing/internal/ax_tree_fixing_screenshotter.h
@@ -0,0 +1,75 @@
+// Copyright 2025 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_ACCESSIBILITY_TREE_FIXING_INTERNAL_AX_TREE_FIXING_SCREENSHOTTER_H_
+#define CHROME_BROWSER_ACCESSIBILITY_TREE_FIXING_INTERNAL_AX_TREE_FIXING_SCREENSHOTTER_H_
+
+#include <memory>
+
+#include "base/memory/raw_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "components/paint_preview/browser/paint_preview_base_service.h"
+#include "components/paint_preview/public/paint_preview_compositor_service.h"
+#include "content/public/browser/web_contents.h"
+
+// Used to capture a full-page screenshot of a WebContents.
+class AXTreeFixingScreenshotter
+    : public paint_preview::PaintPreviewBaseService {
+ public:
+  AXTreeFixingScreenshotter();
+  AXTreeFixingScreenshotter(const AXTreeFixingScreenshotter&) = delete;
+  AXTreeFixingScreenshotter& operator=(const AXTreeFixingScreenshotter&) =
+      delete;
+  ~AXTreeFixingScreenshotter() override;
+
+  // Initiates the process of capturing a paint preview screenshot for the
+  // given |web_contents|. On completion, OnScreenshotCaptured() will be
+  // invoked. Does nothing if |web_contents| is null.
+  void RequestScreenshot(const raw_ptr<content::WebContents> web_contents);
+
+ private:
+  // Called when the connection to the Paint Preview Compositor Service is lost.
+  // Resets the compositor client and service pointers to ensure they are not
+  // used in a disconnected state.
+  void OnCompositorServiceDisconnected();
+
+  // Callback invoked when the capture initiated by RequestScreenshot()
+  // completes. If the capture was successful, it proceeds to the compositing
+  // stage by calling SendCompositeRequest(). If this is the first successful
+  // capture, it will also establish the connection to the compositor service
+  // via CreateCompositor.
+  void OnScreenshotCaptured(
+      paint_preview::PaintPreviewBaseService::CaptureStatus status,
+      std::unique_ptr<paint_preview::CaptureResult> result);
+
+  // Sends the captured paint preview data (|result|) to the compositor service
+  // to begin compositing into a bitmap.
+  // OnCompositeFinished() will be called upon completion of the compositing.
+  void SendCompositeRequest(
+      std::unique_ptr<paint_preview::CaptureResult> result);
+
+  // Callback invoked when the compositor service finishes the request initiated
+  // by SendCompositeRequest(). If compositing was successful (or partially
+  // successful), it requests the final bitmap of the main frame.
+  void OnCompositeFinished(
+      paint_preview::mojom::PaintPreviewCompositor::BeginCompositeStatus status,
+      paint_preview::mojom::PaintPreviewBeginCompositeResponsePtr response);
+
+  // Callback invoked when the compositor service provides the requested bitmap.
+  // This is the final step in the screenshot pipeline.
+  void OnBitmapReceived(
+      paint_preview::mojom::PaintPreviewCompositor::BitmapStatus status,
+      const SkBitmap& bitmap);
+
+  std::unique_ptr<paint_preview::PaintPreviewCompositorService,
+                  base::OnTaskRunnerDeleter>
+      compositor_service_;
+  std::unique_ptr<paint_preview::PaintPreviewCompositorClient,
+                  base::OnTaskRunnerDeleter>
+      compositor_client_;
+
+  base::WeakPtrFactory<AXTreeFixingScreenshotter> weak_ptr_factory_{this};
+};
+
+#endif  // CHROME_BROWSER_ACCESSIBILITY_TREE_FIXING_INTERNAL_AX_TREE_FIXING_SCREENSHOTTER_H_
diff --git a/chrome/browser/ai/ai_language_model.cc b/chrome/browser/ai/ai_language_model.cc
index c00f524..1eb246cc 100644
--- a/chrome/browser/ai/ai_language_model.cc
+++ b/chrome/browser/ai/ai_language_model.cc
@@ -410,6 +410,7 @@
 void AILanguageModel::PromptGetInputSizeCompletion(
     mojo::RemoteSetElementId responder_id,
     Context::ContextItem current_item,
+    const std::optional<std::string>& response_json_schema,
     std::optional<uint32_t> result) {
   if (!session_) {
     // If the session is destroyed before this callback is invoked, we should
@@ -447,8 +448,8 @@
   MultimodalMessage request = context_->MakeRequest();
   AddCurrentRequest(request, current_item);
   session_->SetInput(std::move(request));
-  session_->ExecuteModel(
-      PromptApiRequest(),
+  session_->ExecuteModelWithResponseJsonSchema(
+      PromptApiRequest(), response_json_schema,
       base::BindRepeating(&AILanguageModel::ModelExecutionCallback,
                           weak_ptr_factory_.GetWeakPtr(),
                           std::move(current_item), responder_id));
@@ -514,6 +515,7 @@
 
 void AILanguageModel::Prompt(
     std::vector<blink::mojom::AILanguageModelPromptPtr> prompts,
+    const std::optional<std::string>& response_json_schema,
     mojo::PendingRemote<blink::mojom::ModelStreamingResponder>
         pending_responder) {
   if (!session_) {
@@ -562,7 +564,7 @@
       request.read(),
       base::BindOnce(&AILanguageModel::PromptGetInputSizeCompletion,
                      weak_ptr_factory_.GetWeakPtr(), responder_id,
-                     std::move(item)));
+                     std::move(item), response_json_schema));
 }
 
 void AILanguageModel::Fork(
diff --git a/chrome/browser/ai/ai_language_model.h b/chrome/browser/ai/ai_language_model.h
index 814b972..a1c2eca9 100644
--- a/chrome/browser/ai/ai_language_model.h
+++ b/chrome/browser/ai/ai_language_model.h
@@ -158,6 +158,7 @@
 
   // `blink::mojom::AILanguageModel` implementation.
   void Prompt(std::vector<blink::mojom::AILanguageModelPromptPtr> prompts,
+              const std::optional<std::string>& response_json_schema,
               mojo::PendingRemote<blink::mojom::ModelStreamingResponder>
                   pending_responder) override;
   void Fork(
@@ -179,9 +180,11 @@
   mojo::PendingRemote<blink::mojom::AILanguageModel> TakePendingRemote();
 
  private:
-  void PromptGetInputSizeCompletion(mojo::RemoteSetElementId responder_id,
-                                    Context::ContextItem current_item,
-                                    std::optional<uint32_t> result);
+  void PromptGetInputSizeCompletion(
+      mojo::RemoteSetElementId responder_id,
+      Context::ContextItem current_item,
+      const std::optional<std::string>& response_json_schema,
+      std::optional<uint32_t> result);
   void ModelExecutionCallback(
       const Context::ContextItem& current_item,
       mojo::RemoteSetElementId responder_id,
diff --git a/chrome/browser/ai/ai_language_model_unittest.cc b/chrome/browser/ai/ai_language_model_unittest.cc
index d6c15c8..b058416 100644
--- a/chrome/browser/ai/ai_language_model_unittest.cc
+++ b/chrome/browser/ai/ai_language_model_unittest.cc
@@ -335,9 +335,10 @@
                   }
                 });
 
-            EXPECT_CALL(*session, ExecuteModel(_, _))
+            EXPECT_CALL(*session, ExecuteModelWithResponseJsonSchema(_, _, _))
                 .WillOnce(
                     [&](const google::protobuf::MessageLite& request_metadata,
+                        const std::optional<std::string>& response_json_schema,
                         optimization_guide::
                             OptimizationGuideModelExecutionResultStreamingCallback
                                 callback) {
@@ -361,9 +362,10 @@
                               options.expected_cloned_context +
                                   options.expected_prompt);
                 });
-            EXPECT_CALL(*session, ExecuteModel(_, _))
+            EXPECT_CALL(*session, ExecuteModelWithResponseJsonSchema(_, _, _))
                 .WillOnce(
                     [&](const google::protobuf::MessageLite& request_metadata,
+                        const std::optional<std::string>& response_json_schema,
                         optimization_guide::
                             OptimizationGuideModelExecutionResultStreamingCallback
                                 callback) {
@@ -511,7 +513,8 @@
                       });
 
               // The model should not be executed.
-              EXPECT_CALL(*session, ExecuteModel(_, _)).Times(0);
+              EXPECT_CALL(*session, ExecuteModelWithResponseJsonSchema(_, _, _))
+                  .Times(0);
               return session;
             });
 
@@ -586,10 +589,11 @@
                                                    : "U: A\nM: OK\nU: B\nM: ");
               });
 
-          EXPECT_CALL(*session, ExecuteModel(_, _))
+          EXPECT_CALL(*session, ExecuteModelWithResponseJsonSchema(_, _, _))
               .Times(2)
               .WillRepeatedly(
                   [&](const google::protobuf::MessageLite& request_metadata,
+                      const std::optional<std::string>& response_json_schema,
                       optimization_guide::
                           OptimizationGuideModelExecutionResultStreamingCallback
                               callback) {
@@ -631,10 +635,10 @@
               responder_run_loop_2.Quit();
             }));
 
-    mock_session->Prompt(MakeInput("A"),
+    mock_session->Prompt(MakeInput("A"), /*response_json_schema=*/std::nullopt,
                          mock_responder_1.BindNewPipeAndPassRemote());
     responder_run_loop_1.Run();
-    mock_session->Prompt(MakeInput("B"),
+    mock_session->Prompt(MakeInput("B"), /*response_json_schema=*/std::nullopt,
                          mock_responder_2.BindNewPipeAndPassRemote());
     responder_run_loop_2.Run();
   }
@@ -716,6 +720,7 @@
             }));
 
     mock_session->Prompt(MakeInput(prompt),
+                         /*response_json_schema=*/std::nullopt,
                          mock_responder.BindNewPipeAndPassRemote());
     responder_run_loop.Run();
   }
@@ -861,6 +866,7 @@
          AITestUtils::MockModelStreamingResponder& mock_responder) {
         mock_session->Destroy();
         mock_session->Prompt(MakeInput(kTestPrompt),
+                             /*response_json_schema=*/std::nullopt,
                              mock_responder.BindNewPipeAndPassRemote());
       }));
 }
@@ -872,6 +878,7 @@
       [](mojo::Remote<blink::mojom::AILanguageModel> mock_session,
          AITestUtils::MockModelStreamingResponder& mock_responder) {
         mock_session->Prompt(MakeInput(kTestPrompt),
+                             /*response_json_schema=*/std::nullopt,
                              mock_responder.BindNewPipeAndPassRemote());
         mock_session->Destroy();
       }));
@@ -950,9 +957,10 @@
                           "U: <audio>\n"
                           "M: ");
             });
-        EXPECT_CALL(*session, ExecuteModel(_, _))
+        EXPECT_CALL(*session, ExecuteModelWithResponseJsonSchema(_, _, _))
             .WillOnce(
                 [&](const google::protobuf::MessageLite& request_metadata,
+                    const std::optional<std::string>& response_json_schema,
                     optimization_guide::
                         OptimizationGuideModelExecutionResultStreamingCallback
                             callback) {
@@ -981,7 +989,7 @@
   input.push_back(blink::mojom::AILanguageModelPrompt::New(
       Role::kUser,
       blink::mojom::AILanguageModelPromptContent::NewAudio(CreateTestAudio())));
-  mock_session->Prompt(std::move(input),
+  mock_session->Prompt(std::move(input), /*response_json_schema=*/std::nullopt,
                        mock_responder.BindNewPipeAndPassRemote());
   run_loop.Run();
 }
@@ -1233,7 +1241,7 @@
   input.push_back(blink::mojom::AILanguageModelPrompt::New(
       Role::kUser, blink::mojom::AILanguageModelPromptContent::NewBitmap(
                        CreateTestBitmap(10, 10))));
-  mock_session->Prompt(std::move(input),
+  mock_session->Prompt(std::move(input), /*response_json_schema=*/std::nullopt,
                        mock_responder.BindNewPipeAndPassRemote());
   run_loop.Run();
 }
diff --git a/chrome/browser/ash/accessibility/spoken_feedback_browsertest.cc b/chrome/browser/ash/accessibility/spoken_feedback_browsertest.cc
index 05aad8c..46a0d9d 100644
--- a/chrome/browser/ash/accessibility/spoken_feedback_browsertest.cc
+++ b/chrome/browser/ash/accessibility/spoken_feedback_browsertest.cc
@@ -1162,8 +1162,7 @@
   sm_.Replay();
 }
 
-// TODO(crbug.com/407459387): Fix and re-enable this test.
-IN_PROC_BROWSER_TEST_P(SpokenFeedbackTest, DISABLED_NavigateChromeVoxMenu) {
+IN_PROC_BROWSER_TEST_P(SpokenFeedbackTest, NavigateChromeVoxMenu) {
   EnableChromeVox();
   sm_.Call([this]() { SendKeyPressWithSearch(ui::VKEY_OEM_PERIOD); });
   sm_.ExpectSpeech("Search the menus");
@@ -2286,8 +2285,7 @@
 }
 
 // Tests basic behavior of the tutorial when signed in.
-// TODO(crbug.com/407459387): Fix and re-enable this test.
-IN_PROC_BROWSER_TEST_P(SpokenFeedbackTest, DISABLED_Tutorial) {
+IN_PROC_BROWSER_TEST_P(SpokenFeedbackTest, Tutorial) {
   EnableChromeVox();
   sm_.Call([this]() {
     NavigateToUrl(GURL(R"(data:text/html;charset=utf-8,
diff --git a/chrome/browser/ash/system_web_apps/system_web_app_manager_unittest.cc b/chrome/browser/ash/system_web_apps/system_web_app_manager_unittest.cc
index f124b38..c7ff7de 100644
--- a/chrome/browser/ash/system_web_apps/system_web_app_manager_unittest.cc
+++ b/chrome/browser/ash/system_web_apps/system_web_app_manager_unittest.cc
@@ -255,8 +255,10 @@
 
   EXPECT_THAT(externally_managed_app_manager().install_requests(),
               ElementsAre(options));
-  EXPECT_THAT(externally_managed_app_manager().uninstall_requests(),
-              ElementsAre(AppUrl2()));
+  EXPECT_FALSE(provider()
+                   .registrar_unsafe()
+                   .LookUpAppIdByInstallUrl(AppUrl2())
+                   .has_value());
 }
 
 TEST_F(SystemWebAppManagerTest, AlwaysUpdate) {
diff --git a/chrome/browser/chrome_web_platform_security_metrics_browsertest.cc b/chrome/browser/chrome_web_platform_security_metrics_browsertest.cc
index ff4df49..438de83 100644
--- a/chrome/browser/chrome_web_platform_security_metrics_browsertest.cc
+++ b/chrome/browser/chrome_web_platform_security_metrics_browsertest.cc
@@ -146,9 +146,6 @@
         // SharedArrayBuffer is needed for these tests.
         features::kSharedArrayBuffer,
         // Some PNA worker feature relies on this.
-        // TODO(crbug.com/40263073): Remove this once PNA for workers
-        // metric logging doesn't rely on kPlzDedicatedWorker
-        blink::features::kPlzDedicatedWorker,
         blink::features::kPartitionedPopins,
     };
   }
diff --git a/chrome/browser/data_sharing/DEPS b/chrome/browser/data_sharing/DEPS
index e38944b8..6aa1a53 100644
--- a/chrome/browser/data_sharing/DEPS
+++ b/chrome/browser/data_sharing/DEPS
@@ -1,4 +1,5 @@
 include_rules = [
+  "+components/collaboration/internal/metrics.h",
   "+components/data_sharing/public/data_sharing_sdk_delegate.h",
   # Minimize dependencies on internal code, but allow service construction.
   "+components/data_sharing/internal/data_sharing_service_impl.h",
diff --git a/chrome/browser/data_sharing/android/java/src/org/chromium/chrome/browser/data_sharing/DataSharingServiceFactoryTest.java b/chrome/browser/data_sharing/android/java/src/org/chromium/chrome/browser/data_sharing/DataSharingServiceFactoryTest.java
index 7a2c504..d49ac240 100644
--- a/chrome/browser/data_sharing/android/java/src/org/chromium/chrome/browser/data_sharing/DataSharingServiceFactoryTest.java
+++ b/chrome/browser/data_sharing/android/java/src/org/chromium/chrome/browser/data_sharing/DataSharingServiceFactoryTest.java
@@ -43,22 +43,20 @@
     @Test
     @MediumTest
     public void testSettingTestFactory() throws TimeoutException {
-        DataSharingService testService = new TestDataSharingService();
-
-        DataSharingServiceFactory.setForTesting(testService);
+        ThreadUtils.runOnUiThreadBlocking(
+                () -> {
+                    DataSharingService testService = new TestDataSharingService();
+                    DataSharingServiceFactory.setForTesting(testService);
+                });
         LibraryLoader.getInstance().ensureInitialized();
         mActivityTestRule.startMainActivityOnBlankPage();
 
         ThreadUtils.runOnUiThreadBlocking(
-                new Runnable() {
-                    @Override
-                    public void run() {
-                        DataSharingService dataSharingService =
-                                DataSharingServiceFactory.getForProfile(
-                                        ProfileManager.getLastUsedRegularProfile());
-                        Assert.assertTrue(dataSharingService.isEmptyService());
-                        Assert.assertEquals(dataSharingService, testService);
-                    }
+                () -> {
+                    DataSharingService dataSharingService =
+                            DataSharingServiceFactory.getForProfile(
+                                    ProfileManager.getLastUsedRegularProfile());
+                    Assert.assertTrue(dataSharingService.isEmptyService());
                 });
     }
 
diff --git a/chrome/browser/data_sharing/data_sharing_navigation_throttle.cc b/chrome/browser/data_sharing/data_sharing_navigation_throttle.cc
index b902155..59d66ca0 100644
--- a/chrome/browser/data_sharing/data_sharing_navigation_throttle.cc
+++ b/chrome/browser/data_sharing/data_sharing_navigation_throttle.cc
@@ -7,6 +7,7 @@
 #include "chrome/browser/data_sharing/data_sharing_navigation_utils.h"
 #include "chrome/browser/data_sharing/data_sharing_service_factory.h"
 #include "chrome/browser/profiles/profile.h"
+#include "components/collaboration/internal/metrics.h"
 #include "components/data_sharing/public/features.h"
 #include "content/public/browser/navigation_handle.h"
 #include "content/public/browser/web_contents.h"
@@ -98,6 +99,9 @@
   const GURL& url = navigation_handle()->GetURL();
   if (data_sharing_service &&
       data_sharing_service->ShouldInterceptNavigationForShareURL(url)) {
+    collaboration::metrics::RecordJoinPageTransitionType(
+        data_sharing_service->GetLogger(),
+        navigation_handle()->GetPageTransition());
     if (ShouldHandleShareURLNavigation(navigation_handle())) {
       data_sharing_service->HandleShareURLNavigationIntercepted(
           url, /* context = */ nullptr);
diff --git a/chrome/browser/download/chrome_download_manager_delegate.cc b/chrome/browser/download/chrome_download_manager_delegate.cc
index f960dec..177076d 100644
--- a/chrome/browser/download/chrome_download_manager_delegate.cc
+++ b/chrome/browser/download/chrome_download_manager_delegate.cc
@@ -43,6 +43,7 @@
 #include "chrome/browser/download/download_request_limiter.h"
 #include "chrome/browser/download/download_stats.h"
 #include "chrome/browser/download/download_target_determiner.h"
+#include "chrome/browser/download/download_ui_safe_browsing_util.h"
 #include "chrome/browser/download/insecure_download_blocking.h"
 #include "chrome/browser/download/save_package_file_picker.h"
 #include "chrome/browser/enterprise/connectors/common.h"
@@ -183,12 +184,20 @@
 
 namespace {
 
-#if !BUILDFLAG(IS_ANDROID)
 // How long an ephemeral warning lasts before being automatically canceled (if
 // there is no user interaction).
 constexpr base::TimeDelta kEphemeralWarningLifetimeBeforeCancel =
     base::Hours(1);
+
+bool IsEphemeralWarningCancellationEnabled() {
+#if BUILDFLAG(IS_ANDROID)
+  return ShouldShowSafeBrowsingAndroidDownloadWarnings();
 #else
+  return download::IsDownloadBubbleEnabled();
+#endif
+}
+
+#if BUILDFLAG(IS_ANDROID)
 const char kPdfDirName[] = "pdfs";
 #endif
 
@@ -480,7 +489,6 @@
 }
 #endif  // BUILDFLAG(SAFE_BROWSING_DOWNLOAD_PROTECTION)
 
-#if !BUILDFLAG(IS_ANDROID)
 // Events related to ephemeral warning cancellation.
 // These values are persisted to logs. Entries should not be renumbered and
 // numeric values should never be reused.
@@ -503,7 +511,6 @@
   base::UmaHistogramEnumeration("SBClientDownload.CancelEphemeralWarning",
                                 event);
 }
-#endif  // !BUILDFLAG(IS_ANDROID)
 
 void OnCheckDownloadAllowedFailed(
     content::CheckDownloadAllowedCallback check_download_allowed_cb) {
@@ -2204,15 +2211,14 @@
     download::GetDownloadTaskRunner()->PostTask(
         FROM_HERE, base::BindOnce([]() { base::DeleteFile(GetTempPdfDir()); }));
   }
-#else
-  CancelAllEphemeralWarnings();
 #endif
+
+  CancelAllEphemeralWarnings();
 }
 
-#if !BUILDFLAG(IS_ANDROID)
 void ChromeDownloadManagerDelegate::ScheduleCancelForEphemeralWarning(
     const std::string& guid) {
-  if (!download::IsDownloadBubbleEnabled()) {
+  if (!IsEphemeralWarningCancellationEnabled()) {
     return;
   }
   LogCancelEphemeralWarningEvent(
@@ -2226,6 +2232,7 @@
 
 void ChromeDownloadManagerDelegate::CancelForEphemeralWarning(
     const std::string& guid) {
+  CHECK(IsEphemeralWarningCancellationEnabled());
   LogCancelEphemeralWarningEvent(
       CancelEphemeralWarningEvent::kCancellationTriggered);
   download::DownloadItem* download = download_manager_->GetDownloadByGuid(guid);
@@ -2250,7 +2257,7 @@
 }
 
 void ChromeDownloadManagerDelegate::CancelAllEphemeralWarnings() {
-  if (!download::IsDownloadBubbleEnabled()) {
+  if (!IsEphemeralWarningCancellationEnabled()) {
     return;
   }
   content::DownloadManager::DownloadVector downloads;
@@ -2263,4 +2270,3 @@
     }
   }
 }
-#endif  // !BUILDFLAG(IS_ANDROID)
diff --git a/chrome/browser/download/chrome_download_manager_delegate.h b/chrome/browser/download/chrome_download_manager_delegate.h
index 41db535e..b3a0d69 100644
--- a/chrome/browser/download/chrome_download_manager_delegate.h
+++ b/chrome/browser/download/chrome_download_manager_delegate.h
@@ -206,12 +206,15 @@
   bool ShouldBlockFile(download::DownloadItem* item,
                        download::DownloadDangerType danger_type) const;
 
-#if !BUILDFLAG(IS_ANDROID)
-  // Schedules the ephemeral warning download to be canceled. It will only be
+  // Schedules an ephemeral warning download to be canceled. It will only be
   // canceled if it continues to be an ephemeral warning that hasn't been acted
-  // on when the scheduled time arrives.
+  // on when the scheduled time arrives. This should be scheduled after the user
+  // sees the warning in the UI, i.e. should not be scheduled before the user
+  // has a chance to see the warning.
+  // Note: Any scheduled cancellations will be abandoned at browser shutdown,
+  // but the cancellation should occur when the browser next starts up, in
+  // CancelAllEphemeralWarnings().
   void ScheduleCancelForEphemeralWarning(const std::string& guid);
-#endif
 
   // Returns true if |path| should open in the browser.
   virtual bool IsOpenInBrowserPreferredForFile(const base::FilePath& path);
@@ -346,9 +349,6 @@
   // multiple downloads are associated with the same file path.
   bool IsMostRecentDownloadItemAtFilePath(download::DownloadItem* download);
 
-  // TODO(crbug.com/397407934): Enable ephemeral warning cancellation on
-  // Android.
-#if !BUILDFLAG(IS_ANDROID)
   // Cancels a download if it's still an ephemeral warning (and has not been
   // acted on by the user).
   void CancelForEphemeralWarning(const std::string& guid);
@@ -356,7 +356,6 @@
   // that were not cleaned up. This function cleans them up on startup, when the
   // download manager is initialized.
   void CancelAllEphemeralWarnings();
-#endif
 
   // content::DownloadManager::Observer
   void OnManagerInitialized() override;
diff --git a/chrome/browser/download/chrome_download_manager_delegate_unittest.cc b/chrome/browser/download/chrome_download_manager_delegate_unittest.cc
index d3c7fe6..03267ac0 100644
--- a/chrome/browser/download/chrome_download_manager_delegate_unittest.cc
+++ b/chrome/browser/download/chrome_download_manager_delegate_unittest.cc
@@ -1846,9 +1846,15 @@
 }
 #endif  // BUILDFLAG(IS_ANDROID)
 
-#if !BUILDFLAG(IS_ANDROID)
 #if !BUILDFLAG(IS_CHROMEOS)
 TEST_F(ChromeDownloadManagerDelegateTest, ScheduleCancelForEphemeralWarning) {
+#if BUILDFLAG(IS_ANDROID)
+  // Enable the feature on Android to activate warnings, and thus ephemeral
+  // warning cancellation.
+  base::test::ScopedFeatureList feature_list;
+  feature_list.InitAndEnableFeature(safe_browsing::kMaliciousApkDownloadCheck);
+#endif
+
   std::unique_ptr<download::MockDownloadItem> download_item =
       CreateActiveDownloadItem(0);
   EXPECT_CALL(*download_item, GetDangerType())
@@ -1865,6 +1871,13 @@
 
 TEST_F(ChromeDownloadManagerDelegateTest,
        ScheduleCancelForEphemeralWarning_DownloadKept) {
+#if BUILDFLAG(IS_ANDROID)
+  // Enable the feature on Android to activate warnings, and thus ephemeral
+  // warning cancellation.
+  base::test::ScopedFeatureList feature_list;
+  feature_list.InitAndEnableFeature(safe_browsing::kMaliciousApkDownloadCheck);
+#endif
+
   std::unique_ptr<download::MockDownloadItem> download_item =
       CreateActiveDownloadItem(0);
   EXPECT_CALL(*download_item, GetDangerType())
@@ -1879,6 +1892,13 @@
 #endif  // !BUILDFLAG(IS_CHROMEOS)
 
 TEST_F(ChromeDownloadManagerDelegateTest, CancelAllEphemeralWarnings) {
+#if BUILDFLAG(IS_ANDROID)
+  // Enable the feature on Android to activate warnings, and thus ephemeral
+  // warning cancellation.
+  base::test::ScopedFeatureList feature_list;
+  feature_list.InitAndEnableFeature(safe_browsing::kMaliciousApkDownloadCheck);
+#endif
+
   std::vector<raw_ptr<download::DownloadItem, VectorExperimental>> items;
   auto safe_item = CreateActiveDownloadItem(0);
   EXPECT_CALL(*safe_item, GetDangerType())
@@ -1910,7 +1930,6 @@
 
   delegate()->CancelAllEphemeralWarnings();
 }
-#endif  // !BUILDFLAG(IS_ANDROID)
 
 #if BUILDFLAG(SAFE_BROWSING_DOWNLOAD_PROTECTION)
 namespace {
@@ -2324,6 +2343,7 @@
 }
 
 // Auto cancel is only available on platforms with download bubble.
+// TODO(crbug.com/397407934): Support auto cancel reports on Android.
 #if !BUILDFLAG(IS_CHROMEOS) && !BUILDFLAG(IS_ANDROID)
 TEST_F(ChromeDownloadManagerDelegateTestWithSafeBrowsing,
        AutoCanceledReport_Sent) {
diff --git a/chrome/browser/download/download_item_model.cc b/chrome/browser/download/download_item_model.cc
index 38081a59..deb3f64 100644
--- a/chrome/browser/download/download_item_model.cc
+++ b/chrome/browser/download/download_item_model.cc
@@ -1066,8 +1066,13 @@
 
   return DownloadUIModel::ShouldShowInBubble();
 }
+#endif  // !BUILDFLAG(IS_ANDROID)
 
 bool DownloadItemModel::IsEphemeralWarning() const {
+  // On Android, insecure downloads display a InsecureDownloadDialog prior to
+  // the download and do not display any warning in the UI, so there is no
+  // associated warning message to hide/cancel.
+#if !BUILDFLAG(IS_ANDROID)
   switch (GetInsecureDownloadStatus()) {
     case download::DownloadItem::InsecureDownloadStatus::BLOCK:
     case download::DownloadItem::InsecureDownloadStatus::WARN:
@@ -1078,6 +1083,7 @@
     case download::DownloadItem::InsecureDownloadStatus::SILENT_BLOCK:
       break;
   }
+#endif
 
   switch (GetDangerType()) {
     case download::DOWNLOAD_DANGER_TYPE_DANGEROUS_FILE:
@@ -1109,8 +1115,6 @@
   }
 }
 
-#endif  // !BUILDFLAG(IS_ANDROID)
-
 offline_items_collection::FailState DownloadItemModel::GetLastFailState()
     const {
   return OfflineItemUtils::ConvertDownloadInterruptReasonToFailState(
diff --git a/chrome/browser/download/download_item_model.h b/chrome/browser/download/download_item_model.h
index 61cff67..d7d554b 100644
--- a/chrome/browser/download/download_item_model.h
+++ b/chrome/browser/download/download_item_model.h
@@ -28,7 +28,8 @@
                           public download::DownloadItem::Observer {
  public:
 #if !BUILDFLAG(IS_ANDROID)
-  // How long an ephemeral warning is displayed on the download bubble.
+  // How long an ephemeral warning is displayed on the download bubble on
+  // Desktop.
   static constexpr base::TimeDelta kEphemeralWarningLifetimeOnBubble =
       base::Minutes(5);
 #endif
@@ -130,9 +131,10 @@
   TailoredWarningType GetTailoredWarningType() const override;
   DangerUiPattern GetDangerUiPattern() const override;
   bool ShouldShowInBubble() const override;
-  bool IsEphemeralWarning() const override;
 #endif
 
+  bool IsEphemeralWarning() const override;
+
 #if BUILDFLAG(SAFE_BROWSING_DOWNLOAD_PROTECTION)
   void CompleteSafeBrowsingScan() override;
   void ReviewScanningVerdict(content::WebContents* web_contents) override;
diff --git a/chrome/browser/download/download_ui_model.cc b/chrome/browser/download/download_ui_model.cc
index b31f4dc..13308380 100644
--- a/chrome/browser/download/download_ui_model.cc
+++ b/chrome/browser/download/download_ui_model.cc
@@ -799,13 +799,12 @@
 bool DownloadUIModel::ShouldShowInBubble() const {
   return ShouldShowInShelf();
 }
+#endif  // !BUILDFLAG(IS_ANDROID)
 
 bool DownloadUIModel::IsEphemeralWarning() const {
   return false;
 }
 
-#endif  // !BUILDFLAG(IS_ANDROID)
-
 std::string DownloadUIModel::GetMimeType() const {
   return "text/html";
 }
diff --git a/chrome/browser/download/download_ui_model.h b/chrome/browser/download/download_ui_model.h
index cf15fb1..05775c7 100644
--- a/chrome/browser/download/download_ui_model.h
+++ b/chrome/browser/download/download_ui_model.h
@@ -489,12 +489,12 @@
   // Returns the UI pattern to be used for the download, e.g. dangerous or
   // suspicious. Returns kNoWarning if the download has no warning.
   virtual DangerUiPattern GetDangerUiPattern() const;
+#endif
 
-  // Ephemeral warnings are ones that are quickly removed from the bubble if the
+  // Ephemeral warnings are ones that are quickly removed from the UI if the
   // user has not acted on them, and later deleted altogether. Is this that kind
   // of warning?
   virtual bool IsEphemeralWarning() const;
-#endif
 
 #if BUILDFLAG(SAFE_BROWSING_DOWNLOAD_PROTECTION)
   // Complete the Safe Browsing scan early.
diff --git a/chrome/browser/download/download_ui_safe_browsing_util.cc b/chrome/browser/download/download_ui_safe_browsing_util.cc
index dc88d10..04266cbf 100644
--- a/chrome/browser/download/download_ui_safe_browsing_util.cc
+++ b/chrome/browser/download/download_ui_safe_browsing_util.cc
@@ -94,3 +94,11 @@
   }
 }
 #endif  // BUILDFLAG(SAFE_BROWSING_DOWNLOAD_PROTECTION)
+
+#if BUILDFLAG(IS_ANDROID)
+bool ShouldShowSafeBrowsingAndroidDownloadWarnings() {
+  return base::FeatureList::IsEnabled(
+             safe_browsing::kMaliciousApkDownloadCheck) &&
+         !safe_browsing::kMaliciousApkDownloadCheckTelemetryOnly.Get();
+}
+#endif
diff --git a/chrome/browser/download/download_ui_safe_browsing_util.h b/chrome/browser/download/download_ui_safe_browsing_util.h
index 26cfa3f..f743419 100644
--- a/chrome/browser/download/download_ui_safe_browsing_util.h
+++ b/chrome/browser/download/download_ui_safe_browsing_util.h
@@ -7,6 +7,7 @@
 
 #include <string>
 
+#include "build/build_config.h"
 #include "components/download/public/common/download_danger_type.h"
 #include "components/safe_browsing/buildflags.h"
 
@@ -58,4 +59,11 @@
     download::DownloadItem* item);
 #endif  // BUILDFLAG(SAFE_BROWSING_DOWNLOAD_PROTECTION)
 
+#if BUILDFLAG(IS_ANDROID)
+// Whether Safe Browsing Android Download Protection warnings should be shown
+// in the UI (for malicious APK downloads). This checks the feature state only;
+// Safe Browsing state is checked elsewhere.
+bool ShouldShowSafeBrowsingAndroidDownloadWarnings();
+#endif
+
 #endif  // CHROME_BROWSER_DOWNLOAD_DOWNLOAD_UI_SAFE_BROWSING_UTIL_H_
diff --git a/chrome/browser/enterprise/signals/profile_signals_collector.cc b/chrome/browser/enterprise/signals/profile_signals_collector.cc
new file mode 100644
index 0000000..4bfd36a
--- /dev/null
+++ b/chrome/browser/enterprise/signals/profile_signals_collector.cc
@@ -0,0 +1,92 @@
+// Copyright 2025 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/enterprise/signals/profile_signals_collector.h"
+
+#include <utility>
+
+#include "base/check.h"
+#include "base/functional/bind.h"
+#include "chrome/browser/browser_process.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/common/pref_names.h"
+#include "components/device_signals/core/browser/browser_utils.h"
+#include "components/device_signals/core/browser/signals_types.h"
+#include "components/device_signals/core/browser/user_permission_service.h"
+#include "components/device_signals/core/common/platform_utils.h"
+#include "components/enterprise/buildflags/buildflags.h"
+#include "components/policy/content/policy_blocklist_service.h"
+#include "components/policy/core/common/cloud/cloud_policy_manager.h"
+#include "components/prefs/pref_service.h"
+#include "components/version_info/version_info.h"
+
+#if BUILDFLAG(ENTERPRISE_CLOUD_CONTENT_ANALYSIS)
+#include "chrome/browser/enterprise/connectors/connectors_service.h"
+#endif
+
+namespace device_signals {
+
+namespace {
+
+bool GetBuiltInDnsClientEnabled(PrefService* local_state) {
+  DCHECK(local_state);
+  return local_state->GetBoolean(prefs::kBuiltInDnsClientEnabled);
+}
+
+}  // namespace
+
+ProfileSignalsCollector::ProfileSignalsCollector(Profile* profile)
+    : BaseSignalsCollector({
+          {SignalName::kBrowserContextSignals,
+           base::BindRepeating(&ProfileSignalsCollector::GetProfileSignals,
+                               base::Unretained(this))},
+      }),
+      policy_blocklist_service_(
+          PolicyBlocklistFactory::GetForBrowserContext(profile)),
+      profile_prefs_(profile->GetPrefs()),
+      policy_manager_(profile->GetCloudPolicyManager()),
+      connectors_service_(
+          enterprise_connectors::ConnectorsServiceFactory::GetForBrowserContext(
+              profile)) {
+#if BUILDFLAG(ENTERPRISE_CLOUD_CONTENT_ANALYSIS)
+  DCHECK(connectors_service_);
+#endif  // BUILDFLAG(ENTERPRISE_CLOUD_CONTENT_ANALYSIS)
+  DCHECK(policy_blocklist_service_);
+}
+
+ProfileSignalsCollector::~ProfileSignalsCollector() = default;
+
+void ProfileSignalsCollector::GetProfileSignals(
+    UserPermission permission,
+    const SignalsAggregationRequest& request,
+    SignalsAggregationResponse& response,
+    base::OnceClosure done_closure) {
+  ProfileSignalsResponse signal_response;
+  signal_response.built_in_dns_client_enabled =
+      GetBuiltInDnsClientEnabled(g_browser_process->local_state());
+  signal_response.chrome_remote_desktop_app_blocked =
+      device_signals::GetChromeRemoteDesktopAppBlocked(
+          policy_blocklist_service_);
+  signal_response.password_protection_warning_trigger =
+      device_signals::GetPasswordProtectionWarningTrigger(profile_prefs_);
+  signal_response.profile_enrollment_domain =
+      device_signals::TryGetEnrollmentDomain(policy_manager_);
+  signal_response.safe_browsing_protection_level =
+      device_signals::GetSafeBrowsingProtectionLevel(profile_prefs_);
+  signal_response.site_isolation_enabled =
+      device_signals::GetSiteIsolationEnabled();
+#if BUILDFLAG(ENTERPRISE_CLOUD_CONTENT_ANALYSIS)
+  signal_response.realtime_url_check_mode =
+      connectors_service_->GetAppliedRealTimeUrlCheck();
+#endif  // BUILDFLAG(ENTERPRISE_CLOUD_CONTENT_ANALYSIS)
+
+  response.profile_signals_response = std::move(signal_response);
+
+  // All signals are fetched synchronously for now, so we can run the closure
+  // immediately. Once async signals are added, `done_closure` should be moved
+  // to be run in the callback.
+  std::move(done_closure).Run();
+}
+
+}  // namespace device_signals
diff --git a/chrome/browser/enterprise/signals/profile_signals_collector.h b/chrome/browser/enterprise/signals/profile_signals_collector.h
new file mode 100644
index 0000000..5d96e0893
--- /dev/null
+++ b/chrome/browser/enterprise/signals/profile_signals_collector.h
@@ -0,0 +1,50 @@
+// Copyright 2025 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_ENTERPRISE_SIGNALS_PROFILE_SIGNALS_COLLECTOR_H_
+#define CHROME_BROWSER_ENTERPRISE_SIGNALS_PROFILE_SIGNALS_COLLECTOR_H_
+
+#include "base/memory/raw_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "components/device_signals/core/browser/base_signals_collector.h"
+
+class PrefService;
+class Profile;
+class PolicyBlocklistService;
+
+namespace policy {
+class CloudPolicyManager;
+}  // namespace policy
+
+namespace enterprise_connectors {
+class ConnectorsService;
+}  // namespace enterprise_connectors
+
+namespace device_signals {
+
+class ProfileSignalsCollector : public BaseSignalsCollector {
+ public:
+  explicit ProfileSignalsCollector(Profile* profile);
+
+  ProfileSignalsCollector(const ProfileSignalsCollector&) = delete;
+  ProfileSignalsCollector& operator=(const ProfileSignalsCollector&) = delete;
+
+  ~ProfileSignalsCollector() override;
+
+ private:
+  void GetProfileSignals(UserPermission permission,
+                         const SignalsAggregationRequest& request,
+                         SignalsAggregationResponse& response,
+                         base::OnceClosure done_closure);
+
+  const raw_ptr<PolicyBlocklistService> policy_blocklist_service_;
+  const raw_ptr<PrefService> profile_prefs_;
+  const raw_ptr<policy::CloudPolicyManager> policy_manager_;
+  const raw_ptr<enterprise_connectors::ConnectorsService> connectors_service_;
+  base::WeakPtrFactory<ProfileSignalsCollector> weak_factory_{this};
+};
+
+}  // namespace device_signals
+
+#endif  // CHROME_BROWSER_ENTERPRISE_SIGNALS_PROFILE_SIGNALS_COLLECTOR_H_
diff --git a/chrome/browser/enterprise/signals/profile_signals_collector_browsertest.cc b/chrome/browser/enterprise/signals/profile_signals_collector_browsertest.cc
new file mode 100644
index 0000000..23b6ce9
--- /dev/null
+++ b/chrome/browser/enterprise/signals/profile_signals_collector_browsertest.cc
@@ -0,0 +1,134 @@
+// Copyright 2025 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/enterprise/signals/profile_signals_collector.h"
+
+#include <array>
+#include <utility>
+
+#include "base/memory/raw_ptr.h"
+#include "base/test/task_environment.h"
+#include "base/values.h"
+#include "chrome/browser/browser_process.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/ui/browser.h"
+#include "chrome/common/pref_names.h"
+#include "chrome/test/base/in_process_browser_test.h"
+#include "components/device_signals/core/browser/signals_types.h"
+#include "components/device_signals/core/browser/user_permission_service.h"
+#include "components/device_signals/core/common/signals_constants.h"
+#include "components/policy/core/common/cloud/cloud_policy_manager.h"
+#include "components/policy/core/common/cloud/cloud_policy_util.h"
+#include "components/policy/proto/device_management_backend.pb.h"
+#include "components/prefs/pref_service.h"
+#include "components/safe_browsing/core/common/safe_browsing_prefs.h"
+#include "content/public/test/browser_test.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+constexpr char kFakeUserEnrollmentDomain[] = "fake.domain.google.com";
+
+}  // namespace
+
+namespace device_signals {
+
+class ProfileSignalsCollectorTest : public InProcessBrowserTest {
+ protected:
+  std::unique_ptr<ProfileSignalsCollector> CreateProfileSignalsCollector() {
+    return std::make_unique<ProfileSignalsCollector>(browser()->profile());
+  }
+
+  void SetFakePolicyAndPrefData() {
+    auto policy_data = std::make_unique<enterprise_management::PolicyData>();
+    policy_data->set_managed_by(kFakeUserEnrollmentDomain);
+    browser()
+        ->profile()
+        ->GetCloudPolicyManager()
+        ->core()
+        ->store()
+        ->set_policy_data_for_testing(std::move(policy_data));
+
+    g_browser_process->local_state()->SetBoolean(
+        prefs::kBuiltInDnsClientEnabled, true);
+
+    // Give the testing profile a safe browsing level of "STANDARD_PROTECTION"
+    browser()->profile()->GetPrefs()->SetBoolean(prefs::kSafeBrowsingEnabled,
+                                                 true);
+    browser()->profile()->GetPrefs()->SetBoolean(prefs::kSafeBrowsingEnhanced,
+                                                 false);
+  }
+
+  // Helper function to check the profile level signals are collected correctly.
+  void CheckSignalsCollected(ProfileSignalsResponse& response) {
+    EXPECT_EQ(response.profile_enrollment_domain, kFakeUserEnrollmentDomain);
+    EXPECT_EQ(response.safe_browsing_protection_level,
+              safe_browsing::SafeBrowsingState::STANDARD_PROTECTION);
+    EXPECT_TRUE(response.built_in_dns_client_enabled);
+  }
+
+  std::unique_ptr<ProfileSignalsCollector> signal_collector_ = nullptr;
+};
+
+// Test that runs a sanity check on the set of signals supported by this
+// collector. Will need to be updated if new signals become supported.
+IN_PROC_BROWSER_TEST_F(ProfileSignalsCollectorTest,
+                       SupportedBrowserContextSignalNames) {
+  auto signals_collector = CreateProfileSignalsCollector();
+  const std::array<SignalName, 1> supported_signals{
+      {SignalName::kBrowserContextSignals}};
+
+  const auto names_set = signals_collector->GetSupportedSignalNames();
+
+  EXPECT_EQ(names_set.size(), supported_signals.size());
+  for (const auto& signal_name : supported_signals) {
+    EXPECT_TRUE(names_set.find(signal_name) != names_set.end());
+  }
+}
+
+// Happy path test case for OS signals collection with full permission.
+IN_PROC_BROWSER_TEST_F(ProfileSignalsCollectorTest, GetSignal_Success) {
+  auto signals_collector = CreateProfileSignalsCollector();
+  SetFakePolicyAndPrefData();
+
+  SignalName signal_name = SignalName::kBrowserContextSignals;
+  SignalsAggregationRequest empty_request;
+  SignalsAggregationResponse response;
+  signals_collector->GetSignal(signal_name, UserPermission::kGranted,
+                               empty_request, response, base::DoNothing());
+
+  ASSERT_FALSE(response.top_level_error.has_value());
+  ASSERT_TRUE(response.profile_signals_response);
+  CheckSignalsCollected(response.profile_signals_response.value());
+}
+
+// Tests that an unsupported signal is marked as unsupported.
+IN_PROC_BROWSER_TEST_F(ProfileSignalsCollectorTest,
+                       GetBrowserContextSignal_Unsupported) {
+  auto signals_collector = CreateProfileSignalsCollector();
+  SignalName signal_name = SignalName::kAntiVirus;
+  SignalsAggregationRequest empty_request;
+  SignalsAggregationResponse response;
+  signals_collector->GetSignal(signal_name, UserPermission::kGranted,
+                               empty_request, response, base::DoNothing());
+
+  ASSERT_TRUE(response.top_level_error.has_value());
+  EXPECT_EQ(response.top_level_error.value(),
+            SignalCollectionError::kUnsupported);
+}
+
+// Tests that signal collection is halted if permission is not sufficient.
+IN_PROC_BROWSER_TEST_F(ProfileSignalsCollectorTest, GetSignal_MissingUser) {
+  auto signals_collector = CreateProfileSignalsCollector();
+  SignalName signal_name = SignalName::kBrowserContextSignals;
+  SignalsAggregationRequest empty_request;
+  SignalsAggregationResponse response;
+  signals_collector->GetSignal(signal_name, UserPermission::kMissingUser,
+                               empty_request, response, base::DoNothing());
+
+  ASSERT_FALSE(response.top_level_error.has_value());
+  ASSERT_FALSE(response.profile_signals_response);
+}
+
+}  // namespace device_signals
diff --git a/chrome/browser/extensions/BUILD.gn b/chrome/browser/extensions/BUILD.gn
index e0e1eab..5ffdb47f9 100644
--- a/chrome/browser/extensions/BUILD.gn
+++ b/chrome/browser/extensions/BUILD.gn
@@ -204,6 +204,8 @@
     "extension_management_constants.h",
     "extension_management_internal.cc",
     "extension_management_internal.h",
+    "extension_migrator.cc",
+    "extension_migrator.h",
     "extension_special_storage_policy.cc",
     "extension_special_storage_policy.h",
     "extension_tab_util.cc",
@@ -218,11 +220,19 @@
     "extension_web_ui_override_registrar.h",
     "extension_webkit_preferences.cc",
     "extension_webkit_preferences.h",
+    "external_component_loader.cc",
+    "external_component_loader.h",
     "external_install_error.h",
     "external_install_manager.cc",
     "external_install_manager.h",
     "external_install_manager_factory.cc",
     "external_install_manager_factory.h",
+    "external_loader.cc",
+    "external_loader.h",
+    "external_pref_loader.cc",
+    "external_pref_loader.h",
+    "external_provider_impl.cc",
+    "external_provider_impl.h",
     "external_provider_manager.cc",
     "external_provider_manager.h",
     "external_provider_manager_factory.cc",
@@ -322,6 +332,7 @@
     "//base",
     "//chrome/app:command_ids",
     "//chrome/browser:browser_process",
+    "//chrome/browser/apps:user_type_filter",
     "//chrome/browser/bitmap_fetcher",
     "//chrome/browser/content_settings:content_settings_factory",
     "//chrome/browser/extensions:cws_item_service_proto",
@@ -330,6 +341,7 @@
     "//chrome/browser/google",
     "//chrome/browser/prefetch",
     "//chrome/browser/prefs",
+    "//chrome/browser/prefs:util",
     "//chrome/browser/preloading:prefs",
     "//chrome/browser/profiles:profile",
     "//chrome/browser/safe_browsing",
@@ -651,8 +663,6 @@
       "extension_management.h",
       "extension_menu_icon_loader.cc",
       "extension_menu_icon_loader.h",
-      "extension_migrator.cc",
-      "extension_migrator.h",
       "extension_safety_check_utils.cc",
       "extension_safety_check_utils.h",
       "extension_service.cc",
@@ -676,18 +686,10 @@
       "extension_view_host.h",
       "extension_view_host_factory.cc",
       "extension_view_host_factory.h",
-      "external_component_loader.cc",
-      "external_component_loader.h",
       "external_install_error_desktop.cc",
       "external_install_error_desktop.h",
-      "external_loader.cc",
-      "external_loader.h",
       "external_policy_loader.cc",
       "external_policy_loader.h",
-      "external_pref_loader.cc",
-      "external_pref_loader.h",
-      "external_provider_impl.cc",
-      "external_provider_impl.h",
       "file_handlers/file_handling_launch_utils.cc",
       "file_handlers/file_handling_launch_utils.h",
       "forced_extensions/force_installed_metrics.cc",
diff --git a/chrome/browser/extensions/extension_protocols_unittest.cc b/chrome/browser/extensions/extension_protocols_unittest.cc
index 54e59ebf..7d59dec 100644
--- a/chrome/browser/extensions/extension_protocols_unittest.cc
+++ b/chrome/browser/extensions/extension_protocols_unittest.cc
@@ -52,7 +52,6 @@
 #include "services/network/test/test_url_loader_client.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
-#include "third_party/blink/public/common/features.h"
 #include "third_party/blink/public/common/loader/referrer_utils.h"
 
 using extensions::ExtensionRegistry;
@@ -767,9 +766,6 @@
       network::mojom::RequestDestination::kStyle,
       network::mojom::RequestDestination::kVideo,
   };
-  if (!base::FeatureList::IsEnabled(blink::features::kPlzDedicatedWorker)) {
-    destinations.push_back(network::mojom::RequestDestination::kWorker);
-  }
   for (network::mojom::RequestDestination destination : destinations) {
     auto get_result =
         RequestOrLoad(extension->GetResourceURL("background.js"), destination);
diff --git a/chrome/browser/extensions/external_provider_impl.cc b/chrome/browser/extensions/external_provider_impl.cc
index c299e9717..9bed348 100644
--- a/chrome/browser/extensions/external_provider_impl.cc
+++ b/chrome/browser/extensions/external_provider_impl.cc
@@ -29,10 +29,8 @@
 #include "chrome/browser/app_mode/app_mode_utils.h"
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/browser_process_platform_part.h"
-#include "chrome/browser/extensions/extension_management.h"
 #include "chrome/browser/extensions/extension_migrator.h"
 #include "chrome/browser/extensions/external_component_loader.h"
-#include "chrome/browser/extensions/external_policy_loader.h"
 #include "chrome/browser/extensions/external_pref_loader.h"
 #include "chrome/browser/extensions/forced_extensions/install_stage_tracker.h"
 #include "chrome/browser/policy/profile_policy_connector.h"
@@ -55,6 +53,11 @@
 #include "extensions/common/manifest.h"
 #include "ui/base/l10n/l10n_util.h"
 
+#if !BUILDFLAG(IS_ANDROID)
+#include "chrome/browser/extensions/extension_management.h"
+#include "chrome/browser/extensions/external_policy_loader.h"
+#endif
+
 #if BUILDFLAG(IS_CHROMEOS)
 #include "ash/constants/ash_paths.h"
 #include "ash/constants/ash_switches.h"
@@ -654,7 +657,8 @@
                "ExternalProviderImpl::CreateExternalProviders");
   scoped_refptr<ExternalLoader> external_loader;
   scoped_refptr<ExternalLoader> external_recommended_loader;
-  ManifestLocation crx_location = ManifestLocation::kInvalidLocation;
+  [[maybe_unused]] ManifestLocation crx_location =
+      ManifestLocation::kInvalidLocation;
 
 #if BUILDFLAG(IS_CHROMEOS)
   if (ash::ProfileHelper::IsSigninProfile(profile)) {
@@ -700,6 +704,10 @@
   }
 #endif
 
+#if BUILDFLAG(IS_ANDROID)
+  // TODO(crbug.com/394876083): Port ExtensionManagement to Android.
+  NOTIMPLEMENTED() << "Policy loaded extensions not yet supported on Android";
+#else
   if (!external_loader.get()) {
     external_loader = base::MakeRefCounted<ExternalPolicyLoader>(
         profile, ExtensionManagementFactory::GetForBrowserContext(profile),
@@ -715,6 +723,7 @@
       ManifestLocation::kExternalPolicyDownload, Extension::NO_FLAGS);
   policy_provider->set_allow_updates(true);
   provider_list->push_back(std::move(policy_provider));
+#endif  // BUILDFLAG(IS_ANDROID)
 
   // Load the KioskAppExternalProvider when running in the Chrome App kiosk
   // mode.
@@ -758,7 +767,10 @@
     return;
   }
 
+#if !BUILDFLAG(IS_ANDROID)
   // Extensions provided by recommended policies.
+  // TODO(crbug.com/394876083): Port ExtensionManagement to Android.
+  // No NOTIMPLEMENTED() here because we already logged above.
   if (external_recommended_loader.get()) {
     auto recommended_provider = std::make_unique<ExternalProviderImpl>(
         service, external_recommended_loader, profile, crx_location,
@@ -766,6 +778,7 @@
     recommended_provider->set_auto_acknowledge(true);
     provider_list->push_back(std::move(recommended_provider));
   }
+#endif  // !BUILDFLAG(IS_ANDROID)
 
   // In tests don't install pre-installed apps.
   // It would only slowdown tests and make them flaky.
diff --git a/chrome/browser/extensions/external_provider_manager.cc b/chrome/browser/extensions/external_provider_manager.cc
index e249edb..73f7b3c1 100644
--- a/chrome/browser/extensions/external_provider_manager.cc
+++ b/chrome/browser/extensions/external_provider_manager.cc
@@ -20,6 +20,7 @@
 #include "chrome/browser/extensions/crx_installer.h"
 #include "chrome/browser/extensions/extension_error_controller.h"
 #include "chrome/browser/extensions/external_install_manager.h"
+#include "chrome/browser/extensions/external_provider_impl.h"
 #include "chrome/browser/extensions/external_provider_manager_factory.h"
 #include "chrome/browser/extensions/forced_extensions/install_stage_tracker.h"
 #include "chrome/browser/extensions/installed_loader.h"
@@ -44,10 +45,6 @@
 #include "extensions/common/verifier_formats.h"
 #include "url/gurl.h"
 
-#if !BUILDFLAG(IS_ANDROID)
-#include "chrome/browser/extensions/external_provider_impl.h"
-#endif
-
 #if BUILDFLAG(IS_CHROMEOS)
 #include "chrome/browser/ash/extensions/install_limiter.h"
 #endif
@@ -92,14 +89,9 @@
 }
 
 void ExternalProviderManager::CreateExternalProviders() {
-#if BUILDFLAG(IS_ANDROID)
-  // TODO(crbug.com/407824044): Port ExternalProviderImpl to desktop Android.
-  NOTIMPLEMENTED() << "External providers not yet supported on Android";
-#else
   ExternalProviderImpl::CreateExternalProviders(
       this, Profile::FromBrowserContext(context_.get()),
       &external_extension_providers_);
-#endif
 }
 
 // Some extensions will autoupdate themselves externally from Chrome.  These
diff --git a/chrome/browser/extensions/installed_loader.cc b/chrome/browser/extensions/installed_loader.cc
index 61d59e4..fe69fdb3 100644
--- a/chrome/browser/extensions/installed_loader.cc
+++ b/chrome/browser/extensions/installed_loader.cc
@@ -1050,7 +1050,6 @@
                               extension_user_count + extension_external_count);
   base::UmaHistogramCounts100("Extensions.LoadExtensionExternal",
                               extension_external_count);
-  base::UmaHistogramCounts100("Extensions.LoadUserScript", user_script_count);
   base::UmaHistogramCounts100("Extensions.LoadTheme", theme_count);
   // Histogram name different for legacy reasons.
   base::UmaHistogramCounts100("PageActionController.ExtensionsWithPageActions",
diff --git a/chrome/browser/file_system_access/chrome_file_system_access_permission_context.cc b/chrome/browser/file_system_access/chrome_file_system_access_permission_context.cc
index f08ff19..cc84089 100644
--- a/chrome/browser/file_system_access/chrome_file_system_access_permission_context.cc
+++ b/chrome/browser/file_system_access/chrome_file_system_access_permission_context.cc
@@ -551,6 +551,8 @@
     case Result::UNKNOWN:
     case Result::SAFE:
     case Result::ALLOWLISTED_BY_POLICY:
+    case Result::SENSITIVE_CONTENT_WARNING:
+    case Result::DEEP_SCANNED_SAFE:
       return ChromeFileSystemAccessPermissionContext::AfterWriteCheckResult::
           kAllow;
 
@@ -562,15 +564,13 @@
     case Result::BLOCKED_TOO_LARGE:
     case Result::DANGEROUS_ACCOUNT_COMPROMISE:
     case Result::BLOCKED_SCAN_FAILED:
+    case Result::SENSITIVE_CONTENT_BLOCK:
       return ChromeFileSystemAccessPermissionContext::AfterWriteCheckResult::
           kBlock;
 
     // This shouldn't be returned for File System Access write checks.
     case Result::ASYNC_SCANNING:
     case Result::ASYNC_LOCAL_PASSWORD_SCANNING:
-    case Result::SENSITIVE_CONTENT_WARNING:
-    case Result::SENSITIVE_CONTENT_BLOCK:
-    case Result::DEEP_SCANNED_SAFE:
     case Result::PROMPT_FOR_SCANNING:
     case Result::PROMPT_FOR_LOCAL_PASSWORD_SCANNING:
     case Result::DEEP_SCANNED_FAILED:
diff --git a/chrome/browser/flag-metadata.json b/chrome/browser/flag-metadata.json
index 97279b2..3351cfa 100644
--- a/chrome/browser/flag-metadata.json
+++ b/chrome/browser/flag-metadata.json
@@ -2115,6 +2115,14 @@
     "expiry_milestone": 100
   },
   {
+    "name": "disable-system-blur",
+    "owners": [
+      "zoraiznaeem@chromium.org",
+      "chromeos-foundations@google.com"
+    ],
+    "expiry_milestone": 140
+  },
+  {
     "name": "disable-virtual-keyboard",
     "owners": [ "dvallet@chromium.org", "essential-inputs-team@google.com" ],
     "expiry_milestone": 116
@@ -4722,6 +4730,11 @@
     "expiry_milestone": 140
   },
   {
+    "name": "feed-header-removal",
+    "owners": [ "//chrome/android/feed/OWNERS", "jianli@chromium.org" ],
+    "expiry_milestone": 142
+  },
+  {
     "name": "feed-header-stick-to-top",
     "owners": [ "//chrome/android/feed/OWNERS", "chili@chromium.org" ],
     "expiry_milestone": 115
diff --git a/chrome/browser/flag_descriptions.cc b/chrome/browser/flag_descriptions.cc
index 6d45b52..e797bb6 100644
--- a/chrome/browser/flag_descriptions.cc
+++ b/chrome/browser/flag_descriptions.cc
@@ -1165,6 +1165,10 @@
     "unrelated tabs. This is an experimental mode that will result in more "
     "processes being created.";
 
+const char kDisableSystemBlur[] = "Disable system blur";
+const char kDisableSystemBlurDescription[] =
+    "Removes background blur from system UI";
+
 const char kDisallowDocWrittenScriptsUiName[] =
     "Block scripts loaded via document.write";
 const char kDisallowDocWrittenScriptsUiDescription[] =
@@ -4673,6 +4677,9 @@
 const char kFeedDynamicColorsDescription[] =
     "Allows feed to fully respect dynamic colors if supported by the client.";
 
+const char kFeedHeaderRemovalName[] = "Removing feed header";
+const char kFeedHeaderRemovalDescription[] = "Stops showing the feed header.";
+
 const char kFloatingSnackbarName[] = "FloatingSnackbar";
 const char kFloatingSnackbarDescription[] =
     "Enables the snackbar to float on top of the web content.";
diff --git a/chrome/browser/flag_descriptions.h b/chrome/browser/flag_descriptions.h
index d722672..43991f28 100644
--- a/chrome/browser/flag_descriptions.h
+++ b/chrome/browser/flag_descriptions.h
@@ -611,6 +611,9 @@
 extern const char kDisableProcessReuse[];
 extern const char kDisableProcessReuseDescription[];
 
+extern const char kDisableSystemBlur[];
+extern const char kDisableSystemBlurDescription[];
+
 extern const char kDoubleBufferCompositingName[];
 extern const char kDoubleBufferCompositingDescription[];
 
@@ -2748,6 +2751,9 @@
 extern const char kFeedDiscoFeedEndpointName[];
 extern const char kFeedDiscoFeedEndpointDescription[];
 
+extern const char kFeedHeaderRemovalName[];
+extern const char kFeedHeaderRemovalDescription[];
+
 extern const char kFloatingSnackbarName[];
 extern const char kFloatingSnackbarDescription[];
 
diff --git a/chrome/browser/flags/android/chrome_feature_list.cc b/chrome/browser/flags/android/chrome_feature_list.cc
index c7e3e8d4..6eeb1d01 100644
--- a/chrome/browser/flags/android/chrome_feature_list.cc
+++ b/chrome/browser/flags/android/chrome_feature_list.cc
@@ -157,6 +157,7 @@
     &feed::kFeedContainment,
     &feed::kFeedDynamicColors,
     &feed::kFeedFollowUiUpdate,
+    &feed::kFeedHeaderRemoval,
     &feed::kFeedImageMemoryCacheSizePercentage,
     &feed::kFeedLoadingPlaceholder,
     &feed::kFeedNoViewCache,
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 dcebb54..e860690c 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
@@ -353,6 +353,7 @@
     public static final String ESB_AI_STRING_UPDATE = "EsbAiStringUpdate";
     public static final String FEED_CONTAINMENT = "FeedContainment";
     public static final String FEED_FOLLOW_UI_UPDATE = "FeedFollowUiUpdate";
+    public static final String FEED_HEADER_REMOVAL = "FeedHeaderRemoval";
     public static final String FEED_IMAGE_MEMORY_CACHE_SIZE_PERCENTAGE =
             "FeedImageMemoryCacheSizePercentage";
     public static final String FEED_LOADING_PLACEHOLDER = "FeedLoadingPlaceholder";
diff --git a/chrome/browser/glic/widget/glic_window_controller.cc b/chrome/browser/glic/widget/glic_window_controller.cc
index 26a0c5f2..e158447b 100644
--- a/chrome/browser/glic/widget/glic_window_controller.cc
+++ b/chrome/browser/glic/widget/glic_window_controller.cc
@@ -775,6 +775,9 @@
   // There is no open detached animation so wait for glic to load to continue
   // opening.
   SetWindowState(State::kWaitingForGlicToLoad);
+
+  // This is needed in case of theme difference between OS and chrome.
+  GetGlicWidget()->ThemeChanged();
 }
 
 // This happens after the web client is initialized. It signals the web client
diff --git a/chrome/browser/net/private_network_access_browsertest.cc b/chrome/browser/net/private_network_access_browsertest.cc
index 736f29f..6c91626 100644
--- a/chrome/browser/net/private_network_access_browsertest.cc
+++ b/chrome/browser/net/private_network_access_browsertest.cc
@@ -43,7 +43,6 @@
 #include "services/network/public/cpp/private_network_access_check_result.h"
 #include "services/network/public/cpp/url_loader_completion_status.h"
 #include "testing/gmock/include/gmock/gmock.h"
-#include "third_party/blink/public/common/features.h"
 #include "third_party/blink/public/mojom/use_counter/metrics/web_feature.mojom.h"
 
 namespace {
@@ -263,7 +262,6 @@
       bool is_warning_only = false)
       : PrivateNetworkAccessBrowserTestBase(
             {
-                blink::features::kPlzDedicatedWorker,
                 features::kBlockInsecurePrivateNetworkRequests,
                 features::kBlockInsecurePrivateNetworkRequestsFromPrivate,
                 features::kBlockInsecurePrivateNetworkRequestsDeprecationTrial,
@@ -297,7 +295,6 @@
   PrivateNetworkAccessRespectPreflightResultsBrowserTest()
       : PrivateNetworkAccessBrowserTestBase(
             {
-                blink::features::kPlzDedicatedWorker,
                 features::kBlockInsecurePrivateNetworkRequests,
                 features::kPrivateNetworkAccessSendPreflights,
                 features::kPrivateNetworkAccessRespectPreflightResults,
@@ -1474,7 +1471,6 @@
   PrivateNetworkAccessWithNullIPKillswitchTest()
       : PrivateNetworkAccessBrowserTestBase(
             {
-                blink::features::kPlzDedicatedWorker,
                 features::kBlockInsecurePrivateNetworkRequests,
                 features::kBlockInsecurePrivateNetworkRequestsFromPrivate,
                 features::kBlockInsecurePrivateNetworkRequestsDeprecationTrial,
diff --git a/chrome/browser/pdf/pdf_extension_js_test.cc b/chrome/browser/pdf/pdf_extension_js_test.cc
index 0fc110c..0f2713a 100644
--- a/chrome/browser/pdf/pdf_extension_js_test.cc
+++ b/chrome/browser/pdf/pdf_extension_js_test.cc
@@ -590,6 +590,10 @@
   RunTestsInJsModule("ink2_text_side_panel_test.js", "test.pdf");
 }
 
+IN_PROC_BROWSER_TEST_P(PDFExtensionJSInk2Test, Ink2TextStylesSelector) {
+  RunTestsInJsModule("ink2_text_styles_selector_test.js", "test.pdf");
+}
+
 IN_PROC_BROWSER_TEST_P(PDFExtensionJSInk2Test, Ink2BrushSelector) {
   RunTestsInJsModule("ink2_brush_selector_test.js", "test.pdf");
 }
diff --git a/chrome/browser/privacy_sandbox/browsing_topics_settings_interactive_uitest.cc b/chrome/browser/privacy_sandbox/browsing_topics_settings_interactive_uitest.cc
index 041cb01..ada6566 100644
--- a/chrome/browser/privacy_sandbox/browsing_topics_settings_interactive_uitest.cc
+++ b/chrome/browser/privacy_sandbox/browsing_topics_settings_interactive_uitest.cc
@@ -17,6 +17,10 @@
 #include "components/privacy_sandbox/privacy_sandbox_prefs.h"
 #include "content/public/test/browser_test.h"
 
+#if BUILDFLAG(IS_MAC)
+#include "base/mac/mac_util.h"
+#endif
+
 namespace {
 
 using DeepQuery = WebContentsInteractionTestUtil::DeepQuery;
@@ -139,6 +143,13 @@
 // topic toggle is ON (checked == true).
 IN_PROC_BROWSER_TEST_F(PrivacySandboxSettingsTopicsInteractiveTest,
                        UnblockOneTopicOnAdTopicsPage) {
+#if BUILDFLAG(IS_MAC)
+  // https://crbug.com/407801060
+  if (base::mac::MacOSMajorVersion() == 15) {
+    GTEST_SKIP() << "Disabled on macOS Sequoia.";
+  }
+#endif
+
   BlockTopic(1);
   RunTestSequence(
       InstrumentTab(kPrivacySandboxTopicsElementId),
@@ -151,7 +162,7 @@
       ExecuteJsAt(kPrivacySandboxTopicsElementId, firstBlockedItemButton,
                   "(el) => el.click()"),
       CheckResult([this]() { return GetBlockedTopicsSize(); }, 0u,
-                  "Checking that there is 0 blocked topics"),
+                  "Checking that there are 0 blocked topics"),
       NavigateWebContents(kPrivacySandboxTopicsElementId,
                           GURL(chrome::kPrivacySandboxManageTopicsURL)),
       CheckJsResultAt(kPrivacySandboxTopicsElementId, firstToggle,
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 98facd6..64100a4 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
@@ -364,16 +364,6 @@
     }
 
     @Test
-    public void testDontHidePlayer_nonTabSwitcherUi() {
-        requestAndStartPlayback();
-        mLayoutStateObserver.getValue().onStartedShowing(LayoutType.START_SURFACE);
-        verify(mPlayerCoordinator, never()).hidePlayers();
-
-        mLayoutStateObserver.getValue().onFinishedHiding(LayoutType.START_SURFACE);
-        verify(mPlayerCoordinator, never()).restorePlayers();
-    }
-
-    @Test
     public void testHidePlayer_FullScreen() {
         requestAndStartPlayback();
         mFullscreenObserver.getValue().onEnterFullscreen(mTab, new FullscreenOptions(true, true));
diff --git a/chrome/browser/resources/ash/settings/common/deep_linking_mixin.ts b/chrome/browser/resources/ash/settings/common/deep_linking_mixin.ts
index d94c149..78a036e 100644
--- a/chrome/browser/resources/ash/settings/common/deep_linking_mixin.ts
+++ b/chrome/browser/resources/ash/settings/common/deep_linking_mixin.ts
@@ -45,15 +45,6 @@
               type: Object,
               value: Setting,
             },
-
-            /**
-             * Set of setting IDs that could be deep linked to. Initialized as
-             * an empty set, should be overridden with applicable setting IDs.
-             */
-            supportedSettingIds: {
-              type: Object,
-              value: () => new Set<Setting>(),
-            },
           };
         }
 
@@ -61,7 +52,12 @@
         // the exact value of the Setting enum.
         // eslint-disable-next-line @typescript-eslint/naming-convention
         Setting: Setting;
-        supportedSettingIds: Set<Setting>;
+
+        /**
+         * Set of setting IDs that could be deep linked to. Initialized as an
+         * empty set, should be overridden with applicable setting IDs.
+         */
+        supportedSettingIds = new Set<Setting>();
 
         /**
          * Retrieves the settingId saved in the url's query parameter. Returns
diff --git a/chrome/browser/resources/ash/settings/crostini_page/bruschetta_subpage.ts b/chrome/browser/resources/ash/settings/crostini_page/bruschetta_subpage.ts
index 84f15c17..e75d599d 100644
--- a/chrome/browser/resources/ash/settings/crostini_page/bruschetta_subpage.ts
+++ b/chrome/browser/resources/ash/settings/crostini_page/bruschetta_subpage.ts
@@ -43,16 +43,6 @@
         type: Boolean,
         value: false,
       },
-
-      /**
-       * Used by DeepLinkingMixin to focus this page's deep links.
-       */
-      supportedSettingIds: {
-        type: Object,
-        value: () => new Set<Setting>([
-          Setting.kBruschettaMicAccess,
-        ]),
-      },
     };
   }
 
@@ -62,6 +52,11 @@
     ];
   }
 
+  // DeepLinkingMixin override
+  override supportedSettingIds = new Set<Setting>([
+    Setting.kBruschettaMicAccess,
+  ]);
+
   private browserProxy_: CrostiniBrowserProxy;
   private showBruschettaMicPermissionDialog_: boolean;
 
diff --git a/chrome/browser/resources/ash/settings/crostini_page/crostini_arc_adb.ts b/chrome/browser/resources/ash/settings/crostini_page/crostini_arc_adb.ts
index d3e6ad27..0a454e2 100644
--- a/chrome/browser/resources/ash/settings/crostini_page/crostini_arc_adb.ts
+++ b/chrome/browser/resources/ash/settings/crostini_page/crostini_arc_adb.ts
@@ -94,18 +94,16 @@
         type: Boolean,
         value: false,
       },
-
-      /**
-       * Used by DeepLinkingMixin to focus this page's deep links.
-       */
-      supportedSettingIds: {
-        type: Object,
-        value: () => new Set<Setting>([Setting.kCrostiniAdbDebugging]),
-      },
     };
   }
 
   prefs: PrefsState;
+
+  // DeepLinkingMixin override
+  override supportedSettingIds = new Set<Setting>([
+    Setting.kCrostiniAdbDebugging,
+  ]);
+
   private arcAdbEnabled_: boolean;
   private arcAdbNeedPowerwash_: boolean;
   private browserProxy_: CrostiniBrowserProxy;
diff --git a/chrome/browser/resources/ash/settings/crostini_page/crostini_export_import.ts b/chrome/browser/resources/ash/settings/crostini_page/crostini_export_import.ts
index 5314803..d416212 100644
--- a/chrome/browser/resources/ash/settings/crostini_page/crostini_export_import.ts
+++ b/chrome/browser/resources/ash/settings/crostini_page/crostini_export_import.ts
@@ -117,21 +117,17 @@
         type: String,
         value: DEFAULT_CROSTINI_VM,
       },
-
-      /**
-       * Used by DeepLinkingMixin to focus this page's deep links.
-       */
-      supportedSettingIds: {
-        type: Object,
-        value: () => new Set<Setting>([
-          Setting.kBackupLinuxAppsAndFiles,
-          Setting.kRestoreLinuxAppsAndFiles,
-        ]),
-      },
     };
   }
 
   prefs: PrefsState;
+
+  // DeepLinkingMixin override
+  override supportedSettingIds = new Set<Setting>([
+    Setting.kBackupLinuxAppsAndFiles,
+    Setting.kRestoreLinuxAppsAndFiles,
+  ]);
+
   private allContainers_: ContainerInfo[];
   private browserProxy_: CrostiniBrowserProxy;
   private defaultVmName_: string;
diff --git a/chrome/browser/resources/ash/settings/crostini_page/crostini_settings_card.ts b/chrome/browser/resources/ash/settings/crostini_page/crostini_settings_card.ts
index 15763bfd..bfda4e4 100644
--- a/chrome/browser/resources/ash/settings/crostini_page/crostini_settings_card.ts
+++ b/chrome/browser/resources/ash/settings/crostini_page/crostini_settings_card.ts
@@ -66,14 +66,6 @@
         },
       },
 
-      /**
-       * Used by DeepLinkingMixin to focus this page's deep links.
-       */
-      supportedSettingIds: {
-        type: Object,
-        value: () => new Set<Setting>([Setting.kSetUpCrostini]),
-      },
-
       showBruschetta_: {
         type: Boolean,
         value() {
@@ -83,6 +75,11 @@
     };
   }
 
+  // DeepLinkingMixin override
+  override supportedSettingIds = new Set<Setting>([
+    Setting.kSetUpCrostini,
+  ]);
+
   private browserProxy_: CrostiniBrowserProxy;
   private disableCrostiniInstall_: boolean;
   private isCrostiniAllowed_: boolean;
diff --git a/chrome/browser/resources/ash/settings/crostini_page/crostini_subpage.ts b/chrome/browser/resources/ash/settings/crostini_page/crostini_subpage.ts
index a6944a5..df1012d2 100644
--- a/chrome/browser/resources/ash/settings/crostini_page/crostini_subpage.ts
+++ b/chrome/browser/resources/ash/settings/crostini_page/crostini_subpage.ts
@@ -169,19 +169,6 @@
         type: Boolean,
         value: false,
       },
-
-      /**
-       * Used by DeepLinkingMixin to focus this page's deep links.
-       */
-      supportedSettingIds: {
-        type: Object,
-        value: () => new Set<Setting>([
-          Setting.kUninstallCrostini,
-          Setting.kCrostiniDiskResize,
-          Setting.kCrostiniMicAccess,
-          Setting.kCrostiniContainerUpgrade,
-        ]),
-      },
     };
   }
 
@@ -192,6 +179,14 @@
     ];
   }
 
+  // DeepLinkingMixin override
+  override supportedSettingIds = new Set<Setting>([
+    Setting.kUninstallCrostini,
+    Setting.kCrostiniDiskResize,
+    Setting.kCrostiniMicAccess,
+    Setting.kCrostiniContainerUpgrade,
+  ]);
+
   private browserProxy_: CrostiniBrowserProxy;
   private canDiskResize_: boolean;
   private diskResizeButtonAriaLabel_: string;
diff --git a/chrome/browser/resources/ash/settings/date_time_page/date_time_settings_card.ts b/chrome/browser/resources/ash/settings/date_time_page/date_time_settings_card.ts
index 328fb4d1..6839796 100644
--- a/chrome/browser/resources/ash/settings/date_time_page/date_time_settings_card.ts
+++ b/chrome/browser/resources/ash/settings/date_time_page/date_time_settings_card.ts
@@ -49,17 +49,6 @@
       },
 
       /**
-       * Used by DeepLinkingMixin to focus this page's deep links.
-       */
-      supportedSettingIds: {
-        type: Object,
-        value: () => new Set<Setting>([
-          Setting.k24HourClock,
-          Setting.kChangeTimeZone,
-        ]),
-      },
-
-      /**
        * Whether date and time are settable. Normally the date and time are
        * forced by network time, so default to false to initially hide the
        * button.
@@ -91,6 +80,13 @@
   }
 
   activeTimeZoneDisplayName: string;
+
+  // DeepLinkingMixin override
+  override supportedSettingIds = new Set<Setting>([
+    Setting.k24HourClock,
+    Setting.kChangeTimeZone,
+  ]);
+
   private canSetDateTime_: boolean;
   private shouldShowManagedByParentIcon_: boolean;
   private timeZoneSettingSublabel_: string;
diff --git a/chrome/browser/resources/ash/settings/date_time_page/timezone_subpage.ts b/chrome/browser/resources/ash/settings/date_time_page/timezone_subpage.ts
index 957794b..760cf707 100644
--- a/chrome/browser/resources/ash/settings/date_time_page/timezone_subpage.ts
+++ b/chrome/browser/resources/ash/settings/date_time_page/timezone_subpage.ts
@@ -68,14 +68,6 @@
         },
       },
 
-      /**
-       * Used by DeepLinkingMixin to focus this page's deep links.
-       */
-      supportedSettingIds: {
-        type: Object,
-        value: () => new Set<Setting>([Setting.kChangeTimeZone]),
-      },
-
       geolocationWarningText_: {
         type: String,
         computed: 'computedGeolocationWarningText(activeTimeZoneDisplayName,' +
@@ -97,6 +89,12 @@
   }
 
   activeTimeZoneDisplayName: string;
+
+  // DeepLinkingMixin override
+  override supportedSettingIds = new Set<Setting>([
+    Setting.kChangeTimeZone,
+  ]);
+
   private canSetSystemTimezone_: boolean;
   private browserProxy_: DateTimeBrowserProxy;
   private showEnableSystemGeolocationDialog_: boolean;
diff --git a/chrome/browser/resources/ash/settings/device_page/audio.ts b/chrome/browser/resources/ash/settings/device_page/audio.ts
index c6b8fa4..f73898dc2 100644
--- a/chrome/browser/resources/ash/settings/device_page/audio.ts
+++ b/chrome/browser/resources/ash/settings/device_page/audio.ts
@@ -116,17 +116,6 @@
         value: false,
       },
 
-      /**
-       * Used by DeepLinkingMixin to focus this page's deep links.
-       */
-      supportedSettingIds: {
-        type: Object,
-        value: () => new Set<Setting>([
-          Setting.kChargingSounds,
-          Setting.kLowBatterySound,
-        ]),
-      },
-
       showAllowAGC: {
         type: Boolean,
         value: loadTimeData.getBoolean('enableForceRespectUiGainsToggle'),
@@ -153,6 +142,12 @@
     };
   }
 
+  // DeepLinkingMixin override
+  override supportedSettingIds = new Set<Setting>([
+    Setting.kChargingSounds,
+    Setting.kLowBatterySound,
+  ]);
+
   protected isAllowAGCEnabled: boolean;
   protected showAllowAGC: boolean;
   protected isHfpMicSrEnabled: boolean;
diff --git a/chrome/browser/resources/ash/settings/device_page/display.ts b/chrome/browser/resources/ash/settings/device_page/display.ts
index 88a7ecb0..88ed85326e 100644
--- a/chrome/browser/resources/ash/settings/device_page/display.ts
+++ b/chrome/browser/resources/ash/settings/device_page/display.ts
@@ -281,25 +281,6 @@
         type: Number,
         value: null,
       },
-
-      /**
-       * Used by DeepLinkingMixin to focus this page's deep links.
-       */
-      supportedSettingIds: {
-        type: Object,
-        value: () => new Set<Setting>([
-          Setting.kDisplaySize,
-          Setting.kDisplayOrientation,
-          Setting.kDisplayArrangement,
-          Setting.kDisplayResolution,
-          Setting.kDisplayRefreshRate,
-          Setting.kDisplayMirroring,
-          Setting.kAllowWindowsToSpanDisplays,
-          Setting.kAmbientColors,
-          Setting.kTouchscreenCalibration,
-          Setting.kDisplayOverscan,
-        ]),
-      },
     };
   }
 
@@ -320,6 +301,21 @@
   overscanDisplayId: string;
   primaryDisplayId: string;
   selectedDisplay?: DisplayUnitInfo;
+
+  // DeepLinkingMixin override
+  override supportedSettingIds = new Set<Setting>([
+    Setting.kDisplaySize,
+    Setting.kDisplayOrientation,
+    Setting.kDisplayArrangement,
+    Setting.kDisplayResolution,
+    Setting.kDisplayRefreshRate,
+    Setting.kDisplayMirroring,
+    Setting.kAllowWindowsToSpanDisplays,
+    Setting.kAmbientColors,
+    Setting.kTouchscreenCalibration,
+    Setting.kDisplayOverscan,
+  ]);
+
   private browserProxy_: DevicePageBrowserProxy;
   private brightnessSliderMax_: number;
   private brightnessSliderMin_: number;
diff --git a/chrome/browser/resources/ash/settings/device_page/display_night_light.ts b/chrome/browser/resources/ash/settings/device_page/display_night_light.ts
index a110244..887aa73d 100644
--- a/chrome/browser/resources/ash/settings/device_page/display_night_light.ts
+++ b/chrome/browser/resources/ash/settings/device_page/display_night_light.ts
@@ -95,17 +95,6 @@
 
       nightLightScheduleSubLabel_: String,
 
-      /**
-       * Used by DeepLinkingMixin to focus this page's deep links.
-       */
-      supportedSettingIds: {
-        type: Object,
-        value: () => new Set<Setting>([
-          Setting.kNightLight,
-          Setting.kNightLightColorTemperature,
-        ]),
-      },
-
       shouldShowGeolocationWarningText_: {
         type: Boolean,
         computed: 'computeShouldShowGeolocationWarningText_(' +
@@ -165,6 +154,13 @@
   }
 
   isInternalDisplay: boolean;
+
+  // DeepLinkingMixin override
+  override supportedSettingIds = new Set<Setting>([
+    Setting.kNightLight,
+    Setting.kNightLightColorTemperature,
+  ]);
+
   private displaySettingsProvider: DisplaySettingsProviderInterface =
       getDisplaySettingsProvider();
   private nightLightScheduleSubLabel_: string;
diff --git a/chrome/browser/resources/ash/settings/device_page/keyboard.ts b/chrome/browser/resources/ash/settings/device_page/keyboard.ts
index 8855697..c5b1d4a3 100644
--- a/chrome/browser/resources/ash/settings/device_page/keyboard.ts
+++ b/chrome/browser/resources/ash/settings/device_page/keyboard.ts
@@ -117,23 +117,6 @@
       },
 
       /**
-       * Whether the setting for long press diacritics should be shown
-       */
-
-      /**
-       * Used by DeepLinkingMixin to focus this page's deep links.
-       */
-      supportedSettingIds: {
-        type: Object,
-        value: () => new Set<Setting>([
-          Setting.kKeyboardFunctionKeys,
-          Setting.kKeyboardAutoRepeat,
-          Setting.kKeyboardShortcuts,
-          Setting.kShowDiacritic,
-        ]),
-      },
-
-      /**
        * Whether settings should be split per device.
        */
       isDeviceSettingsSplitEnabled_: {
@@ -147,6 +130,15 @@
   }
 
   prefs: PrefsState;
+
+  // DeepLinkingMixin override
+  override supportedSettingIds = new Set<Setting>([
+    Setting.kKeyboardFunctionKeys,
+    Setting.kKeyboardAutoRepeat,
+    Setting.kKeyboardShortcuts,
+    Setting.kShowDiacritic,
+  ]);
+
   private browserProxy_: DevicePageBrowserProxy;
   private hasAssistantKey_: boolean;
   private hasLauncherKey_: boolean;
diff --git a/chrome/browser/resources/ash/settings/device_page/per_device_keyboard.ts b/chrome/browser/resources/ash/settings/device_page/per_device_keyboard.ts
index e16e9dc..66c3376 100644
--- a/chrome/browser/resources/ash/settings/device_page/per_device_keyboard.ts
+++ b/chrome/browser/resources/ash/settings/device_page/per_device_keyboard.ts
@@ -90,21 +90,17 @@
         value: [2000, 1000, 500, 300, 200, 100, 50, 30, 20],
         readOnly: true,
       },
-
-      /**
-       * Used by DeepLinkingMixin to focus this page's deep links.
-       */
-      supportedSettingIds: {
-        type: Object,
-        value: () => new Set<Setting>([
-          Setting.kKeyboardAutoRepeat,
-          Setting.kKeyboardShortcuts,
-        ]),
-      },
     };
   }
 
   prefs: PrefsState;
+
+  // DeepLinkingMixin override
+  override supportedSettingIds = new Set<Setting>([
+    Setting.kKeyboardAutoRepeat,
+    Setting.kKeyboardShortcuts,
+  ]);
+
   protected keyboards: Keyboard[];
   protected keyboardPolicies: KeyboardPolicies;
   private autoRepeatDelays: number[];
diff --git a/chrome/browser/resources/ash/settings/device_page/per_device_keyboard_subsection.ts b/chrome/browser/resources/ash/settings/device_page/per_device_keyboard_subsection.ts
index e35f03d1..79f4c2f 100644
--- a/chrome/browser/resources/ash/settings/device_page/per_device_keyboard_subsection.ts
+++ b/chrome/browser/resources/ash/settings/device_page/per_device_keyboard_subsection.ts
@@ -130,17 +130,6 @@
         value: '',
       },
 
-      /**
-       * Used by DeepLinkingMixin to focus this page's deep links.
-       */
-      supportedSettingIds: {
-        type: Object,
-        value: () => new Set<Setting>([
-          Setting.kKeyboardFunctionKeys,
-          Setting.kKeyboardRemapKeys,
-        ]),
-      },
-
       keyboardIndex: {
         type: Number,
       },
@@ -201,6 +190,12 @@
     }
   }
 
+  // DeepLinkingMixin override
+  override supportedSettingIds = new Set<Setting>([
+    Setting.kKeyboardFunctionKeys,
+    Setting.kKeyboardRemapKeys,
+  ]);
+
   protected keyboard: Keyboard;
   protected keyboardPolicies: KeyboardPolicies;
   private topRowAreFunctionKeysPref: chrome.settingsPrivate.PrefObject;
diff --git a/chrome/browser/resources/ash/settings/device_page/per_device_mouse_subsection.ts b/chrome/browser/resources/ash/settings/device_page/per_device_mouse_subsection.ts
index 1b6e791..12070c4f 100644
--- a/chrome/browser/resources/ash/settings/device_page/per_device_mouse_subsection.ts
+++ b/chrome/browser/resources/ash/settings/device_page/per_device_mouse_subsection.ts
@@ -165,20 +165,6 @@
         type: Object,
       },
 
-      /**
-       * Used by DeepLinkingMixin to focus this page's deep links.
-       */
-      supportedSettingIds: {
-        type: Object,
-        value: () => new Set<Setting>([
-          Setting.kMouseSwapPrimaryButtons,
-          Setting.kMouseReverseScrolling,
-          Setting.kMouseAcceleration,
-          Setting.kMouseScrollAcceleration,
-          Setting.kMouseSpeed,
-        ]),
-      },
-
       mouseIndex: {
         type: Number,
       },
@@ -261,6 +247,16 @@
   }
 
   isWelcomeExperienceEnabled: boolean;
+
+  // DeepLinkingMixin override
+  override supportedSettingIds = new Set<Setting>([
+    Setting.kMouseSwapPrimaryButtons,
+    Setting.kMouseReverseScrolling,
+    Setting.kMouseAcceleration,
+    Setting.kMouseScrollAcceleration,
+    Setting.kMouseSpeed,
+  ]);
+
   private mouse: Mouse;
   protected mousePolicies: MousePolicies;
   private primaryRightPref: chrome.settingsPrivate.PrefObject;
diff --git a/chrome/browser/resources/ash/settings/device_page/per_device_pointing_stick_subsection.ts b/chrome/browser/resources/ash/settings/device_page/per_device_pointing_stick_subsection.ts
index 4c1c6ed..f504402f 100644
--- a/chrome/browser/resources/ash/settings/device_page/per_device_pointing_stick_subsection.ts
+++ b/chrome/browser/resources/ash/settings/device_page/per_device_pointing_stick_subsection.ts
@@ -112,18 +112,6 @@
 
       pointingStick: {type: Object},
 
-      /**
-       * Used by DeepLinkingMixin to focus this page's deep links.
-       */
-      supportedSettingIds: {
-        type: Object,
-        value: () => new Set<Setting>([
-          Setting.kPointingStickAcceleration,
-          Setting.kPointingStickSpeed,
-          Setting.kPointingStickSwapPrimaryButtons,
-        ]),
-      },
-
       pointingStickIndex: {
         type: Number,
       },
@@ -156,6 +144,13 @@
     }
   }
 
+  // DeepLinkingMixin override
+  override supportedSettingIds = new Set<Setting>([
+    Setting.kPointingStickAcceleration,
+    Setting.kPointingStickSpeed,
+    Setting.kPointingStickSwapPrimaryButtons,
+  ]);
+
   private pointingStick: PointingStick;
   private sensitivityValues: number[];
   private swapPrimaryOptions: number[];
diff --git a/chrome/browser/resources/ash/settings/device_page/per_device_touchpad_subsection.ts b/chrome/browser/resources/ash/settings/device_page/per_device_touchpad_subsection.ts
index e449791..040d0c59 100644
--- a/chrome/browser/resources/ash/settings/device_page/per_device_touchpad_subsection.ts
+++ b/chrome/browser/resources/ash/settings/device_page/per_device_touchpad_subsection.ts
@@ -191,24 +191,6 @@
 
       touchpad: {type: Object},
 
-      /**
-       * Used by DeepLinkingMixin to focus this page's deep links.
-       */
-      supportedSettingIds: {
-        type: Object,
-        value: () => new Set<Setting>([
-          Setting.kTouchpadTapToClick,
-          Setting.kTouchpadTapDragging,
-          Setting.kTouchpadReverseScrolling,
-          Setting.kTouchpadAcceleration,
-          Setting.kTouchpadScrollAcceleration,
-          Setting.kTouchpadSpeed,
-          Setting.kTouchpadHapticFeedback,
-          Setting.kTouchpadHapticClickSensitivity,
-          Setting.kTouchpadSimulateRightClick,
-        ]),
-      },
-
       touchpadIndex: {
         type: Number,
       },
@@ -259,6 +241,19 @@
     }
   }
 
+  // DeepLinkingMixin override
+  override supportedSettingIds = new Set<Setting>([
+    Setting.kTouchpadTapToClick,
+    Setting.kTouchpadTapDragging,
+    Setting.kTouchpadReverseScrolling,
+    Setting.kTouchpadAcceleration,
+    Setting.kTouchpadScrollAcceleration,
+    Setting.kTouchpadSpeed,
+    Setting.kTouchpadHapticFeedback,
+    Setting.kTouchpadHapticClickSensitivity,
+    Setting.kTouchpadSimulateRightClick,
+  ]);
+
   private touchpad: Touchpad;
   private enableTapToClickPref: chrome.settingsPrivate.PrefObject;
   private enableTapDraggingPref: chrome.settingsPrivate.PrefObject;
diff --git a/chrome/browser/resources/ash/settings/device_page/pointers.ts b/chrome/browser/resources/ash/settings/device_page/pointers.ts
index 1739bf8..390d93e 100644
--- a/chrome/browser/resources/ash/settings/device_page/pointers.ts
+++ b/chrome/browser/resources/ash/settings/device_page/pointers.ts
@@ -109,29 +109,6 @@
       },
 
       /**
-       * Used by DeepLinkingMixin to focus this page's deep links.
-       */
-      supportedSettingIds: {
-        type: Object,
-        value: () => new Set<Setting>([
-          Setting.kTouchpadTapToClick,
-          Setting.kTouchpadTapDragging,
-          Setting.kTouchpadReverseScrolling,
-          Setting.kTouchpadAcceleration,
-          Setting.kTouchpadSpeed,
-          Setting.kTouchpadHapticFeedback,
-          Setting.kTouchpadHapticClickSensitivity,
-          Setting.kPointingStickAcceleration,
-          Setting.kPointingStickSpeed,
-          Setting.kPointingStickSwapPrimaryButtons,
-          Setting.kMouseSwapPrimaryButtons,
-          Setting.kMouseReverseScrolling,
-          Setting.kMouseAcceleration,
-          Setting.kMouseSpeed,
-        ]),
-      },
-
-      /**
        * Whether settings should be split per device.
        */
       isDeviceSettingsSplitEnabled_: {
@@ -148,6 +125,25 @@
   hasPointingStick: boolean;
   hasTouchpad: boolean;
   hasHapticTouchpad: boolean;
+
+  // DeepLinkingMixin override
+  override supportedSettingIds = new Set<Setting>([
+    Setting.kTouchpadTapToClick,
+    Setting.kTouchpadTapDragging,
+    Setting.kTouchpadReverseScrolling,
+    Setting.kTouchpadAcceleration,
+    Setting.kTouchpadSpeed,
+    Setting.kTouchpadHapticFeedback,
+    Setting.kTouchpadHapticClickSensitivity,
+    Setting.kPointingStickAcceleration,
+    Setting.kPointingStickSpeed,
+    Setting.kPointingStickSwapPrimaryButtons,
+    Setting.kMouseSwapPrimaryButtons,
+    Setting.kMouseReverseScrolling,
+    Setting.kMouseAcceleration,
+    Setting.kMouseSpeed,
+  ]);
+
   private isDeviceSettingsSplitEnabled_: boolean;
 
   /**
diff --git a/chrome/browser/resources/ash/settings/device_page/power.ts b/chrome/browser/resources/ash/settings/device_page/power.ts
index 87d1d96f..568b7c7 100644
--- a/chrome/browser/resources/ash/settings/device_page/power.ts
+++ b/chrome/browser/resources/ash/settings/device_page/power.ts
@@ -179,25 +179,19 @@
         computed:
             'computeBatterySaverHidden_(batteryStatus_, batterySaverFeatureEnabled_)',
       },
-
-      /**
-       * Used by DeepLinkingMixin to focus this page's deep links.
-       */
-      supportedSettingIds: {
-        type: Object,
-        value: () => new Set<Setting>([
-          Setting.kPowerIdleBehaviorWhileCharging,
-          Setting.kPowerSource,
-          Setting.kSleepWhenLaptopLidClosed,
-          Setting.kPowerIdleBehaviorWhileOnBattery,
-          Setting.kAdaptiveCharging,
-          Setting.kBatterySaver,
-        ]),
-      },
-
     };
   }
 
+  // DeepLinkingMixin override
+  override supportedSettingIds = new Set<Setting>([
+    Setting.kPowerIdleBehaviorWhileCharging,
+    Setting.kPowerSource,
+    Setting.kSleepWhenLaptopLidClosed,
+    Setting.kPowerIdleBehaviorWhileOnBattery,
+    Setting.kAdaptiveCharging,
+    Setting.kBatterySaver,
+  ]);
+
   private acIdleManaged_: boolean;
   private acIdleOptions_: IdleOption[];
   private adaptiveChargingEnabled_: boolean;
diff --git a/chrome/browser/resources/ash/settings/device_page/stylus.ts b/chrome/browser/resources/ash/settings/device_page/stylus.ts
index 73ccc15..0f9a9a7 100644
--- a/chrome/browser/resources/ash/settings/device_page/stylus.ts
+++ b/chrome/browser/resources/ash/settings/device_page/stylus.ts
@@ -94,22 +94,17 @@
         type: Boolean,
         value: false,
       },
-
-      /**
-       * Used by DeepLinkingMixin to focus this page's deep links.
-       */
-      supportedSettingIds: {
-        type: Object,
-        value: () => new Set<Setting>([
-          Setting.kStylusToolsInShelf,
-          Setting.kStylusNoteTakingApp,
-        ]),
-      },
-
     };
   }
 
   prefs: PrefsState;
+
+  // DeepLinkingMixin override
+  override supportedSettingIds = new Set<Setting>([
+    Setting.kStylusToolsInShelf,
+    Setting.kStylusNoteTakingApp,
+  ]);
+
   private appChoices_: NoteAppInfo[];
   private browserProxy_: DevicePageBrowserProxy;
   private selectedApp_: NoteAppInfo|null;
diff --git a/chrome/browser/resources/ash/settings/guest_os/guest_os_shared_usb_devices.ts b/chrome/browser/resources/ash/settings/guest_os/guest_os_shared_usb_devices.ts
index e6f5e20..a78d2127 100644
--- a/chrome/browser/resources/ash/settings/guest_os/guest_os_shared_usb_devices.ts
+++ b/chrome/browser/resources/ash/settings/guest_os/guest_os_shared_usb_devices.ts
@@ -123,23 +123,19 @@
           return [];
         },
       },
-
-      /**
-       * Used by DeepLinkingMixin to focus this page's deep links.
-       */
-      supportedSettingIds: {
-        type: Object,
-        value: () => new Set<Setting>([
-          Setting.kGuestUsbNotification,
-          Setting.kGuestUsbPersistentPassthrough,
-        ]),
-      },
     };
   }
 
   defaultGuestId: GuestId;
   guestOsType: GuestOsType;
   hasContainers: boolean;
+
+  // DeepLinkingMixin override
+  override supportedSettingIds = new Set<Setting>([
+    Setting.kGuestUsbNotification,
+    Setting.kGuestUsbPersistentPassthrough,
+  ]);
+
   private allContainers_: ContainerInfo[];
   private browserProxy_: GuestOsBrowserProxy;
   private reassignDevice_: GuestOsSharedUsbDevice|null;
diff --git a/chrome/browser/resources/ash/settings/internet_page/hotspot_subpage.ts b/chrome/browser/resources/ash/settings/internet_page/hotspot_subpage.ts
index 779c105..ef7a07d 100644
--- a/chrome/browser/resources/ash/settings/internet_page/hotspot_subpage.ts
+++ b/chrome/browser/resources/ash/settings/internet_page/hotspot_subpage.ts
@@ -69,19 +69,17 @@
           };
         },
       },
-
-      /**
-       * Used by DeepLinkingMixin to focus this page's deep links.
-       */
-      supportedSettingIds: {
-        type: Object,
-        value: () => new Set<Setting>(
-            [Setting.kHotspotOnOff, Setting.kHotspotAutoDisabled]),
-      },
     };
   }
 
   hotspotInfo: HotspotInfo|undefined;
+
+  // DeepLinkingMixin override
+  override supportedSettingIds = new Set<Setting>([
+    Setting.kHotspotOnOff,
+    Setting.kHotspotAutoDisabled,
+  ]);
+
   private isHotspotToggleOn_: boolean;
   private autoDisableVirtualPref_: chrome.settingsPrivate.PrefObject<boolean>;
 
diff --git a/chrome/browser/resources/ash/settings/internet_page/internet_detail_menu.ts b/chrome/browser/resources/ash/settings/internet_page/internet_detail_menu.ts
index 21d4be19..c1092ad 100644
--- a/chrome/browser/resources/ash/settings/internet_page/internet_detail_menu.ts
+++ b/chrome/browser/resources/ash/settings/internet_page/internet_detail_menu.ts
@@ -75,21 +75,17 @@
         type: String,
         value: '',
       },
-
-      /**
-       * Used by DeepLinkingMixin to focus this page's deep links.
-       */
-      supportedSettingIds: {
-        type: Object,
-        value: () => new Set<Setting>([
-          Setting.kCellularRenameESimNetwork,
-          Setting.kCellularRemoveESimNetwork,
-        ]),
-      },
     };
   }
 
   deviceState: OncMojo.DeviceStateProperties|undefined;
+
+  // DeepLinkingMixin override
+  override supportedSettingIds = new Set<Setting>([
+    Setting.kCellularRenameESimNetwork,
+    Setting.kCellularRemoveESimNetwork,
+  ]);
+
   private eSimNetworkState_: OncMojo.NetworkStateProperties|null;
   private isGuest_: boolean;
   private guid_: string;
diff --git a/chrome/browser/resources/ash/settings/internet_page/internet_detail_subpage.ts b/chrome/browser/resources/ash/settings/internet_page/internet_detail_subpage.ts
index 31e81b7..8d40ed4 100644
--- a/chrome/browser/resources/ash/settings/internet_page/internet_detail_subpage.ts
+++ b/chrome/browser/resources/ash/settings/internet_page/internet_detail_subpage.ts
@@ -335,38 +335,6 @@
       proxyExpanded_: Boolean,
 
       dataUsageExpanded_: Boolean,
-
-      /**
-       * Used by DeepLinkingMixin to focus this page's deep links.
-       */
-      supportedSettingIds: {
-        type: Object,
-        value: () => new Set<Setting>([
-          Setting.kConfigureEthernet,
-          Setting.kEthernetAutoConfigureIp,
-          Setting.kEthernetDns,
-          Setting.kEthernetProxy,
-          Setting.kDisconnectWifiNetwork,
-          Setting.kPreferWifiNetwork,
-          Setting.kForgetWifiNetwork,
-          Setting.kWifiAutoConfigureIp,
-          Setting.kWifiDns,
-          Setting.kWifiHidden,
-          Setting.kWifiProxy,
-          Setting.kWifiAutoConnectToNetwork,
-          Setting.kCellularSimLock,
-          Setting.kCellularRoaming,
-          Setting.kCellularApn,
-          Setting.kDisconnectCellularNetwork,
-          Setting.kCellularAutoConfigureIp,
-          Setting.kCellularDns,
-          Setting.kCellularProxy,
-          Setting.kCellularAutoConnectToNetwork,
-          Setting.kDisconnectTetherNetwork,
-          Setting.kWifiMetered,
-          Setting.kCellularMetered,
-        ]),
-      },
     };
   }
 
@@ -388,6 +356,34 @@
   globalPolicy?: GlobalPolicy;
   guid: string;
   managedNetworkAvailable: boolean;
+
+  // DeepLinkingMixin override
+  override supportedSettingIds = new Set<Setting>([
+    Setting.kConfigureEthernet,
+    Setting.kEthernetAutoConfigureIp,
+    Setting.kEthernetDns,
+    Setting.kEthernetProxy,
+    Setting.kDisconnectWifiNetwork,
+    Setting.kPreferWifiNetwork,
+    Setting.kForgetWifiNetwork,
+    Setting.kWifiAutoConfigureIp,
+    Setting.kWifiDns,
+    Setting.kWifiHidden,
+    Setting.kWifiProxy,
+    Setting.kWifiAutoConnectToNetwork,
+    Setting.kCellularSimLock,
+    Setting.kCellularRoaming,
+    Setting.kCellularApn,
+    Setting.kDisconnectCellularNetwork,
+    Setting.kCellularAutoConfigureIp,
+    Setting.kCellularDns,
+    Setting.kCellularProxy,
+    Setting.kCellularAutoConnectToNetwork,
+    Setting.kDisconnectTetherNetwork,
+    Setting.kWifiMetered,
+    Setting.kCellularMetered,
+  ]);
+
   private advancedExpanded_: boolean;
   private alwaysOnVpn_: chrome.settingsPrivate.PrefObject<boolean>;
   private applyingChanges_: boolean;
diff --git a/chrome/browser/resources/ash/settings/internet_page/internet_known_networks_subpage.ts b/chrome/browser/resources/ash/settings/internet_page/internet_known_networks_subpage.ts
index b0b170e..6fa435e 100644
--- a/chrome/browser/resources/ash/settings/internet_page/internet_known_networks_subpage.ts
+++ b/chrome/browser/resources/ash/settings/internet_page/internet_known_networks_subpage.ts
@@ -127,21 +127,17 @@
         type: Number,
         value: null,
       },
-
-      /**
-       * Used by DeepLinkingMixin to focus this page's deep links.
-       */
-      supportedSettingIds: {
-        type: Object,
-        value: () => new Set<Setting>([
-          Setting.kPreferWifiNetwork,
-          Setting.kForgetWifiNetwork,
-        ]),
-      },
     };
   }
 
   networkType: NetworkType|undefined;
+
+  // DeepLinkingMixin override
+  override supportedSettingIds = new Set<Setting>([
+    Setting.kPreferWifiNetwork,
+    Setting.kForgetWifiNetwork,
+  ]);
+
   private enableForget_: boolean;
   private networkConfig_: CrosNetworkConfigInterface;
   private networkStateList_: OncMojo.NetworkStateProperties[];
diff --git a/chrome/browser/resources/ash/settings/internet_page/internet_page.ts b/chrome/browser/resources/ash/settings/internet_page/internet_page.ts
index 1998358..a944a571 100644
--- a/chrome/browser/resources/ash/settings/internet_page/internet_page.ts
+++ b/chrome/browser/resources/ash/settings/internet_page/internet_page.ts
@@ -329,18 +329,6 @@
         value: '',
       },
 
-      /**
-       * Used by DeepLinkingMixin to focus this page's deep links.
-       */
-      supportedSettingIds: {
-        type: Object,
-        value: () => new Set<Setting>([
-          Setting.kWifiOnOff,
-          Setting.kMobileOnOff,
-          Setting.kCellularAddApn,
-        ]),
-      },
-
       errorToastMessage_: {
         type: String,
         value: '',
@@ -387,6 +375,14 @@
   deviceStates: Record<string, OncMojo.DeviceStateProperties>|undefined;
   hotspotInfo: HotspotInfo|undefined;
   managedNetworkAvailable: boolean;
+
+  // DeepLinkingMixin override
+  override supportedSettingIds = new Set<Setting>([
+    Setting.kWifiOnOff,
+    Setting.kMobileOnOff,
+    Setting.kCellularAddApn,
+  ]);
+
   private addConnectionExpanded_: boolean;
   private browserProxy_: InternetPageBrowserProxy;
   private cellularSetupDialogPageName_: CellularSetupPageName|null;
diff --git a/chrome/browser/resources/ash/settings/internet_page/internet_subpage.ts b/chrome/browser/resources/ash/settings/internet_page/internet_subpage.ts
index 10c5ec5..4e8f4f71 100644
--- a/chrome/browser/resources/ash/settings/internet_page/internet_subpage.ts
+++ b/chrome/browser/resources/ash/settings/internet_page/internet_subpage.ts
@@ -234,20 +234,6 @@
         type: Number,
         value: null,
       },
-
-      /**
-       * Used by DeepLinkingMixin to focus this page's deep links.
-       */
-      supportedSettingIds: {
-        type: Object,
-        value: () => new Set<Setting>([
-          Setting.kWifiOnOff,
-          Setting.kWifiAddNetwork,
-          Setting.kMobileOnOff,
-          Setting.kInstantTetheringOnOff,
-          Setting.kAddESimNetwork,
-        ]),
-      },
     };
   }
 
@@ -267,6 +253,16 @@
   showSpinner: boolean;
   tetherDeviceState: OncMojo.DeviceStateProperties|undefined;
   vpnProviders: VpnProvider[];
+
+  // DeepLinkingMixin override
+  override supportedSettingIds = new Set<Setting>([
+    Setting.kWifiOnOff,
+    Setting.kWifiAddNetwork,
+    Setting.kMobileOnOff,
+    Setting.kInstantTetheringOnOff,
+    Setting.kAddESimNetwork,
+  ]);
+
   private alwaysOnVpnMode_: AlwaysOnVpnMode|undefined;
   private alwaysOnVpnService_: string|undefined;
   private browserProxy_: InternetPageBrowserProxy;
diff --git a/chrome/browser/resources/ash/settings/kerberos_page/kerberos_accounts_subpage.ts b/chrome/browser/resources/ash/settings/kerberos_page/kerberos_accounts_subpage.ts
index f54ce5cc..0884d908 100644
--- a/chrome/browser/resources/ash/settings/kerberos_page/kerberos_accounts_subpage.ts
+++ b/chrome/browser/resources/ash/settings/kerberos_page/kerberos_accounts_subpage.ts
@@ -89,21 +89,16 @@
         type: String,
         value: '',
       },
-
-      /**
-       * Used by DeepLinkingMixin to focus this page's deep links.
-       */
-      supportedSettingIds: {
-        type: Object,
-        value: () => new Set<Setting>([
-          Setting.kAddKerberosTicketV2,
-          Setting.kRemoveKerberosTicketV2,
-          Setting.kSetActiveKerberosTicketV2,
-        ]),
-      },
     };
   }
 
+  // DeepLinkingMixin override
+  override supportedSettingIds = new Set<Setting>([
+    Setting.kAddKerberosTicketV2,
+    Setting.kRemoveKerberosTicketV2,
+    Setting.kSetActiveKerberosTicketV2,
+  ]);
+
   private accountToastText_: string;
   private accounts_: KerberosAccount[];
   private addAccountsAllowed_: boolean;
diff --git a/chrome/browser/resources/ash/settings/multidevice_page/multidevice_page.ts b/chrome/browser/resources/ash/settings/multidevice_page/multidevice_page.ts
index 5fc605a..7204596 100644
--- a/chrome/browser/resources/ash/settings/multidevice_page/multidevice_page.ts
+++ b/chrome/browser/resources/ash/settings/multidevice_page/multidevice_page.ts
@@ -164,20 +164,6 @@
       },
 
       /**
-       * Used by DeepLinkingMixin to focus this page's deep links.
-       */
-      supportedSettingIds: {
-        type: Object,
-        value: () => new Set<Setting>([
-          Setting.kSetUpMultiDevice,
-          Setting.kVerifyMultiDeviceSetup,
-          Setting.kMultiDeviceOnOff,
-          Setting.kNearbyShareDeviceVisibility,
-          Setting.kNearbyShareOnOff,
-        ]),
-      },
-
-      /**
        * Reflects the password sub-dialog property.
        */
       isPasswordDialogShowing_: {
@@ -239,6 +225,16 @@
   }
 
   isSettingsRetreived: boolean;
+
+  // DeepLinkingMixin override
+  override supportedSettingIds = new Set<Setting>([
+    Setting.kSetUpMultiDevice,
+    Setting.kVerifyMultiDeviceSetup,
+    Setting.kMultiDeviceOnOff,
+    Setting.kNearbyShareDeviceVisibility,
+    Setting.kNearbyShareOnOff,
+  ]);
+
   private authToken_: TokenInfo|undefined;
   private authTokenReply_: RequestTokenReply|undefined|null;
   private browserProxy_: MultiDeviceBrowserProxy;
diff --git a/chrome/browser/resources/ash/settings/multidevice_page/multidevice_subpage.ts b/chrome/browser/resources/ash/settings/multidevice_page/multidevice_subpage.ts
index dbfd765..9218dc3 100644
--- a/chrome/browser/resources/ash/settings/multidevice_page/multidevice_subpage.ts
+++ b/chrome/browser/resources/ash/settings/multidevice_page/multidevice_subpage.ts
@@ -48,28 +48,19 @@
     return getTemplate();
   }
 
-  static get properties() {
-    return {
-      /**
-       * Used by DeepLinkingMixin to focus this page's deep links.
-       */
-      supportedSettingIds: {
-        type: Object,
-        value: () => new Set<Setting>([
-          Setting.kInstantTetheringOnOff,
-          Setting.kMultiDeviceOnOff,
-          Setting.kSmartLockOnOff,
-          Setting.kForgetPhone,
-          Setting.kPhoneHubOnOff,
-          Setting.kPhoneHubCameraRollOnOff,
-          Setting.kPhoneHubNotificationsOnOff,
-          Setting.kPhoneHubTaskContinuationOnOff,
-          Setting.kWifiSyncOnOff,
-          Setting.kPhoneHubAppsOnOff,
-        ]),
-      },
-    };
-  }
+  // DeepLinkingMixin override
+  override supportedSettingIds = new Set<Setting>([
+    Setting.kInstantTetheringOnOff,
+    Setting.kMultiDeviceOnOff,
+    Setting.kSmartLockOnOff,
+    Setting.kForgetPhone,
+    Setting.kPhoneHubOnOff,
+    Setting.kPhoneHubCameraRollOnOff,
+    Setting.kPhoneHubNotificationsOnOff,
+    Setting.kPhoneHubTaskContinuationOnOff,
+    Setting.kWifiSyncOnOff,
+    Setting.kPhoneHubAppsOnOff,
+  ]);
 
   private browserProxy_: MultiDeviceBrowserProxy;
 
diff --git a/chrome/browser/resources/ash/settings/nearby_share_page/nearby_share_subpage.ts b/chrome/browser/resources/ash/settings/nearby_share_page/nearby_share_subpage.ts
index 0533b80..09b21e14 100644
--- a/chrome/browser/resources/ash/settings/nearby_share_page/nearby_share_subpage.ts
+++ b/chrome/browser/resources/ash/settings/nearby_share_page/nearby_share_subpage.ts
@@ -117,21 +117,6 @@
         value: () => loadTimeData.getBoolean('isQuickShareV2Enabled'),
       },
 
-      /**
-       * Used by DeepLinkingMixin to focus this page's deep links.
-       */
-      supportedSettingIds: {
-        type: Object,
-        value: () => new Set<Setting>([
-          Setting.kNearbyShareOnOff,
-          Setting.kNearbyShareDeviceName,
-          Setting.kNearbyShareDeviceVisibility,
-          Setting.kNearbyShareContacts,
-          Setting.kNearbyShareDataUsage,
-          Setting.kDevicesNearbyAreSharingNotificationOnOff,
-        ]),
-      },
-
       shouldShowFastInititationNotificationToggle_: {
         type: Boolean,
         computed: `computeShouldShowFastInititationNotificationToggle_(
@@ -158,6 +143,17 @@
 
   isSettingsRetreived: boolean;
   settings: NearbySettings;
+
+  // DeepLinkingMixin override
+  override supportedSettingIds = new Set<Setting>([
+    Setting.kNearbyShareOnOff,
+    Setting.kNearbyShareDeviceName,
+    Setting.kNearbyShareDeviceVisibility,
+    Setting.kNearbyShareContacts,
+    Setting.kNearbyShareDataUsage,
+    Setting.kDevicesNearbyAreSharingNotificationOnOff,
+  ]);
+
   private inHighVisibility_: boolean;
   private isQuickShareV2Enabled_: boolean;
   private manageContactsUrl_: string;
diff --git a/chrome/browser/resources/ash/settings/os_a11y_page/audio_and_captions_page.ts b/chrome/browser/resources/ash/settings/os_a11y_page/audio_and_captions_page.ts
index 682005fe..ae42f21 100644
--- a/chrome/browser/resources/ash/settings/os_a11y_page/audio_and_captions_page.ts
+++ b/chrome/browser/resources/ash/settings/os_a11y_page/audio_and_captions_page.ts
@@ -134,24 +134,20 @@
           ];
         },
       },
-
-      /**
-       * Used by DeepLinkingMixin to focus this page's deep links.
-       */
-      supportedSettingIds: {
-        type: Object,
-        value: () => new Set<Setting>([
-          Setting.kFlashNotifications,
-          Setting.kLiveCaption,
-          Setting.kMonoAudio,
-          Setting.kStartupSound,
-        ]),
-      },
     };
   }
 
   languages: LanguagesModel;
   languageHelper: LanguageHelper;
+
+  // DeepLinkingMixin override
+  override supportedSettingIds = new Set<Setting>([
+    Setting.kFlashNotifications,
+    Setting.kLiveCaption,
+    Setting.kMonoAudio,
+    Setting.kStartupSound,
+  ]);
+
   private audioAndCaptionsBrowserProxy_: AudioAndCaptionsPageBrowserProxy;
   private isKioskModeActive_: boolean;
 
diff --git a/chrome/browser/resources/ash/settings/os_a11y_page/cursor_and_touchpad_page.ts b/chrome/browser/resources/ash/settings/os_a11y_page/cursor_and_touchpad_page.ts
index cf5fde5..a88ad61 100644
--- a/chrome/browser/resources/ash/settings/os_a11y_page/cursor_and_touchpad_page.ts
+++ b/chrome/browser/resources/ash/settings/os_a11y_page/cursor_and_touchpad_page.ts
@@ -267,23 +267,6 @@
       },
 
       /**
-       * Used by DeepLinkingMixin to focus this page's deep links.
-       */
-      supportedSettingIds: {
-        type: Object,
-        value: () => new Set<Setting>([
-          Setting.kAutoClickWhenCursorStops,
-          Setting.kDisableTouchpad,
-          Setting.kEnableCursorColor,
-          Setting.kHighlightCursorWhileMoving,
-          Setting.kLargeCursor,
-          Setting.kMouseKeysEnabled,
-          Setting.kOverscrollEnabled,
-          Setting.kTabletNavigationButtons,
-        ]),
-      },
-
-      /**
        * Check if at least one mouse is connected.
        */
       hasMouse_: {
@@ -313,6 +296,18 @@
     ];
   }
 
+  // DeepLinkingMixin override
+  override supportedSettingIds = new Set<Setting>([
+    Setting.kAutoClickWhenCursorStops,
+    Setting.kDisableTouchpad,
+    Setting.kEnableCursorColor,
+    Setting.kHighlightCursorWhileMoving,
+    Setting.kLargeCursor,
+    Setting.kMouseKeysEnabled,
+    Setting.kOverscrollEnabled,
+    Setting.kTabletNavigationButtons,
+  ]);
+
   private autoClickDelayOptions_: Option[];
   private autoClickMovementThresholdOptions_: Option[];
   private cursorAndTouchpadBrowserProxy_: CursorAndTouchpadPageBrowserProxy;
diff --git a/chrome/browser/resources/ash/settings/os_a11y_page/display_and_magnification_subpage.ts b/chrome/browser/resources/ash/settings/os_a11y_page/display_and_magnification_subpage.ts
index 04990582..bf9722b 100644
--- a/chrome/browser/resources/ash/settings/os_a11y_page/display_and_magnification_subpage.ts
+++ b/chrome/browser/resources/ash/settings/os_a11y_page/display_and_magnification_subpage.ts
@@ -134,29 +134,24 @@
           return loadTimeData.getBoolean('isKioskModeActive');
         },
       },
-
-      /**
-       * Used by DeepLinkingMixin to focus this page's deep links.
-       */
-      supportedSettingIds: {
-        type: Object,
-        value: () => new Set<Setting>([
-          Setting.kAccessibilityMagnifierFollowsSts,
-          Setting.kColorCorrectionEnabled,
-          Setting.kColorCorrectionFilterType,
-          Setting.kColorCorrectionFilterAmount,
-          Setting.kDockedMagnifier,
-          Setting.kFullscreenMagnifier,
-          Setting.kFullscreenMagnifierMouseFollowingMode,
-          Setting.kFullscreenMagnifierFocusFollowing,
-          Setting.kMagnifierFollowsChromeVox,
-          Setting.kReducedAnimationsEnabled,
-          Setting.kAlwaysShowScrollbarsEnabled,
-        ]),
-      },
     };
   }
 
+  // DeepLinkingMixin override
+  override supportedSettingIds = new Set<Setting>([
+    Setting.kAccessibilityMagnifierFollowsSts,
+    Setting.kColorCorrectionEnabled,
+    Setting.kColorCorrectionFilterType,
+    Setting.kColorCorrectionFilterAmount,
+    Setting.kDockedMagnifier,
+    Setting.kFullscreenMagnifier,
+    Setting.kFullscreenMagnifierMouseFollowingMode,
+    Setting.kFullscreenMagnifierFocusFollowing,
+    Setting.kMagnifierFollowsChromeVox,
+    Setting.kReducedAnimationsEnabled,
+    Setting.kAlwaysShowScrollbarsEnabled,
+  ]);
+
   private isKioskModeActive_: boolean;
   private screenMagnifierMouseFollowingModePrefValues_: {[key: string]: number};
   private screenMagnifierZoomOptions_: Array<{value: number, name: string}>;
diff --git a/chrome/browser/resources/ash/settings/os_a11y_page/facegaze_subpage.ts b/chrome/browser/resources/ash/settings/os_a11y_page/facegaze_subpage.ts
index fb8c7ef..3eb711a 100644
--- a/chrome/browser/resources/ash/settings/os_a11y_page/facegaze_subpage.ts
+++ b/chrome/browser/resources/ash/settings/os_a11y_page/facegaze_subpage.ts
@@ -48,26 +48,20 @@
         computed:
             'getToggleLabel_(prefs.settings.a11y.face_gaze.enabled_sentinel.value)',
       },
-
-      supportedSettingIds: {
-        type: Object,
-        value: () => new Set<Setting>([
-          Setting.kFaceGaze,
-        ]),
-      },
     };
   }
 
+  // DeepLinkingMixin override
+  override supportedSettingIds = new Set<Setting>([
+    Setting.kFaceGaze,
+  ]);
+
   private getToggleLabel_(): string {
     return this.getPref('settings.a11y.face_gaze.enabled_sentinel').value ?
         this.i18n('deviceOn') :
         this.i18n('deviceOff');
   }
 
-  static get observers() {
-    return [];
-  }
-
   override currentRouteChanged(route: Route): void {
     // Does not apply to this page.
     if (route !== routes.MANAGE_FACEGAZE_SETTINGS) {
diff --git a/chrome/browser/resources/ash/settings/os_a11y_page/keyboard_and_text_input_page.ts b/chrome/browser/resources/ash/settings/os_a11y_page/keyboard_and_text_input_page.ts
index 4edcd81d..fea7d0a6 100644
--- a/chrome/browser/resources/ash/settings/os_a11y_page/keyboard_and_text_input_page.ts
+++ b/chrome/browser/resources/ash/settings/os_a11y_page/keyboard_and_text_input_page.ts
@@ -114,25 +114,6 @@
         },
       },
 
-      /**
-       * Used by DeepLinkingMixin to focus this page's deep links.
-       */
-      supportedSettingIds: {
-        type: Object,
-        value: () => new Set<Setting>([
-          Setting.kBounceKeys,
-          Setting.kCaretBlinkInterval,
-          Setting.kCaretBrowsing,
-          Setting.kDictation,
-          Setting.kEnableSwitchAccess,
-          Setting.kHighlightKeyboardFocus,
-          Setting.kHighlightTextCaret,
-          Setting.kOnScreenKeyboard,
-          Setting.kSlowKeys,
-          Setting.kStickyKeys,
-        ]),
-      },
-
       focusHighlightEnabledVirtualPref_: {
         type: Object,
         computed: 'computeEnabledWithConflictingFeature_(' +
@@ -186,6 +167,20 @@
     ];
   }
 
+  // DeepLinkingMixin override
+  override supportedSettingIds = new Set<Setting>([
+    Setting.kBounceKeys,
+    Setting.kCaretBlinkInterval,
+    Setting.kCaretBrowsing,
+    Setting.kDictation,
+    Setting.kEnableSwitchAccess,
+    Setting.kHighlightKeyboardFocus,
+    Setting.kHighlightTextCaret,
+    Setting.kOnScreenKeyboard,
+    Setting.kSlowKeys,
+    Setting.kStickyKeys,
+  ]);
+
   private dictationLearnMoreUrl_: string;
   private dictationLocaleMenuSubtitle_: string;
   private dictationLocaleOptions_: LocaleInfo[];
diff --git a/chrome/browser/resources/ash/settings/os_a11y_page/mouse_keys_subpage.ts b/chrome/browser/resources/ash/settings/os_a11y_page/mouse_keys_subpage.ts
index 8d9e216..3feedf4 100644
--- a/chrome/browser/resources/ash/settings/os_a11y_page/mouse_keys_subpage.ts
+++ b/chrome/browser/resources/ash/settings/os_a11y_page/mouse_keys_subpage.ts
@@ -71,13 +71,6 @@
             'getToggleLabel_(prefs.settings.a11y.mouse_keys.enabled.value)',
       },
 
-      supportedSettingIds: {
-        type: Object,
-        value: () => new Set<Setting>([
-          Setting.kMouseKeysEnabled,
-        ]),
-      },
-
       mouseKeysDominantHandOptions_: {
         readOnly: true,
         type: Array,
@@ -208,6 +201,11 @@
     };
   }
 
+  // DeepLinkingMixin override
+  override supportedSettingIds = new Set<Setting>([
+    Setting.kMouseKeysEnabled,
+  ]);
+
   private primaryKeyboardRightHandPreviewOptions_: KeyboardPreviewOption[];
   private primaryKeyboardLeftHandPreviewOptions_: KeyboardPreviewOption[];
 
diff --git a/chrome/browser/resources/ash/settings/os_a11y_page/os_a11y_page.ts b/chrome/browser/resources/ash/settings/os_a11y_page/os_a11y_page.ts
index dd8cb70..16ef717 100644
--- a/chrome/browser/resources/ash/settings/os_a11y_page/os_a11y_page.ts
+++ b/chrome/browser/resources/ash/settings/os_a11y_page/os_a11y_page.ts
@@ -101,18 +101,6 @@
       },
 
       languageHelper: Object,
-
-      /**
-       * Used by DeepLinkingMixin to focus this page's deep links.
-       */
-      supportedSettingIds: {
-        type: Object,
-        value: () => new Set<Setting>([
-          Setting.kA11yQuickSettings,
-          Setting.kGetImageDescriptionsFromGoogle,
-          Setting.kLiveCaption,
-        ]),
-      },
     };
   }
 
@@ -120,6 +108,13 @@
   languages: LanguagesModel;
   languageHelper: LanguageHelper;
 
+  // DeepLinkingMixin override
+  override supportedSettingIds = new Set<Setting>([
+    Setting.kA11yQuickSettings,
+    Setting.kGetImageDescriptionsFromGoogle,
+    Setting.kLiveCaption,
+  ]);
+
   private browserProxy_: OsA11yPageBrowserProxy;
   private hasScreenReader_: boolean;
   private isGuest_: boolean;
diff --git a/chrome/browser/resources/ash/settings/os_a11y_page/select_to_speak_subpage.ts b/chrome/browser/resources/ash/settings/os_a11y_page/select_to_speak_subpage.ts
index edd94ed..f1db633 100644
--- a/chrome/browser/resources/ash/settings/os_a11y_page/select_to_speak_subpage.ts
+++ b/chrome/browser/resources/ash/settings/os_a11y_page/select_to_speak_subpage.ts
@@ -215,18 +215,6 @@
               loadTimeData.getString('selectToSpeakLearnMoreUrl');
         },
       },
-
-      /**
-       * Used by DeepLinkingMixin to focus this page's deep links.
-       */
-      supportedSettingIds: {
-        type: Object,
-        value: () => new Set<Setting>([
-          Setting.kSelectToSpeakWordHighlight,
-          Setting.kSelectToSpeakBackgroundShading,
-          Setting.kSelectToSpeakNavigationControls,
-        ]),
-      },
     };
   }
 
@@ -242,6 +230,13 @@
     ];
   }
 
+  // DeepLinkingMixin override
+  override supportedSettingIds = new Set<Setting>([
+    Setting.kSelectToSpeakWordHighlight,
+    Setting.kSelectToSpeakBackgroundShading,
+    Setting.kSelectToSpeakNavigationControls,
+  ]);
+
   private langBrowserProxy_: LanguagesBrowserProxy;
   private enhancedNetworkVoicesVirtualPref_:
       chrome.settingsPrivate.PrefObject<boolean>;
diff --git a/chrome/browser/resources/ash/settings/os_a11y_page/switch_access_subpage.ts b/chrome/browser/resources/ash/settings/os_a11y_page/switch_access_subpage.ts
index d0592bd..374134f586 100644
--- a/chrome/browser/resources/ash/settings/os_a11y_page/switch_access_subpage.ts
+++ b/chrome/browser/resources/ash/settings/os_a11y_page/switch_access_subpage.ts
@@ -152,18 +152,6 @@
         value: 1,
       },
 
-      /**
-       * Used by DeepLinkingMixin to focus this page's deep links.
-       */
-      supportedSettingIds: {
-        type: Object,
-        value: () => new Set<Setting>([
-          Setting.kSwitchActionAssignment,
-          Setting.kSwitchActionAutoScan,
-          Setting.kSwitchActionAutoScanKeyboard,
-        ]),
-      },
-
       showSwitchAccessActionAssignmentDialog_: {
         type: Boolean,
         value: false,
@@ -187,6 +175,13 @@
     };
   }
 
+  // DeepLinkingMixin override
+  override supportedSettingIds = new Set<Setting>([
+    Setting.kSwitchActionAssignment,
+    Setting.kSwitchActionAutoScan,
+    Setting.kSwitchActionAutoScanKeyboard,
+  ]);
+
   private action_: SwitchAccessCommand|null;
   private autoScanSpeedRangeMs_: number[];
   private focusAfterDialogClose_: HTMLElement|null;
diff --git a/chrome/browser/resources/ash/settings/os_a11y_page/text_to_speech_subpage.ts b/chrome/browser/resources/ash/settings/os_a11y_page/text_to_speech_subpage.ts
index 6954fad..7911d898 100644
--- a/chrome/browser/resources/ash/settings/os_a11y_page/text_to_speech_subpage.ts
+++ b/chrome/browser/resources/ash/settings/os_a11y_page/text_to_speech_subpage.ts
@@ -67,21 +67,17 @@
        * Indicate whether a screen reader is enabled.
        */
       hasScreenReader: Boolean,
-
-      /**
-       * Used by DeepLinkingMixin to focus this page's deep links.
-       */
-      supportedSettingIds: {
-        type: Object,
-        value: () => new Set<Setting>([
-          Setting.kChromeVox,
-          Setting.kSelectToSpeak,
-        ]),
-      },
     };
   }
 
   hasScreenReader: boolean;
+
+  // DeepLinkingMixin override
+  override supportedSettingIds = new Set<Setting>([
+    Setting.kChromeVox,
+    Setting.kSelectToSpeak,
+  ]);
+
   private deviceBrowserProxy_: DevicePageBrowserProxy;
   private hasKeyboard_: boolean;
   private textToSpeechBrowserProxy_: TextToSpeechSubpageBrowserProxy;
diff --git a/chrome/browser/resources/ash/settings/os_a11y_page/tts_voice_subpage.ts b/chrome/browser/resources/ash/settings/os_a11y_page/tts_voice_subpage.ts
index 2d7cb77..36c1abf 100644
--- a/chrome/browser/resources/ash/settings/os_a11y_page/tts_voice_subpage.ts
+++ b/chrome/browser/resources/ash/settings/os_a11y_page/tts_voice_subpage.ts
@@ -149,20 +149,6 @@
         type: Boolean,
         value: false,
       },
-
-      /**
-       * Used by DeepLinkingMixin to focus this page's deep links.
-       */
-      supportedSettingIds: {
-        type: Object,
-        value: () => new Set<Setting>([
-          Setting.kTextToSpeechRate,
-          Setting.kTextToSpeechPitch,
-          Setting.kTextToSpeechVolume,
-          Setting.kTextToSpeechVoice,
-          Setting.kTextToSpeechEngines,
-        ]),
-      },
     };
   }
 
@@ -173,6 +159,16 @@
   languagesOpened: boolean;
   languagesToVoices: TtsLanguage[];
   prefs: PrefsState;
+
+  // DeepLinkingMixin override
+  override supportedSettingIds = new Set<Setting>([
+    Setting.kTextToSpeechRate,
+    Setting.kTextToSpeechPitch,
+    Setting.kTextToSpeechVolume,
+    Setting.kTextToSpeechVoice,
+    Setting.kTextToSpeechEngines,
+  ]);
+
   private isPreviewing_: boolean;
   private langBrowserProxy_: LanguagesBrowserProxy;
   private previewText_: string;
diff --git a/chrome/browser/resources/ash/settings/os_about_page/detailed_build_info_subpage.ts b/chrome/browser/resources/ash/settings/os_about_page/detailed_build_info_subpage.ts
index 5aedd812..c0af283 100644
--- a/chrome/browser/resources/ash/settings/os_about_page/detailed_build_info_subpage.ts
+++ b/chrome/browser/resources/ash/settings/os_about_page/detailed_build_info_subpage.ts
@@ -84,18 +84,6 @@
         value: '',
       },
 
-      /**
-       * Used by DeepLinkingMixin to focus this page's deep links.
-       */
-      supportedSettingIds: {
-        type: Object,
-        value: () => new Set<Setting>([
-          Setting.kChangeChromeChannel,
-          Setting.kChangeDeviceName,
-          Setting.kCopyDetailedBuildInfo,
-        ]),
-      },
-
       shouldHideEolInfo_: {
         type: Boolean,
         computed: 'computeShouldHideEolInfo_(eolMessageWithMonthAndYear)',
@@ -158,6 +146,13 @@
     };
   }
 
+  // DeepLinkingMixin override
+  override supportedSettingIds = new Set<Setting>([
+    Setting.kChangeChromeChannel,
+    Setting.kChangeDeviceName,
+    Setting.kCopyDetailedBuildInfo,
+  ]);
+
   private versionInfo_: VersionInfo;
   private channelInfo_: ChannelInfo;
   private deviceNameMetadata_: DeviceNameMetadata;
diff --git a/chrome/browser/resources/ash/settings/os_about_page/os_about_page.ts b/chrome/browser/resources/ash/settings/os_about_page/os_about_page.ts
index 8a53799..9aefa3583 100644
--- a/chrome/browser/resources/ash/settings/os_about_page/os_about_page.ts
+++ b/chrome/browser/resources/ash/settings/os_about_page/os_about_page.ts
@@ -224,22 +224,6 @@
       },
 
       /**
-       * Used by DeepLinkingMixin to focus this page's deep links.
-       */
-      supportedSettingIds: {
-        type: Object,
-        value: () => new Set<Setting>([
-          Setting.kCheckForOsUpdate,
-          Setting.kSeeWhatsNew,
-          Setting.kGetHelpWithChromeOs,
-          Setting.kReportAnIssue,
-          Setting.kTermsOfService,
-          Setting.kDiagnostics,
-          Setting.kFirmwareUpdates,
-        ]),
-      },
-
-      /**
        * Controls whether the extended updates opt-in option is shown.
        */
       showExtendedUpdatesOption_: {
@@ -292,6 +276,17 @@
     ];
   }
 
+  // DeepLinkingMixin override
+  override supportedSettingIds = new Set<Setting>([
+    Setting.kCheckForOsUpdate,
+    Setting.kSeeWhatsNew,
+    Setting.kGetHelpWithChromeOs,
+    Setting.kReportAnIssue,
+    Setting.kTermsOfService,
+    Setting.kDiagnostics,
+    Setting.kFirmwareUpdates,
+  ]);
+
   private isDarkModeActive_: boolean;
   private currentUpdateStatusEvent_: UpdateStatusChangedEvent;
   private isManaged_: boolean;
diff --git a/chrome/browser/resources/ash/settings/os_apps_page/android_apps_subpage.ts b/chrome/browser/resources/ash/settings/os_apps_page/android_apps_subpage.ts
index f32f0178..0c71bd41 100644
--- a/chrome/browser/resources/ash/settings/os_apps_page/android_apps_subpage.ts
+++ b/chrome/browser/resources/ash/settings/os_apps_page/android_apps_subpage.ts
@@ -73,22 +73,18 @@
 
       /** Whether Arc VM manage usb subpage should be shown. */
       isArcVmManageUsbAvailable: Boolean,
-
-      /**
-       * Used by DeepLinkingMixin to focus this page's deep links.
-       */
-      supportedSettingIds: {
-        type: Object,
-        value: () => new Set<Setting>([
-          Setting.kManageAndroidPreferences,
-          Setting.kRemovePlayStore,
-        ]),
-      },
     };
   }
 
   androidAppsInfo: AndroidAppsInfo;
   isArcVmManageUsbAvailable: boolean;
+
+  // DeepLinkingMixin override
+  override supportedSettingIds = new Set<Setting>([
+    Setting.kManageAndroidPreferences,
+    Setting.kRemovePlayStore,
+  ]);
+
   private dialogBody_: string;
   private playStoreEnabled_: boolean;
 
diff --git a/chrome/browser/resources/ash/settings/os_apps_page/app_notifications_page/app_notifications_subpage.ts b/chrome/browser/resources/ash/settings/os_apps_page/app_notifications_page/app_notifications_subpage.ts
index 167cc3ba..9694a10 100644
--- a/chrome/browser/resources/ash/settings/os_apps_page/app_notifications_page/app_notifications_subpage.ts
+++ b/chrome/browser/resources/ash/settings/os_apps_page/app_notifications_page/app_notifications_subpage.ts
@@ -73,21 +73,17 @@
         type: Array,
         value: [],
       },
-
-      /**
-       * Used by DeepLinkingMixin to focus this page's deep links.
-       */
-      supportedSettingIds: {
-        type: Object,
-        value: () => new Set<Setting>([
-          Setting.kDoNotDisturbOnOff,
-          Setting.kAppBadgingOnOff,
-        ]),
-      },
     };
   }
 
   prefs: PrefsState;
+
+  // DeepLinkingMixin override
+  override supportedSettingIds = new Set<Setting>([
+    Setting.kDoNotDisturbOnOff,
+    Setting.kAppBadgingOnOff,
+  ]);
+
   private appList_: App[];
   private appNotificationsObserverReceiver_: AppNotificationsObserverReceiver|
       null;
diff --git a/chrome/browser/resources/ash/settings/os_apps_page/os_apps_page.ts b/chrome/browser/resources/ash/settings/os_apps_page/os_apps_page.ts
index a668e64..5a1329d 100644
--- a/chrome/browser/resources/ash/settings/os_apps_page/os_apps_page.ts
+++ b/chrome/browser/resources/ash/settings/os_apps_page/os_apps_page.ts
@@ -205,23 +205,19 @@
         type: Boolean,
         value: false,
       },
-
-      /**
-       * Used by DeepLinkingMixin to focus this page's deep links.
-       */
-      supportedSettingIds: {
-        type: Object,
-        value: () => new Set<Setting>([
-          Setting.kManageAndroidPreferences,
-          Setting.kTurnOnPlayStore,
-          Setting.kAppParentalControls,
-        ]),
-      },
     };
   }
 
   androidAppsInfo: AndroidAppsInfo;
   searchTerm: string;
+
+  // DeepLinkingMixin override
+  override supportedSettingIds = new Set<Setting>([
+    Setting.kManageAndroidPreferences,
+    Setting.kTurnOnPlayStore,
+    Setting.kAppParentalControls,
+  ]);
+
   private app_: App;
   private appNotificationsObserverReceiver_: AppNotificationsObserverReceiver;
   private appsWithNotifications_: AppWithNotifications[];
diff --git a/chrome/browser/resources/ash/settings/os_bluetooth_page/os_bluetooth_devices_subpage.ts b/chrome/browser/resources/ash/settings/os_bluetooth_page/os_bluetooth_devices_subpage.ts
index 248665f0..f985185 100644
--- a/chrome/browser/resources/ash/settings/os_bluetooth_page/os_bluetooth_devices_subpage.ts
+++ b/chrome/browser/resources/ash/settings/os_bluetooth_page/os_bluetooth_devices_subpage.ts
@@ -54,15 +54,6 @@
       },
 
       /**
-       * Used by DeepLinkingMixin to focus this page's deep links.
-       */
-      supportedSettingIds: {
-        type: Object,
-        value: () =>
-            new Set<Setting>([Setting.kBluetoothOnOff, Setting.kFastPairOnOff]),
-      },
-
-      /**
        * Reflects the current state of the toggle button. This will be set when
        * the |systemProperties| state changes or when the user presses the
        * toggle.
@@ -100,6 +91,12 @@
     };
   }
 
+  // DeepLinkingMixin override
+  override supportedSettingIds = new Set<Setting>([
+    Setting.kBluetoothOnOff,
+    Setting.kFastPairOnOff,
+  ]);
+
   systemProperties: BluetoothSystemProperties;
   private browserProxy_: OsBluetoothDevicesSubpageBrowserProxy;
   private connectedDevices_: PairedBluetoothDeviceProperties[];
diff --git a/chrome/browser/resources/ash/settings/os_files_page/files_settings_card.ts b/chrome/browser/resources/ash/settings/os_files_page/files_settings_card.ts
index 47037f4b..2e5cd23 100644
--- a/chrome/browser/resources/ash/settings/os_files_page/files_settings_card.ts
+++ b/chrome/browser/resources/ash/settings/os_files_page/files_settings_card.ts
@@ -26,7 +26,6 @@
 import {assertExhaustive} from '../assert_extras.js';
 import {DeepLinkingMixin} from '../common/deep_linking_mixin.js';
 import {RouteOriginMixin} from '../common/route_origin_mixin.js';
-import type {Setting} from '../mojom-webui/setting.mojom-webui.js';
 import type {Route} from '../router.js';
 import {Router, routes} from '../router.js';
 
@@ -48,14 +47,6 @@
 
   static get properties() {
     return {
-      /**
-       * Used by DeepLinkingMixin to focus this page's deep links.
-       */
-      supportedSettingIds: {
-        type: Object,
-        value: () => new Set<Setting>([]),
-      },
-
       bulkPinningPrefEnabled_: Boolean,
       mirrorSyncPrefEnabled_: Boolean,
 
diff --git a/chrome/browser/resources/ash/settings/os_files_page/google_drive_subpage.ts b/chrome/browser/resources/ash/settings/os_files_page/google_drive_subpage.ts
index f0144128..b86d733 100644
--- a/chrome/browser/resources/ash/settings/os_files_page/google_drive_subpage.ts
+++ b/chrome/browser/resources/ash/settings/os_files_page/google_drive_subpage.ts
@@ -82,15 +82,6 @@
   static get properties() {
     return {
       /**
-       * Used by DeepLinkingMixin to focus this page's deep links.
-       */
-      supportedSettingIds: {
-        type: Object,
-        value: () => new Set<Setting>(
-            [Setting.kGoogleDriveRemoveAccess, Setting.kGoogleDriveFileSync]),
-      },
-
-      /**
        * Ensures the data binding is updated on the UI when
        * `contentCacheSize_` is updated.
        */
@@ -137,6 +128,12 @@
     ];
   }
 
+  // DeepLinkingMixin override
+  override supportedSettingIds = new Set<Setting>([
+    Setting.kGoogleDriveRemoveAccess,
+    Setting.kGoogleDriveFileSync,
+  ]);
+
   /**
    * Reflects the state of `prefs.gdata.disabled` pref.
    */
diff --git a/chrome/browser/resources/ash/settings/os_people_page/additional_accounts_settings_card.ts b/chrome/browser/resources/ash/settings/os_people_page/additional_accounts_settings_card.ts
index 7087ff9..55c9a477 100644
--- a/chrome/browser/resources/ash/settings/os_people_page/additional_accounts_settings_card.ts
+++ b/chrome/browser/resources/ash/settings/os_people_page/additional_accounts_settings_card.ts
@@ -93,21 +93,17 @@
         },
         readOnly: true,
       },
-
-      /**
-       * Used by DeepLinkingMixin to focus this page's deep links.
-       */
-      supportedSettingIds: {
-        type: Object,
-        value: () => new Set<Setting>([
-          Setting.kAddAccount,
-          Setting.kRemoveAccount,
-        ]),
-      },
     };
   }
 
   accounts: Account[];
+
+  // DeepLinkingMixin override
+  override supportedSettingIds = new Set<Setting>([
+    Setting.kAddAccount,
+    Setting.kRemoveAccount,
+  ]);
+
   private actionMenuAccount_: Account|null;
   private browserProxy_: AccountManagerBrowserProxy;
   private isChildUser_: boolean;
diff --git a/chrome/browser/resources/ash/settings/os_people_page/fingerprint_list_subpage.ts b/chrome/browser/resources/ash/settings/os_people_page/fingerprint_list_subpage.ts
index 80fd9b89..a579c96 100644
--- a/chrome/browser/resources/ash/settings/os_people_page/fingerprint_list_subpage.ts
+++ b/chrome/browser/resources/ash/settings/os_people_page/fingerprint_list_subpage.ts
@@ -70,21 +70,17 @@
         type: Boolean,
         value: true,
       },
-
-      /**
-       * Used by DeepLinkingMixin to focus this page's deep links.
-       */
-      supportedSettingIds: {
-        type: Object,
-        value: () => new Set<Setting>([
-          Setting.kAddFingerprintV2,
-          Setting.kRemoveFingerprintV2,
-        ]),
-      },
     };
   }
 
   authToken: string|undefined;
+
+  // DeepLinkingMixin override
+  override supportedSettingIds = new Set<Setting>([
+    Setting.kAddFingerprintV2,
+    Setting.kRemoveFingerprintV2,
+  ]);
+
   private fingerprints_: string[];
   private showSetupFingerprintDialog_: boolean;
   private allowAddAnotherFinger_: boolean;
diff --git a/chrome/browser/resources/ash/settings/os_people_page/graduation/graduation_settings_card.ts b/chrome/browser/resources/ash/settings/os_people_page/graduation/graduation_settings_card.ts
index eb1b3c91..63861474 100644
--- a/chrome/browser/resources/ash/settings/os_people_page/graduation/graduation_settings_card.ts
+++ b/chrome/browser/resources/ash/settings/os_people_page/graduation/graduation_settings_card.ts
@@ -36,19 +36,10 @@
     return getTemplate();
   }
 
-  static get properties() {
-    return {
-      /**
-       * Used by DeepLinkingMixin to focus this page's deep links.
-       */
-      supportedSettingIds: {
-        type: Object,
-        value: () => new Set<Setting>([
-          Setting.kGraduation,
-        ]),
-      },
-    };
-  }
+  // DeepLinkingMixin override
+  override supportedSettingIds = new Set<Setting>([
+    Setting.kGraduation,
+  ]);
 
   override currentRouteChanged(newRoute: Route): void {
     if (newRoute !== routes.OS_PEOPLE) {
diff --git a/chrome/browser/resources/ash/settings/os_people_page/lock_screen_subpage.ts b/chrome/browser/resources/ash/settings/os_people_page/lock_screen_subpage.ts
index 8508640..a5fcfef4 100644
--- a/chrome/browser/resources/ash/settings/os_people_page/lock_screen_subpage.ts
+++ b/chrome/browser/resources/ash/settings/os_people_page/lock_screen_subpage.ts
@@ -156,19 +156,6 @@
       showDisableRecoveryDialog_: Boolean,
 
       /**
-       * Used by DeepLinkingMixin to focus this page's deep links.
-       */
-      supportedSettingIds: {
-        type: Object,
-        value: () => new Set<Setting>([
-          Setting.kLockScreenV2,
-          Setting.kChangeAuthPinV2,
-          Setting.kLockScreenNotification,
-          Setting.kDataRecovery,
-        ]),
-      },
-
-      /**
        * Whether the device account is managed.
        */
       deviceAccountManaged_: {
@@ -183,6 +170,15 @@
 
   prefs: PrefsState;
   authToken: string|undefined;
+
+  // DeepLinkingMixin override
+  override supportedSettingIds = new Set<Setting>([
+    Setting.kLockScreenV2,
+    Setting.kChangeAuthPinV2,
+    Setting.kLockScreenNotification,
+    Setting.kDataRecovery,
+  ]);
+
   private fingerprintUnlockEnabled_: boolean;
   private numFingerprints_: number;
   private numFingerprintDescription_: string;
diff --git a/chrome/browser/resources/ash/settings/os_people_page/os_sync_controls_subpage.ts b/chrome/browser/resources/ash/settings/os_people_page/os_sync_controls_subpage.ts
index 35f87d5..28e2909 100644
--- a/chrome/browser/resources/ash/settings/os_people_page/os_sync_controls_subpage.ts
+++ b/chrome/browser/resources/ash/settings/os_people_page/os_sync_controls_subpage.ts
@@ -85,17 +85,14 @@
         value: true,
         computed: `computeDataTypeTogglesDisabled_(osSyncPrefs.syncAllOsTypes)`,
       },
-
-      /**
-       * Used by DeepLinkingMixin to focus this page's deep links.
-       */
-      supportedSettingIds: {
-        type: Object,
-        value: () => new Set<Setting>([Setting.kSplitSyncOnOff]),
-      },
     };
   }
 
+  // DeepLinkingMixin override
+  override supportedSettingIds = new Set<Setting>([
+    Setting.kSplitSyncOnOff,
+  ]);
+
   private areDataTypeTogglesDisabled_: boolean;
   private supportedSettingsIds: Set<Setting>;
   private browserProxy_: OsSyncBrowserProxy;
diff --git a/chrome/browser/resources/ash/settings/os_printing_page/cups_printers.ts b/chrome/browser/resources/ash/settings/os_printing_page/cups_printers.ts
index c144772..9a26ef33 100644
--- a/chrome/browser/resources/ash/settings/os_printing_page/cups_printers.ts
+++ b/chrome/browser/resources/ash/settings/os_printing_page/cups_printers.ts
@@ -203,18 +203,6 @@
       },
 
       /**
-       * Used by DeepLinkingMixin to focus this page's deep links.
-       */
-      supportedSettingIds: {
-        type: Object,
-        value: () => new Set<Setting>([
-          Setting.kAddPrinter,
-          Setting.kSavedPrinters,
-          Setting.kPrintJobs,
-        ]),
-      },
-
-      /**
        * Indicates whether the nearby printers section is expanded.
        * @private {boolean}
        */
@@ -240,6 +228,13 @@
   printers: CupsPrinterInfo[];
   searchTerm: string;
 
+  // DeepLinkingMixin override
+  override supportedSettingIds = new Set<Setting>([
+    Setting.kAddPrinter,
+    Setting.kSavedPrinters,
+    Setting.kPrintJobs,
+  ]);
+
   private addPrintServerResultText_: string;
   private addPrinterResultText_: string;
   private attemptedLoadingPrinters_: boolean;
diff --git a/chrome/browser/resources/ash/settings/os_printing_page/printing_settings_card.ts b/chrome/browser/resources/ash/settings/os_printing_page/printing_settings_card.ts
index c3dffbc1..5bc681f 100644
--- a/chrome/browser/resources/ash/settings/os_printing_page/printing_settings_card.ts
+++ b/chrome/browser/resources/ash/settings/os_printing_page/printing_settings_card.ts
@@ -38,18 +38,11 @@
     return getTemplate();
   }
 
-  static get properties() {
-    return {
-      /**
-       * Used by DeepLinkingMixin to focus this page's deep links.
-       */
-      supportedSettingIds: {
-        type: Object,
-        value: () =>
-            new Set<Setting>([Setting.kPrintJobs, Setting.kScanningApp]),
-      },
-    };
-  }
+  // DeepLinkingMixin override
+  override supportedSettingIds = new Set<Setting>([
+    Setting.kPrintJobs,
+    Setting.kScanningApp,
+  ]);
 
   private browserProxy_: CupsPrintersBrowserProxy;
 
diff --git a/chrome/browser/resources/ash/settings/os_privacy_page/manage_users_subpage.ts b/chrome/browser/resources/ash/settings/os_privacy_page/manage_users_subpage.ts
index 022d0a5..2d7760c 100644
--- a/chrome/browser/resources/ash/settings/os_privacy_page/manage_users_subpage.ts
+++ b/chrome/browser/resources/ash/settings/os_privacy_page/manage_users_subpage.ts
@@ -73,24 +73,20 @@
           return isChild();
         },
       },
-
-      /**
-       * Used by DeepLinkingMixin to focus this page's deep links.
-       */
-      supportedSettingIds: {
-        type: Object,
-        value: () => new Set<Setting>([
-          Setting.kGuestBrowsingV2,
-          Setting.kShowUsernamesAndPhotosAtSignInV2,
-          Setting.kRestrictSignInV2,
-          Setting.kAddToUserAllowlistV2,
-          Setting.kRemoveFromUserAllowlistV2,
-        ]),
-      },
     };
   }
 
   prefs: PrefsState;
+
+  // DeepLinkingMixin override
+  override supportedSettingIds = new Set<Setting>([
+    Setting.kGuestBrowsingV2,
+    Setting.kShowUsernamesAndPhotosAtSignInV2,
+    Setting.kRestrictSignInV2,
+    Setting.kAddToUserAllowlistV2,
+    Setting.kRemoveFromUserAllowlistV2,
+  ]);
+
   private isOwner_: boolean;
   private isUserListManaged_: boolean;
   private isChild_: boolean;
diff --git a/chrome/browser/resources/ash/settings/os_privacy_page/os_privacy_page.ts b/chrome/browser/resources/ash/settings/os_privacy_page/os_privacy_page.ts
index 7189d068..dce5899 100644
--- a/chrome/browser/resources/ash/settings/os_privacy_page/os_privacy_page.ts
+++ b/chrome/browser/resources/ash/settings/os_privacy_page/os_privacy_page.ts
@@ -104,19 +104,6 @@
       syncStatus: Object,
 
       /**
-       * Used by DeepLinkingMixin to focus this page's deep links.
-       */
-      supportedSettingIds: {
-        type: Object,
-        value: () => new Set<Setting>([
-          Setting.kVerifiedAccess,
-          Setting.kNonSplitSyncEncryptionOptions,
-          Setting.kImproveSearchSuggestions,
-          Setting.kMakeSearchesAndBrowsingBetter,
-        ]),
-      },
-
-      /**
        * True if fingerprint settings should be displayed on this machine.
        */
       fingerprintUnlockEnabled_: {
@@ -232,6 +219,15 @@
   }
 
   syncStatus: SyncStatus;
+
+  // DeepLinkingMixin override
+  override supportedSettingIds = new Set<Setting>([
+    Setting.kVerifiedAccess,
+    Setting.kNonSplitSyncEncryptionOptions,
+    Setting.kImproveSearchSuggestions,
+    Setting.kMakeSearchesAndBrowsingBetter,
+  ]);
+
   private authTokenInfo_: chrome.quickUnlockPrivate.TokenInfo|undefined;
   private browserProxy_: PeripheralDataAccessBrowserProxy;
   private authTokenReply_: RequestTokenReply|undefined|null;
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 bf52acf..4822c80 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
@@ -143,23 +143,18 @@
         computed: 'computeMicrophoneToggleTooltipText_(isMicListEmpty_, ' +
             'microphoneHardwareToggleActive_)',
       },
-
-      /**
-       * Used by DeepLinkingMixin to focus this page's deep links.
-       */
-      supportedSettingIds: {
-        type: Object,
-        value: () => new Set<Setting>([
-          Setting.kCameraOnOff,
-          Setting.kMicrophoneOnOff,
-          Setting.kSpeakOnMuteDetectionOnOff,
-          Setting.kGeolocationOnOff,
-          Setting.kUsageStatsAndCrashReports,
-        ]),
-      },
     };
   }
 
+  // DeepLinkingMixin override
+  override supportedSettingIds = new Set<Setting>([
+    Setting.kCameraOnOff,
+    Setting.kMicrophoneOnOff,
+    Setting.kSpeakOnMuteDetectionOnOff,
+    Setting.kGeolocationOnOff,
+    Setting.kUsageStatsAndCrashReports,
+  ]);
+
   private browserProxy_: PrivacyHubBrowserProxy;
   private showPrivacyHubLocationControl_: boolean;
   private locationSublabel_: string;
diff --git a/chrome/browser/resources/ash/settings/os_privacy_page/smart_privacy_subpage.ts b/chrome/browser/resources/ash/settings/os_privacy_page/smart_privacy_subpage.ts
index 00059e6..ebe236f 100644
--- a/chrome/browser/resources/ash/settings/os_privacy_page/smart_privacy_subpage.ts
+++ b/chrome/browser/resources/ash/settings/os_privacy_page/smart_privacy_subpage.ts
@@ -93,20 +93,15 @@
           return loadTimeData.getBoolean('isSnoopingProtectionEnabled');
         },
       },
-
-      /**
-       * Used by DeepLinkingMixin to focus this page's deep links.
-       */
-      supportedSettingIds: {
-        type: Object,
-        value: () => new Set<Setting>([
-          Setting.kQuickDim,
-          Setting.kSnoopingProtection,
-        ]),
-      },
     };
   }
 
+  // DeepLinkingMixin override
+  override supportedSettingIds = new Set<Setting>([
+    Setting.kQuickDim,
+    Setting.kSnoopingProtection,
+  ]);
+
   private isQuickDimEnabled_: boolean;
   private isSnoopingProtectionEnabled_: boolean;
   private smartPrivacyQuickLockRangeMs_: SliderTick[];
diff --git a/chrome/browser/resources/ash/settings/os_reset_page/reset_settings_card.ts b/chrome/browser/resources/ash/settings/os_reset_page/reset_settings_card.ts
index 49ca90e0..460a34bb 100644
--- a/chrome/browser/resources/ash/settings/os_reset_page/reset_settings_card.ts
+++ b/chrome/browser/resources/ash/settings/os_reset_page/reset_settings_card.ts
@@ -67,20 +67,15 @@
         },
         readOnly: true,
       },
-
-      /**
-       * Used by DeepLinkingMixin to focus this page's deep links.
-       */
-      supportedSettingIds: {
-        type: Object,
-        value: () => new Set<Setting>([
-          Setting.kPowerwash,
-          Setting.kSanitizeCrosSettings,
-        ]),
-      },
     };
   }
 
+  // DeepLinkingMixin override
+  override supportedSettingIds = new Set<Setting>([
+    Setting.kPowerwash,
+    Setting.kSanitizeCrosSettings,
+  ]);
+
   private osResetBrowserProxy_: OsResetBrowserProxy;
   private installedESimProfiles_: ESimProfileRemote[];
   private readonly isSanitizeAllowed_: boolean;
diff --git a/chrome/browser/resources/ash/settings/os_search_page/google_assistant_subpage.ts b/chrome/browser/resources/ash/settings/os_search_page/google_assistant_subpage.ts
index 7f8f7671..a1980d7 100644
--- a/chrome/browser/resources/ash/settings/os_search_page/google_assistant_subpage.ts
+++ b/chrome/browser/resources/ash/settings/os_search_page/google_assistant_subpage.ts
@@ -125,21 +125,6 @@
       },
 
       dspHotwordState_: Number,
-
-      /**
-       * Used by DeepLinkingMixin to focus this page's deep links.
-       */
-      supportedSettingIds: {
-        type: Object,
-        value: () => new Set<Setting>([
-          Setting.kAssistantOnOff,
-          Setting.kAssistantRelatedInfo,
-          Setting.kAssistantOkGoogle,
-          Setting.kAssistantNotifications,
-          Setting.kAssistantVoiceInput,
-          Setting.kTrainAssistantVoiceModel,
-        ]),
-      },
     };
   }
 
@@ -154,6 +139,16 @@
     ];
   }
 
+  // DeepLinkingMixin override
+  override supportedSettingIds = new Set<Setting>([
+    Setting.kAssistantOnOff,
+    Setting.kAssistantRelatedInfo,
+    Setting.kAssistantOkGoogle,
+    Setting.kAssistantNotifications,
+    Setting.kAssistantVoiceInput,
+    Setting.kTrainAssistantVoiceModel,
+  ]);
+
   private browserProxy_: GoogleAssistantBrowserProxy;
   private dspHotwordState_: DspHotwordState;
   private hotwordDspAvailable_: boolean;
diff --git a/chrome/browser/resources/ash/settings/os_search_page/search_and_assistant_settings_card.ts b/chrome/browser/resources/ash/settings/os_search_page/search_and_assistant_settings_card.ts
index 3ea739b..a47f3bd 100644
--- a/chrome/browser/resources/ash/settings/os_search_page/search_and_assistant_settings_card.ts
+++ b/chrome/browser/resources/ash/settings/os_search_page/search_and_assistant_settings_card.ts
@@ -127,26 +127,22 @@
           return isAssistantAllowed();
         },
       },
-
-      /**
-       * Used by DeepLinkingMixin to focus this page's deep links.
-       */
-      supportedSettingIds: {
-        type: Object,
-        value: () => new Set<Setting>([
-          Setting.kPreferredSearchEngine,
-          Setting.kMagicBoostOnOff,
-          Setting.kMahiOnOff,
-          Setting.kShowOrca,
-          Setting.kLobsterOnOff,
-          Setting.kSunfishOnOff,
-          Setting.kScannerOnOff,
-        ]),
-      },
     };
   }
 
   prefs: PrefsState;
+
+  // DeepLinkingMixin override
+  override supportedSettingIds = new Set<Setting>([
+    Setting.kPreferredSearchEngine,
+    Setting.kMagicBoostOnOff,
+    Setting.kMahiOnOff,
+    Setting.kShowOrca,
+    Setting.kLobsterOnOff,
+    Setting.kSunfishOnOff,
+    Setting.kScannerOnOff,
+  ]);
+
   private isAssistantAllowed_: boolean;
   private isQuickAnswersSupported_: boolean;
   private isMagicBoostFeatureEnabled_: boolean;
diff --git a/chrome/browser/resources/ash/settings/os_search_page/search_subpage.ts b/chrome/browser/resources/ash/settings/os_search_page/search_subpage.ts
index d22daad..7937586 100644
--- a/chrome/browser/resources/ash/settings/os_search_page/search_subpage.ts
+++ b/chrome/browser/resources/ash/settings/os_search_page/search_subpage.ts
@@ -48,20 +48,6 @@
 
   static get properties() {
     return {
-      /**
-       * Used by DeepLinkingMixin to focus this page's deep links.
-       */
-      supportedSettingIds: {
-        type: Object,
-        value: () => new Set<Setting>([
-          Setting.kPreferredSearchEngine,
-          Setting.kQuickAnswersOnOff,
-          Setting.kQuickAnswersDefinition,
-          Setting.kQuickAnswersTranslation,
-          Setting.kQuickAnswersUnitConversion,
-        ]),
-      },
-
       quickAnswersTranslationDisabled_: {
         type: Boolean,
         value() {
@@ -86,6 +72,15 @@
     };
   }
 
+  // DeepLinkingMixin override
+  override supportedSettingIds = new Set<Setting>([
+    Setting.kPreferredSearchEngine,
+    Setting.kQuickAnswersOnOff,
+    Setting.kQuickAnswersDefinition,
+    Setting.kQuickAnswersTranslation,
+    Setting.kQuickAnswersUnitConversion,
+  ]);
+
   private quickAnswersSubLabel_: string;
   private quickAnswersSubToggleEnabled_: boolean;
   private quickAnswersTranslationDisabled_: boolean;
diff --git a/chrome/browser/resources/ash/settings/parental_controls_page/parental_controls_settings_card.ts b/chrome/browser/resources/ash/settings/parental_controls_page/parental_controls_settings_card.ts
index 9b3cf1c..149da67 100644
--- a/chrome/browser/resources/ash/settings/parental_controls_page/parental_controls_settings_card.ts
+++ b/chrome/browser/resources/ash/settings/parental_controls_page/parental_controls_settings_card.ts
@@ -43,14 +43,6 @@
 
   static get properties() {
     return {
-      /**
-       * Used by DeepLinkingMixin to focus this page's deep links.
-       */
-      supportedSettingIds: {
-        type: Object,
-        value: () => new Set<Setting>([Setting.kSetUpParentalControls]),
-      },
-
       isChild_: {
         type: Boolean,
         value() {
@@ -68,6 +60,11 @@
     };
   }
 
+  // DeepLinkingMixin override
+  override supportedSettingIds = new Set<Setting>([
+    Setting.kSetUpParentalControls,
+  ]);
+
   private online_: boolean;
   private browserProxy_: ParentalControlsBrowserProxy;
 
diff --git a/chrome/browser/resources/ash/settings/personalization_page/personalization_page.ts b/chrome/browser/resources/ash/settings/personalization_page/personalization_page.ts
index 46407d1..2296d6e 100644
--- a/chrome/browser/resources/ash/settings/personalization_page/personalization_page.ts
+++ b/chrome/browser/resources/ash/settings/personalization_page/personalization_page.ts
@@ -21,7 +21,6 @@
 import {DeepLinkingMixin} from '../common/deep_linking_mixin.js';
 import {RouteObserverMixin} from '../common/route_observer_mixin.js';
 import {Section} from '../mojom-webui/routes.mojom-webui.js';
-import {Setting} from '../mojom-webui/setting.mojom-webui.js';
 import type {Route} from '../router.js';
 import {routes} from '../router.js';
 
@@ -49,16 +48,6 @@
         value: Section.kPersonalization,
         readOnly: true,
       },
-
-      /**
-       * Used by DeepLinkingMixin to focus this page's deep links.
-       */
-      supportedSettingIds: {
-        type: Object,
-        value: () => new Set<Setting>([
-          Setting.kSnapWindowSuggestions,
-        ]),
-      },
     };
   }
 
diff --git a/chrome/browser/resources/ash/settings/system_preferences_page/multitasking_settings_card.ts b/chrome/browser/resources/ash/settings/system_preferences_page/multitasking_settings_card.ts
index 88fbe3ec..23b61f0 100644
--- a/chrome/browser/resources/ash/settings/system_preferences_page/multitasking_settings_card.ts
+++ b/chrome/browser/resources/ash/settings/system_preferences_page/multitasking_settings_card.ts
@@ -44,19 +44,16 @@
         type: Object,
         notify: true,
       },
-
-      /**
-       * Used by DeepLinkingMixin to focus this element's deep links.
-       */
-      supportedSettingIds: {
-        type: Object,
-        value: () => new Set<Setting>([Setting.kSnapWindowSuggestions]),
-      },
     };
   }
 
   prefs: PrefsState;
 
+  // DeepLinkingMixin override
+  override supportedSettingIds = new Set<Setting>([
+    Setting.kSnapWindowSuggestions,
+  ]);
+
   override currentRouteChanged(newRoute: Route): void {
     if (newRoute !== routes.SYSTEM_PREFERENCES) {
       return;
diff --git a/chrome/browser/resources/ash/settings/system_preferences_page/startup_settings_card.ts b/chrome/browser/resources/ash/settings/system_preferences_page/startup_settings_card.ts
index ec8667d..eea62141 100644
--- a/chrome/browser/resources/ash/settings/system_preferences_page/startup_settings_card.ts
+++ b/chrome/browser/resources/ash/settings/system_preferences_page/startup_settings_card.ts
@@ -44,14 +44,6 @@
       },
 
       /**
-       * Used by DeepLinkingMixin to focus this element's deep links.
-       */
-      supportedSettingIds: {
-        type: Object,
-        value: () => new Set<Setting>([Setting.kRestoreAppsAndPages]),
-      },
-
-      /**
        * List of options for the on startup dropdown menu.
        */
       onStartupDropdownOptions_: {
@@ -69,6 +61,12 @@
   }
 
   prefs: PrefsState;
+
+  // DeepLinkingMixin override
+  override supportedSettingIds = new Set<Setting>([
+    Setting.kRestoreAppsAndPages,
+  ]);
+
   private readonly onStartupDropdownOptions_:
       Array<{value: number, name: string}>;
 
diff --git a/chrome/browser/resources/chromeos/accessibility/BUILD.gn b/chrome/browser/resources/chromeos/accessibility/BUILD.gn
index 0b18344..113b5504 100644
--- a/chrome/browser/resources/chromeos/accessibility/BUILD.gn
+++ b/chrome/browser/resources/chromeos/accessibility/BUILD.gn
@@ -29,6 +29,7 @@
     "common:build",
     "enhanced_network_tts:build",
     "select_to_speak/mv2:build",
+    "select_to_speak/mv3:build",
     "switch_access/mv2:build",
     "switch_access/mv3:build",
   ]
@@ -92,6 +93,7 @@
     "common:browser_tests",
     "enhanced_network_tts:browser_tests",
     "select_to_speak/mv2:browser_tests",
+    "select_to_speak/mv3:browser_tests",
     "switch_access/mv2:browser_tests",
     "switch_access/mv3:browser_tests",
   ]
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/mv2/panel/tutorial_test.js b/chrome/browser/resources/chromeos/accessibility/chromevox/mv2/panel/tutorial_test.js
index 94639e3..d02efcd 100644
--- a/chrome/browser/resources/chromeos/accessibility/chromevox/mv2/panel/tutorial_test.js
+++ b/chrome/browser/resources/chromeos/accessibility/chromevox/mv2/panel/tutorial_test.js
@@ -205,9 +205,8 @@
 // The first three nudges should read the current item with full context.
 // Afterward, general hints will be given about using ChromeVox. Lastly,
 // we will give a hint for exiting the tutorial.
-// TODO(crbug.com/407459387): Fix and re-enable this test.
 AX_TEST_F(
-    'ChromeVoxTutorialTest', 'DISABLED_GeneralNudgesTest', async function() {
+    'ChromeVoxTutorialTest', 'GeneralNudgesTest', async function() {
       const mockFeedback = this.createMockFeedback();
       const root = await this.runWithLoadedTree(this.simpleDoc);
       await this.launchAndWaitForTutorial();
@@ -399,8 +398,7 @@
     });
 
 // Tests that the title of an interactive lesson is read when shown.
-// TODO(crbug.com/407459387): Fix and re-enable this test.
-AX_TEST_F('ChromeVoxTutorialTest', 'DISABLED_AutoReadTitle', async function() {
+AX_TEST_F('ChromeVoxTutorialTest', 'AutoReadTitle', async function() {
   const mockFeedback = this.createMockFeedback();
   const root = await this.runWithLoadedTree(this.simpleDoc);
   await this.launchAndWaitForTutorial();
@@ -449,8 +447,7 @@
 });
 
 // Tests for correct speech and earcons on the earcons lesson.
-// TODO(crbug.com/407459387): Fix and re-enable this test.
-AX_TEST_F('ChromeVoxTutorialTest', 'DISABLED_EarconLesson', async function() {
+AX_TEST_F('ChromeVoxTutorialTest', 'EarconLesson', async function() {
   const mockFeedback = this.createMockFeedback();
   const root = await this.runWithLoadedTree(this.simpleDoc);
   await this.launchAndWaitForTutorial();
@@ -550,8 +547,7 @@
     });
 
 // Tests that tutorial nudges are restarted whenever the current range changes.
-// TODO(crbug.com/407459387): Fix and re-enable this test.
-AX_TEST_F('ChromeVoxTutorialTest', 'DISABLED_RestartNudges', async function() {
+AX_TEST_F('ChromeVoxTutorialTest', 'RestartNudges', async function() {
   const root = await this.runWithLoadedTree(this.simpleDoc);
   await this.launchAndWaitForTutorial();
   const tutorial = this.getTutorial();
@@ -575,8 +571,7 @@
 });
 
 // Tests that the tutorial closes and ChromeVox navigates to a resource link.
-// TODO(crbug.com/407459387): Fix and re-enable this test.
-AX_TEST_F('ChromeVoxTutorialTest', 'DISABLED_ResourcesTest', async function() {
+AX_TEST_F('ChromeVoxTutorialTest', 'ResourcesTest', async function() {
   const mockFeedback = this.createMockFeedback();
   const root = await this.runWithLoadedTree(this.simpleDoc);
   await this.launchAndWaitForTutorial();
@@ -600,8 +595,7 @@
 
 // Tests that choosing a curriculum with only 1 lesson automatically opens the
 // lesson.
-// TODO(crbug.com/407459387): Fix and re-enable this test.
-AX_TEST_F('ChromeVoxTutorialTest', 'DISABLED_OnlyLessonTest', async function() {
+AX_TEST_F('ChromeVoxTutorialTest', 'OnlyLessonTest', async function() {
   const mockFeedback = this.createMockFeedback();
   const root = await this.runWithLoadedTree(this.simpleDoc);
   await this.launchAndWaitForTutorial();
@@ -635,10 +629,8 @@
 
 // Tests that interactive mode and ForcedActionPath are properly set when
 // showing different screens in the tutorial.
-// TODO(crbug.com/407459387): Fix and re-enable this test.
 AX_TEST_F(
-    'ChromeVoxTutorialTest', 'DISABLED_StartStopInteractiveMode',
-    async function() {
+    'ChromeVoxTutorialTest', 'StartStopInteractiveMode', async function() {
       const root = await this.runWithLoadedTree(this.simpleDoc);
       await this.launchAndWaitForTutorial();
       const tutorial = this.getTutorial();
@@ -749,9 +741,8 @@
       await mockFeedback.replay();
     });
 
-// TODO(crbug.com/407459387): Fix and re-enable this test.
 AX_TEST_F(
-    'ChromeVoxTutorialTest', 'DISABLED_GeneralTouchNudges', async function() {
+    'ChromeVoxTutorialTest', 'GeneralTouchNudges', async function() {
       const mockFeedback = this.createMockFeedback();
       const root = await this.runWithLoadedTree(this.simpleDoc);
       await this.launchAndWaitForTutorial();
diff --git a/chrome/browser/resources/chromeos/accessibility/select_to_speak/mv2/BUILD.gn b/chrome/browser/resources/chromeos/accessibility/select_to_speak/mv2/BUILD.gn
index fec8bef..f855d19 100644
--- a/chrome/browser/resources/chromeos/accessibility/select_to_speak/mv2/BUILD.gn
+++ b/chrome/browser/resources/chromeos/accessibility/select_to_speak/mv2/BUILD.gn
@@ -18,7 +18,7 @@
 }
 
 select_to_speak_out_dir =
-    "$root_out_dir/resources/chromeos/accessibility/select_to_speak"
+    "$root_out_dir/resources/chromeos/accessibility/select_to_speak/mv2"
 
 # Directory where typescript build will occur.
 ts_build_staging_dir = "$target_gen_dir/ts_build_staging"
diff --git a/chrome/browser/resources/chromeos/accessibility/select_to_speak/mv2/background.html b/chrome/browser/resources/chromeos/accessibility/select_to_speak/mv2/background.html
index 8409c11..f20e69e 100644
--- a/chrome/browser/resources/chromeos/accessibility/select_to_speak/mv2/background.html
+++ b/chrome/browser/resources/chromeos/accessibility/select_to_speak/mv2/background.html
@@ -1,2 +1,3 @@
 <!-- Module entrypoint. -->
-<script type="module" src="/select_to_speak/select_to_speak_main.js"></script>
+<script type="module" src="/select_to_speak/mv2/select_to_speak_main.js">
+</script>
diff --git a/chrome/browser/resources/chromeos/accessibility/select_to_speak/mv3/BUILD.gn b/chrome/browser/resources/chromeos/accessibility/select_to_speak/mv3/BUILD.gn
index 474f1e9..5c39eca 100644
--- a/chrome/browser/resources/chromeos/accessibility/select_to_speak/mv3/BUILD.gn
+++ b/chrome/browser/resources/chromeos/accessibility/select_to_speak/mv3/BUILD.gn
@@ -18,7 +18,7 @@
 }
 
 select_to_speak_out_dir =
-    "$root_out_dir/resources/chromeos/accessibility/select_to_speak"
+    "$root_out_dir/resources/chromeos/accessibility/select_to_speak/mv3"
 
 # Directory where typescript build will occur.
 ts_build_staging_dir = "$target_gen_dir/ts_build_staging"
@@ -91,7 +91,6 @@
     "../../common:copied_files",
   ]
   sources = [
-    "background.html",
     "checked.png",
     "earcons/null_selection.ogg",
     "gdocs_script.js",
diff --git a/chrome/browser/resources/chromeos/accessibility/select_to_speak/mv3/background.html b/chrome/browser/resources/chromeos/accessibility/select_to_speak/mv3/background.html
deleted file mode 100644
index 8409c11..0000000
--- a/chrome/browser/resources/chromeos/accessibility/select_to_speak/mv3/background.html
+++ /dev/null
@@ -1,2 +0,0 @@
-<!-- Module entrypoint. -->
-<script type="module" src="/select_to_speak/select_to_speak_main.js"></script>
diff --git a/chrome/browser/resources/chromeos/accessibility/select_to_speak/mv3/select_to_speak.ts b/chrome/browser/resources/chromeos/accessibility/select_to_speak/mv3/select_to_speak.ts
index b7b104a..1574579 100644
--- a/chrome/browser/resources/chromeos/accessibility/select_to_speak/mv3/select_to_speak.ts
+++ b/chrome/browser/resources/chromeos/accessibility/select_to_speak/mv3/select_to_speak.ts
@@ -1830,7 +1830,7 @@
    */
   async maybeCreateOffscreenDocument_() {
     const offscreenUrl =
-        chrome.runtime.getURL('select_to_speak/offscreen.html');
+        chrome.runtime.getURL('select_to_speak/mv3/offscreen.html');
 
     const existingContexts = await chrome.runtime.getContexts({
       contextTypes: [chrome.runtime.ContextType.OFFSCREEN_DOCUMENT],
diff --git a/chrome/browser/resources/chromeos/accessibility/select_to_speak_manifest.json.jinja2 b/chrome/browser/resources/chromeos/accessibility/select_to_speak_manifest.json.jinja2
index 377a6df..addb6b87b2 100644
--- a/chrome/browser/resources/chromeos/accessibility/select_to_speak_manifest.json.jinja2
+++ b/chrome/browser/resources/chromeos/accessibility/select_to_speak_manifest.json.jinja2
@@ -6,13 +6,13 @@
   "manifest_version": 3,
   "minimum_chrome_version": "93",
   "background": {
-    "service_worker": "select_to_speak/select_to_speak_main.js",
+    "service_worker": "select_to_speak/mv3/select_to_speak_main.js",
     "type": "module"
   },
 {% else %}
   "manifest_version": 2,
   "background": {
-    "page": "select_to_speak/background.html"
+    "page": "select_to_speak/mv2/background.html"
   },
 {% endif %}
   "name": "__MSG_SELECT_TO_SPEAK_NAME__",
@@ -47,9 +47,9 @@
    ],
 {% endif %}
   "icons": {
-      "128": "select_to_speak/sts-icon-128.png",
-      "16": "select_to_speak/sts-icon-16.png",
-      "48": "select_to_speak/sts-icon-48.png"
+      "128": "select_to_speak/mv3/sts-icon-128.png",
+      "16": "select_to_speak/mv3/sts-icon-16.png",
+      "48": "select_to_speak/mv3/sts-icon-48.png"
   },
   "automation": {
     "desktop": true
@@ -62,7 +62,7 @@
       "all_frames": true,
 {% if is_manifest_v3 == '1' %}
       "js": [
-        "select_to_speak/gdocs_script.js"
+        "select_to_speak/mv3/gdocs_script.js"
       ],
       "world": "MAIN",
 {% else %}
diff --git a/chrome/browser/resources/on_device_internals/tools.ts b/chrome/browser/resources/on_device_internals/tools.ts
index 53b8bd2..9072fcf5 100644
--- a/chrome/browser/resources/on_device_internals/tools.ts
+++ b/chrome/browser/resources/on_device_internals/tools.ts
@@ -416,6 +416,7 @@
           maxOutputTokens: 0,
           topK: null,
           temperature: null,
+          responseJsonSchema: '',
         },
         this.responseRouter_.$.bindNewPipeAndPassRemote());
     const onResponseId =
diff --git a/chrome/browser/resources/pdf/BUILD.gn b/chrome/browser/resources/pdf/BUILD.gn
index 83a832a..d2ec296a 100644
--- a/chrome/browser/resources/pdf/BUILD.gn
+++ b/chrome/browser/resources/pdf/BUILD.gn
@@ -118,6 +118,8 @@
       "elements/ink_size_selector.ts",
       "elements/selectable_icon_button.html.ts",
       "elements/selectable_icon_button.ts",
+      "elements/text_styles_selector.html.ts",
+      "elements/text_styles_selector.ts",
       "elements/viewer_bottom_toolbar.html.ts",
       "elements/viewer_bottom_toolbar.ts",
       "elements/viewer_bottom_toolbar_dropdown.html.ts",
@@ -162,6 +164,7 @@
       "elements/ink_size_selector.css",
       "elements/selectable_icon_button.css",
       "elements/side_panel_shared.css",
+      "elements/text_styles_selector.css",
       "elements/viewer_bottom_toolbar.css",
       "elements/viewer_bottom_toolbar_dropdown.css",
       "elements/viewer_side_panel.css",
diff --git a/chrome/browser/resources/pdf/elements/text_styles_selector.css b/chrome/browser/resources/pdf/elements/text_styles_selector.css
new file mode 100644
index 0000000..2ddab22
--- /dev/null
+++ b/chrome/browser/resources/pdf/elements/text_styles_selector.css
@@ -0,0 +1,25 @@
+/* Copyright 2025 The Chromium Authors
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file. */
+
+/* #css_wrapper_metadata_start
+ * #type=style-lit
+ * #import=./shared_vars.css.js
+ * #css_wrapper_metadata_end */
+
+:host {
+  display: block;
+}
+
+cr-icon-button {
+  --cr-icon-button-fill-color: var(--viewer-icon-ink-fill-color);
+  --cr-icon-button-focus-outline-color: var(--viewer-icon-focus-outline-color);
+  --cr-icon-button-margin-end: 6px;
+  --cr-icon-button-margin-start: 6px;
+}
+
+cr-icon-button.active {
+  --cr-icon-button-fill-color: var(--viewer-icon-ink-selected-fill-color);
+  background-color: var(--viewer-icon-ink-selected-background-color);
+  border-radius: 50%;
+}
diff --git a/chrome/browser/resources/pdf/elements/text_styles_selector.html.ts b/chrome/browser/resources/pdf/elements/text_styles_selector.html.ts
new file mode 100644
index 0000000..fb9d4ba
--- /dev/null
+++ b/chrome/browser/resources/pdf/elements/text_styles_selector.html.ts
@@ -0,0 +1,26 @@
+// Copyright 2025 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'chrome://resources/cr_elements/cr_icon_button/cr_icon_button.js';
+import './icons.html.js';
+
+import {html} from 'chrome://resources/lit/v3_0/lit.rollup.js';
+
+import type {TextStylesSelectorElement} from './text_styles_selector.js';
+
+export function getHtml(this: TextStylesSelectorElement) {
+  // clang-format off
+  return html`<!--_html_template_start_-->
+    ${this.getTextStyles_().map(style => html`
+      <cr-icon-button class="${this.getActiveClass_(style)}"
+          @click="${this.onStyleButtonClick_}"
+          data-style="${style}"
+          iron-icon="pdf:text-format-${style}"
+          aria-pressed="${this.getAriaPressed_(style)}"
+          aria-label="${style}"
+          title="${style}">
+      </cr-icon-button>`)}
+  <!--_html_template_end_-->`;
+  // clang-format on
+}
diff --git a/chrome/browser/resources/pdf/elements/text_styles_selector.ts b/chrome/browser/resources/pdf/elements/text_styles_selector.ts
new file mode 100644
index 0000000..bf41599
--- /dev/null
+++ b/chrome/browser/resources/pdf/elements/text_styles_selector.ts
@@ -0,0 +1,92 @@
+// Copyright 2025 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import {EventTracker} from 'chrome://resources/js/event_tracker.js';
+import {CrLitElement} from 'chrome://resources/lit/v3_0/lit.rollup.js';
+
+import type {AnnotationText, TextStyles} from '../constants.js';
+import {TextStyle} from '../constants.js';
+import {Ink2Manager} from '../ink2_manager.js';
+
+import {getCss} from './text_styles_selector.css.js';
+import {getHtml} from './text_styles_selector.html.js';
+
+
+export class TextStylesSelectorElement extends CrLitElement {
+  static get is() {
+    return 'text-styles-selector';
+  }
+
+  static override get styles() {
+    return getCss();
+  }
+
+  override render() {
+    return getHtml.bind(this)();
+  }
+
+  static override get properties() {
+    return {
+      currentStyles_: {type: Object},
+    };
+  }
+
+  protected currentStyles_: TextStyles = {
+    [TextStyle.BOLD]: false,
+    [TextStyle.ITALIC]: false,
+    [TextStyle.UNDERLINE]: false,
+    [TextStyle.STRIKETHROUGH]: false,
+  };
+
+  private tracker_: EventTracker = new EventTracker();
+
+  constructor() {
+    super();
+    this.onTextChanged_(Ink2Manager.getInstance().getCurrentText());
+  }
+
+  override connectedCallback() {
+    super.connectedCallback();
+    this.tracker_.add(
+        Ink2Manager.getInstance(), 'text-changed',
+        (e: Event) =>
+            this.onTextChanged_((e as CustomEvent<AnnotationText>).detail));
+  }
+
+  override disconnectedCallback() {
+    super.disconnectedCallback();
+    this.tracker_.removeAll();
+  }
+
+  protected getTextStyles_(): TextStyle[] {
+    return Object.values(TextStyle);
+  }
+
+  protected onStyleButtonClick_(e: Event) {
+    const style = (e.target as HTMLElement).dataset['style'] as TextStyle;
+    const newStyles = Object.assign({}, this.currentStyles_);
+    newStyles[style] = !newStyles[style];
+    Ink2Manager.getInstance().setTextStyles(newStyles);
+  }
+
+  protected getActiveClass_(style: TextStyle) {
+    return this.currentStyles_[style] ? 'active' : '';
+  }
+
+  protected getAriaPressed_(style: TextStyle) {
+    return this.currentStyles_[style] ? 'true' : 'false';
+  }
+
+  private onTextChanged_(text: AnnotationText) {
+    this.currentStyles_ = text.styles;
+  }
+}
+
+declare global {
+  interface HTMLElementTagNameMap {
+    'text-styles-selector': TextStylesSelectorElement;
+  }
+}
+
+customElements.define(TextStylesSelectorElement.is, TextStylesSelectorElement);
diff --git a/chrome/browser/resources/pdf/elements/viewer_text_side_panel.css b/chrome/browser/resources/pdf/elements/viewer_text_side_panel.css
index 4e9dda2..8910959a 100644
--- a/chrome/browser/resources/pdf/elements/viewer_text_side_panel.css
+++ b/chrome/browser/resources/pdf/elements/viewer_text_side_panel.css
@@ -12,8 +12,6 @@
  * #css_wrapper_metadata_end */
 
 :host {
-  --cr-icon-button-margin-end: 6px;
-  --cr-icon-button-margin-start: 6px;
   --cr-radio-group-item-padding: 6px;
 }
 
@@ -43,7 +41,7 @@
   border-radius: 50%;
 }
 
-cr-radio-group, .style-buttons {
+cr-radio-group, text-styles-selector {
   padding-top: 16px;
 }
 
diff --git a/chrome/browser/resources/pdf/elements/viewer_text_side_panel.html.ts b/chrome/browser/resources/pdf/elements/viewer_text_side_panel.html.ts
index a3e1a3d..3ffc243bd 100644
--- a/chrome/browser/resources/pdf/elements/viewer_text_side_panel.html.ts
+++ b/chrome/browser/resources/pdf/elements/viewer_text_side_panel.html.ts
@@ -2,11 +2,11 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-import 'chrome://resources/cr_elements/cr_icon_button/cr_icon_button.js';
 import 'chrome://resources/cr_elements/cr_radio_group/cr_radio_group.js';
 import './icons.html.js';
 import './ink_color_selector.js';
 import './selectable_icon_button.js';
+import './text_styles_selector.js';
 
 import {html} from 'chrome://resources/lit/v3_0/lit.rollup.js';
 
@@ -35,17 +35,7 @@
     </div>
     <div class="side-panel-content">
       <h2>Styles</h2>
-      <div class="style-buttons">
-        ${this.getTextStyles_().map(style => html`
-          <cr-icon-button class="${this.getActiveClass_(style)}"
-              @click="${this.onStyleButtonClick_}"
-              data-style="${style}"
-              iron-icon="pdf:text-format-${style}"
-              aria-pressed="${this.getAriaPressed_(style)}"
-              aria-label="${style}"
-              title="${style}">
-          </cr-icon-button>`)}
-      </div>
+      <text-styles-selector></text-styles-selector>
       <cr-radio-group selectable-elements="selectable-icon-button"
           .selected="${this.currentAlignment_}"
           @selected-changed="${this.onSelectedAlignmentChanged_}">
diff --git a/chrome/browser/resources/pdf/elements/viewer_text_side_panel.ts b/chrome/browser/resources/pdf/elements/viewer_text_side_panel.ts
index cd4e3e8..9232feea 100644
--- a/chrome/browser/resources/pdf/elements/viewer_text_side_panel.ts
+++ b/chrome/browser/resources/pdf/elements/viewer_text_side_panel.ts
@@ -5,8 +5,8 @@
 import {EventTracker} from 'chrome://resources/js/event_tracker.js';
 import {CrLitElement} from 'chrome://resources/lit/v3_0/lit.rollup.js';
 
-import type {AnnotationText, Color, TextStyles} from '../constants.js';
-import {TextAlignment, TextStyle} from '../constants.js';
+import type {AnnotationText, Color} from '../constants.js';
+import {TextAlignment} from '../constants.js';
 import {Ink2Manager} from '../ink2_manager.js';
 import {hexToColor} from '../pdf_viewer_utils.js';
 
@@ -67,7 +67,6 @@
       currentColor_: {type: Object},
       currentFont_: {type: String},
       currentSize_: {type: Number},
-      currentStyles_: {type: Object},
       colors_: {type: Array},
       fonts_: {type: Array},
       sizes_: {type: Array},
@@ -78,12 +77,6 @@
   protected currentColor_: Color = hexToColor(TEXT_COLORS[0]!.color);
   protected currentFont_: string = '';
   protected currentSize_: number = TEXT_SIZES[0]!;
-  protected currentStyles_: TextStyles = {
-    [TextStyle.BOLD]: false,
-    [TextStyle.ITALIC]: false,
-    [TextStyle.UNDERLINE]: false,
-    [TextStyle.STRIKETHROUGH]: false,
-  };
 
   protected colors_ = TEXT_COLORS;
   protected fonts_ = [
@@ -114,10 +107,6 @@
     this.tracker_.removeAll();
   }
 
-  protected getTextStyles_(): TextStyle[] {
-    return Object.values(TextStyle);
-  }
-
   protected isSelectedFont_(font: string) {
     return font === this.currentFont_;
   }
@@ -136,21 +125,6 @@
     Ink2Manager.getInstance().setTextSize(newValue);
   }
 
-  protected onStyleButtonClick_(e: Event) {
-    const style = (e.target as HTMLElement).dataset['style'] as TextStyle;
-    const newStyles = Object.assign({}, this.currentStyles_);
-    newStyles[style] = !newStyles[style];
-    Ink2Manager.getInstance().setTextStyles(newStyles);
-  }
-
-  protected getActiveClass_(style: TextStyle) {
-    return this.currentStyles_[style] ? 'active' : '';
-  }
-
-  protected getAriaPressed_(style: TextStyle) {
-    return this.currentStyles_[style] ? 'true' : 'false';
-  }
-
   protected onSelectedAlignmentChanged_(e: CustomEvent<{value: string}>) {
     const newAlignment = e.detail.value as TextAlignment;
     Ink2Manager.getInstance().setTextAlignment(newAlignment);
@@ -170,7 +144,6 @@
     this.currentColor_ = text.color;
     this.currentFont_ = text.font;
     this.currentSize_ = text.size;
-    this.currentStyles_ = text.styles;
     this.currentAlignment_ = text.alignment;
   }
 }
diff --git a/chrome/browser/resources/pdf/pdf_viewer_wrapper.ts b/chrome/browser/resources/pdf/pdf_viewer_wrapper.ts
index 252e006..252b42c0 100644
--- a/chrome/browser/resources/pdf/pdf_viewer_wrapper.ts
+++ b/chrome/browser/resources/pdf/pdf_viewer_wrapper.ts
@@ -28,6 +28,7 @@
 export {InkColorSelectorElement} from './elements/ink_color_selector.js';
 export {InkSizeSelectorElement, HIGHLIGHTER_SIZES, PEN_SIZES} from './elements/ink_size_selector.js';
 export {SelectableIconButtonElement} from './elements/selectable_icon_button.js';
+export {TextStylesSelectorElement} from './elements/text_styles_selector.js';
 // </if>
 export {ViewerAttachmentElement} from './elements/viewer_attachment.js';
 export {ViewerAttachmentBarElement} from './elements/viewer_attachment_bar.js';
diff --git a/chrome/browser/resources/settings/autofill_page/credit_card_list_entry.html b/chrome/browser/resources/settings/autofill_page/credit_card_list_entry.html
index 1e6c9ee..1762a35 100644
--- a/chrome/browser/resources/settings/autofill_page/credit_card_list_entry.html
+++ b/chrome/browser/resources/settings/autofill_page/credit_card_list_entry.html
@@ -35,18 +35,46 @@
             [[getSummaryAriaLabel_(creditCard)]],
             [[getSummaryAriaSublabel_(creditCard)]]
           </div>
-          <div id="summaryLabel" class="ellipses" aria-hidden="true">
-            [[creditCard.metadata.summaryLabel]]
-          </div>
+          <template is="dom-if" if="[[!shouldShowNewFopDisplay_()]]">
+            <div id="summaryLabel" class="ellipses" aria-hidden="true">
+              [[creditCard.metadata.summaryLabel]]
+            </div>
+          </template>
+          <template is="dom-if" if="[[shouldShowNewFopDisplay_()]]">
+            <div>
+              <span id="label" class="ellipses" aria-hidden="true">
+                [[creditCard.metadata.summaryLabel]]
+              </span>
+              <template is="dom-if" if="[[hasCardIdentifier_(creditCard)]]">
+                <div class="sub-label" aria-hidden="true">
+                  <span id="summaryLabel_1">
+                    [[creditCard.metadata.summarySublabel]]
+                  </span>
+                  <span id="summaryLabel_2">
+                    [[getExpirationlabel_(creditCard)]]
+                  </span>
+                </div>
+              </template>
+              <template is="dom-if" if="[[!hasCardIdentifier_(creditCard)]]">
+                <span id="expirationLabel" class="sub-label">
+                  [[getExpirationlabel_(creditCard)]]
+                </span>
+              </template>
+            </div>
+          </template>
           <div id="summarySublabel" class="sub-label">
             <span aria-hidden="true">[[getSummarySublabel_(creditCard)]]</span>
+            <template is="dom-if"
+                if="[[hasSummaryAndBenefitSublabel_(creditCard)]]">
+              |
+            </template>
             <template is="dom-if" if="[[isCardBenefitsProductUrlAvailable_(creditCard)]]">
-              | <a id="summaryTermsLink"
-                  aria-label="[[getBenefitsTermsAriaLabel_(creditCard)]]"
-                  aria-description="$i18n{opensInNewTab}"
-                  on-click="onSummarySublabelTermsLinkClick_"
-                  href="[[getCardBenefitsProductUrl_(creditCard)]]"
-                  target="_blank">$i18n{benefitsTermsTagForCreditCardListEntry}</a>
+              <a id="summaryTermsLink"
+                aria-label="[[getBenefitsTermsAriaLabel_(creditCard)]]"
+                aria-description="$i18n{opensInNewTab}"
+                on-click="onSummarySublabelTermsLinkClick_"
+                href="[[getCardBenefitsProductUrl_(creditCard)]]"
+                target="_blank">$i18n{benefitsTermsTagForCreditCardListEntry}</a>
             </template>
           </div>
         </div>
diff --git a/chrome/browser/resources/settings/autofill_page/credit_card_list_entry.ts b/chrome/browser/resources/settings/autofill_page/credit_card_list_entry.ts
index 44be1c9..4636e4c 100644
--- a/chrome/browser/resources/settings/autofill_page/credit_card_list_entry.ts
+++ b/chrome/browser/resources/settings/autofill_page/credit_card_list_entry.ts
@@ -49,11 +49,21 @@
     return {
       /** A saved credit card. */
       creditCard: Object,
+
+      showNewFopDisplayEnabled_: {
+        type: Boolean,
+        value() {
+          return loadTimeData.getBoolean('enableNewFopDisplay');
+        },
+        readOnly: true,
+      },
     };
   }
 
   creditCard: chrome.autofillPrivate.CreditCardEntry;
 
+  private showNewFopDisplayEnabled_: boolean;
+
   get dotsMenu(): HTMLElement|null {
     return this.shadowRoot!.getElementById('creditCardMenu');
   }
@@ -120,6 +130,20 @@
   }
 
   /**
+   * Returns true if the new FOP display should be shown.
+   */
+  private shouldShowNewFopDisplay_(): boolean {
+    return this.showNewFopDisplayEnabled_;
+  }
+
+  /**
+   * The card has a product description or a nickname.
+   */
+  private hasCardIdentifier_(): boolean {
+    return (this.creditCard.metadata!.summarySublabel || '').length > 0;
+  }
+
+  /**
    * The 3-dot menu should be shown if the card is not a masked server card or
    * if the card is eligible for virtual card enrollment.
    */
@@ -190,17 +214,23 @@
   }
 
   /**
+   * Returns expiration date if applicable.
+   */
+  private getExpirationlabel_(): string {
+    return this.isVirtualCardEnrolled_() ? '' :
+                                           ' · ' + this.getCardExpiryDate_();
+  }
+
+  /**
    * Returns one of the following sublabels, based on the card's status:
    *   Virtual card enrollment tag
-   *   Expiration date tag (MM/YY)
    *   'CVC saved' tag
    *   Benefit tag (Place the benefit tag last because it includes a link to
    *                product terms.)
    * e.g., one of the following:
-   *   11/23
-   *   11/23 | CVC saved
-   *   11/23 | Card benefits available (terms apply)
-   *   11/23 | CVC saved | Card benefits available (terms apply)
+   *   CVC saved
+   *   Card benefits available (terms apply)
+   *   CVC saved | Card benefits available (terms apply)
    *   Virtual card turned on
    *   Virtual card turned on | CVC saved
    *   Virtual card turned on | Card benefits available (terms apply)
@@ -211,13 +241,21 @@
     const separator = ' | ';
     let summarySublabel = this.isVirtualCardEnrolled_() ?
         this.i18n('virtualCardTurnedOn') :
-        this.getCardExpiryDate_();
+        (this.shouldShowNewFopDisplay_() ? '' : this.getCardExpiryDate_());
     if (this.isCardCvcAvailable_()) {
-      summarySublabel += separator + this.i18n('cvcTagForCreditCardListEntry');
+      if (summarySublabel.length > 0) {
+        summarySublabel += separator;
+      }
+      summarySublabel += this.i18n('cvcTagForCreditCardListEntry');
     }
     return summarySublabel;
   }
 
+  private hasSummaryAndBenefitSublabel_(): boolean {
+    return this.getSummarySublabel_().length > 0 &&
+        this.isCardBenefitsProductUrlAvailable_();
+  }
+
   private getSummaryAriaSublabel_(): string {
     switch (this.getCardSublabelType()) {
       case CardSummarySublabelType.VIRTUAL_CARD_WITH_CVC_AND_BENEFITS_TAG:
diff --git a/chrome/browser/resources/settings/people_page/sync_page.html b/chrome/browser/resources/settings/people_page/sync_page.html
index be8abd1..ede8a7e 100644
--- a/chrome/browser/resources/settings/people_page/sync_page.html
+++ b/chrome/browser/resources/settings/people_page/sync_page.html
@@ -225,7 +225,7 @@
 <if expr="not chromeos_ash">
     <template is="dom-if" if="[[showSetupCancelDialog_]]" restamp>
       <cr-dialog id="setupCancelDialog" on-close="onSetupCancelDialogClose_"
-          ignore-popstate>
+          ignore-popstate show-on-attach>
         <div slot="title">$i18n{syncSetupCancelDialogTitle}</div>
         <div slot="body">$i18n{syncSetupCancelDialogBody}</div>
         <div slot="button-container">
diff --git a/chrome/browser/resources/settings/people_page/sync_page.ts b/chrome/browser/resources/settings/people_page/sync_page.ts
index d9fe4d9..d292ce94 100644
--- a/chrome/browser/resources/settings/people_page/sync_page.ts
+++ b/chrome/browser/resources/settings/people_page/sync_page.ts
@@ -32,7 +32,7 @@
 import {WebUiListenerMixin} from '//resources/cr_elements/web_ui_listener_mixin.js';
 import {assert, assertNotReached} from '//resources/js/assert.js';
 import {focusWithoutInk} from '//resources/js/focus_without_ink.js';
-import {flush, PolymerElement} from '//resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+import {PolymerElement} from '//resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 import type {SyncBrowserProxy, SyncPrefs, SyncStatus} from '/shared/settings/people_page/sync_browser_proxy.js';
 import {PageStatus, SignedInState, StatusAction, SyncBrowserProxyImpl} from '/shared/settings/people_page/sync_browser_proxy.js';
 import {I18nMixin} from 'chrome://resources/cr_elements/i18n_mixin.js';
@@ -427,10 +427,6 @@
       requestAnimationFrame(() => {
         router.navigateTo(router.getRoutes().SYNC);
         this.showSetupCancelDialog_ = true;
-        // Flush to make sure that the setup cancel dialog is attached.
-        flush();
-        this.shadowRoot!.querySelector<CrDialogElement>(
-                            '#setupCancelDialog')!.showModal();
       });
       return;
     }
diff --git a/chrome/browser/safe_browsing/download_protection/deep_scanning_browsertest.cc b/chrome/browser/safe_browsing/download_protection/deep_scanning_browsertest.cc
index 9b8d9f8..0e43eb0 100644
--- a/chrome/browser/safe_browsing/download_protection/deep_scanning_browsertest.cc
+++ b/chrome/browser/safe_browsing/download_protection/deep_scanning_browsertest.cc
@@ -39,6 +39,7 @@
 #include "chrome/browser/safe_browsing/download_protection/ppapi_download_request.h"
 #include "chrome/browser/safe_browsing/test_safe_browsing_service.h"
 #include "chrome/browser/ui/browser.h"
+#include "chrome/browser/ui/views/file_system_access/file_system_access_test_utils.h"
 #include "chrome/common/chrome_paths.h"
 #include "chrome/common/pref_names.h"
 #include "chrome/test/base/in_process_browser_test.h"
@@ -63,6 +64,7 @@
 #include "content/public/browser/download_manager.h"
 #include "content/public/browser/save_page_type.h"
 #include "content/public/test/browser_test.h"
+#include "content/public/test/browser_test_utils.h"
 #include "content/public/test/download_test_observer.h"
 #include "content/public/test/test_utils.h"
 #include "mojo/public/cpp/bindings/remote.h"
@@ -83,6 +85,8 @@
     enterprise_obfuscation::kKeySize + enterprise_obfuscation::kNonceSize +
     enterprise_obfuscation::kAuthTagSize;
 
+constexpr char kTestContent[] = "test content";
+
 // Extract the metadata proto from the raw request string based on multipart
 // upload protocol. Returns true on success.
 bool GetMultipartUploadMetadata(
@@ -1573,4 +1577,306 @@
   EXPECT_FALSE(base::PathExists(extra_files_dir));
 }
 
+class FileSystemAccessDeepScanningBrowserTest
+    : public DownloadDeepScanningBrowserTestBase {
+ public:
+  FileSystemAccessDeepScanningBrowserTest()
+      : DownloadDeepScanningBrowserTestBase(/*connectors_machine_scope=*/true,
+                                            /*is_consumer=*/false,
+                                            /*is_obfuscated=*/false) {
+    scoped_feature_list_.InitAndEnableFeature(
+        safe_browsing::kEnterpriseFileSystemAccessDeepScan);
+  }
+
+  void SetUpOnMainThread() override {
+    DownloadDeepScanningBrowserTestBase::SetUpOnMainThread();
+
+    // Setup a temporary directory for the file save path.
+    ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
+
+    // Have file save path automatically selected by picker.
+    ui::SelectFileDialog::SetFactory(
+        std::make_unique<SelectPredeterminedFileDialogFactory>(
+            std::vector<base::FilePath>{GetTestFilePath()}));
+
+    // Navigate to a simple page for FS API calls to run from.
+    ASSERT_TRUE(ui_test_utils::NavigateToURL(
+        browser(), embedded_test_server()->GetURL("/title1.html")));
+  }
+
+  void TearDownOnMainThread() override {
+    ui::SelectFileDialog::SetFactory(nullptr);
+    DownloadDeepScanningBrowserTestBase::TearDownOnMainThread();
+  }
+
+  base::FilePath GetTestFilePath() {
+    return temp_dir_.GetPath().AppendASCII("test_fsa_file.txt");
+  }
+
+  void InitiateWriteViaFSA(content::WebContents* web_contents,
+                           const std::string& content) {
+    // Script that triggers FSA API write. Executes async and reports results
+    // via messaging.
+    const std::string js_script = R"(
+      (async (content) => {
+        try {
+          const handle = await window.showSaveFilePicker();
+          const writable = await handle.createWritable();
+          await writable.write(content);
+          await writable.close();
+          window.domAutomationController.send("Success");
+        } catch (e) {
+          window.domAutomationController.send("Error: " + e.name +
+                                              " - " + e.message);
+        }
+      })(')" + content + R"(');
+    )";
+
+    content::ExecuteScriptAsync(web_contents, js_script);
+  }
+
+ protected:
+  base::ScopedTempDir temp_dir_;
+
+ private:
+  base::test::ScopedFeatureList scoped_feature_list_;
+};
+
+IN_PROC_BROWSER_TEST_F(FileSystemAccessDeepScanningBrowserTest, BlockedWrite) {
+  SetUpReporting();
+
+  // Prepare the scan response with DLP block and successful malware scan.
+  enterprise_connectors::ContentAnalysisResponse response;
+  auto* dlp_result = response.add_results();
+  dlp_result->set_tag("dlp");
+  dlp_result->set_status(
+      enterprise_connectors::ContentAnalysisResponse::Result::SUCCESS);
+  auto* dlp_rule = dlp_result->add_triggered_rules();
+  dlp_rule->set_action(enterprise_connectors::TriggeredRule::BLOCK);
+
+  auto* malware_result = response.add_results();
+  malware_result->set_tag("malware");
+  malware_result->set_status(
+      enterprise_connectors::ContentAnalysisResponse::Result::SUCCESS);
+
+  ExpectContentAnalysisResumableMetadataResponse({"dlp", "malware"});
+  ExpectContentAnalysisResumableContentResponse(response);
+
+  // Setup message queue for JS responses.
+  content::WebContents* web_contents =
+      browser()->tab_strip_model()->GetActiveWebContents();
+  ASSERT_TRUE(web_contents);
+  content::DOMMessageQueue message_queue(web_contents);
+
+  base::RunLoop validator_run_loop;
+  enterprise_connectors::test::EventReportValidator validator(client());
+  validator.SetDoneClosure(validator_run_loop.QuitClosure());
+
+  InitiateWriteViaFSA(web_contents, kTestContent);
+
+  WaitForDeepScanRequest();
+
+  GURL url = embedded_test_server()->GetURL("/title1.html");
+  std::set<std::string> mimetypes = {"text/plain"};
+  validator.ExpectSensitiveDataEvent(
+      /*url*/ url.spec(),
+      /*tab_url*/ url.spec(),
+      /*source*/ "",
+      /*destination*/ "",
+      /*filename*/ GetTestFilePath().AsUTF8Unsafe(),
+      // echo -n [kTestContent] | sha256sum | tr a-f A-F
+      /*sha*/
+      "6AE8A75555209FD6C44157C0AED8016E763FF435A19CF186F76863140143FF72",
+      /*trigger*/
+      extensions::SafeBrowsingPrivateEventRouter::kTriggerFileDownload,
+      /*dlp_verdict*/ response.results(0),
+      /*mimetypes*/ &mimetypes,
+      /*size*/ sizeof(kTestContent) - 1,
+      enterprise_connectors::EventResultToString(
+          enterprise_connectors::EventResult::BLOCKED),
+      /*username*/ kUserName,
+      /*profile_identifier*/ GetProfileIdentifier(),
+      /*scan_id*/ last_request().request_token(),
+      /*content_transfer_method*/ std::nullopt,
+      /*user_justification*/ std::nullopt);
+
+  EXPECT_EQ(last_request().reason(),
+            enterprise_connectors::ContentAnalysisRequest::NORMAL_DOWNLOAD);
+
+  validator_run_loop.Run();
+
+  std::string js_completion_result;
+  ASSERT_TRUE(message_queue.WaitForMessage(&js_completion_result));
+  // TODO(crbug.com/407065784): Improve error message for SB checks.
+  EXPECT_EQ(js_completion_result,
+            "\"Error: AbortError - Blocked by Safe Browsing.\"");
+
+  // File is created but remains empty due to block.
+  base::ScopedAllowBlockingForTesting allow_blocking;
+  EXPECT_TRUE(base::PathExists(GetTestFilePath()));
+  EXPECT_EQ(0, base::GetFileSize(GetTestFilePath()));
+}
+
+IN_PROC_BROWSER_TEST_F(FileSystemAccessDeepScanningBrowserTest, AllowedWrite) {
+  SetUpReporting();
+
+  // Prepare the scan response with no DLP or malware rules triggered.
+  enterprise_connectors::ContentAnalysisResponse response;
+  auto* dlp_result = response.add_results();
+  dlp_result->set_tag("dlp");
+  dlp_result->set_status(
+      enterprise_connectors::ContentAnalysisResponse::Result::SUCCESS);
+
+  auto* malware_result = response.add_results();
+  malware_result->set_tag("malware");
+  malware_result->set_status(
+      enterprise_connectors::ContentAnalysisResponse::Result::SUCCESS);
+
+  ExpectContentAnalysisResumableMetadataResponse({"dlp", "malware"});
+  ExpectContentAnalysisResumableContentResponse(response);
+
+  // Setup message queue for JS responses.
+  content::WebContents* web_contents =
+      browser()->tab_strip_model()->GetActiveWebContents();
+  ASSERT_TRUE(web_contents);
+  content::DOMMessageQueue message_queue(web_contents);
+
+  InitiateWriteViaFSA(web_contents, kTestContent);
+
+  WaitForDeepScanRequest();
+
+  EXPECT_EQ(last_request().reason(),
+            enterprise_connectors::ContentAnalysisRequest::NORMAL_DOWNLOAD);
+
+  std::string js_completion_result;
+  ASSERT_TRUE(message_queue.WaitForMessage(&js_completion_result));
+  EXPECT_EQ(js_completion_result, "\"Success\"");
+
+  // Checks that file is written successfully.
+  base::ScopedAllowBlockingForTesting allow_blocking;
+  EXPECT_TRUE(base::PathExists(GetTestFilePath()));
+
+  std::string file_content;
+  EXPECT_TRUE(base::ReadFileToString(GetTestFilePath(), &file_content));
+  EXPECT_EQ(file_content, kTestContent);
+}
+
+IN_PROC_BROWSER_TEST_F(FileSystemAccessDeepScanningBrowserTest, WarnedWrite) {
+  SetUpReporting();
+
+  // Prepare the scan response with warn DLP rule triggered.
+  enterprise_connectors::ContentAnalysisResponse response;
+  auto* dlp_result = response.add_results();
+  dlp_result->set_tag("dlp");
+  dlp_result->set_status(
+      enterprise_connectors::ContentAnalysisResponse::Result::SUCCESS);
+  auto* dlp_rule = dlp_result->add_triggered_rules();
+  dlp_rule->set_action(enterprise_connectors::TriggeredRule::WARN);
+
+  auto* malware_result = response.add_results();
+  malware_result->set_tag("malware");
+  malware_result->set_status(
+      enterprise_connectors::ContentAnalysisResponse::Result::SUCCESS);
+
+  ExpectContentAnalysisResumableMetadataResponse({"dlp", "malware"});
+  ExpectContentAnalysisResumableContentResponse(response);
+
+  // Setup message queue for JS responses.
+  content::WebContents* web_contents =
+      browser()->tab_strip_model()->GetActiveWebContents();
+  ASSERT_TRUE(web_contents);
+  content::DOMMessageQueue message_queue(web_contents);
+
+  base::RunLoop validator_run_loop;
+  enterprise_connectors::test::EventReportValidator validator(client());
+  validator.SetDoneClosure(validator_run_loop.QuitClosure());
+
+  InitiateWriteViaFSA(web_contents, kTestContent);
+
+  WaitForDeepScanRequest();
+
+  GURL url = embedded_test_server()->GetURL("/title1.html");
+  std::set<std::string> mimetypes = {"text/plain"};
+  validator.ExpectSensitiveDataEvent(
+      /*url*/ url.spec(),
+      /*tab_url*/ url.spec(),
+      /*source*/ "",
+      /*destination*/ "",
+      /*filename*/ GetTestFilePath().AsUTF8Unsafe(),
+      // echo -n [kTestContent] | sha256sum | tr a-f A-F
+      /*sha*/
+      "6AE8A75555209FD6C44157C0AED8016E763FF435A19CF186F76863140143FF72",
+      /*trigger*/
+      extensions::SafeBrowsingPrivateEventRouter::kTriggerFileDownload,
+      /*dlp_verdict*/ response.results(0),
+      /*mimetypes*/ &mimetypes,
+      /*size*/ sizeof(kTestContent) - 1,
+      enterprise_connectors::EventResultToString(
+          enterprise_connectors::EventResult::WARNED),
+      /*username*/ kUserName,
+      /*profile_identifier*/ GetProfileIdentifier(),
+      /*scan_id*/ last_request().request_token(),
+      /*content_transfer_method*/ std::nullopt,
+      /*user_justification*/ std::nullopt);
+
+  validator_run_loop.Run();
+
+  // For warn verdicts, we allow the write to happen as there is currently no
+  // dialog that allows the user to bypass warnings.
+  std::string js_completion_result;
+  ASSERT_TRUE(message_queue.WaitForMessage(&js_completion_result));
+  EXPECT_EQ(js_completion_result, "\"Success\"");
+
+  // Checks that file is written successfully.
+  base::ScopedAllowBlockingForTesting allow_blocking;
+  EXPECT_TRUE(base::PathExists(GetTestFilePath()));
+
+  std::string file_content;
+  EXPECT_TRUE(base::ReadFileToString(GetTestFilePath(), &file_content));
+  EXPECT_EQ(file_content, kTestContent);
+}
+
+IN_PROC_BROWSER_TEST_F(FileSystemAccessDeepScanningBrowserTest,
+                       DeepScanFailure) {
+  SetUpReporting();
+
+  // Configure scan response with failed status.
+  enterprise_connectors::ContentAnalysisResponse response;
+  auto* dlp_result = response.add_results();
+  dlp_result->set_tag("dlp");
+  dlp_result->set_status(
+      enterprise_connectors::ContentAnalysisResponse::Result::FAILURE);
+
+  auto* malware_result = response.add_results();
+  malware_result->set_tag("malware");
+  malware_result->set_status(
+      enterprise_connectors::ContentAnalysisResponse::Result::FAILURE);
+
+  ExpectContentAnalysisResumableMetadataResponse({"dlp", "malware"});
+  ExpectContentAnalysisResumableContentResponse(response);
+
+  // Setup message queue for JS responses.
+  content::WebContents* web_contents =
+      browser()->tab_strip_model()->GetActiveWebContents();
+  ASSERT_TRUE(web_contents);
+  content::DOMMessageQueue message_queue(web_contents);
+
+  InitiateWriteViaFSA(web_contents, kTestContent);
+
+  WaitForDeepScanRequest();
+
+  std::string js_completion_result;
+  ASSERT_TRUE(message_queue.WaitForMessage(&js_completion_result));
+
+  // When scan fails, write should still succeed (fail-open behavior).
+  EXPECT_EQ(js_completion_result, "\"Success\"");
+
+  base::ScopedAllowBlockingForTesting allow_blocking;
+  EXPECT_TRUE(base::PathExists(GetTestFilePath()));
+
+  std::string file_content;
+  EXPECT_TRUE(base::ReadFileToString(GetTestFilePath(), &file_content));
+  EXPECT_EQ(file_content, kTestContent);
+}
+
 }  // namespace safe_browsing
diff --git a/chrome/browser/safe_browsing/download_protection/file_system_access_metadata.cc b/chrome/browser/safe_browsing/download_protection/file_system_access_metadata.cc
index 0ac92df2..d0ca964 100644
--- a/chrome/browser/safe_browsing/download_protection/file_system_access_metadata.cc
+++ b/chrome/browser/safe_browsing/download_protection/file_system_access_metadata.cc
@@ -49,7 +49,12 @@
       ext = ext.substr(1);
     }
 
-    if (net::GetMimeTypeFromExtension(ext, &mime_type)) {
+    // Searches within chrome's built-in list of filetype/extension
+    // associations, as using `GetMimeTypeFromExtension` includes
+    // platform-defined mime type mappings that can potentially block.
+    // TODO(crbug.com/407598185): Add platform MIME types lookup for better
+    // protection.
+    if (net::GetWellKnownMimeTypeFromExtension(ext, &mime_type)) {
       mime_type_ = mime_type;
     } else {
       // Default to octet-stream for unknown MIME type.
diff --git a/chrome/browser/send_tab_to_self/send_tab_to_self_client_service.cc b/chrome/browser/send_tab_to_self/send_tab_to_self_client_service.cc
index 8512732..6e79d3b7 100644
--- a/chrome/browser/send_tab_to_self/send_tab_to_self_client_service.cc
+++ b/chrome/browser/send_tab_to_self/send_tab_to_self_client_service.cc
@@ -19,15 +19,13 @@
 SendTabToSelfClientService::SendTabToSelfClientService(
     std::unique_ptr<ReceivingUiHandler> receiving_ui_handler,
     SendTabToSelfModel* model)
-    : model_(model), receiving_ui_handler_(std::move(receiving_ui_handler)) {
-  model_->AddObserver(this);
+    : receiving_ui_handler_(std::move(receiving_ui_handler)) {
+  model_observation_.Observe(model);
 }
 
 SendTabToSelfClientService::~SendTabToSelfClientService() = default;
 
 void SendTabToSelfClientService::Shutdown() {
-  model_->RemoveObserver(this);
-  model_ = nullptr;
   receiving_ui_handler_.reset();
 }
 
diff --git a/chrome/browser/send_tab_to_self/send_tab_to_self_client_service.h b/chrome/browser/send_tab_to_self/send_tab_to_self_client_service.h
index 29c1ece..969cfa1 100644
--- a/chrome/browser/send_tab_to_self/send_tab_to_self_client_service.h
+++ b/chrome/browser/send_tab_to_self/send_tab_to_self_client_service.h
@@ -9,6 +9,7 @@
 #include <vector>
 
 #include "base/memory/raw_ptr.h"
+#include "base/scoped_observation.h"
 #include "chrome/browser/send_tab_to_self/receiving_ui_handler.h"
 #include "components/keyed_service/core/keyed_service.h"
 #include "components/send_tab_to_self/send_tab_to_self_model.h"
@@ -25,6 +26,9 @@
 class SendTabToSelfClientService : public KeyedService,
                                    public SendTabToSelfModelObserver {
  public:
+  // `model` must outlive this object. `receiving_ui_handler` must be usable
+  // until this keyed service is Shutdown() (in particular it cannot depend on
+  // any services that are instantiated after this one).
   SendTabToSelfClientService(
       std::unique_ptr<ReceivingUiHandler> receiving_ui_handler,
       SendTabToSelfModel* model);
@@ -51,8 +55,10 @@
   ReceivingUiHandler* GetReceivingUiHandler() const;
 
  private:
-  // Owned by the SendTabToSelfSyncService which should outlive this class
-  raw_ptr<SendTabToSelfModel> model_;
+  // The model outlives this object, so this is fine.
+  base::ScopedObservation<SendTabToSelfModel, SendTabToSelfModelObserver>
+      model_observation_{this};
+  // Reset on Shutdown().
   std::unique_ptr<ReceivingUiHandler> receiving_ui_handler_;
 };
 
diff --git a/chrome/browser/speech/on_device_speech_recognition_impl.cc b/chrome/browser/speech/on_device_speech_recognition_impl.cc
index 6bd429ec..f6e91404 100644
--- a/chrome/browser/speech/on_device_speech_recognition_impl.cc
+++ b/chrome/browser/speech/on_device_speech_recognition_impl.cc
@@ -28,23 +28,9 @@
 #include "components/soda/soda_util.h"
 
 namespace {
-// Returns a boolean indicating whether the language is both enabled and not
-// already installed.
+// Returns a boolean indicating whether the language is enabled.
 bool IsLanguageInstallable(const std::string& language_code,
                            bool is_soda_binary_installed) {
-  if (is_soda_binary_installed) {
-    // Check if the language pack is already installed if the SODA binary was
-    // already installed. If the SODA binary has not been installed,
-    // `prefs::kSodaRegisteredLanguagePacks` will contain the default language
-    // pack which may not exist on the device.
-    for (const auto& language : g_browser_process->local_state()->GetList(
-             prefs::kSodaRegisteredLanguagePacks)) {
-      if (language.GetString() == language_code) {
-        return false;
-      }
-    }
-  }
-
   return base::Contains(
       speech::SodaInstaller::GetInstance()->GetLiveCaptionEnabledLanguages(),
       language_code);
@@ -55,7 +41,17 @@
 
 namespace speech {
 
-OnDeviceSpeechRecognitionImpl::~OnDeviceSpeechRecognitionImpl() = default;
+OnDeviceSpeechRecognitionImpl::~OnDeviceSpeechRecognitionImpl() {
+#if !BUILDFLAG(IS_ANDROID)
+  speech::SodaInstaller* soda_installer = speech::SodaInstaller::GetInstance();
+  // `soda_installer` is not guaranteed to be valid, since it's possible for
+  // this class to out-live it. This means that this class cannot use
+  // ScopedObservation and needs to manage removing the observer itself.
+  if (soda_installer) {
+    soda_installer->RemoveObserver(this);
+  }
+#endif  // !BUILDFLAG(IS_ANDROID)
+}
 
 void OnDeviceSpeechRecognitionImpl::Bind(
     mojo::PendingReceiver<media::mojom::OnDeviceSpeechRecognition> receiver) {
@@ -79,6 +75,7 @@
         callback) {
 #if BUILDFLAG(IS_ANDROID)
   std::move(callback).Run(false);
+}
 #else
   std::optional<speech::SodaLanguagePackComponentConfig> language_config =
       speech::GetLanguageComponentConfigMatchingLanguageSubtag(language);
@@ -99,6 +96,7 @@
     return;
   }
 
+  language_installation_callbacks_[language].push_back(std::move(callback));
   // `InstallSoda` will only install the SODA binary if it is not already
   // installed.
   speech::SodaInstaller::GetInstance()->InstallSoda(
@@ -108,10 +106,22 @@
   // installed.
   speech::SodaInstaller::GetInstance()->InstallLanguage(
       language_config.value().language_name, g_browser_process->local_state());
-  std::move(callback).Run(true);
-#endif  // BUILDFLAG(IS_ANDROID)
 }
 
+void OnDeviceSpeechRecognitionImpl::OnSodaInstalled(
+    speech::LanguageCode language_code) {
+  RunAndRemoveInstallationCallbacks(GetLanguageName(language_code),
+                                    /*installation_success=*/true);
+}
+
+void OnDeviceSpeechRecognitionImpl::OnSodaInstallError(
+    speech::LanguageCode language_code,
+    speech::SodaInstaller::ErrorCode error_code) {
+  RunAndRemoveInstallationCallbacks(GetLanguageName(language_code),
+                                    /*installation_success=*/false);
+}
+#endif  // !BUILDFLAG(IS_ANDROID)
+
 OnDeviceSpeechRecognitionImpl::OnDeviceSpeechRecognitionImpl(
     content::RenderFrameHost* frame_host)
     : content::DocumentUserData<OnDeviceSpeechRecognitionImpl>(frame_host),
@@ -119,7 +129,14 @@
                         frame_host->GetProcess()->GetBrowserContext())
                         ->GetPrefs()),
       language_prefs_(
-          std::make_unique<language::LanguagePrefs>(pref_service_)) {}
+          std::make_unique<language::LanguagePrefs>(pref_service_)) {
+#if !BUILDFLAG(IS_ANDROID)
+  speech::SodaInstaller* soda_installer = speech::SodaInstaller::GetInstance();
+  if (soda_installer) {
+    soda_installer->AddObserver(this);
+  }
+#endif  // !BUILDFLAG(IS_ANDROID)
+}
 
 bool OnDeviceSpeechRecognitionImpl::CanInstallWithoutUserConsent(
     const std::string& language) {
@@ -142,6 +159,22 @@
   return false;
 }
 
+#if !BUILDFLAG(IS_ANDROID)
+void OnDeviceSpeechRecognitionImpl::RunAndRemoveInstallationCallbacks(
+    const std::string& language,
+    bool installation_success) {
+  auto it = language_installation_callbacks_.find(language);
+  if (it != language_installation_callbacks_.end()) {
+    std::list<InstallOnDeviceSpeechRecognitionCallback>& callbacks = it->second;
+    for (auto callback_iterator = callbacks.begin();
+         callback_iterator != callbacks.end();) {
+      std::move(*callback_iterator).Run(installation_success);
+      callback_iterator = callbacks.erase(callback_iterator);
+    }
+    language_installation_callbacks_.erase(it);
+  }
+}
+#endif  // !BUILDFLAG(IS_ANDROID)
 DOCUMENT_USER_DATA_KEY_IMPL(OnDeviceSpeechRecognitionImpl);
 
 }  // namespace speech
diff --git a/chrome/browser/speech/on_device_speech_recognition_impl.h b/chrome/browser/speech/on_device_speech_recognition_impl.h
index 80355bb..6b0a612 100644
--- a/chrome/browser/speech/on_device_speech_recognition_impl.h
+++ b/chrome/browser/speech/on_device_speech_recognition_impl.h
@@ -11,6 +11,13 @@
 #include "media/mojo/mojom/speech_recognizer.mojom.h"
 #include "mojo/public/cpp/bindings/receiver_set.h"
 
+#if !BUILDFLAG(IS_ANDROID)
+#include <list>
+
+#include "base/containers/flat_map.h"
+#include "components/soda/soda_installer.h"
+#endif  // !BUILDFLAG(IS_ANDROID)
+
 class PrefService;
 
 namespace content {
@@ -25,6 +32,9 @@
 
 class OnDeviceSpeechRecognitionImpl
     : public content::DocumentUserData<OnDeviceSpeechRecognitionImpl>,
+#if !BUILDFLAG(IS_ANDROID)
+      public speech::SodaInstaller::Observer,
+#endif  // !BUILDFLAG(IS_ANDROID)
       public media::mojom::OnDeviceSpeechRecognition {
  public:
   OnDeviceSpeechRecognitionImpl(const OnDeviceSpeechRecognitionImpl&) = delete;
@@ -46,6 +56,15 @@
       OnDeviceSpeechRecognitionImpl::InstallOnDeviceSpeechRecognitionCallback
           callback) override;
 
+#if !BUILDFLAG(IS_ANDROID)
+  // SodaInstaller::Observer:
+  void OnSodaInstalled(speech::LanguageCode language_code) override;
+  void OnSodaInstallError(speech::LanguageCode language_code,
+                          speech::SodaInstaller::ErrorCode error_code) override;
+  void OnSodaProgress(speech::LanguageCode language_code,
+                      int combined_progress) override {}
+#endif  // !BUILDFLAG(IS_ANDROID)
+
  private:
   friend class content::DocumentUserData<OnDeviceSpeechRecognitionImpl>;
   explicit OnDeviceSpeechRecognitionImpl(content::RenderFrameHost* frame_host);
@@ -54,9 +73,20 @@
   // explicit user consent.
   bool CanInstallWithoutUserConsent(const std::string& language);
 
+#if !BUILDFLAG(IS_ANDROID)
+  void RunAndRemoveInstallationCallbacks(const std::string& language,
+                                         bool installation_success);
+#endif  // !BUILDFLAG(IS_ANDROID)
+
   raw_ptr<PrefService> pref_service_;
   std::unique_ptr<language::LanguagePrefs> language_prefs_;
 
+#if !BUILDFLAG(IS_ANDROID)
+  base::flat_map<std::string,
+                 std::list<InstallOnDeviceSpeechRecognitionCallback>>
+      language_installation_callbacks_;
+#endif  // !BUILDFLAG(IS_ANDROID)
+
   mojo::Receiver<media::mojom::OnDeviceSpeechRecognition> receiver_{this};
 
   DOCUMENT_USER_DATA_KEY_DECL();
diff --git a/chrome/browser/tab_group_suggestion/android/java/src/org/chromium/chrome/browser/tab_group_suggestion/SuggestionEventObserver.java b/chrome/browser/tab_group_suggestion/android/java/src/org/chromium/chrome/browser/tab_group_suggestion/SuggestionEventObserver.java
index 76763f33..f5b31fd 100644
--- a/chrome/browser/tab_group_suggestion/android/java/src/org/chromium/chrome/browser/tab_group_suggestion/SuggestionEventObserver.java
+++ b/chrome/browser/tab_group_suggestion/android/java/src/org/chromium/chrome/browser/tab_group_suggestion/SuggestionEventObserver.java
@@ -47,8 +47,7 @@
                         @TabLaunchType int type,
                         @TabCreationState int creationState,
                         boolean markedForSelection) {
-                    if (markedForSelection
-                            && creationState == TabCreationState.LIVE_IN_FOREGROUND) {
+                    if (creationState != TabCreationState.FROZEN_ON_RESTORE) {
                         mGroupSuggestionsService.didAddTab(tab.getId(), type);
                     }
                 }
diff --git a/chrome/browser/tab_group_suggestion/android/junit/src/org/chromium/chrome/browser/tab_group_suggestion/SuggestionEventObserverUnitTest.java b/chrome/browser/tab_group_suggestion/android/junit/src/org/chromium/chrome/browser/tab_group_suggestion/SuggestionEventObserverUnitTest.java
index afade1d..4e79580 100644
--- a/chrome/browser/tab_group_suggestion/android/junit/src/org/chromium/chrome/browser/tab_group_suggestion/SuggestionEventObserverUnitTest.java
+++ b/chrome/browser/tab_group_suggestion/android/junit/src/org/chromium/chrome/browser/tab_group_suggestion/SuggestionEventObserverUnitTest.java
@@ -98,11 +98,24 @@
                 .getValue()
                 .didAddTab(
                         mTab,
-                        TabLaunchType.FROM_RESTORE,
+                        TabLaunchType.FROM_CHROME_UI,
                         TabCreationState.LIVE_IN_FOREGROUND,
                         /* markedForSelection= */ true);
 
-        verify(mGroupSuggestionsService).didAddTab(eq(TAB_ID), eq(TabLaunchType.FROM_RESTORE));
+        verify(mGroupSuggestionsService).didAddTab(eq(TAB_ID), eq(TabLaunchType.FROM_CHROME_UI));
+    }
+
+    @Test
+    public void testDidAddTab_IgnoreRestore() {
+        mTabModelObserverCaptor
+                .getValue()
+                .didAddTab(
+                        mTab,
+                        TabLaunchType.FROM_RESTORE,
+                        TabCreationState.FROZEN_ON_RESTORE,
+                        /* markedForSelection= */ true);
+
+        verify(mGroupSuggestionsService, never()).didAddTab(anyInt(), anyInt());
     }
 
     @Test
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 9977abb..242d3aa 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
@@ -272,7 +272,6 @@
   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_;
 };
 
@@ -467,12 +466,6 @@
 class TouchToFillDelegateAndroidImplCreditCardUnitTest
     : public TouchToFillDelegateAndroidImplUnitTest {
  public:
-  TouchToFillDelegateAndroidImplCreditCardUnitTest() {
-    scoped_feature_list_.InitWithFeatureState(
-        features::kAutofillEnableVcnGrayOutForMerchantOptOut,
-        /*enabled=*/false);
-  }
-
   static std::vector<CreditCard> GetCardsToSuggest(
       std::vector<const CreditCard*> credit_cards) {
     std::vector<CreditCard> cards_to_suggest;
@@ -488,7 +481,6 @@
     TouchToFillDelegateAndroidImplUnitTest::SetUp();
     ConfigureForCreditCards(test::GetCreditCard());
   }
-  base::test::ScopedFeatureList scoped_feature_list_;
 };
 
 TEST_F(TouchToFillDelegateAndroidImplCreditCardUnitTest,
@@ -771,39 +763,6 @@
   TryToShowTouchToFill(/*expected_success=*/true);
 }
 
-TEST_F(
-    TouchToFillDelegateAndroidImplCreditCardUnitTest,
-    TryToShowTouchToFillDoesNotShowVirtualCardSuggestionsForOptedOutMerchants) {
-  autofill_client_.GetPersonalDataManager()
-      .test_payments_data_manager()
-      .ClearCreditCards();
-  CreditCard credit_card =
-      test::GetMaskedServerCardEnrolledIntoVirtualCardNumber();
-  autofill_client_.GetPersonalDataManager()
-      .payments_data_manager()
-      .AddCreditCard(credit_card);
-
-  ASSERT_FALSE(touch_to_fill_delegate_->IsShowingTouchToFill());
-
-  ON_CALL(*static_cast<MockAutofillOptimizationGuide*>(
-              autofill_client_.GetAutofillOptimizationGuide()),
-          ShouldBlockFormFieldSuggestion)
-      .WillByDefault(testing::Return(true));
-
-  // Since merchant has opted out of virtual cards and gray-out feature is
-  // disabled, `HasDeactivatedStyle()` should return false for the virtual card
-  // suggestion.
-  EXPECT_CALL(
-      payments_autofill_client(),
-      ShowTouchToFillCreditCard(
-          _, ElementsAre(credit_card),
-          ElementsAre(EqualsSuggestionFields(
-              credit_card.CardNameForAutofillDisplay(credit_card.nickname()),
-              credit_card.ObfuscatedNumberWithVisibleLastFourDigits(),
-              /*has_deactivated_style=*/false))));
-  TryToShowTouchToFill(/*expected_success=*/true);
-}
-
 TEST_F(TouchToFillDelegateAndroidImplCreditCardUnitTest,
        TryToShowTouchToFillShowsVirtualCardSuggestionsForEnrolledCards) {
   autofill_client_.GetPersonalDataManager()
@@ -1102,17 +1061,11 @@
     : public TouchToFillDelegateAndroidImplCreditCardUnitTest {
  public:
   TouchToFillDelegateAndroidImplVcnGrayOutForMerchantOptOutUnitTest() {
-    scoped_feature_list_.InitWithFeatureState(
-        features::kAutofillEnableVcnGrayOutForMerchantOptOut,
-        /*enabled=*/true);
     ON_CALL(*static_cast<MockAutofillOptimizationGuide*>(
                 autofill_client_.GetAutofillOptimizationGuide()),
             ShouldBlockFormFieldSuggestion)
         .WillByDefault(testing::Return(true));
   }
-
- protected:
-  base::test::ScopedFeatureList scoped_feature_list_;
 };
 
 TEST_F(TouchToFillDelegateAndroidImplVcnGrayOutForMerchantOptOutUnitTest,
diff --git a/chrome/browser/ui/android/layouts/java/src/org/chromium/chrome/browser/layouts/LayoutType.java b/chrome/browser/ui/android/layouts/java/src/org/chromium/chrome/browser/layouts/LayoutType.java
index e50e399..0f7a9f40 100644
--- a/chrome/browser/ui/android/layouts/java/src/org/chromium/chrome/browser/layouts/LayoutType.java
+++ b/chrome/browser/ui/android/layouts/java/src/org/chromium/chrome/browser/layouts/LayoutType.java
@@ -21,7 +21,6 @@
     LayoutType.TAB_SWITCHER,
     LayoutType.TOOLBAR_SWIPE,
     LayoutType.SIMPLE_ANIMATION,
-    LayoutType.START_SURFACE
 })
 @Retention(RetentionPolicy.SOURCE)
 @NullMarked
@@ -31,6 +30,5 @@
     int TAB_SWITCHER = 2;
     int TOOLBAR_SWIPE = 4;
     int SIMPLE_ANIMATION = 8;
-    @Deprecated int START_SURFACE = 16;
-    // Next layout type should be 32.
+    // Next layout type should be 16.
 }
diff --git a/chrome/browser/ui/android/omnibox/BUILD.gn b/chrome/browser/ui/android/omnibox/BUILD.gn
index 9f7acfc..bb27f16 100644
--- a/chrome/browser/ui/android/omnibox/BUILD.gn
+++ b/chrome/browser/ui/android/omnibox/BUILD.gn
@@ -274,7 +274,6 @@
     "java/res/color-night/omnibox_suggestion_bg.xml",
     "java/res/color-night/omnibox_suggestion_dropdown_bg.xml",
     "java/res/color/omnibox_suggestion_bg.xml",
-    "java/res/color/omnibox_suggestion_bg_hover.xml",
     "java/res/color/omnibox_suggestion_dropdown_bg.xml",
     "java/res/drawable-hdpi/bookmark_edit_active.png",
     "java/res/drawable-hdpi/btn_suggestion_refine.png",
@@ -317,7 +316,6 @@
     "java/res/drawable/ic_wb_sunny_round.xml",
     "java/res/drawable/logo_partly_cloudy.xml",
     "java/res/drawable/logo_translate_round.xml",
-    "java/res/drawable/omnibox_suggestion_bg_hover_layers.xml",
     "java/res/drawable/switch_to_tab.xml",
     "java/res/drawable/trending_up_black_24dp.xml",
     "java/res/layout-sw600dp/location_bar.xml",
diff --git a/chrome/browser/ui/android/omnibox/java/res/color/omnibox_suggestion_bg_hover.xml b/chrome/browser/ui/android/omnibox/java/res/color/omnibox_suggestion_bg_hover.xml
deleted file mode 100644
index e5b8b59a..0000000
--- a/chrome/browser/ui/android/omnibox/java/res/color/omnibox_suggestion_bg_hover.xml
+++ /dev/null
@@ -1,23 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-Copyright 2025 The Chromium Authors
-Use of this source code is governed by a BSD-style license that can be
-found in the LICENSE file.
--->
-
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
-  <!-- selected -->
-  <item
-      android:alpha="@dimen/omnibox_suggestion_bg_hover_alpha"
-      android:color="?attr/colorOnSurface"
-      android:state_selected="true"/>
-
-  <!-- hovered -->
-  <item
-      android:alpha="@dimen/omnibox_suggestion_bg_hover_alpha"
-      android:color="?attr/colorOnSurface"
-      android:state_hovered="true"/>
-
-  <!-- default state -->
-  <item android:color="@android:color/transparent"/>
-</selector>
\ No newline at end of file
diff --git a/chrome/browser/ui/android/omnibox/java/res/drawable/omnibox_suggestion_bg_hover_layers.xml b/chrome/browser/ui/android/omnibox/java/res/drawable/omnibox_suggestion_bg_hover_layers.xml
deleted file mode 100644
index fc2de20..0000000
--- a/chrome/browser/ui/android/omnibox/java/res/drawable/omnibox_suggestion_bg_hover_layers.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-Copyright 2025 The Chromium Authors
-Use of this source code is governed by a BSD-style license that can be
-found in the LICENSE file.
--->
-
-<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
-  <!-- base surface color-->
-  <item>
-    <shape android:shape="rectangle">
-      <solid android:color="@color/omnibox_suggestion_bg"/>
-    </shape>
-  </item>
-
-  <!-- hover layer -->
-  <item>
-    <shape android:shape="rectangle">
-      <solid android:color="@color/omnibox_suggestion_bg_hover"/>
-    </shape>
-  </item>
-</layer-list>
\ No newline at end of file
diff --git a/chrome/browser/ui/android/omnibox/java/res/values/dimens.xml b/chrome/browser/ui/android/omnibox/java/res/values/dimens.xml
index 555e642e..5d46000 100644
--- a/chrome/browser/ui/android/omnibox/java/res/values/dimens.xml
+++ b/chrome/browser/ui/android/omnibox/java/res/values/dimens.xml
@@ -111,7 +111,7 @@
     <dimen name="omnibox_suggestion_dropdown_round_corner_radius">0dp</dimen>
     <dimen name="omnibox_min_space_above_window_bottom">0dp</dimen>
 
-    <item name="omnibox_suggestion_bg_hover_alpha" format="float" type="dimen">0.08</item>
+    <item name="omnibox_suggestion_bg_hover_overlay_fraction" type="fraction">8%</item>
 
     <!-- Adding search engine logo to the omnibox. -->
     <!-- Max size which will fit completely in the composed/rounded bg. -->
diff --git a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/styles/OmniboxResourceProvider.java b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/styles/OmniboxResourceProvider.java
index 93e32bd8..9a8257a3 100644
--- a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/styles/OmniboxResourceProvider.java
+++ b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/styles/OmniboxResourceProvider.java
@@ -10,6 +10,7 @@
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.Drawable.ConstantState;
 import android.graphics.drawable.LayerDrawable;
+import android.graphics.drawable.StateListDrawable;
 import android.util.SparseArray;
 import android.util.TypedValue;
 
@@ -389,30 +390,42 @@
                 : ContextCompat.getColor(context, R.color.omnibox_suggestion_bg);
     }
 
-    /**
-     * Returns the background hover drawable for suggestions in a "standard" (non-incognito) model
-     * with the given context.
-     */
-    private static Drawable getHoverSuggestionBackgroundDrawable(
+    /** Returns the background hover color for suggestions in a model with the given context. */
+    private static @ColorInt int getHoverSuggestionBackgroundColor(
             Context context, @BrandedColorScheme int colorScheme) {
-        return colorScheme == BrandedColorScheme.INCOGNITO
-                ? new ColorDrawable(context.getColor(R.color.omnibox_suggestion_bg_hover_incognito))
-                : AppCompatResources.getDrawable(
-                        context, R.drawable.omnibox_suggestion_bg_hover_layers);
+
+        if (colorScheme == BrandedColorScheme.INCOGNITO) {
+            return context.getColor(R.color.omnibox_suggestion_bg_hover_incognito);
+        }
+
+        // omnibox_suggestion_bg + 8% colorOnSurface
+        @ColorInt int baseColor = ContextCompat.getColor(context, R.color.omnibox_suggestion_bg);
+        @ColorInt int hoverColor = MaterialColors.getColor(context, R.attr.colorOnSurface, TAG);
+        float fraction =
+                context.getResources()
+                        .getFraction(R.fraction.omnibox_suggestion_bg_hover_overlay_fraction, 1, 1);
+
+        return ColorUtils.overlayColor(baseColor, hoverColor, fraction);
     }
 
     /** Returns a stateful suggestion background with the select default state. */
     public static Drawable getStatefulSuggestionBackground(
             Context context, @ColorInt int defaultColor, @BrandedColorScheme int colorScheme) {
         var background = new ColorDrawable(defaultColor);
-        // Hover, selected, hover and selected are defined in the drawable here.
-        var hover = getHoverSuggestionBackgroundDrawable(context, colorScheme);
+        var hover = new ColorDrawable(getHoverSuggestionBackgroundColor(context, colorScheme));
 
         // Ripple effect to use when the user interacts with the suggestion.
         var ripple =
                 resolveAttributeToDrawable(context, colorScheme, R.attr.selectableItemBackground);
 
-        return new LayerDrawable(new Drawable[] {background, hover, ripple});
+        var statefulBackground = new StateListDrawable();
+        statefulBackground.addState(new int[] {android.R.attr.state_selected}, hover);
+        statefulBackground.addState(new int[] {android.R.attr.state_hovered}, hover);
+        statefulBackground.addState(
+                new int[] {android.R.attr.state_selected, android.R.attr.state_hovered}, hover);
+        statefulBackground.addState(new int[] {}, background);
+
+        return new LayerDrawable(new Drawable[] {statefulBackground, ripple});
     }
 
     /**
diff --git a/chrome/browser/ui/autofill/payments/desktop_payments_window_manager.cc b/chrome/browser/ui/autofill/payments/desktop_payments_window_manager.cc
index 1264898..2f7b944b 100644
--- a/chrome/browser/ui/autofill/payments/desktop_payments_window_manager.cc
+++ b/chrome/browser/ui/autofill/payments/desktop_payments_window_manager.cc
@@ -17,6 +17,7 @@
 #include "chrome/browser/ui/tabs/tab_strip_model.h"
 #include "components/autofill/content/browser/content_autofill_client.h"
 #include "components/autofill/core/browser/autofill_progress_dialog_type.h"
+#include "components/autofill/core/browser/metrics/payments/bnpl_metrics.h"
 #include "components/autofill/core/browser/metrics/payments/payments_window_metrics.h"
 #include "components/autofill/core/browser/payments/card_unmask_challenge_option.h"
 #include "components/autofill/core/browser/payments/payments_autofill_client.h"
@@ -110,6 +111,7 @@
   flow_type_ = FlowType::kBnpl;
   bnpl_context_ = std::move(context);
   CreatePopup(bnpl_context_->initial_url, GetPopupSizeForBnpl());
+  autofill_metrics::LogBnplPopupWindowShown(bnpl_context_->issuer_id);
 }
 
 void DesktopPaymentsWindowManager::DidFinishNavigation(
@@ -302,6 +304,7 @@
   }
   std::move(bnpl_context_->completion_callback)
       .Run(result, std::move(most_recent_url_navigation_));
+  autofill_metrics::LogBnplPopupWindowResult(bnpl_context_->issuer_id, result);
   Reset();
 }
 
diff --git a/chrome/browser/ui/autofill/payments/desktop_payments_window_manager_interactive_uitest.cc b/chrome/browser/ui/autofill/payments/desktop_payments_window_manager_interactive_uitest.cc
index 126d03e3..89c101a3 100644
--- a/chrome/browser/ui/autofill/payments/desktop_payments_window_manager_interactive_uitest.cc
+++ b/chrome/browser/ui/autofill/payments/desktop_payments_window_manager_interactive_uitest.cc
@@ -22,8 +22,10 @@
 #include "chrome/test/interaction/interactive_browser_test.h"
 #include "components/autofill/content/browser/test_autofill_client_injector.h"
 #include "components/autofill/content/browser/test_content_autofill_client.h"
+#include "components/autofill/core/browser/metrics/payments/bnpl_metrics.h"
 #include "components/autofill/core/browser/metrics/payments/payments_window_metrics.h"
 #include "components/autofill/core/browser/payments/card_unmask_challenge_option.h"
+#include "components/autofill/core/browser/payments/constants.h"
 #include "components/autofill/core/browser/payments/payments_autofill_client.h"
 #include "components/autofill/core/browser/payments/payments_window_manager.h"
 #include "components/autofill/core/browser/payments/test_payments_autofill_client.h"
@@ -101,6 +103,7 @@
       window_manager().InitVcn3dsAuthentication(std::move(context));
     } else if (name.find("Bnpl") != std::string::npos) {
       PaymentsWindowManager::BnplContext context;
+      context.issuer_id = kBnplAffirmIssuerId;
       context.success_url_prefix = GURL(kBnplSuccessUrlPrefix);
       context.failure_url_prefix = GURL(kBnplFailureUrlPrefix);
       context.initial_url = GURL(kBnplInitialUrl);
@@ -821,6 +824,41 @@
   EXPECT_FALSE(test_api(window_manager()).GetBnplContext().has_value());
 }
 
+// Test that the PopupWindowShown histogram is logged if the BNPL pop-up is
+// shown.
+IN_PROC_BROWSER_TEST_F(DesktopPaymentsWindowManagerInteractiveUiTest,
+                       InvokeUi_Bnpl_PopupWindowShownLogged) {
+  ShowUi("Bnpl");
+
+  histogram_tester_.ExpectUniqueSample(
+      /*name=*/"Autofill.Bnpl.PopupWindowShown.Affirm",
+      /*sample=*/true, /*expected_bucket_count=*/1);
+}
+
+// Test that the BNPL PopupWindowResult histogram logs a success result if the
+// pop-up flow was successful.
+IN_PROC_BROWSER_TEST_F(DesktopPaymentsWindowManagerInteractiveUiTest,
+                       InvokeUi_Bnpl_Success_PopupWindowResultLogged) {
+  ShowUi("Bnpl");
+
+  // Navigate to the URL that denotes success inside of the BNPL pop-up.
+  GetPopupWebContents()->OpenURL(
+      content::OpenURLParams(GURL(std::string(kBnplSuccessUrlPrefix) +
+                                  "testqueryparam?param1=true"),
+                             content::Referrer(),
+                             WindowOpenDisposition::CURRENT_TAB,
+                             ui::PageTransition::PAGE_TRANSITION_AUTO_TOPLEVEL,
+                             /*is_renderer_initiated=*/false),
+      /*navigation_handle_callback=*/{});
+
+  WaitForPopupClose();
+
+  histogram_tester_.ExpectUniqueSample(
+      /*name=*/"Autofill.Bnpl.PopupWindowResult.Affirm",
+      /*sample=*/PaymentsWindowManager::BnplFlowResult::kSuccess,
+      /*expected_bucket_count=*/1);
+}
+
 // Test that the BNPL pop-up is shown correctly, and on close the completion
 // callback is triggered with a failure result if the flow was a failure.
 IN_PROC_BROWSER_TEST_F(DesktopPaymentsWindowManagerInteractiveUiTest,
@@ -850,6 +888,30 @@
   EXPECT_FALSE(test_api(window_manager()).GetBnplContext().has_value());
 }
 
+// Test that the BNPL PopupWindowResult histogram logs a failure result if the
+// pop-up flow was a failure.
+IN_PROC_BROWSER_TEST_F(DesktopPaymentsWindowManagerInteractiveUiTest,
+                       InvokeUi_Bnpl_Failure_PopupWindowResultLogged) {
+  ShowUi("Bnpl");
+
+  // Navigate to the URL that denotes failure inside of the BNPL pop-up.
+  GetPopupWebContents()->OpenURL(
+      content::OpenURLParams(GURL(std::string(kBnplFailureUrlPrefix) +
+                                  "testqueryparam?param1=true"),
+                             content::Referrer(),
+                             WindowOpenDisposition::CURRENT_TAB,
+                             ui::PageTransition::PAGE_TRANSITION_AUTO_TOPLEVEL,
+                             /*is_renderer_initiated=*/false),
+      /*navigation_handle_callback=*/{});
+
+  WaitForPopupClose();
+
+  histogram_tester_.ExpectUniqueSample(
+      /*name=*/"Autofill.Bnpl.PopupWindowResult.Affirm",
+      /*sample=*/PaymentsWindowManager::BnplFlowResult::kFailure,
+      /*expected_bucket_count=*/1);
+}
+
 // Test that the BNPL pop-up is shown correctly, and on close the completion
 // callback is triggered with a "user closed" result if the flow was closed by
 // the user.
@@ -870,6 +932,19 @@
   EXPECT_FALSE(test_api(window_manager()).GetBnplContext().has_value());
 }
 
+// Test that the PopupWindowResult histogram logs a "user closed" result if the
+// pop-up flow was closed by the user.
+IN_PROC_BROWSER_TEST_F(DesktopPaymentsWindowManagerInteractiveUiTest,
+                       InvokeUi_Bnpl_UserClosedPopup_PopupWindowResultLogged) {
+  ShowUi("Bnpl");
+  ClosePopupAndWait();
+
+  histogram_tester_.ExpectUniqueSample(
+      /*name=*/"Autofill.Bnpl.PopupWindowResult.Affirm",
+      /*sample=*/PaymentsWindowManager::BnplFlowResult::kUserClosed,
+      /*expected_bucket_count=*/1);
+}
+
 // Test that the BNPL pop-up is shown correctly, and on close the completion
 // callback is triggered with a "user closed" result if the flow was closed and
 // the URL contained the success URL prefix, but the success URL prefix was not
diff --git a/chrome/browser/ui/views/frame/browser_view.cc b/chrome/browser/ui/views/frame/browser_view.cc
index c336b6d..1686198 100644
--- a/chrome/browser/ui/views/frame/browser_view.cc
+++ b/chrome/browser/ui/views/frame/browser_view.cc
@@ -197,8 +197,6 @@
 #include "chrome/grit/branded_strings.h"
 #include "chrome/grit/generated_resources.h"
 #include "chrome/grit/theme_resources.h"
-#include "chromeos/components/mgs/managed_guest_session_utils.h"
-#include "chromeos/ui/frame/caption_buttons/frame_caption_button_container_view.h"
 #include "components/autofill/core/common/autofill_payments_features.h"
 #include "components/content_settings/core/common/features.h"
 #include "components/enterprise/buildflags/buildflags.h"
@@ -310,6 +308,8 @@
 #include "chrome/browser/ui/views/frame/browser_non_client_frame_view_chromeos.h"
 #include "chrome/browser/ui/views/frame/top_controls_slide_controller_chromeos.h"
 #include "chrome/grit/chrome_unscaled_resources.h"
+#include "chromeos/components/mgs/managed_guest_session_utils.h"
+#include "chromeos/ui/frame/caption_buttons/frame_caption_button_container_view.h"
 #include "chromeos/ui/frame/caption_buttons/frame_size_button.h"
 #include "chromeos/ui/wm/desks/desks_helper.h"
 #include "ui/compositor/compositor_metrics_tracker.h"
diff --git a/chrome/browser/ui/views/frame/picture_in_picture_browser_frame_view_interactive_uitest.cc b/chrome/browser/ui/views/frame/picture_in_picture_browser_frame_view_interactive_uitest.cc
index ceb3e878..3efc317 100644
--- a/chrome/browser/ui/views/frame/picture_in_picture_browser_frame_view_interactive_uitest.cc
+++ b/chrome/browser/ui/views/frame/picture_in_picture_browser_frame_view_interactive_uitest.cc
@@ -28,6 +28,7 @@
 #include "media/base/media_switches.h"
 #include "net/dns/mock_host_resolver.h"
 #include "third_party/blink/public/common/features.h"
+#include "ui/compositor/layer.h"
 #include "ui/events/test/event_generator.h"
 #include "ui/gfx/animation/animation_test_api.h"
 #include "ui/views/widget/widget_utils.h"
diff --git a/chrome/browser/ui/views/location_bar/icon_label_bubble_view.cc b/chrome/browser/ui/views/location_bar/icon_label_bubble_view.cc
index 083e136..7863d06 100644
--- a/chrome/browser/ui/views/location_bar/icon_label_bubble_view.cc
+++ b/chrome/browser/ui/views/location_bar/icon_label_bubble_view.cc
@@ -24,6 +24,7 @@
 #include "ui/gfx/geometry/rect.h"
 #include "ui/gfx/geometry/skia_conversions.h"
 #include "ui/gfx/scoped_canvas.h"
+#include "ui/gfx/text_constants.h"
 #include "ui/views/accessibility/ax_virtual_view.h"
 #include "ui/views/accessibility/view_accessibility.h"
 #include "ui/views/animation/flood_fill_ink_drop_ripple.h"
@@ -219,13 +220,33 @@
   layout.child_layouts.emplace_back(ink_drop_container(), true,
                                     GetLocalBounds());
 
+  // First calculate the preferred size of this view, according to the
+  // preferred size of the label (i.e. with an expanded label).
+  // If that won't fit in the available bounds, then size this view
+  // based on a collapsed label.
+  const int preferred_label_width = label()->GetPreferredSize().width();
+  gfx::Size preferred_size = GetSizeForLabelWidth(preferred_label_width);
+  if (size_bounds.width().is_bounded() &&
+      preferred_size.width() > size_bounds.width()) {
+    if (label()->GetElideBehavior() == gfx::NO_ELIDE) {
+      preferred_size = GetSizeForLabelWidth(0);
+    } else {
+      // If the label supports eliding, whittle away from its fully expanded
+      // size.
+      preferred_size.set_width(size_bounds.width().value());
+    }
+  }
+  layout.host_size = preferred_size;
+  const int height = preferred_size.height();
+
   // We may not have horizontal room for both the image and the trailing
   // padding. When the view is expanding (or showing-label steady state), the
   // image. When the view is contracting (or hidden-label steady state), whittle
   // away at the trailing padding instead.
   int bubble_trailing_padding = GetEndPaddingWithSeparator();
   int image_width = image_container_view()->GetPreferredSize().width();
-  const int space_shortage = image_width + bubble_trailing_padding - width();
+  const int space_shortage =
+      image_width + bubble_trailing_padding - preferred_size.width();
   if (space_shortage > 0) {
     if (ShouldShowLabel()) {
       image_width -= space_shortage;
@@ -233,29 +254,44 @@
       bubble_trailing_padding -= space_shortage;
     }
   }
-  gfx::Rect image_bounds(GetInsets().left(), 0, image_width, height());
-  layout.child_layouts.emplace_back(
-      const_cast<views::View*>(this->image_container_view()), true,
-      image_bounds);
+
+  const int image_x = GetInsets().left();
+  const gfx::Rect image_bounds(image_x, 0, image_width, height);
+
+  // There may be extra padding added if the label is shown.
+  // The "_with_label" image values are used to compute the label's bounds,
+  // which is then compared to the available bounds for the label. If the
+  // label would fit in the bounds (i.e. not collapsed), then the "_with_label"
+  // values will accepted as the image's bounds too.
+  const int image_x_with_label =
+      image_x + GetWidthBetween(0, expanded_label_additional_insets_.leading());
+  const gfx::Rect image_bounds_with_label(image_x_with_label, 0, image_width,
+                                          height);
 
   // Compute the label bounds. The label gets whatever size is left over after
   // accounting for the preferred image width and padding amounts. Note that if
   // the label has zero size it doesn't actually matter what we compute its X
   // value to be, since it won't be visible.
-  const int label_x = image_bounds.right() + GetInternalSpacing();
-  int label_width = label()->GetPreferredSize(size_bounds).width();
-  if (size_bounds.width().is_bounded() &&
-      label_width > size_bounds.width() - label_x - bubble_trailing_padding -
-                        GetWidthBetweenIconAndSeparator()) {
-    label_width = 0;
-  }
-  gfx::Rect label_bounds(label_x, 0, label_width, height());
+  const int label_x = image_bounds_with_label.right() + GetInternalSpacing();
+  const int available_label_width =
+      std::max(0, layout.host_size.width() - label_x - bubble_trailing_padding -
+                      GetWidthBetweenIconAndSeparator());
+  gfx::Rect label_bounds(label_x, 0, available_label_width, height);
   layout.child_layouts.emplace_back(
       label(),
       static_cast<views::LayoutManagerBase*>(GetLayoutManager())
           ->CanBeVisible(label()),
       label_bounds);
 
+  // If the fully expanded label fits, or it is mid-animation, then we
+  // accept the "_with_label" image bounds.
+  const bool can_label_expand = available_label_width >= preferred_label_width;
+  layout.child_layouts.emplace_back(
+      const_cast<views::View*>(this->image_container_view()), true,
+      (can_label_expand || slide_animation_.is_animating())
+          ? image_bounds_with_label
+          : image_bounds);
+
   // The separator should be the same height as the icons.
   const int separator_height = GetLayoutConstant(LOCATION_BAR_ICON_SIZE);
   gfx::Rect separator_bounds(label_bounds);
@@ -271,7 +307,6 @@
       gfx::Rect(separator_x, separator_bounds.y(), separator_width,
                 separator_height));
 
-  layout.host_size = GetSizeForLabelWidth(label_width);
   return layout;
 }
 
@@ -302,6 +337,12 @@
   label()->SetFontList(font_list);
 }
 
+void IconLabelBubbleView::SetExpandedLabelAdditionalInsets(
+    const views::Inset1D& insets) {
+  expanded_label_additional_insets_ = insets;
+  InvalidateLayout();
+}
+
 SkColor IconLabelBubbleView::GetBackgroundColor() const {
   ui::ColorId background_color_id = ui::kUiColorsStart;
   if (background_color_id_.has_value()) {
@@ -597,7 +638,8 @@
 
   const int min_width =
       shrinking ? image_size.width() : grow_animation_starting_width_;
-  const int max_width = image_size.width() + GetInternalSpacing() + label_width;
+  const int max_width = image_size.width() + GetInternalSpacing() +
+                        label_width + expanded_label_additional_insets_.size();
 
   return gfx::Size(GetWidthBetween(min_width, max_width), image_size.height());
 }
diff --git a/chrome/browser/ui/views/location_bar/icon_label_bubble_view.h b/chrome/browser/ui/views/location_bar/icon_label_bubble_view.h
index 3ce698e2..9762422 100644
--- a/chrome/browser/ui/views/location_bar/icon_label_bubble_view.h
+++ b/chrome/browser/ui/views/location_bar/icon_label_bubble_view.h
@@ -23,6 +23,7 @@
 #include "ui/views/animation/ink_drop_observer.h"
 #include "ui/views/controls/button/label_button.h"
 #include "ui/views/controls/label.h"
+#include "ui/views/layout/flex_layout_types.h"
 #include "ui/views/widget/widget_observer.h"
 
 namespace gfx {
@@ -119,6 +120,10 @@
   void SetLabel(std::u16string_view label, std::u16string_view accessible_name);
   void SetFontList(const gfx::FontList& font_list);
 
+  // Applies additional padding around the icon and label whenever the label
+  // is visible and not collapsed.
+  void SetExpandedLabelAdditionalInsets(const views::Inset1D& insets);
+
   gfx::RoundedCornersF GetCornerRadii() const;
   void SetCornerRadii(const gfx::RoundedCornersF& radii);
 
@@ -335,6 +340,9 @@
   std::optional<ui::ColorId> foreground_color_id_;
 
   std::optional<gfx::RoundedCornersF> radii_;
+
+  // Padding that should be applied when the label is expanded.
+  views::Inset1D expanded_label_additional_insets_;
 };
 
 #endif  // CHROME_BROWSER_UI_VIEWS_LOCATION_BAR_ICON_LABEL_BUBBLE_VIEW_H_
diff --git a/chrome/browser/ui/views/location_bar/icon_label_bubble_view_unittest.cc b/chrome/browser/ui/views/location_bar/icon_label_bubble_view_unittest.cc
index 6a1c892..5dd974b2 100644
--- a/chrome/browser/ui/views/location_bar/icon_label_bubble_view_unittest.cc
+++ b/chrome/browser/ui/views/location_bar/icon_label_bubble_view_unittest.cc
@@ -21,11 +21,14 @@
 #include "ui/events/base_event_utils.h"
 #include "ui/events/gesture_detection/gesture_configuration.h"
 #include "ui/events/test/event_generator.h"
+#include "ui/gfx/geometry/rect.h"
 #include "ui/gfx/image/image_unittest_util.h"
 #include "ui/views/animation/ink_drop.h"
 #include "ui/views/animation/test/ink_drop_host_test_api.h"
 #include "ui/views/animation/test/test_ink_drop.h"
+#include "ui/views/border.h"
 #include "ui/views/controls/image_view.h"
+#include "ui/views/layout/flex_layout_types.h"
 #include "ui/views/widget/widget_utils.h"
 
 #if defined(USE_AURA)
@@ -622,6 +625,81 @@
   EXPECT_EQ(nullptr, view()->GetBackground());
 }
 
+// Tests that the client-provided additional padding for labels is correctly
+// applied when the label is expanded.
+TEST_F(IconLabelBubbleViewTest, AdditionalPaddingForLabelAppliedWhenExpanded) {
+  view()->SetUpAnimation();
+  view()->ResetSlideAnimation(true);
+  ASSERT_TRUE(view()->IsLabelVisible());
+  view()->SizeToPreferredSize();
+  EXPECT_EQ(view()->GetInsets().left(), view()->GetImageContainerView()->x());
+
+  const int initial_width = view()->width();
+  const int initial_image_x = view()->GetImageContainerView()->x();
+  const int initial_label_x = view()->GetLabelBounds().x();
+  const int initial_label_right = view()->GetLabelBounds().right();
+
+  // Set the additional insets for labels.
+  constexpr int kExpandedLabelLeftPadding = 1;
+  constexpr int kExpandedLabelRightPadding = 2;
+  view()->SetExpandedLabelAdditionalInsets(
+      views::Inset1D(kExpandedLabelLeftPadding, kExpandedLabelRightPadding));
+  view()->SizeToPreferredSize();
+
+  // The container and label should be shifted to the right by the additional
+  // left padding.
+  EXPECT_EQ(initial_image_x + kExpandedLabelLeftPadding,
+            view()->GetImageContainerView()->x());
+  EXPECT_EQ(initial_label_x + kExpandedLabelLeftPadding,
+            view()->GetLabelBounds().x());
+
+  // The total width should also include the left and right values of the
+  // additional padding.
+  EXPECT_EQ(initial_label_right + kExpandedLabelLeftPadding +
+                kExpandedLabelRightPadding,
+            view()->GetLabelBounds().right());
+  EXPECT_EQ(
+      initial_width + kExpandedLabelLeftPadding + kExpandedLabelRightPadding,
+      view()->width());
+
+  // Collapse the label. The additional padding should not be applied anymore.
+  view()->SetBounds(0, 0, initial_width - view()->GetLabelBounds().width(),
+                    view()->height());
+  EXPECT_EQ(0, view()->GetLabelBounds().width());
+  EXPECT_EQ(initial_image_x, view()->GetImageContainerView()->x());
+}
+
+// Tests that the client-provided additional padding for labels is not
+// applied when the label is not shown.
+TEST_F(IconLabelBubbleViewTest, AdditionalPaddingRespectsLabelVisibility) {
+  view()->SetUpAnimation();
+  view()->ResetSlideAnimation(false);
+  view()->SizeToPreferredSize();
+  ASSERT_FALSE(view()->IsLabelVisible());
+
+  const int initial_width = view()->width();
+  const int initial_image_x = view()->GetImageContainerView()->x();
+
+  // Set the additional insets for labels. These shouldn't be applied until
+  // the label is shown.
+  constexpr int kExpandedLabelLeftPadding = 1;
+  constexpr int kExpandedLabelRightPadding = 2;
+  view()->SetExpandedLabelAdditionalInsets(
+      views::Inset1D(kExpandedLabelLeftPadding, kExpandedLabelRightPadding));
+  view()->SizeToPreferredSize();
+
+  // Nothing should have changed.
+  EXPECT_EQ(initial_image_x, view()->GetImageContainerView()->x());
+  EXPECT_EQ(initial_width, view()->width());
+
+  // Show the label. The additional padding should be applied.
+  view()->ResetSlideAnimation(true);
+  view()->SizeToPreferredSize();
+  ASSERT_TRUE(view()->IsLabelVisible());
+  EXPECT_EQ(initial_image_x + kExpandedLabelLeftPadding,
+            view()->GetImageContainerView()->x());
+}
+
 #if defined(USE_AURA)
 // Verifies IconLabelBubbleView::CalculatePreferredSize() doesn't crash when
 // there is a widget but no compositor.
@@ -645,29 +723,28 @@
 }
 #endif
 
-// This view facilitates checking each of its calculated widths, used
+// This view facilitates checking view state during animation steps, used
 // for regression testing crbug.com/401231035.
-class TestIconLabelBubbleViewWidthChecker : public TestIconLabelBubbleView {
+class TestIconLabelBubbleViewAnimationChecker : public TestIconLabelBubbleView {
  public:
   using TestIconLabelBubbleView::TestIconLabelBubbleView;
 
-  void SetWidthCheckCallback(base::RepeatingCallback<void(int)> cb) {
-    width_check_cb_ = std::move(cb);
+  void SetAnimationProgressedCallback(
+      base::RepeatingCallback<void(views::View*)> callback) {
+    animation_progress_callback_ = std::move(callback);
   }
 
  private:
-  int GetWidthBetween(int min, int max) const override {
-    int result = IconLabelBubbleView::GetWidthBetween(min, max);
-    if (width_check_cb_) {
-      width_check_cb_.Run(result);
+  void AnimationProgressed(const gfx::Animation* animation) override {
+    IconLabelBubbleView::AnimationProgressed(animation);
+    if (animation_progress_callback_) {
+      animation_progress_callback_.Run(this);
     }
-    return result;
   }
-
-  base::RepeatingCallback<void(int)> width_check_cb_;
+  base::RepeatingCallback<void(views::View*)> animation_progress_callback_;
 };
 
-class IconLabelBubbleViewWidthTest : public IconLabelBubbleViewTestBase {
+class IconLabelBubbleViewAnimationTest : public IconLabelBubbleViewTestBase {
  public:
   using IconLabelBubbleViewTestBase::IconLabelBubbleViewTestBase;
 
@@ -678,7 +755,8 @@
 
     widget_ = CreateTestWidget(views::Widget::InitParams::CLIENT_OWNS_WIDGET);
     view_ = widget_->SetContentsView(
-        std::make_unique<TestIconLabelBubbleViewWidthChecker>(font_list, this));
+        std::make_unique<TestIconLabelBubbleViewAnimationChecker>(font_list,
+                                                                  this));
     widget_->Show();
   }
 
@@ -688,16 +766,16 @@
     ChromeViewsTestBase::TearDown();
   }
 
-  TestIconLabelBubbleViewWidthChecker* view() { return view_; }
+  TestIconLabelBubbleViewAnimationChecker* view() { return view_; }
 
  private:
   std::unique_ptr<views::Widget> widget_;
-  raw_ptr<TestIconLabelBubbleViewWidthChecker> view_;
+  raw_ptr<TestIconLabelBubbleViewAnimationChecker> view_;
 };
 
 // Regression test for crbug.com/401231035, where AnimateOut would flicker at
 // the beginning of the animation.
-TEST_F(IconLabelBubbleViewWidthTest, WidthDecreasesDuringAnimateOut) {
+TEST_F(IconLabelBubbleViewAnimationTest, WidthDecreasesDuringAnimateOut) {
   gfx::Animation::SetPrefersReducedMotionForTesting(false);
   ASSERT_FALSE(gfx::Animation::PrefersReducedMotion());
 
@@ -709,9 +787,10 @@
 
   int last_width = view()->GetPreferredSize().width();
   int animation_step_count = 0;
-  view()->SetWidthCheckCallback(base::BindRepeating(
-      [](int& last_width, int& animation_step_count, int width) {
-        EXPECT_LE(width, last_width)
+  view()->SetAnimationProgressedCallback(base::BindRepeating(
+      [](int& last_width, int& animation_step_count, views::View* view) {
+        const int width = view->GetPreferredSize().width();
+        EXPECT_LT(width, last_width)
             << "Failed on animation step #" << animation_step_count;
         last_width = width;
         ++animation_step_count;
diff --git a/chrome/browser/ui/views/omnibox/omnibox_match_cell_view.cc b/chrome/browser/ui/views/omnibox/omnibox_match_cell_view.cc
index cd657b87..439f1880 100644
--- a/chrome/browser/ui/views/omnibox/omnibox_match_cell_view.cc
+++ b/chrome/browser/ui/views/omnibox/omnibox_match_cell_view.cc
@@ -249,9 +249,6 @@
                                          const AutocompleteMatch& match) {
   if (ShouldDisplayImage(match)) {
     // Enterprise search aggregator people suggestions may display an image.
-    CHECK(AutocompleteMatch::IsSearchType(match.type) ||
-          match.provider->type() ==
-              AutocompleteProvider::TYPE_ENTERPRISE_SEARCH_AGGREGATOR);
     layout_style_ = LayoutStyle::SEARCH_SUGGESTION_WITH_IMAGE;
   } else if (AutocompleteMatch::IsSearchType(match.type)) {
     layout_style_ = LayoutStyle::SEARCH_SUGGESTION;
diff --git a/chrome/browser/ui/views/overlay/video_overlay_window_views.h b/chrome/browser/ui/views/overlay/video_overlay_window_views.h
index 2bd1a297..0034e5bc 100644
--- a/chrome/browser/ui/views/overlay/video_overlay_window_views.h
+++ b/chrome/browser/ui/views/overlay/video_overlay_window_views.h
@@ -11,7 +11,6 @@
 #include "base/memory/weak_ptr.h"
 #include "base/timer/timer.h"
 #include "chrome/browser/picture_in_picture/auto_pip_setting_overlay_view.h"
-#include "chromeos/ui/frame/highlight_border_overlay.h"
 #include "components/global_media_controls/public/views/media_progress_view.h"
 #include "content/public/browser/overlay_window.h"
 #include "content/public/browser/video_picture_in_picture_window_controller.h"
@@ -21,6 +20,10 @@
 #include "ui/views/view_observer.h"
 #include "ui/views/widget/widget.h"
 
+#if BUILDFLAG(IS_CHROMEOS)
+#include "chromeos/ui/frame/highlight_border_overlay.h"
+#endif
+
 namespace views {
 class ImageView;
 class Label;
diff --git a/chrome/browser/ui/webui/omnibox/omnibox_page_handler.cc b/chrome/browser/ui/webui/omnibox/omnibox_page_handler.cc
index bbed140..781b4237 100644
--- a/chrome/browser/ui/webui/omnibox/omnibox_page_handler.cc
+++ b/chrome/browser/ui/webui/omnibox/omnibox_page_handler.cc
@@ -270,7 +270,12 @@
         base::UTF16ToUTF8(input.inline_autocompletion);
     result->destination_url = input.destination_url.spec();
     result->stripped_destination_url = input.stripped_destination_url.spec();
-    result->image = input.ImageUrl().spec().c_str();
+
+    GURL image = input.ImageUrl();
+    if (image.is_empty())
+      image = input.icon_url;
+    result->image = image.spec().c_str();
+
     result->contents = base::UTF16ToUTF8(input.contents);
     result->contents_class =
         mojo::ConvertTo<std::vector<mojom::ACMatchClassificationPtr>>(
diff --git a/chrome/browser/web_applications/policy/web_app_policy_manager_unittest.cc b/chrome/browser/web_applications/policy/web_app_policy_manager_unittest.cc
index d378d4c6..eb14a415 100644
--- a/chrome/browser/web_applications/policy/web_app_policy_manager_unittest.cc
+++ b/chrome/browser/web_applications/policy/web_app_policy_manager_unittest.cc
@@ -30,6 +30,7 @@
 #include "chrome/browser/web_applications/mojom/user_display_mode.mojom.h"
 #include "chrome/browser/web_applications/os_integration/os_integration_manager.h"
 #include "chrome/browser/web_applications/policy/web_app_policy_constants.h"
+#include "chrome/browser/web_applications/proto/web_app_install_state.pb.h"
 #include "chrome/browser/web_applications/test/fake_externally_managed_app_manager.h"
 #include "chrome/browser/web_applications/test/fake_web_app_provider.h"
 #include "chrome/browser/web_applications/test/os_integration_test_override_impl.h"
@@ -124,116 +125,32 @@
       .Set(kDefaultLaunchContainerKey, kDefaultLaunchContainerWindowValue);
 }
 
-ExternalInstallOptions GetWindowedInstallOptions(
-    PlaceholderResolutionBehavior placeholder_resolution_behavior =
-        PlaceholderResolutionBehavior::kClose) {
-  ExternalInstallOptions options(GURL(kWindowedUrl),
-                                 mojom::UserDisplayMode::kStandalone,
-                                 ExternalInstallSource::kExternalPolicy);
-  options.add_to_applications_menu = true;
-  options.add_to_desktop = false;
-  options.add_to_quick_launch_bar = false;
-  options.install_placeholder = true;
-  options.placeholder_resolution_behavior = placeholder_resolution_behavior;
-  return options;
-}
-
 base::Value::Dict GetTabbedItem() {
   return base::Value::Dict()
       .Set(kUrlKey, kTabbedUrl)
       .Set(kDefaultLaunchContainerKey, kDefaultLaunchContainerTabValue);
 }
 
-ExternalInstallOptions GetTabbedInstallOptions(
-    PlaceholderResolutionBehavior placeholder_resolution_behavior =
-        PlaceholderResolutionBehavior::kClose) {
-  ExternalInstallOptions options(GURL(kTabbedUrl),
-                                 mojom::UserDisplayMode::kBrowser,
-                                 ExternalInstallSource::kExternalPolicy);
-  options.add_to_applications_menu = true;
-  options.add_to_desktop = false;
-  options.add_to_quick_launch_bar = false;
-  options.install_placeholder = true;
-  options.placeholder_resolution_behavior = placeholder_resolution_behavior;
-  return options;
-}
-
 base::Value::Dict GetNoContainerItem() {
   return base::Value::Dict().Set(kUrlKey, kNoContainerUrl);
 }
 
-ExternalInstallOptions GetNoContainerInstallOptions(
-    PlaceholderResolutionBehavior placeholder_resolution_behavior =
-        PlaceholderResolutionBehavior::kClose) {
-  ExternalInstallOptions options(GURL(kNoContainerUrl),
-                                 mojom::UserDisplayMode::kBrowser,
-                                 ExternalInstallSource::kExternalPolicy);
-  options.add_to_applications_menu = true;
-  options.add_to_desktop = false;
-  options.add_to_quick_launch_bar = false;
-  options.install_placeholder = true;
-  options.placeholder_resolution_behavior = placeholder_resolution_behavior;
-  return options;
-}
-
 base::Value::Dict GetCreateDesktopShortcutDefaultItem() {
   return base::Value::Dict().Set(kUrlKey, kNoContainerUrl);
 }
 
-ExternalInstallOptions GetCreateDesktopShortcutDefaultInstallOptions(
-    PlaceholderResolutionBehavior placeholder_resolution_behavior =
-        PlaceholderResolutionBehavior::kClose) {
-  ExternalInstallOptions options(GURL(kNoContainerUrl),
-                                 mojom::UserDisplayMode::kBrowser,
-                                 ExternalInstallSource::kExternalPolicy);
-  options.add_to_applications_menu = true;
-  options.add_to_desktop = false;
-  options.add_to_quick_launch_bar = false;
-  options.install_placeholder = true;
-  options.placeholder_resolution_behavior = placeholder_resolution_behavior;
-  return options;
-}
-
 base::Value::Dict GetCreateDesktopShortcutFalseItem() {
   return base::Value::Dict()
       .Set(kUrlKey, kNoContainerUrl)
       .Set(kCreateDesktopShortcutKey, false);
 }
 
-ExternalInstallOptions GetCreateDesktopShortcutFalseInstallOptions(
-    PlaceholderResolutionBehavior placeholder_resolution_behavior =
-        PlaceholderResolutionBehavior::kClose) {
-  ExternalInstallOptions options(GURL(kNoContainerUrl),
-                                 mojom::UserDisplayMode::kBrowser,
-                                 ExternalInstallSource::kExternalPolicy);
-  options.add_to_applications_menu = true;
-  options.add_to_desktop = false;
-  options.add_to_quick_launch_bar = false;
-  options.install_placeholder = true;
-  options.placeholder_resolution_behavior = placeholder_resolution_behavior;
-  return options;
-}
-
 base::Value::Dict GetCreateDesktopShortcutTrueItem() {
   return base::Value::Dict()
       .Set(kUrlKey, kNoContainerUrl)
       .Set(kCreateDesktopShortcutKey, true);
 }
 
-ExternalInstallOptions GetCreateDesktopShortcutTrueInstallOptions(
-    PlaceholderResolutionBehavior placeholder_resolution_behavior =
-        PlaceholderResolutionBehavior::kClose) {
-  ExternalInstallOptions options(GURL(kNoContainerUrl),
-                                 mojom::UserDisplayMode::kBrowser,
-                                 ExternalInstallSource::kExternalPolicy);
-  options.add_to_applications_menu = true;
-  options.add_to_desktop = true;
-  options.add_to_quick_launch_bar = false;
-  options.install_placeholder = true;
-  options.placeholder_resolution_behavior = placeholder_resolution_behavior;
-  return options;
-}
-
 class MockAppRegistrarObserver : public WebAppRegistrarObserver {
  public:
   void OnWebAppSettingsPolicyChanged() override {
@@ -258,21 +175,6 @@
       .Set(kFallbackAppNameKey, kDefaultFallbackAppName);
 }
 
-ExternalInstallOptions GetFallbackAppNameInstallOptions(
-    PlaceholderResolutionBehavior placeholder_resolution_behavior =
-        PlaceholderResolutionBehavior::kClose) {
-  ExternalInstallOptions options(GURL(kWindowedUrl),
-                                 mojom::UserDisplayMode::kStandalone,
-                                 ExternalInstallSource::kExternalPolicy);
-  options.add_to_applications_menu = true;
-  options.add_to_desktop = false;
-  options.add_to_quick_launch_bar = false;
-  options.install_placeholder = true;
-  options.placeholder_resolution_behavior = placeholder_resolution_behavior;
-  options.fallback_app_name = kDefaultFallbackAppName;
-  return options;
-}
-
 base::Value::Dict GetCustomAppNameItem(std::string name) {
   return base::Value::Dict()
       .Set(kUrlKey, kWindowedUrl)
@@ -280,22 +182,6 @@
       .Set(kCustomNameKey, std::move(name));
 }
 
-ExternalInstallOptions GetCustomAppNameInstallOptions(
-    std::string name,
-    PlaceholderResolutionBehavior placeholder_resolution_behavior =
-        PlaceholderResolutionBehavior::kClose) {
-  ExternalInstallOptions options(GURL(kWindowedUrl),
-                                 mojom::UserDisplayMode::kStandalone,
-                                 ExternalInstallSource::kExternalPolicy);
-  options.add_to_applications_menu = true;
-  options.add_to_desktop = false;
-  options.add_to_quick_launch_bar = false;
-  options.install_placeholder = true;
-  options.placeholder_resolution_behavior = placeholder_resolution_behavior;
-  options.override_name = std::move(name);
-  return options;
-}
-
 base::Value::Dict GetCustomAppIconItem(bool secure = true) {
   return base::Value::Dict()
       .Set(kUrlKey, kWindowedUrl)
@@ -307,21 +193,6 @@
                .Set(kCustomIconHashKey, kDefaultCustomIconHash));
 }
 
-ExternalInstallOptions GetCustomAppIconInstallOptions(
-    PlaceholderResolutionBehavior placeholder_resolution_behavior =
-        PlaceholderResolutionBehavior::kClose) {
-  ExternalInstallOptions options(GURL(kWindowedUrl),
-                                 mojom::UserDisplayMode::kStandalone,
-                                 ExternalInstallSource::kExternalPolicy);
-  options.add_to_applications_menu = true;
-  options.add_to_desktop = false;
-  options.add_to_quick_launch_bar = false;
-  options.install_placeholder = true;
-  options.placeholder_resolution_behavior = placeholder_resolution_behavior;
-  options.override_icon_url = GURL(kDefaultCustomIconUrl);
-  return options;
-}
-
 void SetWebAppSettingsListPref(Profile* profile, std::string_view pref) {
   ASSERT_OK_AND_ASSIGN(
       auto result,
@@ -403,6 +274,11 @@
                 std::unique_ptr<WebApp> web_app = test::CreateWebApp(
                     install_url,
                     ConvertExternalInstallSourceToSource(install_source));
+                if (install_options.fallback_app_name) {
+                  web_app->SetName(install_options.fallback_app_name.value());
+                }
+                // Since `override_name` gets precendence over
+                // `fallback_app_name`, this will override the `fallback`.
                 if (install_options.override_name) {
                   web_app->SetName(install_options.override_name.value());
                 }
@@ -522,6 +398,21 @@
     loop.Run();
   }
 
+  const WebApp* GetPolicyInstalledWindowedApp() {
+    return app_registrar().LookUpAppByInstallSourceInstallUrl(
+        WebAppManagement::kPolicy, GURL(kWindowedUrl));
+  }
+
+  const WebApp* GetPolicyInstalledTabbedApp() {
+    return app_registrar().LookUpAppByInstallSourceInstallUrl(
+        WebAppManagement::kPolicy, GURL(kTabbedUrl));
+  }
+
+  const WebApp* GetPolicyInstalledNoContainerApp() {
+    return app_registrar().LookUpAppByInstallSourceInstallUrl(
+        WebAppManagement::kPolicy, GURL(kNoContainerUrl));
+  }
+
  private:
   webapps::InstallResultCode install_result_code_ =
       webapps::InstallResultCode::kSuccessNewInstall;
@@ -553,9 +444,6 @@
 };
 
 TEST_F(WebAppPolicyManagerTest, NoPrefValues) {
-  const auto& install_requests =
-      externally_managed_app_manager().install_requests();
-  EXPECT_TRUE(install_requests.empty());
   ValidateEmptyWebAppSettingsPolicy();
 }
 
@@ -564,10 +452,7 @@
                                  base::Value::List());
 
   WaitForAppsToSynchronize();
-
-  const auto& install_requests =
-      externally_managed_app_manager().install_requests();
-  EXPECT_TRUE(install_requests.empty());
+  ASSERT_TRUE(app_registrar().is_empty());
 }
 
 TEST_F(WebAppPolicyManagerTest, NoWebAppSettings) {
@@ -688,14 +573,8 @@
 
   WaitForAppsToSynchronize();
 
-  const auto& install_requests =
-      externally_managed_app_manager().install_requests();
-
-  std::vector<ExternalInstallOptions> expected_install_options_list;
-  expected_install_options_list.push_back(GetWindowedInstallOptions());
-  expected_install_options_list.push_back(GetTabbedInstallOptions());
-
-  EXPECT_EQ(install_requests, expected_install_options_list);
+  EXPECT_NE(GetPolicyInstalledWindowedApp(), nullptr);
+  EXPECT_NE(GetPolicyInstalledTabbedApp(), nullptr);
 }
 
 TEST_F(WebAppPolicyManagerTest, ForceInstallAppWithNoDefaultLaunchContainer) {
@@ -706,13 +585,7 @@
 
   WaitForAppsToSynchronize();
 
-  const auto& install_requests =
-      externally_managed_app_manager().install_requests();
-
-  std::vector<ExternalInstallOptions> expected_install_options_list;
-  expected_install_options_list.push_back(GetNoContainerInstallOptions());
-
-  EXPECT_EQ(install_requests, expected_install_options_list);
+  EXPECT_NE(GetPolicyInstalledNoContainerApp(), nullptr);
 }
 
 TEST_F(WebAppPolicyManagerTest,
@@ -724,14 +597,10 @@
 
   WaitForAppsToSynchronize();
 
-  const auto& install_requests =
-      externally_managed_app_manager().install_requests();
-
-  std::vector<ExternalInstallOptions> expected_install_options_list;
-  expected_install_options_list.push_back(
-      GetCreateDesktopShortcutDefaultInstallOptions());
-
-  EXPECT_EQ(install_requests, expected_install_options_list);
+  EXPECT_NE(GetPolicyInstalledNoContainerApp(), nullptr);
+  EXPECT_EQ(proto::INSTALLED_WITHOUT_OS_INTEGRATION,
+            app_registrar().GetInstallState(
+                GetPolicyInstalledNoContainerApp()->app_id()));
 }
 
 TEST_F(WebAppPolicyManagerTest, ForceInstallAppWithCreateDesktopShortcut) {
@@ -743,16 +612,10 @@
 
   WaitForAppsToSynchronize();
 
-  const auto& install_requests =
-      externally_managed_app_manager().install_requests();
-
-  std::vector<ExternalInstallOptions> expected_install_options_list;
-  expected_install_options_list.push_back(
-      GetCreateDesktopShortcutFalseInstallOptions());
-  expected_install_options_list.push_back(
-      GetCreateDesktopShortcutTrueInstallOptions());
-
-  EXPECT_EQ(install_requests, expected_install_options_list);
+  EXPECT_NE(GetPolicyInstalledNoContainerApp(), nullptr);
+  EXPECT_EQ(proto::INSTALLED_WITHOUT_OS_INTEGRATION,
+            app_registrar().GetInstallState(
+                GetPolicyInstalledNoContainerApp()->app_id()));
 }
 
 TEST_F(WebAppPolicyManagerTest, ForceInstallAppWithFallbackAppName) {
@@ -763,13 +626,10 @@
 
   WaitForAppsToSynchronize();
 
-  const auto& install_requests =
-      externally_managed_app_manager().install_requests();
-
-  std::vector<ExternalInstallOptions> expected_install_options_list;
-  expected_install_options_list.push_back(GetFallbackAppNameInstallOptions());
-
-  EXPECT_EQ(install_requests, expected_install_options_list);
+  EXPECT_NE(GetPolicyInstalledWindowedApp(), nullptr);
+  EXPECT_EQ(kDefaultFallbackAppName,
+            app_registrar().GetAppShortName(
+                GetPolicyInstalledWindowedApp()->app_id()));
 }
 
 TEST_F(WebAppPolicyManagerTest, ForceInstallAppWithCustomAppIcon) {
@@ -779,14 +639,7 @@
                                  std::move(list));
 
   WaitForAppsToSynchronize();
-
-  const auto& install_requests =
-      externally_managed_app_manager().install_requests();
-
-  std::vector<ExternalInstallOptions> expected_install_options_list;
-  expected_install_options_list.push_back(GetCustomAppIconInstallOptions());
-
-  EXPECT_EQ(install_requests, expected_install_options_list);
+  EXPECT_NE(GetPolicyInstalledWindowedApp(), nullptr);
 }
 
 // If the custom icon URL is not https, the icon should be ignored.
@@ -798,14 +651,10 @@
 
   WaitForAppsToSynchronize();
 
-  const auto& install_requests =
-      externally_managed_app_manager().install_requests();
-
-  std::vector<ExternalInstallOptions> expected_install_options_list;
-  expected_install_options_list.push_back(GetCustomAppIconInstallOptions());
-
-  EXPECT_EQ(1u, install_requests.size());
-  EXPECT_FALSE(install_requests[0].override_icon_url);
+  EXPECT_NE(GetPolicyInstalledWindowedApp(), nullptr);
+  const webapps::AppId& app_id = GetPolicyInstalledWindowedApp()->app_id();
+  auto icon_infos = app_registrar().GetAppIconInfos(app_id);
+  EXPECT_EQ(0u, icon_infos.size());
 }
 
 TEST_F(WebAppPolicyManagerTest, ForceInstallAppWithCustomAppName) {
@@ -816,14 +665,10 @@
 
   WaitForAppsToSynchronize();
 
-  const auto& install_requests =
-      externally_managed_app_manager().install_requests();
-
-  std::vector<ExternalInstallOptions> expected_install_options_list;
-  expected_install_options_list.push_back(
-      GetCustomAppNameInstallOptions(kDefaultCustomAppName));
-
-  EXPECT_EQ(install_requests, expected_install_options_list);
+  EXPECT_NE(GetPolicyInstalledWindowedApp(), nullptr);
+  EXPECT_EQ(kDefaultCustomAppName,
+            app_registrar().GetAppShortName(
+                GetPolicyInstalledWindowedApp()->app_id()));
 }
 
 TEST_F(WebAppPolicyManagerTest, ForceInstallAppWithCustomAppNameRefresh) {
@@ -837,6 +682,12 @@
                                    std::move(list));
   }
   WaitForAppsToSynchronize();
+
+  EXPECT_NE(GetPolicyInstalledWindowedApp(), nullptr);
+  EXPECT_EQ(kDefaultCustomAppName,
+            app_registrar().GetAppShortName(
+                GetPolicyInstalledWindowedApp()->app_id()));
+
   // Change custom name
   {
     base::Value::List list;
@@ -846,20 +697,6 @@
   }
   WaitForAppsToSynchronize();
 
-  const auto& install_requests =
-      externally_managed_app_manager().install_requests();
-
-  // App should have been installed twice, with a force-install the second time.
-  std::vector<ExternalInstallOptions> expected_install_options_list;
-  expected_install_options_list.push_back(
-      GetCustomAppNameInstallOptions(kDefaultCustomAppName));
-  auto options =
-      GetCustomAppNameInstallOptions(kPrefix + kDefaultCustomAppName);
-  options.force_reinstall = true;
-  expected_install_options_list.push_back(options);
-
-  EXPECT_EQ(install_requests, expected_install_options_list);
-
   base::flat_map<webapps::AppId, base::flat_set<GURL>> apps =
       app_registrar().GetExternallyInstalledApps(
           ExternalInstallSource::kExternalPolicy);
@@ -875,14 +712,7 @@
                                  std::move(first_list));
 
   WaitForAppsToSynchronize();
-
-  const auto& install_requests =
-      externally_managed_app_manager().install_requests();
-
-  std::vector<ExternalInstallOptions> expected_install_options_list;
-  expected_install_options_list.push_back(GetWindowedInstallOptions());
-
-  EXPECT_EQ(install_requests, expected_install_options_list);
+  EXPECT_NE(GetPolicyInstalledWindowedApp(), nullptr);
 
   base::Value::List second_list;
   second_list.Append(GetTabbedItem());
@@ -891,9 +721,7 @@
 
   WaitForAppsToSynchronize();
 
-  expected_install_options_list.push_back(GetTabbedInstallOptions());
-
-  EXPECT_EQ(install_requests, expected_install_options_list);
+  EXPECT_NE(GetPolicyInstalledTabbedApp(), nullptr);
 }
 
 TEST_F(WebAppPolicyManagerTest, UninstallAppInstalledInPreviousSession) {
@@ -914,15 +742,11 @@
 
   WaitForAppsToSynchronize();
 
-  // We should only try to install the app in the policy.
-  std::vector<ExternalInstallOptions> expected_install_options_list;
-  expected_install_options_list.push_back(GetWindowedInstallOptions());
-  EXPECT_EQ(externally_managed_app_manager().install_requests(),
-            expected_install_options_list);
-
-  // We should try to uninstall the app that is no longer in the policy.
-  EXPECT_EQ(std::vector<GURL>({GURL(kTabbedUrl)}),
-            externally_managed_app_manager().uninstall_requests());
+  // Verify only the app corresponding to `kWindowedUrl` is installed,
+  // everything else is removed.
+  EXPECT_NE(GetPolicyInstalledWindowedApp(), nullptr);
+  EXPECT_EQ(GetPolicyInstalledNoContainerApp(), nullptr);
+  EXPECT_EQ(GetPolicyInstalledTabbedApp(), nullptr);
 }
 
 // Tests that we correctly uninstall an app that we installed in the same
@@ -936,14 +760,8 @@
                                  std::move(first_list));
   WaitForAppsToSynchronize();
 
-  const auto& install_requests =
-      externally_managed_app_manager().install_requests();
-
-  std::vector<ExternalInstallOptions> expected_install_options_list;
-  expected_install_options_list.push_back(GetWindowedInstallOptions());
-  expected_install_options_list.push_back(GetTabbedInstallOptions());
-
-  EXPECT_EQ(install_requests, expected_install_options_list);
+  EXPECT_NE(GetPolicyInstalledWindowedApp(), nullptr);
+  EXPECT_NE(GetPolicyInstalledTabbedApp(), nullptr);
 
   // Push a new policy without the tabbed site.
   base::Value::List second_list;
@@ -952,14 +770,9 @@
                                  std::move(second_list));
   WaitForAppsToSynchronize();
 
-  // We'll try to install the app again but ExternallyManagedAppManager will
-  // handle not re-installing the app.
-  expected_install_options_list.push_back(GetWindowedInstallOptions());
-
-  EXPECT_EQ(install_requests, expected_install_options_list);
-
-  EXPECT_EQ(std::vector<GURL>({GURL(kTabbedUrl)}),
-            externally_managed_app_manager().uninstall_requests());
+  // GetPolicyInstalledTabbedApp() will be deleted.
+  EXPECT_NE(GetPolicyInstalledWindowedApp(), nullptr);
+  EXPECT_EQ(GetPolicyInstalledTabbedApp(), nullptr);
 }
 
 // Tests that we correctly reinstall a placeholder app.
@@ -970,13 +783,10 @@
                                  std::move(list));
 
   WaitForAppsToSynchronize();
-
-  std::vector<ExternalInstallOptions> expected_options_list;
-  expected_options_list.push_back(GetWindowedInstallOptions());
-
-  const auto& install_options_list =
-      externally_managed_app_manager().install_requests();
-  EXPECT_EQ(expected_options_list, install_options_list);
+  ASSERT_NE(GetPolicyInstalledWindowedApp(), nullptr);
+  const webapps::AppId& app_id = GetPolicyInstalledWindowedApp()->app_id();
+  EXPECT_FALSE(
+      app_registrar().IsPlaceholderApp(app_id, WebAppManagement::kPolicy));
 
   MakeInstalledAppPlaceholder(GURL(kWindowedUrl));
   base::test::TestFuture<const GURL&,
@@ -987,13 +797,9 @@
   EXPECT_EQ(future.Get<1>().code,
             webapps::InstallResultCode::kSuccessNewInstall);
 
-  auto reinstall_options = GetWindowedInstallOptions(
-      /*placeholder_resolution_behavior=*/PlaceholderResolutionBehavior::
-          kWaitForAppWindowsClosed);
-  reinstall_options.install_placeholder = false;
-  expected_options_list.push_back(std::move(reinstall_options));
-
-  EXPECT_EQ(expected_options_list, install_options_list);
+  EXPECT_NE(GetPolicyInstalledWindowedApp(), nullptr);
+  EXPECT_TRUE(
+      app_registrar().IsPlaceholderApp(app_id, WebAppManagement::kPolicy));
 }
 
 TEST_F(WebAppPolicyManagerTest, DoNotReinstallIfNotPlaceholder) {
@@ -1004,12 +810,10 @@
 
   WaitForAppsToSynchronize();
 
-  std::vector<ExternalInstallOptions> expected_options_list;
-  expected_options_list.push_back(GetWindowedInstallOptions());
-
-  const auto& install_options_list =
-      externally_managed_app_manager().install_requests();
-  EXPECT_EQ(expected_options_list, install_options_list);
+  ASSERT_NE(GetPolicyInstalledWindowedApp(), nullptr);
+  const webapps::AppId& app_id = GetPolicyInstalledWindowedApp()->app_id();
+  EXPECT_FALSE(
+      app_registrar().IsPlaceholderApp(app_id, WebAppManagement::kPolicy));
 
   // By default, the app being installed is not a placeholder app.
   base::test::TestFuture<const GURL&,
@@ -1020,9 +824,10 @@
   EXPECT_EQ(future.Get<1>().code,
             webapps::InstallResultCode::kFailedPlaceholderUninstall);
 
-  // No other options are added to list as the app is currently not
-  // installed as a placeholder app.
-  EXPECT_EQ(expected_options_list, install_options_list);
+  // App is still currently not installed as a placeholder app.
+  EXPECT_NE(GetPolicyInstalledWindowedApp(), nullptr);
+  EXPECT_FALSE(
+      app_registrar().IsPlaceholderApp(app_id, WebAppManagement::kPolicy));
 }
 
 // Tests that we correctly reinstall a placeholder app when the placeholder
@@ -1035,12 +840,11 @@
 
   WaitForAppsToSynchronize();
 
-  std::vector<ExternalInstallOptions> expected_options_list;
-  expected_options_list.push_back(GetFallbackAppNameInstallOptions());
-
-  const auto& install_options_list =
-      externally_managed_app_manager().install_requests();
-  EXPECT_EQ(expected_options_list, install_options_list);
+  ASSERT_NE(GetPolicyInstalledWindowedApp(), nullptr);
+  const webapps::AppId& app_id = GetPolicyInstalledWindowedApp()->app_id();
+  EXPECT_FALSE(
+      app_registrar().IsPlaceholderApp(app_id, WebAppManagement::kPolicy));
+  EXPECT_EQ(kDefaultFallbackAppName, app_registrar().GetAppShortName(app_id));
 
   MakeInstalledAppPlaceholder(GURL(kWindowedUrl));
   base::test::TestFuture<const GURL&,
@@ -1051,12 +855,9 @@
   EXPECT_EQ(future.Get<1>().code,
             webapps::InstallResultCode::kSuccessNewInstall);
 
-  auto reinstall_options = GetFallbackAppNameInstallOptions(
-      PlaceholderResolutionBehavior::kWaitForAppWindowsClosed);
-  reinstall_options.install_placeholder = false;
-  expected_options_list.push_back(std::move(reinstall_options));
-
-  EXPECT_EQ(expected_options_list, install_options_list);
+  EXPECT_TRUE(
+      app_registrar().IsPlaceholderApp(app_id, WebAppManagement::kPolicy));
+  EXPECT_EQ(kDefaultFallbackAppName, app_registrar().GetAppShortName(app_id));
 }
 
 TEST_F(WebAppPolicyManagerTest, TryToInexistentPlaceholderApp) {
@@ -1067,12 +868,9 @@
 
   WaitForAppsToSynchronize();
 
-  std::vector<ExternalInstallOptions> expected_options_list;
-  expected_options_list.push_back(GetWindowedInstallOptions());
-
-  const auto& install_options_list =
-      externally_managed_app_manager().install_requests();
-  EXPECT_EQ(expected_options_list, install_options_list);
+  ASSERT_NE(GetPolicyInstalledWindowedApp(), nullptr);
+  EXPECT_FALSE(app_registrar().IsPlaceholderApp(
+      GetPolicyInstalledWindowedApp()->app_id(), WebAppManagement::kPolicy));
 
   base::test::TestFuture<const GURL&,
                          ExternallyManagedAppManager::InstallResult>
@@ -1083,7 +881,7 @@
   EXPECT_EQ(future.Get<1>().code,
             webapps::InstallResultCode::kFailedPlaceholderUninstall);
 
-  EXPECT_EQ(expected_options_list, install_options_list);
+  ASSERT_EQ(GetPolicyInstalledTabbedApp(), nullptr);
 }
 
 TEST_F(WebAppPolicyManagerTest, SayRefreshTwoTimesQuickly) {
@@ -1106,18 +904,7 @@
   WaitForAppsToSynchronize();
   WaitForAppsToSynchronize();
 
-  // Both apps should have been installed.
-  std::vector<ExternalInstallOptions> expected_options_list;
-  expected_options_list.push_back(GetWindowedInstallOptions());
-  expected_options_list.push_back(GetTabbedInstallOptions());
-
-  const auto& install_options_list =
-      externally_managed_app_manager().install_requests();
-  EXPECT_EQ(expected_options_list, install_options_list);
-  EXPECT_EQ(std::vector<GURL>({GURL(kWindowedUrl)}),
-            externally_managed_app_manager().uninstall_requests());
-
-  // There should be exactly 1 app remaining.
+  // There should be exactly 1 app remaining at the end for the `tabbed` item.
   base::flat_map<webapps::AppId, base::flat_set<GURL>> apps =
       app_registrar().GetExternallyInstalledApps(
           ExternalInstallSource::kExternalPolicy);
@@ -1178,9 +965,8 @@
 
   WaitForAppsToSynchronize();
 
-  const auto& install_requests =
-      externally_managed_app_manager().install_requests();
-  EXPECT_EQ(0u, install_requests.size());
+  // No apps are installed.
+  ASSERT_TRUE(app_registrar().is_empty());
 }
 
 #if BUILDFLAG(IS_CHROMEOS)
@@ -1287,14 +1073,8 @@
                                  std::move(list));
   WaitForAppsToSynchronize();
 
-  const auto& install_requests =
-      externally_managed_app_manager().install_requests();
-
-  std::vector<ExternalInstallOptions> expected_install_options_list;
-  expected_install_options_list.push_back(GetWindowedInstallOptions());
-  expected_install_options_list.push_back(GetTabbedInstallOptions());
-
-  EXPECT_EQ(install_requests, expected_install_options_list);
+  EXPECT_NE(GetPolicyInstalledWindowedApp(), nullptr);
+  EXPECT_NE(GetPolicyInstalledTabbedApp(), nullptr);
 
   EXPECT_EQ(GetUrlRunOnOsLoginPolicy(kWindowedUrl),
             RunOnOsLoginPolicy::kAllowed);
@@ -1347,18 +1127,11 @@
         base::Value::List().Append(GetWindowedItem()).Append(GetTabbedItem()));
     loop.Run();
   }
-  // WaitForAppsToSynchronize();
 
   provider()->command_manager().AwaitAllCommandsCompleteForTesting();
 
-  const auto& install_requests =
-      externally_managed_app_manager().install_requests();
-
-  std::vector<ExternalInstallOptions> expected_install_options_list;
-  expected_install_options_list.push_back(GetWindowedInstallOptions());
-  expected_install_options_list.push_back(GetTabbedInstallOptions());
-
-  EXPECT_EQ(install_requests, expected_install_options_list);
+  EXPECT_NE(GetPolicyInstalledWindowedApp(), nullptr);
+  EXPECT_NE(GetPolicyInstalledTabbedApp(), nullptr);
   EXPECT_EQ(2, mock_observer.GetOnWebAppSettingsPolicyChangedCalledCount());
   app_registrar().RemoveObserver(&mock_observer);
 }
diff --git a/chrome/browser/web_applications/test/fake_externally_managed_app_manager.cc b/chrome/browser/web_applications/test/fake_externally_managed_app_manager.cc
index 9c34d4c3..6e92550 100644
--- a/chrome/browser/web_applications/test/fake_externally_managed_app_manager.cc
+++ b/chrome/browser/web_applications/test/fake_externally_managed_app_manager.cc
@@ -56,7 +56,6 @@
     std::vector<GURL> uninstall_urls,
     ExternalInstallSource install_source,
     const UninstallCallback& callback) {
-  std::ranges::copy(uninstall_urls, std::back_inserter(uninstall_requests_));
   if (handle_uninstall_request_callback_) {
     for (auto& app_url : uninstall_urls) {
       base::SequencedTaskRunner::GetCurrentDefault()
diff --git a/chrome/browser/web_applications/test/fake_externally_managed_app_manager.h b/chrome/browser/web_applications/test/fake_externally_managed_app_manager.h
index 075e8a8..dbd654eb 100644
--- a/chrome/browser/web_applications/test/fake_externally_managed_app_manager.h
+++ b/chrome/browser/web_applications/test/fake_externally_managed_app_manager.h
@@ -30,9 +30,6 @@
   const std::vector<ExternalInstallOptions>& install_requests() const {
     return install_requests_;
   }
-  const std::vector<GURL>& uninstall_requests() const {
-    return uninstall_requests_;
-  }
 
   void SetDropRequestsForTesting(bool drop_requests_for_testing) {
     drop_requests_for_testing_ = drop_requests_for_testing;
@@ -58,7 +55,6 @@
 
  private:
   std::vector<ExternalInstallOptions> install_requests_;
-  std::vector<GURL> uninstall_requests_;
   bool drop_requests_for_testing_ = false;
   HandleInstallRequestCallback handle_install_request_callback_;
   HandleUninstallRequestCallback handle_uninstall_request_callback_;
diff --git a/chrome/build/android-arm64.pgo.txt b/chrome/build/android-arm64.pgo.txt
index fa08221..7c965598 100644
--- a/chrome/build/android-arm64.pgo.txt
+++ b/chrome/build/android-arm64.pgo.txt
@@ -1 +1 @@
-chrome-android64-main-1743612696-a6233e2c17a9191a0908cca5b88020b34767c1fa-03db902005f7450fd40b6935eb5034e6291926d3.profdata
+chrome-android64-main-1743630746-edd340869be9643cbbcd15b49db23717448ca94e-262b719445890ba9222827f07a1815dd4559d4f3.profdata
diff --git a/chrome/build/mac-arm.pgo.txt b/chrome/build/mac-arm.pgo.txt
index 2b546f4..1826d90 100644
--- a/chrome/build/mac-arm.pgo.txt
+++ b/chrome/build/mac-arm.pgo.txt
@@ -1 +1 @@
-chrome-mac-arm-main-1743616772-33794e3c7e056c516f71432241e1fbf3c1737f03-352a1f4e325c40d92576d3c712839d7d4b68364a.profdata
+chrome-mac-arm-main-1743631082-84fe0935cb4e1078ded9f1f7c64e7f7ac726111e-512affd6b7a308ed019ba89dcdac95f92dd57f00.profdata
diff --git a/chrome/build/mac.pgo.txt b/chrome/build/mac.pgo.txt
index 5664b45..8099781 100644
--- a/chrome/build/mac.pgo.txt
+++ b/chrome/build/mac.pgo.txt
@@ -1 +1 @@
-chrome-mac-main-1743595152-026471aea975f940172aedde0a9c063517ec2c78-02a1e3553feb7b0b624e4a65be2e64d3b2df651e.profdata
+chrome-mac-main-1743616772-1503d4a6683266afb5b58ed4ae9fa1927943e323-352a1f4e325c40d92576d3c712839d7d4b68364a.profdata
diff --git a/chrome/build/win-arm64.pgo.txt b/chrome/build/win-arm64.pgo.txt
index a956511..71a9612 100644
--- a/chrome/build/win-arm64.pgo.txt
+++ b/chrome/build/win-arm64.pgo.txt
@@ -1 +1 @@
-chrome-win-arm64-main-1743595152-f2e151e1f2e71e803704a33f7a10ab5679af8349-02a1e3553feb7b0b624e4a65be2e64d3b2df651e.profdata
+chrome-win-arm64-main-1743616772-fb01b97b315bc9c55aa01c36b614d92412b953c0-352a1f4e325c40d92576d3c712839d7d4b68364a.profdata
diff --git a/chrome/build/win32.pgo.txt b/chrome/build/win32.pgo.txt
index 13b23274d..96d08818 100644
--- a/chrome/build/win32.pgo.txt
+++ b/chrome/build/win32.pgo.txt
@@ -1 +1 @@
-chrome-win32-main-1743595152-52c531b88da1a8df21a6e2078f89905dfc910382-02a1e3553feb7b0b624e4a65be2e64d3b2df651e.profdata
+chrome-win32-main-1743605912-6c264f91807c8b76131f7a79820a84de5e6a019d-f1a583c6e39d26bbb02710ce3ccfd253cf812415.profdata
diff --git a/chrome/common/extensions/manifest_handlers/background_scripts_manifest_unittest.cc b/chrome/common/extensions/manifest_handlers/background_scripts_manifest_unittest.cc
new file mode 100644
index 0000000..661b979
--- /dev/null
+++ b/chrome/common/extensions/manifest_handlers/background_scripts_manifest_unittest.cc
@@ -0,0 +1,28 @@
+// Copyright 2025 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/memory/scoped_refptr.h"
+#include "base/strings/string_number_conversions.h"
+#include "chrome/common/extensions/manifest_tests/chrome_manifest_test.h"
+#include "extensions/common/error_utils.h"
+#include "extensions/common/extension.h"
+#include "extensions/common/manifest_constants.h"
+#include "extensions/common/manifest_handlers/background_info.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace extensions {
+
+namespace errors = manifest_errors;
+
+using BackgroundScriptsManifestTest = ChromeManifestTest;
+
+TEST_F(BackgroundScriptsManifestTest, FailLoadingNonJsScripts) {
+  scoped_refptr<Extension> extension = LoadAndExpectWarning(
+      "background_bad_mime_type_manifest.json",
+      ErrorUtils::FormatErrorMessage(errors::kInvalidBackgroundScriptMimeType,
+                                     base::NumberToString(0)));
+  EXPECT_FALSE(BackgroundInfo::HasPersistentBackgroundPage(extension.get()));
+}
+
+}  // namespace extensions
diff --git a/chrome/installer/mac/BUILD.gn b/chrome/installer/mac/BUILD.gn
index f78a5985..3deac9b 100644
--- a/chrome/installer/mac/BUILD.gn
+++ b/chrome/installer/mac/BUILD.gn
@@ -120,13 +120,9 @@
       "//chrome/app/theme/google_chrome/mac/document_canary.icns",
       "//chrome/app/theme/google_chrome/mac/document_dev.icns",
       "internal/Google_Chrome.765bb3620a0f7a33500da39b20122b1cec41140f.provisionprofile",
-      "internal/Google_Chrome.e809e0e54407d4f3914b7883b7ed28650316ac2e.provisionprofile",
       "internal/Google_Chrome_Beta.765bb3620a0f7a33500da39b20122b1cec41140f.provisionprofile",
-      "internal/Google_Chrome_Beta.e809e0e54407d4f3914b7883b7ed28650316ac2e.provisionprofile",
       "internal/Google_Chrome_Canary.765bb3620a0f7a33500da39b20122b1cec41140f.provisionprofile",
-      "internal/Google_Chrome_Canary.e809e0e54407d4f3914b7883b7ed28650316ac2e.provisionprofile",
       "internal/Google_Chrome_Dev.765bb3620a0f7a33500da39b20122b1cec41140f.provisionprofile",
-      "internal/Google_Chrome_Dev.e809e0e54407d4f3914b7883b7ed28650316ac2e.provisionprofile",
       "internal/chrome_beta_dmg_dsstore",
       "internal/chrome_beta_dmg_icon.icns",
       "internal/chrome_canary_dmg_dsstore",
diff --git a/chrome/installer/mac/signing/driver.py b/chrome/installer/mac/signing/driver.py
index 7ad8c88..2fd03817 100644
--- a/chrome/installer/mac/signing/driver.py
+++ b/chrome/installer/mac/signing/driver.py
@@ -42,6 +42,10 @@
                 return ''
 
             @property
+            def provisioning_profile_basename(self):
+                return None
+
+            @property
             def run_spctl_assess(self):
                 # Self-signed or ad-hoc signed signing identities won't pass
                 # spctl assessment so don't do it.
diff --git a/chrome/installer/mac/signing/model.py b/chrome/installer/mac/signing/model.py
index cdae0e7f..b65876c 100644
--- a/chrome/installer/mac/signing/model.py
+++ b/chrome/installer/mac/signing/model.py
@@ -14,35 +14,6 @@
 from signing import commands
 
 
-def _get_unexpired_identities():
-    """Returns a set of the SHA-1 hashes of unexpired code signing identities
-
-    Raises:
-        ValueError: If no unexpired code signing identities are found.
-    """
-    # Avoid -v because it filters out self-signed certificates.
-    command = ['security', 'find-identity', '-p', 'codesigning']
-    output = commands.run_command_output(command)
-
-    matches = re.finditer(
-        rb'\d+\) (?P<id>[0-9A-Fa-f]{40}) "[^"]+"( \((?P<error>[^\)]+)\))?',
-        output,
-        flags=re.MULTILINE)
-
-    identities = set()
-    for match in matches:
-        # Exclude expired certificates. Other errors are ignored.
-        if match.group('error') == b'CSSMERR_TP_CERT_EXPIRED':
-            continue
-
-        identities.add(match.group('id'))
-
-    if not identities:
-        raise ValueError('No code signing identities found')
-
-    return identities
-
-
 def _get_identity_hash(identity):
     """Returns a string of the SHA-1 hash of a specified keychain identity.
 
@@ -58,21 +29,15 @@
     if len(identity) == 40 and all(ch in string.hexdigits for ch in identity):
         return identity.lower()
 
-    unexpired_identities = _get_unexpired_identities()
-
     command = ['security', 'find-certificate', '-a', '-c', identity, '-Z']
     output = commands.run_command_output(command)
 
-    hashes = re.findall(
+    hash_match = re.search(
         b'^SHA-1 hash: ([0-9A-Fa-f]{40})$', output, flags=re.MULTILINE)
-    if not hashes:
+    if not hash_match:
         raise ValueError('Cannot find identity', identity)
 
-    valid_hashes = [h for h in hashes if h in unexpired_identities]
-    if not valid_hashes:
-        raise ValueError('Identity found, but expired', identity)
-
-    return valid_hashes[0].decode('utf-8').lower()
+    return hash_match.group(1).decode('utf-8').lower()
 
 
 class CodeSignedProduct(object):
diff --git a/chrome/release_scripts b/chrome/release_scripts
index dd1c8ec..4f61c19 160000
--- a/chrome/release_scripts
+++ b/chrome/release_scripts
@@ -1 +1 @@
-Subproject commit dd1c8ec0edb90fc7d5860a50b690f3bd8026ce14
+Subproject commit 4f61c195e5c4e456130a2de1ad5362a125ea7a61
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn
index c0da1bb0..a4f2f6d 100644
--- a/chrome/test/BUILD.gn
+++ b/chrome/test/BUILD.gn
@@ -2432,6 +2432,7 @@
       "//components/plus_addresses:test_support",
       "//components/plus_addresses/resources/strings",
       "//components/policy:chrome_settings_proto_generated_compile",
+      "//components/policy/content",
       "//components/policy/core/browser:pref_mapping_test_support",
       "//components/policy/core/browser:test_support",
       "//components/policy/test_support",
@@ -2955,6 +2956,7 @@
       "../browser/enterprise/browser_management/management_service_browsertest.cc",
       "../browser/enterprise/connectors/reporting/crash_reporting_context_browsertest.cc",
       "../browser/enterprise/incognito/incognito_navigation_throttle_browsertest.cc",
+      "../browser/enterprise/signals/profile_signals_collector_browsertest.cc",
       "../browser/enterprise/util/managed_browser_utils_browsertest.cc",
       "../browser/enterprise/watermark/watermark_browsertest.cc",
       "../browser/error_reporting/crash_client_upload_info_browsertest.cc",
@@ -8810,6 +8812,7 @@
       "../browser/extensions/installed_loader_unittest.cc",
       "../common/extensions/chrome_manifest_url_handlers_unittest.cc",
       "../common/extensions/manifest_handlers/automation_unittest.cc",
+      "../common/extensions/manifest_handlers/background_scripts_manifest_unittest.cc",
       "../common/extensions/manifest_handlers/content_scripts_manifest_unittest.cc",
       "../common/extensions/manifest_handlers/exclude_matches_manifest_unittest.cc",
       "../common/extensions/manifest_handlers/natively_connectable_handler_unittest.cc",
diff --git a/chrome/test/android/javatests/src/org/chromium/chrome/test/transit/hub/HubBaseStation.java b/chrome/test/android/javatests/src/org/chromium/chrome/test/transit/hub/HubBaseStation.java
index 80d2da5..835038dd 100644
--- a/chrome/test/android/javatests/src/org/chromium/chrome/test/transit/hub/HubBaseStation.java
+++ b/chrome/test/android/javatests/src/org/chromium/chrome/test/transit/hub/HubBaseStation.java
@@ -15,7 +15,6 @@
 import static org.hamcrest.CoreMatchers.allOf;
 import static org.hamcrest.CoreMatchers.containsString;
 
-import static org.chromium.base.test.transit.Condition.whether;
 import static org.chromium.base.test.transit.ViewSpec.viewSpec;
 
 import androidx.annotation.Nullable;
@@ -24,19 +23,16 @@
 
 import org.chromium.base.supplier.Supplier;
 import org.chromium.base.test.transit.Condition;
-import org.chromium.base.test.transit.ConditionStatus;
 import org.chromium.base.test.transit.Elements;
 import org.chromium.base.test.transit.Station;
 import org.chromium.base.test.transit.Transition;
 import org.chromium.base.test.transit.TravelException;
-import org.chromium.base.test.transit.UiThreadCondition;
 import org.chromium.base.test.transit.ViewElement;
 import org.chromium.base.test.transit.ViewSpec;
 import org.chromium.chrome.browser.ChromeTabbedActivity;
 import org.chromium.chrome.browser.flags.ChromeFeatureList;
 import org.chromium.chrome.browser.hub.PaneId;
 import org.chromium.chrome.browser.hub.R;
-import org.chromium.chrome.browser.layouts.LayoutManager;
 import org.chromium.chrome.browser.layouts.LayoutType;
 import org.chromium.chrome.browser.tabmodel.TabModelSelector;
 import org.chromium.chrome.test.transit.layouts.LayoutTypeVisibleCondition;
@@ -98,7 +94,6 @@
             }
         }
 
-        elements.declareEnterCondition(new HubLayoutNotInTransition());
         elements.declareEnterCondition(
                 new LayoutTypeVisibleCondition(mActivityElement, LayoutType.TAB_SWITCHER));
     }
@@ -170,27 +165,4 @@
                                 withContentDescription(containsString(contentDescription))))
                 .perform(click());
     }
-
-    private class HubLayoutNotInTransition extends UiThreadCondition {
-        private HubLayoutNotInTransition() {
-            dependOnSupplier(mActivityElement, "ChromeTabbedActivity");
-        }
-
-        @Override
-        protected ConditionStatus checkWithSuppliers() {
-            LayoutManager layoutManager = mActivityElement.get().getLayoutManager();
-            boolean startingToShow = layoutManager.isLayoutStartingToShow(LayoutType.TAB_SWITCHER);
-            boolean startingToHide = layoutManager.isLayoutStartingToHide(LayoutType.TAB_SWITCHER);
-            return whether(
-                    !startingToShow && !startingToHide,
-                    "startingToShow=%b, startingToHide=%b",
-                    startingToShow,
-                    startingToHide);
-        }
-
-        @Override
-        public String buildDescription() {
-            return "LayoutManager is not in transition to or from TAB_SWITCHER (Hub)";
-        }
-    }
 }
diff --git a/chrome/test/android/javatests/src/org/chromium/chrome/test/transit/layouts/LayoutTypeVisibleCondition.java b/chrome/test/android/javatests/src/org/chromium/chrome/test/transit/layouts/LayoutTypeVisibleCondition.java
index ede5bee..b0b9e22 100644
--- a/chrome/test/android/javatests/src/org/chromium/chrome/test/transit/layouts/LayoutTypeVisibleCondition.java
+++ b/chrome/test/android/javatests/src/org/chromium/chrome/test/transit/layouts/LayoutTypeVisibleCondition.java
@@ -8,6 +8,7 @@
 import org.chromium.base.test.transit.Condition;
 import org.chromium.base.test.transit.ConditionStatus;
 import org.chromium.chrome.browser.ChromeTabbedActivity;
+import org.chromium.chrome.browser.compositor.layouts.Layout;
 import org.chromium.chrome.browser.layouts.LayoutType;
 
 /** Check that the current LayoutType is the expected one. */
@@ -24,12 +25,30 @@
 
     @Override
     protected ConditionStatus checkWithSuppliers() throws Exception {
-        // TODO(crbug.com/394600771): Also check that the layout is not in transition, like
-        // {@link HubLayoutNotInTransition} does.
-        @LayoutType
-        int layoutType = mActivitySupplier.get().getLayoutManager().getActiveLayoutType();
-        return whetherEquals(
-                mExpectedLayoutType, layoutType, LayoutTypeVisibleCondition::getLayoutTypeName);
+        Layout activeLayout = mActivitySupplier.get().getLayoutManager().getActiveLayout();
+        if (activeLayout == null) {
+            return whetherEquals(
+                    mExpectedLayoutType,
+                    LayoutType.NONE,
+                    LayoutTypeVisibleCondition::getLayoutTypeName);
+        }
+
+        @LayoutType int layoutType = activeLayout.getLayoutType();
+        boolean isStartingToHide = activeLayout.isStartingToHide();
+        boolean isStartingToShow = activeLayout.isStartingToShow();
+        String expectedDescription = getLayoutTypeName(mExpectedLayoutType);
+        String actualDescription = getLayoutTypeName(layoutType);
+        if (isStartingToHide) {
+            actualDescription += " (starting to hide)";
+        }
+        if (isStartingToShow) {
+            actualDescription += " (starting to show)";
+        }
+        return whether(
+                mExpectedLayoutType == layoutType && !isStartingToHide && !isStartingToShow,
+                "Expected: %s; Actual: %s",
+                expectedDescription,
+                actualDescription);
     }
 
     @Override
diff --git a/chrome/test/android/javatests/src/org/chromium/chrome/test/transit/page/PageStation.java b/chrome/test/android/javatests/src/org/chromium/chrome/test/transit/page/PageStation.java
index 920535d8..6e655c3 100644
--- a/chrome/test/android/javatests/src/org/chromium/chrome/test/transit/page/PageStation.java
+++ b/chrome/test/android/javatests/src/org/chromium/chrome/test/transit/page/PageStation.java
@@ -274,9 +274,6 @@
             elements.declareEnterCondition(
                     new PageUrlContainsCondition(mExpectedUrlSubstring, mPageLoadedSupplier));
         }
-
-        elements.declareEnterCondition(
-                new LayoutTypeVisibleCondition(mActivityElement, LayoutType.BROWSING));
     }
 
     public boolean isIncognito() {
diff --git a/chrome/test/data/extensions/manifest_tests/background_bad_mime_type_manifest.json b/chrome/test/data/extensions/manifest_tests/background_bad_mime_type_manifest.json
new file mode 100644
index 0000000..7ca36162
--- /dev/null
+++ b/chrome/test/data/extensions/manifest_tests/background_bad_mime_type_manifest.json
@@ -0,0 +1,8 @@
+{
+  "name": "background script with bad mime type",
+  "manifest_version": 2,
+  "version": "1",
+  "background": {
+    "scripts": ["background_bad_mime_type_script.json"]
+  }
+}
diff --git a/chrome/test/data/extensions/manifest_tests/background_bad_mime_type_script.json b/chrome/test/data/extensions/manifest_tests/background_bad_mime_type_script.json
new file mode 100644
index 0000000..0670b51a
--- /dev/null
+++ b/chrome/test/data/extensions/manifest_tests/background_bad_mime_type_script.json
@@ -0,0 +1 @@
+console.log("This is not a valid background script file type");
diff --git a/chrome/test/data/pdf/BUILD.gn b/chrome/test/data/pdf/BUILD.gn
index 44deba3..1b82097 100644
--- a/chrome/test/data/pdf/BUILD.gn
+++ b/chrome/test/data/pdf/BUILD.gn
@@ -75,6 +75,7 @@
       "ink2_size_selector_test.ts",
       "ink2_test.ts",
       "ink2_text_side_panel_test.ts",
+      "ink2_text_styles_selector_test.ts",
       "ink2_viewer_toolbar_test.ts",
       "selectable_icon_button_test.ts",
     ]
diff --git a/chrome/test/data/pdf/ink2_text_side_panel_test.ts b/chrome/test/data/pdf/ink2_text_side_panel_test.ts
index d114f9f..ddcdaeb 100644
--- a/chrome/test/data/pdf/ink2_text_side_panel_test.ts
+++ b/chrome/test/data/pdf/ink2_text_side_panel_test.ts
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-import {AnnotationMode, hexToColor, Ink2Manager, TEXT_COLORS, TextAlignment, TextStyle, UserAction} from 'chrome-extension://mhjfbmdgcfjbbpaeojofohoefgiehjai/pdf_viewer_wrapper.js';
-import type {Color, CrIconButtonElement, SelectableIconButtonElement} from 'chrome-extension://mhjfbmdgcfjbbpaeojofohoefgiehjai/pdf_viewer_wrapper.js';
+import {AnnotationMode, hexToColor, Ink2Manager, TEXT_COLORS, TextAlignment, UserAction} from 'chrome-extension://mhjfbmdgcfjbbpaeojofohoefgiehjai/pdf_viewer_wrapper.js';
+import type {Color, SelectableIconButtonElement} from 'chrome-extension://mhjfbmdgcfjbbpaeojofohoefgiehjai/pdf_viewer_wrapper.js';
 import {assert} from 'chrome://resources/js/assert.js';
 import {loadTimeData} from 'chrome://resources/js/load_time_data.js';
 import {eventToPromise, isVisible, microtasksFinished} from 'chrome://webui-test/test_util.js';
@@ -88,45 +88,13 @@
     chrome.test.succeed();
   },
 
-  // Test that the styles can be toggled.
-  async function testSelectStyles() {
+  // Test that the side panel has a style select element.
+  function testHasStyleSelect() {
     chrome.test.assertEq(AnnotationMode.TEXT, viewer.$.toolbar.annotationMode);
     const sidePanel = viewer.shadowRoot.querySelector('viewer-text-side-panel');
     assert(sidePanel);
-
-    const initialStyles = Ink2Manager.getInstance().getCurrentText().styles;
-
-    // Check that the button toggles its style and aria-pressed state and
-    // triggers a text-changed event when clicked.
-    async function testButton(
-        button: CrIconButtonElement, style: TextStyle, icon: string) {
-      chrome.test.assertEq(icon, button.ironIcon);
-      const initialValue = initialStyles[style];
-      chrome.test.assertEq(initialValue, button.classList.contains('active'));
-      chrome.test.assertEq(
-          initialValue.toString(), button.getAttribute('aria-pressed'));
-
-      const whenChanged =
-          eventToPromise('text-changed', Ink2Manager.getInstance());
-      button.click();
-      const changedEvent = await whenChanged;
-      chrome.test.assertEq(!initialValue, changedEvent.detail.styles[style]);
-      await microtasksFinished();
-      chrome.test.assertEq(!initialValue, button.classList.contains('active'));
-      chrome.test.assertEq(
-          (!initialValue).toString(), button.getAttribute('aria-pressed'));
-    }
-
-    // For each button, check that it can be toggled and change the styles.
-    const buttons = sidePanel.shadowRoot.querySelectorAll('cr-icon-button');
-    chrome.test.assertEq(4, buttons.length);
-    await testButton(buttons[0]!, TextStyle.BOLD, 'pdf:text-format-bold');
-    await testButton(buttons[1]!, TextStyle.ITALIC, 'pdf:text-format-italic');
-    await testButton(
-        buttons[2]!, TextStyle.UNDERLINE, 'pdf:text-format-underline');
-    await testButton(
-        buttons[3]!, TextStyle.STRIKETHROUGH, 'pdf:text-format-strikethrough');
-
+    chrome.test.assertTrue(
+        !!sidePanel.shadowRoot.querySelector('text-styles-selector'));
     chrome.test.succeed();
   },
 
diff --git a/chrome/test/data/pdf/ink2_text_styles_selector_test.ts b/chrome/test/data/pdf/ink2_text_styles_selector_test.ts
new file mode 100644
index 0000000..caa9c91
--- /dev/null
+++ b/chrome/test/data/pdf/ink2_text_styles_selector_test.ts
@@ -0,0 +1,54 @@
+// Copyright 2025 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import {Ink2Manager, TextStyle} from 'chrome-extension://mhjfbmdgcfjbbpaeojofohoefgiehjai/pdf_viewer_wrapper.js';
+import type {CrIconButtonElement} from 'chrome-extension://mhjfbmdgcfjbbpaeojofohoefgiehjai/pdf_viewer_wrapper.js';
+import {eventToPromise, microtasksFinished} from 'chrome://webui-test/test_util.js';
+
+import {setupTestMockPluginForInk} from './test_util.js';
+
+setupTestMockPluginForInk();
+const manager = Ink2Manager.getInstance();
+const styleSelector = document.createElement('text-styles-selector');
+document.body.appendChild(styleSelector);
+
+chrome.test.runTests([
+  // Test that the styles can be toggled.
+  async function testSelectStyles() {
+    const initialStyles = manager.getCurrentText().styles;
+
+    // Check that the button toggles its style and aria-pressed state and
+    // triggers a text-changed event when clicked.
+    async function testButton(
+        button: CrIconButtonElement, style: TextStyle, icon: string) {
+      chrome.test.assertEq(icon, button.ironIcon);
+      const initialValue = initialStyles[style];
+      chrome.test.assertEq(initialValue, button.classList.contains('active'));
+      chrome.test.assertEq(
+          initialValue.toString(), button.getAttribute('aria-pressed'));
+
+      const whenChanged = eventToPromise('text-changed', manager);
+      button.click();
+      const changedEvent = await whenChanged;
+      chrome.test.assertEq(!initialValue, changedEvent.detail.styles[style]);
+      await microtasksFinished();
+      chrome.test.assertEq(!initialValue, button.classList.contains('active'));
+      chrome.test.assertEq(
+          (!initialValue).toString(), button.getAttribute('aria-pressed'));
+    }
+
+    // For each button, check that it can be toggled and confirm it is
+    // displaying the expected icon.
+    const buttons = styleSelector.shadowRoot.querySelectorAll('cr-icon-button');
+    chrome.test.assertEq(4, buttons.length);
+    await testButton(buttons[0]!, TextStyle.BOLD, 'pdf:text-format-bold');
+    await testButton(buttons[1]!, TextStyle.ITALIC, 'pdf:text-format-italic');
+    await testButton(
+        buttons[2]!, TextStyle.UNDERLINE, 'pdf:text-format-underline');
+    await testButton(
+        buttons[3]!, TextStyle.STRIKETHROUGH, 'pdf:text-format-strikethrough');
+
+    chrome.test.succeed();
+  },
+]);
diff --git a/chrome/test/data/webui/cr_elements/cr_lit_element_test.ts b/chrome/test/data/webui/cr_elements/cr_lit_element_test.ts
index 32ea30d..2ce804f 100644
--- a/chrome/test/data/webui/cr_elements/cr_lit_element_test.ts
+++ b/chrome/test/data/webui/cr_elements/cr_lit_element_test.ts
@@ -309,7 +309,13 @@
     assertThrows(function() {
       element.$.foo;
       assertNotReached('Previous statement should have thrown an exception');
-    }, 'CrLitElement CR-DUMMY-LIT $ dictionary accessed before element is connected at least once.');
+    }, 'CrLitElement CR-DUMMY-LIT accessed \'$.foo\' before connected at least once.');
+
+    assertThrows(function() {
+      element.id = 'dummyId';
+      element.$.foo;
+      assertNotReached('Previous statement should have thrown an exception');
+    }, 'CrLitElement CR-DUMMY-LIT#dummyId accessed \'$.foo\' before connected at least once.');
 
     assertDeepEquals([], element.lifecycleCallbacks);
   });
diff --git a/chrome/test/data/webui/settings/autofill_fake_data.ts b/chrome/test/data/webui/settings/autofill_fake_data.ts
index 1b6e5c2b8..9a2d70a 100644
--- a/chrome/test/data/webui/settings/autofill_fake_data.ts
+++ b/chrome/test/data/webui/settings/autofill_fake_data.ts
@@ -84,12 +84,15 @@
 /**
  * Creates a new random credit card entry for testing.
  */
-export function createCreditCardEntry():
-    chrome.autofillPrivate.CreditCardEntry {
+export function createCreditCardEntry(
+    isNewFopDisplay: boolean = true,
+    hasIdentifier: boolean = false): chrome.autofillPrivate.CreditCardEntry {
   const cards = ['Visa', 'Mastercard', 'Discover', 'Card'];
   const card = cards[Math.floor(Math.random() * cards.length)];
   const cardNumber = appendLuhnCheckBit(patternMaker('xxxxxxxxxxxxxxx', 10));
   const now = new Date();
+  const networkAndLastFour = card + ' ' +
+      '****' + cardNumber.substr(-4);
   return {
     guid: makeGuid(),
     name: 'Jane Doe',
@@ -101,9 +104,10 @@
     imageSrc: 'chrome://theme/IDR_AUTOFILL_CC_GENERIC',
     metadata: {
       isLocal: true,
-      summaryLabel: card + ' ' +
-          '****' + cardNumber.substr(-4),
-      summarySublabel: 'Jane Doe',
+      summaryLabel: hasIdentifier ? `My Credit Card` : networkAndLastFour,
+      summarySublabel: isNewFopDisplay ?
+          (hasIdentifier ? networkAndLastFour : '') :
+          'Jane Doe',
     },
   };
 }
diff --git a/chrome/test/data/webui/settings/payments_section_card_rows_test.ts b/chrome/test/data/webui/settings/payments_section_card_rows_test.ts
index a9b09ff..454cd90c 100644
--- a/chrome/test/data/webui/settings/payments_section_card_rows_test.ts
+++ b/chrome/test/data/webui/settings/payments_section_card_rows_test.ts
@@ -35,21 +35,33 @@
     loadTimeData.overrideValues({
       migrationEnabled: true,
       showIbansSettings: true,
+      enableNewFopDisplay: true,
     });
     metricsBrowserProxy = new TestMetricsBrowserProxy();
     MetricsBrowserProxyImpl.setInstance(metricsBrowserProxy);
   });
 
   test('verifyCreditCardFields', async function() {
+    loadTimeData.overrideValues({
+      enableNewFopDisplay: true,
+    });
     const creditCard = createCreditCardEntry();
     const section = await createPaymentsSection(
         [creditCard], /*ibans=*/[], /*payOverTimeIssuers=*/[],
         /*prefValues=*/ {});
     const rowShadowRoot = getCardRowShadowRoot(section.$.paymentsList);
+    assertTrue(isVisible(rowShadowRoot.querySelector<HTMLElement>('#label')));
+    assertTrue(isVisible(
+        rowShadowRoot.querySelector<HTMLElement>('#expirationLabel')));
     assertEquals(
         creditCard.metadata!.summaryLabel,
         rowShadowRoot.querySelector<HTMLElement>(
-                         '#summaryLabel')!.textContent!.trim());
+                         '#label')!.textContent!.trim());
+    assertEquals(
+        '· ' + parseInt(creditCard.expirationMonth!, 10) + '/' +
+            creditCard.expirationYear!.substring(2),
+        rowShadowRoot.querySelector<HTMLElement>(
+                         '#expirationLabel')!.textContent!.trim());
   });
 
   test('verifyCreditCardRowButtonIsDropdownWhenLocal', async function() {
@@ -368,27 +380,39 @@
     assertFalse(!!menu);
   });
 
-  test('verifyCreditCardSummarySublabelWithExpirationDate', async function() {
-    const creditCard = createCreditCardEntry();
+  test(
+      'verifyCreditCardSummarySublabelWithNetworkLastFourAndExpirationDate',
+      async function() {
+        loadTimeData.overrideValues({
+          enableNewFopDisplay: true,
+        });
+        const creditCard = createCreditCardEntry();
 
-    const section = await createPaymentsSection(
-        [creditCard], /*ibans=*/[], /*payOverTimeIssuers=*/[],
-        /*prefValues=*/ {});
+        const section = await createPaymentsSection(
+            [creditCard], /*ibans=*/[], /*payOverTimeIssuers=*/[],
+            /*prefValues=*/ {});
 
-    const creditCardList = section.$.paymentsList;
-    assertTrue(!!creditCardList);
-    assertEquals(1, getLocalAndServerCreditCardListItems().length);
-    assertFalse(getCardRowShadowRoot(section.$.paymentsList)
-                    .querySelector<HTMLElement>('#summarySublabel')!.hidden);
-    assertTrue(!!creditCard.expirationMonth);
-    assertTrue(!!creditCard.expirationYear);
-    assertEquals(
-        parseInt(creditCard.expirationMonth, 10) + '/' +
-            creditCard.expirationYear.substring(2),
-        getCardRowShadowRoot(section.$.paymentsList)
-            .querySelector<HTMLElement>(
-                '#summarySublabel')!.textContent!.trim());
-  });
+        const creditCardList = section.$.paymentsList;
+        assertTrue(!!creditCardList);
+        assertEquals(1, getLocalAndServerCreditCardListItems().length);
+        assertTrue(isVisible(getCardRowShadowRoot(section.$.paymentsList)
+                                 .querySelector<HTMLElement>('#label')));
+        assertEquals(
+            creditCard.metadata!.summaryLabel,
+            getCardRowShadowRoot(section.$.paymentsList)
+                .querySelector<HTMLElement>('#label')!.textContent!.trim());
+        assertTrue(
+            isVisible(getCardRowShadowRoot(section.$.paymentsList)
+                          .querySelector<HTMLElement>('#expirationLabel')));
+        assertTrue(!!creditCard.expirationMonth);
+        assertTrue(!!creditCard.expirationYear);
+        assertEquals(
+            '· ' + parseInt(creditCard.expirationMonth, 10) + '/' +
+                creditCard.expirationYear.substring(2),
+            getCardRowShadowRoot(section.$.paymentsList)
+                .querySelector<HTMLElement>(
+                    '#expirationLabel')!.textContent!.trim());
+      });
 
   test('verifyCreditCardSummarySublabelWhenSublabelIsValid', async function() {
     const creditCard = createCreditCardEntry();
@@ -409,6 +433,9 @@
   test(
       'verifyCreditCardSummarySublabelWhenVirtualCardAvailable',
       async function() {
+        loadTimeData.overrideValues({
+          enableNewFopDisplay: true,
+        });
         const creditCard = createCreditCardEntry();
         creditCard.metadata!.isLocal = false;
         creditCard.metadata!.isVirtualCardEnrollmentEligible = true;
@@ -420,17 +447,22 @@
         const creditCardList = section.$.paymentsList;
         assertTrue(!!creditCardList);
         assertEquals(1, getLocalAndServerCreditCardListItems().length);
-        assertFalse(
-            getCardRowShadowRoot(section.$.paymentsList)
-                .querySelector<HTMLElement>('#summarySublabel')!.hidden);
-        assertTrue(!!creditCard.expirationMonth);
-        assertTrue(!!creditCard.expirationYear);
+
+        assertTrue(isVisible(getCardRowShadowRoot(section.$.paymentsList)
+                                 .querySelector<HTMLElement>('#label')));
+        assertTrue(
+            isVisible(getCardRowShadowRoot(section.$.paymentsList)
+                          .querySelector<HTMLElement>('#expirationLabel')));
         assertEquals(
-            parseInt(creditCard.expirationMonth, 10) + '/' +
-                creditCard.expirationYear.substring(2),
+            creditCard.metadata!.summaryLabel,
+            getCardRowShadowRoot(section.$.paymentsList)
+                .querySelector<HTMLElement>('#label')!.textContent!.trim());
+        assertEquals(
+            '· ' + parseInt(creditCard.expirationMonth!, 10) + '/' +
+                creditCard.expirationYear!.substring(2),
             getCardRowShadowRoot(section.$.paymentsList)
                 .querySelector<HTMLElement>(
-                    '#summarySublabel')!.textContent!.trim());
+                    '#expirationLabel')!.textContent!.trim());
       });
 
   test(
@@ -637,6 +669,9 @@
                            benefitsAvailable,
                            productTermsUrlAvailable,
                          }) => {
+    loadTimeData.overrideValues({
+      enableNewFopDisplay: true,
+    });
     const benefitsStatus = benefitsAvailable ? 'Available' : 'NotAvailable';
     const termUrlStatus =
         productTermsUrlAvailable ? 'Available' : 'NotAvailable';
@@ -660,16 +695,17 @@
 
       assertTrue(!!paymentsList);
       assertEquals(1, paymentsList.length);
-      assertTrue(
-          isVisible(paymentsList[0]!.shadowRoot!.querySelector<HTMLElement>(
-              '#summarySublabel')));
+      if (benefitsAvailable && productTermsUrlAvailable) {
+        assertTrue(isVisible(
+            paymentsList[0]!.shadowRoot!.querySelector<HTMLElement>(
+                '#summarySublabel')));
+      }
 
       // Build the expected resulting sublabel based on which features are
       // enabled.
-      let benefitExpectedSublabel = serverCreditCard.expirationMonth + '/' +
-          serverCreditCard.expirationYear!.toString().substring(2);
+      let benefitExpectedSublabel = '';
       if (benefitsAvailable && productTermsUrlAvailable) {
-        benefitExpectedSublabel += ' | ' +
+        benefitExpectedSublabel +=
             loadTimeData.getString('benefitsTermsTagForCreditCardListEntry');
       }
 
@@ -685,8 +721,8 @@
         assertTrue(!!termsLink);
         assertEquals(serverCreditCard.productTermsUrl, termsLink.href);
       } else {
-        assertFalse(
-            isVisible(paymentsList[0]!.shadowRoot!.querySelector<HTMLElement>(
+        assertFalse(isVisible(
+            paymentsList[0]!.shadowRoot!.querySelector<HTMLElement>(
                 '#summaryTermsLink')));
       }
     });
@@ -698,6 +734,9 @@
                            benefitsAvailable,
                            productTermsUrlAvailable,
                          }) => {
+    loadTimeData.overrideValues({
+      enableNewFopDisplay: true,
+    });
     const benefitsStatus = benefitsAvailable ? 'Available' : 'NotAvailable';
     const termUrlStatus =
         productTermsUrlAvailable ? 'Available' : 'NotAvailable';
@@ -723,11 +762,20 @@
 
       assertTrue(!!paymentsList);
       assertEquals(1, paymentsList.length);
-      assertTrue(
-          isVisible(paymentsList[0]!.shadowRoot!.querySelector<HTMLElement>(
-              '#summarySublabel')));
-      let benefitExpectedSublabel = serverCreditCard.expirationMonth + '/' +
-          serverCreditCard.expirationYear!.toString().substring(2) + ' | ' +
+
+      assertTrue(isVisible(
+          paymentsList[0]!.shadowRoot!.querySelector<HTMLElement>('#label')));
+      assertTrue(isVisible(
+          paymentsList[0]!.shadowRoot!.querySelector<HTMLElement>(
+              '#expirationLabel')));
+      assertEquals(
+          '· ' + parseInt(serverCreditCard.expirationMonth!, 10) + '/' +
+              serverCreditCard.expirationYear!.substring(2),
+          paymentsList[0]!.shadowRoot!
+              .querySelector<HTMLElement>(
+                  '#expirationLabel')!.textContent!.trim());
+
+      let benefitExpectedSublabel =
           loadTimeData.getString('cvcTagForCreditCardListEntry');
       if (benefitsAvailable && productTermsUrlAvailable) {
         benefitExpectedSublabel += ' | ' +
@@ -745,8 +793,8 @@
         assertTrue(!!termsLink);
         assertEquals(serverCreditCard.productTermsUrl, termsLink.href);
       } else {
-        assertFalse(
-            isVisible(paymentsList[0]!.shadowRoot!.querySelector<HTMLElement>(
+        assertFalse(isVisible(
+            paymentsList[0]!.shadowRoot!.querySelector<HTMLElement>(
                 '#summaryTermsLink')));
       }
     });
@@ -821,8 +869,8 @@
     assertEquals(termsLink.ariaLabel, expectedAriaLabel);
   });
 
-  // Test to verify the cvc tag is visible when cvc is present on a
-  // server/local cards.
+  // Test to verify the CVC tag is visible when CVC is present on a
+  // server/local card.
   [true, false].forEach(cvcOnServerCard => {
     test(
         'verifyCvcTagPresentFor_' +
@@ -844,6 +892,181 @@
               /*payOverTimeIssuers=*/[],
               /*prefValues=*/ {});
 
+          const serverCardExpectedSublabel = '· ' +
+              serverCreditCard.expirationMonth + '/' +
+              serverCreditCard.expirationYear!.toString().substring(2);
+          const localCardExpectedSublabel = '· ' +
+              localCreditCard.expirationMonth + '/' +
+              localCreditCard.expirationYear!.toString().substring(2);
+          const paymentsList = getLocalAndServerCreditCardListItems();
+
+          assertTrue(isVisible(
+              paymentsList[0]!.shadowRoot!.querySelector<HTMLElement>(
+                  '#label')));
+          assertEquals(
+              serverCreditCard.metadata!.summaryLabel,
+              paymentsList[0]!.shadowRoot!.querySelector<HTMLElement>(
+                                              '#label')!.textContent!.trim());
+          assertTrue(isVisible(
+              paymentsList[1]!.shadowRoot!.querySelector<HTMLElement>(
+                  '#label')));
+          assertEquals(
+              localCreditCard.metadata!.summaryLabel,
+              paymentsList[1]!.shadowRoot!.querySelector<HTMLElement>(
+                                              '#label')!.textContent!.trim());
+          if (cvcOnServerCard) {
+            assertTrue(isVisible(
+                paymentsList[0]!.shadowRoot!.querySelector<HTMLElement>(
+                    '#expirationLabel')));
+            assertEquals(
+                serverCardExpectedSublabel,
+                paymentsList[0]!.shadowRoot!
+                    .querySelector<HTMLElement>(
+                        '#expirationLabel')!.textContent!.trim());
+            assertTrue(isVisible(
+                paymentsList[0]!.shadowRoot!.querySelector<HTMLElement>(
+                    '#summarySublabel')));
+          } else {
+            assertTrue(isVisible(
+                paymentsList[1]!.shadowRoot!.querySelector<HTMLElement>(
+                    '#expirationLabel')));
+            assertEquals(
+                localCardExpectedSublabel,
+                paymentsList[1]!.shadowRoot!
+                    .querySelector<HTMLElement>(
+                        '#expirationLabel')!.textContent!.trim());
+            assertTrue(isVisible(
+                paymentsList[1]!.shadowRoot!.querySelector<HTMLElement>(
+                    '#summarySublabel')));
+          }
+
+          let serverExpectedSublabel = '';
+          let localExpectedSublabel = '';
+          if (cvcOnServerCard) {
+            serverExpectedSublabel =
+                loadTimeData.getString('cvcTagForCreditCardListEntry');
+          } else {
+            localExpectedSublabel =
+                loadTimeData.getString('cvcTagForCreditCardListEntry');
+          }
+
+          assertTrue(!!paymentsList);
+          assertEquals(2, paymentsList.length);
+          assertEquals(
+              serverExpectedSublabel,
+              paymentsList[0]!.shadowRoot!
+                  .querySelector<HTMLElement>(
+                      '#summarySublabel')!.textContent!.trim());
+          assertEquals(
+              localExpectedSublabel,
+              paymentsList[1]!.shadowRoot!
+                  .querySelector<HTMLElement>(
+                      '#summarySublabel')!.textContent!.trim());
+        });
+  });
+
+  test('verifyCreditCardFields_newFopDisplayFlagOff', async function() {
+    loadTimeData.overrideValues({
+      enableNewFopDisplay: false,
+    });
+    const creditCard = createCreditCardEntry(/*isNewFopDisplay=*/ false,
+                                             /*hasIdentifier=*/ false);
+    const section = await createPaymentsSection(
+        [creditCard], /*ibans=*/[], /*payOverTimeIssuers=*/[],
+        /*prefValues=*/ {});
+    const rowShadowRoot = getCardRowShadowRoot(section.$.paymentsList);
+    assertEquals(
+        creditCard.metadata!.summaryLabel,
+        rowShadowRoot.querySelector<HTMLElement>(
+                         '#summaryLabel')!.textContent!.trim());
+  });
+
+  test(
+      'verifyCreditCardSummarySublabelWithExpirationDate_newFopDisplayOff',
+      async function() {
+        loadTimeData.overrideValues({
+          enableNewFopDisplay: false,
+        });
+        const creditCard = createCreditCardEntry(/*isNewFopDisplay=*/ false,
+                                                 /*hasIdentifier=*/ false);
+
+        const section = await createPaymentsSection(
+            [creditCard], /*ibans=*/[], /*payOverTimeIssuers=*/[],
+            /*prefValues=*/ {});
+
+        const creditCardList = section.$.paymentsList;
+        assertTrue(!!creditCardList);
+        assertEquals(1, getLocalAndServerCreditCardListItems().length);
+        assertFalse(
+            getCardRowShadowRoot(section.$.paymentsList)
+                .querySelector<HTMLElement>('#summarySublabel')!.hidden);
+        assertTrue(!!creditCard.expirationMonth);
+        assertTrue(!!creditCard.expirationYear);
+        assertEquals(
+            parseInt(creditCard.expirationMonth, 10) + '/' +
+                creditCard.expirationYear.substring(2),
+            getCardRowShadowRoot(section.$.paymentsList)
+                .querySelector<HTMLElement>(
+                    '#summarySublabel')!.textContent!.trim());
+      });
+
+  test(
+      'verifyCreditCardSummarySublabelWhenVirtualCardAvailable_newFopDisplayOff',
+      async function() {
+        loadTimeData.overrideValues({
+          enableNewFopDisplay: false,
+        });
+        const creditCard = createCreditCardEntry(/*isNewFopDisplay=*/ false,
+                                                 /*hasIdentifier=*/ false);
+        creditCard.metadata!.isLocal = false;
+        creditCard.metadata!.isVirtualCardEnrollmentEligible = true;
+        creditCard.metadata!.isVirtualCardEnrolled = false;
+        const section = await createPaymentsSection(
+            [creditCard], /*ibans=*/[], /*payOverTimeIssuers=*/[],
+            /*prefValues=*/ {});
+
+        const creditCardList = section.$.paymentsList;
+        assertTrue(!!creditCardList);
+        assertEquals(1, getLocalAndServerCreditCardListItems().length);
+        assertFalse(
+            getCardRowShadowRoot(section.$.paymentsList)
+                .querySelector<HTMLElement>('#summarySublabel')!.hidden);
+        assertTrue(!!creditCard.expirationMonth);
+        assertTrue(!!creditCard.expirationYear);
+        assertEquals(
+            parseInt(creditCard.expirationMonth, 10) + '/' +
+                creditCard.expirationYear.substring(2),
+            getCardRowShadowRoot(section.$.paymentsList)
+                .querySelector<HTMLElement>(
+                    '#summarySublabel')!.textContent!.trim());
+      });
+
+  // Test to verify the CVC tag is visible when CVC is present on a
+  // server/local card.
+  [true, false].forEach(cvcOnServerCard => {
+    test(
+        'verifyCvcTagPresentFor_NewFopDisplayOff_' +
+            (cvcOnServerCard ? 'ServerCard' : 'LocalCard'),
+        async function() {
+          loadTimeData.overrideValues({
+            cvcStorageAvailable: true,
+            enableNewFopDisplay: false,
+          });
+          const serverCreditCard =
+              createCreditCardEntry(/*isNewFopDisplay=*/ false,
+                                    /*hasIdentifier=*/ false);
+          serverCreditCard.metadata!.isLocal = false;
+          const localCreditCard = createCreditCardEntry();
+          if (cvcOnServerCard) {
+            serverCreditCard.cvc = '***';
+          } else {
+            localCreditCard.cvc = '***';
+          }
+          await createPaymentsSection(
+              [serverCreditCard, localCreditCard], /*ibans=*/[],
+              /*payOverTimeIssuers=*/[],
+              /*prefValues=*/ {});
+
           let serverCardExpectedSublabel = serverCreditCard.expirationMonth +
               '/' + serverCreditCard.expirationYear!.toString().substring(2);
           let localCardExpectedSublabel = localCreditCard.expirationMonth +
@@ -877,6 +1100,132 @@
                       '#summarySublabel')!.textContent!.trim());
         });
   });
+
+  // Test to verify the existence of the benefits tag based on the existence of
+  // the benefits and the product terms URL on a server card without a CVC.
+  benefitsStatus.forEach(({
+                           benefitsAvailable,
+                           productTermsUrlAvailable,
+                         }) => {
+    const benefitsStatus = benefitsAvailable ? 'Available' : 'NotAvailable';
+    const termUrlStatus =
+        productTermsUrlAvailable ? 'Available' : 'NotAvailable';
+    const testName = `ServerCardSummarySublabel_NewFopDisplayOff_Benefits${
+        benefitsStatus}_ProductTermsUrl${termUrlStatus}`;
+    test(testName, async () => {
+      loadTimeData.overrideValues({
+        autofillCardBenefitsAvailable: benefitsAvailable,
+        enableNewFopDisplay: false,
+      });
+      const serverCreditCard = createCreditCardEntry(/*isNewFopDisplay=*/ false,
+                                                     /*hasIdentifier=*/ false);
+      assertTrue(!!serverCreditCard.metadata);
+      serverCreditCard.metadata.isLocal = false;
+      if (benefitsAvailable && productTermsUrlAvailable) {
+        serverCreditCard.productTermsUrl = 'https://google.com/';
+      }
+      await createPaymentsSection(
+          [serverCreditCard], /*ibans=*/[], /*payOverTimeIssuers=*/[],
+          /*prefValues=*/ {});
+
+      const paymentsList = getLocalAndServerCreditCardListItems();
+
+      assertTrue(!!paymentsList);
+      assertEquals(1, paymentsList.length);
+      assertTrue(isVisible(
+          paymentsList[0]!.shadowRoot!.querySelector<HTMLElement>(
+              '#summarySublabel')));
+
+      // Build the expected resulting sublabel based on which features are
+      // enabled.
+      let benefitExpectedSublabel = serverCreditCard.expirationMonth + '/' +
+          serverCreditCard.expirationYear!.toString().substring(2);
+      if (benefitsAvailable && productTermsUrlAvailable) {
+        benefitExpectedSublabel += ' | ' +
+            loadTimeData.getString('benefitsTermsTagForCreditCardListEntry');
+      }
+
+      assertEquals(
+          benefitExpectedSublabel,
+          cleanUpWhitespace(
+              paymentsList[0]!.shadowRoot!.querySelector<HTMLElement>(
+                  '#summarySublabel')!));
+      if (benefitsAvailable && productTermsUrlAvailable) {
+        const termsLink =
+            paymentsList[0]!.shadowRoot!.querySelector<HTMLAnchorElement>(
+                '#summaryTermsLink');
+        assertTrue(!!termsLink);
+        assertEquals(serverCreditCard.productTermsUrl, termsLink.href);
+      } else {
+        assertFalse(isVisible(
+            paymentsList[0]!.shadowRoot!.querySelector<HTMLElement>(
+                '#summaryTermsLink')));
+      }
+    });
+  });
+
+  // Test to verify the existence of the benefits tag based on the existence of
+  // the benefits and the product terms URL on a server card with a CVC saved.
+  benefitsStatus.forEach(({
+                           benefitsAvailable,
+                           productTermsUrlAvailable,
+                         }) => {
+    const benefitsStatus = benefitsAvailable ? 'Available' : 'NotAvailable';
+    const termUrlStatus =
+        productTermsUrlAvailable ? 'Available' : 'NotAvailable';
+    const testName =
+        `ServerCardWithCvcSummarySublabel_NewFopDisplayOff_Benefits${
+            benefitsStatus}_ProductTermsUrl${termUrlStatus}`;
+    test(testName, async () => {
+      loadTimeData.overrideValues({
+        cvcStorageAvailable: true,
+        autofillCardBenefitsAvailable: benefitsAvailable,
+        enableNewFopDisplay: false,
+      });
+      const serverCreditCard = createCreditCardEntry(/*isNewFopDisplay=*/ false,
+                                                     /*hasIdentifier=*/ false);
+      assertTrue(!!serverCreditCard.metadata);
+      serverCreditCard.metadata.isLocal = false;
+      serverCreditCard.cvc = '***';
+      if (benefitsAvailable && productTermsUrlAvailable) {
+        serverCreditCard.productTermsUrl = 'https://google.com/';
+      }
+      await createPaymentsSection(
+          [serverCreditCard], /*ibans=*/[], /*payOverTimeIssuers=*/[],
+          /*prefValues=*/ {});
+
+      const paymentsList = getLocalAndServerCreditCardListItems();
+
+      assertTrue(!!paymentsList);
+      assertEquals(1, paymentsList.length);
+      assertTrue(isVisible(
+          paymentsList[0]!.shadowRoot!.querySelector<HTMLElement>(
+              '#summarySublabel')));
+      let benefitExpectedSublabel = serverCreditCard.expirationMonth + '/' +
+          serverCreditCard.expirationYear!.toString().substring(2) + ' | ' +
+          loadTimeData.getString('cvcTagForCreditCardListEntry');
+      if (benefitsAvailable && productTermsUrlAvailable) {
+        benefitExpectedSublabel += ' | ' +
+            loadTimeData.getString('benefitsTermsTagForCreditCardListEntry');
+      }
+      assertEquals(
+          benefitExpectedSublabel,
+          cleanUpWhitespace(
+              paymentsList[0]!.shadowRoot!.querySelector<HTMLElement>(
+                  '#summarySublabel')!));
+      if (benefitsAvailable && productTermsUrlAvailable) {
+        const termsLink =
+            paymentsList[0]!.shadowRoot!.querySelector<HTMLAnchorElement>(
+                '#summaryTermsLink');
+        assertTrue(!!termsLink);
+        assertEquals(serverCreditCard.productTermsUrl, termsLink.href);
+      } else {
+        assertFalse(isVisible(
+            paymentsList[0]!.shadowRoot!.querySelector<HTMLElement>(
+                '#summaryTermsLink')));
+      }
+    });
+  });
 });
 
 suite('PaymentsSectionEditCreditCardLink', function() {
diff --git a/chrome/updater/mac/BUILD.gn b/chrome/updater/mac/BUILD.gn
index e8da7ed5..47a2953 100644
--- a/chrome/updater/mac/BUILD.gn
+++ b/chrome/updater/mac/BUILD.gn
@@ -332,6 +332,7 @@
     "//chrome/updater:base_app",
     "//chrome/updater:ks_ticket",
     "//chrome/updater:public_sources",
+    "//chrome/updater:tagging",
     "//chrome/updater:version_header",
   ]
 
diff --git a/chrome/updater/mac/keystone/ksadmin.mm b/chrome/updater/mac/keystone/ksadmin.mm
index e932462..12488675 100644
--- a/chrome/updater/mac/keystone/ksadmin.mm
+++ b/chrome/updater/mac/keystone/ksadmin.mm
@@ -40,12 +40,14 @@
 #include "base/task/single_thread_task_executor.h"
 #include "base/task/thread_pool/thread_pool_instance.h"
 #include "base/time/time.h"
+#include "base/types/expected.h"
 #include "chrome/updater/app/app.h"
 #include "chrome/updater/constants.h"
 #include "chrome/updater/ipc/ipc_support.h"
 #include "chrome/updater/mac/setup/ks_tickets.h"
 #include "chrome/updater/registration_data.h"
 #include "chrome/updater/service_proxy_factory.h"
+#include "chrome/updater/tag.h"
 #include "chrome/updater/update_service.h"
 #include "chrome/updater/updater_scope.h"
 #include "chrome/updater/updater_version.h"
@@ -124,6 +126,7 @@
 constexpr char kCommandVersionKey[] = "version-key";
 constexpr char kCommandVersionPath[] = "version-path";
 constexpr char kCommandXCPath[] = "xcpath";
+constexpr char kCommandXattrTagBrand[] = "print-xattr-tag-brand";
 
 bool HasSwitch(const std::string& arg,
                const std::map<std::string, std::string>& switches) {
@@ -257,6 +260,7 @@
   void PrintUsage(const std::string& error_message);
   void PrintVersion();
   void PrintTickets();
+  void PrintXattrTagBrand();
 
   void DoUpdateApp(UpdaterScope scope);
   void DoListAppUpdate(UpdaterScope scope);
@@ -794,6 +798,27 @@
       base::BindOnce(&KSAdminApp::Shutdown, this)));
 }
 
+void KSAdminApp::PrintXattrTagBrand() {
+  const std::string path_str = SwitchValue(kCommandXattrTagBrand);
+  if (path_str.empty()) {
+    LOG(ERROR) << kCommandXattrTagBrand << " requires a path.";
+    Shutdown(1);
+    return;
+  }
+  const base::FilePath path = base::FilePath(path_str);
+  base::expected<tagging::TagArgs, tagging::ErrorCode> tag_result =
+      tagging::ReadTagFromApplicationInstanceXattr(path);
+  if (!tag_result.has_value()) {
+    LOG(ERROR) << "Can't read tag: " << tag_result.error();
+    Shutdown(1);
+    return;
+  }
+
+  // Empty brand code is not an error.
+  printf("%s\n", tag_result->brand_code.c_str());
+  Shutdown(0);
+}
+
 void KSAdminApp::FirstTaskRun() {
   if ((geteuid() == 0) && (getuid() != 0)) {
     if (setuid(0) || setgid(0)) {
@@ -810,6 +835,7 @@
       {kCommandPrintTag, &KSAdminApp::PrintTag},
       {kCommandPrintTickets, &KSAdminApp::PrintTickets},
       {kCommandRegister, &KSAdminApp::Register},
+      {kCommandXattrTagBrand, &KSAdminApp::PrintXattrTagBrand},
   };
   for (const auto& [command, method] : commands) {
     if (HasSwitch(command)) {
diff --git a/chrome/updater/test/integration_test_commands.h b/chrome/updater/test/integration_test_commands.h
index b95a3af..b0c66db 100644
--- a/chrome/updater/test/integration_test_commands.h
+++ b/chrome/updater/test/integration_test_commands.h
@@ -204,7 +204,11 @@
       const base::FilePath& xc_path,
       std::optional<UpdaterScope> store_flag,
       std::optional<std::string> want_tag) const = 0;
-#endif  // BUILDFLAG(IS_WIN)
+  virtual void ExpectKSAdminXattrBrand(
+      bool elevate,
+      const base::FilePath& path,
+      std::optional<std::string> want_brand) const = 0;
+#endif  // BUILDFLAG(IS_MAC)
   virtual void ExpectLegacyUpdaterMigrated() const = 0;
   virtual void RunRecoveryComponent(const std::string& app_id,
                                     const base::Version& version) const = 0;
diff --git a/chrome/updater/test/integration_test_commands_system.cc b/chrome/updater/test/integration_test_commands_system.cc
index 04a334e..980582e 100644
--- a/chrome/updater/test/integration_test_commands_system.cc
+++ b/chrome/updater/test/integration_test_commands_system.cc
@@ -554,7 +554,16 @@
       std::optional<UpdaterScope> store_flag,
       std::optional<std::string> want_tag) const override {
     updater::test::ExpectKSAdminFetchTag(updater_scope_, elevate, product_id,
-                                         xc_path, store_flag, want_tag);
+                                         xc_path, store_flag,
+                                         std::move(want_tag));
+  }
+
+  void ExpectKSAdminXattrBrand(
+      bool elevate,
+      const base::FilePath& path,
+      std::optional<std::string> want_brand) const override {
+    updater::test::ExpectKSAdminXattrBrand(updater_scope_, elevate, path,
+                                           std::move(want_brand));
   }
 #endif  // BUILDFLAG(IS_MAC)
 
diff --git a/chrome/updater/test/integration_test_commands_user.cc b/chrome/updater/test/integration_test_commands_user.cc
index 80cc9986..3009aaa 100644
--- a/chrome/updater/test/integration_test_commands_user.cc
+++ b/chrome/updater/test/integration_test_commands_user.cc
@@ -449,7 +449,16 @@
       std::optional<UpdaterScope> store_flag,
       std::optional<std::string> want_tag) const override {
     updater::test::ExpectKSAdminFetchTag(updater_scope_, elevate, product_id,
-                                         xc_path, store_flag, want_tag);
+                                         xc_path, store_flag,
+                                         std::move(want_tag));
+  }
+
+  void ExpectKSAdminXattrBrand(
+      bool elevate,
+      const base::FilePath& path,
+      std::optional<std::string> want_brand) const override {
+    updater::test::ExpectKSAdminXattrBrand(updater_scope_, elevate, path,
+                                           std::move(want_brand));
   }
 #endif  // BUILDFLAG(IS_MAC)
 
diff --git a/chrome/updater/test/integration_tests.cc b/chrome/updater/test/integration_tests.cc
index a8ad6b56..4230f4da 100644
--- a/chrome/updater/test/integration_tests.cc
+++ b/chrome/updater/test/integration_tests.cc
@@ -694,6 +694,13 @@
                                           store_flag, want_tag);
   }
 
+  void ExpectKSAdminXattrBrand(bool elevate,
+                               const base::FilePath& path,
+                               std::optional<std::string> want_brand) {
+    test_commands_->ExpectKSAdminXattrBrand(elevate, path,
+                                            std::move(want_brand));
+  }
+
 #endif  // BUILDFLAG(IS_MAC)
 
   void ExpectAppInstalled(const std::string& appid,
@@ -1770,6 +1777,36 @@
   ASSERT_FALSE(read_result.has_value());
   EXPECT_EQ(read_result.error(), tagging::ErrorCode::kTagNotFound);
 }
+
+TEST_F(IntegrationTest, KSAdminXattrTagReadBrandSuccess) {
+  ASSERT_NO_FATAL_FAILURE(Install({kEnableCecaExperimentSwitch}));
+  base::ScopedTempFile tag_me;
+  ASSERT_TRUE(tag_me.Create());
+  EXPECT_TRUE(tagging::WriteTagStringToApplicationInstanceXattr(
+      tag_me.path(),
+      "brand=TEST&iid=TestInstallId&appguid=org.chromium.test&ap=example"));
+  ExpectKSAdminXattrBrand(false, tag_me.path(), "TEST");
+  ASSERT_NO_FATAL_FAILURE(Uninstall());
+}
+
+TEST_F(IntegrationTest, KSAdminXattrTagReadNoBrandSuccess) {
+  ASSERT_NO_FATAL_FAILURE(Install({kEnableCecaExperimentSwitch}));
+  base::ScopedTempFile tag_me_without_brand;
+  ASSERT_TRUE(tag_me_without_brand.Create());
+  EXPECT_TRUE(tagging::WriteTagStringToApplicationInstanceXattr(
+      tag_me_without_brand.path(),
+      "iid=TestInstallId&appguid=org.chromium.test&ap=example"));
+  ExpectKSAdminXattrBrand(false, tag_me_without_brand.path(), "");
+  ASSERT_NO_FATAL_FAILURE(Uninstall());
+}
+
+TEST_F(IntegrationTest, KSAdminXattrTagBrandNoXattrFailure) {
+  ASSERT_NO_FATAL_FAILURE(Install({kEnableCecaExperimentSwitch}));
+  base::ScopedTempFile dont_tag_me;
+  ASSERT_TRUE(dont_tag_me.Create());
+  ExpectKSAdminXattrBrand(false, dont_tag_me.path(), std::nullopt);
+  ASSERT_NO_FATAL_FAILURE(Uninstall());
+}
 #endif
 
 TEST_F(IntegrationTest, InstallId) {
diff --git a/chrome/updater/test/integration_tests_impl.h b/chrome/updater/test/integration_tests_impl.h
index 7a86176..200e007 100644
--- a/chrome/updater/test/integration_tests_impl.h
+++ b/chrome/updater/test/integration_tests_impl.h
@@ -624,6 +624,21 @@
                            std::optional<UpdaterScope> store_flag,
                            std::optional<std::string> want_tag);
 
+// Expect ksadmin to fetch the specified brand code from a tag stored in the
+// `com.apple.application-instance` extended attribute of the item at the
+// specified path, or to fail to retrieve a brand code.
+//
+// Params:
+//      scope -- Picks which ksadmin binary to use.
+//    elevate -- Whether to run as root instead of the current user.
+//       path -- Path to send to ksadmin via `--print-xattr-tag-brand`.
+// want_brand -- if valid, the brand code that ksadmin is expected to
+//               successfully retrieve, which may be the empty string. If
+//               nullopt, specifies that ksadmin should return EXIT_FAILURE.
+void ExpectKSAdminXattrBrand(UpdaterScope scope,
+                             bool elevate,
+                             const base::FilePath& path,
+                             std::optional<std::string> want_brand);
 #endif  // BUILDFLAG(IS_MAC)
 
 }  // namespace updater::test
diff --git a/chrome/updater/test/integration_tests_mac.mm b/chrome/updater/test/integration_tests_mac.mm
index e726c37..ffab1f5 100644
--- a/chrome/updater/test/integration_tests_mac.mm
+++ b/chrome/updater/test/integration_tests_mac.mm
@@ -559,6 +559,20 @@
   ExpectKSAdminResult(scope, elevate, switches, std::move(want_tag), want_exit);
 }
 
+void ExpectKSAdminXattrBrand(UpdaterScope scope,
+                             bool elevate,
+                             const base::FilePath& path,
+                             std::optional<std::string> want_brand) {
+  int want_exit = EXIT_FAILURE;
+  if (want_brand) {
+    *want_brand = base::StrCat({*want_brand, "\n"});
+    want_exit = EXIT_SUCCESS;
+  }
+  ExpectKSAdminResult(scope, elevate,
+                      {{"--print-xattr-tag-brand", path.value()}},
+                      std::move(want_brand), want_exit);
+}
+
 void ExpectCRURegistrationCannotFindKSAdmin() {
   @autoreleasepool {
     CRURegistration* registration = [[CRURegistration alloc]
diff --git a/chromeos/constants/chromeos_features.cc b/chromeos/constants/chromeos_features.cc
index ce60496..7930a0a 100644
--- a/chromeos/constants/chromeos_features.cc
+++ b/chromeos/constants/chromeos_features.cc
@@ -540,7 +540,8 @@
 }
 
 bool IsSystemBlurEnabled() {
-  return !base::FeatureList::IsEnabled(kDisableSystemBlur);
+  static bool disable_blur = base::FeatureList::IsEnabled(kDisableSystemBlur);
+  return !disable_blur;
 }
 
 bool IsPkcs12ToChapsDualWriteEnabled() {
diff --git a/clank b/clank
index 0b6dec3..75c0772 160000
--- a/clank
+++ b/clank
@@ -1 +1 @@
-Subproject commit 0b6dec35eaa57d814798237cad2cd20a7453d50d
+Subproject commit 75c07728388055f0e7b7a0496693ee75827e374d
diff --git a/components/autofill/android/java/res/layout/autofill_dropdown_item.xml b/components/autofill/android/java/res/layout/autofill_dropdown_item.xml
index e1086d6a..43ef2a02 100644
--- a/components/autofill/android/java/res/layout/autofill_dropdown_item.xml
+++ b/components/autofill/android/java/res/layout/autofill_dropdown_item.xml
@@ -14,13 +14,6 @@
     android:orientation="horizontal" >
 
     <!-- These layout params are overwritten in DropdownAdapter.java -->
-    <ImageView
-        android:id="@+id/start_dropdown_icon"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_margin="@dimen/autofill_dropdown_icon_margin"
-        tools:ignore="ContentDescription" />
-
     <LinearLayout
         android:id="@+id/dropdown_label_wrapper"
         android:layout_width="0dp"
diff --git a/components/autofill/android/java/src/org/chromium/components/autofill/AutofillDropdownAdapter.java b/components/autofill/android/java/src/org/chromium/components/autofill/AutofillDropdownAdapter.java
index 815760e..2e492fe 100644
--- a/components/autofill/android/java/src/org/chromium/components/autofill/AutofillDropdownAdapter.java
+++ b/components/autofill/android/java/src/org/chromium/components/autofill/AutofillDropdownAdapter.java
@@ -165,18 +165,10 @@
             secondarySublabelView.setTextColor(mContext.getColor(item.getSublabelFontColorResId()));
         }
 
-        ImageView iconViewStart = (ImageView) layout.findViewById(R.id.start_dropdown_icon);
         ImageView iconViewEnd = (ImageView) layout.findViewById(R.id.end_dropdown_icon);
-        if (item.isIconAtStart()) {
-            iconViewEnd.setVisibility(View.GONE);
-            iconViewStart.setVisibility(View.VISIBLE);
-        } else {
-            iconViewStart.setVisibility(View.GONE);
-            iconViewEnd.setVisibility(View.VISIBLE);
-        }
+        iconViewEnd.setVisibility(View.VISIBLE);
 
-        ImageView iconView =
-                populateIconView(item.isIconAtStart() ? iconViewStart : iconViewEnd, item);
+        ImageView iconView = populateIconView(iconViewEnd, item);
         if (iconView != null) {
             iconView.setLayoutParams(getSizeAndMarginParamsForIconView(iconView, item));
         }
diff --git a/components/autofill/android/java/src/org/chromium/components/autofill/AutofillSuggestion.java b/components/autofill/android/java/src/org/chromium/components/autofill/AutofillSuggestion.java
index 6d4cbe0..f1cbb86 100644
--- a/components/autofill/android/java/src/org/chromium/components/autofill/AutofillSuggestion.java
+++ b/components/autofill/android/java/src/org/chromium/components/autofill/AutofillSuggestion.java
@@ -26,7 +26,6 @@
     private final @Nullable String mSecondarySublabel;
     private final @Nullable String mLabelContentDescription;
     private final int mIconId;
-    private final boolean mIsIconAtStart;
     private final int mSuggestionType;
     private final boolean mIsDeletable;
     private final boolean mApplyDeactivatedStyle;
@@ -44,7 +43,6 @@
      * @param sublabel The describing sublabel of the Autofill suggestion.
      * @param iconId The resource ID for the icon associated with the suggestion, or {@code
      *     DropdownItem.NO_ICON} for no icon.
-     * @param isIconAtStart {@code true} if {@code iconId} is displayed before {@code label}.
      * @param popupItemId The type of suggestion.
      * @param isDeletable Whether the item can be deleted by the user.
      * @param applyDeactivatedStyle Whether to apply deactivated style to the suggestion.
@@ -62,7 +60,6 @@
             @Nullable String secondarySublabel,
             @Nullable String labelContentDescription,
             int iconId,
-            boolean isIconAtStart,
             @SuggestionType int popupItemId,
             boolean isDeletable,
             boolean applyDeactivatedStyle,
@@ -77,7 +74,6 @@
         mSecondarySublabel = secondarySublabel;
         mLabelContentDescription = labelContentDescription;
         mIconId = iconId;
-        mIsIconAtStart = isIconAtStart;
         mSuggestionType = popupItemId;
         mIsDeletable = isDeletable;
         mApplyDeactivatedStyle = applyDeactivatedStyle;
@@ -122,14 +118,6 @@
     }
 
     @Override
-    public boolean isIconAtStart() {
-        if (mIsIconAtStart) {
-            return true;
-        }
-        return super.isIconAtStart();
-    }
-
-    @Override
     public @Nullable GURL getCustomIconUrl() {
         return mCustomIconUrl;
     }
@@ -187,7 +175,6 @@
                 && Objects.equals(this.mSecondarySublabel, other.mSecondarySublabel)
                 && Objects.equals(this.mLabelContentDescription, other.mLabelContentDescription)
                 && this.mIconId == other.mIconId
-                && this.mIsIconAtStart == other.mIsIconAtStart
                 && this.mSuggestionType == other.mSuggestionType
                 && this.mIsDeletable == other.mIsDeletable
                 && this.mApplyDeactivatedStyle == other.mApplyDeactivatedStyle
@@ -203,7 +190,6 @@
         private int mIconId;
         private @Nullable GURL mCustomIconUrl;
         private @Nullable Drawable mIconDrawable;
-        private boolean mIsIconAtStart;
         private boolean mIsDeletable;
         private boolean mApplyDeactivatedStyle;
         private boolean mShouldDisplayTermsAvailable;
@@ -231,11 +217,6 @@
             return this;
         }
 
-        public Builder setIsIconAtStart(boolean isIconAtStart) {
-            this.mIsIconAtStart = isIconAtStart;
-            return this;
-        }
-
         public Builder setIsDeletable(boolean isDeletable) {
             this.mIsDeletable = isDeletable;
             return this;
@@ -303,7 +284,6 @@
                     mSecondarySubLabel,
                     mLabelContentDescription,
                     mIconId,
-                    mIsIconAtStart,
                     mSuggestionType,
                     mIsDeletable,
                     mApplyDeactivatedStyle,
diff --git a/components/autofill/core/browser/metrics/payments/bnpl_metrics.cc b/components/autofill/core/browser/metrics/payments/bnpl_metrics.cc
index 451444f5..3b26690e2c 100644
--- a/components/autofill/core/browser/metrics/payments/bnpl_metrics.cc
+++ b/components/autofill/core/browser/metrics/payments/bnpl_metrics.cc
@@ -10,7 +10,7 @@
 #include "components/autofill/core/browser/payments/constants.h"
 
 namespace autofill::autofill_metrics {
-namespace {
+
 std::string GetHistogramSuffixFromIssuerId(std::string_view issuer_id) {
   if (issuer_id == kBnplAffirmIssuerId) {
     return "Affirm";
@@ -21,7 +21,6 @@
   }
   NOTREACHED();
 }
-}  // namespace
 
 void LogBnplPrefToggled(bool enabled) {
   base::UmaHistogramBoolean("Autofill.SettingsPage.BnplToggled", enabled);
@@ -44,4 +43,19 @@
                                 reason);
 }
 
+void LogBnplPopupWindowShown(std::string_view issuer_id) {
+  std::string histogram_name =
+      base::StrCat({"Autofill.Bnpl.PopupWindowShown.",
+                    GetHistogramSuffixFromIssuerId(issuer_id)});
+  base::UmaHistogramBoolean(histogram_name, /*sample=*/true);
+}
+
+void LogBnplPopupWindowResult(std::string_view issuer_id,
+                              BnplFlowResult result) {
+  std::string histogram_name =
+      base::StrCat({"Autofill.Bnpl.PopupWindowResult.",
+                    GetHistogramSuffixFromIssuerId(issuer_id)});
+  base::UmaHistogramEnumeration(histogram_name, result);
+}
+
 }  // namespace autofill::autofill_metrics
diff --git a/components/autofill/core/browser/metrics/payments/bnpl_metrics.h b/components/autofill/core/browser/metrics/payments/bnpl_metrics.h
index b9a2f45..0e4ecf0 100644
--- a/components/autofill/core/browser/metrics/payments/bnpl_metrics.h
+++ b/components/autofill/core/browser/metrics/payments/bnpl_metrics.h
@@ -7,8 +7,12 @@
 
 #include <string_view>
 
+#include "components/autofill/core/browser/payments/payments_window_manager.h"
+
 namespace autofill::autofill_metrics {
 
+using BnplFlowResult = payments::PaymentsWindowManager::BnplFlowResult;
+
 // The reason why a BNPL suggestion was not shown on the page.
 enum class BnplSuggestionNotShownReason {
   // The checkout amount could not be extracted from the page. This value is
@@ -22,6 +26,9 @@
   kMaxValue = kCheckoutAmountNotSupported,
 };
 
+// Returns the histogram suffix corresponding to the given issuer_id.
+std::string GetHistogramSuffixFromIssuerId(std::string_view issuer_id);
+
 // Logs if the buy-now-pay-later preference is changed by the user through the
 // pay-over-time toggle in the payment methods settings page. Records true when
 // the user switches on buy-now-pay-later. Records false when the user switches
@@ -37,6 +44,13 @@
 // Logs that the BNPL suggestion was not shown and the reason why.
 void LogBnplSuggestionNotShownReason(BnplSuggestionNotShownReason reason);
 
+// Logs that the BNPL popup window was shown.
+void LogBnplPopupWindowShown(std::string_view issuer_id);
+
+// Logs the result of the BNPL popup window.
+void LogBnplPopupWindowResult(std::string_view issuer_id,
+                              BnplFlowResult result);
+
 }  // namespace autofill::autofill_metrics
 
 #endif  // COMPONENTS_AUTOFILL_CORE_BROWSER_METRICS_PAYMENTS_BNPL_METRICS_H_
diff --git a/components/autofill/core/browser/metrics/payments/bnpl_metrics_unittest.cc b/components/autofill/core/browser/metrics/payments/bnpl_metrics_unittest.cc
index fbe428a..e19b08a 100644
--- a/components/autofill/core/browser/metrics/payments/bnpl_metrics_unittest.cc
+++ b/components/autofill/core/browser/metrics/payments/bnpl_metrics_unittest.cc
@@ -13,7 +13,11 @@
 
 namespace autofill::autofill_metrics {
 
-class BnplMetricsTest : public AutofillMetricsBaseTest, public testing::Test {
+// Params of the BnplMetricsTest:
+// -- std::string_view issuer_id;
+class BnplMetricsTest : public AutofillMetricsBaseTest,
+                        public testing::Test,
+                        public testing::WithParamInterface<std::string_view> {
  public:
   BnplMetricsTest() = default;
   ~BnplMetricsTest() override = default;
@@ -21,6 +25,8 @@
   void SetUp() override { SetUpHelper(); }
 
   void TearDown() override { TearDownHelper(); }
+
+  std::string_view GetBnplIssuerId() { return GetParam(); }
 };
 
 // BNPL is currently only available for desktop platforms.
@@ -69,28 +75,16 @@
                                     2);
 }
 
-TEST_F(BnplMetricsTest, LogBnplTosDialogShownZip) {
+TEST_P(BnplMetricsTest, LogBnplTosDialogShown) {
   base::HistogramTester histogram_tester;
-  LogBnplTosDialogShown(kBnplZipIssuerId);
-  histogram_tester.ExpectUniqueSample("Autofill.Bnpl.TosDialogShown.Zip",
-                                      /*sample=*/true,
-                                      /*expected_bucket_count=*/1);
-}
+  std::string_view issuer_id = GetBnplIssuerId();
 
-TEST_F(BnplMetricsTest, LogBnplTosDialogShownAffirm) {
-  base::HistogramTester histogram_tester;
-  LogBnplTosDialogShown(kBnplAffirmIssuerId);
-  histogram_tester.ExpectUniqueSample("Autofill.Bnpl.TosDialogShown.Affirm",
-                                      /*sample=*/true,
-                                      /*expected_bucket_count=*/1);
-}
-
-TEST_F(BnplMetricsTest, LogBnplTosDialogShownAfterpay) {
-  base::HistogramTester histogram_tester;
-  LogBnplTosDialogShown(kBnplAfterpayIssuerId);
-  histogram_tester.ExpectUniqueSample("Autofill.Bnpl.TosDialogShown.Afterpay",
-                                      /*sample=*/true,
-                                      /*expected_bucket_count=*/1);
+  LogBnplTosDialogShown(issuer_id);
+  histogram_tester.ExpectUniqueSample(
+      base::StrCat({"Autofill.Bnpl.TosDialogShown.",
+                    GetHistogramSuffixFromIssuerId(issuer_id)}),
+      /*sample=*/true,
+      /*expected_bucket_count=*/1);
 }
 
 TEST_F(BnplMetricsTest,
@@ -117,6 +111,57 @@
       BnplSuggestionNotShownReason::kCheckoutAmountNotSupported, 1);
 }
 
+TEST_P(BnplMetricsTest, LogBnplPopupWindowShown) {
+  base::HistogramTester histogram_tester;
+  std::string_view issuer_id = GetBnplIssuerId();
+
+  LogBnplPopupWindowShown(issuer_id);
+  histogram_tester.ExpectUniqueSample(
+      base::StrCat({"Autofill.Bnpl.PopupWindowShown.",
+                    GetHistogramSuffixFromIssuerId(issuer_id)}),
+      /*sample=*/true,
+      /*expected_bucket_count=*/1);
+}
+
+TEST_P(BnplMetricsTest, LogBnplPopupWindowResult_Success) {
+  base::HistogramTester histogram_tester;
+  std::string_view issuer_id = GetBnplIssuerId();
+
+  LogBnplPopupWindowResult(issuer_id, BnplFlowResult::kSuccess);
+  histogram_tester.ExpectUniqueSample(
+      base::StrCat({"Autofill.Bnpl.PopupWindowResult.",
+                    GetHistogramSuffixFromIssuerId(issuer_id)}),
+      BnplFlowResult::kSuccess, 1);
+}
+
+TEST_P(BnplMetricsTest, LogBnplPopupWindowResult_Failure) {
+  base::HistogramTester histogram_tester;
+  std::string_view issuer_id = GetBnplIssuerId();
+
+  LogBnplPopupWindowResult(issuer_id, BnplFlowResult::kFailure);
+  histogram_tester.ExpectUniqueSample(
+      base::StrCat({"Autofill.Bnpl.PopupWindowResult.",
+                    GetHistogramSuffixFromIssuerId(issuer_id)}),
+      BnplFlowResult::kFailure, 1);
+}
+
+TEST_P(BnplMetricsTest, LogBnplPopupWindowResult_UserClosed) {
+  base::HistogramTester histogram_tester;
+  std::string_view issuer_id = GetBnplIssuerId();
+
+  LogBnplPopupWindowResult(issuer_id, BnplFlowResult::kUserClosed);
+  histogram_tester.ExpectUniqueSample(
+      base::StrCat({"Autofill.Bnpl.PopupWindowResult.",
+                    GetHistogramSuffixFromIssuerId(issuer_id)}),
+      BnplFlowResult::kUserClosed, 1);
+}
+
+INSTANTIATE_TEST_SUITE_P(,
+                         BnplMetricsTest,
+                         testing::Values(kBnplAffirmIssuerId,
+                                         kBnplZipIssuerId,
+                                         kBnplAfterpayIssuerId));
+
 #endif  // BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX) ||
         // BUILDFLAG(IS_CHROMEOS)
 
diff --git a/components/autofill/core/browser/payments/bnpl_manager.cc b/components/autofill/core/browser/payments/bnpl_manager.cc
index 3bd3a493..9992e92b 100644
--- a/components/autofill/core/browser/payments/bnpl_manager.cc
+++ b/components/autofill/core/browser/payments/bnpl_manager.cc
@@ -347,15 +347,20 @@
     ongoing_flow_state_->redirect_url = std::move(response.redirect_url);
     ongoing_flow_state_->context_token = std::move(response.context_token);
 
-    PaymentsWindowManager::BnplContext bnpl_context;
-    bnpl_context.initial_url = ongoing_flow_state_->redirect_url;
-    bnpl_context.success_url_prefix = std::move(response.success_url_prefix);
-    bnpl_context.failure_url_prefix = std::move(response.failure_url_prefix);
-    bnpl_context.completion_callback = base::BindOnce(
+    PaymentsWindowManager::BnplContext payments_window_bnpl_context;
+    payments_window_bnpl_context.issuer_id =
+        ongoing_flow_state_->issuer.issuer_id();
+    payments_window_bnpl_context.initial_url =
+        ongoing_flow_state_->redirect_url;
+    payments_window_bnpl_context.success_url_prefix =
+        std::move(response.success_url_prefix);
+    payments_window_bnpl_context.failure_url_prefix =
+        std::move(response.failure_url_prefix);
+    payments_window_bnpl_context.completion_callback = base::BindOnce(
         &BnplManager::OnPopupWindowCompleted, weak_factory_.GetWeakPtr());
 
     payments_autofill_client().GetPaymentsWindowManager()->InitBnplFlow(
-        std::move(bnpl_context));
+        std::move(payments_window_bnpl_context));
   } else {
     payments_autofill_client().ShowAutofillErrorDialog(
         AutofillErrorDialogContext::WithBnplPermanentOrTemporaryError(
diff --git a/components/autofill/core/browser/payments/bnpl_manager_unittest.cc b/components/autofill/core/browser/payments/bnpl_manager_unittest.cc
index 97825be..8c62895 100644
--- a/components/autofill/core/browser/payments/bnpl_manager_unittest.cc
+++ b/components/autofill/core/browser/payments/bnpl_manager_unittest.cc
@@ -534,7 +534,8 @@
   EXPECT_CALL(*static_cast<MockPaymentsWindowManager*>(
                   autofill_client_->GetPaymentsAutofillClient()
                       ->GetPaymentsWindowManager()),
-              InitBnplFlow(FieldsAre(kRedirectUrl, response.success_url_prefix,
+              InitBnplFlow(FieldsAre(linked_issuer.issuer_id(), kRedirectUrl,
+                                     response.success_url_prefix,
                                      response.failure_url_prefix,
                                      /*completion_callback=*/_)))
       .Times(1);
diff --git a/components/autofill/core/browser/payments/payments_window_manager.h b/components/autofill/core/browser/payments/payments_window_manager.h
index 28e6289..91019248 100644
--- a/components/autofill/core/browser/payments/payments_window_manager.h
+++ b/components/autofill/core/browser/payments/payments_window_manager.h
@@ -73,6 +73,11 @@
 
   // The result of the BNPL flow, which will be sent to the caller that
   // initiated the flow.
+  //
+  // These values are persisted to logs. Entries should not be renumbered and
+  // numeric values should never be reused.
+  //
+  // LINT.IfChange(BnplFlowResult)
   enum class BnplFlowResult {
     // The BNPL flow was successful.
     kSuccess = 0,
@@ -80,7 +85,10 @@
     kFailure = 1,
     // The user closed the pop-up which ended the BNPL flow.
     kUserClosed = 2,
+
+    kMaxValue = kUserClosed,
   };
+  // LINT.ThenChange(/tools/metrics/histograms/metadata/autofill/enums.xml:BnplPopupWindowResult)
 
   using OnVcn3dsAuthenticationCompleteCallback =
       base::OnceCallback<void(Vcn3dsAuthenticationResponse)>;
@@ -122,6 +130,8 @@
     BnplContext& operator=(BnplContext&&);
     ~BnplContext();
 
+    // The ID of the issuer for the BNPL flow.
+    std::string issuer_id;
     // The starting location of the BNPL flow, which is an initial URL to
     // open inside of the pop-up.
     GURL initial_url;
diff --git a/components/autofill/core/browser/suggestions/payments/payments_suggestion_generator.cc b/components/autofill/core/browser/suggestions/payments/payments_suggestion_generator.cc
index 7f63e98..88ebebb 100644
--- a/components/autofill/core/browser/suggestions/payments/payments_suggestion_generator.cc
+++ b/components/autofill/core/browser/suggestions/payments/payments_suggestion_generator.cc
@@ -472,9 +472,7 @@
         Suggestion::Acceptability::kUnacceptableWithDeactivatedStyle;
   }
   suggestion.iph_metadata = Suggestion::IPHMetadata(
-      suggestion.HasDeactivatedStyle() &&
-              base::FeatureList::IsEnabled(
-                  features::kAutofillEnableVcnGrayOutForMerchantOptOut)
+      suggestion.HasDeactivatedStyle()
           ? &feature_engagement::
                 kIPHAutofillDisabledVirtualCardSuggestionFeature
           : &feature_engagement::kIPHAutofillVirtualCardSuggestionFeature);
@@ -657,31 +655,6 @@
   return virtual_card_guid_to_last_four_map;
 }
 
-// Returns true if we should show a virtual card option for the server card
-// `card`, false otherwise.
-bool ShouldShowVirtualCardOptionForServerCard(const CreditCard& card,
-                                              const AutofillClient& client) {
-  // If the card is not enrolled into virtual cards, we should not show a
-  // virtual card suggestion for it.
-  if (card.virtual_card_enrollment_state() !=
-      CreditCard::VirtualCardEnrollmentState::kEnrolled) {
-    return false;
-  }
-  // We should not show a suggestion for this card if the autofill
-  // optimization guide returns that this suggestion should be blocked.
-  if (auto* autofill_optimization_guide =
-          client.GetAutofillOptimizationGuide()) {
-    return !autofill_optimization_guide->ShouldBlockFormFieldSuggestion(
-               client.GetLastCommittedPrimaryMainFrameOrigin().GetURL(),
-               card) ||
-           base::FeatureList::IsEnabled(
-               features::kAutofillEnableVcnGrayOutForMerchantOptOut);
-  }
-  // No conditions to prevent displaying a virtual card suggestion were
-  // found, so return true.
-  return true;
-}
-
 // Returns display name based on `issuer_id` in a vector.
 std::u16string GetDisplayNameForIssuerId(const std::string& issuer_id) {
   if (issuer_id == "paypay") {
@@ -733,8 +706,11 @@
     return false;
   }
   candidate_card = candidate_server_card;
-  return ShouldShowVirtualCardOptionForServerCard(*candidate_server_card,
-                                                  client);
+
+  // Virtual card suggestion is shown only when the card is enrolled into
+  // virtual cards.
+  return candidate_card->virtual_card_enrollment_state() ==
+         CreditCard::VirtualCardEnrollmentState::kEnrolled;
 }
 
 // Returns the local and server cards ordered by the Autofill ranking.
@@ -820,10 +796,6 @@
                                  ? Suggestion::Acceptability::kAcceptable
                                  : Suggestion::Acceptability::kUnacceptable;
   suggestion.payload = Suggestion::Guid(credit_card.guid());
-#if BUILDFLAG(IS_ANDROID)
-  // The card art icon should always be shown at the start of the suggestion.
-  suggestion.is_icon_at_start = true;
-#endif  // BUILDFLAG(IS_ANDROID)
 
   // Manual fallback suggestions labels are computed as if the triggering field
   // type was the credit card number.
diff --git a/components/autofill/core/browser/suggestions/payments/payments_suggestion_generator_unittest.cc b/components/autofill/core/browser/suggestions/payments/payments_suggestion_generator_unittest.cc
index d2164c3..edd27b2d 100644
--- a/components/autofill/core/browser/suggestions/payments/payments_suggestion_generator_unittest.cc
+++ b/components/autofill/core/browser/suggestions/payments/payments_suggestion_generator_unittest.cc
@@ -1569,9 +1569,6 @@
 // of virtual cards.
 TEST_F(PaymentsSuggestionGeneratorTest,
        ShouldShowVirtualCardOption_InDisabledStateForOptedOutMerchants) {
-  base::test::ScopedFeatureList features(
-      features::kAutofillEnableVcnGrayOutForMerchantOptOut);
-
   // Create an enrolled server card.
   CreditCard server_card =
       test::GetMaskedServerCardEnrolledIntoVirtualCardNumber();
@@ -1587,36 +1584,6 @@
       ShouldShowVirtualCardOptionForTest(&server_card, *autofill_client()));
 }
 
-// Test that the virtual card option is not shown if the merchant is opted-out
-// of virtual cards.
-TEST_F(PaymentsSuggestionGeneratorTest,
-       ShouldNotShowVirtualCardOption_MerchantOptedOutOfVirtualCards) {
-  base::test::ScopedFeatureList features;
-  features.InitAndDisableFeature(
-      features::kAutofillEnableVcnGrayOutForMerchantOptOut);
-  // Create an enrolled server card.
-  CreditCard server_card =
-      CreateServerCard(/*guid=*/"00000000-0000-0000-0000-000000000001");
-  server_card.set_virtual_card_enrollment_state(
-      CreditCard::VirtualCardEnrollmentState::kEnrolled);
-  payments_data().AddServerCreditCard(server_card);
-
-  // Create a local card with same information.
-  CreditCard local_card =
-      CreateLocalCard(/*guid=*/"00000000-0000-0000-0000-000000000002");
-
-  // If the URL is opted-out of virtual cards for `server_card`, do not display
-  // the virtual card suggestion.
-  auto* optimization_guide = autofill_client()->GetAutofillOptimizationGuide();
-  ON_CALL(*static_cast<MockAutofillOptimizationGuide*>(optimization_guide),
-          ShouldBlockFormFieldSuggestion)
-      .WillByDefault(testing::Return(true));
-  EXPECT_FALSE(
-      ShouldShowVirtualCardOptionForTest(&server_card, *autofill_client()));
-  EXPECT_FALSE(
-      ShouldShowVirtualCardOptionForTest(&local_card, *autofill_client()));
-}
-
 // Test that the virtual card option is not shown if the server card we might be
 // showing a virtual card option for is not enrolled into virtual card.
 TEST_F(PaymentsSuggestionGeneratorTest,
@@ -2089,19 +2056,7 @@
 // This class helps test the credit card contents that are displayed in
 // Autofill suggestions. It covers suggestions on Desktop/Android dropdown,
 // and on Android keyboard accessory.
-class AutofillCreditCardSuggestionContentTest
-    : public PaymentsSuggestionGeneratorTest {
- public:
-  AutofillCreditCardSuggestionContentTest() {
-    feature_list_metadata_.InitAndEnableFeature(
-        features::kAutofillEnableVcnGrayOutForMerchantOptOut);
-  }
-
-  ~AutofillCreditCardSuggestionContentTest() override = default;
-
- private:
-  base::test::ScopedFeatureList feature_list_metadata_;
-};
+using AutofillCreditCardSuggestionContentTest = PaymentsSuggestionGeneratorTest;
 
 // Verify that the suggestion's texts are populated correctly for a virtual card
 // suggestion when the cardholder name field is focused.
@@ -2516,17 +2471,11 @@
  private:
   void SetUp() override {
     AutofillCreditCardSuggestionContentTest::SetUp();
-    // Content test is only needed when the gray-out feature is enabled.
-    // Otherwise user will not see a VCN for opted out merchants.
-    scoped_feature_list_.InitWithFeatureState(
-        features::kAutofillEnableVcnGrayOutForMerchantOptOut, true);
-
     ON_CALL(*static_cast<MockAutofillOptimizationGuide*>(
                 autofill_client()->GetAutofillOptimizationGuide()),
             ShouldBlockFormFieldSuggestion)
         .WillByDefault(testing::Return(is_merchant_opted_out()));
   }
-  base::test::ScopedFeatureList scoped_feature_list_;
 };
 
 INSTANTIATE_TEST_SUITE_P(
diff --git a/components/autofill/core/browser/suggestions/suggestion.h b/components/autofill/core/browser/suggestions/suggestion.h
index a5b7a3eb..7d25a995 100644
--- a/components/autofill/core/browser/suggestions/suggestion.h
+++ b/components/autofill/core/browser/suggestions/suggestion.h
@@ -461,9 +461,6 @@
   // submenus.
   std::vector<Suggestion> children;
 #if BUILDFLAG(IS_ANDROID)
-  // On Android, the icon can be at the start of the suggestion before the label
-  // or at the end of the label.
-  bool is_icon_at_start = false;
   // TODO(crbug.com/346469807): Remove once strings are passed directly.
   std::u16string iph_description_text;
 #endif  // BUILDFLAG(IS_ANDROID)
diff --git a/components/browser_ui/widget/android/java/src/org/chromium/components/browser_ui/widget/FullscreenAlertDialog.java b/components/browser_ui/widget/android/java/src/org/chromium/components/browser_ui/widget/FullscreenAlertDialog.java
index 5d2d287..3f1d1c8 100644
--- a/components/browser_ui/widget/android/java/src/org/chromium/components/browser_ui/widget/FullscreenAlertDialog.java
+++ b/components/browser_ui/widget/android/java/src/org/chromium/components/browser_ui/widget/FullscreenAlertDialog.java
@@ -181,6 +181,8 @@
                 super.setView(automotiveLayout);
             } else if (mEdgeToEdgeLayout != null) {
                 super.setView(mEdgeToEdgeLayout.wrapContentView(view));
+            } else {
+                super.setView(view);
             }
             return this;
         }
diff --git a/components/browser_ui/widget/android/java/src/org/chromium/components/browser_ui/widget/FullscreenAlertDialogUnitTest.java b/components/browser_ui/widget/android/java/src/org/chromium/components/browser_ui/widget/FullscreenAlertDialogUnitTest.java
index 73ee873..718b015 100644
--- a/components/browser_ui/widget/android/java/src/org/chromium/components/browser_ui/widget/FullscreenAlertDialogUnitTest.java
+++ b/components/browser_ui/widget/android/java/src/org/chromium/components/browser_ui/widget/FullscreenAlertDialogUnitTest.java
@@ -71,6 +71,36 @@
     }
 
     @Test
+    public void notPadForInsetsWithBuilder() {
+        launchActivity();
+
+        FrameLayout dialogView = new FrameLayout(mActivity);
+        AlertDialog dialog =
+                new FullscreenAlertDialog.Builder(mActivity, /* shouldPadForContent= */ false)
+                        .setView(dialogView)
+                        .create();
+        dialog.show();
+
+        Assert.assertFalse(
+                "Dialog view should not be wrapped.",
+                dialogView.getParent() instanceof EdgeToEdgeBaseLayout);
+    }
+
+    @Test
+    public void notPadForInsetsWithConstructor() {
+        launchActivity();
+
+        FrameLayout dialogView = new FrameLayout(mActivity);
+        AlertDialog dialog = new FullscreenAlertDialog(mActivity, /* shouldPadForContent= */ false);
+        dialog.setView(dialogView);
+        dialog.show();
+
+        Assert.assertFalse(
+                "Dialog view should not be wrapped.",
+                dialogView.getParent() instanceof EdgeToEdgeBaseLayout);
+    }
+
+    @Test
     public void padForInsetsDisabledOnAutomotive() {
         mAutomotiveRule.setIsAutomotive(true);
         launchActivity();
diff --git a/components/collaboration/internal/metrics.cc b/components/collaboration/internal/metrics.cc
index de4bef7..455bfffe 100644
--- a/components/collaboration/internal/metrics.cc
+++ b/components/collaboration/internal/metrics.cc
@@ -9,6 +9,7 @@
 #include "components/data_sharing/public/logger.h"
 #include "components/data_sharing/public/logger_common.mojom.h"
 #include "components/data_sharing/public/logger_utils.h"
+#include "ui/base/page_transition_types.h"
 
 namespace collaboration::metrics {
 
@@ -153,6 +154,16 @@
   switch (entry) {
     case CollaborationServiceJoinEntryPoint::kUnknown:
       return "Unknown";
+    case CollaborationServiceJoinEntryPoint::kLinkClick:
+      return "LinkClick";
+    case CollaborationServiceJoinEntryPoint::kUserTyped:
+      return "UserTyped";
+    case CollaborationServiceJoinEntryPoint::kExternalApp:
+      return "ExternalApp";
+    case CollaborationServiceJoinEntryPoint::kForwardBackButton:
+      return "ForwardBackButton";
+    case CollaborationServiceJoinEntryPoint::kRedirect:
+      return "Redirect";
   }
 }
 
@@ -250,6 +261,40 @@
                    logger, CreateJoinEntryLogToString(entry));
 }
 
+void RecordJoinPageTransitionType(data_sharing::Logger* logger,
+                                  ui::PageTransition transition) {
+  switch (ui::PageTransitionStripQualifier(transition)) {
+    case ui::PageTransition::PAGE_TRANSITION_LINK:
+      RecordJoinEntryPoint(logger,
+                           CollaborationServiceJoinEntryPoint::kLinkClick);
+      break;
+    case ui::PageTransition::PAGE_TRANSITION_TYPED:
+    case ui::PageTransition::PAGE_TRANSITION_FROM_ADDRESS_BAR:
+      RecordJoinEntryPoint(logger,
+                           CollaborationServiceJoinEntryPoint::kUserTyped);
+      break;
+    case ui::PageTransition::PAGE_TRANSITION_FROM_API:
+      RecordJoinEntryPoint(logger,
+                           CollaborationServiceJoinEntryPoint::kExternalApp);
+      break;
+    case ui::PageTransition::PAGE_TRANSITION_FORWARD_BACK:
+      RecordJoinEntryPoint(
+          logger, CollaborationServiceJoinEntryPoint::kForwardBackButton);
+      break;
+    case ui::PageTransition::PAGE_TRANSITION_CHAIN_START:
+    case ui::PageTransition::PAGE_TRANSITION_CHAIN_END:
+    case ui::PageTransition::PAGE_TRANSITION_CLIENT_REDIRECT:
+    case ui::PageTransition::PAGE_TRANSITION_SERVER_REDIRECT:
+    case ui::PageTransition::PAGE_TRANSITION_IS_REDIRECT_MASK:
+      RecordJoinEntryPoint(logger,
+                           CollaborationServiceJoinEntryPoint::kRedirect);
+      break;
+    default:
+      RecordJoinEntryPoint(logger,
+                           CollaborationServiceJoinEntryPoint::kUnknown);
+  }
+}
+
 void RecordShareOrManageEntryPoint(
     data_sharing::Logger* logger,
     CollaborationServiceShareOrManageEntryPoint entry) {
diff --git a/components/collaboration/internal/metrics.h b/components/collaboration/internal/metrics.h
index b8dec75fe..fc041e1 100644
--- a/components/collaboration/internal/metrics.h
+++ b/components/collaboration/internal/metrics.h
@@ -7,6 +7,7 @@
 
 #include "components/collaboration/public/collaboration_flow_entry_point.h"
 #include "components/collaboration/public/collaboration_flow_type.h"
+#include "ui/base/page_transition_types.h"
 
 namespace data_sharing {
 class Logger;
@@ -103,6 +104,8 @@
     CollaborationServiceShareOrManageEvent share_or_manage_event);
 void RecordJoinEntryPoint(data_sharing::Logger* logger,
                           CollaborationServiceJoinEntryPoint entry);
+void RecordJoinPageTransitionType(data_sharing::Logger* logger,
+                                  ui::PageTransition transition);
 void RecordShareOrManageEntryPoint(
     data_sharing::Logger* logger,
     CollaborationServiceShareOrManageEntryPoint entry);
diff --git a/components/collaboration/public/collaboration_flow_entry_point.h b/components/collaboration/public/collaboration_flow_entry_point.h
index 22c9851..fcd98454 100644
--- a/components/collaboration/public/collaboration_flow_entry_point.h
+++ b/components/collaboration/public/collaboration_flow_entry_point.h
@@ -17,7 +17,12 @@
 //   org.chromium.components.collaboration)
 enum class CollaborationServiceJoinEntryPoint {
   kUnknown = 0,
-  kMaxValue = kUnknown,
+  kLinkClick = 1,
+  kUserTyped = 2,
+  kExternalApp = 3,
+  kForwardBackButton = 4,
+  kRedirect = 5,
+  kMaxValue = kRedirect,
 };
 // LINT.ThenChange(//tools/metrics/histograms/metadata/collaboration_service/enums.xml:CollaborationServiceJoinEntryPoint)
 
diff --git a/components/device_signals/core/browser/signals_types.cc b/components/device_signals/core/browser/signals_types.cc
index 3c2c205..34d3a306 100644
--- a/components/device_signals/core/browser/signals_types.cc
+++ b/components/device_signals/core/browser/signals_types.cc
@@ -111,6 +111,18 @@
 
 OsSignalsResponse::~OsSignalsResponse() = default;
 
+ProfileSignalsResponse::ProfileSignalsResponse() = default;
+ProfileSignalsResponse::ProfileSignalsResponse(const ProfileSignalsResponse&) =
+    default;
+
+ProfileSignalsResponse& ProfileSignalsResponse::operator=(
+    const ProfileSignalsResponse&) = default;
+
+bool ProfileSignalsResponse::operator==(const ProfileSignalsResponse&) const =
+    default;
+
+ProfileSignalsResponse::~ProfileSignalsResponse() = default;
+
 FileSystemInfoResponse::FileSystemInfoResponse() = default;
 FileSystemInfoResponse::FileSystemInfoResponse(const FileSystemInfoResponse&) =
     default;
diff --git a/components/device_signals/core/browser/signals_types.h b/components/device_signals/core/browser/signals_types.h
index 8d7efa6..0f752176 100644
--- a/components/device_signals/core/browser/signals_types.h
+++ b/components/device_signals/core/browser/signals_types.h
@@ -12,6 +12,8 @@
 #include "base/values.h"
 #include "build/build_config.h"
 #include "components/device_signals/core/common/common_types.h"
+#include "components/enterprise/connectors/core/common.h"
+#include "components/safe_browsing/core/common/safe_browsing_prefs.h"
 
 #if BUILDFLAG(IS_WIN)
 #include "components/device_signals/core/common/win/win_types.h"
@@ -231,6 +233,28 @@
   std::optional<std::string> windows_user_domain = std::nullopt;
 };
 
+struct ProfileSignalsResponse : BaseSignalResponse {
+  ProfileSignalsResponse();
+
+  ProfileSignalsResponse(const ProfileSignalsResponse&);
+  ProfileSignalsResponse& operator=(const ProfileSignalsResponse&);
+
+  bool operator==(const ProfileSignalsResponse&) const;
+
+  ~ProfileSignalsResponse() override;
+
+  bool built_in_dns_client_enabled;
+  bool chrome_remote_desktop_app_blocked;
+  std::optional<safe_browsing::PasswordProtectionTrigger>
+      password_protection_warning_trigger = std::nullopt;
+  std::optional<std::string> profile_enrollment_domain = std::nullopt;
+  safe_browsing::SafeBrowsingState safe_browsing_protection_level;
+  bool site_isolation_enabled;
+
+  // Enterprise cloud content analysis exclusive
+  enterprise_connectors::EnterpriseRealTimeUrlCheckMode realtime_url_check_mode;
+};
+
 struct FileSystemInfoResponse : BaseSignalResponse {
   FileSystemInfoResponse();
 
@@ -308,6 +332,7 @@
 #endif  // BUILDFLAG(IS_WIN)
   std::optional<SettingsResponse> settings_response = std::nullopt;
   std::optional<OsSignalsResponse> os_signals_response = std::nullopt;
+  std::optional<ProfileSignalsResponse> profile_signals_response = std::nullopt;
 
   std::optional<FileSystemInfoResponse> file_system_info_response =
       std::nullopt;
diff --git a/components/dom_distiller/content/browser/distiller_page_web_contents.cc b/components/dom_distiller/content/browser/distiller_page_web_contents.cc
index f0900d9..a4800b2e 100644
--- a/components/dom_distiller/content/browser/distiller_page_web_contents.cc
+++ b/components/dom_distiller/content/browser/distiller_page_web_contents.cc
@@ -9,6 +9,7 @@
 
 #include "base/functional/bind.h"
 #include "base/functional/callback.h"
+#include "base/metrics/histogram_functions.h"
 #include "base/strings/utf_string_conversions.h"
 #include "components/dom_distiller/content/browser/distiller_javascript_utils.h"
 #include "components/dom_distiller/core/distiller_page.h"
@@ -183,7 +184,8 @@
 
   if (!javascript_start.is_null()) {
     base::TimeDelta javascript_time = base::TimeTicks::Now() - javascript_start;
-    DVLOG(1) << "DomDistiller.Time.RunJavaScript = " << javascript_time;
+    base::UmaHistogramTimes("DomDistiller.Time.RunDistillationJavaScript",
+                            javascript_time);
   }
 
   DistillerPage::OnDistillationDone(page_url, &value);
diff --git a/components/dom_distiller/core/distiller.cc b/components/dom_distiller/core/distiller.cc
index ea672c9..7d15ab5 100644
--- a/components/dom_distiller/core/distiller.cc
+++ b/components/dom_distiller/core/distiller.cc
@@ -15,6 +15,7 @@
 #include "base/functional/callback.h"
 #include "base/location.h"
 #include "base/memory/ptr_util.h"
+#include "base/metrics/histogram_functions.h"
 #include "base/strings/string_number_conversions.h"
 #include "base/strings/utf_string_conversions.h"
 #include "base/task/single_thread_task_runner.h"
@@ -240,6 +241,10 @@
                     distiller_result->content_images(img_num).url());
   }
 
+  base::UmaHistogramCounts100000(
+      "DomDistiller.WordCount",
+      distiller_result->statistics_info().word_count());
+
   AddPageIfDone(page_num);
   DistillNextPage();
 }
diff --git a/components/feed/feed_feature_list.cc b/components/feed/feed_feature_list.cc
index 554a4d8c..5353c65 100644
--- a/components/feed/feed_feature_list.cc
+++ b/components/feed/feed_feature_list.cc
@@ -17,6 +17,10 @@
 
 namespace feed {
 
+const char kFeedHeaderRemovalTreatmentParam[] = "treatment";
+const char kFeedHeaderRemovalTreatmentValue1[] = "label";
+const char kFeedHeaderRemovalTreatmentValue2[] = "none";
+
 // InterestFeedV2 takes precedence over InterestFeedContentSuggestions.
 // InterestFeedV2 is cached in ChromeCachedFlags. If the default value here is
 // changed, please update the cached one's default value in CachedFeatureFlags.
@@ -109,6 +113,10 @@
              "FeedStreaming",
              base::FEATURE_DISABLED_BY_DEFAULT);
 
+BASE_FEATURE(kFeedHeaderRemoval,
+             "FeedHeaderRemoval",
+             base::FEATURE_DISABLED_BY_DEFAULT);
+
 bool IsWebFeedEnabledForLocale(const std::string& country) {
   const std::vector<std::string> launched_countries = {"AU", "CA", "GB",
                                                        "NZ", "US", "ZA"};
diff --git a/components/feed/feed_feature_list.h b/components/feed/feed_feature_list.h
index 92083e2..6b7fd102 100644
--- a/components/feed/feed_feature_list.h
+++ b/components/feed/feed_feature_list.h
@@ -24,6 +24,13 @@
 }
 
 COMPONENT_EXPORT(COMPONENTS_FEED_FEATURE_LIST)
+extern const char kFeedHeaderRemovalTreatmentParam[];
+COMPONENT_EXPORT(COMPONENTS_FEED_FEATURE_LIST)
+extern const char kFeedHeaderRemovalTreatmentValue1[];
+COMPONENT_EXPORT(COMPONENTS_FEED_FEATURE_LIST)
+extern const char kFeedHeaderRemovalTreatmentValue2[];
+
+COMPONENT_EXPORT(COMPONENTS_FEED_FEATURE_LIST)
 BASE_DECLARE_FEATURE(kInterestFeedV2);
 
 // Use the new DiscoFeed endpoint.
@@ -122,6 +129,10 @@
 COMPONENT_EXPORT(COMPONENTS_FEED_FEATURE_LIST)
 BASE_DECLARE_FEATURE(kFeedStreaming);
 
+// Feature that removes feed header.
+COMPONENT_EXPORT(COMPONENTS_FEED_FEATURE_LIST)
+BASE_DECLARE_FEATURE(kFeedHeaderRemoval);
+
 COMPONENT_EXPORT(COMPONENTS_FEED_FEATURE_LIST)
 bool IsWebFeedEnabledForLocale(const std::string& country);
 
diff --git a/components/omnibox/browser/autocomplete_match.cc b/components/omnibox/browser/autocomplete_match.cc
index e67b1fe..05106cc 100644
--- a/components/omnibox/browser/autocomplete_match.cc
+++ b/components/omnibox/browser/autocomplete_match.cc
@@ -114,12 +114,13 @@
 
   constexpr auto kProviderPrefMap =
       base::MakeFixedFlatMap<AutocompleteProvider::Type, int>({
-          // Prefer live document suggestions. We check provider type instead
+          // Prefer live remote suggestions. We check provider type instead
           // of match type in order to distinguish live suggestions from the
-          // document provider from stale suggestions from the shortcuts
-          // providers, because the latter omits changing metadata such as last
-          // access date.
+          // from their stale counterparts from the shortcut, history, and
+          // bookmark providers. The latter often have stale metadata such as
+          // last access date.
           {AutocompleteProvider::TYPE_DOCUMENT, 2},
+          {AutocompleteProvider::TYPE_ENTERPRISE_SEARCH_AGGREGATOR, 2},
           // Prefer bookmark suggestions, as:
           // 1) Their titles may be explicitly set.
           // 2) They may display enhanced information such as the bookmark
@@ -145,7 +146,8 @@
 size_t ACMatchKeyHash<Args...>::operator()(
     const ACMatchKey<Args...>& key) const {
   size_t seed = 0;
-  // Compute a hash by applying `HashCombine` to each element of the "key" tuple.
+  // Compute a hash by applying `HashCombine` to each element of the "key"
+  // tuple.
   std::apply(
       [&seed](auto&&... args) { ((base::HashCombine(seed, args)), ...); }, key);
   return seed;
@@ -1875,6 +1877,11 @@
         duplicate_match.description_class_for_shortcuts;
     swap_contents_and_description =
         duplicate_match.swap_contents_and_description;
+
+    // Image data should stay in sync with the suggestion text.
+    image_dominant_color = duplicate_match.image_dominant_color;
+    image_url = duplicate_match.image_url;
+    icon_url = duplicate_match.icon_url;
   }
 
   // Copy `rich_autocompletion_triggered` for counterfactual logging.
diff --git a/components/omnibox/common/omnibox_feature_configs.cc b/components/omnibox/common/omnibox_feature_configs.cc
index d559ce1..28d00ea 100644
--- a/components/omnibox/common/omnibox_feature_configs.cc
+++ b/components/omnibox/common/omnibox_feature_configs.cc
@@ -110,6 +110,9 @@
   disable_drive = base::FeatureParam<bool>(&kSearchAggregatorProvider,
                                            "disable_drive", true)
                       .Get();
+  multiple_requests = base::FeatureParam<bool>(&kSearchAggregatorProvider,
+                                               "multiple_requests", false)
+                          .Get();
 
   scoring_max_matches_created_per_type =
       base::FeatureParam<size_t>(&kSearchAggregatorProvider,
diff --git a/components/omnibox/common/omnibox_feature_configs.h b/components/omnibox/common/omnibox_feature_configs.h
index 3abd31c5..17125b2 100644
--- a/components/omnibox/common/omnibox_feature_configs.h
+++ b/components/omnibox/common/omnibox_feature_configs.h
@@ -193,6 +193,9 @@
   // provider will run unscoped. Either way, doc provider won't run when in the
   // enterprise scope.
   bool disable_drive;
+  // If true, the `EnterpriseSearchAggregatorSuggestionsService` will make
+  // parallel requests for each type of suggestion.
+  bool multiple_requests;
 
   // See comments in enterprise_search_aggregator_provider.cc
   size_t scoring_max_matches_created_per_type;
diff --git a/components/optimization_guide/core/bloom_filter.cc b/components/optimization_guide/core/bloom_filter.cc
index ef284b11..755907be 100644
--- a/components/optimization_guide/core/bloom_filter.cc
+++ b/components/optimization_guide/core/bloom_filter.cc
@@ -2,17 +2,13 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifdef UNSAFE_BUFFERS_BUILD
-// TODO(crbug.com/390223051): Remove C-library calls to fix the errors.
-#pragma allow_unsafe_libc_calls
-#endif
-
 #include "components/optimization_guide/core/bloom_filter.h"
 
 #include <stddef.h>
 #include <stdint.h>
 
 #include "base/check_op.h"
+#include "base/containers/span.h"
 #include "third_party/smhasher/src/src/MurmurHash3.h"
 
 namespace optimization_guide {
@@ -33,19 +29,13 @@
 }  // namespace
 
 BloomFilter::BloomFilter(uint32_t num_hash_functions, uint32_t num_bits)
-
-    : num_hash_functions_(num_hash_functions),
-      num_bits_(num_bits),
-      bytes_(((num_bits + 7) / 8), 0) {
-  // May be created on one thread but used on another. The first call to
-  // CalledOnValidSequence() will re-bind it.
-  DETACH_FROM_SEQUENCE(sequence_checker_);
-}
+    : BloomFilter(num_hash_functions,
+                  num_bits,
+                  std::string(((num_bits + 7) / 8), 0)) {}
 
 BloomFilter::BloomFilter(uint32_t num_hash_functions,
                          uint32_t num_bits,
-                         std::string filter_data)
-
+                         const std::string& filter_data)
     : num_hash_functions_(num_hash_functions),
       num_bits_(num_bits),
       bytes_(filter_data.size()) {
@@ -53,7 +43,7 @@
   // CalledOnValidSequence() will re-bind it.
   DETACH_FROM_SEQUENCE(sequence_checker_);
   CHECK_GE(filter_data.size() * 8, num_bits);
-  memcpy(&bytes_[0], filter_data.data(), filter_data.size());
+  base::span(bytes_).copy_from(base::as_byte_span(filter_data));
 }
 
 BloomFilter::~BloomFilter() = default;
@@ -64,8 +54,9 @@
     uint64_t n = MurmurHash3(str, i) % num_bits_;
     uint32_t byte_index = (n / 8);
     uint32_t bit_index = n % 8;
-    if ((bytes_[byte_index] & (1 << bit_index)) == 0)
+    if ((bytes_[byte_index] & (1 << bit_index)) == 0) {
       return false;
+    }
   }
   return true;
 }
diff --git a/components/optimization_guide/core/bloom_filter.h b/components/optimization_guide/core/bloom_filter.h
index b06a48c5..b62dea1 100644
--- a/components/optimization_guide/core/bloom_filter.h
+++ b/components/optimization_guide/core/bloom_filter.h
@@ -30,7 +30,7 @@
   // type) and using |num_hash_functions| per entry.
   BloomFilter(uint32_t num_hash_functions,
               uint32_t num_bits,
-              std::string filter_data);
+              const std::string& filter_data);
 
   BloomFilter(const BloomFilter&) = delete;
   BloomFilter& operator=(const BloomFilter&) = delete;
diff --git a/components/optimization_guide/core/hints_processing_util.cc b/components/optimization_guide/core/hints_processing_util.cc
index 50fa81a7..6ff3609 100644
--- a/components/optimization_guide/core/hints_processing_util.cc
+++ b/components/optimization_guide/core/hints_processing_util.cc
@@ -179,6 +179,8 @@
       return "GlicContextualCueing";
     case proto::OptimizationType::GLIC_ZERO_STATE_SUGGESTIONS:
       return "GlicZeroStateSuggestions";
+    case proto::OptimizationType::GLIC_ACTION_PAGE_BLOCK:
+      return "GlicActionPageBlock";
   }
 
   // The returned string is used to record histograms for the optimization type.
diff --git a/components/optimization_guide/core/mock_optimization_guide_model_executor.cc b/components/optimization_guide/core/mock_optimization_guide_model_executor.cc
index 636b1df7..593c9c0 100644
--- a/components/optimization_guide/core/mock_optimization_guide_model_executor.cc
+++ b/components/optimization_guide/core/mock_optimization_guide_model_executor.cc
@@ -61,6 +61,12 @@
       .WillByDefault([impl](const auto& input, auto callback) {
         impl->ExecuteModel(input, std::move(callback));
       });
+  ON_CALL(*this, ExecuteModelWithResponseJsonSchema)
+      .WillByDefault([impl](const auto& input, const auto& response_json_schema,
+                            auto callback) {
+        impl->ExecuteModelWithResponseJsonSchema(input, response_json_schema,
+                                                 std::move(callback));
+      });
   ON_CALL(*this, GetSizeInTokens)
       .WillByDefault([impl](const auto& input, auto callback) {
         impl->GetSizeInTokens(input, std::move(callback));
diff --git a/components/optimization_guide/core/mock_optimization_guide_model_executor.h b/components/optimization_guide/core/mock_optimization_guide_model_executor.h
index 2f7a8cda..5511972 100644
--- a/components/optimization_guide/core/mock_optimization_guide_model_executor.h
+++ b/components/optimization_guide/core/mock_optimization_guide_model_executor.h
@@ -73,6 +73,12 @@
       ExecuteModel,
       (const google::protobuf::MessageLite& request_metadata,
        OptimizationGuideModelExecutionResultStreamingCallback callback));
+  MOCK_METHOD(
+      void,
+      ExecuteModelWithResponseJsonSchema,
+      (const google::protobuf::MessageLite& request_metadata,
+       const std::optional<std::string>& response_json_schema,
+       OptimizationGuideModelExecutionResultStreamingCallback callback));
   MOCK_METHOD(void,
               GetSizeInTokens,
               (const std::string& text,
diff --git a/components/optimization_guide/core/model_execution/on_device_execution.cc b/components/optimization_guide/core/model_execution/on_device_execution.cc
index d1f520e..035bd68f 100644
--- a/components/optimization_guide/core/model_execution/on_device_execution.cc
+++ b/components/optimization_guide/core/model_execution/on_device_execution.cc
@@ -121,6 +121,7 @@
     OnDeviceOptions opts,
     ExecuteRemoteFn execute_remote_fn,
     MultimodalMessage message,
+    std::optional<std::string> response_json_schema,
     std::unique_ptr<ResultLogger> logger,
     OptimizationGuideModelExecutionResultStreamingCallback callback,
     base::OnceCallback<void(bool)> cleanup_callback)
@@ -128,6 +129,7 @@
       opts_(std::move(opts)),
       execute_remote_fn_(execute_remote_fn),
       last_message_(std::move(message)),
+      response_json_schema_(std::move(response_json_schema)),
       histogram_logger_(std::move(logger)),
       callback_(std::move(callback)),
       cleanup_callback_(std::move(cleanup_callback)) {
@@ -210,6 +212,7 @@
 
   auto options = on_device_model::mojom::GenerateOptions::New();
   options->max_output_tokens = opts_.token_limits.max_output_tokens;
+  options->response_json_schema = std::move(response_json_schema_);
 
   opts_.safety_checker->RunRequestChecks(
       last_message_,
diff --git a/components/optimization_guide/core/model_execution/on_device_execution.h b/components/optimization_guide/core/model_execution/on_device_execution.h
index 23e8c2e5..826b352 100644
--- a/components/optimization_guide/core/model_execution/on_device_execution.h
+++ b/components/optimization_guide/core/model_execution/on_device_execution.h
@@ -120,6 +120,7 @@
       OnDeviceOptions opts,
       ExecuteRemoteFn execute_remote_fn,
       MultimodalMessage message,
+      std::optional<std::string> response_json_schema,
       std::unique_ptr<ResultLogger> logger,
       OptimizationGuideModelExecutionResultStreamingCallback callback,
       base::OnceCallback<void(bool)> cleanup_callback);
@@ -228,6 +229,8 @@
 
   // The request message.
   MultimodalMessage last_message_;
+  // A JSON schema defining structured output requirements for the response.
+  std::optional<std::string> response_json_schema_;
   // Time ExecuteModel() was called.
   base::TimeTicks start_;
   // Used to log the result of ExecuteModel().
diff --git a/components/optimization_guide/core/model_execution/session_impl.cc b/components/optimization_guide/core/model_execution/session_impl.cc
index 0f236415..728df13 100644
--- a/components/optimization_guide/core/model_execution/session_impl.cc
+++ b/components/optimization_guide/core/model_execution/session_impl.cc
@@ -176,6 +176,16 @@
     const google::protobuf::MessageLite& request_metadata,
     optimization_guide::OptimizationGuideModelExecutionResultStreamingCallback
         callback) {
+  ExecuteModelWithResponseJsonSchema(request_metadata,
+                                     /*response_json_schema=*/std::nullopt,
+                                     std::move(callback));
+}
+
+void SessionImpl::ExecuteModelWithResponseJsonSchema(
+    const google::protobuf::MessageLite& request_metadata,
+    const std::optional<std::string>& response_json_schema,
+    optimization_guide::OptimizationGuideModelExecutionResultStreamingCallback
+        callback) {
   auto logger = std::make_unique<OnDeviceExecution::ResultLogger>(feature_);
 
   // Compute the amount of time context would have for processing assuming
@@ -212,7 +222,8 @@
   // Set new pending response.
   on_device_execution_.emplace(
       feature_, on_device_context_->opts(), execute_remote_fn_,
-      std::move(merged_request), std::move(logger), std::move(callback),
+      std::move(merged_request), std::move(response_json_schema),
+      std::move(logger), std::move(callback),
       base::BindOnce(&SessionImpl::OnDeviceExecutionTerminated,
                      weak_ptr_factory_.GetWeakPtr()));
 
diff --git a/components/optimization_guide/core/model_execution/session_impl.h b/components/optimization_guide/core/model_execution/session_impl.h
index 209d56f74..b30eebe 100644
--- a/components/optimization_guide/core/model_execution/session_impl.h
+++ b/components/optimization_guide/core/model_execution/session_impl.h
@@ -72,6 +72,10 @@
   void ExecuteModel(
       const google::protobuf::MessageLite& request_metadata,
       OptimizationGuideModelExecutionResultStreamingCallback callback) override;
+  void ExecuteModelWithResponseJsonSchema(
+      const google::protobuf::MessageLite& request_metadata,
+      const std::optional<std::string>& response_json_schema,
+      OptimizationGuideModelExecutionResultStreamingCallback callback) override;
   void GetSizeInTokens(
       const std::string& text,
       OptimizationGuideModelSizeInTokenCallback callback) override;
diff --git a/components/optimization_guide/core/optimization_guide_model_executor.h b/components/optimization_guide/core/optimization_guide_model_executor.h
index f1a44f4b0f..729b0bf 100644
--- a/components/optimization_guide/core/optimization_guide_model_executor.h
+++ b/components/optimization_guide/core/optimization_guide_model_executor.h
@@ -275,6 +275,13 @@
         const google::protobuf::MessageLite& request_metadata,
         OptimizationGuideModelExecutionResultStreamingCallback callback) = 0;
 
+    // A JSON schema is provided to define structured output requirements for
+    // the response.
+    virtual void ExecuteModelWithResponseJsonSchema(
+        const google::protobuf::MessageLite& request_metadata,
+        const std::optional<std::string>& response_json_schema,
+        OptimizationGuideModelExecutionResultStreamingCallback callback) = 0;
+
     // Call `GetSizeInTokens()` from the model to get the size of the given text
     // in tokens. The result will be passed back through the callback.
     virtual void GetSizeInTokens(
diff --git a/components/optimization_guide/proto/hints.proto b/components/optimization_guide/proto/hints.proto
index 5ba0183..8577a4c9 100644
--- a/components/optimization_guide/proto/hints.proto
+++ b/components/optimization_guide/proto/hints.proto
@@ -320,6 +320,8 @@
   // Provides information about the contextual zero state suggestions for a
   // page.
   GLIC_ZERO_STATE_SUGGESTIONS = 91;
+  // Provides information about whether GLIC may act on a page.
+  GLIC_ACTION_PAGE_BLOCK = 92;
 }
 
 // Presents semantics for how page load URLs should be matched.
diff --git a/components/visited_url_ranking/internal/url_grouping/grouping_heuristics.cc b/components/visited_url_ranking/internal/url_grouping/grouping_heuristics.cc
index 34b3407..f0ea094 100644
--- a/components/visited_url_ranking/internal/url_grouping/grouping_heuristics.cc
+++ b/components/visited_url_ranking/internal/url_grouping/grouping_heuristics.cc
@@ -129,7 +129,13 @@
         URLVisitAggregateRankingModelInputSignals::kTabParentId);
     const char* tab_group_sync_id_input = GetNameForInput(
         URLVisitAggregateRankingModelInputSignals::kTabGroupSyncId);
+    const char* tab_id_input =
+        GetNameForInput(URLVisitAggregateRankingModelInputSignals::kTabId);
 
+    std::unordered_map<float, float> tab_id_to_parent_id_map;
+    tab_id_to_parent_id_map.reserve(inputs.size());
+    std::unordered_map<float, unsigned> tab_id_to_tab_index_map;
+    tab_id_to_tab_index_map.reserve(inputs.size());
     for (unsigned i = 0; i < inputs.size(); ++i) {
       std::optional<ProcessedValue> tab_opened_by_user =
           inputs[i]->GetMetadataArgument(tab_opened_by_user_input);
@@ -141,6 +147,8 @@
           inputs[i]->GetMetadataArgument(tab_parent_id_input);
       std::optional<ProcessedValue> tab_group_sync_id =
           inputs[i]->GetMetadataArgument(tab_group_sync_id_input);
+      std::optional<ProcessedValue> tab_id =
+          inputs[i]->GetMetadataArgument(tab_id_input);
 
       if (!tab_opened_by_user || tab_opened_by_user->float_val == 0) {
         // Do not group tabs not opened by user.
@@ -156,11 +164,37 @@
         result[i] = base::FastHash(tab_launch_package_name->str_val);
         continue;
       }
-      if (tab_parent_id) {
-        result[i] = tab_parent_id->float_val;
+      if (tab_parent_id && tab_id) {
+        tab_id_to_parent_id_map[tab_id->float_val] = tab_parent_id->float_val;
+        tab_id_to_tab_index_map[tab_id->float_val] = i;
         continue;
       }
-      // TODO(ssid): Reconsider grouping based on launch types.
+    }
+    // Cluster tabs based on parent tab relationship by finding disjoint sets in
+    // the tab-parent DAG.
+    // A bool to track whether there are any cluster merge happen in each round.
+    bool merged = true;
+    while (merged) {
+      merged = false;
+      std::unordered_map<float, float> new_tab_id_to_parent_id_map;
+      new_tab_id_to_parent_id_map.reserve(tab_id_to_parent_id_map.size());
+      for (const auto& pair : tab_id_to_parent_id_map) {
+        float tab_id = pair.first;
+        float parent_id = pair.second;
+        if (base::Contains(tab_id_to_parent_id_map, parent_id) &&
+            tab_id_to_parent_id_map[parent_id] != parent_id) {
+          new_tab_id_to_parent_id_map[tab_id] =
+              tab_id_to_parent_id_map[parent_id];
+          // Keep track of merge.
+          merged = true;
+        } else {
+          new_tab_id_to_parent_id_map[tab_id] = parent_id;
+        }
+      }
+      tab_id_to_parent_id_map.swap(new_tab_id_to_parent_id_map);
+    }
+    for (const auto& pair : tab_id_to_tab_index_map) {
+      result[pair.second] = tab_id_to_parent_id_map[pair.first];
     }
     return result;
   }
diff --git a/components/visited_url_ranking/internal/url_grouping/grouping_heuristics_unittest.cc b/components/visited_url_ranking/internal/url_grouping/grouping_heuristics_unittest.cc
index 605ceda..36e6689 100644
--- a/components/visited_url_ranking/internal/url_grouping/grouping_heuristics_unittest.cc
+++ b/components/visited_url_ranking/internal/url_grouping/grouping_heuristics_unittest.cc
@@ -405,4 +405,42 @@
   ASSERT_FALSE(suggestions.has_value());
 }
 
+TEST_F(GroupingHeuristicsTest, SimilarSourceHeuristic_SameParentTabCluster) {
+  std::vector<URLVisitAggregate> candidates = {};
+
+  // The parent tab relationship for the below 6 tabs are
+  // 111->112
+  // (112, 113) -> 114
+  // 115 -> 116
+  // (111, 112, 113, 114) will be clustered.
+  candidates.push_back(CreateVisitForTab(base::Seconds(60), 111));
+  GetTabMetadata(candidates[0]).parent_tab_id = 112;
+
+  candidates.push_back(CreateVisitForTab(base::Seconds(250), 112));
+  GetTabMetadata(candidates[1]).parent_tab_id = 114;
+
+  candidates.push_back(CreateVisitForTab(base::Seconds(350), 113));
+  GetTabMetadata(candidates[2]).parent_tab_id = 114;
+
+  candidates.push_back(CreateVisitForTab(base::Seconds(800), 114));
+  GetTabMetadata(candidates[3]).parent_tab_id = 114;
+
+  // Not clustered since parent ID is different.
+  candidates.push_back(CreateVisitForTab(base::Seconds(350), 115));
+  GetTabMetadata(candidates[4]).parent_tab_id = 116;
+
+  candidates.push_back(CreateVisitForTab(base::Seconds(800), 116));
+  GetTabMetadata(candidates[4]).parent_tab_id = 116;
+
+  std::optional<GroupSuggestions> suggestions = GetSuggestionsFor(
+      std::move(candidates), GroupSuggestion::SuggestionReason::kSimilarSource);
+
+  ASSERT_TRUE(suggestions.has_value());
+  ASSERT_EQ(1u, suggestions->suggestions.size());
+  const auto& suggestion = suggestions->suggestions[0];
+  EXPECT_EQ(GroupSuggestion::SuggestionReason::kSimilarSource,
+            suggestion.suggestion_reason);
+  EXPECT_THAT(suggestion.tab_ids, ElementsAre(111, 112, 113, 114));
+}
+
 }  // namespace visited_url_ranking
diff --git a/components/visited_url_ranking/public/url_visit_schema.cc b/components/visited_url_ranking/public/url_visit_schema.cc
index 8f5aab0..d253c9f 100644
--- a/components/visited_url_ranking/public/url_visit_schema.cc
+++ b/components/visited_url_ranking/public/url_visit_schema.cc
@@ -43,6 +43,7 @@
 const char kSignalTabParentId[] = "tab_parent_id";
 const char kSignalTimeSinceTabCreationSec[] = "time_since_tab_creation_sec";
 const char kSignalTabGroupSyncId[] = "tab_group_sync_id";
+const char kSignalTabId[] = "tab_id";
 
 constexpr std::array<FieldSchema, kTabResumptionNumInputs>
     kURLVisitAggregateSchema = {{
@@ -138,6 +139,8 @@
          .name = kSignalTimeSinceTabCreationSec},
         {.signal = URLVisitAggregateRankingModelInputSignals::kTabGroupSyncId,
          .name = kSignalTabGroupSyncId},
+        {.signal = URLVisitAggregateRankingModelInputSignals::kTabId,
+         .name = kSignalTabId},
     }};
 
 }  // namespace visited_url_ranking
diff --git a/components/visited_url_ranking/public/url_visit_schema.h b/components/visited_url_ranking/public/url_visit_schema.h
index 10da682..e21b9dc7 100644
--- a/components/visited_url_ranking/public/url_visit_schema.h
+++ b/components/visited_url_ranking/public/url_visit_schema.h
@@ -44,6 +44,7 @@
   kTabParentId = 29,
   kTimeSinceTabCreationSec = 30,
   kTabGroupSyncId = 31,
+  kTabId = 32,
 };
 
 // Represents a field's metadata and is leveraged for the processing and
@@ -62,7 +63,7 @@
     kURLVisitAggregateSchema;
 
 // Collection of relevant fields for URL grouping computation.
-static constexpr size_t kSuggestionsNumInputs = 9;
+static constexpr size_t kSuggestionsNumInputs = 10;
 extern const std::array<FieldSchema, kSuggestionsNumInputs>
     kSuggestionsPredictionSchema;
 
diff --git a/components/visited_url_ranking/public/url_visit_util.cc b/components/visited_url_ranking/public/url_visit_util.cc
index c10f82c..8f38189 100644
--- a/components/visited_url_ranking/public/url_visit_util.cc
+++ b/components/visited_url_ranking/public/url_visit_util.cc
@@ -330,8 +330,6 @@
         break;
       case kTabParentId:
         if (tab_data) {
-          // TODO(crbug.com/397221723): Add a field for tab ID to trace tab
-          // relationship beyond parent.
           value = ProcessedValue::FromFloat(
               tab_data->last_active_tab.tab_metadata.parent_tab_id == -1
                   ? tab_data->last_active_tab.id
@@ -355,6 +353,11 @@
                                      .local_tab_group_id->ToString());
         }
         break;
+      case kTabId:
+        if (tab_data) {
+          value = ProcessedValue::FromFloat(tab_data->last_active_tab.id);
+        }
+        break;
     }
 
     signal_value_map.emplace(field_schema.name, std::move(value));
diff --git a/components/webcrypto/algorithms/ed25519.cc b/components/webcrypto/algorithms/ed25519.cc
index 3508781..f9b164d 100644
--- a/components/webcrypto/algorithms/ed25519.cc
+++ b/components/webcrypto/algorithms/ed25519.cc
@@ -302,14 +302,12 @@
   JwkReader jwk;
 
   // 3. If the kty field of jwk is not "OKP", then throw a DataError.
-  // 5. If the alg field of jwk is present and is not "EdDSA", then throw a
-  // DataError.
   // 7. If the key_ops field of jwk is present, and is invalid according to the
   // requirements of JSON Web Key [JWK], or it does not contain all of the
   // specified usages values, then throw a DataError.
   // 8. If the ext field of jwk is present and has the value false and
   // extractable is true, then throw a DataError.
-  Status status = jwk.Init(key_data, extractable, usages, "OKP", "EdDSA");
+  Status status = jwk.Init(key_data, extractable, usages, "OKP", "");
   if (status.IsError())
     return status;
 
@@ -321,6 +319,18 @@
   if (jwk_crv != "Ed25519")
     return Status::ErrorJwkIncorrectCrv();
 
+  // 5. If the alg field of jwk is present and is not "EdDSA", then throw a
+  // DataError.
+  bool has_alg;
+  std::string jwk_alg;
+  status = jwk.GetAlg(&jwk_alg, &has_alg);
+  if (status.IsError()) {
+    return status;
+  }
+  if (has_alg && jwk_alg != "EdDSA" && jwk_alg != "Ed25519") {
+    return Status::ErrorJwkAlgorithmInconsistent();
+  }
+
   // Only private keys have a "d" parameter. The key may still be invalid, but
   // tentatively decide if it is a public or private key.
   bool is_private_key = jwk.HasMember("d");
@@ -422,9 +432,9 @@
     return Status::OperationError();
   DCHECK_EQ(keylen, sizeof(raw_public_key));
 
-  // No "alg" is set for OKP keys.
   JwkWriter jwk(std::string(), key.Extractable(), key.Usages(), "OKP");
   jwk.SetString("crv", "Ed25519");
+  jwk.SetString("alg", "Ed25519");
 
   // Set "x", and "d" if it is a private key.
   jwk.SetBytes("x", raw_public_key);
diff --git a/components/webxr/android/openxr_platform_helper_android.cc b/components/webxr/android/openxr_platform_helper_android.cc
index 7f67aee7..47214e9 100644
--- a/components/webxr/android/openxr_platform_helper_android.cc
+++ b/components/webxr/android/openxr_platform_helper_android.cc
@@ -27,7 +27,8 @@
 
 std::unique_ptr<device::OpenXrGraphicsBinding>
 OpenXrPlatformHelperAndroid::GetGraphicsBinding() {
-  return std::make_unique<device::OpenXrGraphicsBindingOpenGLES>();
+  return std::make_unique<device::OpenXrGraphicsBindingOpenGLES>(
+      GetExtensionEnumeration());
 }
 
 void OpenXrPlatformHelperAndroid::GetPlatformCreateInfo(
diff --git a/content/browser/ai/echo_ai_language_model.cc b/content/browser/ai/echo_ai_language_model.cc
index cd335cc..8f92148a 100644
--- a/content/browser/ai/echo_ai_language_model.cc
+++ b/content/browser/ai/echo_ai_language_model.cc
@@ -60,6 +60,7 @@
 
 void EchoAILanguageModel::Prompt(
     std::vector<blink::mojom::AILanguageModelPromptPtr> prompts,
+    const std::optional<std::string>& response_json_schema,
     mojo::PendingRemote<blink::mojom::ModelStreamingResponder>
         pending_responder) {
   if (is_destroyed_) {
diff --git a/content/browser/ai/echo_ai_language_model.h b/content/browser/ai/echo_ai_language_model.h
index f81a112..2b5a719 100644
--- a/content/browser/ai/echo_ai_language_model.h
+++ b/content/browser/ai/echo_ai_language_model.h
@@ -25,6 +25,7 @@
 
   // `blink::mojom::AILanguageModel` implementation.
   void Prompt(std::vector<blink::mojom::AILanguageModelPromptPtr> prompts,
+              const std::optional<std::string>& response_json_schema,
               mojo::PendingRemote<blink::mojom::ModelStreamingResponder>
                   pending_responder) override;
   void Fork(
diff --git a/content/browser/btm/btm_test_utils.cc b/content/browser/btm/btm_test_utils.cc
index a3daef1..701d9b99 100644
--- a/content/browser/btm/btm_test_utils.cc
+++ b/content/browser/btm/btm_test_utils.cc
@@ -526,8 +526,10 @@
 }
 
 PausedCookieAccessObservers::PausedCookieAccessObservers(
-    NotifyCookiesAccessedCallback callback)
-    : CookieAccessObservers(std::move(callback)) {}
+    NotifyCookiesAccessedCallback callback,
+    PendingObserversWithContext observers)
+    : CookieAccessObservers(std::move(callback)),
+      pending_receivers_(std::move(observers)) {}
 
 PausedCookieAccessObservers::~PausedCookieAccessObservers() = default;
 
@@ -537,16 +539,9 @@
   pending_receivers_.emplace_back(std::move(receiver), source);
 }
 
-std::vector<mojo::PendingReceiver<network::mojom::CookieAccessObserver>>
-PausedCookieAccessObservers::TakeReceivers() {
-  std::vector<mojo::PendingReceiver<network::mojom::CookieAccessObserver>>
-      pending_receivers;
-  for (auto& [pending_receiver, source] :
-       std::exchange(pending_receivers_, {})) {
-    pending_receivers.push_back(std::move(pending_receiver));
-  }
-
-  return pending_receivers;
+PausedCookieAccessObservers::PendingObserversWithContext
+PausedCookieAccessObservers::TakeReceiversWithContext() {
+  return std::exchange(pending_receivers_, {});
 }
 
 CookieAccessInterceptor::CookieAccessInterceptor(WebContents& web_contents)
@@ -562,15 +557,8 @@
       base::BindRepeating(&NavigationRequest::NotifyCookiesAccessed,
                           // Unretained is safe here because ownership of the
                           // observers is passed to the request below.
-                          base::Unretained(&request)));
-  for (auto& receiver : request.TakeCookieObservers()) {
-    // Since we're taking the observers from the NavigationHandle we can assume
-    // the source is kNavigation.
-    // TODO: crbug.com/394059601 - Replace with TakeReceiversWithContext() once
-    // implemented.
-    observers->Add(std::move(receiver),
-                   CookieAccessDetails::Source::kNavigation);
-  }
+                          base::Unretained(&request)),
+      request.TakeCookieObservers());
   request.SetCookieAccessObserversForTesting(std::move(observers));
 }
 
diff --git a/content/browser/btm/btm_test_utils.h b/content/browser/btm/btm_test_utils.h
index a974274..3110a12 100644
--- a/content/browser/btm/btm_test_utils.h
+++ b/content/browser/btm/btm_test_utils.h
@@ -26,6 +26,7 @@
 #include "content/public/browser/web_contents.h"
 #include "content/public/browser/web_contents_observer.h"
 #include "content/public/test/browser_test_utils.h"
+#include "services/network/public/mojom/cookie_access_observer.mojom.h"
 #include "url/gurl.h"
 
 namespace testing {
@@ -391,21 +392,18 @@
 // them.
 class PausedCookieAccessObservers : public CookieAccessObservers {
  public:
-  explicit PausedCookieAccessObservers(NotifyCookiesAccessedCallback callback);
+  explicit PausedCookieAccessObservers(NotifyCookiesAccessedCallback callback,
+                                       PendingObserversWithContext observers);
   ~PausedCookieAccessObservers() override;
 
   // CookieAccessObservers
   void Add(mojo::PendingReceiver<network::mojom::CookieAccessObserver> receiver,
            CookieAccessDetails::Source source) override;
-  std::vector<mojo::PendingReceiver<network::mojom::CookieAccessObserver>>
-  TakeReceivers() override;
+  PendingObserversWithContext TakeReceiversWithContext() override;
 
  private:
   // Holds existing and new receivers.
-  std::vector<
-      std::pair<mojo::PendingReceiver<network::mojom::CookieAccessObserver>,
-                CookieAccessDetails::Source>>
-      pending_receivers_;
+  PendingObserversWithContext pending_receivers_;
 };
 
 // Class used to pause all cookie access notifications in a WebContents.
diff --git a/content/browser/devtools/dedicated_worker_devtools_agent_host.cc b/content/browser/devtools/dedicated_worker_devtools_agent_host.cc
index 063ad900..78e03b1 100644
--- a/content/browser/devtools/dedicated_worker_devtools_agent_host.cc
+++ b/content/browser/devtools/dedicated_worker_devtools_agent_host.cc
@@ -23,9 +23,6 @@
 // static
 void DedicatedWorkerDevToolsAgentHost::AddAllAgentHosts(
     DevToolsAgentHost::List* result) {
-  if (!base::FeatureList::IsEnabled(blink::features::kPlzDedicatedWorker)) {
-    return;
-  }
   WorkerDevToolsManager::GetInstance().AddAllAgentHosts(result);
 }
 
diff --git a/content/browser/devtools/devtools_renderer_channel.cc b/content/browser/devtools/devtools_renderer_channel.cc
index 782b7e3..f1bfff9 100644
--- a/content/browser/devtools/devtools_renderer_channel.cc
+++ b/content/browser/devtools/devtools_renderer_channel.cc
@@ -174,10 +174,9 @@
   process->FilterURL(true /* empty_allowed */, &filtered_url);
 
   if (context_type ==
-          blink::mojom::DevToolsExecutionContextType::kDedicatedWorker &&
-      base::FeatureList::IsEnabled(blink::features::kPlzDedicatedWorker)) {
-    // WorkerDevToolsAgentHost for dedicated workers is already created on the
-    // browser process when PlzDedicatedWorker is enabled.
+          blink::mojom::DevToolsExecutionContextType::kDedicatedWorker) {
+    // WorkerDevToolsAgentHost for dedicated workers is already created in the
+    // browser process.
     DCHECK(
         content::DevToolsAgentHost::GetForId(devtools_worker_token.ToString()));
     scoped_refptr<DedicatedWorkerDevToolsAgentHost> agent_host =
@@ -217,14 +216,8 @@
           base::BindOnce(&DevToolsRendererChannel::ChildTargetDestroyed,
                          weak_factory_.GetWeakPtr()));
       break;
-    case blink::mojom::DevToolsExecutionContextType::kDedicatedWorker:
-      CHECK(
-          !base::FeatureList::IsEnabled(blink::features::kPlzDedicatedWorker));
-      agent_host = base::MakeRefCounted<DedicatedWorkerDevToolsAgentHost>(
-          process_id_, filtered_url, std::move(name), devtools_worker_token,
-          owner_->GetId(),
-          base::BindOnce(&DevToolsRendererChannel::ChildTargetDestroyed,
-                         weak_factory_.GetWeakPtr()));
+      case blink::mojom::DevToolsExecutionContextType::kDedicatedWorker:
+      NOTREACHED();
   }
   agent_host->SetRenderer(process_id_, std::move(worker_devtools_agent),
                           std::move(host_receiver));
diff --git a/content/browser/devtools/worker_devtools_manager.cc b/content/browser/devtools/worker_devtools_manager.cc
index ef36098..ec2af8b 100644
--- a/content/browser/devtools/worker_devtools_manager.cc
+++ b/content/browser/devtools/worker_devtools_manager.cc
@@ -10,14 +10,12 @@
 #include "content/browser/devtools/devtools_instrumentation.h"
 #include "content/browser/worker_host/dedicated_worker_host.h"
 #include "content/public/browser/browser_thread.h"
-#include "third_party/blink/public/common/features.h"
 
 namespace content {
 
 // static
 WorkerDevToolsManager& WorkerDevToolsManager::GetInstance() {
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
-  CHECK(base::FeatureList::IsEnabled(blink::features::kPlzDedicatedWorker));
   return *base::Singleton<WorkerDevToolsManager>::get();
 }
 
diff --git a/content/browser/devtools/worker_or_worklet_devtools_agent_host.cc b/content/browser/devtools/worker_or_worklet_devtools_agent_host.cc
index 5738a6ca..4081f97 100644
--- a/content/browser/devtools/worker_or_worklet_devtools_agent_host.cc
+++ b/content/browser/devtools/worker_or_worklet_devtools_agent_host.cc
@@ -10,7 +10,6 @@
 #include "content/browser/renderer_host/render_process_host_impl.h"
 #include "content/browser/storage_partition_impl.h"
 #include "content/public/browser/child_process_host.h"
-#include "third_party/blink/public/common/features.h"
 
 namespace content {
 
@@ -56,8 +55,6 @@
     const GURL& url,
     const std::string& name,
     base::OnceCallback<void(DevToolsAgentHostImpl*)> callback) {
-  DCHECK(base::FeatureList::IsEnabled(blink::features::kPlzDedicatedWorker));
-
   url_ = url;
   name_ = name;
   destroyed_callback_ = std::move(callback);
diff --git a/content/browser/renderer_host/cookie_access_observers.cc b/content/browser/renderer_host/cookie_access_observers.cc
index 22d68b69..2be4ac5 100644
--- a/content/browser/renderer_host/cookie_access_observers.cc
+++ b/content/browser/renderer_host/cookie_access_observers.cc
@@ -20,9 +20,9 @@
   cookie_observer_set_.Add(this, std::move(receiver), source);
 }
 
-std::vector<mojo::PendingReceiver<network::mojom::CookieAccessObserver>>
-CookieAccessObservers::TakeReceivers() {
-  return cookie_observer_set_.TakeReceivers();
+CookieAccessObservers::PendingObserversWithContext
+CookieAccessObservers::TakeReceiversWithContext() {
+  return cookie_observer_set_.TakeReceiversWithContext();
 }
 
 void CookieAccessObservers::OnCookiesAccessed(
diff --git a/content/browser/renderer_host/cookie_access_observers.h b/content/browser/renderer_host/cookie_access_observers.h
index 996e31a..b2f1c73b 100644
--- a/content/browser/renderer_host/cookie_access_observers.h
+++ b/content/browser/renderer_host/cookie_access_observers.h
@@ -36,9 +36,10 @@
       mojo::PendingReceiver<network::mojom::CookieAccessObserver> receiver,
       CookieAccessDetails::Source source);
 
-  virtual std::vector<
-      mojo::PendingReceiver<network::mojom::CookieAccessObserver>>
-  TakeReceivers();
+  using PendingObserversWithContext = std::vector<
+      std::pair<mojo::PendingReceiver<network::mojom::CookieAccessObserver>,
+                CookieAccessDetails::Source>>;
+  virtual PendingObserversWithContext TakeReceiversWithContext();
 
   // network::mojom::CookieAccessObserver
   void OnCookiesAccessed(std::vector<network::mojom::CookieAccessDetailsPtr>
diff --git a/content/browser/renderer_host/navigation_request.cc b/content/browser/renderer_host/navigation_request.cc
index 13841f1..3c714ec 100644
--- a/content/browser/renderer_host/navigation_request.cc
+++ b/content/browser/renderer_host/navigation_request.cc
@@ -5016,7 +5016,7 @@
 
   mojo::PendingRemote<network::mojom::CookieAccessObserver> cookie_observer;
   cookie_observers_->Add(cookie_observer.InitWithNewPipeAndPassReceiver(),
-                         CookieAccessDetails::Source::kNavigation);
+                         CookieAccessDetails::Source::kNonNavigation);
 
   mojo::PendingRemote<network::mojom::TrustTokenAccessObserver>
       trust_token_observer;
@@ -9935,9 +9935,11 @@
   }
 }
 
-std::vector<mojo::PendingReceiver<network::mojom::CookieAccessObserver>>
+std::vector<
+    std::pair<mojo::PendingReceiver<network::mojom::CookieAccessObserver>,
+              CookieAccessDetails::Source>>
 NavigationRequest::TakeCookieObservers() {
-  return cookie_observers_->TakeReceivers();
+  return cookie_observers_->TakeReceiversWithContext();
 }
 
 void NavigationRequest::OnTrustTokensAccessed(
diff --git a/content/browser/renderer_host/navigation_request.h b/content/browser/renderer_host/navigation_request.h
index 8b31f08..fce490ee 100644
--- a/content/browser/renderer_host/navigation_request.h
+++ b/content/browser/renderer_host/navigation_request.h
@@ -903,7 +903,8 @@
   // Typically this is called when navigation commits to move these observers to
   // the committed document.
   [[nodiscard]] std::vector<
-      mojo::PendingReceiver<network::mojom::CookieAccessObserver>>
+      std::pair<mojo::PendingReceiver<network::mojom::CookieAccessObserver>,
+                CookieAccessDetails::Source>>
   TakeCookieObservers();
 
   void NotifyCookiesAccessed(
diff --git a/content/browser/renderer_host/private_network_access_browsertest.cc b/content/browser/renderer_host/private_network_access_browsertest.cc
index 7a9830e..8945c85 100644
--- a/content/browser/renderer_host/private_network_access_browsertest.cc
+++ b/content/browser/renderer_host/private_network_access_browsertest.cc
@@ -40,7 +40,6 @@
 #include "services/network/public/cpp/features.h"
 #include "services/network/public/cpp/network_switches.h"
 #include "testing/gmock/include/gmock/gmock.h"
-#include "third_party/blink/public/common/features.h"
 #include "url/gurl.h"
 #include "url/origin.h"
 
@@ -576,7 +575,6 @@
   PrivateNetworkAccessBrowserTest()
       : PrivateNetworkAccessBrowserTestBase(
             {
-                blink::features::kPlzDedicatedWorker,
                 features::kBlockInsecurePrivateNetworkRequests,
                 features::kPrivateNetworkAccessSendPreflights,
             },
@@ -601,13 +599,11 @@
   PrivateNetworkAccessSandboxedDataBrowserTest()
       : PrivateNetworkAccessBrowserTestBase(
             GetParam() ? FeatureVec({
-                             blink::features::kPlzDedicatedWorker,
                              features::kBlockInsecurePrivateNetworkRequests,
                              features::kPrivateNetworkAccessSendPreflights,
                              features::kOriginKeyedProcessesByDefault,
                          })
                        : FeatureVec({
-                             blink::features::kPlzDedicatedWorker,
                              features::kBlockInsecurePrivateNetworkRequests,
                              features::kPrivateNetworkAccessSendPreflights,
                          }),
diff --git a/content/browser/renderer_host/render_frame_host_impl.cc b/content/browser/renderer_host/render_frame_host_impl.cc
index d1016b0f..6dbea82 100644
--- a/content/browser/renderer_host/render_frame_host_impl.cc
+++ b/content/browser/renderer_host/render_frame_host_impl.cc
@@ -15397,9 +15397,8 @@
     // still has a valid receiver, `this` will receive delayed IPC calls from
     // the network service. When the remote interface in the network service is
     // destructed, `mojo::ReceiverSet` automatically removes the receiver.
-    for (auto& receiver : navigation_request->TakeCookieObservers()) {
-      cookie_observers_.Add(std::move(receiver),
-                            CookieAccessDetails::Source::kNavigation);
+    for (auto& [receiver, source] : navigation_request->TakeCookieObservers()) {
+      cookie_observers_.Add(std::move(receiver), source);
     }
     for (auto& receiver : navigation_request->TakeTrustTokenObservers()) {
       trust_token_observers_.Add(this, std::move(receiver));
diff --git a/content/browser/service_worker/service_worker_browsertest.cc b/content/browser/service_worker/service_worker_browsertest.cc
index fe3d045..613fb1b 100644
--- a/content/browser/service_worker/service_worker_browsertest.cc
+++ b/content/browser/service_worker/service_worker_browsertest.cc
@@ -4228,12 +4228,9 @@
     : public base::test::WithFeatureOverride,
       public ServiceWorkerBrowserTest {
  public:
-  // Dedicated worker clients only exist with PlzDedicatedWorker enabled, so
-  // turn on that flag.
   ServiceWorkerBrowserTestWithStoragePartitioning()
       : base::test::WithFeatureOverride(
-            net::features::kThirdPartyStoragePartitioning),
-        scoped_feature_list_(blink::features::kPlzDedicatedWorker) {}
+            net::features::kThirdPartyStoragePartitioning) {}
   bool ThirdPartyStoragePartitioningEnabled() const {
     return IsParamFeatureEnabled();
   }
@@ -4372,9 +4369,6 @@
           testing::UnorderedElementsAre(child_url));
     }
   }
-
- private:
-  base::test::ScopedFeatureList scoped_feature_list_;
 };
 
 INSTANTIATE_FEATURE_OVERRIDE_TEST_SUITE(
diff --git a/content/browser/service_worker/service_worker_container_host.cc b/content/browser/service_worker/service_worker_container_host.cc
index e9a61e6..5992fcd 100644
--- a/content/browser/service_worker/service_worker_container_host.cc
+++ b/content/browser/service_worker/service_worker_container_host.cc
@@ -27,7 +27,6 @@
 #include "content/public/common/content_client.h"
 #include "content/public/common/origin_util.h"
 #include "mojo/public/cpp/bindings/callback_helpers.h"
-#include "third_party/blink/public/common/features.h"
 #include "third_party/blink/public/common/storage_key/storage_key.h"
 #include "third_party/blink/public/mojom/service_worker/service_worker_registration_options.mojom.h"
 
@@ -553,9 +552,6 @@
       SCOPED_CRASH_KEY_NUMBER(
           "SWController", "client_type",
           static_cast<int>(service_worker_client().GetClientType()));
-      SCOPED_CRASH_KEY_BOOL(
-          "SWController", "PlzDedicatedWorker",
-          base::FeatureList::IsEnabled(blink::features::kPlzDedicatedWorker));
       base::debug::DumpWithoutCrashing();
     }
   }
diff --git a/content/browser/service_worker/service_worker_container_host_unittest.cc b/content/browser/service_worker/service_worker_container_host_unittest.cc
index cf2305a..5383187 100644
--- a/content/browser/service_worker/service_worker_container_host_unittest.cc
+++ b/content/browser/service_worker/service_worker_container_host_unittest.cc
@@ -44,7 +44,6 @@
 #include "net/cookies/site_for_cookies.h"
 #include "services/network/public/cpp/is_potentially_trustworthy.h"
 #include "testing/gtest/include/gtest/gtest.h"
-#include "third_party/blink/public/common/features.h"
 #include "third_party/blink/public/common/storage_key/storage_key.h"
 #include "third_party/blink/public/common/tokens/tokens.h"
 #include "third_party/blink/public/mojom/service_worker/service_worker_object.mojom.h"
@@ -315,23 +314,6 @@
   url::ScopedSchemeRegistryForTests scoped_registry_;
 };
 
-// Run tests with PlzDedicatedWorker.
-// TODO(crbug.com/40093136): Merge this test fixture into
-// ServiceWorkerContainerHostTest once PlzDedicatedWorker is enabled by default.
-class ServiceWorkerContainerHostTestWithPlzDedicatedWorker
-    : public ServiceWorkerContainerHostTest {
- public:
-  ServiceWorkerContainerHostTestWithPlzDedicatedWorker() {
-    // ServiceWorkerClient for dedicated workers is available only when
-    // PlzDedicatedWorker is enabled.
-    scoped_feature_list_.InitAndEnableFeature(
-        blink::features::kPlzDedicatedWorker);
-  }
-
- private:
-  base::test::ScopedFeatureList scoped_feature_list_;
-};
-
 TEST_F(ServiceWorkerContainerHostTest, MatchRegistration) {
   ScopedServiceWorkerClient service_worker_client = CreateServiceWorkerClient(
       context_.get(), GURL("https://www.example.com/example1.html"));
@@ -1097,20 +1079,13 @@
     : public ServiceWorkerContainerHostTest,
       public testing::WithParamInterface<ClientType> {
  public:
-  ServiceWorkerContainerHostTestByClientType() {
-    // ServiceWorkerClient for dedicated workers is available only when
-    // PlzDedicatedWorker is enabled.
-    scoped_feature_list_.InitAndEnableFeature(
-        blink::features::kPlzDedicatedWorker);
-  }
+  ServiceWorkerContainerHostTestByClientType() = default;
 
   ScopedServiceWorkerClient CreateClient() {
     switch (GetParam()) {
       case ClientType::kWindow:
         return CreateServiceWorkerClient(context_.get());
       case ClientType::kDedicatedWorker:
-        CHECK(
-            base::FeatureList::IsEnabled(blink::features::kPlzDedicatedWorker));
         return ScopedServiceWorkerClient(
             helper_->context()
                 ->service_worker_client_owner()
@@ -1178,9 +1153,6 @@
         break;
     }
   }
-
- private:
-  base::test::ScopedFeatureList scoped_feature_list_;
 };
 
 // Test that a "reserved" (i.e., not execution ready) client is not included
diff --git a/content/browser/shared_storage/DEPS b/content/browser/shared_storage/DEPS
new file mode 100644
index 0000000..4437dd3f9
--- /dev/null
+++ b/content/browser/shared_storage/DEPS
@@ -0,0 +1,3 @@
+include_rules = [
+  "+components/aggregation_service",
+]
diff --git a/content/browser/shared_storage/shared_storage_browsertest.cc b/content/browser/shared_storage/shared_storage_browsertest.cc
index 236ee96..5f0e9222 100644
--- a/content/browser/shared_storage/shared_storage_browsertest.cc
+++ b/content/browser/shared_storage/shared_storage_browsertest.cc
@@ -595,8 +595,11 @@
                                    "/shared_storage/simple_module.js"),
             /*worklet_id=*/0)},
        {AccessScope::kWindow, AccessMethod::kRun, MainFrameId(), origin_str,
-        SharedStorageEventParams::CreateForRun(
-            "test-operation", blink::CloneableMessage(), /*worklet_id=*/0)}});
+        SharedStorageEventParams::CreateForRunForTesting(
+            "test-operation",
+            /*keep_alive=*/false,
+            SharedStorageEventParams::PrivateAggregationConfigWrapper(),
+            blink::CloneableMessage(), /*worklet_id=*/0)}});
 }
 
 IN_PROC_BROWSER_TEST_P(SharedStorageBrowserTest,
@@ -882,8 +885,10 @@
                 "a.test", "/shared_storage/erroneous_function_module.js"),
             /*worklet_id=*/0)},
        {AccessScope::kWindow, AccessMethod::kRun, MainFrameId(), origin_str,
-        SharedStorageEventParams::CreateForRun(
-            "test-operation", blink::CloneableMessage(), /*worklet_id=*/0)}});
+        SharedStorageEventParams::CreateForRunForTesting(
+            "test-operation", /*keep_alive=*/false,
+            SharedStorageEventParams::PrivateAggregationConfigWrapper(),
+            blink::CloneableMessage(), /*worklet_id=*/0)}});
 }
 
 IN_PROC_BROWSER_TEST_P(
@@ -966,11 +971,15 @@
                                    "/shared_storage/simple_module.js"),
             /*worklet_id=*/0)},
        {AccessScope::kWindow, AccessMethod::kRun, MainFrameId(), origin_str,
-        SharedStorageEventParams::CreateForRun(
-            "test-operation", blink::CloneableMessage(), /*worklet_id=*/0)},
+        SharedStorageEventParams::CreateForRunForTesting(
+            "test-operation", /*keep_alive=*/true,
+            SharedStorageEventParams::PrivateAggregationConfigWrapper(),
+            blink::CloneableMessage(), /*worklet_id=*/0)},
        {AccessScope::kWindow, AccessMethod::kRun, MainFrameId(), origin_str,
-        SharedStorageEventParams::CreateForRun(
-            "test-operation", blink::CloneableMessage(), /*worklet_id=*/0)}});
+        SharedStorageEventParams::CreateForRunForTesting(
+            "test-operation", /*keep_alive=*/false,
+            SharedStorageEventParams::PrivateAggregationConfigWrapper(),
+            blink::CloneableMessage(), /*worklet_id=*/0)}});
 }
 
 IN_PROC_BROWSER_TEST_P(
@@ -1040,8 +1049,10 @@
                                    "/shared_storage/simple_module.js"),
             /*worklet_id=*/0)},
        {AccessScope::kWindow, AccessMethod::kRun, MainFrameId(), origin_str,
-        SharedStorageEventParams::CreateForRun(
-            "test-operation", blink::CloneableMessage(), /*worklet_id=*/0)}});
+        SharedStorageEventParams::CreateForRunForTesting(
+            "test-operation", /*keep_alive=*/false,
+            SharedStorageEventParams::PrivateAggregationConfigWrapper(),
+            blink::CloneableMessage(), /*worklet_id=*/0)}});
 }
 
 IN_PROC_BROWSER_TEST_P(
@@ -1110,8 +1121,10 @@
                                    "/shared_storage/simple_module.js"),
             /*worklet_id=*/0)},
        {AccessScope::kWindow, AccessMethod::kRun, MainFrameId(), origin_str,
-        SharedStorageEventParams::CreateForRun(
-            "test-operation", blink::CloneableMessage(), /*worklet_id=*/0)}});
+        SharedStorageEventParams::CreateForRunForTesting(
+            "test-operation", /*keep_alive=*/false,
+            SharedStorageEventParams::PrivateAggregationConfigWrapper(),
+            blink::CloneableMessage(), /*worklet_id=*/0)}});
 }
 
 IN_PROC_BROWSER_TEST_P(SharedStorageBrowserTest, WorkletDestroyed) {
@@ -1430,8 +1443,10 @@
                                    "/shared_storage/simple_module.js"),
             /*worklet_id=*/0)},
        {AccessScope::kWindow, AccessMethod::kRun, MainFrameId(), origin_str,
-        SharedStorageEventParams::CreateForRun(
-            "test-operation", blink::CloneableMessage(), /*worklet_id=*/0)}});
+        SharedStorageEventParams::CreateForRunForTesting(
+            "test-operation", /*keep_alive=*/false,
+            SharedStorageEventParams::PrivateAggregationConfigWrapper(),
+            blink::CloneableMessage(), /*worklet_id=*/0)}});
 }
 
 IN_PROC_BROWSER_TEST_P(
@@ -1552,8 +1567,10 @@
             /*worklet_id=*/0)},
        {AccessScope::kWindow, AccessMethod::kSelectURL, MainFrameId(),
         origin_str,
-        SharedStorageEventParams::CreateForSelectURL(
-            "test-url-selection-operation", blink::CloneableMessage(),
+        SharedStorageEventParams::CreateForSelectURLForTesting(
+            "test-url-selection-operation", /*keep_alive=*/false,
+            SharedStorageEventParams::PrivateAggregationConfigWrapper(),
+            blink::CloneableMessage(),
             std::vector<SharedStorageUrlSpecWithMetadata>(
                 {{https_server()->GetURL("a.test",
                                          "/fenced_frames/title0.html"),
@@ -1840,8 +1857,10 @@
             /*worklet_id=*/0)},
        {AccessScope::kWindow, AccessMethod::kSelectURL, MainFrameId(),
         origin_str,
-        SharedStorageEventParams::CreateForSelectURL(
-            "test-url-selection-operation", blink::CloneableMessage(),
+        SharedStorageEventParams::CreateForSelectURLForTesting(
+            "test-url-selection-operation", /*keep_alive=*/false,
+            SharedStorageEventParams::PrivateAggregationConfigWrapper(),
+            blink::CloneableMessage(),
             std::vector<SharedStorageUrlSpecWithMetadata>(
                 {{https_server()->GetURL("a.test",
                                          "/fenced_frames/title0.html"),
@@ -1951,8 +1970,10 @@
             /*worklet_id=*/0)},
        {AccessScope::kWindow, AccessMethod::kSelectURL, MainFrameId(),
         origin_str,
-        SharedStorageEventParams::CreateForSelectURL(
-            "test-url-selection-operation", blink::CloneableMessage(),
+        SharedStorageEventParams::CreateForSelectURLForTesting(
+            "test-url-selection-operation", /*keep_alive=*/false,
+            SharedStorageEventParams::PrivateAggregationConfigWrapper(),
+            blink::CloneableMessage(),
             std::vector<SharedStorageUrlSpecWithMetadata>(
                 {{https_server()->GetURL("a.test",
                                          "/fenced_frames/title0.html"),
@@ -2067,8 +2088,10 @@
             /*worklet_id=*/0)},
        {AccessScope::kWindow, AccessMethod::kSelectURL, MainFrameId(),
         origin_str,
-        SharedStorageEventParams::CreateForSelectURL(
-            "test-url-selection-operation", blink::CloneableMessage(),
+        SharedStorageEventParams::CreateForSelectURLForTesting(
+            "test-url-selection-operation", /*keep_alive=*/false,
+            SharedStorageEventParams::PrivateAggregationConfigWrapper(),
+            blink::CloneableMessage(),
             std::vector<SharedStorageUrlSpecWithMetadata>(
                 {{https_server()->GetURL("b.test",
                                          "/fenced_frames/title0.html"),
@@ -2214,8 +2237,10 @@
             /*worklet_id=*/0)},
        {AccessScope::kWindow, AccessMethod::kSelectURL, MainFrameId(),
         origin_str,
-        SharedStorageEventParams::CreateForSelectURL(
-            "test-url-selection-operation", blink::CloneableMessage(),
+        SharedStorageEventParams::CreateForSelectURLForTesting(
+            "test-url-selection-operation", /*keep_alive=*/true,
+            SharedStorageEventParams::PrivateAggregationConfigWrapper(),
+            blink::CloneableMessage(),
             std::vector<SharedStorageUrlSpecWithMetadata>(
                 {{https_server()->GetURL("a.test",
                                          "/fenced_frames/title0.html"),
@@ -2223,8 +2248,10 @@
             /*worklet_id=*/0)},
        {AccessScope::kWindow, AccessMethod::kSelectURL, MainFrameId(),
         origin_str,
-        SharedStorageEventParams::CreateForSelectURL(
-            "test-url-selection-operation", blink::CloneableMessage(),
+        SharedStorageEventParams::CreateForSelectURLForTesting(
+            "test-url-selection-operation", /*keep_alive=*/false,
+            SharedStorageEventParams::PrivateAggregationConfigWrapper(),
+            blink::CloneableMessage(),
             std::vector<SharedStorageUrlSpecWithMetadata>(
                 {{https_server()->GetURL("a.test",
                                          "/fenced_frames/title0.html"),
@@ -2342,8 +2369,10 @@
             /*worklet_id=*/0)},
        {AccessScope::kWindow, AccessMethod::kSelectURL, MainFrameId(),
         origin_str,
-        SharedStorageEventParams::CreateForSelectURL(
-            "test-url-selection-operation", blink::CloneableMessage(),
+        SharedStorageEventParams::CreateForSelectURLForTesting(
+            "test-url-selection-operation", /*keep_alive=*/false,
+            SharedStorageEventParams::PrivateAggregationConfigWrapper(),
+            blink::CloneableMessage(),
             std::vector<SharedStorageUrlSpecWithMetadata>(
                 {{https_server()->GetURL("a.test",
                                          "/fenced_frames/title0.html"),
@@ -2442,8 +2471,10 @@
             /*worklet_id=*/0)},
        {AccessScope::kWindow, AccessMethod::kSelectURL, MainFrameId(),
         origin_str,
-        SharedStorageEventParams::CreateForSelectURL(
-            "test-url-selection-operation", blink::CloneableMessage(),
+        SharedStorageEventParams::CreateForSelectURLForTesting(
+            "test-url-selection-operation", /*keep_alive=*/false,
+            SharedStorageEventParams::PrivateAggregationConfigWrapper(),
+            blink::CloneableMessage(),
             std::vector<SharedStorageUrlSpecWithMetadata>(
                 {{https_server()->GetURL("a.test",
                                          "/fenced_frames/title0.html"),
@@ -2533,8 +2564,10 @@
                                    "/shared_storage/simple_module.js"),
             /*worklet_id=*/0)},
        {AccessScope::kWindow, AccessMethod::kRun, MainFrameId(), origin_str,
-        SharedStorageEventParams::CreateForRun(
-            "test-operation", blink::CloneableMessage(), /*worklet_id=*/0)}});
+        SharedStorageEventParams::CreateForRunForTesting(
+            "test-operation", /*keep_alive=*/false,
+            SharedStorageEventParams::PrivateAggregationConfigWrapper(),
+            blink::CloneableMessage(), /*worklet_id=*/0)}});
 }
 
 IN_PROC_BROWSER_TEST_P(SharedStorageBrowserTest,
@@ -2646,12 +2679,16 @@
                                    "/shared_storage/simple_module.js"),
             /*worklet_id=*/0)},
        {AccessScope::kWindow, AccessMethod::kRun, MainFrameId(), origin_str,
-        SharedStorageEventParams::CreateForRun(
-            "test-operation", blink::CloneableMessage(), /*worklet_id=*/0)},
+        SharedStorageEventParams::CreateForRunForTesting(
+            "test-operation", /*keep_alive=*/true,
+            SharedStorageEventParams::PrivateAggregationConfigWrapper(),
+            blink::CloneableMessage(), /*worklet_id=*/0)},
        {AccessScope::kWindow, AccessMethod::kSelectURL, MainFrameId(),
         origin_str,
-        SharedStorageEventParams::CreateForSelectURL(
-            "test-url-selection-operation", blink::CloneableMessage(),
+        SharedStorageEventParams::CreateForSelectURLForTesting(
+            "test-url-selection-operation", /*keep_alive=*/false,
+            SharedStorageEventParams::PrivateAggregationConfigWrapper(),
+            blink::CloneableMessage(),
             std::vector<SharedStorageUrlSpecWithMetadata>(
                 {{https_server()->GetURL("a.test",
                                          "/fenced_frames/title0.html"),
@@ -2740,8 +2777,10 @@
                                    "/shared_storage/simple_module.js"),
             /*worklet_id=*/0)},
        {AccessScope::kWindow, AccessMethod::kRun, MainFrameId(), origin_str,
-        SharedStorageEventParams::CreateForRun(
-            "test-operation", blink::CloneableMessage(), /*worklet_id=*/0)}});
+        SharedStorageEventParams::CreateForRunForTesting(
+            "test-operation", /*keep_alive=*/false,
+            SharedStorageEventParams::PrivateAggregationConfigWrapper(),
+            blink::CloneableMessage(), /*worklet_id=*/0)}});
 }
 
 IN_PROC_BROWSER_TEST_P(SharedStorageBrowserTest,
@@ -2854,16 +2893,20 @@
             /*worklet_id=*/0)},
        {AccessScope::kWindow, AccessMethod::kSelectURL, MainFrameId(),
         origin_str,
-        SharedStorageEventParams::CreateForSelectURL(
-            "test-url-selection-operation", blink::CloneableMessage(),
+        SharedStorageEventParams::CreateForSelectURLForTesting(
+            "test-url-selection-operation", /*keep_alive=*/true,
+            SharedStorageEventParams::PrivateAggregationConfigWrapper(),
+            blink::CloneableMessage(),
             std::vector<SharedStorageUrlSpecWithMetadata>(
                 {{https_server()->GetURL("a.test",
                                          "/fenced_frames/title0.html"),
                   {}}}),
             /*worklet_id=*/0)},
        {AccessScope::kWindow, AccessMethod::kRun, MainFrameId(), origin_str,
-        SharedStorageEventParams::CreateForRun(
-            "test-operation", blink::CloneableMessage(), /*worklet_id=*/0)}});
+        SharedStorageEventParams::CreateForRunForTesting(
+            "test-operation", /*keep_alive=*/false,
+            SharedStorageEventParams::PrivateAggregationConfigWrapper(),
+            blink::CloneableMessage(), /*worklet_id=*/0)}});
 }
 
 IN_PROC_BROWSER_TEST_P(SharedStorageBrowserTest,
@@ -2966,8 +3009,10 @@
             /*worklet_id=*/0)},
        {AccessScope::kWindow, AccessMethod::kSelectURL, MainFrameId(),
         origin_str,
-        SharedStorageEventParams::CreateForSelectURL(
-            "test-url-selection-operation", blink::CloneableMessage(),
+        SharedStorageEventParams::CreateForSelectURLForTesting(
+            "test-url-selection-operation", /*keep_alive=*/false,
+            SharedStorageEventParams::PrivateAggregationConfigWrapper(),
+            blink::CloneableMessage(),
             std::vector<SharedStorageUrlSpecWithMetadata>(
                 {{https_server()->GetURL("a.test",
                                          "/fenced_frames/title0.html"),
@@ -3074,8 +3119,10 @@
             /*worklet_id=*/0)},
        {AccessScope::kWindow, AccessMethod::kSelectURL, MainFrameId(),
         origin_str,
-        SharedStorageEventParams::CreateForSelectURL(
-            "test-url-selection-operation", blink::CloneableMessage(),
+        SharedStorageEventParams::CreateForSelectURLForTesting(
+            "test-url-selection-operation", /*keep_alive=*/false,
+            SharedStorageEventParams::PrivateAggregationConfigWrapper(),
+            blink::CloneableMessage(),
             std::vector<SharedStorageUrlSpecWithMetadata>(
                 {{https_server()->GetURL("a.test",
                                          "/fenced_frames/title0.html"),
@@ -3174,8 +3221,10 @@
             /*worklet_id=*/0)},
        {AccessScope::kWindow, AccessMethod::kSelectURL, MainFrameId(),
         origin_str,
-        SharedStorageEventParams::CreateForSelectURL(
-            "test-url-selection-operation", blink::CloneableMessage(),
+        SharedStorageEventParams::CreateForSelectURLForTesting(
+            "test-url-selection-operation", /*keep_alive=*/false,
+            SharedStorageEventParams::PrivateAggregationConfigWrapper(),
+            blink::CloneableMessage(),
             std::vector<SharedStorageUrlSpecWithMetadata>(
                 {{https_server()->GetURL("a.test",
                                          "/fenced_frames/title0.html"),
@@ -3249,8 +3298,10 @@
         SharedStorageEventParams::CreateForAddModule(out_script_url,
                                                      /*worklet_id=*/0)},
        {AccessScope::kWindow, AccessMethod::kRun, MainFrameId(), origin_str,
-        SharedStorageEventParams::CreateForRun(
-            "test-operation", blink::CloneableMessage(), /*worklet_id=*/0)},
+        SharedStorageEventParams::CreateForRunForTesting(
+            "test-operation", /*keep_alive=*/true,
+            SharedStorageEventParams::PrivateAggregationConfigWrapper(),
+            blink::CloneableMessage(), /*worklet_id=*/0)},
        {AccessScope::kSharedStorageWorklet, AccessMethod::kGet, MainFrameId(),
         origin_str,
         SharedStorageEventParams::CreateForGetOrDelete("key0",
@@ -3313,8 +3364,10 @@
         SharedStorageEventParams::CreateForAddModule(out_script_url,
                                                      /*worklet_id=*/0)},
        {AccessScope::kWindow, AccessMethod::kRun, MainFrameId(), origin_str,
-        SharedStorageEventParams::CreateForRun(
-            "test-operation", blink::CloneableMessage(), /*worklet_id=*/0)},
+        SharedStorageEventParams::CreateForRunForTesting(
+            "test-operation", /*keep_alive=*/true,
+            SharedStorageEventParams::PrivateAggregationConfigWrapper(),
+            blink::CloneableMessage(), /*worklet_id=*/0)},
        {AccessScope::kSharedStorageWorklet, AccessMethod::kLength,
         MainFrameId(), origin_str,
         SharedStorageEventParams::CreateWithWorkletId(/*worklet_id=*/0)},
@@ -3358,8 +3411,10 @@
         SharedStorageEventParams::CreateForAddModule(out_script_url,
                                                      /*worklet_id=*/0)},
        {AccessScope::kWindow, AccessMethod::kRun, MainFrameId(), origin_str,
-        SharedStorageEventParams::CreateForRun(
-            "test-operation", blink::CloneableMessage(), /*worklet_id=*/0)},
+        SharedStorageEventParams::CreateForRunForTesting(
+            "test-operation", /*keep_alive=*/true,
+            SharedStorageEventParams::PrivateAggregationConfigWrapper(),
+            blink::CloneableMessage(), /*worklet_id=*/0)},
        {AccessScope::kSharedStorageWorklet, AccessMethod::kLength,
         MainFrameId(), origin_str,
         SharedStorageEventParams::CreateWithWorkletId(/*worklet_id=*/0)}});
@@ -3413,8 +3468,10 @@
         SharedStorageEventParams::CreateForAddModule(out_script_url,
                                                      /*worklet_id=*/0)},
        {AccessScope::kWindow, AccessMethod::kRun, MainFrameId(), origin_str,
-        SharedStorageEventParams::CreateForRun(
-            "test-operation", blink::CloneableMessage(), /*worklet_id=*/0)},
+        SharedStorageEventParams::CreateForRunForTesting(
+            "test-operation", /*keep_alive=*/true,
+            SharedStorageEventParams::PrivateAggregationConfigWrapper(),
+            blink::CloneableMessage(), /*worklet_id=*/0)},
        {AccessScope::kSharedStorageWorklet, AccessMethod::kSet, MainFrameId(),
         origin_str,
         SharedStorageEventParams::CreateForSet("key0", "value0", false,
@@ -3496,8 +3553,10 @@
         SharedStorageEventParams::CreateForAddModule(out_script_url,
                                                      /*worklet_id=*/0)},
        {AccessScope::kWindow, AccessMethod::kRun, MainFrameId(), origin_str,
-        SharedStorageEventParams::CreateForRun(
-            "test-operation", blink::CloneableMessage(), /*worklet_id=*/0)},
+        SharedStorageEventParams::CreateForRunForTesting(
+            "test-operation", /*keep_alive=*/true,
+            SharedStorageEventParams::PrivateAggregationConfigWrapper(),
+            blink::CloneableMessage(), /*worklet_id=*/0)},
        {AccessScope::kSharedStorageWorklet, AccessMethod::kSet, MainFrameId(),
         origin_str,
         SharedStorageEventParams::CreateForSet("k", std::string(2621439, 'a'),
@@ -3553,8 +3612,10 @@
         SharedStorageEventParams::CreateForAddModule(out_script_url,
                                                      /*worklet_id=*/0)},
        {AccessScope::kWindow, AccessMethod::kRun, MainFrameId(), origin_str,
-        SharedStorageEventParams::CreateForRun(
-            "test-operation", blink::CloneableMessage(), /*worklet_id=*/0)},
+        SharedStorageEventParams::CreateForRunForTesting(
+            "test-operation", /*keep_alive=*/true,
+            SharedStorageEventParams::PrivateAggregationConfigWrapper(),
+            blink::CloneableMessage(), /*worklet_id=*/0)},
        {AccessScope::kSharedStorageWorklet, AccessMethod::kSet, MainFrameId(),
         origin_str,
         SharedStorageEventParams::CreateForSet("key0", "value0", false,
@@ -3613,8 +3674,10 @@
         SharedStorageEventParams::CreateForAddModule(out_script_url,
                                                      /*worklet_id=*/0)},
        {AccessScope::kWindow, AccessMethod::kRun, MainFrameId(), origin_str,
-        SharedStorageEventParams::CreateForRun(
-            "test-operation", blink::CloneableMessage(), /*worklet_id=*/0)},
+        SharedStorageEventParams::CreateForRunForTesting(
+            "test-operation", /*keep_alive=*/true,
+            SharedStorageEventParams::PrivateAggregationConfigWrapper(),
+            blink::CloneableMessage(), /*worklet_id=*/0)},
        {AccessScope::kSharedStorageWorklet, AccessMethod::kSet, MainFrameId(),
         origin_str,
         SharedStorageEventParams::CreateForSet("key0", "value0", false,
@@ -3732,8 +3795,10 @@
         SharedStorageEventParams::CreateForAddModule(script_url,
                                                      /*worklet_id=*/0)},
        {AccessScope::kWindow, AccessMethod::kRun, MainFrameId(), origin_str,
-        SharedStorageEventParams::CreateForRun(
-            "get-operation", blink::CloneableMessage(), /*worklet_id=*/0)},
+        SharedStorageEventParams::CreateForRunForTesting(
+            "get-operation", /*keep_alive=*/true,
+            SharedStorageEventParams::PrivateAggregationConfigWrapper(),
+            blink::CloneableMessage(), /*worklet_id=*/0)},
        {AccessScope::kSharedStorageWorklet, AccessMethod::kLength,
         MainFrameId(), origin_str,
         SharedStorageEventParams::CreateWithWorkletId(/*worklet_id=*/0)},
@@ -3742,8 +3807,10 @@
         SharedStorageEventParams::CreateForGetOrDelete("key0",
                                                        /*worklet_id=*/0)},
        {AccessScope::kWindow, AccessMethod::kRun, MainFrameId(), origin_str,
-        SharedStorageEventParams::CreateForRun(
-            "get-operation", blink::CloneableMessage(), /*worklet_id=*/0)},
+        SharedStorageEventParams::CreateForRunForTesting(
+            "get-operation", /*keep_alive=*/false,
+            SharedStorageEventParams::PrivateAggregationConfigWrapper(),
+            blink::CloneableMessage(), /*worklet_id=*/0)},
        {AccessScope::kSharedStorageWorklet, AccessMethod::kLength,
         MainFrameId(), origin_str,
         SharedStorageEventParams::CreateWithWorkletId(/*worklet_id=*/0)},
@@ -3788,8 +3855,10 @@
         SharedStorageEventParams::CreateForAddModule(out_script_url,
                                                      /*worklet_id=*/0)},
        {AccessScope::kWindow, AccessMethod::kRun, MainFrameId(), origin_str,
-        SharedStorageEventParams::CreateForRun(
-            "test-operation", blink::CloneableMessage(), /*worklet_id=*/0)},
+        SharedStorageEventParams::CreateForRunForTesting(
+            "test-operation", /*keep_alive=*/true,
+            SharedStorageEventParams::PrivateAggregationConfigWrapper(),
+            blink::CloneableMessage(), /*worklet_id=*/0)},
        {AccessScope::kSharedStorageWorklet, AccessMethod::kLength,
         MainFrameId(), origin_str,
         SharedStorageEventParams::CreateWithWorkletId(/*worklet_id=*/0)}});
@@ -3831,8 +3900,10 @@
         SharedStorageEventParams::CreateForAddModule(out_script_url,
                                                      /*worklet_id=*/0)},
        {AccessScope::kWindow, AccessMethod::kRun, MainFrameId(), origin2_str,
-        SharedStorageEventParams::CreateForRun(
-            "test-operation", blink::CloneableMessage(), /*worklet_id=*/0)},
+        SharedStorageEventParams::CreateForRunForTesting(
+            "test-operation", /*keep_alive=*/true,
+            SharedStorageEventParams::PrivateAggregationConfigWrapper(),
+            blink::CloneableMessage(), /*worklet_id=*/0)},
        {AccessScope::kSharedStorageWorklet, AccessMethod::kLength,
         MainFrameId(), origin2_str,
         SharedStorageEventParams::CreateWithWorkletId(/*worklet_id=*/0)}});
@@ -3888,8 +3959,10 @@
         SharedStorageEventParams::CreateForAddModule(out_script_url,
                                                      /*worklet_id=*/0)},
        {AccessScope::kWindow, AccessMethod::kRun, MainFrameId(), origin_str,
-        SharedStorageEventParams::CreateForRun(
-            "test-operation", blink::CloneableMessage(), /*worklet_id=*/0)},
+        SharedStorageEventParams::CreateForRunForTesting(
+            "test-operation", /*keep_alive=*/true,
+            SharedStorageEventParams::PrivateAggregationConfigWrapper(),
+            blink::CloneableMessage(), /*worklet_id=*/0)},
        {AccessScope::kSharedStorageWorklet, AccessMethod::kKeys, MainFrameId(),
         origin_str,
         SharedStorageEventParams::CreateWithWorkletId(/*worklet_id=*/0)},
@@ -3942,8 +4015,10 @@
         SharedStorageEventParams::CreateForAddModule(out_script_url,
                                                      /*worklet_id=*/0)},
        {AccessScope::kWindow, AccessMethod::kRun, MainFrameId(), origin_str,
-        SharedStorageEventParams::CreateForRun(
-            "test-operation", blink::CloneableMessage(), /*worklet_id=*/0)},
+        SharedStorageEventParams::CreateForRunForTesting(
+            "test-operation", /*keep_alive=*/true,
+            SharedStorageEventParams::PrivateAggregationConfigWrapper(),
+            blink::CloneableMessage(), /*worklet_id=*/0)},
        {AccessScope::kSharedStorageWorklet, AccessMethod::kValues,
         MainFrameId(), origin_str,
         SharedStorageEventParams::CreateWithWorkletId(/*worklet_id=*/0)}});
@@ -4003,8 +4078,10 @@
                                      out_script_url, /*worklet_id=*/0));
   expected_accesses.emplace_back(
       AccessScope::kWindow, AccessMethod::kRun, MainFrameId(), origin_str,
-      SharedStorageEventParams::CreateForRun(
-          "test-operation", blink::CloneableMessage(), /*worklet_id=*/0));
+      SharedStorageEventParams::CreateForRunForTesting(
+          "test-operation", /*keep_alive=*/true,
+          SharedStorageEventParams::PrivateAggregationConfigWrapper(),
+          blink::CloneableMessage(), /*worklet_id=*/0));
   expected_accesses.emplace_back(
       AccessScope::kSharedStorageWorklet, AccessMethod::kKeys, MainFrameId(),
       origin_str,
@@ -9112,8 +9189,10 @@
                                                          "context-origin",
                                                          /*worklet_id=*/0)},
        {AccessScope::kWindow, AccessMethod::kRun, MainFrameId(), origin_str,
-        SharedStorageEventParams::CreateForRun(
-            "test-operation", blink::CloneableMessage(), /*worklet_id=*/0)},
+        SharedStorageEventParams::CreateForRunForTesting(
+            "test-operation", /*keep_alive=*/true,
+            SharedStorageEventParams::PrivateAggregationConfigWrapper(),
+            blink::CloneableMessage(), /*worklet_id=*/0)},
        {AccessScope::kSharedStorageWorklet, AccessMethod::kSet, MainFrameId(),
         origin_str,
         SharedStorageEventParams::CreateForSet("key0", "value0", false,
@@ -9135,8 +9214,10 @@
                                                          "context-origin",
                                                          /*worklet_id=*/1)},
        {AccessScope::kWindow, AccessMethod::kRun, MainFrameId(), origin_str,
-        SharedStorageEventParams::CreateForRun(
-            "test-operation", blink::CloneableMessage(), /*worklet_id=*/1)},
+        SharedStorageEventParams::CreateForRunForTesting(
+            "test-operation", /*keep_alive=*/true,
+            SharedStorageEventParams::PrivateAggregationConfigWrapper(),
+            blink::CloneableMessage(), /*worklet_id=*/1)},
        {AccessScope::kSharedStorageWorklet, AccessMethod::kSet, MainFrameId(),
         origin_str,
         SharedStorageEventParams::CreateForSet("key1", "value2", false,
diff --git a/content/browser/shared_storage/shared_storage_event_params.cc b/content/browser/shared_storage/shared_storage_event_params.cc
index 4b5a39e..fc785f94 100644
--- a/content/browser/shared_storage/shared_storage_event_params.cc
+++ b/content/browser/shared_storage/shared_storage_event_params.cc
@@ -4,6 +4,8 @@
 
 #include "content/browser/shared_storage/shared_storage_event_params.h"
 
+#include <stdint.h>
+
 #include <algorithm>
 #include <ostream>
 #include <sstream>
@@ -13,6 +15,8 @@
 #include "base/strings/strcat.h"
 #include "base/strings/string_number_conversions.h"
 #include "base/strings/string_util.h"
+#include "content/browser/private_aggregation/private_aggregation_host.h"
+#include "third_party/blink/public/mojom/shared_storage/shared_storage.mojom.h"
 
 namespace content {
 
@@ -22,7 +26,7 @@
 
 const size_t kSharedStorageSerializedDataLengthLimitForEventParams = 1024;
 
-std::string SerializeOptionalString(std::optional<std::string> str) {
+std::string SerializeOptionalString(const std::optional<std::string>& str) {
   if (str) {
     return *str;
   }
@@ -88,7 +92,8 @@
   return escaped;
 }
 
-std::string SerializeAndEscapeOptionalString(std::optional<std::string> str) {
+std::string SerializeAndEscapeOptionalString(
+    const std::optional<std::string>& str) {
   if (str) {
     return Escape(*str);
   }
@@ -96,6 +101,14 @@
   return "std::nullopt";
 }
 
+std::string SerializeOptionalOrigin(const std::optional<url::Origin>& origin) {
+  if (origin) {
+    return origin->Serialize();
+  }
+
+  return "std::nullopt";
+}
+
 std::string SerializeOptionalBool(std::optional<bool> b) {
   if (b) {
     return base::ToString(*b);
@@ -112,8 +125,28 @@
   return "std::nullopt";
 }
 
+std::string SerializeOptionalUInt16(std::optional<uint16_t> i) {
+  if (i) {
+    return base::NumberToString(static_cast<unsigned int>(*i));
+  }
+
+  return "std::nullopt";
+}
+
+std::string SerializeOptionalPrivateAggregationConfigWrapper(
+    const std::optional<
+        SharedStorageEventParams::PrivateAggregationConfigWrapper>& wrapper) {
+  if (!wrapper) {
+    return "std::nullopt";
+  }
+
+  std::ostringstream ss;
+  ss << *wrapper;
+  return ss.str();
+}
+
 std::string SerializeOptionalUrlsWithMetadata(
-    std::optional<std::vector<SharedStorageUrlSpecWithMetadata>>
+    const std::optional<std::vector<SharedStorageUrlSpecWithMetadata>>&
         urls_with_metadata) {
   if (!urls_with_metadata) {
     return "std::nullopt";
@@ -148,6 +181,63 @@
 
 }  // namespace
 
+SharedStorageEventParams::PrivateAggregationConfigWrapper::
+    PrivateAggregationConfigWrapper()
+    : config(blink::mojom::PrivateAggregationConfig::New()) {
+  config->filtering_id_max_bytes =
+      PrivateAggregationHost::kDefaultFilteringIdMaxBytes;
+}
+
+SharedStorageEventParams::PrivateAggregationConfigWrapper::
+    PrivateAggregationConfigWrapper(
+        const std::optional<url::Origin>& aggregation_coordinator_origin,
+        const std::optional<std::string>& context_id,
+        uint32_t filtering_id_max_bytes,
+        std::optional<uint16_t> max_contributions)
+    : config(blink::mojom::PrivateAggregationConfig::New(
+          aggregation_coordinator_origin,
+          context_id,
+          filtering_id_max_bytes,
+          max_contributions)) {}
+
+SharedStorageEventParams::PrivateAggregationConfigWrapper::
+    PrivateAggregationConfigWrapper(
+        const blink::mojom::PrivateAggregationConfigPtr& config)
+    : config(config.Clone()) {}
+
+SharedStorageEventParams::PrivateAggregationConfigWrapper::
+    PrivateAggregationConfigWrapper(
+        const PrivateAggregationConfigWrapper& other)
+    : config(other.config.Clone()) {}
+
+SharedStorageEventParams::PrivateAggregationConfigWrapper::
+    ~PrivateAggregationConfigWrapper() = default;
+
+SharedStorageEventParams::PrivateAggregationConfigWrapper&
+SharedStorageEventParams::PrivateAggregationConfigWrapper::operator=(
+    const PrivateAggregationConfigWrapper& other) {
+  if (this != &other) {
+    config = other.config.Clone();
+  }
+  return *this;
+}
+
+bool SharedStorageEventParams::PrivateAggregationConfigWrapper::operator==(
+    const PrivateAggregationConfigWrapper& other) const = default;
+
+std::ostream& operator<<(
+    std::ostream& os,
+    const SharedStorageEventParams::PrivateAggregationConfigWrapper& wrapper) {
+  os << "{ Aggregation Coordinator Origin: "
+     << SerializeOptionalOrigin(wrapper.config->aggregation_coordinator_origin)
+     << "; Context ID: " << SerializeOptionalString(wrapper.config->context_id)
+     << "; Filtering ID Max Bytes: " << wrapper.config->filtering_id_max_bytes
+     << "; Max Contributions: "
+     << SerializeOptionalUInt16(wrapper.config->max_contributions) << " }";
+
+  return os;
+}
+
 SharedStorageEventParams::SharedStorageUrlSpecWithMetadata::
     SharedStorageUrlSpecWithMetadata() = default;
 
@@ -207,6 +297,8 @@
     std::optional<std::string> script_source_url,
     std::optional<std::string> data_origin,
     std::optional<std::string> operation_name,
+    std::optional<bool> keep_alive,
+    std::optional<PrivateAggregationConfigWrapper> private_aggregation_config,
     std::optional<std::string> serialized_data,
     std::optional<std::vector<SharedStorageUrlSpecWithMetadata>>
         urls_with_metadata,
@@ -217,6 +309,8 @@
     : script_source_url(std::move(script_source_url)),
       data_origin(std::move(data_origin)),
       operation_name(std::move(operation_name)),
+      keep_alive(keep_alive),
+      private_aggregation_config(std::move(private_aggregation_config)),
       serialized_data(std::move(serialized_data)),
       urls_with_metadata(std::move(urls_with_metadata)),
       key(std::move(key)),
@@ -244,22 +338,51 @@
 // static
 SharedStorageEventParams SharedStorageEventParams::CreateForRun(
     const std::string& operation_name,
+    bool keep_alive,
+    const blink::mojom::PrivateAggregationConfigPtr& private_aggregation_config,
     const blink::CloneableMessage& serialized_data,
     int worklet_id) {
   return SharedStorageEventParams::CreateForWorkletOperation(
-      operation_name, serialized_data,
+      operation_name, keep_alive, private_aggregation_config, serialized_data,
+      /*urls_with_metadata=*/std::nullopt, worklet_id);
+}
+
+// static
+SharedStorageEventParams SharedStorageEventParams::CreateForRunForTesting(
+    const std::string& operation_name,
+    bool keep_alive,
+    PrivateAggregationConfigWrapper config_wrapper,
+    const blink::CloneableMessage& serialized_data,
+    int worklet_id) {
+  return SharedStorageEventParams::CreateForWorkletOperationForTesting(
+      operation_name, keep_alive, std::move(config_wrapper), serialized_data,
       /*urls_with_metadata=*/std::nullopt, worklet_id);
 }
 
 // static
 SharedStorageEventParams SharedStorageEventParams::CreateForSelectURL(
     const std::string& operation_name,
+    bool keep_alive,
+    const blink::mojom::PrivateAggregationConfigPtr& private_aggregation_config,
     const blink::CloneableMessage& serialized_data,
     std::vector<SharedStorageUrlSpecWithMetadata> urls_with_metadata,
     int worklet_id) {
   return SharedStorageEventParams::CreateForWorkletOperation(
-      operation_name, serialized_data, std::move(urls_with_metadata),
-      worklet_id);
+      operation_name, keep_alive, private_aggregation_config, serialized_data,
+      std::move(urls_with_metadata), worklet_id);
+}
+
+// static
+SharedStorageEventParams SharedStorageEventParams::CreateForSelectURLForTesting(
+    const std::string& operation_name,
+    bool keep_alive,
+    PrivateAggregationConfigWrapper config_wrapper,
+    const blink::CloneableMessage& serialized_data,
+    std::vector<SharedStorageUrlSpecWithMetadata> urls_with_metadata,
+    int worklet_id) {
+  return SharedStorageEventParams::CreateForWorkletOperationForTesting(
+      operation_name, keep_alive, std::move(config_wrapper), serialized_data,
+      std::move(urls_with_metadata), worklet_id);
 }
 
 // static
@@ -314,6 +437,8 @@
   return SharedStorageEventParams(
       script_source_url.spec(), std::move(data_origin),
       /*operation_name=*/std::nullopt,
+      /*keep_alive=*/std::nullopt,
+      /*private_aggregation_config=*/std::nullopt,
       /*serialized_data=*/std::nullopt,
       /*urls_with_metadata=*/std::nullopt,
       /*key=*/std::nullopt,
@@ -324,18 +449,43 @@
 // static
 SharedStorageEventParams SharedStorageEventParams::CreateForWorkletOperation(
     const std::string& operation_name,
+    bool keep_alive,
+    const blink::mojom::PrivateAggregationConfigPtr& private_aggregation_config,
     const blink::CloneableMessage& serialized_data,
     std::optional<std::vector<SharedStorageUrlSpecWithMetadata>>
         urls_with_metadata,
     int worklet_id) {
-  return SharedStorageEventParams(/*script_source_url=*/std::nullopt,
-                                  /*data_origin=*/std::nullopt, operation_name,
-                                  MaybeTruncateSerializedData(serialized_data),
-                                  std::move(urls_with_metadata),
-                                  /*key=*/std::nullopt,
-                                  /*value=*/std::nullopt,
-                                  /*ignore_if_present=*/std::nullopt,
-                                  worklet_id);
+  return SharedStorageEventParams(
+      /*script_source_url=*/std::nullopt,
+      /*data_origin=*/std::nullopt, operation_name, keep_alive,
+      PrivateAggregationConfigWrapper(private_aggregation_config),
+      MaybeTruncateSerializedData(serialized_data),
+      std::move(urls_with_metadata),
+      /*key=*/std::nullopt,
+      /*value=*/std::nullopt,
+      /*ignore_if_present=*/std::nullopt, worklet_id);
+}
+
+// static
+SharedStorageEventParams
+SharedStorageEventParams::CreateForWorkletOperationForTesting(
+    const std::string& operation_name,
+    bool keep_alive,
+    PrivateAggregationConfigWrapper config_wrapper,
+    const blink::CloneableMessage& serialized_data,
+    std::optional<std::vector<SharedStorageUrlSpecWithMetadata>>
+        urls_with_metadata,
+    int worklet_id) {
+  return SharedStorageEventParams(
+      /*script_source_url=*/std::nullopt,
+      /*data_origin=*/std::nullopt, operation_name, keep_alive,
+      /*private_aggregation_config=*/
+      std::make_optional(std::move(config_wrapper)),
+      MaybeTruncateSerializedData(serialized_data),
+      std::move(urls_with_metadata),
+      /*key=*/std::nullopt,
+      /*value=*/std::nullopt,
+      /*ignore_if_present=*/std::nullopt, worklet_id);
 }
 
 // static
@@ -348,6 +498,8 @@
       /*script_source_url=*/std::nullopt,
       /*data_origin=*/std::nullopt,
       /*operation_name=*/std::nullopt,
+      /*keep_alive=*/std::nullopt,
+      /*private_aggregation_config=*/std::nullopt,
       /*serialized_data*/ std::nullopt,
       /*urls_with_metadata=*/std::nullopt, std::move(key), std::move(value),
       ignore_if_present, worklet_id);
@@ -359,6 +511,8 @@
   return lhs.script_source_url == rhs.script_source_url &&
          lhs.data_origin == rhs.data_origin &&
          lhs.operation_name == rhs.operation_name &&
+         lhs.keep_alive == rhs.keep_alive &&
+         lhs.private_aggregation_config == rhs.private_aggregation_config &&
          !!lhs.serialized_data == !!rhs.serialized_data &&
          lhs.urls_with_metadata == rhs.urls_with_metadata &&
          lhs.key == rhs.key && lhs.value == rhs.value &&
@@ -372,6 +526,10 @@
      << SerializeOptionalString(params.script_source_url)
      << "; Data Origin: " << SerializeOptionalString(params.data_origin)
      << "; Operation Name: " << SerializeOptionalString(params.operation_name)
+     << "; Keep Alive: " << SerializeOptionalBool(params.keep_alive)
+     << "; Private Aggregation Config: "
+     << SerializeOptionalPrivateAggregationConfigWrapper(
+            params.private_aggregation_config)
      << "; Serialized Data: "
      << SerializeAndEscapeOptionalString(params.serialized_data)
      << "; URLs With Metadata: "
diff --git a/content/browser/shared_storage/shared_storage_event_params.h b/content/browser/shared_storage/shared_storage_event_params.h
index 6dde29b..c8b675d 100644
--- a/content/browser/shared_storage/shared_storage_event_params.h
+++ b/content/browser/shared_storage/shared_storage_event_params.h
@@ -11,7 +11,9 @@
 
 #include "content/common/content_export.h"
 #include "third_party/blink/public/common/messaging/cloneable_message.h"
+#include "third_party/blink/public/mojom/shared_storage/shared_storage.mojom.h"
 #include "url/gurl.h"
+#include "url/origin.h"
 
 namespace content {
 
@@ -19,6 +21,28 @@
 // events.
 class CONTENT_EXPORT SharedStorageEventParams {
  public:
+  // Wraps a `blink::mojom::PrivateAggregationConfig` for DevTools shared
+  // storage integration.
+  struct CONTENT_EXPORT PrivateAggregationConfigWrapper {
+    blink::mojom::PrivateAggregationConfigPtr config;
+    PrivateAggregationConfigWrapper();
+    PrivateAggregationConfigWrapper(
+        const std::optional<url::Origin>& aggregation_coordinator_origin,
+        const std::optional<std::string>& context_id,
+        uint32_t filtering_id_max_bytes,
+        std::optional<uint16_t> max_contributions);
+    explicit PrivateAggregationConfigWrapper(
+        const blink::mojom::PrivateAggregationConfigPtr& config);
+    PrivateAggregationConfigWrapper(
+        const PrivateAggregationConfigWrapper& other);
+    ~PrivateAggregationConfigWrapper();
+    PrivateAggregationConfigWrapper& operator=(
+        const PrivateAggregationConfigWrapper& other);
+    bool operator==(const PrivateAggregationConfigWrapper&) const;
+    friend std::ostream& operator<<(
+        std::ostream& os,
+        const PrivateAggregationConfigWrapper& config);
+  };
   // Bundles a URL's spec along with a map of any accompanying reporting
   // metadata for DevTools integration.
   struct CONTENT_EXPORT SharedStorageUrlSpecWithMetadata {
@@ -47,10 +71,29 @@
       int worklet_id);
   static SharedStorageEventParams CreateForRun(
       const std::string& operation_name,
+      bool keep_alive,
+      const blink::mojom::PrivateAggregationConfigPtr&
+          private_aggregation_config,
+      const blink::CloneableMessage& serialized_data,
+      int worklet_id);
+  static SharedStorageEventParams CreateForRunForTesting(
+      const std::string& operation_name,
+      bool keep_alive,
+      PrivateAggregationConfigWrapper config_wrapper,
       const blink::CloneableMessage& serialized_data,
       int worklet_id);
   static SharedStorageEventParams CreateForSelectURL(
       const std::string& operation_name,
+      bool keep_alive,
+      const blink::mojom::PrivateAggregationConfigPtr&
+          private_aggregation_config,
+      const blink::CloneableMessage& serialized_data,
+      std::vector<SharedStorageUrlSpecWithMetadata> urls_with_metadata,
+      int worklet_id);
+  static SharedStorageEventParams CreateForSelectURLForTesting(
+      const std::string& operation_name,
+      bool keep_alive,
+      PrivateAggregationConfigWrapper config_wrapper,
       const blink::CloneableMessage& serialized_data,
       std::vector<SharedStorageUrlSpecWithMetadata> urls_with_metadata,
       int worklet_id);
@@ -78,6 +121,8 @@
   std::optional<std::string> script_source_url;
   std::optional<std::string> data_origin;
   std::optional<std::string> operation_name;
+  std::optional<bool> keep_alive;
+  std::optional<PrivateAggregationConfigWrapper> private_aggregation_config;
   std::optional<std::string> serialized_data;
   std::optional<std::vector<SharedStorageUrlSpecWithMetadata>>
       urls_with_metadata;
@@ -92,6 +137,8 @@
       std::optional<std::string> script_source_url,
       std::optional<std::string> data_origin,
       std::optional<std::string> operation_name,
+      std::optional<bool> keep_alive,
+      std::optional<PrivateAggregationConfigWrapper> private_aggregation_config,
       std::optional<std::string> serialized_data,
       std::optional<std::vector<SharedStorageUrlSpecWithMetadata>>
           urls_with_metadata,
@@ -107,6 +154,17 @@
 
   static SharedStorageEventParams CreateForWorkletOperation(
       const std::string& operation_name,
+      bool keep_alive,
+      const blink::mojom::PrivateAggregationConfigPtr&
+          private_aggregation_config,
+      const blink::CloneableMessage& serialized_data,
+      std::optional<std::vector<SharedStorageUrlSpecWithMetadata>>
+          urls_with_metadata,
+      int worklet_id);
+  static SharedStorageEventParams CreateForWorkletOperationForTesting(
+      const std::string& operation_name,
+      bool keep_alive,
+      PrivateAggregationConfigWrapper config_wrapper,
       const blink::CloneableMessage& serialized_data,
       std::optional<std::vector<SharedStorageUrlSpecWithMetadata>>
           urls_with_metadata,
diff --git a/content/browser/shared_storage/shared_storage_event_params_unittest.cc b/content/browser/shared_storage/shared_storage_event_params_unittest.cc
index c39e4bbc..042d4c7e 100644
--- a/content/browser/shared_storage/shared_storage_event_params_unittest.cc
+++ b/content/browser/shared_storage/shared_storage_event_params_unittest.cc
@@ -62,8 +62,10 @@
   blink::CloneableMessage serialized_data_message;
   SetCloneableMessageWithByteArray(serialized_data_message, data);
 
-  auto params = SharedStorageEventParams::CreateForRun(
-      "test-operation", serialized_data_message, /*worklet_id=*/0);
+  auto params = SharedStorageEventParams::CreateForRunForTesting(
+      "test-operation", /*keep_alive=*/false,
+      SharedStorageEventParams::PrivateAggregationConfigWrapper(),
+      serialized_data_message, /*worklet_id=*/0);
 
   EXPECT_FALSE(base::IsStringUTF8(GetSerializedDataDirectFromBytes(params)));
 
@@ -88,8 +90,10 @@
   blink::CloneableMessage serialized_data_message;
   SetCloneableMessageWithByteArray(serialized_data_message, data);
 
-  auto params = SharedStorageEventParams::CreateForRun(
-      "test-operation", serialized_data_message, /*worklet_id=*/0);
+  auto params = SharedStorageEventParams::CreateForRunForTesting(
+      "test-operation", /*keep_alive=*/false,
+      SharedStorageEventParams::PrivateAggregationConfigWrapper(),
+      serialized_data_message, /*worklet_id=*/0);
 
   std::string serialized_data_direct_from_bytes =
       GetSerializedDataDirectFromBytes(params);
diff --git a/content/browser/shared_storage/shared_storage_private_aggregation_browsertest.cc b/content/browser/shared_storage/shared_storage_private_aggregation_browsertest.cc
index 4699687b..4d577c4 100644
--- a/content/browser/shared_storage/shared_storage_private_aggregation_browsertest.cc
+++ b/content/browser/shared_storage/shared_storage_private_aggregation_browsertest.cc
@@ -28,6 +28,7 @@
 #include "base/test/mock_callback.h"
 #include "base/test/scoped_feature_list.h"
 #include "base/time/time.h"
+#include "components/aggregation_service/aggregation_coordinator_utils.h"
 #include "content/browser/aggregation_service/aggregatable_report.h"
 #include "content/browser/private_aggregation/private_aggregation_budget_key.h"
 #include "content/browser/private_aggregation/private_aggregation_budgeter.h"
@@ -36,8 +37,10 @@
 #include "content/browser/private_aggregation/private_aggregation_pending_contributions.h"
 #include "content/browser/private_aggregation/private_aggregation_test_utils.h"
 #include "content/browser/shared_storage/shared_storage_browsertest_base.h"
+#include "content/browser/shared_storage/shared_storage_event_params.h"
 #include "content/browser/shared_storage/shared_storage_runtime_manager.h"
 #include "content/browser/shared_storage/shared_storage_worklet_host.h"
+#include "content/browser/shared_storage/test_shared_storage_observer.h"
 #include "content/browser/shared_storage/test_shared_storage_runtime_manager.h"
 #include "content/browser/shared_storage/test_shared_storage_worklet_host.h"
 #include "content/browser/storage_partition_impl.h"
@@ -76,6 +79,8 @@
 using testing::FieldsAre;
 using testing::Ne;
 using testing::Optional;
+using AccessScope = blink::SharedStorageAccessScope;
+using AccessMethod = TestSharedStorageObserver::AccessMethod;
 
 class SharedStoragePrivateAggregationDisabledBrowserTest
     : public SharedStorageBrowserTestBase {
@@ -355,6 +360,18 @@
   EXPECT_TRUE(console_observer.messages().empty());
 
   run_loop.Run();
+
+  ExpectAccessObserved(
+      {{AccessScope::kWindow, AccessMethod::kAddModule, MainFrameId(),
+        a_test_origin_.Serialize(),
+        SharedStorageEventParams::CreateForAddModule(out_script_url,
+                                                     /*worklet_id=*/0)},
+       {AccessScope::kWindow, AccessMethod::kRun, MainFrameId(),
+        a_test_origin_.Serialize(),
+        SharedStorageEventParams::CreateForRunForTesting(
+            "test-operation", /*keep_alive=*/true,
+            SharedStorageEventParams::PrivateAggregationConfigWrapper(),
+            blink::CloneableMessage(), /*worklet_id=*/0)}});
 }
 
 IN_PROC_BROWSER_TEST_F(SharedStoragePrivateAggregationEnabledBrowserTest,
@@ -574,6 +591,22 @@
   EXPECT_TRUE(console_observer.messages().empty());
 
   run_loop.Run();
+
+  ExpectAccessObserved(
+      {{AccessScope::kWindow, AccessMethod::kAddModule, MainFrameId(),
+        a_test_origin_.Serialize(),
+        SharedStorageEventParams::CreateForAddModule(out_script_url,
+                                                     /*worklet_id=*/0)},
+       {AccessScope::kWindow, AccessMethod::kRun, MainFrameId(),
+        a_test_origin_.Serialize(),
+        SharedStorageEventParams::CreateForRunForTesting(
+            "test-operation", /*keep_alive=*/true,
+            SharedStorageEventParams::PrivateAggregationConfigWrapper(
+                /*aggregation_coordinator_origin=*/std::nullopt,
+                /*context_id=*/"example_context_id",
+                /*filtering_id_max_bytes=*/1,
+                /*max_contributions=*/std::nullopt),
+            blink::CloneableMessage(), /*worklet_id=*/0)}});
 }
 
 IN_PROC_BROWSER_TEST_F(SharedStoragePrivateAggregationEnabledBrowserTest,
@@ -631,6 +664,21 @@
   EXPECT_TRUE(console_observer.messages().empty());
 
   run_loop.Run();
+
+  ExpectAccessObserved(
+      {{AccessScope::kWindow, AccessMethod::kAddModule, MainFrameId(),
+        a_test_origin_.Serialize(),
+        SharedStorageEventParams::CreateForAddModule(out_script_url,
+                                                     /*worklet_id=*/0)},
+       {AccessScope::kWindow, AccessMethod::kRun, MainFrameId(),
+        a_test_origin_.Serialize(),
+        SharedStorageEventParams::CreateForRunForTesting(
+            "test-operation", /*keep_alive=*/true,
+            SharedStorageEventParams::PrivateAggregationConfigWrapper(
+                /*aggregation_coordinator_origin=*/std::nullopt,
+                /*context_id=*/std::string(), /*filtering_id_max_bytes=*/1,
+                /*max_contributions=*/std::nullopt),
+            blink::CloneableMessage(), /*worklet_id=*/0)}});
 }
 
 IN_PROC_BROWSER_TEST_F(SharedStoragePrivateAggregationEnabledBrowserTest,
@@ -693,6 +741,23 @@
   EXPECT_TRUE(console_observer.messages().empty());
 
   run_loop.Run();
+
+  ExpectAccessObserved(
+      {{AccessScope::kWindow, AccessMethod::kAddModule, MainFrameId(),
+        a_test_origin_.Serialize(),
+        SharedStorageEventParams::CreateForAddModule(out_script_url,
+                                                     /*worklet_id=*/0)},
+       {AccessScope::kWindow, AccessMethod::kRun, MainFrameId(),
+        a_test_origin_.Serialize(),
+        SharedStorageEventParams::CreateForRunForTesting(
+            "test-operation", /*keep_alive=*/true,
+            SharedStorageEventParams::PrivateAggregationConfigWrapper(
+                /*aggregation_coordinator_origin=*/std::nullopt, /*context_id=*/
+                "an_example_of_a_context_id_with_the_exact_maximum_allowed_"
+                "length",
+                /*filtering_id_max_bytes=*/1,
+                /*max_contributions=*/std::nullopt),
+            blink::CloneableMessage(), /*worklet_id=*/0)}});
 }
 
 IN_PROC_BROWSER_TEST_F(SharedStoragePrivateAggregationEnabledBrowserTest,
@@ -1242,6 +1307,21 @@
   EXPECT_TRUE(console_observer.messages().empty());
 
   run_loop.Run();
+
+  ExpectAccessObserved(
+      {{AccessScope::kWindow, AccessMethod::kAddModule, MainFrameId(),
+        a_test_origin_.Serialize(),
+        SharedStorageEventParams::CreateForAddModule(out_script_url,
+                                                     /*worklet_id=*/0)},
+       {AccessScope::kWindow, AccessMethod::kRun, MainFrameId(),
+        a_test_origin_.Serialize(),
+        SharedStorageEventParams::CreateForRunForTesting(
+            "test-operation", /*keep_alive=*/true,
+            SharedStorageEventParams::PrivateAggregationConfigWrapper(
+                /*aggregation_coordinator_origin=*/std::nullopt,
+                /*context_id=*/std::nullopt, /*filtering_id_max_bytes=*/8,
+                /*max_contributions=*/std::nullopt),
+            blink::CloneableMessage(), /*worklet_id=*/0)}});
 }
 
 IN_PROC_BROWSER_TEST_F(SharedStoragePrivateAggregationEnabledBrowserTest,
@@ -1317,6 +1397,21 @@
   EXPECT_TRUE(console_observer.messages().empty());
 
   run_loop.Run();
+
+  ExpectAccessObserved(
+      {{AccessScope::kWindow, AccessMethod::kAddModule, MainFrameId(),
+        a_test_origin_.Serialize(),
+        SharedStorageEventParams::CreateForAddModule(out_script_url,
+                                                     /*worklet_id=*/0)},
+       {AccessScope::kWindow, AccessMethod::kRun, MainFrameId(),
+        a_test_origin_.Serialize(),
+        SharedStorageEventParams::CreateForRunForTesting(
+            "test-operation", /*keep_alive=*/true,
+            SharedStorageEventParams::PrivateAggregationConfigWrapper(
+                /*aggregation_coordinator_origin=*/std::nullopt,
+                /*context_id=*/std::nullopt, /*filtering_id_max_bytes=*/8,
+                /*max_contributions=*/std::nullopt),
+            blink::CloneableMessage(), /*worklet_id=*/0)}});
 }
 
 IN_PROC_BROWSER_TEST_F(SharedStoragePrivateAggregationEnabledBrowserTest,
@@ -1383,6 +1478,21 @@
   EXPECT_TRUE(console_observer.messages().empty());
 
   run_loop.Run();
+
+  ExpectAccessObserved(
+      {{AccessScope::kWindow, AccessMethod::kAddModule, MainFrameId(),
+        a_test_origin_.Serialize(),
+        SharedStorageEventParams::CreateForAddModule(out_script_url,
+                                                     /*worklet_id=*/0)},
+       {AccessScope::kWindow, AccessMethod::kRun, MainFrameId(),
+        a_test_origin_.Serialize(),
+        SharedStorageEventParams::CreateForRunForTesting(
+            "test-operation", /*keep_alive=*/true,
+            SharedStorageEventParams::PrivateAggregationConfigWrapper(
+                /*aggregation_coordinator_origin=*/std::nullopt,
+                /*context_id=*/std::nullopt, /*filtering_id_max_bytes=*/8,
+                /*max_contributions=*/std::nullopt),
+            blink::CloneableMessage(), /*worklet_id=*/0)}});
 }
 
 IN_PROC_BROWSER_TEST_F(SharedStoragePrivateAggregationEnabledBrowserTest,
@@ -2065,6 +2175,30 @@
       /*max_contributions=*/base::NumberToString(GetParam().max_contributions));
   EXPECT_TRUE(console_observer.messages().empty());
   run_loop.Run();
+
+  std::optional<uint16_t> expected_max_contributions;
+  if (GetParam().is_feature_enabled) {
+    expected_max_contributions = static_cast<uint16_t>(
+        std::min(GetParam().max_contributions,
+                 static_cast<size_t>(std::numeric_limits<uint16_t>::max())));
+  }
+  auto expected_config_to_observe =
+      SharedStorageEventParams::PrivateAggregationConfigWrapper(
+          /*aggregation_coordinator_origin=*/std::nullopt,
+          /*context_id=*/"example_context_id",
+          /*filtering_id_max_bytes=*/1,
+          /*max_contributions=*/expected_max_contributions);
+  ExpectAccessObserved(
+      {{AccessScope::kWindow, AccessMethod::kAddModule, MainFrameId(),
+        a_test_origin_.Serialize(),
+        SharedStorageEventParams::CreateForAddModule(out_script_url,
+                                                     /*worklet_id=*/0)},
+       {AccessScope::kWindow, AccessMethod::kRun, MainFrameId(),
+        a_test_origin_.Serialize(),
+        SharedStorageEventParams::CreateForRunForTesting(
+            "test-operation", /*keep_alive=*/true, expected_config_to_observe,
+            blink::CloneableMessage(),
+            /*worklet_id=*/0)}});
 }
 
 class SharedStoragePrivateAggregationErrorReportingDisabledBrowserTest
@@ -2993,6 +3127,247 @@
   run_loop.Run();
 }
 
+IN_PROC_BROWSER_TEST_F(SharedStoragePrivateAggregationEnabledBrowserTest,
+                       RunWithExplicitAggregationCoordinatorOriginValue) {
+  WebContentsConsoleObserver console_observer(shell()->web_contents());
+
+  base::RunLoop run_loop;
+
+  EXPECT_CALL(mock_callback(), Run)
+      .WillOnce(testing::Invoke(
+          [&](PrivateAggregationHost::ReportRequestGenerator generator,
+              PrivateAggregationPendingContributions::Wrapper contributions,
+              PrivateAggregationBudgetKey budget_key,
+              PrivateAggregationHost::NullReportBehavior null_report_behavior) {
+            AggregatableReportRequest request = GenerateReportRequest(
+                std::move(generator), std::move(contributions),
+                null_report_behavior);
+            ASSERT_EQ(request.payload_contents().contributions.size(), 1u);
+            EXPECT_EQ(request.payload_contents().contributions[0].bucket, 1);
+            EXPECT_EQ(request.payload_contents().contributions[0].value, 2);
+            EXPECT_EQ(request.shared_info().reporting_origin, a_test_origin_);
+            EXPECT_EQ(budget_key.origin(), a_test_origin_);
+            EXPECT_EQ(budget_key.caller_api(),
+                      PrivateAggregationCallerApi::kSharedStorage);
+            EXPECT_TRUE(request.additional_fields().empty());
+            EXPECT_EQ(
+                null_report_behavior,
+                PrivateAggregationHost::NullReportBehavior::kDontSendReport);
+            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));
+  ON_CALL(browser_client(), IsPrivateAggregationAllowed)
+      .WillByDefault(testing::Return(true));
+  ON_CALL(browser_client(), IsSharedStorageAllowed)
+      .WillByDefault(testing::Return(true));
+
+  std::string worklet_script = R"(
+      privateAggregation.contributeToHistogram({bucket: 1n, value: 2});
+    )";
+
+  base::StringPairs run_function_body_replacement;
+  run_function_body_replacement.emplace_back("{{RUN_FUNCTION_BODY}}",
+                                             worklet_script);
+
+  RenderFrameHost* rfh = shell()->web_contents()->GetPrimaryMainFrame();
+  std::string host = rfh->GetLastCommittedOrigin().host();
+
+  GURL out_script_url = https_server()->GetURL(
+      host, net::test_server::GetFilePathWithReplacements(
+                "/shared_storage/customizable_module.js",
+                run_function_body_replacement));
+
+  EXPECT_TRUE(ExecJs(shell(), JsReplace("sharedStorage.worklet.addModule($1)",
+                                        out_script_url)));
+
+  auto* worklet_host =
+      test_runtime_manager().GetLastAttachedWorkletHostForFrameWithScriptSrc(
+          rfh, out_script_url);
+  ASSERT_TRUE(worklet_host);
+
+  EXPECT_EQ(1u, test_runtime_manager().GetAttachedWorkletHostsCount());
+
+  EXPECT_EQ(worklet_host->creation_method(),
+            blink::mojom::SharedStorageWorkletCreationMethod::kAddModule);
+
+  // There is 1 more "worklet operation": `run()`.
+  worklet_host->SetExpectedWorkletResponsesCount(1);
+
+  std::string private_aggregation_config_js = JsReplace(
+      "privateAggregationConfig: {aggregationCoordinatorOrigin: $1, }",
+      aggregation_service::GetDefaultAggregationCoordinatorOrigin()
+          .Serialize());
+
+  std::string run_operation_script =
+      base::StrCat({"sharedStorage.run('test-operation', {",
+                    private_aggregation_config_js, "});"});
+
+  EXPECT_TRUE(ExecJs(shell(), run_operation_script));
+
+  CHECK(worklet_host);
+  worklet_host->WaitForWorkletResponses();
+
+  EXPECT_TRUE(console_observer.messages().empty());
+
+  run_loop.Run();
+
+  ExpectAccessObserved(
+      {{AccessScope::kWindow, AccessMethod::kAddModule, MainFrameId(),
+        a_test_origin_.Serialize(),
+        SharedStorageEventParams::CreateForAddModule(out_script_url,
+                                                     /*worklet_id=*/0)},
+       {AccessScope::kWindow, AccessMethod::kRun, MainFrameId(),
+        a_test_origin_.Serialize(),
+        SharedStorageEventParams::CreateForRunForTesting(
+            "test-operation", /*keep_alive=*/false,
+            SharedStorageEventParams::PrivateAggregationConfigWrapper(
+                /*aggregation_coordinator_origin=*/aggregation_service::
+                    GetDefaultAggregationCoordinatorOrigin(),
+                /*context_id=*/std::nullopt, /*filtering_id_max_bytes=*/1,
+                /*max_contributions=*/std::nullopt),
+            blink::CloneableMessage(), /*worklet_id=*/0)}});
+}
+
+IN_PROC_BROWSER_TEST_F(SharedStoragePrivateAggregationEnabledBrowserTest,
+                       SelectURLWithExplicitAggregationCoordinatorOriginValue) {
+  WebContentsConsoleObserver console_observer(shell()->web_contents());
+
+  base::RunLoop run_loop;
+
+  EXPECT_CALL(mock_callback(), Run)
+      .WillOnce(testing::Invoke(
+          [&](PrivateAggregationHost::ReportRequestGenerator generator,
+              PrivateAggregationPendingContributions::Wrapper contributions,
+              PrivateAggregationBudgetKey budget_key,
+              PrivateAggregationHost::NullReportBehavior null_report_behavior) {
+            AggregatableReportRequest request = GenerateReportRequest(
+                std::move(generator), std::move(contributions),
+                null_report_behavior);
+            ASSERT_EQ(request.payload_contents().contributions.size(), 1u);
+            EXPECT_EQ(request.payload_contents().contributions[0].bucket, 1);
+            EXPECT_EQ(request.payload_contents().contributions[0].value, 2);
+            EXPECT_EQ(request.shared_info().reporting_origin, a_test_origin_);
+            EXPECT_EQ(budget_key.origin(), a_test_origin_);
+            EXPECT_EQ(budget_key.caller_api(),
+                      PrivateAggregationCallerApi::kSharedStorage);
+            EXPECT_TRUE(request.additional_fields().empty());
+            EXPECT_EQ(
+                null_report_behavior,
+                PrivateAggregationHost::NullReportBehavior::kDontSendReport);
+            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));
+  ON_CALL(browser_client(), IsPrivateAggregationAllowed)
+      .WillByDefault(testing::Return(true));
+  ON_CALL(browser_client(), IsSharedStorageAllowed)
+      .WillByDefault(testing::Return(true));
+
+  std::string worklet_script = R"(
+      privateAggregation.contributeToHistogram({bucket: 1n, value: 2});
+    )";
+
+  base::StringPairs run_function_body_replacement;
+  run_function_body_replacement.emplace_back("{{RUN_FUNCTION_BODY}}",
+                                             worklet_script);
+
+  RenderFrameHost* rfh = shell()->web_contents()->GetPrimaryMainFrame();
+  std::string host = rfh->GetLastCommittedOrigin().host();
+
+  GURL out_script_url = https_server()->GetURL(
+      host, net::test_server::GetFilePathWithReplacements(
+                "/shared_storage/customizable_selecturl_module.js",
+                run_function_body_replacement));
+
+  EXPECT_TRUE(ExecJs(shell(), JsReplace("sharedStorage.worklet.addModule($1)",
+                                        out_script_url)));
+
+  auto* worklet_host =
+      test_runtime_manager().GetLastAttachedWorkletHostForFrameWithScriptSrc(
+          rfh, out_script_url);
+  ASSERT_TRUE(worklet_host);
+
+  EXPECT_EQ(1u, test_runtime_manager().GetAttachedWorkletHostsCount());
+
+  EXPECT_EQ(worklet_host->creation_method(),
+            blink::mojom::SharedStorageWorkletCreationMethod::kAddModule);
+
+  // There is 1 more "worklet operation": `selectURL()`.
+  worklet_host->SetExpectedWorkletResponsesCount(1);
+
+  std::string select_url_operation_script = JsReplace(
+      R"(
+      (async function() {
+        window.select_url_result = await sharedStorage.selectURL(
+          'test-url-selection-operation',
+          [
+            {
+              url: "fenced_frames/title0.html"
+            }
+          ],
+          {
+            privateAggregationConfig: {aggregationCoordinatorOrigin: $1},
+            resolveToConfig: true
+          }
+        );
+        if (!(select_url_result instanceof FencedFrameConfig)) {
+          throw new Error('selectURL() did not return a FencedFrameConfig.');
+        }
+        return window.select_url_result;
+      })()
+    )",
+      aggregation_service::GetDefaultAggregationCoordinatorOrigin()
+          .Serialize());
+
+  EvalJsResult result = EvalJs(shell(), select_url_operation_script);
+  EXPECT_TRUE(result.error.empty());
+
+  CHECK(worklet_host);
+  worklet_host->WaitForWorkletResponses();
+
+  EXPECT_TRUE(console_observer.messages().empty());
+
+  run_loop.Run();
+
+  ExpectAccessObserved(
+      {{AccessScope::kWindow, AccessMethod::kAddModule, MainFrameId(),
+        a_test_origin_.Serialize(),
+        SharedStorageEventParams::CreateForAddModule(out_script_url,
+                                                     /*worklet_id=*/0)},
+       {AccessScope::kWindow, AccessMethod::kSelectURL, MainFrameId(),
+        a_test_origin_.Serialize(),
+        SharedStorageEventParams::CreateForSelectURLForTesting(
+            "test-url-selection-operation", /*keep_alive=*/false,
+            SharedStorageEventParams::PrivateAggregationConfigWrapper(
+                /*aggregation_coordinator_origin=*/aggregation_service::
+                    GetDefaultAggregationCoordinatorOrigin(),
+                /*context_id=*/std::nullopt, /*filtering_id_max_bytes=*/1,
+                /*max_contributions=*/std::nullopt),
+            blink::CloneableMessage(),
+            std::vector<
+                SharedStorageEventParams::SharedStorageUrlSpecWithMetadata>(
+                {{https_server()->GetURL(host, "/fenced_frames/title0.html"),
+                  {}}}),
+            /*worklet_id=*/0)}});
+}
+
 // TODO(alexmt): Consider testing that reserved.uncaught-exception not triggered
 // for selectURL if the incorrect type is returned.
 
diff --git a/content/browser/shared_storage/shared_storage_worklet_host.cc b/content/browser/shared_storage/shared_storage_worklet_host.cc
index 9b8985b..54454c6c 100644
--- a/content/browser/shared_storage/shared_storage_worklet_host.cc
+++ b/content/browser/shared_storage/shared_storage_worklet_host.cc
@@ -807,7 +807,8 @@
       AccessScope::kWindow, AccessMethod::kSelectURL,
       document_service_->main_frame_id(), shared_storage_origin_.Serialize(),
       SharedStorageEventParams::CreateForSelectURL(
-          name, serialized_data, std::move(converted_urls), worklet_id_));
+          name, keep_alive_after_operation, private_aggregation_config,
+          serialized_data, std::move(converted_urls), worklet_id_));
 
   if (saved_queries_enabled_ && !saved_query_name.empty()) {
     auto saved_query_callback = base::BindOnce(
@@ -959,8 +960,9 @@
   shared_storage_runtime_manager_->NotifySharedStorageAccessed(
       AccessScope::kWindow, AccessMethod::kRun,
       document_service_->main_frame_id(), shared_storage_origin_.Serialize(),
-      SharedStorageEventParams::CreateForRun(name, serialized_data,
-                                             worklet_id_));
+      SharedStorageEventParams::CreateForRun(name, keep_alive_after_operation,
+                                             private_aggregation_config,
+                                             serialized_data, worklet_id_));
 
   GetAndConnectToSharedStorageWorkletService()->RunOperation(
       name, std::move(serialized_data),
diff --git a/content/browser/web_contents/web_contents_observer_browsertest.cc b/content/browser/web_contents/web_contents_observer_browsertest.cc
index 74294586..ce45aff9 100644
--- a/content/browser/web_contents/web_contents_observer_browsertest.cc
+++ b/content/browser/web_contents/web_contents_observer_browsertest.cc
@@ -7,6 +7,7 @@
 #include "base/test/scoped_feature_list.h"
 #include "base/test/test_future.h"
 #include "build/build_config.h"
+#include "components/network_session_configurator/common/network_switches.h"
 #include "content/browser/renderer_host/render_frame_host_impl.h"
 #include "content/browser/service_worker/embedded_worker_instance.h"
 #include "content/browser/web_contents/web_contents_impl.h"
@@ -20,15 +21,20 @@
 #include "content/public/test/browser_test_utils.h"
 #include "content/public/test/content_browser_test.h"
 #include "content/public/test/content_browser_test_content_browser_client.h"
+#include "content/public/test/content_mock_cert_verifier.h"
 #include "content/public/test/mock_web_contents_observer.h"
 #include "content/public/test/test_utils.h"
 #include "content/shell/browser/shell.h"
 #include "net/base/features.h"
+#include "net/cert/cert_verify_result.h"
 #include "net/cookies/cookie_access_result.h"
 #include "net/cookies/cookie_constants.h"
 #include "net/cookies/site_for_cookies.h"
 #include "net/dns/mock_host_resolver.h"
+#include "net/test/cert_test_util.h"
 #include "net/test/embedded_test_server/default_handlers.h"
+#include "net/test/quic_simple_test_server.h"
+#include "net/test/test_data_directory.h"
 #include "testing/gmock/include/gmock/gmock-matchers.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
@@ -988,17 +994,62 @@
 }  // namespace
 
 // Tests for the CookieAccessDetails::source reported by
-// WebContentsObserver::OnCookiesAccessed().
+// WebContentsObserver::OnCookiesAccessed(). Some tests use QuicSimpleTestServer
+// because Early Hints are only plumbed over HTTP/2 or HTTP/3 (QUIC).
 class CookieSourceBrowserTest : public ContentBrowserTest {
  public:
+  CookieSourceBrowserTest() {
+    feature_list_.InitWithFeatures(
+        std::vector<base::test::FeatureRef>{
+            net::features::kSplitCacheByNetworkIsolationKey},
+        std::vector<base::test::FeatureRef>{
+            net::features::kMigrateSessionsOnNetworkChangeV2});
+  }
+
+  WebContents* web_contents() { return shell()->web_contents(); }
+
   void SetUpOnMainThread() override {
     host_resolver()->AddRule("*", "127.0.0.1");
     embedded_https_test_server().SetSSLConfig(
         net::EmbeddedTestServer::CERT_TEST_NAMES);
     ASSERT_TRUE(embedded_https_test_server().Start());
+
+    // Configure the certificate for the QUIC server.
+    auto test_cert =
+        net::ImportCertFromFile(net::GetTestCertsDirectory(), "quic-chain.pem");
+    net::CertVerifyResult verify_result;
+    verify_result.verified_cert = test_cert;
+    mock_cert_verifier_.mock_cert_verifier()->AddResultForCert(
+        test_cert, verify_result, net::OK);
+    mock_cert_verifier_.mock_cert_verifier()->set_default_result(net::OK);
   }
 
-  WebContents* web_contents() { return shell()->web_contents(); }
+  void SetUpCommandLine(base::CommandLine* command_line) override {
+    ASSERT_TRUE(net::QuicSimpleTestServer::Start());
+    command_line->AppendSwitchASCII(
+        switches::kOriginToForceQuicOn,
+        net::QuicSimpleTestServer::GetHostPort().ToString());
+    mock_cert_verifier_.SetUpCommandLine(command_line);
+  }
+
+  void TearDown() override {
+    // Needed by net::QuicSimpleTestServer::Shutdown() below.
+    base::ScopedAllowBaseSyncPrimitivesForTesting allow_wait;
+    net::QuicSimpleTestServer::Shutdown();
+  }
+
+  void SetUpInProcessBrowserTestFixture() override {
+    mock_cert_verifier_.SetUpInProcessBrowserTestFixture();
+  }
+
+  void TearDownInProcessBrowserTestFixture() override {
+    mock_cert_verifier_.TearDownInProcessBrowserTestFixture();
+  }
+
+ private:
+  base::test::ScopedFeatureList feature_list_;
+
+  ContentMockCertVerifier mock_cert_verifier_;
 };
 
 IN_PROC_BROWSER_TEST_F(CookieSourceBrowserTest, NavigationCookie) {
@@ -1183,4 +1234,50 @@
             CookieAccessDetails::Source::kNonNavigation);
 }
 
+IN_PROC_BROWSER_TEST_F(CookieSourceBrowserTest, EarlyHints) {
+  // Register a response for /early.css
+  {
+    quiche::HttpHeaderBlock headers;
+    headers[":path"] = "/early.css";
+    headers[":status"] = base::ToString(net::HTTP_OK);
+    headers["content-type"] = "text/css";
+    headers["cache-control"] = "max-age=3600";
+    headers["set-cookie"] = "foo=bar;";
+    net::QuicSimpleTestServer::AddResponse("/early.css", std::move(headers),
+                                           "/* empty */");
+  }
+
+  // Register a response for /early_hints.html
+  {
+    quiche::HttpHeaderBlock headers;
+    headers[":path"] = "/early_hints.html";
+    headers[":status"] = base::ToString(net::HTTP_OK);
+    headers["content-type"] = "text/html";
+
+    // Early Hint header.
+    quiche::HttpHeaderBlock early_hint_header;
+    early_hint_header["link"] = "</early.css>; rel=preload; as=style";
+    std::vector<quiche::HttpHeaderBlock> early_hints;
+    early_hints.push_back(std::move(early_hint_header));
+
+    net::QuicSimpleTestServer::AddResponseWithEarlyHints(
+        /*path=*/"/early_hints.html",
+        /*response_headers=*/std::move(headers),
+        /*response_body=*/
+        R"(<link rel="stylesheet" href="/early.css" />)",
+        /*early_hints=*/std::move(early_hints));
+  }
+  const GURL page_url =
+      net::QuicSimpleTestServer::GetFileURL("/early_hints.html");
+  const GURL early_url = net::QuicSimpleTestServer::GetFileURL("/early.css");
+
+  CookieObserver observer(web_contents(), early_url,
+                          CookieAccessDetails::Type::kChange);
+  EXPECT_TRUE(NavigateToURL(web_contents(), page_url));
+  ASSERT_TRUE(observer.Wait());
+  EXPECT_EQ(observer.details().url, early_url);
+  EXPECT_EQ(observer.details().source,
+            CookieAccessDetails::Source::kNonNavigation);
+}
+
 }  // namespace content
diff --git a/content/browser/webid/webid_browsertest.cc b/content/browser/webid/webid_browsertest.cc
index 3b5c720e..4ac261d04 100644
--- a/content/browser/webid/webid_browsertest.cc
+++ b/content/browser/webid/webid_browsertest.cc
@@ -1904,35 +1904,6 @@
   }
 };
 
-// Verify that using mode: button in the API call logs to console.
-IN_PROC_BROWSER_TEST_F(WebIdModeBrowserTest, UseModeButtonInsteadOfActive) {
-  idp_server()->SetConfigResponseDetails(BuildValidConfigDetails());
-
-  std::string script = R"(
-        (async () => {
-          var x = (await navigator.credentials.get({
-            identity: {
-              providers: [{
-                configURL: ')" +
-                       BaseIdpUrl() + R"(',
-                clientId: 'client_id_1',
-                nonce: '12345',
-              }],
-              mode: 'button'
-            },
-          }));
-          return x.token;
-        }) ()
-    )";
-
-  WebContentsConsoleObserver console_observer(shell()->web_contents());
-  console_observer.SetPattern(
-      "The mode button/widget are renamed to active/passive respectively and "
-      "will be deprecated soon.");
-  EXPECT_EQ(std::string(kToken), EvalJs(shell(), script));
-  ASSERT_TRUE(console_observer.Wait());
-}
-
 std::vector<uint8_t> TestSha256(std::string_view data) {
   std::string str = crypto::SHA256HashString(data);
   std::vector<uint8_t> result(str.begin(), str.end());
@@ -2059,7 +2030,7 @@
                 clientId: 'client_id_1',
                 nonce: '12345',
               }],
-              mode: 'button'
+              mode: 'active'
             },
           }));
           return x.token;
diff --git a/content/browser/worker_host/dedicated_worker_host.cc b/content/browser/worker_host/dedicated_worker_host.cc
index 585138b..e9fe38c 100644
--- a/content/browser/worker_host/dedicated_worker_host.cc
+++ b/content/browser/worker_host/dedicated_worker_host.cc
@@ -113,13 +113,6 @@
 
   scoped_process_host_observation_.Observe(worker_process_host_.get());
 
-  if (!base::FeatureList::IsEnabled(blink::features::kPlzDedicatedWorker)) {
-    // This is a workaround to make the worker's COEP have a value when
-    // PlzDedicatedWorker is disabled. When the feature is enabled, The value is
-    // initialized in DedicatedWorkerHost::DidStartScriptLoad().
-    worker_client_security_state_ = creator_client_security_state_->Clone();
-  }
-
   service_->NotifyWorkerCreated(this);
 
   auto* ancestor_render_frame_host =
@@ -166,9 +159,7 @@
 
   service_->NotifyBeforeWorkerDestroyed(token_, creator_);
 
-  if (base::FeatureList::IsEnabled(blink::features::kPlzDedicatedWorker)) {
-    WorkerDevToolsManager::GetInstance().WorkerDestroyed(this);
-  }
+  WorkerDevToolsManager::GetInstance().WorkerDestroyed(this);
 }
 
 void DedicatedWorkerHost::BindBrowserInterfaceBrokerReceiver(
@@ -234,7 +225,6 @@
   TRACE_EVENT("loading", "DedicatedWorkerHost::StartScriptLoad", "script_url",
               script_url);
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
-  DCHECK(base::FeatureList::IsEnabled(blink::features::kPlzDedicatedWorker));
 
   DCHECK(!client_);
   DCHECK(client);
@@ -366,7 +356,6 @@
 void DedicatedWorkerHost::DidStartScriptLoad(
     std::optional<WorkerScriptFetcherResult> result) {
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
-  DCHECK(base::FeatureList::IsEnabled(blink::features::kPlzDedicatedWorker));
   TRACE_EVENT_NESTABLE_ASYNC_END0(
       "loading", "WorkerScriptFetcher CreateAndStart", TRACE_ID_LOCAL(this));
   TRACE_EVENT("loading", "DedicatedWorkerHost::DidStartScriptLoad",
@@ -564,7 +553,6 @@
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
   DCHECK(ancestor_render_frame_host);
   DCHECK(bypass_redirect_checks);
-  DCHECK(base::FeatureList::IsEnabled(blink::features::kPlzDedicatedWorker));
 
   mojo::PendingRemote<network::mojom::CrossOriginEmbedderPolicyReporter>
       coep_reporter;
@@ -625,7 +613,6 @@
 // [spec]
 // https://html.spec.whatwg.org/C/#check-a-global-object's-embedder-policy
 bool DedicatedWorkerHost::CheckCOEP() {
-  DCHECK(base::FeatureList::IsEnabled(blink::features::kPlzDedicatedWorker));
   DCHECK(final_response_url_);
 
   if (!creator_coep_reporter_) {
@@ -763,9 +750,6 @@
 void DedicatedWorkerHost::CreateNestedDedicatedWorker(
     mojo::PendingReceiver<blink::mojom::DedicatedWorkerHostFactory> receiver) {
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
-  // For the non-PlzDedicatedWorker case, use ancestor's COEP reporter as a
-  // `creator_coep_reporter` to keep the current behavior, but it's not aligned
-  // with the spec.
   base::WeakPtr<CrossOriginEmbedderPolicyReporter> creator_coep_reporter =
       GetWorkerCoepReporter();
 
@@ -922,8 +906,6 @@
 
 void DedicatedWorkerHost::ObserveNetworkServiceCrash(
     StoragePartitionImpl* storage_partition_impl) {
-  DCHECK(base::FeatureList::IsEnabled(blink::features::kPlzDedicatedWorker));
-
   auto params = network::mojom::URLLoaderFactoryParams::New();
   params->process_id = worker_process_host_->GetDeprecatedID();
   params->debug_tag = "DedicatedWorkerHost::ObserveNetworkServiceCrash";
@@ -942,7 +924,6 @@
   DCHECK(subresource_loader_updater_.is_bound());
   DCHECK(network_service_connection_error_handler_holder_);
   DCHECK(!network_service_connection_error_handler_holder_.is_connected());
-  DCHECK(base::FeatureList::IsEnabled(blink::features::kPlzDedicatedWorker));
 
   auto* storage_partition_impl = static_cast<StoragePartitionImpl*>(
       worker_process_host_->GetStoragePartition());
@@ -1001,113 +982,10 @@
       std::move(subresource_loader_factories));
 }
 
-void DedicatedWorkerHost::MaybeCountWebFeature(const GURL& script_url) {
-  DCHECK_CURRENTLY_ON(BrowserThread::UI);
-  DCHECK(!base::FeatureList::IsEnabled(blink::features::kPlzDedicatedWorker));
-
-  RenderFrameHostImpl* ancestor_render_frame_host =
-      RenderFrameHostImpl::FromID(ancestor_render_frame_host_id_);
-  if (!ancestor_render_frame_host) {
-    return;
-  }
-
-  base::WeakPtr<ServiceWorkerClient> service_worker_client =
-      ancestor_render_frame_host->GetLastCommittedServiceWorkerClient();
-  if (!service_worker_client || !service_worker_client->controller()) {
-    return;
-  }
-
-  if (!blink::ServiceWorkerScopeMatches(
-          service_worker_client->controller()->scope(), script_url) ||
-      service_worker_client->key() != storage_key_) {
-    // Count the number of dedicated workers that 1) are controlled by a service
-    // worker that is inherited from a controlled document, and 2) will not be
-    // controlled by that service worker after PlzDedicatedWorker is enabled.
-    service_worker_client->CountFeature(
-        blink::mojom::WebFeature::kWorkerControlledByServiceWorkerOutOfScope);
-
-    DCHECK_NE(service_worker_client->controller()->fetch_handler_existence(),
-              ServiceWorkerVersion::FetchHandlerExistence::UNKNOWN);
-    if (service_worker_client->controller()->fetch_handler_existence() ==
-        ServiceWorkerVersion::FetchHandlerExistence::EXISTS) {
-      // Count the number of dedicated workers that 1) are controlled by a
-      // service worker that is inherited from a controlled document, 2) will
-      // not be controlled by that service worker after PlzDedicatedWorker is
-      // enabled, and 3) have a fetch event handler.
-      // `kControlledWorkerWillBeUncontrolled` excludes the cases if a
-      // dedicated worker is controlled by any registered service worker.
-      service_worker_client->CountFeature(
-          blink::mojom::WebFeature::
-              kWorkerControlledByServiceWorkerWithFetchEventHandlerOutOfScope);
-
-      ServiceWorkerContextWrapper* service_worker_context =
-          static_cast<StoragePartitionImpl*>(
-              worker_process_host_->GetStoragePartition())
-              ->GetServiceWorkerContext();
-      if (!service_worker_context) {
-        return;
-      }
-
-      service_worker_context->GetRegistrationsForStorageKey(
-          blink::StorageKey::CreateFirstParty(
-              ancestor_render_frame_host->GetLastCommittedOrigin()),
-          base::BindOnce(&DedicatedWorkerHost::ContinueOnMaybeCountWebFeature,
-                         weak_factory_.GetWeakPtr(), script_url,
-                         std::move(service_worker_client)));
-    }
-  }
-}
-
-void DedicatedWorkerHost::ContinueOnMaybeCountWebFeature(
-    const GURL& script_url,
-    base::WeakPtr<ServiceWorkerClient> ancestor_service_worker_client,
-    blink::ServiceWorkerStatusCode status,
-    const std::vector<scoped_refptr<ServiceWorkerRegistration>>&
-        registrations) {
-  DCHECK(!base::FeatureList::IsEnabled(blink::features::kPlzDedicatedWorker));
-  if (!ancestor_service_worker_client ||
-      status != blink::ServiceWorkerStatusCode::kOk) {
-    return;
-  }
-
-  for (const auto& registration : registrations) {
-    // Do not record the UseCounter because a dedicated worker is in scope of
-    // one of service workers registered for the origin. The scope matched
-    // service worker may be different from the one that controls the ancestor
-    // frame.
-    if (blink::ServiceWorkerScopeMatches(registration->scope(), script_url) &&
-        registration->key() == storage_key_) {
-      return;
-    }
-  }
-
-  // Count the number of dedicated workers that are not controlled by any
-  // service worker registered for the origin after PlzDedicatedWorker is
-  // enabled.
-  ancestor_service_worker_client->CountFeature(
-      blink::mojom::WebFeature::kControlledWorkerWillBeUncontrolled);
-
-  // Exclude the cases that `script_url` is a blob URL from
-  // kControlledWorkerWillBeUncontrolled.
-  if (!script_url.SchemeIsBlob()) {
-    ancestor_service_worker_client->CountFeature(
-        blink::mojom::WebFeature::
-            kControlledNonBlobURLWorkerWillBeUncontrolled);
-  }
-}
-
 base::WeakPtr<CrossOriginEmbedderPolicyReporter>
 DedicatedWorkerHost::GetWorkerCoepReporter() {
-  if (base::FeatureList::IsEnabled(blink::features::kPlzDedicatedWorker)) {
-    DCHECK(coep_reporter_);
-    return coep_reporter_->GetWeakPtr();
-  }
-  // For the non-PlzDedicatedWorker case, use ancestor's COEP reporter to keep
-  // the current behavior, but it's not aligned with the spec.
-  // `ancestor_coep_reporter_` is possible to be nullptr, which means the
-  // ancestor render frame has already been closed or navigated and this worker
-  // will also be terminated soon.
-  return ancestor_coep_reporter_;
+  DCHECK(coep_reporter_);
+  return coep_reporter_->GetWeakPtr();
 }
 
 void DedicatedWorkerHost::EvictFromBackForwardCache(
diff --git a/content/browser/worker_host/dedicated_worker_host.h b/content/browser/worker_host/dedicated_worker_host.h
index e305bcef..c079ef6 100644
--- a/content/browser/worker_host/dedicated_worker_host.h
+++ b/content/browser/worker_host/dedicated_worker_host.h
@@ -73,7 +73,6 @@
 class DedicatedWorkerServiceImpl;
 class ServiceWorkerClient;
 class ServiceWorkerMainResourceHandle;
-class ServiceWorkerRegistration;
 class StoragePartitionImpl;
 struct WorkerScriptFetcherResult;
 
@@ -179,7 +178,6 @@
   void BindHidService(mojo::PendingReceiver<blink::mojom::HidService> receiver);
 #endif
 
-  // PlzDedicatedWorker:
   void StartScriptLoad(
       const GURL& script_url,
       network::mojom::CredentialsMode credentials_mode,
@@ -191,18 +189,6 @@
 
   void ReportNoBinderForInterface(const std::string& error);
 
-  // TODO(crbug.com/40093136): Remove this method once PlzDedicatedWorker is
-  // enabled by default.
-  void MaybeCountWebFeature(const GURL& script_url);
-  // TODO(crbug.com/40093136): Remove this method once PlzDedicatedWorker is
-  // enabled by default.
-  void ContinueOnMaybeCountWebFeature(
-      const GURL& script_url,
-      base::WeakPtr<ServiceWorkerClient> service_worker_client,
-      blink::ServiceWorkerStatusCode status,
-      const std::vector<scoped_refptr<ServiceWorkerRegistration>>&
-          registrations);
-
   const net::NetworkIsolationKey& GetNetworkIsolationKey() const {
     return isolation_info_.network_isolation_key();
   }
@@ -373,15 +359,11 @@
   // creator's client security state lazily instead of eagerly.
   const network::mojom::ClientSecurityStatePtr creator_client_security_state_;
 
-  // The client security state of this worker, used for subresource fetches.
-  //
-  // If PlzDedicatedWorker is disabled, it is cloned from
-  // `creator_client_security_state_` at construction time.
-  //
-  // Otherwise, it is nullptr until the script's response head is loaded, at
-  // which point it is calculated based on the response info. If the response is
-  // loaded from a URL with a local scheme, then the worker inherits its
-  // creator's client security state.
+  // The client security state of this worker, used for subresource fetches. It
+  // is nullptr until the script's response head is loaded, at which point it is
+  // calculated based on the response info. If the response is loaded from a URL
+  // with a local scheme, then the worker inherits its creator's client security
+  // state.
   network::mojom::ClientSecurityStatePtr worker_client_security_state_;
 
   // This is kept alive during the lifetime of the dedicated worker, since it's
@@ -397,7 +379,7 @@
 #endif  // BUILDFLAG(ENABLE_COMPUTE_PRESSURE)
 
   // Script request URL used, only for DevTools and tracing. Only set after
-  // `StartScriptLoad()`. Only set and used if PlzDedicatedWorker is enabled.
+  // `StartScriptLoad()`.
   GURL script_request_url_;
 
   // BrowserInterfaceBroker implementation through which this
@@ -424,17 +406,14 @@
   mojo::Remote<blink::mojom::SubresourceLoaderUpdater>
       subresource_loader_updater_;
 
-  // For the PlzDedicatedWorker case. `coep_reporter_` is valid after
-  // DidStartScriptLoad() and remains non-null for the lifetime of `this`.
+  // `coep_reporter_` is valid after DidStartScriptLoad() and remains non-null
+  // for the lifetime of `this`.
   std::unique_ptr<CrossOriginEmbedderPolicyReporter> coep_reporter_;
   // TODO(crbug.com/40054797): Remove `creator_coep_reporter_` after this
   // class's lifetime is aligned with the associated frame.
   base::WeakPtr<CrossOriginEmbedderPolicyReporter> creator_coep_reporter_;
 
-  // For the non-PlzDedicatedWorker case. Sending reports to the ancestor frame
-  // is not the behavior defined in the spec, but keep the current behavior and
-  // not to lose reports.
-  // TODO(crbug.com/40093136): Remove `ancestor_coep_reporter_` once
+  // TODO(crbug.com/40093136): Remove `ancestor_coep_reporter_` now that
   // PlzDedicatedWorker is enabled by default.
   base::WeakPtr<CrossOriginEmbedderPolicyReporter> ancestor_coep_reporter_;
 
diff --git a/content/browser/worker_host/dedicated_worker_host_factory_impl.cc b/content/browser/worker_host/dedicated_worker_host_factory_impl.cc
index c6de23d..66261dd 100644
--- a/content/browser/worker_host/dedicated_worker_host_factory_impl.cc
+++ b/content/browser/worker_host/dedicated_worker_host_factory_impl.cc
@@ -80,10 +80,6 @@
       "DedicatedWorkerHostFactoryImpl::CreateWorkerHostAndStartScriptLoad",
       "script_url", script_url);
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
-  if (!base::FeatureList::IsEnabled(blink::features::kPlzDedicatedWorker)) {
-    mojo::ReportBadMessage("DWH_BROWSER_SCRIPT_FETCH_DISABLED");
-    return;
-  }
   base::TimeTicks start_time = base::TimeTicks::Now();
 
   // Get the dedicated worker service.
diff --git a/content/child/runtime_features.cc b/content/child/runtime_features.cc
index 27d2f0d..2ac903fa 100644
--- a/content/child/runtime_features.cc
+++ b/content/child/runtime_features.cc
@@ -372,13 +372,9 @@
           {"OriginIsolationHeader", raw_ref(features::kOriginIsolationHeader)},
           {"ReduceAcceptLanguage",
            raw_ref(network::features::kReduceAcceptLanguage)},
-          {"Serial",
 #if BUILDFLAG(IS_ANDROID)
-           raw_ref(device::features::kBluetoothRfcommAndroid)
-#else
-           raw_ref(device::features::kSerial)
+          {"Serial", raw_ref(device::features::kBluetoothRfcommAndroid)},
 #endif
-          },
           {"SerialPortConnected", raw_ref(features::kSerialPortConnected)},
           {"SignatureBasedIntegrity",
            raw_ref(network::features::kSRIMessageSignatureEnforcement)},
diff --git a/content/public/browser/web_contents_observer.h b/content/public/browser/web_contents_observer.h
index cfa13a9a..255420b 100644
--- a/content/public/browser/web_contents_observer.h
+++ b/content/public/browser/web_contents_observer.h
@@ -517,6 +517,9 @@
   // Called when a network request issued by the navigation reads or sets a
   // cookie. If a notification is received after the navigation has committed,
   // it will be attributed to the RenderFrameHost created by the navigation.
+  // This method not only includes accesses from the navigation's
+  // request/response, but also accesses from other requests/responses triggered
+  // by the navigation e.g. early hints requests.
   virtual void OnCookiesAccessed(NavigationHandle* navigation_handle,
                                  const CookieAccessDetails& details) {}
 
diff --git a/content/renderer/render_frame_impl.cc b/content/renderer/render_frame_impl.cc
index 836eff5..a1df129e 100644
--- a/content/renderer/render_frame_impl.cc
+++ b/content/renderer/render_frame_impl.cc
@@ -3584,9 +3584,8 @@
       cors_exempt_header_list, web_cors_exempt_header_list.begin(),
       [](const auto& header) { return blink::WebString::FromLatin1(header); });
 
-  // |pending_subresource_loader_updater| and
-  // |pending_resource_load_info_notifier| are not used for
-  // non-PlzDedicatedWorker and worklets.
+  // `pending_subresource_loader_updater` and
+  // `pending_resource_load_info_notifier` are not used for worklets.
   scoped_refptr<blink::WebDedicatedOrSharedWorkerFetchContext>
       web_dedicated_or_shared_worker_fetch_context =
           blink::WebDedicatedOrSharedWorkerFetchContext::Create(
@@ -3614,7 +3613,6 @@
 scoped_refptr<blink::WebWorkerFetchContext>
 RenderFrameImpl::CreateWorkerFetchContextForPlzDedicatedWorker(
     blink::WebDedicatedWorkerHostFactoryClient* factory_client) {
-  DCHECK(base::FeatureList::IsEnabled(blink::features::kPlzDedicatedWorker));
   DCHECK(factory_client);
 
   mojo::PendingReceiver<blink::mojom::RendererPreferenceWatcher>
diff --git a/content/renderer/worker/dedicated_worker_host_factory_client.cc b/content/renderer/worker/dedicated_worker_host_factory_client.cc
index 244f973db..04912a00 100644
--- a/content/renderer/worker/dedicated_worker_host_factory_client.cc
+++ b/content/renderer/worker/dedicated_worker_host_factory_client.cc
@@ -14,7 +14,6 @@
 #include "content/renderer/worker/fetch_client_settings_object_helpers.h"
 #include "mojo/public/cpp/bindings/pending_remote.h"
 #include "net/storage_access_api/status.h"
-#include "third_party/blink/public/common/features.h"
 #include "third_party/blink/public/common/loader/worker_main_script_load_parameters.h"
 #include "third_party/blink/public/common/tokens/tokens.h"
 #include "third_party/blink/public/common/tokens/tokens_mojom_traits.h"
@@ -51,7 +50,6 @@
     blink::CrossVariantMojoRemote<blink::mojom::BlobURLTokenInterfaceBase>
         blob_url_token,
     net::StorageAccessApiStatus storage_access_api_status) {
-  DCHECK(base::FeatureList::IsEnabled(blink::features::kPlzDedicatedWorker));
   factory_->CreateWorkerHostAndStartScriptLoad(
       dedicated_worker_token, script_url, credentials_mode,
       FetchClientSettingsObjectFromWebToMojom(fetch_client_settings_object),
@@ -65,22 +63,15 @@
     scoped_refptr<base::SingleThreadTaskRunner> task_runner) {
   scoped_refptr<blink::WebDedicatedOrSharedWorkerFetchContext>
       cloned_web_dedicated_or_shared_worker_fetch_context;
-  if (base::FeatureList::IsEnabled(blink::features::kPlzDedicatedWorker)) {
-    cloned_web_dedicated_or_shared_worker_fetch_context =
-        static_cast<blink::WebDedicatedOrSharedWorkerFetchContext*>(
-            web_worker_fetch_context)
-            ->CloneForNestedWorker(
-                service_worker_provider_context_.get(),
-                subresource_loader_factory_bundle_->Clone(),
-                subresource_loader_factory_bundle_->Clone(),
-                std::move(pending_subresource_loader_updater_),
-                std::move(task_runner));
-  } else {
-    cloned_web_dedicated_or_shared_worker_fetch_context =
-        static_cast<blink::WebDedicatedOrSharedWorkerFetchContext*>(
-            web_worker_fetch_context)
-            ->CloneForNestedWorkerDeprecated(std::move(task_runner));
-  }
+  cloned_web_dedicated_or_shared_worker_fetch_context =
+      static_cast<blink::WebDedicatedOrSharedWorkerFetchContext*>(
+          web_worker_fetch_context)
+          ->CloneForNestedWorker(
+              service_worker_provider_context_.get(),
+              subresource_loader_factory_bundle_->Clone(),
+              subresource_loader_factory_bundle_->Clone(),
+              std::move(pending_subresource_loader_updater_),
+              std::move(task_runner));
   return cloned_web_dedicated_or_shared_worker_fetch_context;
 }
 
@@ -91,7 +82,6 @@
         watcher_receiver,
     mojo::PendingRemote<blink::mojom::ResourceLoadInfoNotifier>
         pending_resource_load_info_notifier) {
-  DCHECK(base::FeatureList::IsEnabled(blink::features::kPlzDedicatedWorker));
   DCHECK(subresource_loader_factory_bundle_);
   std::vector<std::string> cors_exempt_header_list =
       RenderThreadImpl::current()->cors_exempt_header_list();
@@ -135,7 +125,6 @@
         coep_reporting_observer,
     mojo::PendingReceiver<blink::mojom::ReportingObserver>
         dip_reporting_observer) {
-  DCHECK(base::FeatureList::IsEnabled(blink::features::kPlzDedicatedWorker));
   DCHECK(main_script_load_params);
   DCHECK(pending_subresource_loader_factory_bundle);
 
@@ -182,7 +171,6 @@
 }
 
 void DedicatedWorkerHostFactoryClient::OnScriptLoadStartFailed() {
-  DCHECK(base::FeatureList::IsEnabled(blink::features::kPlzDedicatedWorker));
   worker_->OnScriptLoadStartFailed();
   // |this| may be destroyed at this point.
 }
diff --git a/content/test/content_test_bundle_data.filelist b/content/test/content_test_bundle_data.filelist
index f470ce58..280cedb 100644
--- a/content/test/content_test_bundle_data.filelist
+++ b/content/test/content_test_bundle_data.filelist
@@ -8151,6 +8151,7 @@
 data/shared_dictionary/with_dict_header.html.mock-http-headers
 data/shared_storage/customizable_module.js
 data/shared_storage/customizable_script.js
+data/shared_storage/customizable_selecturl_module.js
 data/shared_storage/erroneous_function_module.js
 data/shared_storage/erroneous_module.js
 data/shared_storage/getter_module.js
diff --git a/content/test/data/shared_storage/customizable_selecturl_module.js b/content/test/data/shared_storage/customizable_selecturl_module.js
new file mode 100644
index 0000000..f4e557a2
--- /dev/null
+++ b/content/test/data/shared_storage/customizable_selecturl_module.js
@@ -0,0 +1,12 @@
+// Copyright 2025 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+class TestURLSelectionOperation {
+  async run(urls, data) {
+    {{RUN_FUNCTION_BODY}}
+    return 0;
+  }
+}
+
+register('test-url-selection-operation', TestURLSelectionOperation);
diff --git a/crypto/BUILD.gn b/crypto/BUILD.gn
index 9f76eb9..1ac8226 100644
--- a/crypto/BUILD.gn
+++ b/crypto/BUILD.gn
@@ -38,6 +38,8 @@
     "hmac.h",
     "kdf.cc",
     "kdf.h",
+    "keypair.cc",
+    "keypair.h",
     "openssl_util.cc",
     "openssl_util.h",
     "process_bound_string.cc",
@@ -53,6 +55,8 @@
     "secure_util.h",
     "sha2.cc",
     "sha2.h",
+    "sign.cc",
+    "sign.h",
     "signature_creator.cc",
     "signature_creator.h",
     "signature_verifier.cc",
@@ -178,11 +182,13 @@
     "hash_unittest.cc",
     "hmac_unittest.cc",
     "kdf_unittest.cc",
+    "keypair_unittest.cc",
     "process_bound_string_unittest.cc",
     "random_unittest.cc",
     "rsa_private_key_unittest.cc",
     "secure_hash_unittest.cc",
     "sha2_unittest.cc",
+    "sign_unittest.cc",
     "signature_creator_unittest.cc",
     "signature_verifier_unittest.cc",
     "unexportable_key_unittest.cc",
@@ -235,6 +241,8 @@
     "scoped_fake_unexportable_key_provider.h",
     "scoped_fake_user_verifying_key_provider.cc",
     "scoped_fake_user_verifying_key_provider.h",
+    "test_support.cc",
+    "test_support.h",
   ]
 
   if (use_nss_certs) {
diff --git a/crypto/keypair.cc b/crypto/keypair.cc
new file mode 100644
index 0000000..eddf939
--- /dev/null
+++ b/crypto/keypair.cc
@@ -0,0 +1,207 @@
+// Copyright 2025 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "crypto/keypair.h"
+
+#include "base/logging.h"
+#include "crypto/openssl_util.h"
+#include "third_party/boringssl/src/include/openssl/bn.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/evp.h"
+#include "third_party/boringssl/src/include/openssl/mem.h"
+#include "third_party/boringssl/src/include/openssl/rsa.h"
+
+namespace crypto::keypair {
+
+namespace {
+
+bssl::UniquePtr<EVP_PKEY> GenerateRsa(size_t bits) {
+  OpenSSLErrStackTracer err_tracer(FROM_HERE);
+
+  bssl::UniquePtr<RSA> rsa_key(RSA_new());
+  bssl::UniquePtr<BIGNUM> bn(BN_new());
+
+  CHECK(rsa_key.get());
+  CHECK(bn.get());
+  CHECK(BN_set_word(bn.get(), 65537L));
+
+  CHECK(RSA_generate_key_ex(rsa_key.get(), bits, bn.get(), nullptr));
+
+  bssl::UniquePtr<EVP_PKEY> key(EVP_PKEY_new());
+  CHECK(EVP_PKEY_set1_RSA(key.get(), rsa_key.get()));
+
+  return key;
+}
+
+bool IsSupportedEvpId(int evp_id) {
+  return evp_id == EVP_PKEY_RSA || evp_id == EVP_PKEY_EC;
+}
+
+std::vector<uint8_t> ExportEVPPublicKey(EVP_PKEY* pkey) {
+  OpenSSLErrStackTracer err_tracer(FROM_HERE);
+  bssl::ScopedCBB cbb;
+
+  CHECK(CBB_init(cbb.get(), 0));
+  CHECK(EVP_marshal_public_key(cbb.get(), pkey));
+
+  uint8_t* data;
+  size_t len;
+  CHECK(CBB_finish(cbb.get(), &data, &len));
+
+  std::vector<uint8_t> result(len);
+  // SAFETY: OpenSSL freshly allocated data for us and ensured it pointed to at
+  // least len bytes.
+  UNSAFE_BUFFERS(result.assign(data, data + len));
+  OPENSSL_free(data);
+  return result;
+}
+
+}  // namespace
+
+PrivateKey::PrivateKey(bssl::UniquePtr<EVP_PKEY> key, crypto::SubtlePassKey)
+    : PrivateKey(std::move(key)) {}
+PrivateKey::~PrivateKey() = default;
+PrivateKey::PrivateKey(PrivateKey&& other) = default;
+PrivateKey::PrivateKey(const PrivateKey& other)
+    : key_(bssl::UpRef(const_cast<PrivateKey&>(other).key())) {}
+
+// static
+PrivateKey PrivateKey::GenerateRsa2048() {
+  return PrivateKey(GenerateRsa(2048));
+}
+
+// static
+PrivateKey PrivateKey::GenerateRsa4096() {
+  return PrivateKey(GenerateRsa(4096));
+}
+
+// static
+PrivateKey PrivateKey::GenerateEcP256() {
+  OpenSSLErrStackTracer err_tracer(FROM_HERE);
+
+  bssl::UniquePtr<EC_KEY> ec_key(
+      EC_KEY_new_by_curve_name(NID_X9_62_prime256v1));
+  CHECK(ec_key);
+  CHECK(EC_KEY_generate_key(ec_key.get()));
+
+  bssl::UniquePtr<EVP_PKEY> key(EVP_PKEY_new());
+  CHECK(EVP_PKEY_set1_EC_KEY(key.get(), ec_key.get()));
+  return PrivateKey(std::move(key));
+}
+
+// static
+std::optional<PrivateKey> PrivateKey::FromPrivateKeyInfo(
+    base::span<const uint8_t> pki) {
+  OpenSSLErrStackTracer err_tracer(FROM_HERE);
+
+  CBS cbs(pki);
+  bssl::UniquePtr<EVP_PKEY> pkey(EVP_parse_private_key(&cbs));
+  if (!pkey || CBS_len(&cbs) != 0) {
+    LOG(WARNING) << "Malformed PrivateKeyInfo or trailing data";
+    return std::nullopt;
+  }
+
+  auto id = EVP_PKEY_id(pkey.get());
+  if (!IsSupportedEvpId(id)) {
+    LOG(WARNING) << "Unsupported key type (EVP ID: " << id << ")";
+    return std::nullopt;
+  }
+
+  return std::optional<PrivateKey>(PrivateKey(std::move(pkey)));
+}
+
+std::vector<uint8_t> PrivateKey::ToPrivateKeyInfo() const {
+  OpenSSLErrStackTracer err_tracer(FROM_HERE);
+  bssl::ScopedCBB cbb;
+
+  CHECK(CBB_init(cbb.get(), 0));
+  CHECK(EVP_marshal_private_key(cbb.get(), key_.get()));
+
+  uint8_t* data;
+  size_t len;
+  CHECK(CBB_finish(cbb.get(), &data, &len));
+
+  std::vector<uint8_t> result(len);
+  // SAFETY: OpenSSL freshly allocated data for us and ensured it pointed to at
+  // least len bytes.
+  UNSAFE_BUFFERS(result.assign(data, data + len));
+  OPENSSL_free(data);
+  return result;
+}
+
+std::vector<uint8_t> PrivateKey::ToSubjectPublicKeyInfo() const {
+  return ExportEVPPublicKey(key_.get());
+}
+
+std::vector<uint8_t> PrivateKey::ToUncompressedForm() const {
+  OpenSSLErrStackTracer err_tracer(FROM_HERE);
+
+  std::vector<uint8_t> buf(65);
+  EC_KEY* ec_key = EVP_PKEY_get0_EC_KEY(key_.get());
+  CHECK(EC_POINT_point2oct(
+      EC_KEY_get0_group(ec_key), EC_KEY_get0_public_key(ec_key),
+      POINT_CONVERSION_UNCOMPRESSED, buf.data(), buf.size(), /*ctx=*/nullptr));
+
+  return buf;
+}
+
+PrivateKey::PrivateKey(bssl::UniquePtr<EVP_PKEY> key) : key_(std::move(key)) {}
+
+bool PrivateKey::IsRsa() const {
+  return EVP_PKEY_id(key_.get()) == EVP_PKEY_RSA;
+}
+
+bool PrivateKey::IsEc() const {
+  return EVP_PKEY_id(key_.get()) == EVP_PKEY_EC;
+}
+
+PublicKey::PublicKey(bssl::UniquePtr<EVP_PKEY> key, crypto::SubtlePassKey)
+    : PublicKey(std::move(key)) {}
+PublicKey::~PublicKey() = default;
+PublicKey::PublicKey(PublicKey&& other) = default;
+PublicKey::PublicKey(const PublicKey& other)
+    : key_(bssl::UpRef(const_cast<PublicKey&>(other).key())) {}
+
+// static
+PublicKey PublicKey::FromPrivateKey(const PrivateKey& key) {
+  return *FromSubjectPublicKeyInfo(key.ToSubjectPublicKeyInfo());
+}
+
+// static
+std::optional<PublicKey> PublicKey::FromSubjectPublicKeyInfo(
+    base::span<const uint8_t> spki) {
+  OpenSSLErrStackTracer err_tracer(FROM_HERE);
+
+  CBS cbs(spki);
+  bssl::UniquePtr<EVP_PKEY> pkey(EVP_parse_public_key(&cbs));
+  if (!pkey || CBS_len(&cbs) != 0) {
+    LOG(WARNING) << "Malformed PublicKeyInfo or trailing data";
+    return std::nullopt;
+  }
+
+  auto id = EVP_PKEY_id(pkey.get());
+  if (!IsSupportedEvpId(id)) {
+    LOG(WARNING) << "Unsupported key type (EVP ID: " << id << ")";
+    return std::nullopt;
+  }
+
+  return std::optional<PublicKey>(PublicKey(std::move(pkey)));
+}
+
+std::vector<uint8_t> PublicKey::ToSubjectPublicKeyInfo() const {
+  return ExportEVPPublicKey(key_.get());
+}
+
+bool PublicKey::IsRsa() const {
+  return EVP_PKEY_id(key_.get()) == EVP_PKEY_RSA;
+}
+
+bool PublicKey::IsEc() const {
+  return EVP_PKEY_id(key_.get()) == EVP_PKEY_EC;
+}
+
+PublicKey::PublicKey(bssl::UniquePtr<EVP_PKEY> key) : key_(std::move(key)) {}
+
+}  // namespace crypto::keypair
diff --git a/crypto/keypair.h b/crypto/keypair.h
new file mode 100644
index 0000000..61e2c0f
--- /dev/null
+++ b/crypto/keypair.h
@@ -0,0 +1,107 @@
+// Copyright 2025 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CRYPTO_KEYPAIR_H_
+#define CRYPTO_KEYPAIR_H_
+
+#include "base/containers/span.h"
+#include "crypto/crypto_export.h"
+#include "crypto/subtle_passkey.h"
+#include "third_party/boringssl/src/include/openssl/base.h"
+
+namespace crypto::keypair {
+
+// This class wraps an EVP_PKEY containing a private key. Since EVP_PKEY is
+// refcounted, PrivateKey is extremely cheap to copy and is intended to be
+// passed around by value. All the public constructors are static functions that
+// enforce constraints on the type of key they will generate or import; the
+// constructor that accepts a raw EVP_PKEY requires a SubtlePassKey to
+// discourage client code from dealing in EVP_PKEYs directly.
+class CRYPTO_EXPORT PrivateKey {
+ public:
+  // Directly construct a PrivateKey from an EVP_PKEY. Prefer to use one of the
+  // static factory functions below, which do not require a SubtlePassKey.
+  PrivateKey(bssl::UniquePtr<EVP_PKEY> key, crypto::SubtlePassKey);
+  ~PrivateKey();
+  PrivateKey(PrivateKey&& other);
+  PrivateKey(const PrivateKey& other);
+
+  // These functions generate fresh, random RSA private keys of the named sizes
+  // with e = 65537.
+  // If you believe you need an RSA key of a size other than these, or with a
+  // different exponent, please contact a member of //CRYPTO_OWNERS.
+  static PrivateKey GenerateRsa2048();
+  static PrivateKey GenerateRsa4096();
+
+  // Generates a fresh, random elliptic curve key on the NIST P-256 curve.
+  static PrivateKey GenerateEcP256();
+
+  // Imports a PKCS#8 PrivateKeyInfo block. Returns nullopt if the passed-in
+  // buffer is not a valid PrivateKeyInfo block, or if there is trailing data in
+  // it after the PrivateKeyInfo block.
+  static std::optional<PrivateKey> FromPrivateKeyInfo(
+      base::span<const uint8_t> pki);
+
+  // Deliberately not present in this API:
+  // ECPrivateKey::CreateFromEncryptedPrivateKeyInfo(): imports a PKCS#8
+  // EncryptedPrivateKeyInfo with a hardcoded empty password. There is no reason
+  // to ever do this and there is only one client (the GCM code).
+
+  // Exports a PKCS#8 PrivateKeyInfo block.
+  std::vector<uint8_t> ToPrivateKeyInfo() const;
+
+  // Computes and exports an X.509 SubjectPublicKeyInfo block corresponding to
+  // this key.
+  std::vector<uint8_t> ToSubjectPublicKeyInfo() const;
+
+  // Exports an EC public key in X9.62 uncompressed form. It is illegal to call
+  // this on a non-EC PrivateKey.
+  std::vector<uint8_t> ToUncompressedForm() const;
+
+  EVP_PKEY* key() { return key_.get(); }
+
+  bool IsRsa() const;
+  bool IsEc() const;
+
+ private:
+  explicit PrivateKey(bssl::UniquePtr<EVP_PKEY> key);
+
+  bssl::UniquePtr<EVP_PKEY> key_;
+};
+
+class CRYPTO_EXPORT PublicKey {
+ public:
+  // Construct a PublicKey directly from an EVP_PKEY. Prefer to use one of the
+  // static factory functions below, which do not require a SubtlePassKey.
+  PublicKey(bssl::UniquePtr<EVP_PKEY> key, crypto::SubtlePassKey);
+  ~PublicKey();
+  PublicKey(PublicKey&& other);
+  PublicKey(const PublicKey& other);
+
+  // Produces the PublicKey corresponding to the given PrivateKey. This is
+  // mostly useful in tests but is fine to use in production as well.
+  static PublicKey FromPrivateKey(const PrivateKey& key);
+
+  // Imports a PublicKey from an X.509 SubjectPublicKeyInfo. This may return
+  // nullopt if the SubjectPublicKeyInfo is ill-formed.
+  static std::optional<PublicKey> FromSubjectPublicKeyInfo(
+      base::span<const uint8_t> spki);
+
+  // Exports a PublicKey as an X.509 SubjectPublicKeyInfo.
+  std::vector<uint8_t> ToSubjectPublicKeyInfo() const;
+
+  EVP_PKEY* key() { return key_.get(); }
+
+  bool IsRsa() const;
+  bool IsEc() const;
+
+ private:
+  explicit PublicKey(bssl::UniquePtr<EVP_PKEY> key);
+
+  bssl::UniquePtr<EVP_PKEY> key_;
+};
+
+}  // namespace crypto::keypair
+
+#endif  // CRYPTO_KEYPAIR_H_
diff --git a/crypto/keypair_unittest.cc b/crypto/keypair_unittest.cc
new file mode 100644
index 0000000..f21a150
--- /dev/null
+++ b/crypto/keypair_unittest.cc
@@ -0,0 +1,66 @@
+// Copyright 2025 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// crypto::keypair tests
+#include "crypto/keypair.h"
+
+#include "crypto/test_support.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+using crypto::keypair::PrivateKey;
+using crypto::keypair::PublicKey;
+
+using crypto::test::FixedRsa2048PrivateKeyForTesting;
+using crypto::test::FixedRsa4096PrivateKeyForTesting;
+
+using crypto::test::FixedRsa2048PublicKeyForTesting;
+using crypto::test::FixedRsa4096PublicKeyForTesting;
+
+// Generate keys, roundtrip them through encoding/decoding to PrivateKeyInfo,
+// and ensure the resulting key is equivalent. That gives some confidence that
+// the generated key is actually a valid key because of the validation in
+// FromPrivateKeyInfo().
+TEST(Keypair, GenerateAndRoundtripPrivateKey) {
+  auto expect_roundtrip = [](const PrivateKey& key) {
+    auto k = PrivateKey::FromPrivateKeyInfo(key.ToPrivateKeyInfo());
+    ASSERT_TRUE(k);
+    EXPECT_EQ(key.ToPrivateKeyInfo(), k->ToPrivateKeyInfo());
+    EXPECT_EQ(key.IsRsa(), k->IsRsa());
+    EXPECT_EQ(key.IsEc(), k->IsEc());
+  };
+
+  expect_roundtrip(PrivateKey::GenerateRsa2048());
+  expect_roundtrip(PrivateKey::GenerateRsa4096());
+  expect_roundtrip(PrivateKey::GenerateEcP256());
+}
+
+// Export a public key from each private key and ensure it matches the expected
+// public key.
+TEST(Keypair, ExportPublicKey) {
+  auto expect_export = [](const PrivateKey& priv, const PublicKey& pub) {
+    EXPECT_EQ(priv.ToSubjectPublicKeyInfo(), pub.ToSubjectPublicKeyInfo());
+  };
+
+  expect_export(FixedRsa2048PrivateKeyForTesting(),
+                FixedRsa2048PublicKeyForTesting());
+  expect_export(FixedRsa4096PrivateKeyForTesting(),
+                FixedRsa4096PublicKeyForTesting());
+}
+
+TEST(Keypair, ImportWithTrailingJunkFails) {
+  auto priv = FixedRsa2048PrivateKeyForTesting().ToPrivateKeyInfo();
+  auto pub = FixedRsa2048PublicKeyForTesting().ToSubjectPublicKeyInfo();
+
+  EXPECT_TRUE(PrivateKey::FromPrivateKeyInfo(priv));
+  priv.push_back(0);
+  EXPECT_FALSE(PrivateKey::FromPrivateKeyInfo(priv));
+
+  EXPECT_TRUE(PublicKey::FromSubjectPublicKeyInfo(pub));
+  pub.push_back(0);
+  EXPECT_FALSE(PublicKey::FromSubjectPublicKeyInfo(pub));
+}
+
+}  // namespace
diff --git a/crypto/sign.cc b/crypto/sign.cc
new file mode 100644
index 0000000..02b9078
--- /dev/null
+++ b/crypto/sign.cc
@@ -0,0 +1,130 @@
+// Copyright 2025 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "crypto/sign.h"
+
+#include "crypto/openssl_util.h"
+#include "third_party/boringssl/src/include/openssl/evp.h"
+#include "third_party/boringssl/src/include/openssl/rsa.h"
+
+namespace crypto::sign {
+
+namespace {
+
+bool CanUseKeyForSignatureKind(SignatureKind kind,
+                               const crypto::keypair::PrivateKey& key) {
+  return true;
+}
+
+bool CanUseKeyForSignatureKind(SignatureKind kind,
+                               const crypto::keypair::PublicKey& key) {
+  return true;
+}
+
+const EVP_MD* DigestForSignatureKind(SignatureKind kind) {
+  switch (kind) {
+    case RSA_PKCS1_SHA1:
+      return EVP_sha1();
+    case RSA_PKCS1_SHA256:
+      return EVP_sha256();
+    case RSA_PSS_SHA256:
+      return EVP_sha256();
+    case ECDSA_SHA256:
+      return EVP_sha256();
+  }
+}
+
+}  // namespace
+
+std::vector<uint8_t> Sign(SignatureKind kind,
+                          const crypto::keypair::PrivateKey& key,
+                          base::span<const uint8_t> data) {
+  Signer signer(kind, key);
+  signer.Update(data);
+  return signer.Finish();
+}
+
+bool Verify(SignatureKind kind,
+            const crypto::keypair::PublicKey& key,
+            base::span<const uint8_t> data,
+            base::span<const uint8_t> signature) {
+  Verifier verifier(kind, key, signature);
+  verifier.Update(data);
+  return verifier.Finish();
+}
+
+Signer::Signer(SignatureKind kind, crypto::keypair::PrivateKey key)
+    : key_(key), sign_context_(EVP_MD_CTX_new()) {
+  CHECK(CanUseKeyForSignatureKind(kind, key));
+  OpenSSLErrStackTracer err_tracer(FROM_HERE);
+
+  const EVP_MD* const md = DigestForSignatureKind(kind);
+  EVP_PKEY_CTX* pkctx;
+  CHECK(
+      EVP_DigestSignInit(sign_context_.get(), &pkctx, md, nullptr, key.key()));
+
+  if (kind == RSA_PSS_SHA256) {
+    CHECK(EVP_PKEY_CTX_set_rsa_padding(pkctx, RSA_PKCS1_PSS_PADDING));
+    CHECK(EVP_PKEY_CTX_set_rsa_mgf1_md(pkctx, md));
+    // -1 here means "use digest's length"
+    CHECK(EVP_PKEY_CTX_set_rsa_pss_saltlen(pkctx, -1));
+  }
+}
+Signer::~Signer() = default;
+
+void Signer::Update(base::span<const uint8_t> data) {
+  CHECK(EVP_DigestSignUpdate(sign_context_.get(), data.data(), data.size()));
+}
+
+std::vector<uint8_t> Signer::Finish() {
+  OpenSSLErrStackTracer err_tracer(FROM_HERE);
+
+  size_t len = 0;
+  // Determine the maximum length of the signature.
+  CHECK(EVP_DigestSignFinal(sign_context_.get(), nullptr, &len));
+
+  std::vector<uint8_t> signature(len);
+  CHECK(EVP_DigestSignFinal(sign_context_.get(), signature.data(), &len));
+  signature.resize(len);
+  return signature;
+}
+
+Verifier::Verifier(SignatureKind kind,
+                   crypto::keypair::PublicKey key,
+                   base::span<const uint8_t> signature)
+    : key_(key), verify_context_(EVP_MD_CTX_new()) {
+  OpenSSLErrStackTracer err_tracer(FROM_HERE);
+  CHECK(CanUseKeyForSignatureKind(kind, key));
+  signature_.resize(signature.size());
+  base::span(signature_).copy_from(signature);
+
+  EVP_PKEY_CTX* pkctx;
+  const EVP_MD* const md = DigestForSignatureKind(kind);
+  CHECK(EVP_DigestVerifyInit(verify_context_.get(), &pkctx, md, nullptr,
+                             key.key()));
+
+  if (kind == RSA_PSS_SHA256) {
+    CHECK(EVP_PKEY_CTX_set_rsa_padding(pkctx, RSA_PKCS1_PSS_PADDING));
+    CHECK(EVP_PKEY_CTX_set_rsa_mgf1_md(pkctx, md));
+    // -1 here means "use digest's length"
+    CHECK(EVP_PKEY_CTX_set_rsa_pss_saltlen(pkctx, -1));
+  }
+}
+Verifier::~Verifier() = default;
+
+void Verifier::Update(base::span<const uint8_t> data) {
+  OpenSSLErrStackTracer err_tracer(FROM_HERE);
+  CHECK_EQ(
+      EVP_DigestVerifyUpdate(verify_context_.get(), data.data(), data.size()),
+      1);
+}
+
+bool Verifier::Finish() {
+  OpenSSLErrStackTracer err_tracer(FROM_HERE);
+  int rv = EVP_DigestVerifyFinal(verify_context_.get(), signature_.data(),
+                                 signature_.size());
+  return rv == 1;
+}
+
+}  // namespace crypto::sign
diff --git a/crypto/sign.h b/crypto/sign.h
new file mode 100644
index 0000000..40d7255e
--- /dev/null
+++ b/crypto/sign.h
@@ -0,0 +1,79 @@
+// Copyright 2025 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CRYPTO_SIGN_H_
+#define CRYPTO_SIGN_H_
+
+#include "base/containers/span.h"
+#include "crypto/crypto_export.h"
+#include "crypto/keypair.h"
+#include "third_party/boringssl/src/include/openssl/base.h"
+#include "third_party/boringssl/src/include/openssl/evp.h"
+
+namespace crypto::sign {
+
+enum SignatureKind {
+  RSA_PKCS1_SHA1,
+  RSA_PKCS1_SHA256,
+  // RSA-PSS with SHA-256 as both the signing hash and the MGF-1 hash, with a
+  // salt length of 32.
+  RSA_PSS_SHA256,
+  ECDSA_SHA256,
+};
+
+// One-shot signature function: produce a signature of `data` using `key`.
+CRYPTO_EXPORT std::vector<uint8_t> Sign(SignatureKind kind,
+                                        const crypto::keypair::PrivateKey& key,
+                                        base::span<const uint8_t> data);
+
+// One-shot verification function: check a signature and return whether it is
+// valid.
+[[nodiscard]] CRYPTO_EXPORT bool Verify(SignatureKind kind,
+                                        const crypto::keypair::PublicKey& key,
+                                        base::span<const uint8_t> data,
+                                        base::span<const uint8_t> signature);
+
+// A streaming signer interface. Calling Finish() produces the final signature.
+class CRYPTO_EXPORT Signer {
+ public:
+  Signer(SignatureKind kind, crypto::keypair::PrivateKey key);
+  ~Signer();
+
+  // Put more data into the signing function.
+  void Update(base::span<const uint8_t> data);
+
+  // Finish the signature and return the signature value. After this is called,
+  // the Signer cannot be used any more.
+  std::vector<uint8_t> Finish();
+
+ private:
+  crypto::keypair::PrivateKey key_;
+  bssl::UniquePtr<EVP_MD_CTX> sign_context_;
+};
+
+// A streaming verifier interface. Calling Finish() checks the signature.
+class CRYPTO_EXPORT Verifier {
+ public:
+  Verifier(SignatureKind kind,
+           crypto::keypair::PublicKey key,
+           base::span<const uint8_t> signature);
+  ~Verifier();
+
+  // Put more data into the verification function.
+  void Update(base::span<const uint8_t> data);
+
+  // Finish the verification and return whether the signature matched the
+  // expected value provided at construction time. After this is called, the
+  // Verifier cannot be used any more.
+  [[nodiscard]] bool Finish();
+
+ private:
+  crypto::keypair::PublicKey key_;
+  std::vector<uint8_t> signature_;
+  bssl::UniquePtr<EVP_MD_CTX> verify_context_;
+};
+
+}  // namespace crypto::sign
+
+#endif  // CRYPTO_SIGN_H_
diff --git a/crypto/sign_unittest.cc b/crypto/sign_unittest.cc
new file mode 100644
index 0000000..003b61e
--- /dev/null
+++ b/crypto/sign_unittest.cc
@@ -0,0 +1,42 @@
+// Copyright 2025 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "crypto/sign.h"
+
+#include "crypto/test_support.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+using crypto::keypair::PrivateKey;
+using crypto::keypair::PublicKey;
+using crypto::sign::SignatureKind;
+
+TEST(Sign, RoundTripSignVerify) {
+  constexpr auto data = std::to_array<uint8_t>({
+      0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a,
+      0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15,
+      0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
+  });
+
+  auto expect_roundtrip = [&](const PrivateKey& priv, const PublicKey& pub,
+                              SignatureKind kind) {
+    auto sig = crypto::sign::Sign(kind, priv, data);
+    EXPECT_TRUE(crypto::sign::Verify(kind, pub, data, sig));
+  };
+
+  auto rsa_priv = crypto::test::FixedRsa2048PrivateKeyForTesting();
+  auto rsa_pub = crypto::test::FixedRsa2048PublicKeyForTesting();
+
+  expect_roundtrip(rsa_priv, rsa_pub, SignatureKind::RSA_PKCS1_SHA1);
+  expect_roundtrip(rsa_priv, rsa_pub, SignatureKind::RSA_PKCS1_SHA256);
+  expect_roundtrip(rsa_priv, rsa_pub, SignatureKind::RSA_PSS_SHA256);
+
+  auto ec_priv = PrivateKey::GenerateEcP256();
+  auto ec_pub = PublicKey::FromPrivateKey(ec_priv);
+
+  expect_roundtrip(ec_priv, ec_pub, SignatureKind::ECDSA_SHA256);
+}
+
+}  // namespace
diff --git a/crypto/test_support.cc b/crypto/test_support.cc
new file mode 100644
index 0000000..e408fea
--- /dev/null
+++ b/crypto/test_support.cc
@@ -0,0 +1,412 @@
+// Copyright 2025 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "crypto/test_support.h"
+
+namespace crypto::test {
+
+// To generate these keys:
+// $ openssl genrsa -out test.key $BITS
+// $ openssl rsa -in test.key -outform DER | xxd -i > private.key
+// $ openssl rsa -in test.key -outform DER -pubout | xxd -i > public.key
+//
+// That produces the big arrays of hex given below. These are DER-encoded
+// private and public keys respectively.
+
+static constexpr auto kRsa2048Private = std::to_array<uint8_t>(
+    {0x30, 0x82, 0x04, 0xbd, 0x02, 0x01, 0x00, 0x30, 0x0d, 0x06, 0x09, 0x2a,
+     0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x04, 0x82,
+     0x04, 0xa7, 0x30, 0x82, 0x04, 0xa3, 0x02, 0x01, 0x00, 0x02, 0x82, 0x01,
+     0x01, 0x00, 0xb7, 0x73, 0x11, 0xa1, 0x47, 0x56, 0xb8, 0xdf, 0x45, 0xab,
+     0x81, 0xc1, 0x17, 0x24, 0x00, 0x6d, 0x70, 0xc2, 0x3f, 0x54, 0xac, 0x71,
+     0xda, 0xf5, 0x5d, 0xd8, 0x7c, 0x77, 0x36, 0xa6, 0x2e, 0x30, 0xbd, 0x07,
+     0x27, 0x34, 0x20, 0xdf, 0xc5, 0xf7, 0xdb, 0x9d, 0x93, 0xdc, 0x68, 0xa1,
+     0xc4, 0x02, 0x4f, 0xcb, 0xb1, 0x1e, 0xb9, 0xd0, 0x12, 0x92, 0xa2, 0xf0,
+     0x0d, 0xfb, 0xe6, 0xa0, 0x62, 0x36, 0xd2, 0x6e, 0xae, 0x03, 0xd5, 0xa8,
+     0x53, 0x46, 0x77, 0x7a, 0xe3, 0x83, 0xfe, 0x8d, 0x54, 0x29, 0x03, 0xc1,
+     0x19, 0x6d, 0x22, 0x44, 0x0f, 0x1a, 0x58, 0x57, 0x01, 0xef, 0x81, 0x3f,
+     0x0b, 0xf3, 0x96, 0x8e, 0x51, 0x40, 0xd3, 0x67, 0xd8, 0x85, 0x9f, 0x85,
+     0x80, 0x8e, 0x84, 0xc6, 0xaa, 0x35, 0xae, 0x35, 0xff, 0x38, 0x20, 0xbb,
+     0xe3, 0x95, 0xc5, 0xe3, 0xd6, 0xdd, 0xe9, 0x28, 0x87, 0xb9, 0x38, 0xce,
+     0xbd, 0x53, 0x17, 0x97, 0x90, 0xed, 0x87, 0xd5, 0x08, 0x6c, 0x84, 0xef,
+     0xb1, 0x9e, 0x71, 0x5e, 0x2c, 0x0b, 0xce, 0xc1, 0x76, 0xbb, 0xfb, 0xd6,
+     0x5b, 0x55, 0x85, 0xe0, 0x4d, 0xf7, 0xaf, 0xcf, 0x34, 0xe1, 0x40, 0x0a,
+     0x76, 0x1f, 0xb1, 0xdc, 0xd7, 0xb0, 0x63, 0xa2, 0xc7, 0x23, 0x85, 0x56,
+     0xfd, 0x8c, 0xec, 0x6b, 0x0f, 0xfb, 0x6e, 0xcb, 0xba, 0x3b, 0x12, 0x04,
+     0xbb, 0xf7, 0x5d, 0x28, 0xe2, 0x57, 0x9c, 0x38, 0x1d, 0x8f, 0xc9, 0x51,
+     0x3f, 0x43, 0x8a, 0x6e, 0xb0, 0xa1, 0xbb, 0x6f, 0x19, 0x79, 0x8f, 0x81,
+     0x5c, 0xd9, 0x89, 0x91, 0xde, 0xfa, 0xce, 0x09, 0x53, 0xb7, 0x37, 0x36,
+     0x04, 0x48, 0x5f, 0x44, 0x7c, 0x19, 0x09, 0xb8, 0x1b, 0x8f, 0xf1, 0xf5,
+     0x86, 0x3a, 0x81, 0xf8, 0x44, 0x76, 0x72, 0xc8, 0x4b, 0xbf, 0x70, 0x75,
+     0xb7, 0xc1, 0x5d, 0xff, 0x91, 0xdd, 0x02, 0x03, 0x01, 0x00, 0x01, 0x02,
+     0x82, 0x01, 0x00, 0x4a, 0x50, 0x5a, 0x69, 0x77, 0x81, 0x39, 0x85, 0xf4,
+     0xee, 0xab, 0xb3, 0x01, 0x41, 0x4b, 0xcd, 0x09, 0xbb, 0x1e, 0xb5, 0x99,
+     0xdf, 0xcb, 0x7f, 0xcf, 0x42, 0xf5, 0xf4, 0xc3, 0x16, 0x21, 0xab, 0x0b,
+     0xc0, 0x1c, 0x91, 0x00, 0xea, 0x35, 0x83, 0x99, 0x1b, 0x25, 0xfd, 0x49,
+     0x03, 0x92, 0xe8, 0x0e, 0xd4, 0x28, 0x8d, 0x96, 0x5f, 0x24, 0x4e, 0xf3,
+     0xa3, 0x84, 0x3e, 0xb1, 0xa7, 0xf1, 0xf1, 0x5c, 0x60, 0x8a, 0xac, 0xb1,
+     0xfe, 0x2c, 0xb1, 0xe4, 0x8f, 0xcb, 0x1f, 0xba, 0xdc, 0x1c, 0xa6, 0x3a,
+     0xc7, 0x9c, 0x19, 0xba, 0x22, 0x50, 0xed, 0xee, 0xb0, 0x5c, 0x6f, 0xdd,
+     0xef, 0x20, 0xd3, 0xcd, 0xff, 0x1c, 0x0a, 0x43, 0x39, 0x93, 0x9c, 0x59,
+     0xc9, 0x56, 0x91, 0x25, 0x57, 0x67, 0x37, 0x34, 0xb9, 0xda, 0x08, 0x2e,
+     0x36, 0x7e, 0xd4, 0xba, 0xe3, 0xf7, 0xb4, 0x50, 0x91, 0xe3, 0x1c, 0xa3,
+     0x96, 0x0e, 0xec, 0x37, 0x52, 0xe7, 0xd9, 0x64, 0xb8, 0xa7, 0x54, 0x81,
+     0xe6, 0x08, 0x87, 0xda, 0x71, 0x6a, 0xb7, 0x86, 0x00, 0xce, 0xe8, 0x22,
+     0xcf, 0xed, 0x6d, 0x5f, 0x72, 0xa0, 0x16, 0xe4, 0x3b, 0x0f, 0xc0, 0xc5,
+     0xf9, 0x57, 0x21, 0xee, 0x00, 0x2b, 0xf5, 0x0c, 0x37, 0x3d, 0x49, 0x00,
+     0x91, 0x00, 0xc4, 0x0d, 0x1f, 0xa6, 0x3a, 0x76, 0x59, 0x6e, 0xd4, 0xeb,
+     0x0f, 0x8b, 0x82, 0x0c, 0x98, 0x61, 0xaf, 0x4c, 0x98, 0x6a, 0xb4, 0x09,
+     0xe0, 0xde, 0xb8, 0x12, 0x1a, 0xcc, 0xf9, 0xdd, 0xfb, 0x6d, 0x3d, 0x3f,
+     0xa6, 0xe2, 0x43, 0x31, 0x01, 0x77, 0xcf, 0xfa, 0xf1, 0xd5, 0x32, 0xe3,
+     0xd6, 0xe6, 0x1a, 0x92, 0x88, 0xbd, 0x78, 0xbb, 0x1e, 0xdc, 0x13, 0xbb,
+     0xff, 0x1c, 0x12, 0x2f, 0x93, 0x28, 0x82, 0xe8, 0x03, 0xf6, 0xec, 0x56,
+     0x4c, 0x08, 0xa2, 0xe7, 0xc3, 0xc9, 0x3f, 0x02, 0x81, 0x81, 0x00, 0xf8,
+     0xcc, 0xfa, 0xca, 0xdf, 0x58, 0x59, 0x8e, 0x1d, 0x70, 0x99, 0xce, 0x78,
+     0x9e, 0xdc, 0x56, 0x08, 0x91, 0x5f, 0x63, 0xb0, 0x92, 0x69, 0xbb, 0x17,
+     0xc4, 0xd1, 0x69, 0xc4, 0xdb, 0xc3, 0xf0, 0x36, 0xe8, 0x0e, 0x76, 0x42,
+     0x21, 0xd2, 0x9a, 0x2c, 0xe5, 0x38, 0x5d, 0x89, 0xe7, 0xe0, 0x9f, 0x4c,
+     0xde, 0xb4, 0x9c, 0x7a, 0x7c, 0x00, 0x5c, 0x09, 0xed, 0x41, 0x98, 0x7f,
+     0x3c, 0x90, 0xfd, 0x35, 0xac, 0x77, 0x3d, 0xa2, 0x66, 0x4a, 0x9d, 0x0c,
+     0x9b, 0xdf, 0xab, 0xeb, 0x5e, 0xa9, 0xb3, 0x84, 0x71, 0x9a, 0xb9, 0x2a,
+     0x9e, 0x1c, 0x38, 0xe4, 0xea, 0xaf, 0xf7, 0xa3, 0x46, 0x5b, 0x14, 0xdf,
+     0x0f, 0xd9, 0xc4, 0x8c, 0x18, 0x70, 0x21, 0xfd, 0x2f, 0xd6, 0x1c, 0xb9,
+     0x83, 0x36, 0x56, 0xe6, 0x15, 0xd2, 0x3a, 0xf9, 0xbf, 0x50, 0x5a, 0x8a,
+     0x82, 0xd7, 0xc8, 0x23, 0xd7, 0x2e, 0x0b, 0x02, 0x81, 0x81, 0x00, 0xbc,
+     0xc1, 0xfe, 0x10, 0x4c, 0xe4, 0x0e, 0x9d, 0x65, 0xfc, 0x31, 0x96, 0x44,
+     0xdf, 0xf2, 0x46, 0x59, 0xd4, 0xf5, 0xe1, 0x44, 0xab, 0x67, 0x80, 0x43,
+     0xb0, 0xa5, 0x73, 0xec, 0xcb, 0xb5, 0x9a, 0xc1, 0xce, 0xa7, 0x22, 0xb0,
+     0x3e, 0xbf, 0x4f, 0x2e, 0x6c, 0x3c, 0xb3, 0x54, 0x44, 0x61, 0xb4, 0x70,
+     0xc3, 0x99, 0xdf, 0xfd, 0xa5, 0x39, 0x27, 0x1d, 0x5c, 0x0b, 0xf7, 0xbe,
+     0x6a, 0x65, 0x80, 0xb7, 0xb0, 0x59, 0xf5, 0xb3, 0xb8, 0x3f, 0xa5, 0x73,
+     0x2b, 0x49, 0xe0, 0x9e, 0x55, 0xf8, 0x46, 0xdb, 0x22, 0xb4, 0x4b, 0xc1,
+     0xb6, 0x50, 0x36, 0xd9, 0xa7, 0x1c, 0xf2, 0x7c, 0xca, 0x0e, 0xa7, 0xef,
+     0xce, 0xa1, 0x42, 0xb7, 0x42, 0x17, 0x07, 0xc4, 0xc0, 0x3e, 0xd4, 0x56,
+     0x27, 0x07, 0xd7, 0x31, 0x47, 0xe6, 0xbf, 0xe6, 0xa5, 0x2e, 0x2b, 0x7a,
+     0x7d, 0xcc, 0x60, 0x12, 0x99, 0xf8, 0xb7, 0x02, 0x81, 0x80, 0x5a, 0xd5,
+     0xc3, 0x8e, 0x83, 0xe2, 0x66, 0xb7, 0xdb, 0x09, 0xbc, 0x2d, 0xc4, 0x9e,
+     0x03, 0x45, 0xa9, 0xd5, 0x21, 0x65, 0x6d, 0x16, 0xd7, 0x61, 0x46, 0x39,
+     0x46, 0x57, 0x7e, 0x56, 0xd9, 0xff, 0x7e, 0x9c, 0x54, 0x83, 0x5a, 0x7b,
+     0xac, 0xbf, 0x3b, 0x3a, 0xe8, 0xcc, 0x45, 0xc8, 0x11, 0x9b, 0x37, 0x5e,
+     0x6b, 0xc4, 0x61, 0x77, 0x9a, 0x4e, 0x00, 0x15, 0xce, 0x08, 0x16, 0x14,
+     0x0f, 0xbf, 0x52, 0x74, 0x48, 0x08, 0x89, 0x9d, 0x1d, 0x0a, 0x9f, 0x8a,
+     0xdd, 0x2b, 0x90, 0x40, 0x3c, 0x66, 0xdd, 0x28, 0xf8, 0xdb, 0x37, 0xb3,
+     0x08, 0x0c, 0xc1, 0x8e, 0xe9, 0x75, 0xd8, 0xf7, 0x9b, 0xd3, 0x4f, 0xe9,
+     0x22, 0x91, 0x7e, 0xb0, 0x81, 0x67, 0xf7, 0x5f, 0x1a, 0xa5, 0xdc, 0x19,
+     0x0a, 0xa2, 0xc9, 0x58, 0x18, 0x2c, 0x0d, 0xf8, 0x8a, 0x26, 0xb4, 0x41,
+     0x36, 0xf4, 0xcc, 0x19, 0x08, 0xa7, 0x02, 0x81, 0x80, 0x62, 0x13, 0x59,
+     0xf3, 0x16, 0x40, 0x98, 0xe7, 0x67, 0x8a, 0x36, 0x29, 0xa1, 0xf7, 0xca,
+     0x66, 0x8b, 0x5e, 0x7f, 0xb3, 0x60, 0x7e, 0xbe, 0xf4, 0x82, 0x37, 0x52,
+     0x80, 0x7d, 0x55, 0x0b, 0x33, 0x31, 0xe8, 0x32, 0x27, 0x6f, 0xf3, 0xea,
+     0x6b, 0x35, 0xef, 0xbf, 0x4a, 0x5e, 0x4a, 0x79, 0x89, 0xcb, 0xdd, 0x96,
+     0x22, 0x30, 0x24, 0x9d, 0x21, 0x99, 0xbb, 0xad, 0xec, 0x37, 0xe0, 0x08,
+     0x85, 0x6c, 0xec, 0x10, 0x91, 0xfd, 0xa3, 0x8a, 0x4e, 0x69, 0x1c, 0xe0,
+     0xf1, 0xf8, 0xd3, 0x2a, 0x81, 0x86, 0x72, 0xed, 0xc3, 0x3f, 0x0f, 0x7f,
+     0x76, 0x40, 0x78, 0xf8, 0x2d, 0x76, 0x71, 0x76, 0x54, 0x03, 0xe2, 0x15,
+     0x20, 0x19, 0x20, 0x19, 0xdf, 0x4b, 0x77, 0xa8, 0x2d, 0xa3, 0xe5, 0xfb,
+     0xc8, 0xf0, 0x2e, 0x2f, 0xd3, 0x1e, 0x00, 0x4d, 0x91, 0x01, 0xc0, 0x43,
+     0x64, 0xd7, 0xc5, 0x70, 0xd9, 0x02, 0x81, 0x81, 0x00, 0xe9, 0x63, 0xbf,
+     0xee, 0xc7, 0x30, 0x95, 0x4e, 0x89, 0x41, 0xdb, 0x6f, 0x58, 0x10, 0x56,
+     0x50, 0x4b, 0x37, 0xf6, 0x90, 0xa9, 0x20, 0x12, 0x95, 0xdd, 0xa1, 0x61,
+     0x88, 0x3a, 0xe1, 0x85, 0x32, 0x2d, 0x4d, 0x41, 0x44, 0x8e, 0xae, 0x33,
+     0x2e, 0x30, 0xc7, 0xf7, 0x2e, 0x14, 0xde, 0xb0, 0x38, 0x9d, 0x1e, 0x56,
+     0x85, 0xb3, 0x31, 0x8e, 0x12, 0x55, 0x7a, 0x88, 0x53, 0xe8, 0x75, 0x3c,
+     0x5b, 0x38, 0x98, 0xf3, 0x9b, 0x65, 0x70, 0x33, 0xf4, 0x62, 0x58, 0x04,
+     0x6f, 0x55, 0x1d, 0xa5, 0xff, 0xb1, 0xa4, 0x48, 0x34, 0x4d, 0xf3, 0xbc,
+     0xa9, 0xbc, 0xa5, 0x8d, 0x66, 0x18, 0xad, 0x60, 0x45, 0x20, 0x9b, 0xd7,
+     0x5b, 0xd6, 0x53, 0x11, 0x59, 0x9d, 0x68, 0xf9, 0x02, 0x5a, 0x25, 0x77,
+     0x41, 0x09, 0x6d, 0x9d, 0x6a, 0x99, 0x02, 0xc5, 0x25, 0x7a, 0x0b, 0xbc,
+     0x0d, 0xcb, 0x2e, 0x84, 0xad});
+
+static constexpr auto kRsa2048Public = std::to_array<uint8_t>(
+    {0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86,
+     0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00,
+     0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01, 0x01, 0x00, 0xb7, 0x73, 0x11,
+     0xa1, 0x47, 0x56, 0xb8, 0xdf, 0x45, 0xab, 0x81, 0xc1, 0x17, 0x24, 0x00,
+     0x6d, 0x70, 0xc2, 0x3f, 0x54, 0xac, 0x71, 0xda, 0xf5, 0x5d, 0xd8, 0x7c,
+     0x77, 0x36, 0xa6, 0x2e, 0x30, 0xbd, 0x07, 0x27, 0x34, 0x20, 0xdf, 0xc5,
+     0xf7, 0xdb, 0x9d, 0x93, 0xdc, 0x68, 0xa1, 0xc4, 0x02, 0x4f, 0xcb, 0xb1,
+     0x1e, 0xb9, 0xd0, 0x12, 0x92, 0xa2, 0xf0, 0x0d, 0xfb, 0xe6, 0xa0, 0x62,
+     0x36, 0xd2, 0x6e, 0xae, 0x03, 0xd5, 0xa8, 0x53, 0x46, 0x77, 0x7a, 0xe3,
+     0x83, 0xfe, 0x8d, 0x54, 0x29, 0x03, 0xc1, 0x19, 0x6d, 0x22, 0x44, 0x0f,
+     0x1a, 0x58, 0x57, 0x01, 0xef, 0x81, 0x3f, 0x0b, 0xf3, 0x96, 0x8e, 0x51,
+     0x40, 0xd3, 0x67, 0xd8, 0x85, 0x9f, 0x85, 0x80, 0x8e, 0x84, 0xc6, 0xaa,
+     0x35, 0xae, 0x35, 0xff, 0x38, 0x20, 0xbb, 0xe3, 0x95, 0xc5, 0xe3, 0xd6,
+     0xdd, 0xe9, 0x28, 0x87, 0xb9, 0x38, 0xce, 0xbd, 0x53, 0x17, 0x97, 0x90,
+     0xed, 0x87, 0xd5, 0x08, 0x6c, 0x84, 0xef, 0xb1, 0x9e, 0x71, 0x5e, 0x2c,
+     0x0b, 0xce, 0xc1, 0x76, 0xbb, 0xfb, 0xd6, 0x5b, 0x55, 0x85, 0xe0, 0x4d,
+     0xf7, 0xaf, 0xcf, 0x34, 0xe1, 0x40, 0x0a, 0x76, 0x1f, 0xb1, 0xdc, 0xd7,
+     0xb0, 0x63, 0xa2, 0xc7, 0x23, 0x85, 0x56, 0xfd, 0x8c, 0xec, 0x6b, 0x0f,
+     0xfb, 0x6e, 0xcb, 0xba, 0x3b, 0x12, 0x04, 0xbb, 0xf7, 0x5d, 0x28, 0xe2,
+     0x57, 0x9c, 0x38, 0x1d, 0x8f, 0xc9, 0x51, 0x3f, 0x43, 0x8a, 0x6e, 0xb0,
+     0xa1, 0xbb, 0x6f, 0x19, 0x79, 0x8f, 0x81, 0x5c, 0xd9, 0x89, 0x91, 0xde,
+     0xfa, 0xce, 0x09, 0x53, 0xb7, 0x37, 0x36, 0x04, 0x48, 0x5f, 0x44, 0x7c,
+     0x19, 0x09, 0xb8, 0x1b, 0x8f, 0xf1, 0xf5, 0x86, 0x3a, 0x81, 0xf8, 0x44,
+     0x76, 0x72, 0xc8, 0x4b, 0xbf, 0x70, 0x75, 0xb7, 0xc1, 0x5d, 0xff, 0x91,
+     0xdd, 0x02, 0x03, 0x01, 0x00, 0x01});
+
+static constexpr auto kRsa4096Private = std::to_array<uint8_t>(
+    {0x30, 0x82, 0x09, 0x43, 0x02, 0x01, 0x00, 0x30, 0x0d, 0x06, 0x09, 0x2a,
+     0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x04, 0x82,
+     0x09, 0x2d, 0x30, 0x82, 0x09, 0x29, 0x02, 0x01, 0x00, 0x02, 0x82, 0x02,
+     0x01, 0x00, 0xc5, 0xca, 0x53, 0x4b, 0x6e, 0x08, 0x5c, 0xa2, 0x96, 0x53,
+     0xe3, 0x3e, 0xfd, 0xf8, 0x31, 0x93, 0x56, 0x50, 0x0b, 0xec, 0x46, 0x5c,
+     0x21, 0x78, 0x66, 0x1a, 0x94, 0x2f, 0xcb, 0xec, 0x52, 0xba, 0xa7, 0xe5,
+     0xeb, 0x39, 0x8a, 0x34, 0xd6, 0x94, 0x37, 0xd9, 0x4e, 0xd5, 0xab, 0xaa,
+     0xb8, 0xde, 0xc4, 0xe4, 0x05, 0xa5, 0x51, 0xeb, 0x74, 0x15, 0x9a, 0x99,
+     0x79, 0xe2, 0x51, 0xb2, 0x4d, 0xc9, 0x90, 0xd8, 0x47, 0x5b, 0x78, 0xee,
+     0x15, 0xa4, 0xb7, 0x72, 0x94, 0x39, 0x09, 0x77, 0xf9, 0x6e, 0x48, 0xae,
+     0x7c, 0xa5, 0x65, 0xf3, 0x92, 0xb2, 0xad, 0x3f, 0x43, 0x4a, 0xfb, 0x9f,
+     0x33, 0x46, 0x18, 0x72, 0x08, 0xe2, 0x68, 0xf7, 0xa6, 0x65, 0x32, 0x0b,
+     0xa4, 0xa7, 0xc4, 0x64, 0xbb, 0xd4, 0x6e, 0x60, 0x9f, 0x38, 0x3a, 0x5b,
+     0x26, 0x82, 0x1c, 0x20, 0x66, 0x8f, 0x4b, 0x34, 0x44, 0x45, 0xdf, 0xd6,
+     0xe2, 0x32, 0x1e, 0xa8, 0x4e, 0xb2, 0x3d, 0xa5, 0x5d, 0xe7, 0x22, 0x11,
+     0x3f, 0x4a, 0xc0, 0xba, 0xea, 0x58, 0xf5, 0x22, 0xa4, 0x53, 0x49, 0xfa,
+     0x83, 0xd1, 0xa1, 0x90, 0xd8, 0xfc, 0x5c, 0x02, 0x74, 0xa1, 0xd8, 0xaf,
+     0xc7, 0xec, 0x97, 0x2f, 0x1f, 0xf9, 0xda, 0xb6, 0xd0, 0x4e, 0x7e, 0x48,
+     0xd0, 0x22, 0xf7, 0x4d, 0x8a, 0x6d, 0xef, 0xe4, 0xc0, 0xf9, 0x04, 0xbb,
+     0x1e, 0x84, 0x96, 0xa8, 0x78, 0xd0, 0x21, 0xb6, 0x24, 0x0c, 0x39, 0x2f,
+     0xd0, 0x47, 0x3f, 0x1d, 0x6e, 0xcc, 0xef, 0x59, 0xf1, 0xd8, 0xba, 0x11,
+     0xab, 0x2c, 0x72, 0x6b, 0xef, 0x81, 0xcd, 0xf7, 0x1b, 0x3d, 0xcf, 0x39,
+     0x66, 0x7e, 0xf2, 0x11, 0x41, 0x8b, 0x19, 0x7c, 0x77, 0xdf, 0x89, 0x35,
+     0x04, 0xd8, 0x55, 0xfa, 0xb1, 0xe5, 0xca, 0xdd, 0xca, 0x85, 0x03, 0xcb,
+     0xb1, 0xca, 0xa2, 0x76, 0x70, 0xa8, 0x4a, 0x6e, 0x20, 0x0e, 0x25, 0xd4,
+     0xb4, 0xda, 0xe6, 0x10, 0x04, 0x82, 0xd3, 0x70, 0x0c, 0xd3, 0x01, 0x82,
+     0x46, 0x63, 0x1f, 0x96, 0x4c, 0x9a, 0x9b, 0x24, 0x9e, 0xab, 0xc3, 0x0b,
+     0x9a, 0x3c, 0x94, 0x9e, 0xf0, 0xef, 0x1a, 0xac, 0xa0, 0x74, 0x3b, 0x0d,
+     0x10, 0xdd, 0xbe, 0xca, 0x7a, 0x69, 0x5e, 0x70, 0x90, 0xaa, 0x96, 0xe2,
+     0xa8, 0x46, 0x9b, 0x9c, 0xa2, 0x19, 0xc2, 0x55, 0x07, 0xbf, 0x6c, 0x1a,
+     0x8b, 0x7f, 0x34, 0xf2, 0x3c, 0x6f, 0x21, 0x60, 0x6b, 0x7e, 0xc0, 0x79,
+     0x35, 0x5b, 0x8e, 0x5a, 0x2e, 0xec, 0xfb, 0x92, 0xfb, 0xa7, 0x73, 0x6b,
+     0xde, 0xe5, 0xd3, 0xbb, 0xa7, 0x83, 0xbc, 0xdb, 0x73, 0x1b, 0xdc, 0xb8,
+     0xe1, 0x9c, 0x1c, 0xec, 0x39, 0x2c, 0xaf, 0xcd, 0x25, 0x45, 0xa6, 0x56,
+     0x70, 0x9b, 0x31, 0x88, 0x66, 0x01, 0x09, 0x7c, 0x61, 0xfb, 0xca, 0x05,
+     0x93, 0x7a, 0x16, 0x2a, 0xdb, 0xbd, 0xf2, 0xe1, 0x46, 0x01, 0x5e, 0x8e,
+     0x82, 0x68, 0x8f, 0xf1, 0xd8, 0x9a, 0x30, 0xda, 0xa3, 0xf1, 0x9a, 0x67,
+     0xd8, 0x59, 0xe8, 0x4c, 0x89, 0x70, 0x97, 0xdc, 0x6d, 0x83, 0x1a, 0x01,
+     0xe8, 0x00, 0x74, 0xfd, 0x44, 0x73, 0x0f, 0x86, 0x72, 0x03, 0x71, 0x48,
+     0x90, 0x01, 0x12, 0xd3, 0xd1, 0x85, 0x2e, 0x67, 0xf1, 0x07, 0x78, 0x8d,
+     0x9f, 0xc8, 0x44, 0xd1, 0xa3, 0x41, 0x9f, 0xd7, 0xc6, 0x50, 0x44, 0x9d,
+     0x09, 0xf4, 0xab, 0x7c, 0x12, 0x65, 0x59, 0xc4, 0x43, 0x51, 0x94, 0x25,
+     0x9d, 0x0a, 0xb1, 0x1b, 0xd2, 0x3d, 0xeb, 0xd4, 0xef, 0xbf, 0xd7, 0x66,
+     0xa3, 0x51, 0x67, 0xc1, 0x42, 0x88, 0xe2, 0x7a, 0x28, 0x9a, 0x40, 0x1a,
+     0x60, 0xce, 0xa2, 0x42, 0x72, 0x45, 0x8a, 0x4c, 0x08, 0xcc, 0x06, 0xa0,
+     0x6d, 0xab, 0xa3, 0xea, 0xee, 0x63, 0x65, 0x9e, 0x40, 0x19, 0x02, 0x03,
+     0x01, 0x00, 0x01, 0x02, 0x82, 0x02, 0x00, 0x0d, 0xec, 0x84, 0x80, 0xdd,
+     0xc4, 0x03, 0xae, 0x73, 0xf5, 0xff, 0x9c, 0x4c, 0x81, 0x7b, 0x8e, 0xf7,
+     0x5f, 0x6e, 0xf9, 0x71, 0x0a, 0x15, 0x16, 0x9e, 0x5c, 0x7e, 0x64, 0x5f,
+     0x7f, 0x19, 0x63, 0x57, 0xc4, 0xc3, 0x32, 0x93, 0xa8, 0xcc, 0xd6, 0xd6,
+     0x18, 0x2b, 0x99, 0x35, 0xea, 0xb0, 0xb5, 0x49, 0x07, 0xd2, 0xe0, 0x6c,
+     0xa4, 0x0e, 0x51, 0xe3, 0x86, 0x6c, 0xaa, 0xca, 0xac, 0xca, 0x56, 0x57,
+     0x66, 0xac, 0x5e, 0x53, 0x84, 0xf2, 0x3b, 0xdc, 0x68, 0xb9, 0xe7, 0xca,
+     0x83, 0x7a, 0x49, 0x21, 0xdf, 0x7b, 0xb9, 0xa2, 0x93, 0xdb, 0x36, 0xce,
+     0x24, 0xb3, 0x1a, 0x6d, 0x1c, 0x8b, 0xdd, 0xe2, 0x44, 0x6b, 0xdc, 0xf1,
+     0x7e, 0x06, 0xa6, 0x1e, 0xd7, 0xec, 0x3d, 0x5d, 0xc1, 0x68, 0x2a, 0x28,
+     0x23, 0x8f, 0xed, 0xe3, 0xc8, 0xcd, 0x2d, 0x11, 0x69, 0x14, 0x03, 0x40,
+     0x2c, 0x73, 0xa6, 0x76, 0x1f, 0xb0, 0xe0, 0x8d, 0xa1, 0x34, 0x5a, 0x0e,
+     0xa6, 0x2c, 0xb6, 0x43, 0xe3, 0x12, 0x31, 0x33, 0x60, 0x6d, 0x35, 0x3a,
+     0x3b, 0x98, 0x35, 0x1f, 0x52, 0xfe, 0x12, 0xe4, 0xc4, 0x77, 0x4c, 0x0b,
+     0xb7, 0xa6, 0x09, 0x8b, 0x41, 0x33, 0xaa, 0x33, 0x89, 0xef, 0x1e, 0xb6,
+     0x1f, 0x92, 0xca, 0x45, 0xd0, 0x5f, 0x2b, 0xa8, 0xfc, 0x79, 0xd3, 0x6b,
+     0xb5, 0x0f, 0x43, 0x30, 0x8a, 0xb2, 0x37, 0x2c, 0x0a, 0x7b, 0xe7, 0xce,
+     0x09, 0xbe, 0x1f, 0xa8, 0xaa, 0x75, 0xa7, 0x80, 0x59, 0x54, 0x76, 0xa3,
+     0x9e, 0x71, 0xbc, 0xbb, 0xbb, 0x98, 0xf0, 0xe9, 0xf4, 0xb0, 0x81, 0x93,
+     0x33, 0xf3, 0x44, 0x5e, 0x89, 0x58, 0x4e, 0x75, 0x40, 0xd2, 0xfc, 0x05,
+     0x7d, 0x2a, 0x4c, 0xaf, 0xa2, 0x6e, 0x7f, 0x02, 0xcd, 0x81, 0x56, 0xcf,
+     0x99, 0x5d, 0xc3, 0x8f, 0xac, 0xf8, 0x2d, 0x0f, 0x53, 0x16, 0x72, 0xd8,
+     0xb5, 0xe6, 0x05, 0x2b, 0x1d, 0xc6, 0x4c, 0x2e, 0x71, 0xbf, 0x20, 0xfa,
+     0xda, 0xef, 0x24, 0x9c, 0xba, 0x06, 0x47, 0x5b, 0xab, 0xc1, 0xf7, 0x5c,
+     0x8f, 0x49, 0x6c, 0xfd, 0xb6, 0x10, 0x55, 0x88, 0x65, 0xd1, 0xed, 0x91,
+     0x56, 0xa2, 0xc0, 0xd1, 0x76, 0x76, 0x3b, 0x77, 0x93, 0x18, 0x83, 0xcd,
+     0xa4, 0xc7, 0x7c, 0xe2, 0xfb, 0x37, 0x88, 0xcc, 0x87, 0x84, 0xb1, 0x30,
+     0x2e, 0x9a, 0x12, 0xca, 0xd8, 0xe5, 0xff, 0xe9, 0x90, 0x89, 0x9d, 0x6e,
+     0xf8, 0xae, 0x8f, 0x4b, 0x33, 0x66, 0x81, 0xbb, 0xe4, 0xef, 0xe1, 0xb4,
+     0xbe, 0x05, 0x7e, 0x40, 0x72, 0x7c, 0x42, 0x7f, 0xb8, 0x02, 0xd7, 0xd0,
+     0x94, 0x2e, 0x47, 0xa5, 0x01, 0xe6, 0x0c, 0x60, 0xd2, 0xe7, 0x8e, 0x38,
+     0x5a, 0x21, 0xf7, 0x4e, 0xf8, 0xa7, 0x7f, 0x9f, 0xaf, 0x2a, 0x8e, 0x6d,
+     0x82, 0x89, 0x87, 0x76, 0xad, 0x29, 0xfe, 0x9d, 0xa4, 0x24, 0xe8, 0x68,
+     0x12, 0x94, 0x80, 0x7f, 0x70, 0xe7, 0x7c, 0x8e, 0xfd, 0x8c, 0xb4, 0x5c,
+     0xc0, 0x3a, 0xb0, 0xca, 0xbb, 0x6f, 0x31, 0x50, 0x71, 0x16, 0xa7, 0x71,
+     0x00, 0xfa, 0x19, 0x8b, 0x45, 0xf2, 0xb4, 0x97, 0xee, 0x45, 0x4c, 0xb3,
+     0xf4, 0x8c, 0x35, 0x5b, 0x29, 0x60, 0xc1, 0xb1, 0xb4, 0x98, 0x2f, 0x89,
+     0xbe, 0xc5, 0xb5, 0x6d, 0xda, 0x1a, 0xc0, 0xa4, 0x24, 0x9e, 0xa3, 0x6c,
+     0xa9, 0x02, 0x00, 0xd8, 0x7d, 0x9d, 0x04, 0xea, 0xe4, 0xbb, 0x85, 0xb1,
+     0x7f, 0xf8, 0xd3, 0x51, 0x17, 0x86, 0xf1, 0x69, 0x68, 0x17, 0x2c, 0x27,
+     0x64, 0x77, 0xf9, 0x62, 0x7c, 0xbb, 0x10, 0x3c, 0xd4, 0x2e, 0x9e, 0x5c,
+     0xb3, 0xaa, 0xec, 0x97, 0x18, 0x50, 0x0d, 0xbc, 0x20, 0x2d, 0x7b, 0xd0,
+     0x69, 0xe8, 0xae, 0x72, 0x3c, 0xdd, 0xdc, 0x40, 0xff, 0x57, 0xb4, 0xfe,
+     0x08, 0x05, 0x81, 0x02, 0x82, 0x01, 0x01, 0x00, 0xf2, 0x9d, 0xdf, 0xd3,
+     0xef, 0xa3, 0x95, 0xf9, 0xae, 0xc6, 0x73, 0xcb, 0xcb, 0x26, 0x65, 0x99,
+     0x54, 0xd6, 0x3c, 0x3f, 0xfd, 0x86, 0xec, 0x61, 0x9d, 0x6a, 0xcf, 0x64,
+     0x64, 0x3d, 0xe5, 0xeb, 0x9d, 0x49, 0xcb, 0x07, 0x3e, 0xf0, 0x17, 0xb3,
+     0xcd, 0x00, 0x9b, 0xf6, 0x53, 0x13, 0x30, 0x23, 0x47, 0xe5, 0xec, 0xaa,
+     0x43, 0xfb, 0x7a, 0x1b, 0x4b, 0xfb, 0x9c, 0xa6, 0xdd, 0x05, 0x70, 0xd2,
+     0xab, 0xaa, 0xee, 0x10, 0x32, 0xdd, 0xcd, 0x81, 0x00, 0xe1, 0x52, 0x71,
+     0x43, 0xa8, 0x64, 0x64, 0xb1, 0x8c, 0x2a, 0xa7, 0xbf, 0x05, 0x26, 0x99,
+     0x6e, 0x65, 0xe1, 0x8c, 0x5d, 0x1c, 0x97, 0xc6, 0xa0, 0x47, 0xcc, 0xd1,
+     0x06, 0xb8, 0xe8, 0x06, 0x97, 0x1c, 0x9c, 0x7c, 0xde, 0xd5, 0x28, 0xc1,
+     0x9f, 0xa3, 0xce, 0x3e, 0x75, 0xe0, 0x8c, 0x6f, 0x06, 0xb0, 0xff, 0xc9,
+     0x96, 0x34, 0x68, 0x56, 0xe1, 0xca, 0x21, 0x89, 0xc7, 0x66, 0xb5, 0x87,
+     0x8b, 0xe2, 0xf4, 0x55, 0x6f, 0x85, 0x7d, 0x6e, 0x57, 0xbd, 0x0f, 0x4a,
+     0x92, 0xfd, 0xa8, 0x8e, 0x6e, 0x87, 0x82, 0xb0, 0x4b, 0x50, 0x80, 0xeb,
+     0x2c, 0x8b, 0xf4, 0xf1, 0x35, 0x23, 0xaf, 0x42, 0xfd, 0xf6, 0x36, 0xce,
+     0xcb, 0x5e, 0xe8, 0x4f, 0xcc, 0x67, 0xa3, 0x43, 0x74, 0x10, 0x61, 0x88,
+     0xc2, 0x1c, 0x61, 0xde, 0xc0, 0xfc, 0xd1, 0x60, 0xd1, 0xed, 0x0e, 0x07,
+     0xd8, 0x05, 0xdf, 0xee, 0xcb, 0xcb, 0x26, 0x90, 0x90, 0xa1, 0xf9, 0x80,
+     0x86, 0x84, 0xcb, 0x38, 0x0c, 0x30, 0xc3, 0x2d, 0x8b, 0x18, 0xc8, 0x4e,
+     0xfc, 0x26, 0xad, 0x20, 0x8c, 0x0c, 0x2b, 0x08, 0x78, 0x21, 0x28, 0xcb,
+     0xe4, 0x09, 0x70, 0x64, 0xbf, 0xdd, 0xfa, 0x3d, 0xff, 0xa7, 0xf4, 0xf5,
+     0x0c, 0x51, 0x84, 0x6c, 0xda, 0x93, 0xc4, 0x0b, 0x75, 0x18, 0xdd, 0x99,
+     0x02, 0x82, 0x01, 0x01, 0x00, 0xd0, 0xb3, 0x6e, 0xd7, 0x23, 0x36, 0x15,
+     0xaa, 0x0d, 0xf9, 0x46, 0xfc, 0xa0, 0xfc, 0xef, 0x39, 0xa0, 0x51, 0x82,
+     0xcb, 0x8a, 0x97, 0xba, 0xc5, 0xaa, 0x48, 0xe2, 0xc6, 0x36, 0xfe, 0xb2,
+     0x95, 0x77, 0x84, 0x84, 0x3f, 0x87, 0x37, 0x9b, 0xa7, 0x9f, 0x2c, 0xad,
+     0x65, 0x00, 0x1b, 0x94, 0x53, 0xef, 0xf5, 0x86, 0x10, 0x35, 0x7d, 0x1f,
+     0x16, 0xe8, 0xa7, 0x5a, 0xfc, 0x56, 0xd0, 0xe1, 0xff, 0xaf, 0x34, 0x10,
+     0x71, 0x50, 0x71, 0xfd, 0x5c, 0xb2, 0x0c, 0x6a, 0xab, 0x70, 0xa3, 0xa3,
+     0x3d, 0xe8, 0x02, 0xbf, 0xdd, 0xe9, 0x4d, 0x80, 0x79, 0xe5, 0xef, 0xe3,
+     0x17, 0x39, 0xd9, 0x05, 0xa2, 0xbe, 0xd1, 0x03, 0x59, 0x21, 0x00, 0xcf,
+     0xa4, 0x06, 0xa6, 0xe2, 0x3d, 0xa4, 0x2f, 0x2f, 0xa1, 0x55, 0x8a, 0x86,
+     0x6f, 0xba, 0x3b, 0x48, 0x13, 0x79, 0x65, 0x0a, 0x82, 0xed, 0x00, 0x26,
+     0x77, 0xcb, 0x91, 0x56, 0xc2, 0x86, 0x61, 0xe7, 0x1f, 0x6a, 0x66, 0x6b,
+     0xd1, 0xea, 0x76, 0x58, 0xd4, 0xf1, 0x5a, 0x09, 0xb3, 0xed, 0x5f, 0x1b,
+     0x70, 0x14, 0x6b, 0x6a, 0x47, 0xe1, 0xb5, 0x95, 0x36, 0x7d, 0xb7, 0x34,
+     0x4d, 0xd3, 0x3f, 0x3b, 0x09, 0x0f, 0x76, 0x36, 0x57, 0x89, 0x4d, 0x7d,
+     0x36, 0x69, 0xcd, 0x05, 0x19, 0x26, 0x3a, 0x52, 0x2d, 0x3b, 0x7c, 0x85,
+     0x58, 0x96, 0x29, 0x9a, 0x7d, 0xed, 0xe7, 0x05, 0x55, 0xfb, 0xfe, 0x7d,
+     0x26, 0x1f, 0xbe, 0x53, 0x81, 0xe2, 0xb9, 0xb7, 0xa5, 0x4a, 0xb3, 0x82,
+     0x98, 0xb7, 0x61, 0xb3, 0x87, 0xba, 0x83, 0xb0, 0x51, 0x0e, 0xa0, 0x0b,
+     0x00, 0x23, 0x93, 0x54, 0x93, 0xcf, 0xba, 0xab, 0xef, 0x91, 0x6a, 0x89,
+     0x69, 0x65, 0x53, 0xe8, 0x18, 0x31, 0x90, 0x4c, 0x39, 0x32, 0x19, 0x06,
+     0xe9, 0x22, 0x92, 0xcb, 0xe9, 0x93, 0x2d, 0x06, 0x81, 0x02, 0x82, 0x01,
+     0x01, 0x00, 0x85, 0x2a, 0xea, 0xa4, 0x65, 0xb0, 0xa0, 0xad, 0x3f, 0xa5,
+     0x66, 0x01, 0xc2, 0x2b, 0xfd, 0x30, 0x40, 0x44, 0xa7, 0x25, 0x68, 0x7f,
+     0x1a, 0x58, 0x2a, 0x13, 0x6a, 0x6a, 0x6c, 0x2b, 0x1d, 0x7c, 0x4a, 0x05,
+     0x2b, 0x0f, 0x7b, 0x18, 0x45, 0xaa, 0x47, 0x27, 0xc2, 0x73, 0x5d, 0xfd,
+     0xf6, 0x3a, 0x27, 0x48, 0xa8, 0xb2, 0x7c, 0x46, 0x18, 0x2b, 0xcb, 0x74,
+     0xfb, 0xf3, 0x0a, 0xf5, 0xe5, 0x8b, 0x7c, 0xf6, 0x81, 0x5d, 0x1a, 0xce,
+     0x6b, 0xf2, 0x17, 0x0d, 0x96, 0x36, 0xdd, 0x30, 0x1c, 0x8a, 0xb8, 0x79,
+     0x7b, 0x20, 0x3f, 0xd5, 0x0f, 0xee, 0xbf, 0x8d, 0xe4, 0x53, 0x2a, 0xf8,
+     0x7a, 0xc6, 0x67, 0x4b, 0x81, 0xbc, 0x69, 0xb8, 0x2c, 0x83, 0x11, 0x86,
+     0x7e, 0xce, 0x7b, 0x70, 0xfc, 0xc1, 0xea, 0x61, 0xfa, 0xde, 0x85, 0xcd,
+     0x0e, 0xc8, 0x12, 0x20, 0x2d, 0x05, 0xf5, 0x10, 0x27, 0x05, 0x29, 0x41,
+     0xcd, 0x4e, 0xd6, 0xc8, 0x25, 0x73, 0x94, 0xf0, 0xa9, 0xb4, 0x0b, 0x56,
+     0x76, 0x16, 0x8e, 0xca, 0x13, 0x0e, 0x97, 0xc7, 0xeb, 0x30, 0xe2, 0xb2,
+     0x36, 0xa5, 0x95, 0x71, 0x46, 0x91, 0xcf, 0x0d, 0xb3, 0x10, 0x82, 0x5e,
+     0x67, 0xe0, 0x99, 0x8c, 0xff, 0x60, 0x5a, 0x78, 0x69, 0x83, 0x03, 0x6b,
+     0x0f, 0x4e, 0x02, 0xf2, 0xca, 0xd6, 0x49, 0xd5, 0x52, 0x52, 0xf6, 0x12,
+     0xce, 0xca, 0x19, 0xef, 0xc7, 0xf7, 0xbe, 0x36, 0xdb, 0x47, 0x33, 0x4c,
+     0xfa, 0x89, 0xf7, 0x19, 0x30, 0xdd, 0xbe, 0xf8, 0x3c, 0xa3, 0x32, 0xed,
+     0xc0, 0xf3, 0xca, 0x99, 0x7b, 0xb8, 0xfe, 0xe6, 0x2b, 0xb8, 0xe5, 0xa5,
+     0xf8, 0x28, 0xd9, 0xe7, 0x39, 0x81, 0x50, 0x55, 0x6d, 0xff, 0x9e, 0xe0,
+     0xb4, 0x6e, 0x3b, 0x59, 0x71, 0x36, 0xdf, 0xe0, 0x0e, 0x93, 0xc1, 0x15,
+     0xc2, 0x51, 0x97, 0xa0, 0x62, 0x61, 0x02, 0x82, 0x01, 0x00, 0x02, 0x1e,
+     0xf0, 0xaf, 0x6b, 0x02, 0x2f, 0xb2, 0x2c, 0xb6, 0x2d, 0xcc, 0x7f, 0x6e,
+     0x52, 0x98, 0x09, 0x53, 0x0a, 0xbb, 0x3a, 0xcb, 0x53, 0xf0, 0x92, 0x4c,
+     0x6f, 0x51, 0x88, 0x59, 0x8a, 0x43, 0x0e, 0x95, 0xe0, 0x2a, 0x2d, 0x1b,
+     0x99, 0x8f, 0x58, 0x84, 0xc1, 0xb6, 0x57, 0x0b, 0xf0, 0xb3, 0xf1, 0xaa,
+     0x53, 0x14, 0x73, 0x16, 0xb4, 0x6c, 0x2d, 0x2d, 0x16, 0x35, 0x9e, 0x44,
+     0x3d, 0x27, 0xb6, 0x06, 0x17, 0x6c, 0xaf, 0x5e, 0x99, 0x2e, 0x89, 0xf8,
+     0xaa, 0x54, 0xd7, 0xae, 0x32, 0x08, 0x7d, 0x05, 0x1a, 0x22, 0x0d, 0x2e,
+     0xe6, 0x71, 0x56, 0xae, 0xdb, 0x65, 0xef, 0x06, 0x8f, 0x92, 0x19, 0xd2,
+     0x51, 0xf0, 0x63, 0xef, 0x78, 0x2c, 0xb8, 0x8c, 0x95, 0x9b, 0xfe, 0xc1,
+     0x24, 0x00, 0xc5, 0xb7, 0xc4, 0xa9, 0xfa, 0x00, 0x84, 0x38, 0xfe, 0x70,
+     0xd2, 0x6c, 0x86, 0x30, 0x0c, 0x34, 0x07, 0x73, 0x90, 0xa8, 0x25, 0x69,
+     0x75, 0x49, 0xd2, 0x70, 0xfc, 0x03, 0x84, 0x18, 0x73, 0xca, 0xa6, 0x31,
+     0x3e, 0x0c, 0x00, 0x0b, 0x89, 0x61, 0xd8, 0x33, 0x47, 0x3f, 0x37, 0xc1,
+     0xfa, 0xa4, 0x35, 0x1e, 0xd9, 0x7f, 0x38, 0xf8, 0x59, 0x87, 0x3c, 0x0a,
+     0xfd, 0x7e, 0x62, 0x3c, 0xd1, 0x5a, 0xee, 0x34, 0x51, 0x2a, 0xf2, 0x42,
+     0x81, 0x77, 0x48, 0x35, 0x79, 0xbd, 0x6e, 0xb9, 0x39, 0x82, 0xb2, 0x1a,
+     0x38, 0xe9, 0xa8, 0xc7, 0xeb, 0x49, 0xa9, 0xe4, 0xeb, 0x40, 0x54, 0xa7,
+     0x82, 0x80, 0x41, 0x84, 0x15, 0x7d, 0xab, 0xcf, 0x68, 0x5d, 0xa6, 0xbd,
+     0x93, 0xdb, 0x1f, 0x04, 0xed, 0x57, 0xb1, 0x04, 0xdc, 0x45, 0x2c, 0x45,
+     0x3f, 0x5e, 0x0d, 0xe2, 0x41, 0x47, 0x3a, 0xea, 0x61, 0x5f, 0x6d, 0x91,
+     0x83, 0xd4, 0xc8, 0xf7, 0x8c, 0x24, 0x6e, 0x5f, 0x83, 0x86, 0xfa, 0x21,
+     0xe9, 0x01, 0x02, 0x82, 0x01, 0x01, 0x00, 0x9c, 0xe1, 0x80, 0x78, 0xc7,
+     0x12, 0xb2, 0x59, 0xe6, 0x3a, 0xe4, 0x3b, 0x0e, 0xf3, 0x28, 0xec, 0x01,
+     0xbf, 0x5a, 0xde, 0xa9, 0x40, 0x3a, 0x44, 0x44, 0x73, 0xaf, 0xe0, 0x2a,
+     0xe7, 0x75, 0xec, 0x38, 0x9b, 0x25, 0x4f, 0x2d, 0xf6, 0x2f, 0x29, 0x90,
+     0x13, 0x7b, 0xa3, 0x5a, 0xab, 0x55, 0xde, 0x13, 0xbd, 0x01, 0x94, 0x5f,
+     0x8b, 0xfc, 0x31, 0x43, 0x7d, 0xb2, 0x25, 0xe0, 0xbe, 0xc4, 0xbb, 0x3f,
+     0xf2, 0x9f, 0x05, 0x6a, 0x75, 0xe1, 0xf5, 0x49, 0xb2, 0xb3, 0xd2, 0x19,
+     0x1b, 0x32, 0x58, 0x2b, 0x6b, 0x48, 0xc2, 0x70, 0x64, 0x0d, 0x25, 0x0b,
+     0x44, 0x2e, 0x31, 0xf6, 0x50, 0xcd, 0x5b, 0xb5, 0xc4, 0xd9, 0xc3, 0x68,
+     0x24, 0x2a, 0x2b, 0x01, 0x31, 0xf5, 0xb8, 0x13, 0x0d, 0x08, 0xd9, 0x6d,
+     0x74, 0x69, 0xf4, 0x86, 0x65, 0x17, 0x0c, 0xe9, 0x36, 0x39, 0x98, 0xb0,
+     0x41, 0x8c, 0xf3, 0xcc, 0x5b, 0x5e, 0x19, 0x58, 0x98, 0x49, 0xb6, 0xcf,
+     0x81, 0xc2, 0x5f, 0x8e, 0x39, 0xf3, 0x00, 0x99, 0x4a, 0xca, 0x1a, 0xe5,
+     0x29, 0x3a, 0xe0, 0x9e, 0xfc, 0x7c, 0xbb, 0x74, 0x55, 0x12, 0xa8, 0x58,
+     0xe0, 0x60, 0x52, 0xbf, 0xd6, 0xcd, 0x75, 0x81, 0x1e, 0x62, 0xdb, 0x30,
+     0x71, 0x5f, 0xb0, 0xf6, 0x1a, 0xa8, 0x45, 0xe2, 0x26, 0xc7, 0xa2, 0x73,
+     0xfd, 0xd8, 0xeb, 0x14, 0xee, 0x43, 0x5f, 0x7d, 0x19, 0xa6, 0x9d, 0xed,
+     0x12, 0x31, 0x91, 0x36, 0x1c, 0x87, 0x17, 0x97, 0xbb, 0x98, 0x74, 0x6c,
+     0xce, 0x5a, 0xe4, 0x9a, 0x26, 0xa0, 0x6f, 0x67, 0x99, 0x70, 0x78, 0xe6,
+     0x90, 0x01, 0x36, 0x8d, 0x38, 0x0e, 0x85, 0xeb, 0x04, 0x2c, 0xcb, 0xe0,
+     0xd5, 0xca, 0xe1, 0x8b, 0xff, 0xea, 0xe7, 0xf1, 0x20, 0xed, 0x56, 0x6d,
+     0x6a, 0x4c, 0xf6, 0xa2, 0xc7, 0x54, 0xf5, 0xbf, 0xc5, 0x48, 0x19});
+
+static constexpr auto kRsa4096Public = std::to_array<uint8_t>(
+    {0x30, 0x82, 0x02, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86,
+     0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x02, 0x0f, 0x00,
+     0x30, 0x82, 0x02, 0x0a, 0x02, 0x82, 0x02, 0x01, 0x00, 0xc5, 0xca, 0x53,
+     0x4b, 0x6e, 0x08, 0x5c, 0xa2, 0x96, 0x53, 0xe3, 0x3e, 0xfd, 0xf8, 0x31,
+     0x93, 0x56, 0x50, 0x0b, 0xec, 0x46, 0x5c, 0x21, 0x78, 0x66, 0x1a, 0x94,
+     0x2f, 0xcb, 0xec, 0x52, 0xba, 0xa7, 0xe5, 0xeb, 0x39, 0x8a, 0x34, 0xd6,
+     0x94, 0x37, 0xd9, 0x4e, 0xd5, 0xab, 0xaa, 0xb8, 0xde, 0xc4, 0xe4, 0x05,
+     0xa5, 0x51, 0xeb, 0x74, 0x15, 0x9a, 0x99, 0x79, 0xe2, 0x51, 0xb2, 0x4d,
+     0xc9, 0x90, 0xd8, 0x47, 0x5b, 0x78, 0xee, 0x15, 0xa4, 0xb7, 0x72, 0x94,
+     0x39, 0x09, 0x77, 0xf9, 0x6e, 0x48, 0xae, 0x7c, 0xa5, 0x65, 0xf3, 0x92,
+     0xb2, 0xad, 0x3f, 0x43, 0x4a, 0xfb, 0x9f, 0x33, 0x46, 0x18, 0x72, 0x08,
+     0xe2, 0x68, 0xf7, 0xa6, 0x65, 0x32, 0x0b, 0xa4, 0xa7, 0xc4, 0x64, 0xbb,
+     0xd4, 0x6e, 0x60, 0x9f, 0x38, 0x3a, 0x5b, 0x26, 0x82, 0x1c, 0x20, 0x66,
+     0x8f, 0x4b, 0x34, 0x44, 0x45, 0xdf, 0xd6, 0xe2, 0x32, 0x1e, 0xa8, 0x4e,
+     0xb2, 0x3d, 0xa5, 0x5d, 0xe7, 0x22, 0x11, 0x3f, 0x4a, 0xc0, 0xba, 0xea,
+     0x58, 0xf5, 0x22, 0xa4, 0x53, 0x49, 0xfa, 0x83, 0xd1, 0xa1, 0x90, 0xd8,
+     0xfc, 0x5c, 0x02, 0x74, 0xa1, 0xd8, 0xaf, 0xc7, 0xec, 0x97, 0x2f, 0x1f,
+     0xf9, 0xda, 0xb6, 0xd0, 0x4e, 0x7e, 0x48, 0xd0, 0x22, 0xf7, 0x4d, 0x8a,
+     0x6d, 0xef, 0xe4, 0xc0, 0xf9, 0x04, 0xbb, 0x1e, 0x84, 0x96, 0xa8, 0x78,
+     0xd0, 0x21, 0xb6, 0x24, 0x0c, 0x39, 0x2f, 0xd0, 0x47, 0x3f, 0x1d, 0x6e,
+     0xcc, 0xef, 0x59, 0xf1, 0xd8, 0xba, 0x11, 0xab, 0x2c, 0x72, 0x6b, 0xef,
+     0x81, 0xcd, 0xf7, 0x1b, 0x3d, 0xcf, 0x39, 0x66, 0x7e, 0xf2, 0x11, 0x41,
+     0x8b, 0x19, 0x7c, 0x77, 0xdf, 0x89, 0x35, 0x04, 0xd8, 0x55, 0xfa, 0xb1,
+     0xe5, 0xca, 0xdd, 0xca, 0x85, 0x03, 0xcb, 0xb1, 0xca, 0xa2, 0x76, 0x70,
+     0xa8, 0x4a, 0x6e, 0x20, 0x0e, 0x25, 0xd4, 0xb4, 0xda, 0xe6, 0x10, 0x04,
+     0x82, 0xd3, 0x70, 0x0c, 0xd3, 0x01, 0x82, 0x46, 0x63, 0x1f, 0x96, 0x4c,
+     0x9a, 0x9b, 0x24, 0x9e, 0xab, 0xc3, 0x0b, 0x9a, 0x3c, 0x94, 0x9e, 0xf0,
+     0xef, 0x1a, 0xac, 0xa0, 0x74, 0x3b, 0x0d, 0x10, 0xdd, 0xbe, 0xca, 0x7a,
+     0x69, 0x5e, 0x70, 0x90, 0xaa, 0x96, 0xe2, 0xa8, 0x46, 0x9b, 0x9c, 0xa2,
+     0x19, 0xc2, 0x55, 0x07, 0xbf, 0x6c, 0x1a, 0x8b, 0x7f, 0x34, 0xf2, 0x3c,
+     0x6f, 0x21, 0x60, 0x6b, 0x7e, 0xc0, 0x79, 0x35, 0x5b, 0x8e, 0x5a, 0x2e,
+     0xec, 0xfb, 0x92, 0xfb, 0xa7, 0x73, 0x6b, 0xde, 0xe5, 0xd3, 0xbb, 0xa7,
+     0x83, 0xbc, 0xdb, 0x73, 0x1b, 0xdc, 0xb8, 0xe1, 0x9c, 0x1c, 0xec, 0x39,
+     0x2c, 0xaf, 0xcd, 0x25, 0x45, 0xa6, 0x56, 0x70, 0x9b, 0x31, 0x88, 0x66,
+     0x01, 0x09, 0x7c, 0x61, 0xfb, 0xca, 0x05, 0x93, 0x7a, 0x16, 0x2a, 0xdb,
+     0xbd, 0xf2, 0xe1, 0x46, 0x01, 0x5e, 0x8e, 0x82, 0x68, 0x8f, 0xf1, 0xd8,
+     0x9a, 0x30, 0xda, 0xa3, 0xf1, 0x9a, 0x67, 0xd8, 0x59, 0xe8, 0x4c, 0x89,
+     0x70, 0x97, 0xdc, 0x6d, 0x83, 0x1a, 0x01, 0xe8, 0x00, 0x74, 0xfd, 0x44,
+     0x73, 0x0f, 0x86, 0x72, 0x03, 0x71, 0x48, 0x90, 0x01, 0x12, 0xd3, 0xd1,
+     0x85, 0x2e, 0x67, 0xf1, 0x07, 0x78, 0x8d, 0x9f, 0xc8, 0x44, 0xd1, 0xa3,
+     0x41, 0x9f, 0xd7, 0xc6, 0x50, 0x44, 0x9d, 0x09, 0xf4, 0xab, 0x7c, 0x12,
+     0x65, 0x59, 0xc4, 0x43, 0x51, 0x94, 0x25, 0x9d, 0x0a, 0xb1, 0x1b, 0xd2,
+     0x3d, 0xeb, 0xd4, 0xef, 0xbf, 0xd7, 0x66, 0xa3, 0x51, 0x67, 0xc1, 0x42,
+     0x88, 0xe2, 0x7a, 0x28, 0x9a, 0x40, 0x1a, 0x60, 0xce, 0xa2, 0x42, 0x72,
+     0x45, 0x8a, 0x4c, 0x08, 0xcc, 0x06, 0xa0, 0x6d, 0xab, 0xa3, 0xea, 0xee,
+     0x63, 0x65, 0x9e, 0x40, 0x19, 0x02, 0x03, 0x01, 0x00, 0x01});
+
+PrivateKey FixedRsa2048PrivateKeyForTesting() {
+  return *PrivateKey::FromPrivateKeyInfo(kRsa2048Private);
+}
+
+PublicKey FixedRsa2048PublicKeyForTesting() {
+  return *PublicKey::FromSubjectPublicKeyInfo(kRsa2048Public);
+}
+
+PrivateKey FixedRsa4096PrivateKeyForTesting() {
+  return *PrivateKey::FromPrivateKeyInfo(kRsa4096Private);
+}
+
+PublicKey FixedRsa4096PublicKeyForTesting() {
+  return *PublicKey::FromSubjectPublicKeyInfo(kRsa4096Public);
+}
+
+}  // namespace crypto::test
diff --git a/crypto/test_support.h b/crypto/test_support.h
new file mode 100644
index 0000000..7e29810
--- /dev/null
+++ b/crypto/test_support.h
@@ -0,0 +1,26 @@
+// Copyright 2025 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CRYPTO_TEST_SUPPORT_H_
+#define CRYPTO_TEST_SUPPORT_H_
+
+#include "crypto/keypair.h"
+
+namespace crypto::test {
+
+using crypto::keypair::PrivateKey;
+using crypto::keypair::PublicKey;
+
+// These create and return PrivateKey instances that wrap fixed, pre-generated
+// private keys for use in tests. Tests should prefer these keys over freshly
+// generating keys whenever practical, since they are much cheaper.
+PrivateKey FixedRsa2048PrivateKeyForTesting();
+PrivateKey FixedRsa4096PrivateKeyForTesting();
+
+PublicKey FixedRsa2048PublicKeyForTesting();
+PublicKey FixedRsa4096PublicKeyForTesting();
+
+}  // namespace crypto::test
+
+#endif  // CRYPTO_TEST_SUPPORT_H_
diff --git a/device/base/features.cc b/device/base/features.cc
index 39218e9..d10a101 100644
--- a/device/base/features.cc
+++ b/device/base/features.cc
@@ -42,11 +42,6 @@
 BASE_FEATURE(kBluetoothRfcommAndroid,
              "BluetoothRfcommAndroid",
              base::FEATURE_DISABLED_BY_DEFAULT);
-#else
-// Controls whether to enable Web serial. Blink runtime features don't allow
-// pure Blink features on a subset of platforms, so we need a separate feature
-// for non-Android platforms to keep the Finch switches.
-BASE_FEATURE(kSerial, "Serial", base::FEATURE_ENABLED_BY_DEFAULT);
 #endif  // BUILDFLAG(IS_ANDROID)
 
 }  // namespace features
diff --git a/device/vr/BUILD.gn b/device/vr/BUILD.gn
index b05a376..432dc29 100644
--- a/device/vr/BUILD.gn
+++ b/device/vr/BUILD.gn
@@ -202,6 +202,8 @@
         "openxr/openxr_interaction_profile_paths.h",
         "openxr/openxr_interaction_profiles.cc",
         "openxr/openxr_interaction_profiles.h",
+        "openxr/openxr_layers.cc",
+        "openxr/openxr_layers.h",
         "openxr/openxr_light_estimator.cc",
         "openxr/openxr_light_estimator.h",
         "openxr/openxr_path_helper.cc",
diff --git a/device/vr/openxr/android/openxr_graphics_binding_open_gles.cc b/device/vr/openxr/android/openxr_graphics_binding_open_gles.cc
index 285c984..acf40979 100644
--- a/device/vr/openxr/android/openxr_graphics_binding_open_gles.cc
+++ b/device/vr/openxr/android/openxr_graphics_binding_open_gles.cc
@@ -41,7 +41,9 @@
   extensions.push_back(XR_KHR_OPENGL_ES_ENABLE_EXTENSION_NAME);
 }
 
-OpenXrGraphicsBindingOpenGLES::OpenXrGraphicsBindingOpenGLES() = default;
+OpenXrGraphicsBindingOpenGLES::OpenXrGraphicsBindingOpenGLES(
+    const OpenXrExtensionEnumeration* extension_enum)
+    : OpenXrGraphicsBinding(extension_enum) {}
 OpenXrGraphicsBindingOpenGLES::~OpenXrGraphicsBindingOpenGLES() {
   if (back_buffer_fbo_) {
     glDeleteFramebuffersEXT(1, &back_buffer_fbo_);
@@ -199,6 +201,11 @@
   return color_swapchain_images_;
 }
 
+base::span<const SwapChainInfo>
+OpenXrGraphicsBindingOpenGLES::GetSwapChainImages() const {
+  return color_swapchain_images_;
+}
+
 bool OpenXrGraphicsBindingOpenGLES::CanUseSharedImages() const {
   return true;
 }
@@ -401,7 +408,7 @@
   return true;
 }
 
-bool OpenXrGraphicsBindingOpenGLES::ShouldFlipSubmittedImage() {
+bool OpenXrGraphicsBindingOpenGLES::ShouldFlipSubmittedImage() const {
   // WebGPU produces textures that are y-flipped relative to WebGL, which needs
   // to be accounted for during frame submission.
   return IsWebGPUSession();
diff --git a/device/vr/openxr/android/openxr_graphics_binding_open_gles.h b/device/vr/openxr/android/openxr_graphics_binding_open_gles.h
index 79bfe0e..a384a79 100644
--- a/device/vr/openxr/android/openxr_graphics_binding_open_gles.h
+++ b/device/vr/openxr/android/openxr_graphics_binding_open_gles.h
@@ -29,7 +29,8 @@
 class DEVICE_VR_EXPORT OpenXrGraphicsBindingOpenGLES
     : public OpenXrGraphicsBinding {
  public:
-  OpenXrGraphicsBindingOpenGLES();
+  explicit OpenXrGraphicsBindingOpenGLES(
+      const OpenXrExtensionEnumeration* extension_enum);
   ~OpenXrGraphicsBindingOpenGLES() override;
 
   // OpenXrGraphicsBinding
@@ -40,6 +41,7 @@
       const XrSwapchain& color_swapchain) override;
   void ClearSwapchainImages() override;
   base::span<SwapChainInfo> GetSwapChainImages() override;
+  base::span<const SwapChainInfo> GetSwapChainImages() const override;
   bool CanUseSharedImages() const override;
   void CreateSharedImages(gpu::SharedImageInterface* sii) override;
   const SwapChainInfo& GetActiveSwapchainImage() override;
@@ -47,7 +49,7 @@
       const scoped_refptr<viz::ContextProvider>& context_provider) override;
   void CleanupWithoutSubmit() override;
   bool WaitOnFence(gfx::GpuFence& gpu_fence) override;
-  bool ShouldFlipSubmittedImage() override;
+  bool ShouldFlipSubmittedImage() const override;
   void SetOverlayAndWebXrVisibility(bool overlay_visible,
                                     bool webxr_visible) override;
   bool SetOverlayTexture(gfx::GpuMemoryBufferHandle texture,
diff --git a/device/vr/openxr/openxr_api_wrapper.cc b/device/vr/openxr/openxr_api_wrapper.cc
index fc03e48..bdc2a2a 100644
--- a/device/vr/openxr/openxr_api_wrapper.cc
+++ b/device/vr/openxr/openxr_api_wrapper.cc
@@ -23,6 +23,7 @@
 #include "device/vr/openxr/openxr_extension_helper.h"
 #include "device/vr/openxr/openxr_graphics_binding.h"
 #include "device/vr/openxr/openxr_input_helper.h"
+#include "device/vr/openxr/openxr_layers.h"
 #include "device/vr/openxr/openxr_stage_bounds_provider.h"
 #include "device/vr/openxr/openxr_util.h"
 #include "device/vr/openxr/openxr_view_configuration.h"
@@ -1093,7 +1094,7 @@
   // Each view configuration has its own layer, which was populated in
   // GraphicsBinding::PrepareViewConfigForRender. These layers are all put into
   // XrFrameEndInfo and passed to xrEndFrame.
-  OpenXrLayers layers(local_space_, blend_mode_,
+  OpenXrLayers layers(local_space_, blend_mode_, *graphics_binding_,
                       primary_view_config_.ProjectionViews());
 
   // Gather all the layers for active secondary views.
@@ -1101,7 +1102,7 @@
     for (const auto& secondary_view_config : secondary_view_configs_) {
       const OpenXrViewConfiguration& view_config = secondary_view_config.second;
       if (view_config.Active()) {
-        layers.AddSecondaryLayerForType(view_config.Type(),
+        layers.AddSecondaryLayerForType(*graphics_binding_, view_config.Type(),
                                         view_config.ProjectionViews());
       }
     }
diff --git a/device/vr/openxr/openxr_graphics_binding.cc b/device/vr/openxr/openxr_graphics_binding.cc
index 431f3fd..dc4ff3e2 100644
--- a/device/vr/openxr/openxr_graphics_binding.cc
+++ b/device/vr/openxr/openxr_graphics_binding.cc
@@ -5,6 +5,7 @@
 #include "device/vr/openxr/openxr_graphics_binding.h"
 
 #include "components/viz/common/gpu/context_provider.h"
+#include "device/vr/openxr/openxr_extension_helper.h"
 #include "device/vr/openxr/openxr_util.h"
 #include "device/vr/openxr/openxr_view_configuration.h"
 #include "gpu/command_buffer/client/shared_image_interface.h"
@@ -13,6 +14,11 @@
 
 namespace device {
 
+// static
+std::vector<std::string> OpenXrGraphicsBinding::GetOptionalExtensions() {
+  return {XR_FB_COMPOSITION_LAYER_IMAGE_LAYOUT_EXTENSION_NAME};
+}
+
 #if BUILDFLAG(IS_WIN)
 SwapChainInfo::SwapChainInfo(ID3D11Texture2D* d3d11_texture)
     : d3d11_texture(d3d11_texture) {}
@@ -41,6 +47,17 @@
 #endif
 }
 
+OpenXrGraphicsBinding::OpenXrGraphicsBinding(
+    const OpenXrExtensionEnumeration* extension_enum)
+    : fb_composition_layer_ext_enabled_(extension_enum->ExtensionSupported(
+          XR_FB_COMPOSITION_LAYER_IMAGE_LAYOUT_EXTENSION_NAME)) {
+  if (fb_composition_layer_ext_enabled_) {
+    y_flip_layer_layout_.type = XR_TYPE_COMPOSITION_LAYER_IMAGE_LAYOUT_FB;
+    y_flip_layer_layout_.flags =
+        XR_COMPOSITION_LAYER_IMAGE_LAYOUT_VERTICAL_FLIP_BIT_FB;
+  }
+}
+
 void OpenXrGraphicsBinding::PrepareViewConfigForRender(
     const XrSwapchain& color_swapchain,
     OpenXrViewConfiguration& view_config) {
@@ -77,8 +94,9 @@
     // WebGL layers may give us flipped content. We need to instruct OpenXR
     // to flip the content before showing it to the user. Some XR runtimes
     // are able to efficiently do this as part of existing post processing
-    // steps.
-    if (ShouldFlipSubmittedImage()) {
+    // steps. However, if we have the composition layer extension enabled, we
+    // will instruct the runtime to invert the image in a different manner.
+    if (ShouldFlipSubmittedImage() && !fb_composition_layer_ext_enabled_) {
       projection_view.subImage.imageRect.offset.y = 0;
       projection_view.fov.angleUp = -view.fov.angleUp;
       projection_view.fov.angleDown = -view.fov.angleDown;
@@ -86,7 +104,22 @@
   }
 }
 
-bool OpenXrGraphicsBinding::IsUsingSharedImages() {
+void OpenXrGraphicsBinding::MaybeFlipLayer(
+    XrCompositionLayerProjection& layer) const {
+  // If we don't need to flip the image, then we have nothing to do here.
+  // If we do need to flip the image and `fb_composition_layer_ext_enabled_`
+  // is false, we have already flipped the image during
+  // `PrepareViewConfigForRender`.
+  if (!ShouldFlipSubmittedImage() || !fb_composition_layer_ext_enabled_) {
+    return;
+  }
+
+  CHECK(layer.next == nullptr);
+
+  layer.next = &y_flip_layer_layout_;
+}
+
+bool OpenXrGraphicsBinding::IsUsingSharedImages() const {
   const auto swapchain_info = GetSwapChainImages();
   return ((swapchain_info.size() > 1) && swapchain_info[0].shared_image);
 }
diff --git a/device/vr/openxr/openxr_graphics_binding.h b/device/vr/openxr/openxr_graphics_binding.h
index fda97d5..799b94d4 100644
--- a/device/vr/openxr/openxr_graphics_binding.h
+++ b/device/vr/openxr/openxr_graphics_binding.h
@@ -43,6 +43,7 @@
 }  // namespace viz
 
 namespace device {
+class OpenXrExtensionEnumeration;
 class OpenXrViewConfiguration;
 
 // TODO(crbug.com/40909689): Refactor this class.
@@ -105,6 +106,9 @@
   // Gets the set of RequiredExtensions that need to be present on the platform.
   static void GetRequiredExtensions(std::vector<const char*>& extensions);
 
+  // Gets any OptionalExtensions that should be enabled if present.
+  static std::vector<std::string> GetOptionalExtensions();
+
   virtual ~OpenXrGraphicsBinding() = default;
 
   // Ensures that the GraphicsBinding is ready for use.
@@ -128,6 +132,9 @@
   // classes.
   virtual base::span<SwapChainInfo> GetSwapChainImages() = 0;
 
+  // Const getter of the above.
+  virtual base::span<const SwapChainInfo> GetSwapChainImages() const = 0;
+
   // Returns whether or not the platform believes it can support using Shared
   // buffers/images.
   virtual bool CanUseSharedImages() const = 0;
@@ -164,7 +171,7 @@
 
   // Returns whether or not the current Swapchain is actually using SharedImages
   // or not.
-  bool IsUsingSharedImages();
+  bool IsUsingSharedImages() const;
 
   // Returns the previously set swapchain image size, or 0,0 if one is not set.
   gfx::Size GetSwapchainImageSize();
@@ -239,14 +246,21 @@
   void SetWebGPUSession(bool is_webgpu) { webgpu_session_ = is_webgpu; }
   bool IsWebGPUSession() const { return webgpu_session_; }
 
+  // Append any necessary data to the `layer` object to instruct the runtime to
+  // flip the layer if necessary.
+  void MaybeFlipLayer(XrCompositionLayerProjection& layer) const;
+
  protected:
+  explicit OpenXrGraphicsBinding(
+      const OpenXrExtensionEnumeration* extension_enum);
+
   // Internal helper to clear the list of images allocated during
   // `EnumerateSwapchainImages`, since the child classes own the actual list.
   virtual void ClearSwapchainImages() = 0;
 
   // Indicates whether the graphics binding expects the submitted image to need
   // to be flipped when being submitted to the runtime.
-  virtual bool ShouldFlipSubmittedImage() = 0;
+  virtual bool ShouldFlipSubmittedImage() const = 0;
 
   // Will be called when SetSwapchainImageSize is called, even if a change is
   // not made, to allow child classes/concrete implementations to override any
@@ -278,6 +292,9 @@
   uint32_t active_swapchain_index_;
   bool has_active_swapchain_image_ = false;
   bool webgpu_session_ = false;
+  bool fb_composition_layer_ext_enabled_ = false;
+  // This will only be valid if `fb_composition_layer_ext_enabled_` is true.
+  XrCompositionLayerImageLayoutFB y_flip_layer_layout_;
 };
 
 }  // namespace device
diff --git a/device/vr/openxr/openxr_layers.cc b/device/vr/openxr/openxr_layers.cc
new file mode 100644
index 0000000..21abb80
--- /dev/null
+++ b/device/vr/openxr/openxr_layers.cc
@@ -0,0 +1,62 @@
+// Copyright 2025 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/vr/openxr/openxr_layers.h"
+
+#include "device/vr/openxr/openxr_graphics_binding.h"
+
+namespace device {
+OpenXrLayers::OpenXrLayers(XrSpace space,
+                           XrEnvironmentBlendMode blend_mode,
+                           const OpenXrGraphicsBinding& graphics_binding,
+                           const std::vector<XrCompositionLayerProjectionView>&
+                               primary_projection_views)
+    : space_(space), blend_mode_(blend_mode) {
+  InitializeLayer(graphics_binding, primary_projection_views,
+                  primary_projection_layer_);
+}
+
+OpenXrLayers::~OpenXrLayers() = default;
+
+void OpenXrLayers::AddSecondaryLayerForType(
+    const OpenXrGraphicsBinding& graphics_binding,
+    XrViewConfigurationType type,
+    const std::vector<XrCompositionLayerProjectionView>& projection_views) {
+  secondary_projection_layers_.emplace_back();
+  InitializeLayer(graphics_binding, projection_views,
+                  secondary_projection_layers_.back());
+  secondary_composition_layers_.push_back(
+      reinterpret_cast<XrCompositionLayerBaseHeader*>(
+          &secondary_projection_layers_.back()));
+
+  secondary_layer_info_.emplace_back();
+  XrSecondaryViewConfigurationLayerInfoMSFT& layer_info =
+      secondary_layer_info_.back();
+  layer_info.type = XR_TYPE_SECONDARY_VIEW_CONFIGURATION_LAYER_INFO_MSFT;
+  layer_info.viewConfigurationType = type;
+  layer_info.environmentBlendMode = blend_mode_;
+  layer_info.layerCount = 1;
+  layer_info.layers = &secondary_composition_layers_.back();
+}
+
+void OpenXrLayers::InitializeLayer(
+    const OpenXrGraphicsBinding& graphics_binding,
+    const std::vector<XrCompositionLayerProjectionView>& projection_views,
+    XrCompositionLayerProjection& layer) {
+  layer.type = XR_TYPE_COMPOSITION_LAYER_PROJECTION;
+  layer.layerFlags = 0;
+  layer.space = space_;
+  layer.viewCount = projection_views.size();
+  layer.views = projection_views.data();
+
+  // GraphicsBinding::MaybeFlipLayer may modify `layer.next`.
+  layer.next = nullptr;
+  graphics_binding.MaybeFlipLayer(layer);
+
+  if (blend_mode_ == XR_ENVIRONMENT_BLEND_MODE_ALPHA_BLEND) {
+    layer.layerFlags |= XR_COMPOSITION_LAYER_BLEND_TEXTURE_SOURCE_ALPHA_BIT;
+  }
+}
+
+}  // namespace device
diff --git a/device/vr/openxr/openxr_layers.h b/device/vr/openxr/openxr_layers.h
new file mode 100644
index 0000000..9eedc72
--- /dev/null
+++ b/device/vr/openxr/openxr_layers.h
@@ -0,0 +1,82 @@
+// Copyright 2025 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_VR_OPENXR_OPENXR_LAYERS_H_
+#define DEVICE_VR_OPENXR_OPENXR_LAYERS_H_
+
+#include <vector>
+
+#include "base/memory/raw_ptr_exclusion.h"
+#include "third_party/openxr/src/include/openxr/openxr.h"
+
+namespace device {
+class OpenXrGraphicsBinding;
+
+// A wrapper around all of the layers to be submitted to a certain frame. Each
+// frame creates its own OpenXrLayers object and populates it with all the
+// layers of active view configurations. This information is passed into
+// xrEndFrame to complete the frame.
+class OpenXrLayers {
+ public:
+  OpenXrLayers(XrSpace space,
+               XrEnvironmentBlendMode blend_mode,
+               const OpenXrGraphicsBinding& graphics_binding,
+               const std::vector<XrCompositionLayerProjectionView>&
+                   primary_projection_views);
+  ~OpenXrLayers();
+
+  void AddSecondaryLayerForType(
+      const OpenXrGraphicsBinding& graphics_binding,
+      XrViewConfigurationType type,
+      const std::vector<XrCompositionLayerProjectionView>& projection_views);
+
+  uint32_t PrimaryLayerCount() const { return 1; }
+
+  const XrCompositionLayerBaseHeader* const* PrimaryLayerData() const {
+    return &primary_composition_layer_;
+  }
+
+  uint32_t SecondaryConfigCount() const { return secondary_layer_info_.size(); }
+
+  const XrSecondaryViewConfigurationLayerInfoMSFT* SecondaryConfigData() const {
+    return secondary_layer_info_.data();
+  }
+
+ private:
+  void InitializeLayer(
+      const OpenXrGraphicsBinding& graphics_binding,
+      const std::vector<XrCompositionLayerProjectionView>& projection_views,
+      XrCompositionLayerProjection& layer);
+
+  XrSpace space_ = XR_NULL_HANDLE;
+  XrEnvironmentBlendMode blend_mode_ = XR_ENVIRONMENT_BLEND_MODE_MAX_ENUM;
+
+  // In OpenXR, it is possible to have multiple layers, as well as multiple
+  // types of layers (such as projection and quad layers). We currently only
+  // support a single projection layer. XrCompositionLayerBaseHeader* is needed
+  // because xrEndFrame expects an array containing pointers of all the layers.
+  XrCompositionLayerProjection primary_projection_layer_;
+
+  // RAW_PTR_EXCLUSION: #addr-of (address returned from a function)
+  RAW_PTR_EXCLUSION XrCompositionLayerBaseHeader* primary_composition_layer_ =
+      reinterpret_cast<XrCompositionLayerBaseHeader*>(
+          &primary_projection_layer_);
+
+  // The layers for secondary view configurations. We currently only support a
+  // single layer per view configuration, so each element in this vector is the
+  // layer for a specific view configuration.
+  std::vector<XrCompositionLayerProjection> secondary_projection_layers_;
+  // Pointers to the corresponding layer in secondary_projection_layers_.
+  // This field is not vector<raw_ptr<...>> due to interaction with third_party
+  // api.
+  RAW_PTR_EXCLUSION std::vector<XrCompositionLayerBaseHeader*>
+      secondary_composition_layers_;
+
+  // The secondary view configuration layer info containing the data above,
+  // which is passed to xrEndFrame.
+  std::vector<XrSecondaryViewConfigurationLayerInfoMSFT> secondary_layer_info_;
+};
+}  // namespace device
+
+#endif  // DEVICE_VR_OPENXR_OPENXR_LAYERS_H_
diff --git a/device/vr/openxr/openxr_platform_helper.cc b/device/vr/openxr/openxr_platform_helper.cc
index 66b7fb8..619fb568 100644
--- a/device/vr/openxr/openxr_platform_helper.cc
+++ b/device/vr/openxr/openxr_platform_helper.cc
@@ -146,6 +146,10 @@
                               factory_extensions.end());
   }
 
+  for (const auto& extension : OpenXrGraphicsBinding::GetOptionalExtensions()) {
+    handled_extensions.insert(extension);
+  }
+
   // Enable the required extensions for any controllers that both we and the
   // runtime support.
   for (const auto& interaction_profile :
diff --git a/device/vr/openxr/openxr_view_configuration.cc b/device/vr/openxr/openxr_view_configuration.cc
index 8f8e1a9..489c0e4 100644
--- a/device/vr/openxr/openxr_view_configuration.cc
+++ b/device/vr/openxr/openxr_view_configuration.cc
@@ -201,48 +201,4 @@
   });
 }
 
-OpenXrLayers::OpenXrLayers(XrSpace space,
-                           XrEnvironmentBlendMode blend_mode,
-                           const std::vector<XrCompositionLayerProjectionView>&
-                               primary_projection_views)
-    : space_(space), blend_mode_(blend_mode) {
-  InitializeLayer(primary_projection_views, primary_projection_layer_);
-}
-
-OpenXrLayers::~OpenXrLayers() = default;
-
-void OpenXrLayers::AddSecondaryLayerForType(
-    XrViewConfigurationType type,
-    const std::vector<XrCompositionLayerProjectionView>& projection_views) {
-  secondary_projection_layers_.emplace_back();
-  InitializeLayer(projection_views, secondary_projection_layers_.back());
-  secondary_composition_layers_.push_back(
-      reinterpret_cast<XrCompositionLayerBaseHeader*>(
-          &secondary_projection_layers_.back()));
-
-  secondary_layer_info_.emplace_back();
-  XrSecondaryViewConfigurationLayerInfoMSFT& layer_info =
-      secondary_layer_info_.back();
-  layer_info.type = XR_TYPE_SECONDARY_VIEW_CONFIGURATION_LAYER_INFO_MSFT;
-  layer_info.viewConfigurationType = type;
-  layer_info.environmentBlendMode = blend_mode_;
-  layer_info.layerCount = 1;
-  layer_info.layers = &secondary_composition_layers_.back();
-}
-
-void OpenXrLayers::InitializeLayer(
-    const std::vector<XrCompositionLayerProjectionView>& projection_views,
-    XrCompositionLayerProjection& layer) {
-  layer.type = XR_TYPE_COMPOSITION_LAYER_PROJECTION;
-  layer.next = nullptr;
-  layer.layerFlags = 0;
-  layer.space = space_;
-  layer.viewCount = projection_views.size();
-  layer.views = projection_views.data();
-
-  if (blend_mode_ == XR_ENVIRONMENT_BLEND_MODE_ALPHA_BLEND) {
-    layer.layerFlags |= XR_COMPOSITION_LAYER_BLEND_TEXTURE_SOURCE_ALPHA_BIT;
-  }
-}
-
 }  // namespace device
diff --git a/device/vr/openxr/openxr_view_configuration.h b/device/vr/openxr/openxr_view_configuration.h
index bcf60dd..7d57a9d2 100644
--- a/device/vr/openxr/openxr_view_configuration.h
+++ b/device/vr/openxr/openxr_view_configuration.h
@@ -7,7 +7,6 @@
 
 #include <vector>
 
-#include "base/memory/raw_ptr_exclusion.h"
 #include "device/vr/public/mojom/vr_service.mojom-forward.h"
 #include "third_party/openxr/src/include/openxr/openxr.h"
 #include "ui/gfx/geometry/rect.h"
@@ -120,75 +119,13 @@
   // The viewport is only set when active_ is true. Otherwise, the viewport is
   // meaningless. This is the viewport of this entire view configuration within
   // the single OpenXR texture.
-  gfx::Rect viewport_ = gfx::Rect();
+  gfx::Rect viewport_;
   std::vector<OpenXrViewProperties> properties_;
 
   std::vector<XrView> local_from_view_;
   std::vector<XrCompositionLayerProjectionView> projection_views_;
 };
 
-// A wrapper around all of the layers to be submitted to a certain frame. Each
-// frame creates its own OpenXrLayers object and populates it with all the
-// layers of active view configurations. This information is passed into
-// xrEndFrame to complete the frame.
-class OpenXrLayers {
- public:
-  OpenXrLayers(XrSpace space,
-               XrEnvironmentBlendMode blend_mode,
-               const std::vector<XrCompositionLayerProjectionView>&
-                   primary_projection_views);
-  ~OpenXrLayers();
-
-  void AddSecondaryLayerForType(
-      XrViewConfigurationType type,
-      const std::vector<XrCompositionLayerProjectionView>& projection_views);
-
-  uint32_t PrimaryLayerCount() const { return 1; }
-
-  const XrCompositionLayerBaseHeader* const* PrimaryLayerData() const {
-    return &primary_composition_layer_;
-  }
-
-  uint32_t SecondaryConfigCount() const { return secondary_layer_info_.size(); }
-
-  const XrSecondaryViewConfigurationLayerInfoMSFT* SecondaryConfigData() const {
-    return secondary_layer_info_.data();
-  }
-
- private:
-  void InitializeLayer(
-      const std::vector<XrCompositionLayerProjectionView>& projection_views,
-      XrCompositionLayerProjection& layer);
-
-  XrSpace space_ = XR_NULL_HANDLE;
-  XrEnvironmentBlendMode blend_mode_ = XR_ENVIRONMENT_BLEND_MODE_MAX_ENUM;
-
-  // In OpenXR, it is possible to have multiple layers, as well as multiple
-  // types of layers (such as projection and quad layers). We currently only
-  // support a single projection layer. XrCompositionLayerBaseHeader* is needed
-  // because xrEndFrame expects an array containing pointers of all the layers.
-  XrCompositionLayerProjection primary_projection_layer_;
-
-  // RAW_PTR_EXCLUSION: #addr-of (address returned from a function)
-  RAW_PTR_EXCLUSION XrCompositionLayerBaseHeader* primary_composition_layer_ =
-      reinterpret_cast<XrCompositionLayerBaseHeader*>(
-          &primary_projection_layer_);
-
-  // The layers for secondary view configurations. We currently only support a
-  // single layer per view configuration, so each element in this vector is the
-  // layer for a specific view configuration.
-  std::vector<XrCompositionLayerProjection> secondary_projection_layers_;
-  // Pointers to the corresponding layer in secondary_projection_layers_.
-  // This field is not vector<raw_ptr<...>> due to interaction with third_party
-  // api.
-  RAW_PTR_EXCLUSION std::vector<XrCompositionLayerBaseHeader*>
-      secondary_composition_layers_;
-
-  // The secondary view configuration layer info containing the data above,
-  // which is passed to xrEndFrame.
-  std::vector<XrSecondaryViewConfigurationLayerInfoMSFT> secondary_layer_info_;
-};
-
 }  // namespace device
 
 #endif  // DEVICE_VR_OPENXR_OPENXR_VIEW_CONFIGURATION_H_
diff --git a/device/vr/openxr/windows/openxr_graphics_binding_d3d11.cc b/device/vr/openxr/windows/openxr_graphics_binding_d3d11.cc
index 9912252..39996df69 100644
--- a/device/vr/openxr/windows/openxr_graphics_binding_d3d11.cc
+++ b/device/vr/openxr/windows/openxr_graphics_binding_d3d11.cc
@@ -38,7 +38,8 @@
 
 OpenXrGraphicsBindingD3D11::OpenXrGraphicsBindingD3D11(
     base::WeakPtr<OpenXrPlatformHelperWindows> weak_platform_helper)
-    : texture_helper_(std::make_unique<D3D11TextureHelper>()),
+    : OpenXrGraphicsBinding(weak_platform_helper->GetExtensionEnumeration()),
+      texture_helper_(std::make_unique<D3D11TextureHelper>()),
       weak_platform_helper_(weak_platform_helper) {}
 
 OpenXrGraphicsBindingD3D11::~OpenXrGraphicsBindingD3D11() = default;
@@ -126,6 +127,11 @@
   return color_swapchain_images_;
 }
 
+base::span<const SwapChainInfo> OpenXrGraphicsBindingD3D11::GetSwapChainImages()
+    const {
+  return color_swapchain_images_;
+}
+
 bool OpenXrGraphicsBindingD3D11::CanUseSharedImages() const {
   // Put shared image feature behind a flag until remaining issues with overlays
   // are resolved. WebGPU sessions always use SharedImages.
@@ -338,7 +344,7 @@
   texture_helper_->CleanupNoSubmit();
 }
 
-bool OpenXrGraphicsBindingD3D11::ShouldFlipSubmittedImage() {
+bool OpenXrGraphicsBindingD3D11::ShouldFlipSubmittedImage() const {
   return IsUsingSharedImages() && !IsWebGPUSession();
 }
 
diff --git a/device/vr/openxr/windows/openxr_graphics_binding_d3d11.h b/device/vr/openxr/windows/openxr_graphics_binding_d3d11.h
index 5699d9d..f2834e47 100644
--- a/device/vr/openxr/windows/openxr_graphics_binding_d3d11.h
+++ b/device/vr/openxr/windows/openxr_graphics_binding_d3d11.h
@@ -34,6 +34,7 @@
       const XrSwapchain& color_swapchain) override;
   void ClearSwapchainImages() override;
   base::span<SwapChainInfo> GetSwapChainImages() override;
+  base::span<const SwapChainInfo> GetSwapChainImages() const override;
   bool CanUseSharedImages() const override;
   void CreateSharedImages(gpu::SharedImageInterface* sii) override;
   const SwapChainInfo& GetActiveSwapchainImage() override;
@@ -41,7 +42,7 @@
   bool Render(
       const scoped_refptr<viz::ContextProvider>& context_provider) override;
   void CleanupWithoutSubmit() override;
-  bool ShouldFlipSubmittedImage() override;
+  bool ShouldFlipSubmittedImage() const override;
   void SetOverlayAndWebXrVisibility(bool overlay_visible,
                                     bool webxr_visible) override;
   void SetWebXrTexture(mojo::PlatformHandle texture_handle,
diff --git a/docs/speed/perf_lab_platforms.md b/docs/speed/perf_lab_platforms.md
index a815249..c954268 100644
--- a/docs/speed/perf_lab_platforms.md
+++ b/docs/speed/perf_lab_platforms.md
@@ -18,6 +18,9 @@
  * [android-pixel6-perf](https://ci.chromium.org/p/chrome/builders/ci/android-pixel6-perf): Android U.
  * [android-pixel6-perf-pgo](https://ci.chromium.org/p/chrome/builders/ci/android-pixel6-perf-pgo): Android U.
  * [android-pixel6-pro-perf](https://ci.chromium.org/p/chrome/builders/ci/android-pixel6-pro-perf): Android T.
+ * [android-pixel9-perf](https://ci.chromium.org/p/chrome/builders/ci/android-pixel9-perf): Android B.
+ * [android-pixel9-pro-perf](https://ci.chromium.org/p/chrome/builders/ci/android-pixel9-pro-perf): Android B.
+ * [android-pixel9-pro-xl-perf](https://ci.chromium.org/p/chrome/builders/ci/android-pixel9-pro-xl-perf): Android B.
 
 ### Linux
 
diff --git a/extensions/browser/content_verifier/content_verifier_unittest.cc b/extensions/browser/content_verifier/content_verifier_unittest.cc
index 67e86a1..5e7116f 100644
--- a/extensions/browser/content_verifier/content_verifier_unittest.cc
+++ b/extensions/browser/content_verifier/content_verifier_unittest.cc
@@ -37,9 +37,9 @@
   kBackgroundPage,
 };
 
-base::FilePath kBackgroundScriptPath(FILE_PATH_LITERAL("foo/bg.txt"));
+base::FilePath kBackgroundScriptPath(FILE_PATH_LITERAL("foo/bg.js"));
 base::FilePath kContentScriptPath(FILE_PATH_LITERAL("foo/content.js"));
-base::FilePath kBackgroundPagePath(FILE_PATH_LITERAL("foo/page.txt"));
+base::FilePath kBackgroundPagePath(FILE_PATH_LITERAL("foo/page.html"));
 base::FilePath kScriptFilePath(FILE_PATH_LITERAL("bar/code.js"));
 base::FilePath kUnknownTypeFilePath(FILE_PATH_LITERAL("bar/code.txt"));
 base::FilePath kHTMLFilePath(FILE_PATH_LITERAL("bar/page.html"));
@@ -188,12 +188,13 @@
     if (background_manifest_type_ ==
         BackgroundManifestType::kBackgroundScript) {
       base::Value::List background_scripts;
-      background_scripts.Append("foo/bg.txt");
+      background_scripts.Append(kBackgroundScriptPath.AsUTF8Unsafe());
       manifest.SetByDottedPath(manifest_keys::kBackgroundScripts,
                                std::move(background_scripts));
     } else if (background_manifest_type_ ==
                BackgroundManifestType::kBackgroundPage) {
-      manifest.SetByDottedPath(manifest_keys::kBackgroundPage, "foo/page.txt");
+      manifest.SetByDottedPath(manifest_keys::kBackgroundPage,
+                               kBackgroundPagePath.AsUTF8Unsafe());
     }
 
     base::Value::List content_scripts;
@@ -242,23 +243,11 @@
 // some file paths even if those paths are specified as browser images.
 TEST_P(ContentVerifierTestWithBackgroundType, BrowserImagesShouldBeVerified) {
   std::vector<base::FilePath> files_to_be_verified = {
-      kContentScriptPath, kScriptFilePath, kHTMLFilePath, kHTMFilePath};
+      kContentScriptPath, kScriptFilePath,       kHTMLFilePath,
+      kHTMFilePath,       kBackgroundScriptPath, kBackgroundPagePath};
   std::vector<base::FilePath> files_not_to_be_verified{kIconPath,
                                                        kUnknownTypeFilePath};
 
-  if (GetBackgroundManifestType() ==
-      BackgroundManifestType::kBackgroundScript) {
-    files_to_be_verified.push_back(kBackgroundScriptPath);
-    files_not_to_be_verified.push_back(kBackgroundPagePath);
-  } else if (GetBackgroundManifestType() ==
-             BackgroundManifestType::kBackgroundPage) {
-    files_to_be_verified.push_back(kBackgroundPagePath);
-    files_not_to_be_verified.push_back(kBackgroundScriptPath);
-  } else {
-    files_not_to_be_verified.push_back(kBackgroundScriptPath);
-    files_not_to_be_verified.push_back(kBackgroundPagePath);
-  }
-
   auto generate_test_cases = [](const std::vector<base::FilePath>& input) {
     std::set<base::FilePath> output;
     for (const auto& path : input) {
diff --git a/extensions/browser/extension_protocols.cc b/extensions/browser/extension_protocols.cc
index f78f905..375e759 100644
--- a/extensions/browser/extension_protocols.cc
+++ b/extensions/browser/extension_protocols.cc
@@ -101,7 +101,6 @@
 #include "services/network/public/mojom/early_hints.mojom.h"
 #include "services/network/public/mojom/fetch_api.mojom.h"
 #include "services/network/public/mojom/url_response_head.mojom.h"
-#include "third_party/blink/public/common/features.h"
 #include "third_party/blink/public/common/loader/resource_type_util.h"
 #include "url/origin.h"
 #include "url/url_util.h"
@@ -220,14 +219,13 @@
 
   // Frame navigations to extensions have already been checked in
   // the ExtensionNavigationThrottle.
-  // Dedicated Worker (with PlzDedicatedWorker) and Shared Worker main scripts
-  // can be loaded with extension URLs in browser process.
-  // Service Worker and the imported scripts can be loaded with extension URLs
-  // in browser process when PlzServiceWorker is enabled or during update check.
+  // Dedicated Worker and Shared Worker main scripts can be loaded with
+  // extension URLs in browser process. Service Worker and the imported scripts
+  // can be loaded with extension URLs in browser process when PlzServiceWorker
+  // is enabled or during update check.
   if (child_id == content::ChildProcessHost::kInvalidUniqueID &&
       (blink::IsRequestDestinationFrame(destination) ||
-       (base::FeatureList::IsEnabled(blink::features::kPlzDedicatedWorker) &&
-        destination == network::mojom::RequestDestination::kWorker) ||
+       destination == network::mojom::RequestDestination::kWorker ||
        destination == network::mojom::RequestDestination::kSharedWorker ||
        destination == network::mojom::RequestDestination::kScript ||
        destination == network::mojom::RequestDestination::kServiceWorker)) {
diff --git a/extensions/common/manifest_constants.h b/extensions/common/manifest_constants.h
index 47aefafb..6bcfc0e 100644
--- a/extensions/common/manifest_constants.h
+++ b/extensions/common/manifest_constants.h
@@ -301,6 +301,10 @@
     "Invalid value for 'background.scripts[*]'.";
 inline constexpr char16_t kInvalidBackgroundScripts[] =
     u"Invalid value for 'background.scripts'.";
+inline constexpr char kInvalidBackgroundScriptMimeType[] =
+    "Invalid background script mime type for 'background.scripts[*]', a "
+    "background script can only be loaded from supported JavaScript files such "
+    "as .js files.";
 inline constexpr char16_t kInvalidBackgroundServiceWorkerScript[] =
     u"Invalid value for 'background.service_worker'.";
 inline constexpr char16_t kInvalidBackgroundServiceWorkerType[] =
diff --git a/extensions/common/manifest_handlers/background_info.cc b/extensions/common/manifest_handlers/background_info.cc
index 43c62b2..a7df151 100644
--- a/extensions/common/manifest_handlers/background_info.cc
+++ b/extensions/common/manifest_handlers/background_info.cc
@@ -9,6 +9,7 @@
 #include <memory>
 
 #include "base/command_line.h"
+#include "base/files/file_path.h"
 #include "base/files/file_util.h"
 #include "base/lazy_instance.h"
 #include "base/strings/string_number_conversions.h"
@@ -21,6 +22,8 @@
 #include "extensions/common/permissions/api_permission_set.h"
 #include "extensions/common/switches.h"
 #include "extensions/strings/grit/extensions_strings.h"
+#include "net/base/mime_util.h"
+#include "third_party/blink/public/common/mime_util/mime_util.h"
 #include "ui/base/l10n/l10n_util.h"
 
 using extensions::mojom::APIPermissionID;
@@ -33,6 +36,10 @@
 
 namespace {
 
+BASE_FEATURE(kValidateBackgroundScriptMimeType,
+             "ValidateBackgroundScriptMimeType",
+             base::FEATURE_ENABLED_BY_DEFAULT);
+
 const char kBackground[] = "background";
 
 static base::LazyInstance<BackgroundInfo>::DestructorAtExit
@@ -118,7 +125,7 @@
       .background_service_worker_script_.has_value();
 }
 
-bool BackgroundInfo::Parse(const Extension* extension, std::u16string* error) {
+bool BackgroundInfo::Parse(Extension* extension, std::u16string* error) {
   const std::string& bg_scripts_key = extension->is_platform_app() ?
       keys::kPlatformAppBackgroundScripts : keys::kBackgroundScripts;
   if (!LoadBackgroundScripts(extension, bg_scripts_key, error) ||
@@ -141,7 +148,7 @@
   return true;
 }
 
-bool BackgroundInfo::LoadBackgroundScripts(const Extension* extension,
+bool BackgroundInfo::LoadBackgroundScripts(Extension* extension,
                                            const std::string& key,
                                            std::u16string* error) {
   const base::Value* background_scripts_value =
@@ -164,7 +171,28 @@
           errors::kInvalidBackgroundScript, base::NumberToString(i));
       return false;
     }
-    background_scripts_.push_back(background_scripts[i].GetString());
+
+    const std::string& background_script = background_scripts[i].GetString();
+
+    std::string mime_type;
+    // TODO(https://crbug.com/40059598): Remove this if-check and always
+    // validate the mime type in M139.
+    if (base::FeatureList::IsEnabled(kValidateBackgroundScriptMimeType) &&
+        (!net::GetWellKnownMimeTypeFromFile(
+             base::FilePath::FromUTF8Unsafe(background_script), &mime_type) ||
+         !blink::IsSupportedJavascriptMimeType(mime_type))) {
+      // Issue a warning and ignore this file. This is a warning and not a
+      // hard-error to preserve both backwards compatibility and potential
+      // future-compatibility if mime types change.
+      extension->AddInstallWarning(
+          InstallWarning(ErrorUtils::FormatErrorMessage(
+                             errors::kInvalidBackgroundScriptMimeType,
+                             base::NumberToString(i)),
+                         key));
+      continue;
+    }
+
+    background_scripts_.push_back(background_script);
   }
 
   return true;
diff --git a/extensions/common/manifest_handlers/background_info.h b/extensions/common/manifest_handlers/background_info.h
index c3c348ce..d5b0583a 100644
--- a/extensions/common/manifest_handlers/background_info.h
+++ b/extensions/common/manifest_handlers/background_info.h
@@ -58,10 +58,10 @@
     return has_background_page() && !is_persistent_;
   }
 
-  bool Parse(const Extension* extension, std::u16string* error);
+  bool Parse(Extension* extension, std::u16string* error);
 
  private:
-  bool LoadBackgroundScripts(const Extension* extension,
+  bool LoadBackgroundScripts(Extension* extension,
                              const std::string& key,
                              std::u16string* error);
   bool LoadBackgroundPage(const Extension* extension,
diff --git a/gpu/command_buffer/service/shared_context_state.cc b/gpu/command_buffer/service/shared_context_state.cc
index a995c01a..b6347ed 100644
--- a/gpu/command_buffer/service/shared_context_state.cc
+++ b/gpu/command_buffer/service/shared_context_state.cc
@@ -105,23 +105,42 @@
 #endif
 }
 
+void ReportPrecompilationStats(
+    std::unique_ptr<skgpu::graphite::PrecompileContext> precompileContext) {
+  precompileContext->reportPipelineStats();
+}
+
 void InitiatePrecompilation(skgpu::graphite::Context* context) {
   constexpr base::TaskTraits precompile_traits = {
       base::TaskPriority::BEST_EFFORT,
       base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN};
 
-  std::unique_ptr<skgpu::graphite::PrecompileContext> precompileContext =
-      context->makePrecompileContext();
+  {
+    std::unique_ptr<skgpu::graphite::PrecompileContext> precompileContext =
+        context->makePrecompileContext();
 
-  // TODO: crbug.com/358074434 - need to determine the actual delay or initiate
-  // precompilation at first idle
-  constexpr base::TimeDelta precompile_wait = base::Seconds(1);
+    // TODO: crbug.com/358074434 - need to determine the actual delay or
+    // initiate precompilation at first idle
+    constexpr base::TimeDelta precompile_wait = base::Seconds(1);
 
-  base::ThreadPool::PostDelayedTask(
-      FROM_HERE, precompile_traits,
-      base::BindOnce(&GraphitePerformPrecompilation,
-                     std::move(precompileContext)),
-      precompile_wait);
+    base::ThreadPool::PostDelayedTask(
+        FROM_HERE, precompile_traits,
+        base::BindOnce(&GraphitePerformPrecompilation,
+                       std::move(precompileContext)),
+        precompile_wait);
+  }
+
+  {
+    std::unique_ptr<skgpu::graphite::PrecompileContext> precompileContext =
+        context->makePrecompileContext();
+
+    // After thirty minutes, report UMA statistics re Precompile Pipeline usage
+    base::ThreadPool::PostDelayedTask(
+        FROM_HERE, precompile_traits,
+        base::BindOnce(&ReportPrecompilationStats,
+                       std::move(precompileContext)),
+        base::Minutes(30));
+  }
 }
 
 // Creates a Graphite recorder, supplying it with a GraphiteImageProvider.
diff --git a/gpu/command_buffer/service/shared_image/iosurface_image_backing.h b/gpu/command_buffer/service/shared_image/iosurface_image_backing.h
index 71eaa23..c30a65eb 100644
--- a/gpu/command_buffer/service/shared_image/iosurface_image_backing.h
+++ b/gpu/command_buffer/service/shared_image/iosurface_image_backing.h
@@ -82,8 +82,8 @@
   // The display for this GL representation.
   const EGLDisplay egl_display_;
 
-  scoped_refptr<gl::GLContext> context_;
-  scoped_refptr<gl::GLSurface> surface_;
+  const scoped_refptr<gl::GLContext> context_;
+  const scoped_refptr<gl::GLSurface> surface_;
 
   // The GL (not EGL) target to which this texture is to be bound.
   const GLuint gl_target_;
@@ -101,7 +101,7 @@
 };
 
 class GPU_GLES2_EXPORT IOSurfaceImageBacking
-    : public SharedImageBacking,
+    : public ClearTrackingSharedImageBacking,
       public IOSurfaceBackingEGLState::Client {
  public:
   IOSurfaceImageBacking(
@@ -130,15 +130,16 @@
 
   bool InitializePixels(base::span<const uint8_t> pixel_data);
 
-  void AddWGPUDeviceWithPendingCommands(wgpu::Device device);
-  void WaitForDawnCommandsToBeScheduled(const wgpu::Device& device_to_exclude);
+  void AddWGPUDeviceWithPendingCommands(wgpu::Device device)
+      EXCLUSIVE_LOCKS_REQUIRED(lock_);
+  void WaitForDawnCommandsToBeScheduled(const wgpu::Device& device_to_exclude)
+      EXCLUSIVE_LOCKS_REQUIRED(lock_);
 
-  void AddEGLDisplayWithPendingCommands(gl::GLDisplayEGL* display);
-  void WaitForANGLECommandsToBeScheduled();
-  void ClearEGLDisplaysWithPendingCommands(gl::GLDisplayEGL* display_to_keep);
-
-  std::unique_ptr<gfx::GpuFence> GetLastWriteGpuFence();
-  void SetReleaseFence(gfx::GpuFenceHandle release_fence);
+  void AddEGLDisplayWithPendingCommands(gl::GLDisplayEGL* display)
+      EXCLUSIVE_LOCKS_REQUIRED(lock_);
+  void WaitForANGLECommandsToBeScheduled() EXCLUSIVE_LOCKS_REQUIRED(lock_);
+  void ClearEGLDisplaysWithPendingCommands(gl::GLDisplayEGL* display_to_keep)
+      EXCLUSIVE_LOCKS_REQUIRED(lock_);
 
  private:
   class GLTextureIRepresentation;
@@ -155,8 +156,7 @@
       base::trace_event::ProcessMemoryDump* pmd,
       uint64_t client_tracing_id) override;
   SharedImageBackingType GetType() const override;
-  gfx::Rect ClearedRect() const final;
-  void SetClearedRect(const gfx::Rect& cleared_rect) final;
+
   std::unique_ptr<GLTextureImageRepresentation> ProduceGLTexture(
       SharedImageManager* manager,
       MemoryTypeTracker* tracker) final;
@@ -188,39 +188,47 @@
 
   // IOSurfaceBackingEGLState::Client:
   bool IOSurfaceBackingEGLStateBeginAccess(IOSurfaceBackingEGLState* egl_state,
-                                           bool readonly) override;
+                                           bool readonly)
+      EXCLUSIVE_LOCKS_REQUIRED(lock_) override;
   void IOSurfaceBackingEGLStateEndAccess(IOSurfaceBackingEGLState* egl_state,
-                                         bool readonly) override;
-  void IOSurfaceBackingEGLStateBeingCreated(
-      IOSurfaceBackingEGLState* egl_state) override;
+                                         bool readonly)
+      EXCLUSIVE_LOCKS_REQUIRED(lock_) override;
+  void IOSurfaceBackingEGLStateBeingCreated(IOSurfaceBackingEGLState* egl_state)
+      EXCLUSIVE_LOCKS_REQUIRED(lock_) override;
   void IOSurfaceBackingEGLStateBeingDestroyed(
       IOSurfaceBackingEGLState* egl_state,
-      bool have_context) override;
+      bool have_context) EXCLUSIVE_LOCKS_REQUIRED(lock_) override;
 
   // Updates the read and write accesses tracker variables on BeginAccess.
-  bool BeginAccess(bool readonly);
+  bool BeginAccess(bool readonly) EXCLUSIVE_LOCKS_REQUIRED(lock_);
   // Updates the read and write accesses tracker variables on EndAccess.
-  void EndAccess(bool readonly);
+  void EndAccess(bool readonly) EXCLUSIVE_LOCKS_REQUIRED(lock_);
 
   void AddSharedEventForEndAccess(id<MTLSharedEvent> shared_event,
                                   uint64_t signal_value,
-                                  bool readonly);
+                                  bool readonly)
+      EXCLUSIVE_LOCKS_REQUIRED(lock_);
   template <typename Fn>
-  void ProcessSharedEventsForBeginAccess(bool readonly, const Fn& fn);
+  void ProcessSharedEventsForBeginAccess(bool readonly, const Fn& fn)
+      EXCLUSIVE_LOCKS_REQUIRED(lock_);
 
+  // Guarded by ScopedIOSurfaceLock instead of |lock_| for memory access.
   const gfx::ScopedIOSurface io_surface_;
+
   const gfx::Size io_surface_size_;
   const uint32_t io_surface_format_;
   const gfx::GenericSharedMemoryId io_surface_id_;
 
   // DawnSharedTextureCache that keeps an internal cache of per-device
   // SharedTextureData that vends WebGPU textures for the underlying IOSurface.
-  scoped_refptr<DawnSharedTextureCache> dawn_texture_cache_;
+  scoped_refptr<DawnSharedTextureCache> dawn_texture_cache_ GUARDED_BY(lock_);
 
-  const scoped_refptr<DawnSharedTextureCache>& GetDawnTextureCache();
+  const scoped_refptr<DawnSharedTextureCache>& GetDawnTextureCache()
+      EXCLUSIVE_LOCKS_REQUIRED(lock_);
 
   // Tracks the number of currently-ongoing accesses to a given WGPU texture.
-  base::flat_map<WGPUTexture, int> wgpu_texture_ongoing_accesses_;
+  base::flat_map<WGPUTexture, int> wgpu_texture_ongoing_accesses_
+      GUARDED_BY(lock_);
 
   // Tracks the devices to invoke waitUntilScheduled.
   // TODO(dawn:2453): The below comparator should be implemented in
@@ -230,49 +238,54 @@
       return lhs.Get() < rhs.Get();
     }
   };
-  base::flat_set<wgpu::Device, WGPUDeviceCompare> wgpu_devices_pending_flush_;
+  base::flat_set<wgpu::Device, WGPUDeviceCompare> wgpu_devices_pending_flush_
+      GUARDED_BY(lock_);
 
   // Returns the number of ongoing accesses that were already present on this
   // texture prior to beginning this access.
-  int TrackBeginAccessToWGPUTexture(wgpu::Texture texture);
+  int TrackBeginAccessToWGPUTexture(wgpu::Texture texture)
+      EXCLUSIVE_LOCKS_REQUIRED(lock_);
 
   // Returns the number of ongoing accesses that will still be present on this
   // texture after ending this access.
-  int TrackEndAccessToWGPUTexture(wgpu::Texture texture);
+  int TrackEndAccessToWGPUTexture(wgpu::Texture texture)
+      EXCLUSIVE_LOCKS_REQUIRED(lock_);
 
   const GLenum gl_target_;
   const bool framebuffer_attachment_angle_;
 
   // Used to determine whether to release the texture in EndAccess() in use
   // cases that need to ensure IOSurface synchronization.
-  uint num_ongoing_read_accesses_ = 0;
+  uint num_ongoing_read_accesses_ GUARDED_BY(lock_) = 0;
   // Used with the above variable to catch cases where clients are performing
   // disallowed concurrent read/write accesses.
-  bool ongoing_write_access_ = false;
+  bool ongoing_write_access_ GUARDED_BY(lock_) = false;
 
-  scoped_refptr<IOSurfaceBackingEGLState> RetainGLTexture();
-  void ReleaseGLTexture(IOSurfaceBackingEGLState* egl_state, bool have_context);
-
-  // This is the cleared rect used by ClearedRect and SetClearedRect when
-  // |texture_| is nullptr.
-  gfx::Rect cleared_rect_;
+  scoped_refptr<IOSurfaceBackingEGLState> RetainGLTexture()
+      EXCLUSIVE_LOCKS_REQUIRED(lock_);
+  void ReleaseGLTexture(IOSurfaceBackingEGLState* egl_state, bool have_context)
+      EXCLUSIVE_LOCKS_REQUIRED(lock_);
 
   // Whether or not the surface is currently purgeable.
-  bool purgeable_ = false;
+  bool purgeable_ GUARDED_BY(lock_) = false;
 
   // This map tracks all IOSurfaceBackingEGLState instances that exist.
-  base::flat_map<EGLDisplay, IOSurfaceBackingEGLState*> egl_state_map_;
+  base::flat_map<EGLDisplay, IOSurfaceBackingEGLState*> egl_state_map_
+      GUARDED_BY(lock_);
 
   // GrContextType for SharedContextState used to distinguish between Ganesh
   // and Graphite.
-  GrContextType gr_context_type_;
+  const GrContextType gr_context_type_;
 
   // If Skia is using GL, this object creates a GL texture at construction time
   // for the Skia GL context and reuses it (for that context) for its lifetime.
-  scoped_refptr<IOSurfaceBackingEGLState> egl_state_for_skia_gl_context_;
+  // This egl_state is set in IOSurfaceImageBacking Ctor only.
+  scoped_refptr<IOSurfaceBackingEGLState> egl_state_for_skia_gl_context_
+      GUARDED_BY(lock_);
 
   // Tracks the displays to invoke eglWaitUntilWorkScheduledANGLE().
-  base::flat_set<gl::GLDisplayEGL*> egl_displays_pending_flush_;
+  base::flat_set<gl::GLDisplayEGL*> egl_displays_pending_flush_
+      GUARDED_BY(lock_);
 
   using ScopedSharedEvent = base::apple::scoped_nsprotocol<id<MTLSharedEvent>>;
   struct SharedEventCompare {
@@ -284,9 +297,9 @@
   using SharedEventMap =
       base::flat_map<ScopedSharedEvent, uint64_t, SharedEventCompare>;
   // Shared events and signals for exclusive accesses.
-  SharedEventMap exclusive_shared_events_;
+  SharedEventMap exclusive_shared_events_ GUARDED_BY(lock_);
   // Shared events and signals for non-exclusive accesses.
-  SharedEventMap non_exclusive_shared_events_;
+  SharedEventMap non_exclusive_shared_events_ GUARDED_BY(lock_);
 
   base::WeakPtrFactory<IOSurfaceImageBacking> weak_factory_;
 };
diff --git a/gpu/command_buffer/service/shared_image/iosurface_image_backing.mm b/gpu/command_buffer/service/shared_image/iosurface_image_backing.mm
index 4a5e90a..226a31b 100644
--- a/gpu/command_buffer/service/shared_image/iosurface_image_backing.mm
+++ b/gpu/command_buffer/service/shared_image/iosurface_image_backing.mm
@@ -291,6 +291,7 @@
 
   bool BeginAccess(GLenum mode) override {
     DCHECK(mode_ == 0);
+    AutoLock auto_lock(backing());
     mode_ = mode;
     bool readonly = mode_ != GL_SHARED_IMAGE_ACCESS_MODE_READWRITE_CHROMIUM;
     return egl_state_->BeginAccess(readonly);
@@ -298,6 +299,7 @@
 
   void EndAccess() override {
     DCHECK(mode_ != 0);
+    AutoLock auto_lock(backing());
     GLenum current_mode = mode_;
     mode_ = 0;
     egl_state_->EndAccess(current_mode !=
@@ -323,9 +325,6 @@
       MemoryTypeTracker* tracker);
   ~SkiaGaneshRepresentation() override;
 
-  void SetBeginReadAccessCallback(
-      base::RepeatingClosure begin_read_access_callback);
-
  private:
   // SkiaGaneshImageRepresentation:
   std::vector<sk_sp<SkSurface>> BeginWriteAccess(
@@ -350,7 +349,7 @@
   void CheckContext();
 
   scoped_refptr<IOSurfaceBackingEGLState> egl_state_;
-  scoped_refptr<SharedContextState> context_state_;
+  const scoped_refptr<SharedContextState> context_state_;
   std::vector<sk_sp<GrPromiseImageTexture>> promise_textures_;
   std::vector<sk_sp<SkSurface>> write_surfaces_;
 #if DCHECK_IS_ON()
@@ -400,7 +399,9 @@
     std::vector<GrBackendSemaphore>* begin_semaphores,
     std::vector<GrBackendSemaphore>* end_semaphores,
     std::unique_ptr<skgpu::MutableTextureState>* end_state) {
+  AutoLock auto_lock(backing());
   CheckContext();
+
   if (egl_state_) {
     DCHECK(context_state_->GrContextIsGL());
     if (!egl_state_->BeginAccess(/*readonly=*/false)) {
@@ -449,7 +450,9 @@
     std::vector<GrBackendSemaphore>* begin_semaphores,
     std::vector<GrBackendSemaphore>* end_semaphores,
     std::unique_ptr<skgpu::MutableTextureState>* end_state) {
+  AutoLock auto_lock(backing());
   CheckContext();
+
   if (egl_state_) {
     DCHECK(context_state_->GrContextIsGL());
     if (!egl_state_->BeginAccess(/*readonly=*/false)) {
@@ -463,6 +466,7 @@
 }
 
 void IOSurfaceImageBacking::SkiaGaneshRepresentation::EndWriteAccess() {
+  AutoLock auto_lock(backing());
 #if DCHECK_IS_ON()
   for (auto& surface : write_surfaces_) {
     DCHECK(surface->unique());
@@ -481,7 +485,9 @@
     std::vector<GrBackendSemaphore>* begin_semaphores,
     std::vector<GrBackendSemaphore>* end_semaphores,
     std::unique_ptr<skgpu::MutableTextureState>* end_state) {
+  AutoLock auto_lock(backing());
   CheckContext();
+
   if (egl_state_) {
     DCHECK(context_state_->GrContextIsGL());
     if (!egl_state_->BeginAccess(/*readonly=*/true)) {
@@ -495,8 +501,11 @@
 }
 
 void IOSurfaceImageBacking::SkiaGaneshRepresentation::EndReadAccess() {
-  if (egl_state_)
+  AutoLock auto_lock(backing());
+
+  if (egl_state_) {
     egl_state_->EndAccess(/*readonly=*/true);
+  }
 }
 
 bool IOSurfaceImageBacking::SkiaGaneshRepresentation::
@@ -562,6 +571,8 @@
 IOSurfaceImageBacking::SkiaGraphiteMetalRepresentation::BeginWriteAccess(
     const SkSurfaceProps& surface_props,
     const gfx::Rect& update_rect) {
+  AutoLock auto_lock(backing_impl());
+
   if (!write_surfaces_.empty()) {
     // Write access is already in progress.
     return {};
@@ -594,6 +605,8 @@
 
 std::vector<scoped_refptr<GraphiteTextureHolder>>
 IOSurfaceImageBacking::SkiaGraphiteMetalRepresentation::BeginWriteAccess() {
+  AutoLock auto_lock(backing_impl());
+
   if (!backing_impl()->BeginAccess(/*readonly=*/false)) {
     return {};
   }
@@ -601,6 +614,7 @@
 }
 
 void IOSurfaceImageBacking::SkiaGraphiteMetalRepresentation::EndWriteAccess() {
+  AutoLock auto_lock(backing_impl());
 #if DCHECK_IS_ON()
   for (auto& surface : write_surfaces_) {
     DCHECK(surface->unique());
@@ -612,6 +626,7 @@
 
 std::vector<scoped_refptr<GraphiteTextureHolder>>
 IOSurfaceImageBacking::SkiaGraphiteMetalRepresentation::BeginReadAccess() {
+  AutoLock auto_lock(backing_impl());
   if (!backing_impl()->BeginAccess(/*readonly=*/true)) {
     return {};
   }
@@ -619,6 +634,7 @@
 }
 
 void IOSurfaceImageBacking::SkiaGraphiteMetalRepresentation::EndReadAccess() {
+  AutoLock auto_lock(backing_impl());
   backing_impl()->EndAccess(/*readonly=*/true);
 }
 #endif
@@ -649,6 +665,7 @@
 bool IOSurfaceImageBacking::OverlayRepresentation::BeginReadAccess(
     gfx::GpuFenceHandle& acquire_fence) {
   auto* iosurface_backing = static_cast<IOSurfaceImageBacking*>(backing());
+  AutoLock auto_lock(iosurface_backing);
 
   if (!iosurface_backing->BeginAccess(/*readonly=*/true)) {
     return false;
@@ -664,11 +681,10 @@
 
   gl::GLContext* context = gl::GLContext::GetCurrent();
   if (context) {
-    const auto& signals = static_cast<IOSurfaceImageBacking*>(backing())
-                              ->exclusive_shared_events_;
     std::vector<std::unique_ptr<BackpressureMetalSharedEvent>>
         backpressure_events;
-    for (const auto& [shared_event, signaled_value] : signals) {
+    for (const auto& [shared_event, signaled_value] :
+         iosurface_backing->exclusive_shared_events_) {
       backpressure_events.push_back(
           std::make_unique<BackpressureMetalSharedEventImpl>(shared_event,
                                                              signaled_value));
@@ -682,8 +698,11 @@
 
 void IOSurfaceImageBacking::OverlayRepresentation::EndReadAccess(
     gfx::GpuFenceHandle release_fence) {
+  auto* iosurface_backing = static_cast<IOSurfaceImageBacking*>(backing());
+  AutoLock auto_lock(iosurface_backing);
   DCHECK(release_fence.is_null());
-  static_cast<IOSurfaceImageBacking*>(backing())->EndAccess(/*readonly=*/true);
+
+  iosurface_backing->EndAccess(/*readonly=*/true);
 }
 
 gfx::ScopedIOSurface
@@ -752,11 +771,13 @@
 wgpu::Texture IOSurfaceImageBacking::DawnRepresentation::BeginAccess(
     wgpu::TextureUsage wgpu_texture_usage,
     wgpu::TextureUsage internal_usage) {
+  IOSurfaceImageBacking* iosurface_backing =
+      static_cast<IOSurfaceImageBacking*>(backing());
+  AutoLock auto_lock(iosurface_backing);
+
   const bool readonly = (wgpu_texture_usage & ~kReadOnlyUsage) == 0 &&
                         (internal_usage & ~kReadOnlyUsage) == 0;
 
-  IOSurfaceImageBacking* iosurface_backing =
-      static_cast<IOSurfaceImageBacking*>(backing());
   if (!iosurface_backing->BeginAccess(readonly)) {
     return {};
   }
@@ -797,12 +818,13 @@
     return texture_;
   }
 
+  bool is_cleared = iosurface_backing->IsClearedInternal();
   wgpu::SharedTextureMemoryBeginAccessDescriptor begin_access_desc = {};
-  begin_access_desc.initialized = IsCleared();
+  begin_access_desc.initialized = is_cleared;
 
   // NOTE: WebGPU allows reads of uncleared textures, in which case Dawn clears
   // the texture on its initial access. Such reads must take exclusive access.
-  begin_access_desc.concurrentRead = readonly && IsCleared();
+  begin_access_desc.concurrentRead = readonly && is_cleared;
 
   std::vector<wgpu::SharedFence> shared_fences;
   std::vector<uint64_t> signaled_values;
@@ -852,6 +874,10 @@
 }
 
 void IOSurfaceImageBacking::DawnRepresentation::EndAccess() {
+  IOSurfaceImageBacking* iosurface_backing =
+      static_cast<IOSurfaceImageBacking*>(backing());
+  AutoLock auto_lock(iosurface_backing);
+
   if (!texture_) {
     // The only valid cases in which this could occur are (a) if
     // SharedTextureMemory::BeginAccess() failed, in which case we already
@@ -864,10 +890,9 @@
 
   // Inform the backing that an access has ended so that it can properly update
   // its state tracking.
-  IOSurfaceImageBacking* iosurface_backing =
-      static_cast<IOSurfaceImageBacking*>(backing());
   const bool readonly = (usage_ & ~kReadOnlyUsage) == 0 &&
                         (internal_usage_ & ~kReadOnlyUsage) == 0;
+
   iosurface_backing->EndAccess(readonly);
   int num_outstanding_accesses =
       iosurface_backing->TrackEndAccessToWGPUTexture(texture_);
@@ -887,7 +912,7 @@
            wgpu::Status::Success);
 
   if (end_access_desc.initialized) {
-    SetCleared();
+    iosurface_backing->SetClearedInternal();
   }
 
   // Not possible to reach this with any other type of backing.
@@ -984,17 +1009,17 @@
     bool is_thread_safe,
     GrContextType gr_context_type,
     std::optional<gfx::BufferUsage> buffer_usage)
-    : SharedImageBacking(mailbox,
-                         format,
-                         size,
-                         color_space,
-                         surface_origin,
-                         alpha_type,
-                         usage,
-                         std::move(debug_label),
-                         format.EstimatedSizeInBytes(size),
-                         is_thread_safe,
-                         std::move(buffer_usage)),
+    : ClearTrackingSharedImageBacking(mailbox,
+                                      format,
+                                      size,
+                                      color_space,
+                                      surface_origin,
+                                      alpha_type,
+                                      usage,
+                                      std::move(debug_label),
+                                      format.EstimatedSizeInBytes(size),
+                                      is_thread_safe,
+                                      std::move(buffer_usage)),
       io_surface_(std::move(io_surface)),
       io_surface_size_(IOSurfaceGetWidth(io_surface_.get()),
                        IOSurfaceGetHeight(io_surface_.get())),
@@ -1003,7 +1028,6 @@
       dawn_texture_cache_(base::MakeRefCounted<DawnSharedTextureCache>()),
       gl_target_(gl_target),
       framebuffer_attachment_angle_(framebuffer_attachment_angle),
-      cleared_rect_(is_cleared ? gfx::Rect(size) : gfx::Rect()),
       gr_context_type_(gr_context_type),
       weak_factory_(this) {
   CHECK(io_surface_);
@@ -1017,6 +1041,8 @@
     return;
   }
 
+  SetClearedRectInternal((is_cleared ? gfx::Rect(size) : gfx::Rect()));
+
   // NOTE: Mac currently retains GLTexture and reuses it. This might lead to
   // issues with context losses, but is also beneficial to performance at
   // least on perf benchmarks.
@@ -1030,6 +1056,7 @@
 }
 
 IOSurfaceImageBacking::~IOSurfaceImageBacking() {
+  AutoLock auto_lock(this);
   if (egl_state_for_skia_gl_context_) {
     egl_state_for_skia_gl_context_->WillRelease(have_context());
     egl_state_for_skia_gl_context_ = nullptr;
@@ -1039,6 +1066,7 @@
 
 bool IOSurfaceImageBacking::ReadbackToMemory(
     const std::vector<SkPixmap>& pixmaps) {
+  AutoLock auto_lock(this);
   CHECK_LE(pixmaps.size(), 3u);
 
   // Make sure any pending ANGLE EGLDisplays and Dawn devices are flushed.
@@ -1090,6 +1118,7 @@
 
 bool IOSurfaceImageBacking::UploadFromMemory(
     const std::vector<SkPixmap>& pixmaps) {
+  AutoLock auto_lock(this);
   CHECK_LE(pixmaps.size(), 3u);
 
   // Make sure any pending ANGLE EGLDisplays and Dawn devices are flushed.
@@ -1141,6 +1170,8 @@
 
 scoped_refptr<IOSurfaceBackingEGLState>
 IOSurfaceImageBacking::RetainGLTexture() {
+  AssertLockAcquired();
+
   gl::GLContext* context = gl::GLContext::GetCurrent();
   gl::GLDisplayEGL* display = context ? context->GetGLDisplayEGL() : nullptr;
   if (!display) {
@@ -1161,7 +1192,8 @@
     MakeTextureAndSetParameters(gl_target_, framebuffer_attachment_angle_,
                                 &gl_texture, nullptr);
     // Set the IOSurface to be initially unbound from the GL texture.
-    gl_texture->SetEstimatedSize(GetEstimatedSize());
+    gl_texture->SetEstimatedSize(format().EstimatedSizeInBytes(size()));
+
     gl_textures.push_back(std::move(gl_texture));
   }
 
@@ -1176,11 +1208,13 @@
 void IOSurfaceImageBacking::ReleaseGLTexture(
     IOSurfaceBackingEGLState* egl_state,
     bool have_context) {
+  AssertLockAcquired();
   DCHECK_EQ(static_cast<int>(egl_state->gl_textures_.size()),
             format().NumberOfPlanes());
   DCHECK(egl_state->egl_surfaces_.empty() ||
          static_cast<int>(egl_state->egl_surfaces_.size()) ==
              format().NumberOfPlanes());
+
   if (!have_context) {
     for (const auto& texture : egl_state->gl_textures_) {
       texture->MarkContextLost();
@@ -1242,14 +1276,6 @@
   return SharedImageBackingType::kIOSurface;
 }
 
-gfx::Rect IOSurfaceImageBacking::ClearedRect() const {
-  return cleared_rect_;
-}
-
-void IOSurfaceImageBacking::SetClearedRect(const gfx::Rect& cleared_rect) {
-  cleared_rect_ = cleared_rect;
-}
-
 std::unique_ptr<GLTextureImageRepresentation>
 IOSurfaceImageBacking::ProduceGLTexture(SharedImageManager* manager,
                                         MemoryTypeTracker* tracker) {
@@ -1259,10 +1285,16 @@
 std::unique_ptr<GLTexturePassthroughImageRepresentation>
 IOSurfaceImageBacking::ProduceGLTexturePassthrough(SharedImageManager* manager,
                                                    MemoryTypeTracker* tracker) {
+  scoped_refptr<IOSurfaceBackingEGLState> egl_state;
+  {
+    AutoLock auto_lock(this);
+    egl_state = RetainGLTexture();
+  }
+
   // The corresponding release will be done when the returned representation is
   // destroyed, in GLTextureImageRepresentationBeingDestroyed.
-  return std::make_unique<GLTextureIRepresentation>(manager, this,
-                                                    RetainGLTexture(), tracker);
+  return std::make_unique<GLTextureIRepresentation>(
+      manager, this, std::move(egl_state), tracker);
 }
 
 std::unique_ptr<OverlayImageRepresentation>
@@ -1274,10 +1306,14 @@
 
 int IOSurfaceImageBacking::TrackBeginAccessToWGPUTexture(
     wgpu::Texture texture) {
+  AssertLockAcquired();
+
   return wgpu_texture_ongoing_accesses_[texture.Get()]++;
 }
 
 int IOSurfaceImageBacking::TrackEndAccessToWGPUTexture(wgpu::Texture texture) {
+  AssertLockAcquired();
+
   if (!wgpu_texture_ongoing_accesses_.contains(texture.Get())) {
     return 0;
   }
@@ -1295,16 +1331,19 @@
 
 const scoped_refptr<DawnSharedTextureCache>&
 IOSurfaceImageBacking::GetDawnTextureCache() {
+  AssertLockAcquired();
   return dawn_texture_cache_;
 }
 
 void IOSurfaceImageBacking::AddWGPUDeviceWithPendingCommands(
     wgpu::Device device) {
+  AssertLockAcquired();
   wgpu_devices_pending_flush_.insert(std::move(device));
 }
 
 void IOSurfaceImageBacking::WaitForDawnCommandsToBeScheduled(
     const wgpu::Device& device_to_exclude) {
+  AssertLockAcquired();
   TRACE_EVENT0("gpu",
                "IOSurfaceImageBacking::WaitForDawnCommandsToBeScheduled");
   bool excluded_device_was_pending_flush = false;
@@ -1323,12 +1362,15 @@
 
 void IOSurfaceImageBacking::AddEGLDisplayWithPendingCommands(
     gl::GLDisplayEGL* display) {
+  AssertLockAcquired();
   egl_displays_pending_flush_.insert(display);
 }
 
 void IOSurfaceImageBacking::WaitForANGLECommandsToBeScheduled() {
+  AssertLockAcquired();
   TRACE_EVENT0("gpu",
                "IOSurfaceImageBacking::WaitForANGLECommandsToBeScheduled");
+
   for (auto* display : std::move(egl_displays_pending_flush_)) {
     eglWaitUntilWorkScheduledANGLE(display->GetDisplay());
   }
@@ -1336,6 +1378,8 @@
 
 void IOSurfaceImageBacking::ClearEGLDisplaysWithPendingCommands(
     gl::GLDisplayEGL* display_to_keep) {
+  AssertLockAcquired();
+
   if (std::move(egl_displays_pending_flush_).contains(display_to_keep)) {
     egl_displays_pending_flush_.insert(display_to_keep);
   }
@@ -1360,55 +1404,64 @@
   }
 
   if (backend_type == wgpu::BackendType::Metal) {
-    // Clear out any cached SharedTextureMemory instances for which the
-    // associated Device has been lost - this both saves memory and more
-    // importantly ensures that a new SharedTextureMemory instance will be
-    // created if another Device occupies the same memory as a previously-used,
-    // now-lost Device.
-    dawn_texture_cache_->EraseDataIfDeviceLost();
+    wgpu::SharedTextureMemory shared_texture_memory;
+    {
+      AutoLock auto_lock(this);
 
-    CHECK(device.HasFeature(wgpu::FeatureName::SharedTextureMemoryIOSurface));
+      // Clear out any cached SharedTextureMemory instances for which the
+      // associated Device has been lost - this both saves memory and more
+      // importantly ensures that a new SharedTextureMemory instance will be
+      // created if another Device occupies the same memory as a
+      // previously-used, now-lost Device.
+      dawn_texture_cache_->EraseDataIfDeviceLost();
 
-    wgpu::SharedTextureMemory shared_texture_memory =
-        dawn_texture_cache_->GetSharedTextureMemory(device);
-    if (!shared_texture_memory) {
-      // NOTE: `shared_dawn_context` may be null if Graphite is not being used.
-      const auto* shared_dawn_context = context_state->dawn_context_provider();
-      const bool is_graphite_device =
-          shared_dawn_context &&
-          shared_dawn_context->GetDevice().Get() == device.Get();
+      CHECK(device.HasFeature(wgpu::FeatureName::SharedTextureMemoryIOSurface));
 
-      wgpu::SharedTextureMemoryIOSurfaceDescriptor io_surface_desc;
-      io_surface_desc.ioSurface = io_surface_.get();
-      // Set storage binding usage only if explicitly needed for WebGPU - this
-      // forces the MTLTexture wrapping the IOSurface to have ShaderWrite usage
-      // which in turn prevents texture compression. It's possible this doesn't
-      // have any effect given that IOSurfaces have linear layout, but it might
-      // if the kernel chooses to create a separate allocation for the GPU.
-      io_surface_desc.allowStorageBinding =
-          (usage() & SHARED_IMAGE_USAGE_WEBGPU_STORAGE_TEXTURE) &&
-          !is_graphite_device;
-
-      wgpu::SharedTextureMemoryDescriptor desc = {};
-      desc.nextInChain = &io_surface_desc;
-
-      shared_texture_memory = device.ImportSharedTextureMemory(&desc);
+      shared_texture_memory =
+          dawn_texture_cache_->GetSharedTextureMemory(device);
       if (!shared_texture_memory) {
-        LOG(ERROR) << "Unable to create SharedTextureMemory - device lost?";
-        return nullptr;
-      }
+        // NOTE: `shared_dawn_context` may be null if Graphite is not being
+        // used.
+        const auto* shared_dawn_context =
+            context_state->dawn_context_provider();
+        const bool is_graphite_device =
+            shared_dawn_context &&
+            shared_dawn_context->GetDevice().Get() == device.Get();
 
-      // We cache the SharedTextureMemory instance that is associated with the
-      // Graphite device.
-      // TODO(crbug.com/345674550): Extend caching to WebGPU devices as well.
-      if (is_graphite_device) {
-        // This is the Graphite device, so we cache its SharedTextureMemory
-        // instance.
-        dawn_texture_cache_->MaybeCacheSharedTextureMemory(
-            device, shared_texture_memory);
+        wgpu::SharedTextureMemoryIOSurfaceDescriptor io_surface_desc;
+        io_surface_desc.ioSurface = io_surface_.get();
+        // Set storage binding usage only if explicitly needed for WebGPU - this
+        // forces the MTLTexture wrapping the IOSurface to have ShaderWrite
+        // usage which in turn prevents texture compression. It's possible this
+        // doesn't have any effect given that IOSurfaces have linear layout, but
+        // it might if the kernel chooses to create a separate allocation for
+        // the GPU.
+        io_surface_desc.allowStorageBinding =
+            (usage() & SHARED_IMAGE_USAGE_WEBGPU_STORAGE_TEXTURE) &&
+            !is_graphite_device;
+
+        wgpu::SharedTextureMemoryDescriptor desc = {};
+        desc.nextInChain = &io_surface_desc;
+
+        shared_texture_memory = device.ImportSharedTextureMemory(&desc);
+        if (!shared_texture_memory) {
+          LOG(ERROR) << "Unable to create SharedTextureMemory - device lost?";
+          return nullptr;
+        }
+
+        // We cache the SharedTextureMemory instance that is associated with the
+        // Graphite device.
+        // TODO(crbug.com/345674550): Extend caching to WebGPU devices as well.
+        if (is_graphite_device) {
+          // This is the Graphite device, so we cache its SharedTextureMemory
+          // instance.
+          dawn_texture_cache_->MaybeCacheSharedTextureMemory(
+              device, shared_texture_memory);
+        }
       }
     }
 
+    // SharedImageRepresentation handles lock.
     return std::make_unique<DawnRepresentation>(
         manager, this, tracker, wgpu::Device(device),
         std::move(shared_texture_memory), io_surface_size_, wgpu_format,
@@ -1429,27 +1482,31 @@
   scoped_refptr<IOSurfaceBackingEGLState> egl_state;
   std::vector<sk_sp<GrPromiseImageTexture>> promise_textures;
 
-  if (context_state->GrContextIsGL()) {
-    egl_state = RetainGLTexture();
-  }
-
-  for (int plane_index = 0; plane_index < format().NumberOfPlanes();
-       plane_index++) {
-    GLFormatDesc format_desc =
-        context_state->GetGLFormatCaps().ToGLFormatDesc(format(), plane_index);
-    GrBackendTexture backend_texture;
-    auto plane_size = format().GetPlaneSize(plane_index, size());
-    GetGrBackendTexture(context_state->feature_info(), egl_state->GetGLTarget(),
-                        plane_size, egl_state->GetGLServiceId(plane_index),
-                        format_desc.storage_internal_format,
-                        context_state->gr_context()->threadSafeProxy(),
-                        &backend_texture);
-    sk_sp<GrPromiseImageTexture> promise_texture =
-        GrPromiseImageTexture::Make(backend_texture);
-    if (!promise_texture) {
-      return nullptr;
+  {
+    AutoLock auto_lock(this);
+    if (context_state->GrContextIsGL()) {
+      egl_state = RetainGLTexture();
     }
-    promise_textures.push_back(std::move(promise_texture));
+
+    for (int plane_index = 0; plane_index < format().NumberOfPlanes();
+         plane_index++) {
+      GLFormatDesc format_desc =
+          context_state->GetGLFormatCaps().ToGLFormatDesc(format(),
+                                                          plane_index);
+      GrBackendTexture backend_texture;
+      auto plane_size = format().GetPlaneSize(plane_index, size());
+      GetGrBackendTexture(
+          context_state->feature_info(), egl_state->GetGLTarget(), plane_size,
+          egl_state->GetGLServiceId(plane_index),
+          format_desc.storage_internal_format,
+          context_state->gr_context()->threadSafeProxy(), &backend_texture);
+      sk_sp<GrPromiseImageTexture> promise_texture =
+          GrPromiseImageTexture::Make(backend_texture);
+      if (!promise_texture) {
+        return nullptr;
+      }
+      promise_textures.push_back(std::move(promise_texture));
+    }
   }
 
   return std::make_unique<SkiaGaneshRepresentation>(manager, this, egl_state,
@@ -1465,6 +1522,7 @@
   CHECK(context_state);
   if (context_state->IsGraphiteDawn()) {
 #if BUILDFLAG(SKIA_USE_DAWN)
+    // No AutoLock here. Lock is handled in ProduceDawn().
     auto device = context_state->dawn_context_provider()->GetDevice();
     auto backend_type = context_state->dawn_context_provider()->backend_type();
     auto dawn_representation =
@@ -1522,6 +1580,7 @@
 }
 
 void IOSurfaceImageBacking::SetPurgeable(bool purgeable) {
+  AutoLock auto_lock(this);
   if (purgeable_ == purgeable)
     return;
   purgeable_ = purgeable;
@@ -1531,7 +1590,7 @@
     DCHECK(!ongoing_write_access_);
     DCHECK(!num_ongoing_read_accesses_);
 
-    SetClearedRect(gfx::Rect());
+    SetClearedRectInternal(gfx::Rect());
   }
 
   uint32_t old_state;
@@ -1539,10 +1598,12 @@
 }
 
 bool IOSurfaceImageBacking::IsPurgeable() const {
+  AutoLock auto_lock(this);
   return purgeable_;
 }
 
 void IOSurfaceImageBacking::Update(std::unique_ptr<gfx::GpuFence> in_fence) {
+  AutoLock auto_lock(this);
   if (in_fence) {
     // TODO(dcastagna): Don't wait for the fence if the SharedImage is going
     // to be scanned out as an HW overlay. Currently we don't know that at
@@ -1565,6 +1626,8 @@
 }
 
 bool IOSurfaceImageBacking::BeginAccess(bool readonly) {
+  AssertLockAcquired();
+
   if (!readonly && ongoing_write_access_) {
     DLOG(ERROR) << "Unable to begin write access because another "
                    "write access is in progress";
@@ -1594,6 +1657,8 @@
 }
 
 void IOSurfaceImageBacking::EndAccess(bool readonly) {
+  AssertLockAcquired();
+
   if (readonly) {
     CHECK_GT(num_ongoing_read_accesses_, 0u);
     if (!(usage().Has(SHARED_IMAGE_USAGE_CONCURRENT_READ_WRITE))) {
@@ -1612,6 +1677,8 @@
 bool IOSurfaceImageBacking::IOSurfaceBackingEGLStateBeginAccess(
     IOSurfaceBackingEGLState* egl_state,
     bool readonly) {
+  AssertLockAcquired();
+
   // It is in error to read or write an IOSurface while it is purgeable.
   CHECK(!purgeable_);
   if (!BeginAccess(readonly)) {
@@ -1713,6 +1780,8 @@
 void IOSurfaceImageBacking::IOSurfaceBackingEGLStateEndAccess(
     IOSurfaceBackingEGLState* egl_state,
     bool readonly) {
+  AssertLockAcquired();
+
   EndAccess(readonly);
 
   // Early out if BeginAccess didn't succeed and we didn't bind any surfaces.
@@ -1782,6 +1851,8 @@
 
 void IOSurfaceImageBacking::IOSurfaceBackingEGLStateBeingCreated(
     IOSurfaceBackingEGLState* egl_state) {
+  AssertLockAcquired();
+
   auto insert_result =
       egl_state_map_.insert(std::make_pair(egl_state->egl_display_, egl_state));
   CHECK(insert_result.second);
@@ -1790,6 +1861,7 @@
 void IOSurfaceImageBacking::IOSurfaceBackingEGLStateBeingDestroyed(
     IOSurfaceBackingEGLState* egl_state,
     bool has_context) {
+  AssertLockAcquired();
   ReleaseGLTexture(egl_state, has_context);
 
   egl_state->egl_surfaces_.clear();
@@ -1803,6 +1875,7 @@
 
 bool IOSurfaceImageBacking::InitializePixels(
     base::span<const uint8_t> pixel_data) {
+  AutoLock auto_lock(this);
   CHECK(format().is_single_plane());
   ScopedIOSurfaceLock io_surface_lock(io_surface_.get(),
                                       kIOSurfaceLockAvoidSync);
@@ -1833,6 +1906,8 @@
     id<MTLSharedEvent> shared_event,
     uint64_t signal_value,
     bool readonly) {
+  AssertLockAcquired();
+
   SharedEventMap& shared_events =
       readonly ? non_exclusive_shared_events_ : exclusive_shared_events_;
   auto [it, _] = shared_events.insert(
@@ -1843,6 +1918,8 @@
 template <typename Fn>
 void IOSurfaceImageBacking::ProcessSharedEventsForBeginAccess(bool readonly,
                                                               const Fn& fn) {
+  AssertLockAcquired();
+
   // Always need wait on exclusive access end events.
   for (const auto& [shared_event, signal_value] : exclusive_shared_events_) {
     fn(shared_event.get(), signal_value);
diff --git a/gpu/command_buffer/service/shared_image/iosurface_image_backing_factory.h b/gpu/command_buffer/service/shared_image/iosurface_image_backing_factory.h
index 9e10a03b..73e9e414 100644
--- a/gpu/command_buffer/service/shared_image/iosurface_image_backing_factory.h
+++ b/gpu/command_buffer/service/shared_image/iosurface_image_backing_factory.h
@@ -135,7 +135,7 @@
   // long.
   const raw_ptr<gl::ProgressReporter> progress_reporter_;
 
-  uint32_t texture_target_;
+  const uint32_t texture_target_;
 };
 
 }  // namespace gpu
diff --git a/gpu/command_buffer/service/shared_image/shared_image_backing.cc b/gpu/command_buffer/service/shared_image/shared_image_backing.cc
index 6878fb8..fecd98a 100644
--- a/gpu/command_buffer/service/shared_image/shared_image_backing.cc
+++ b/gpu/command_buffer/service/shared_image/shared_image_backing.cc
@@ -473,6 +473,14 @@
   cleared_rect_ = cleared_rect;
 }
 
+void ClearTrackingSharedImageBacking::SetClearedInternal() {
+  cleared_rect_ = gfx::Rect(size());
+}
+
+bool ClearTrackingSharedImageBacking::IsClearedInternal() const {
+  return cleared_rect_ == gfx::Rect(size());
+}
+
 scoped_refptr<gfx::NativePixmap> SharedImageBacking::GetNativePixmap() {
   return nullptr;
 }
diff --git a/gpu/command_buffer/service/shared_image/shared_image_backing.h b/gpu/command_buffer/service/shared_image/shared_image_backing.h
index d05801f3..bf886a8 100644
--- a/gpu/command_buffer/service/shared_image/shared_image_backing.h
+++ b/gpu/command_buffer/service/shared_image/shared_image_backing.h
@@ -362,6 +362,12 @@
     return factory_;
   }
 
+  void AssertLockAcquired() const {
+    if (lock_) {
+      lock_->AssertAcquired();
+    }
+  }
+
   // Helper class used by subclasses to acquire |lock_| if it exists.
   class SCOPED_LOCKABLE GPU_GLES2_EXPORT AutoLock {
     STACK_ALLOCATED();
@@ -450,6 +456,8 @@
   gfx::Rect ClearedRectInternal() const EXCLUSIVE_LOCKS_REQUIRED(lock_);
   void SetClearedRectInternal(const gfx::Rect& cleared_rect)
       EXCLUSIVE_LOCKS_REQUIRED(lock_);
+  void SetClearedInternal() EXCLUSIVE_LOCKS_REQUIRED(lock_);
+  bool IsClearedInternal() const EXCLUSIVE_LOCKS_REQUIRED(lock_);
 
  private:
   gfx::Rect cleared_rect_ GUARDED_BY(lock_);
diff --git a/infra/config/generated/luci/cr-buildbucket.cfg b/infra/config/generated/luci/cr-buildbucket.cfg
index 9208a30f4..baa8173 100644
--- a/infra/config/generated/luci/cr-buildbucket.cfg
+++ b/infra/config/generated/luci/cr-buildbucket.cfg
@@ -47271,6 +47271,10 @@
         '      "sdk_package_name": "system-images;android-34;google_apis;x86_64"'
         '    },'
         '    {'
+        '      "cipd_yaml": "third_party/android_sdk/cipd/system_images/android-34/android-automotive/x86_64.yaml",'
+        '      "sdk_package_name": "system-images;android-34-ext9;android-automotive;x86_64"'
+        '    },'
+        '    {'
         '      "cipd_yaml": "third_party/android_sdk/cipd/system_images/android-35/google_apis/x86_64.yaml",'
         '      "sdk_package_name": "system-images;android-35;google_apis;x86_64"'
         '    },'
diff --git a/infra/config/subprojects/chromium/ci/chromium.infra.star b/infra/config/subprojects/chromium/ci/chromium.infra.star
index 5d1adb0e..4950923 100644
--- a/infra/config/subprojects/chromium/ci/chromium.infra.star
+++ b/infra/config/subprojects/chromium/ci/chromium.infra.star
@@ -337,6 +337,10 @@
                 "cipd_yaml": "third_party/android_sdk/cipd/system_images/android-34/google_apis/x86_64.yaml",
             },
             {
+                "sdk_package_name": "system-images;android-34-ext9;android-automotive;x86_64",
+                "cipd_yaml": "third_party/android_sdk/cipd/system_images/android-34/android-automotive/x86_64.yaml",
+            },
+            {
                 "sdk_package_name": "system-images;android-35;google_apis;x86_64",
                 "cipd_yaml": "third_party/android_sdk/cipd/system_images/android-35/google_apis/x86_64.yaml",
             },
diff --git a/internal b/internal
index 063254c..a4e4632 160000
--- a/internal
+++ b/internal
@@ -1 +1 @@
-Subproject commit 063254c1cbbe2562ca28802b862f9d4162ff25f4
+Subproject commit a4e46326699b3f0b96d8071f454f5b762710a6eb
diff --git a/ios/chrome/app/BUILD.gn b/ios/chrome/app/BUILD.gn
index 33df3a9..a6733f9f 100644
--- a/ios/chrome/app/BUILD.gn
+++ b/ios/chrome/app/BUILD.gn
@@ -538,6 +538,10 @@
     "//ui/strings:ui_strings",
   ]
 
+  if (target_environment != "catalyst") {
+    deps += [ "//ios/chrome/browser/default_browser/model/default_status" ]
+  }
+
   if (ios_enable_credential_provider_extension) {
     deps += [ ":credential_provider_migrator_app_agent" ]
   }
diff --git a/ios/chrome/app/main_controller.mm b/ios/chrome/app/main_controller.mm
index dc40126c..166af03 100644
--- a/ios/chrome/app/main_controller.mm
+++ b/ios/chrome/app/main_controller.mm
@@ -159,6 +159,10 @@
 #import "ios/chrome/browser/rlz/rlz_tracker_delegate_impl.h"  // nogncheck
 #endif
 
+#if !BUILDFLAG(IS_IOS_MACCATALYST)
+#import "ios/chrome/browser/default_browser/model/default_status/default_status_helper.h"
+#endif  // !BUILDFLAG(IS_IOS_MACCATALYST)
+
 @interface MainController (ForUnloadProfileMarkedForDeletion)
 
 - (void)unloadProfileMarkedForDeletion:(std::string_view)profileName
@@ -218,6 +222,9 @@
 // Constant for deferred automatic download deletion.
 NSString* const kAutoDeletionFileRemoval = @"AutoDeletionFileRemoval";
 
+// Constant for deferred default browser status API check.
+NSString* const kDefaultBrowserStatusCheck = @"DefaultBrowserStatusCheck";
+
 // Adapted from chrome/browser/ui/browser_init.cc.
 void RegisterComponentsForUpdate() {
   component_updater::ComponentUpdateService* cus =
@@ -1429,6 +1436,7 @@
   [self scheduleEnterpriseManagedDeviceCheck];
   [self scheduleMemoryExperimentation];
   [self scheduleAutoDeletionFileRemoval];
+  [self scheduleDefaultBrowserStatusCheck];
 #if BUILDFLAG(IOS_ENABLE_SANDBOX_DUMP)
   [self scheduleDumpDocumentsStatistics];
 #endif  // BUILDFLAG(IOS_ENABLE_SANDBOX_DUMP)
@@ -1481,6 +1489,16 @@
                   }];
 }
 
+- (void)scheduleDefaultBrowserStatusCheck {
+#if !BUILDFLAG(IS_IOS_MACCATALYST)
+  [_appState.deferredRunner
+      enqueueBlockNamed:kDefaultBrowserStatusCheck
+                  block:^{
+                    default_status::TriggerDefaultStatusCheck();
+                  }];
+#endif  // !BUILDFLAG(IS_IOS_MACCATALYST)
+}
+
 #if BUILDFLAG(IOS_ENABLE_SANDBOX_DUMP)
 - (void)scheduleDumpDocumentsStatistics {
   if ([[NSUserDefaults standardUserDefaults]
diff --git a/ios/chrome/app/strings/ios_strings.grd b/ios/chrome/app/strings/ios_strings.grd
index 9c6c74988..a1bfe18 100644
--- a/ios/chrome/app/strings/ios_strings.grd
+++ b/ios/chrome/app/strings/ios_strings.grd
@@ -2178,6 +2178,15 @@
       <message name="IDS_IOS_EDIT_PASSWORD_DESCRIPTION" desc="Message inside confirmation alert when the user is trying to edit password [iOS only]" meaning="Telling user to ensure provided password matches password on the website.">
         Make sure the password you are saving matches your password for <ph name="WEBSITE">$1<ex>twitter.com</ex></ph>
       </message>
+      <message name="IDS_IOS_ENHANCED_CALENDAR_BOTTOM_SHEET_CANCEL_BUTTON" translateable="false" desc="The title for the enhanced calendar page of the AI prototyping menu [Length: unlimited].">
+        Cancel
+      </message>
+      <message name="IDS_IOS_ENHANCED_CALENDAR_BOTTOM_SHEET_SUBTITLE" translateable="false" desc="The subtitle for the Enhanced Calendar bottom sheet.">
+        Hang tight while we use AI to supercharge your calendar event using the web page's context.
+      </message>
+      <message name="IDS_IOS_ENHANCED_CALENDAR_BOTTOM_SHEET_TITLE" translateable="false" desc="The title for the Enhanced Calendar bottom sheet.">
+        Enhanced Calendar
+      </message>
       <message name="IDS_IOS_ENHANCED_SAFE_BROWSING_PROMO_INSTRUCTIONS_STEP2" desc="Step 2 of instructions telling the user how to enable Enhanced Safe Browsing.">
         Tap "Privacy and Security" and then "Safe Browsing"
       </message>
diff --git a/ios/chrome/browser/DEPS b/ios/chrome/browser/DEPS
index 093d252e..81bf915 100644
--- a/ios/chrome/browser/DEPS
+++ b/ios/chrome/browser/DEPS
@@ -175,6 +175,7 @@
   # Features should add explicit dependencies.
   "-ios/chrome/browser",
   "+ios/chrome/browser/profile/model",
+  "+ios/chrome/browser/default_browser/model",
 
   # Shared dependencies.
   "+ios/chrome/browser/shared/coordinator",
diff --git a/ios/chrome/browser/browser_container/ui_bundled/browser_container_coordinator.mm b/ios/chrome/browser/browser_container/ui_bundled/browser_container_coordinator.mm
index ada38f1..75bc2345 100644
--- a/ios/chrome/browser/browser_container/ui_bundled/browser_container_coordinator.mm
+++ b/ios/chrome/browser/browser_container/ui_bundled/browser_container_coordinator.mm
@@ -38,39 +38,41 @@
 #endif
 
 @interface BrowserContainerCoordinator () <EditMenuAlertDelegate>
-// Whether the coordinator is started.
-@property(nonatomic, assign, getter=isStarted) BOOL started;
+
 // Redefine property as readwrite.
 @property(nonatomic, strong, readwrite)
     BrowserContainerViewController* viewController;
-// The mediator used to configure the BrowserContainerConsumer.
-@property(nonatomic, strong) BrowserContainerMediator* mediator;
-// The mediator used for the Link to Text feature.
-@property(nonatomic, strong) LinkToTextMediator* linkToTextMediator;
-// The mediator used for the Partial Translate feature.
-@property(nonatomic, strong) PartialTranslateMediator* partialTranslateMediator;
-// The mediator used for the Search With feature.
-@property(nonatomic, strong) SearchWithMediator* searchWithMediator;
 // The handler for the edit menu.
 @property(nonatomic, strong) BrowserEditMenuHandler* browserEditMenuHandler;
-// The overlay container coordinator for OverlayModality::kWebContentArea.
-@property(nonatomic, strong)
-    OverlayContainerCoordinator* webContentAreaOverlayContainerCoordinator;
-// The coodinator that manages ScreenTime.
-@property(nonatomic, strong) ChromeCoordinator* screenTimeCoordinator;
-// Coordinator used to present alerts to the user.
-@property(nonatomic, strong) AlertCoordinator* alertCoordinator;
+
 @end
 
-@implementation BrowserContainerCoordinator
+@implementation BrowserContainerCoordinator {
+  // Whether the coordinator is started.
+  BOOL _started;
+  // Coordinator used to present alerts to the user.
+  AlertCoordinator* _alertCoordinator;
+  // The mediator used for the Search With feature.
+  SearchWithMediator* _searchWithMediator;
+  // The coodinator that manages ScreenTime.
+  ChromeCoordinator* _screenTimeCoordinator;
+  // The overlay container coordinator for OverlayModality::kWebContentArea.
+  OverlayContainerCoordinator* _webContentAreaOverlayContainerCoordinator;
+  // The mediator used for the Partial Translate feature.
+  PartialTranslateMediator* _partialTranslateMediator;
+  // The mediator used to configure the BrowserContainerConsumer.
+  BrowserContainerMediator* _mediator;
+  // The mediator used for the Link to Text feature.
+  LinkToTextMediator* _linkToTextMediator;
+}
 
 #pragma mark - ChromeCoordinator
 
 - (void)start {
-  if (self.started) {
+  if (_started) {
     return;
   }
-  self.started = YES;
+  _started = YES;
   DCHECK(self.browser);
   DCHECK(!_viewController);
   Browser* browser = self.browser;
@@ -78,63 +80,62 @@
   ProfileIOS* profile = browser->GetProfile();
   BOOL incognito = profile->IsOffTheRecord();
   self.viewController = [[BrowserContainerViewController alloc] init];
-  self.webContentAreaOverlayContainerCoordinator =
+  _webContentAreaOverlayContainerCoordinator =
       [[OverlayContainerCoordinator alloc]
           initWithBaseViewController:self.viewController
                              browser:browser
                             modality:OverlayModality::kWebContentArea];
 
-  self.linkToTextMediator =
+  _linkToTextMediator =
       [[LinkToTextMediator alloc] initWithWebStateList:webStateList];
-  self.linkToTextMediator.alertDelegate = self;
-  self.linkToTextMediator.activityServiceHandler = HandlerForProtocol(
+  _linkToTextMediator.alertDelegate = self;
+  _linkToTextMediator.activityServiceHandler = HandlerForProtocol(
       browser->GetCommandDispatcher(), ActivityServiceCommands);
 
   self.browserEditMenuHandler = [[BrowserEditMenuHandler alloc] init];
   self.viewController.browserEditMenuHandler = self.browserEditMenuHandler;
-  self.browserEditMenuHandler.linkToTextDelegate = self.linkToTextMediator;
-  self.viewController.linkToTextDelegate = self.linkToTextMediator;
+  self.browserEditMenuHandler.linkToTextDelegate = _linkToTextMediator;
+  self.viewController.linkToTextDelegate = _linkToTextMediator;
 
   PrefService* prefService = profile->GetOriginalProfile()->GetPrefs();
   FullscreenController* fullscreenController =
       FullscreenController::FromBrowser(self.browser);
 
-  self.partialTranslateMediator = [[PartialTranslateMediator alloc]
+  _partialTranslateMediator = [[PartialTranslateMediator alloc]
         initWithWebStateList:webStateList
       withBaseViewController:self.viewController
                  prefService:prefService
         fullscreenController:fullscreenController
                    incognito:incognito];
-  self.partialTranslateMediator.alertDelegate = self;
+  _partialTranslateMediator.alertDelegate = self;
   CommandDispatcher* dispatcher = browser->GetCommandDispatcher();
   id<BrowserCoordinatorCommands> browserCommandsHandler =
       HandlerForProtocol(dispatcher, BrowserCoordinatorCommands);
-  self.partialTranslateMediator.browserHandler = browserCommandsHandler;
+  _partialTranslateMediator.browserHandler = browserCommandsHandler;
   self.browserEditMenuHandler.partialTranslateDelegate =
-      self.partialTranslateMediator;
+      _partialTranslateMediator;
 
   TemplateURLService* templateURLService =
       ios::TemplateURLServiceFactory::GetForProfile(profile);
-  self.searchWithMediator =
+  _searchWithMediator =
       [[SearchWithMediator alloc] initWithWebStateList:webStateList
                                     templateURLService:templateURLService
                                              incognito:incognito];
   id<ApplicationCommands> applicationCommandsHandler =
       HandlerForProtocol(dispatcher, ApplicationCommands);
-  self.searchWithMediator.applicationCommandHandler =
-      applicationCommandsHandler;
-  self.browserEditMenuHandler.searchWithDelegate = self.searchWithMediator;
+  _searchWithMediator.applicationCommandHandler = applicationCommandsHandler;
+  self.browserEditMenuHandler.searchWithDelegate = _searchWithMediator;
 
-  [self.webContentAreaOverlayContainerCoordinator start];
+  [_webContentAreaOverlayContainerCoordinator start];
   self.viewController.webContentsOverlayContainerViewController =
-      self.webContentAreaOverlayContainerCoordinator.viewController;
+      _webContentAreaOverlayContainerCoordinator.viewController;
   OverlayPresenter* overlayPresenter =
       OverlayPresenter::FromBrowser(browser, OverlayModality::kWebContentArea);
-  self.mediator =
+  _mediator =
       [[BrowserContainerMediator alloc] initWithWebStateList:webStateList
                               webContentAreaOverlayPresenter:overlayPresenter];
 
-  self.mediator.consumer = self.viewController;
+  _mediator.consumer = self.viewController;
 
   [self setUpScreenTimeIfEnabled];
 
@@ -142,20 +143,20 @@
 }
 
 - (void)stop {
-  if (!self.started) {
+  if (!_started) {
     return;
   }
   [self dismissAlertCoordinator];
-  self.started = NO;
-  [self.webContentAreaOverlayContainerCoordinator stop];
-  [self.screenTimeCoordinator stop];
-  [self.partialTranslateMediator shutdown];
-  [self.searchWithMediator shutdown];
+  _started = NO;
+  [_webContentAreaOverlayContainerCoordinator stop];
+  [_screenTimeCoordinator stop];
+  [_partialTranslateMediator shutdown];
+  [_searchWithMediator shutdown];
   self.viewController = nil;
-  self.mediator = nil;
-  self.linkToTextMediator = nil;
-  self.partialTranslateMediator = nil;
-  self.searchWithMediator = nil;
+  _mediator = nil;
+  _linkToTextMediator = nil;
+  _partialTranslateMediator = nil;
+  _searchWithMediator = nil;
   [super stop];
 }
 
@@ -168,23 +169,23 @@
 - (void)showAlertWithTitle:(NSString*)title
                    message:(NSString*)message
                    actions:(NSArray<EditMenuAlertDelegateAction*>*)actions {
-  self.alertCoordinator =
+  _alertCoordinator =
       [[AlertCoordinator alloc] initWithBaseViewController:self.viewController
                                                    browser:self.browser
                                                      title:title
                                                    message:message];
   __weak BrowserContainerCoordinator* weakSelf = self;
   for (EditMenuAlertDelegateAction* action in actions) {
-    [self.alertCoordinator addItemWithTitle:action.title
-                                     action:^{
-                                       action.action();
-                                       [weakSelf dismissAlertCoordinator];
-                                     }
-                                      style:action.style
-                                  preferred:action.preferred
-                                    enabled:YES];
+    [_alertCoordinator addItemWithTitle:action.title
+                                 action:^{
+                                   action.action();
+                                   [weakSelf dismissAlertCoordinator];
+                                 }
+                                  style:action.style
+                              preferred:action.preferred
+                                enabled:YES];
   }
-  [self.alertCoordinator start];
+  [_alertCoordinator start];
 }
 
 #pragma mark - Private methods
@@ -203,14 +204,14 @@
   [screenTimeCoordinator start];
   self.viewController.screenTimeViewController =
       screenTimeCoordinator.viewController;
-  self.screenTimeCoordinator = screenTimeCoordinator;
+  _screenTimeCoordinator = screenTimeCoordinator;
 
 #endif
 }
 
 - (void)dismissAlertCoordinator {
-  [self.alertCoordinator stop];
-  self.alertCoordinator = nil;
+  [_alertCoordinator stop];
+  _alertCoordinator = nil;
 }
 
 @end
diff --git a/ios/chrome/browser/browser_view/ui_bundled/BUILD.gn b/ios/chrome/browser/browser_view/ui_bundled/BUILD.gn
index 13ebc6b..26153ab 100644
--- a/ios/chrome/browser/browser_view/ui_bundled/BUILD.gn
+++ b/ios/chrome/browser/browser_view/ui_bundled/BUILD.gn
@@ -131,6 +131,7 @@
     "//ios/chrome/browser/incognito_reauth/ui_bundled:ui",
     "//ios/chrome/browser/infobars/model",
     "//ios/chrome/browser/infobars/ui_bundled:public",
+    "//ios/chrome/browser/intelligence/enhanced_calendar/coordinator",
     "//ios/chrome/browser/intents/model:model_donation_helper",
     "//ios/chrome/browser/itunes_urls/model",
     "//ios/chrome/browser/keyboard/ui_bundled",
diff --git a/ios/chrome/browser/browser_view/ui_bundled/DEPS b/ios/chrome/browser/browser_view/ui_bundled/DEPS
index d7b96597..290f553f 100644
--- a/ios/chrome/browser/browser_view/ui_bundled/DEPS
+++ b/ios/chrome/browser/browser_view/ui_bundled/DEPS
@@ -37,6 +37,7 @@
   "+ios/chrome/browser/google_one/coordinator",
   "+ios/chrome/browser/incognito_reauth/ui_bundled",
   "+ios/chrome/browser/infobars/model",
+  "+ios/chrome/browser/intelligence/enhanced_calendar/coordinator/enhanced_calendar_coordinator.h",
   "+ios/chrome/browser/intents/model/intents_donation_helper.h",
   "+ios/chrome/browser/bubble/model/tab_based_iph_browser_agent.h",
   "+ios/chrome/browser/itunes_urls/model/itunes_urls_handler_tab_helper.h",
diff --git a/ios/chrome/browser/browser_view/ui_bundled/browser_coordinator.mm b/ios/chrome/browser/browser_view/ui_bundled/browser_coordinator.mm
index 05620b5..fb835c67 100644
--- a/ios/chrome/browser/browser_view/ui_bundled/browser_coordinator.mm
+++ b/ios/chrome/browser/browser_view/ui_bundled/browser_coordinator.mm
@@ -117,6 +117,7 @@
 #import "ios/chrome/browser/incognito_reauth/ui_bundled/incognito_reauth_scene_agent.h"
 #import "ios/chrome/browser/infobars/model/infobar_ios.h"
 #import "ios/chrome/browser/infobars/model/infobar_manager_impl.h"
+#import "ios/chrome/browser/intelligence/enhanced_calendar/coordinator/enhanced_calendar_coordinator.h"
 #import "ios/chrome/browser/intents/model/intents_donation_helper.h"
 #import "ios/chrome/browser/lens/ui_bundled/lens_coordinator.h"
 #import "ios/chrome/browser/lens_overlay/coordinator/lens_overlay_availability.h"
@@ -197,6 +198,7 @@
 #import "ios/chrome/browser/shared/public/commands/contextual_sheet_commands.h"
 #import "ios/chrome/browser/shared/public/commands/country_code_picker_commands.h"
 #import "ios/chrome/browser/shared/public/commands/drive_file_picker_commands.h"
+#import "ios/chrome/browser/shared/public/commands/enhanced_calendar_commands.h"
 #import "ios/chrome/browser/shared/public/commands/feed_commands.h"
 #import "ios/chrome/browser/shared/public/commands/find_in_page_commands.h"
 #import "ios/chrome/browser/shared/public/commands/google_one_commands.h"
@@ -328,6 +330,7 @@
     DefaultBrowserGenericPromoCommands,
     DefaultPromoNonModalPresentationDelegate,
     DriveFilePickerCommands,
+    EnhancedCalendarCommands,
     EditMenuBuilder,
     EnterprisePromptCoordinatorDelegate,
     FormInputAccessoryCoordinatorNavigator,
@@ -649,6 +652,9 @@
   LensPromoCoordinator* _lensPromoCoordinator;
   EnhancedSafeBrowsingPromoCoordinator* _enhancedSafeBrowsingPromoCoordinator;
   AutoDeletionCoordinator* _autoDeletionCoordinator;
+
+  // The coordinator for the Enhanced Calendar feature UI (bottom sheet).
+  EnhancedCalendarCoordinator* _enhancedCalendarCoordinator;
 }
 
 #pragma mark - ChromeCoordinator
@@ -1047,6 +1053,7 @@
     @protocol(ContextualSheetCommands),
     @protocol(DefaultBrowserPromoNonModalCommands),
     @protocol(DriveFilePickerCommands),
+    @protocol(EnhancedCalendarCommands),
     @protocol(FeedCommands),
     @protocol(PromosManagerCommands),
     @protocol(FindInPageCommands),
@@ -1638,6 +1645,9 @@
   [_quickDeleteCoordinator stop];
   _quickDeleteCoordinator = nil;
 
+  [_enhancedCalendarCoordinator stop];
+  _enhancedCalendarCoordinator = nil;
+
   [self hideDriveFilePicker];
   [self hideContextualSheet];
   [self dismissEditAddressBottomSheet];
@@ -2526,6 +2536,22 @@
   [_driveFilePickerCoordinator setSelectedIdentity:selectedIdentity];
 }
 
+#pragma mark - EnhancedCalendarCommands
+
+- (void)showEnhancedCalendarBottomSheetWithIntegrationProvider:
+    (ios::provider::AddToCalendarIntegrationProvider)integrationProvider {
+  _enhancedCalendarCoordinator = [[EnhancedCalendarCoordinator alloc]
+      initWithBaseViewController:self.viewController
+                         browser:self.browser
+             integrationProvider:integrationProvider];
+  [_enhancedCalendarCoordinator start];
+}
+
+- (void)hideEnhancedCalendarBottomSheet {
+  [_enhancedCalendarCoordinator stop];
+  _enhancedCalendarCoordinator = nil;
+}
+
 #pragma mark - FeedCommands
 
 - (void)showFirstFollowUIForWebSite:(FollowedWebSite*)followedWebSite {
diff --git a/ios/chrome/browser/content_suggestions/ui_bundled/shop_card/BUILD.gn b/ios/chrome/browser/content_suggestions/ui_bundled/shop_card/BUILD.gn
index 393f694a..e7e539f 100644
--- a/ios/chrome/browser/content_suggestions/ui_bundled/shop_card/BUILD.gn
+++ b/ios/chrome/browser/content_suggestions/ui_bundled/shop_card/BUILD.gn
@@ -36,6 +36,7 @@
     "//ios/chrome/browser/price_notifications/ui_bundled/cells:cells",
     "//ios/chrome/browser/shared/model/application_context:application_context",
     "//ios/chrome/browser/shared/model/prefs:pref_names",
+    "//ios/chrome/browser/shared/ui/symbols",
     "//ios/chrome/browser/shared/ui/util:util",
     "//ios/chrome/common/ui/colors:colors",
     "//ios/chrome/common/ui/elements:elements",
diff --git a/ios/chrome/browser/content_suggestions/ui_bundled/shop_card/shop_card_mediator.mm b/ios/chrome/browser/content_suggestions/ui_bundled/shop_card/shop_card_mediator.mm
index 7b157192..3d31c56 100644
--- a/ios/chrome/browser/content_suggestions/ui_bundled/shop_card/shop_card_mediator.mm
+++ b/ios/chrome/browser/content_suggestions/ui_bundled/shop_card/shop_card_mediator.mm
@@ -231,17 +231,16 @@
                                 length:imageData.size()];
   if (data) {
     self->_shopCardItem.shopCardData.productImage = data;
-
-    // Fetch favicon if product image available.
-    // TODO(crbug.com/392970898): add support for no product image case
-    __weak ShopCardMediator* weakSelf = self;
-    _faviconLoader->FaviconForPageUrl(
-        productUrl, kDesiredSmallFaviconSizePt, kMinFaviconSizePt,
-        /*fallback_to_google_server=*/false, ^(FaviconAttributes* attributes) {
-          [weakSelf onFaviconReceived:attributes];
-        });
   }
   [self.delegate insertShopCard];
+
+  // Fetch favicon, regardless of whether product image available.
+  __weak ShopCardMediator* weakSelf = self;
+  _faviconLoader->FaviconForPageUrl(
+      productUrl, kDesiredSmallFaviconSizePt, kMinFaviconSizePt,
+      /*fallback_to_google_server=*/false, ^(FaviconAttributes* attributes) {
+        [weakSelf onFaviconReceived:attributes];
+      });
 }
 
 - (void)onFaviconReceived:(FaviconAttributes*)attributes {
diff --git a/ios/chrome/browser/content_suggestions/ui_bundled/shop_card/shop_card_view.mm b/ios/chrome/browser/content_suggestions/ui_bundled/shop_card/shop_card_view.mm
index 017a239..5397c3ad2 100644
--- a/ios/chrome/browser/content_suggestions/ui_bundled/shop_card/shop_card_view.mm
+++ b/ios/chrome/browser/content_suggestions/ui_bundled/shop_card/shop_card_view.mm
@@ -13,6 +13,7 @@
 #import "ios/chrome/browser/content_suggestions/ui_bundled/shop_card/shop_card_item.h"
 #import "ios/chrome/browser/content_suggestions/ui_bundled/shop_card/shop_card_mediator.h"
 #import "ios/chrome/browser/price_notifications/ui_bundled/cells/price_notifications_price_chip_view.h"
+#import "ios/chrome/browser/shared/ui/symbols/symbols.h"
 #import "ios/chrome/browser/shared/ui/util/uikit_ui_util.h"
 #import "ios/chrome/common/ui/colors/semantic_color_names.h"
 #import "ios/chrome/common/ui/elements/gradient_view.h"
@@ -23,19 +24,23 @@
 namespace {
 const CGFloat kHorizontalStackSpacing = 16.0f;
 const CGFloat kVerticalStackSpacing = 6.0f;
-const CGFloat kProductImageWidthHeight = 72.0;
+const CGFloat kCenterSymbolSize = 20.0;
+const CGFloat kShopCardProductImageWidthHeight = 72.0;
+const CGFloat kProductImageEmptyWidthHeight = 56.0;
 const CGFloat kFaviconImageWidthHeight = 24.0;
+const CGFloat kFaviconDefaultImageWidthHeight = 22.0;
+const CGFloat kFaviconCenterImageWidthHeight = 30.0;
 const CGFloat kProductCornerRadius = 12.0;
 const CGFloat kFaviconImageContainerTrailingMargin = -5.0;
 const CGFloat kFaviconCornerRadius = 4.0;
 const CGFloat kFaviconImageContainerTrailingCornerRadius = 8.0;
 
-// Alpha for top of gradient overlay.
-// TODO(crbug.com/392970898): add support for no product image case
-const CGFloat kGradientOverlayTopAlpha = 0.0;
+const CGFloat kFaviconContainerShadowVerticalOffset = 4.0;
+const CGFloat kFaviconShadowOpacity = 0.12;
+const CGFloat kFaviconShadowRadius = 11.0;
 
-// Alpha for bottom of gradient overlay.
-// TODO(crbug.com/392970898): add support for no product image case
+// Alpha for top and bottom of gradient overlay.
+const CGFloat kGradientOverlayTopAlpha = 0.0;
 const CGFloat kGradientOverlayBottomAlpha = 0.14;
 
 }  // namespace
@@ -56,9 +61,12 @@
 
   // Left side of ShopCard, holds product image with favicon.
   UIView* _productAndFaviconContainer;
-  UIView* _faviconImageContainer;
-  UIImageView* _productImage;
-  UIImageView* _faviconImage;
+  UIView* _faviconImageContainer;  // container, or grey if using default
+                                   // favicon globe image
+  UIImageView*
+      _productImage;  // the product image, or a placeholder gray if no product
+  UIImageView*
+      _faviconImage;  // the favicon image, or a placeholder globe if no favicon
   UIView* _gradientOverlay;
 
   PriceNotificationsPriceChipView* _priceNotificationsChip;
@@ -87,86 +95,99 @@
 
 #pragma mark - ShopCardFaviconConsumer
 - (void)faviconCompleted:(UIImage*)faviconImage {
-  [self populateFaviconImageAndContainer:faviconImage];
-  [self addFaviconToViewIfPresent];
-  [self addFaviconImageAndContainerConstraintsIfPresent];
+  _item.shopCardData.faviconImage = faviconImage;
+  [self configureViewForTrackedProducts:_item];
 }
 
 - (void)configureViewForTrackedProducts:(ShopCardItem*)configItem {
-  _titleLabel = [[UILabel alloc] init];
-  _titleLabel.textColor = [UIColor colorNamed:kTextPrimaryColor];
-  _titleLabel.numberOfLines = 1;
-  _titleLabel.lineBreakMode = NSLineBreakByTruncatingTail;
-  _titleLabel.font =
-      CreateDynamicFont(UIFontTextStyleFootnote, UIFontWeightSemibold, self);
-  _titleLabel.adjustsFontForContentSizeCategory = YES;
-  _titleLabel.text = _item.shopCardData.productTitle;
+  [self populateTitleLabel];
+  [self populateUrlLabel];
+  [self populatePriceNotificationChip];
 
-  _urlLabel = [[UILabel alloc] init];
-  _urlLabel.text = [self hostnameFromGURL:_item.shopCardData.productURL];
-  _urlLabel.numberOfLines = 1;
-  _urlLabel.lineBreakMode = NSLineBreakByTruncatingTail;
-  _urlLabel.font = [UIFont preferredFontForTextStyle:UIFontTextStyleFootnote];
-  _urlLabel.adjustsFontForContentSizeCategory = YES;
-  _urlLabel.textColor = [UIColor colorNamed:kTextSecondaryColor];
+  // Case 1: both are present, favicon on bottom right.
+  if (_item.shopCardData.productImage && _item.shopCardData.faviconImage) {
+    // Styling
+    [self addProductImageAndOverlay];
+    [self addFaviconImageAndContainer:_item.shopCardData.faviconImage];
+    _faviconImageContainer.layer.mask =
+        [self faviconMaskWithRadius:kFaviconImageContainerTrailingCornerRadius
+                   imageHeightWidth:kFaviconImageWidthHeight];
 
-  _priceNotificationsChip = [[PriceNotificationsPriceChipView alloc] init];
-  _priceNotificationsChip.previousPriceFont =
-      CreateDynamicFont(UIFontTextStyleFootnote, UIFontWeightMedium);
-  _priceNotificationsChip.currentPriceFont =
-      CreateDynamicFont(UIFontTextStyleFootnote, UIFontWeightMedium);
-  _priceNotificationsChip.strikeoutPreviousPrice = YES;
-  [_priceNotificationsChip
-       setPriceDrop:_item.shopCardData.priceDrop->current_price
-      previousPrice:_item.shopCardData.priceDrop->previous_price];
+    // Hierarchy
+    [_productAndFaviconContainer addSubview:_productImage];
+    [_productImage addSubview:_gradientOverlay];
+    [_productAndFaviconContainer addSubview:_faviconImageContainer];
+    [_faviconImageContainer addSubview:_faviconImage];
 
-  _productAndFaviconContainer = [[UIView alloc] init];
-  _productImage = [[UIImageView alloc] init];
-  UIImage* retrievedProductImage =
-      [UIImage imageWithData:_item.shopCardData.productImage
-                       scale:[UIScreen mainScreen].scale];
-
-  _productImage.image = retrievedProductImage;
-  _productImage.contentMode = UIViewContentModeScaleAspectFill;
-  _productImage.translatesAutoresizingMaskIntoConstraints = NO;
-
-  _productImage.layer.borderWidth = 0;
-  _productImage.layer.cornerRadius = kProductCornerRadius;
-  _productImage.layer.masksToBounds = YES;
-  _productImage.backgroundColor = UIColor.whiteColor;
-
-  _gradientOverlay = [[GradientView alloc]
-      initWithTopColor:[[UIColor blackColor]
-                           colorWithAlphaComponent:kGradientOverlayTopAlpha]
-           bottomColor:[[UIColor blackColor] colorWithAlphaComponent:
-                                                 kGradientOverlayBottomAlpha]];
-  _gradientOverlay.layer.masksToBounds = YES;
-  _gradientOverlay.translatesAutoresizingMaskIntoConstraints = NO;
-
-  if (configItem.shopCardData && configItem.shopCardData.faviconImage) {
-    [self
-        populateFaviconImageAndContainer:configItem.shopCardData.faviconImage];
+    // Constraints
+    [self addWidthConstraintsForProductImage:kShopCardProductImageWidthHeight];
+    [_productAndFaviconContainer bringSubviewToFront:_gradientOverlay];
+    AddSameConstraints(_gradientOverlay, _productImage);
+    AddSameConstraints(_productImage, _productAndFaviconContainer);
+    [self addWidthConstraintsForFaviconImage:kFaviconImageWidthHeight];
+    AddSameConstraints(_faviconImage, _faviconImageContainer);
+    [self addConstraintsForFaviconContainerToTrailingEdge];
   }
 
-  // Define hierarchy
-  [_productAndFaviconContainer addSubview:_productImage];
-  [_productImage addSubview:_gradientOverlay];
+  // Case 2: product image and overlay only, favicon absent.
+  if (_item.shopCardData.productImage && !_item.shopCardData.faviconImage) {
+    // Styling
+    [self addProductImageAndOverlay];
 
-  [self addFaviconToViewIfPresent];
+    // Hierarchy
+    [_productAndFaviconContainer addSubview:_productImage];
+    [_productImage addSubview:_gradientOverlay];
 
-  // Add constraints after the hierarchy is defined
-  [_productAndFaviconContainer bringSubviewToFront:_gradientOverlay];
-  [self addConstraintsForProductImage];
-  [NSLayoutConstraint activateConstraints:@[
-    [_gradientOverlay.heightAnchor
-        constraintEqualToConstant:kProductImageWidthHeight],
-    [_gradientOverlay.widthAnchor
-        constraintEqualToAnchor:_gradientOverlay.heightAnchor]
-  ]];
-  AddSameConstraints(_gradientOverlay, _productImage);
-  AddSameConstraints(_productImage, _productAndFaviconContainer);
+    // Constraints
+    [self addWidthConstraintsForProductImage:kShopCardProductImageWidthHeight];
+    [self addGradientOverlayConstraints];
+    [_productAndFaviconContainer bringSubviewToFront:_gradientOverlay];
+    AddSameConstraints(_gradientOverlay, _productImage);
+    AddSameConstraints(_productImage, _productAndFaviconContainer);
+  }
 
-  [self addFaviconImageAndContainerConstraintsIfPresent];
+  // Case 3: no product only favicon in the center. product image is empty -
+  // grey.
+  if (!_item.shopCardData.productImage && _item.shopCardData.faviconImage) {
+    // Styling
+    [self addProductImageEmptyGray];
+    [self addFaviconImageAndContainer:_item.shopCardData.faviconImage];
+    [self addShadowForFaviconContainer];
+
+    // Hierarchy
+    [_productAndFaviconContainer addSubview:_productImage];
+    [_productAndFaviconContainer addSubview:_faviconImageContainer];
+    [_faviconImageContainer addSubview:_faviconImage];
+
+    // Constraints
+    [self addWidthConstraintsForProductImage:kProductImageEmptyWidthHeight];
+    [self addWidthConstraintsForFaviconImage:kFaviconCenterImageWidthHeight];
+    AddSameConstraints(_productImage, _productAndFaviconContainer);
+    AddSameConstraints(_faviconImage, _faviconImageContainer);
+    [self addConstraintsForFaviconContainerToProductContainerCenter];
+  }
+
+  // Case 4: no favicon or product. globe in the center, product image is grey.
+  if (!_item.shopCardData.productImage && !_item.shopCardData.faviconImage) {
+    // Styling
+    [self addProductImageEmptyGray];
+    [self addFaviconImageAndContainer:[self makeDefaultFaviconUIImage]];
+    [self addFaviconImageContainerColorForGlobe];
+    [self addShadowForFaviconContainer];
+
+    // Hierarchy
+    [_productAndFaviconContainer addSubview:_productImage];
+    [_productAndFaviconContainer addSubview:_faviconImageContainer];
+    [_faviconImageContainer addSubview:_faviconImage];
+
+    // Constraints
+    [self addWidthConstraintsForProductImage:kProductImageEmptyWidthHeight];
+    [self addWidthConstraintsForFaviconImage:kFaviconDefaultImageWidthHeight];
+    [self addWidthConstraintsForFaviconContainerDefaultGlobe];
+    AddSameConstraints(_productImage, _productAndFaviconContainer);
+    [self addConstraintsForFaviconImageToFaviconImageContainerCenter];
+    [self addConstraintsForFaviconContainerToProductContainerCenter];
+  }
 
   // Lay out text stack
   _textStack = [[UIStackView alloc] initWithArrangedSubviews:@[
@@ -193,21 +214,50 @@
               URL));
 }
 
-- (void)addConstraintsForProductImage {
-  if (_productImage) {
-    [NSLayoutConstraint activateConstraints:@[
-      [_productImage.heightAnchor
-          constraintEqualToConstant:kProductImageWidthHeight],
-      [_productImage.widthAnchor
-          constraintEqualToAnchor:_productImage.heightAnchor]
-    ]];
-  }
+- (void)populateTitleLabel {
+  _titleLabel = [[UILabel alloc] init];
+  _titleLabel.textColor = [UIColor colorNamed:kTextPrimaryColor];
+  _titleLabel.numberOfLines = 1;
+  _titleLabel.lineBreakMode = NSLineBreakByTruncatingTail;
+  _titleLabel.font =
+      CreateDynamicFont(UIFontTextStyleFootnote, UIFontWeightSemibold, self);
+  _titleLabel.adjustsFontForContentSizeCategory = YES;
+  _titleLabel.text = _item.shopCardData.productTitle;
 }
 
-- (void)addConstraintsForFaviconImage {
+- (void)populateUrlLabel {
+  _urlLabel = [[UILabel alloc] init];
+  _urlLabel.text = [self hostnameFromGURL:_item.shopCardData.productURL];
+  _urlLabel.numberOfLines = 1;
+  _urlLabel.lineBreakMode = NSLineBreakByTruncatingTail;
+  _urlLabel.font = [UIFont preferredFontForTextStyle:UIFontTextStyleFootnote];
+  _urlLabel.adjustsFontForContentSizeCategory = YES;
+  _urlLabel.textColor = [UIColor colorNamed:kTextSecondaryColor];
+}
+
+- (void)populatePriceNotificationChip {
+  _priceNotificationsChip = [[PriceNotificationsPriceChipView alloc] init];
+  _priceNotificationsChip.previousPriceFont =
+      CreateDynamicFont(UIFontTextStyleFootnote, UIFontWeightMedium);
+  _priceNotificationsChip.currentPriceFont =
+      CreateDynamicFont(UIFontTextStyleFootnote, UIFontWeightMedium);
+  _priceNotificationsChip.strikeoutPreviousPrice = YES;
+  [_priceNotificationsChip
+       setPriceDrop:_item.shopCardData.priceDrop->current_price
+      previousPrice:_item.shopCardData.priceDrop->previous_price];
+}
+
+- (void)addWidthConstraintsForProductImage:(const CGFloat)width {
   [NSLayoutConstraint activateConstraints:@[
-    [_faviconImage.heightAnchor
-        constraintEqualToConstant:kFaviconImageWidthHeight],
+    [_productImage.heightAnchor constraintEqualToConstant:width],
+    [_productImage.widthAnchor
+        constraintEqualToAnchor:_productImage.heightAnchor]
+  ]];
+}
+
+- (void)addWidthConstraintsForFaviconImage:(const CGFloat)faviconWidth {
+  [NSLayoutConstraint activateConstraints:@[
+    [_faviconImage.heightAnchor constraintEqualToConstant:faviconWidth],
     [_faviconImage.widthAnchor
         constraintEqualToAnchor:_faviconImage.heightAnchor]
   ]];
@@ -224,6 +274,15 @@
   ]];
 }
 
+- (void)addConstraintsForFaviconContainerToProductContainerCenter {
+  [NSLayoutConstraint activateConstraints:@[
+    [_faviconImageContainer.centerXAnchor
+        constraintEqualToAnchor:_productAndFaviconContainer.centerXAnchor],
+    [_faviconImageContainer.centerYAnchor
+        constraintEqualToAnchor:_productAndFaviconContainer.centerYAnchor]
+  ]];
+}
+
 // Create a mask with radius applied to the trailing corner
 - (CAShapeLayer*)faviconMaskWithRadius:(CGFloat)trailingCornerRadius
                       imageHeightWidth:(CGFloat)imageHeightWidth {
@@ -252,7 +311,11 @@
   [self.commandHandler openShopCardItem:_item];
 }
 
-- (void)populateFaviconImageAndContainer:(UIImage*)faviconImage {
+- (UIImage*)makeDefaultFaviconUIImage {
+  return DefaultSymbolWithPointSize(kGlobeAmericasSymbol, kCenterSymbolSize);
+}
+
+- (void)addFaviconImageAndContainer:(UIImage*)faviconImage {
   _faviconImageContainer = [[UIView alloc] init];
   _faviconImage = [[UIImageView alloc] init];
   _faviconImage.image = faviconImage;
@@ -263,27 +326,85 @@
   _faviconImage.layer.masksToBounds = YES;
   _faviconImageContainer.layer.cornerRadius = kFaviconCornerRadius;
   _faviconImageContainer.layer.masksToBounds = YES;
-
-  _faviconImageContainer.layer.mask =
-      [self faviconMaskWithRadius:kFaviconImageContainerTrailingCornerRadius
-                 imageHeightWidth:kFaviconImageWidthHeight];
+  _faviconImageContainer.translatesAutoresizingMaskIntoConstraints = NO;
 }
 
-- (void)addFaviconToViewIfPresent {
-  if (_faviconImage) {
-    [_productAndFaviconContainer addSubview:_faviconImageContainer];
-    [_faviconImageContainer addSubview:_faviconImage];
-  }
+- (void)addShadowForFaviconContainer {
+  _faviconImageContainer.layer.shadowColor = [UIColor blackColor].CGColor;
+  _faviconImageContainer.layer.shadowOffset =
+      CGSizeMake(0, kFaviconContainerShadowVerticalOffset /*4*/);
+  _faviconImageContainer.layer.shadowOpacity = kFaviconShadowOpacity;  // 12%
+  _faviconImageContainer.layer.shadowRadius = kFaviconShadowRadius;    // 11
 }
 
-- (void)addFaviconImageAndContainerConstraintsIfPresent {
-  if (_faviconImage) {
-    [self addConstraintsForFaviconImage];
-    AddSameConstraints(_faviconImage, _faviconImageContainer);
-    // Place the favicon to trailing-bottom of product image
-    _faviconImageContainer.translatesAutoresizingMaskIntoConstraints = NO;
-    [self addConstraintsForFaviconContainerToTrailingEdge];
-  }
+- (void)addFaviconImageContainerColorForGlobe {
+  _faviconImageContainer.backgroundColor = [UIColor colorNamed:kBlue500Color];
+  // Color inside the globe icon
+  _faviconImageContainer.tintColor = UIColor.whiteColor;
+}
+
+- (void)addConstraintsForFaviconImageToFaviconImageContainerCenter {
+  [NSLayoutConstraint activateConstraints:@[
+    [_faviconImage.centerXAnchor
+        constraintEqualToAnchor:_faviconImageContainer.centerXAnchor],
+    [_faviconImage.centerYAnchor
+        constraintEqualToAnchor:_faviconImageContainer.centerYAnchor],
+  ]];
+}
+
+- (void)addWidthConstraintsForFaviconContainerDefaultGlobe {
+  [NSLayoutConstraint activateConstraints:@[
+    [_faviconImageContainer.widthAnchor
+        constraintEqualToConstant:kFaviconCenterImageWidthHeight],
+    [_faviconImageContainer.heightAnchor
+        constraintEqualToAnchor:_faviconImageContainer.widthAnchor],
+  ]];
+}
+
+- (void)addProductImageEmptyGray {
+  _productAndFaviconContainer = [[UIView alloc] init];
+  _productImage = [[UIImageView alloc] init];
+  _productImage.backgroundColor =
+      [UIColor colorNamed:kTertiaryBackgroundColor];
+  _productImage.contentMode = UIViewContentModeScaleAspectFill;
+  _productImage.translatesAutoresizingMaskIntoConstraints = NO;
+  _productImage.layer.borderWidth = 0;
+  _productImage.layer.cornerRadius = kProductCornerRadius;
+  _productImage.layer.masksToBounds = YES;
+}
+
+- (void)addProductImageAndOverlay {
+  _productAndFaviconContainer = [[UIView alloc] init];
+  _productImage = [[UIImageView alloc] init];
+
+  UIImage* retrievedProductImage =
+      [UIImage imageWithData:_item.shopCardData.productImage
+                       scale:[UIScreen mainScreen].scale];
+  _productImage.image = retrievedProductImage;
+  _productImage.backgroundColor = UIColor.whiteColor;
+  _gradientOverlay = [[GradientView alloc]
+      initWithTopColor:[[UIColor blackColor]
+                           colorWithAlphaComponent:kGradientOverlayTopAlpha]
+           bottomColor:[[UIColor blackColor] colorWithAlphaComponent:
+                                                 kGradientOverlayBottomAlpha]];
+  _gradientOverlay.layer.masksToBounds = YES;
+  _gradientOverlay.layer.zPosition = 1;
+  _gradientOverlay.translatesAutoresizingMaskIntoConstraints = NO;
+
+  _productImage.contentMode = UIViewContentModeScaleAspectFill;
+  _productImage.translatesAutoresizingMaskIntoConstraints = NO;
+  _productImage.layer.borderWidth = 0;
+  _productImage.layer.cornerRadius = kProductCornerRadius;
+  _productImage.layer.masksToBounds = YES;
+}
+
+- (void)addGradientOverlayConstraints {
+  [NSLayoutConstraint activateConstraints:@[
+    [_gradientOverlay.heightAnchor
+        constraintEqualToConstant:kShopCardProductImageWidthHeight],
+    [_gradientOverlay.widthAnchor
+        constraintEqualToAnchor:_gradientOverlay.heightAnchor]
+  ]];
 }
 
 @end
diff --git a/ios/chrome/browser/default_browser/model/default_status/BUILD.gn b/ios/chrome/browser/default_browser/model/default_status/BUILD.gn
new file mode 100644
index 0000000..5059e6a
--- /dev/null
+++ b/ios/chrome/browser/default_browser/model/default_status/BUILD.gn
@@ -0,0 +1,45 @@
+# Copyright 2025 The Chromium Authors
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import("//build/config/apple/mobile_config.gni")
+
+# MacCatalyst does not allow querying the default browser status.
+assert(target_environment != "catalyst")
+
+source_set("default_status") {
+  sources = [
+    "default_status_helper.h",
+    "default_status_helper.mm",
+    "default_status_helper_constants.h",
+    "default_status_helper_constants.mm",
+    "default_status_helper_metrics.h",
+    "default_status_helper_metrics.mm",
+    "default_status_helper_prefs.h",
+    "default_status_helper_prefs.mm",
+    "default_status_helper_types.h",
+  ]
+  deps = []
+  public_deps = [
+    "//base",
+    "//components/prefs",
+    "//ios/chrome/browser/default_browser/model:utils",
+    "//ios/chrome/browser/shared/model/application_context",
+    "//ios/chrome/browser/shared/model/utils",
+    "//ios/chrome/browser/shared/public/features",
+  ]
+}
+
+source_set("unit_tests") {
+  testonly = true
+  sources = [
+    "default_status_helper_metrics_unittest.mm",
+    "default_status_helper_unittest.mm",
+  ]
+  deps = [
+    ":default_status",
+    "//base/test:test_support",
+    "//components/prefs:test_support",
+    "//ios/chrome/browser/shared/model/utils:test_support",
+  ]
+}
diff --git a/ios/chrome/browser/default_browser/model/default_status/default_status_helper.h b/ios/chrome/browser/default_browser/model/default_status/default_status_helper.h
new file mode 100644
index 0000000..a4bd83bab
--- /dev/null
+++ b/ios/chrome/browser/default_browser/model/default_status/default_status_helper.h
@@ -0,0 +1,94 @@
+// Copyright 2025 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_DEFAULT_BROWSER_MODEL_DEFAULT_STATUS_DEFAULT_STATUS_HELPER_H_
+#define IOS_CHROME_BROWSER_DEFAULT_BROWSER_MODEL_DEFAULT_STATUS_DEFAULT_STATUS_HELPER_H_
+
+#import <UIKit/UIKit.h>
+
+#import "base/functional/callback.h"
+
+class PrefService;
+
+namespace base {
+class Time;
+}  // namespace base
+
+// This helper contains the necessary logic to integrate with the default status
+// system API. At a high level, the integration works as follows:
+//    - To spread the reporting out throughout the year, clients are randomly
+//      assigned to a monthly cohort. The cohort assignment is persisted to
+//      prefs.
+//    - There are currently 4 cohorts, which correspond to the following months:
+//        - Cohort 1: January, May, September
+//        - Cohort 2: February, June, October
+//        - Cohort 3: March, July, November
+//        - Cohort 4: April, August, December
+//    - At startup, if the current date is at or after the assigned cohort's
+//      reporting window, a call to the default status API is triggered and
+//      relevant metrics and prefs are recorded.
+namespace default_status {
+
+enum class DefaultStatusAPIResult;
+enum class DefaultStatusRetention;
+
+// Do not call these functions; they are exposed for unit testing only.
+namespace internal {
+
+// Returns true if the current client is too recent (based on time since
+// completing the first run experience) to be assigned to a cohort.
+bool IsNewClient();
+
+// Returns true if the current client is already part of a cohort.
+bool HasCohortAssigned(PrefService* local_state);
+
+// Returns a random cohort number between 1 and the maximum number of cohorts.
+int GenerateRandomCohort();
+
+// Returns a base::Time representing the first day of the next reporting window
+// for the given cohort number. By default, if the current date is already
+// within the specified cohort's reporting window, then the first day of the
+// current month is returned. If `after_current_month` is set to true, the
+// computation to find the next reporting window starts at the beginning of next
+// month. All time computations are in UTC.
+base::Time GetCohortNextStartDate(int cohort_number,
+                                  bool after_current_month = false);
+
+// If eligible (correct system API availability, minimum client age met, no
+// cohort already assigned), randomly assigns the client to a reporting cohort
+// and sets the default status API call's next retry date to that cohort's next
+// reporting window.
+void AssignNewCohortIfNeeded(PrefService* local_state);
+
+// Converts the system's default status enum value to the one used by this
+// helper.
+DefaultStatusAPIResult SystemToLocalEnum(
+    UIApplicationCategoryDefaultStatus system_enum) API_AVAILABLE(ios(18.4));
+
+// Returns the retention type based on the previous and current default status
+// result.
+DefaultStatusRetention DetermineRetentionStatus(DefaultStatusAPIResult previous,
+                                                DefaultStatusAPIResult current);
+
+// Initiates the system API call if this client is outside the cooldown period,
+// and logs all relevant metrics based on the result.
+void QueryDefaultStatusIfReadyAndLogResults(PrefService* local_state);
+
+// Use in tests only. The specified callback will be invoked instead of making a
+// real call to the default status API.
+void OverrideSystemCallForTesting(
+    base::OnceCallback<UIApplicationCategoryDefaultStatus(NSError**)> cb)
+    API_AVAILABLE(ios(18.4));
+
+}  // namespace internal
+
+// Queries the system API to determine whether this is the default browser, and
+// logs the result. This is NOT meant to be used by feature code, as the system
+// call is rate limited. Instead, use the helpers in default browser utils to
+// check whether this is the likely the default browser.
+void TriggerDefaultStatusCheck();
+
+}  // namespace default_status
+
+#endif  // IOS_CHROME_BROWSER_DEFAULT_BROWSER_MODEL_DEFAULT_STATUS_DEFAULT_STATUS_HELPER_H_
diff --git a/ios/chrome/browser/default_browser/model/default_status/default_status_helper.mm b/ios/chrome/browser/default_browser/model/default_status/default_status_helper.mm
new file mode 100644
index 0000000..8fe109e
--- /dev/null
+++ b/ios/chrome/browser/default_browser/model/default_status/default_status_helper.mm
@@ -0,0 +1,278 @@
+// Copyright 2025 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/default_browser/model/default_status/default_status_helper.h"
+
+#import <UIKit/UIKit.h>
+
+#import "base/check.h"
+#import "base/check_is_test.h"
+#import "base/functional/callback.h"
+#import "base/rand_util.h"
+#import "base/time/time.h"
+#import "components/prefs/pref_service.h"
+#import "ios/chrome/browser/default_browser/model/default_status/default_status_helper_constants.h"
+#import "ios/chrome/browser/default_browser/model/default_status/default_status_helper_metrics.h"
+#import "ios/chrome/browser/default_browser/model/default_status/default_status_helper_prefs.h"
+#import "ios/chrome/browser/default_browser/model/default_status/default_status_helper_types.h"
+#import "ios/chrome/browser/shared/model/application_context/application_context.h"
+#import "ios/chrome/browser/shared/model/utils/first_run_util.h"
+#import "ios/chrome/browser/shared/public/features/features.h"
+
+namespace default_status {
+
+namespace internal {
+
+namespace {
+
+// The minimum time required since completing the first run experience to be
+// assigned to a cohort.
+const base::TimeDelta kMinimumNewClientFirstRunAge = base::Days(28);
+
+// Gets the system call to use/override for testing. Uses a function so the
+// static variable can be contained inside the function.
+base::OnceCallback<UIApplicationCategoryDefaultStatus(NSError**)>&
+GetSystemCallForTesting() API_AVAILABLE(ios(18.4)) {
+  static base::NoDestructor<
+      base::OnceCallback<UIApplicationCategoryDefaultStatus(NSError**)>>
+      g_system_call_for_testing;
+  return *g_system_call_for_testing;
+}
+
+// Performs the actual system call to the default status check API. Wrapped in
+// its own function to facilitate mocking in tests.
+UIApplicationCategoryDefaultStatus MakeSystemCall(NSError** error)
+    API_AVAILABLE(ios(18.4)) {
+  if (GetSystemCallForTesting()) {
+    CHECK_IS_TEST();
+    return std::move(GetSystemCallForTesting()).Run(error);  // IN-TEST
+  }
+  return [[UIApplication sharedApplication]
+      defaultStatusForCategory:UIApplicationCategoryWebBrowser
+                         error:error];
+}
+
+}  // namespace
+
+bool IsNewClient() {
+  return IsFirstRunRecent(kMinimumNewClientFirstRunAge);
+}
+
+bool HasCohortAssigned(PrefService* local_state) {
+  return local_state->GetInteger(kDefaultStatusAPICohort) != 0;
+}
+
+int GenerateRandomCohort() {
+  // The arguments passed to RandInt are inclusive.
+  return base::RandInt(1, kCohortCount);
+}
+
+base::Time GetCohortNextStartDate(int cohort_number, bool after_current_month) {
+  DUMP_WILL_BE_CHECK(cohort_number > 0 && cohort_number <= kCohortCount);
+
+  base::Time::Exploded now_exploded;
+  base::Time::Now().UTCExplode(&now_exploded);
+  DUMP_WILL_BE_CHECK(now_exploded.HasValidValues());
+  base::Time::Exploded target_date;
+  target_date.year = now_exploded.year;
+  target_date.month = now_exploded.month;
+  // Day of week does not matter when converting back to base::Time.
+  target_date.day_of_week = 0;
+  target_date.day_of_month = 1;
+  target_date.hour = 0;
+  target_date.minute = 0;
+  target_date.second = 0;
+  target_date.millisecond = 0;
+
+  // Determine which cohort number the current month corresponds to. This
+  // requires a modulo on the cohort count, but because the cohort number is
+  // 1-based, substract one before and add one after.
+  int current_cohort = (now_exploded.month - 1) % kCohortCount + 1;
+
+  if (current_cohort == cohort_number && !after_current_month) {
+    // If the current cohort is the same as the specified cohort, the target
+    // month is the current month.
+    target_date.year = now_exploded.year;
+    target_date.month = now_exploded.month;
+  } else {
+    // Otherwise, find the "cohort cycle" in which the target month is - the
+    // target month is the start of that cycle plus the cohort number. If the
+    // specified cohort is after the current cohort, then the target month is
+    // later in this cohort cycle. If it's before, it means the desired cohort
+    // has already passed this cycle, so the target month is in the next.
+    int target_cycle = (now_exploded.month - 1) / kCohortCount;
+    if (current_cohort >= cohort_number) {
+      ++target_cycle;
+    }
+    int target_month = target_cycle * kCohortCount + cohort_number;
+
+    // If the target month is greater than 12, decrease it by 12 and bump the
+    // target year.
+    if (target_month > 12) {
+      ++target_date.year;
+      target_month -= 12;
+    }
+
+    target_date.month = target_month;
+  }
+
+  base::Time result;
+  DUMP_WILL_BE_CHECK(base::Time::FromUTCExploded(target_date, &result));
+  return result;
+}
+
+void AssignNewCohortIfNeeded(PrefService* local_state) {
+  if (!@available(iOS 18.4, *)) {
+    return;
+  }
+
+  if (IsNewClient()) {
+    return;
+  }
+
+  if (HasCohortAssigned(local_state)) {
+    return;
+  }
+
+  int cohort = GenerateRandomCohort();
+  local_state->SetInteger(kDefaultStatusAPICohort, cohort);
+  base::Time next_retry = GetCohortNextStartDate(cohort);
+  local_state->SetTime(kDefaultStatusAPINextRetry, next_retry);
+}
+
+DefaultStatusAPIResult SystemToLocalEnum(
+    UIApplicationCategoryDefaultStatus system_enum) API_AVAILABLE(ios(18.4)) {
+  switch (system_enum) {
+    case UIApplicationCategoryDefaultStatusIsDefault:
+      return DefaultStatusAPIResult::kIsDefault;
+    case UIApplicationCategoryDefaultStatusNotDefault:
+      return DefaultStatusAPIResult::kIsNotDefault;
+    case UIApplicationCategoryDefaultStatusUnavailable:
+      // If the call was unavailable, the default status of the user is
+      // considered unknown.
+      return DefaultStatusAPIResult::kUnknown;
+    default:
+      return DefaultStatusAPIResult::kUnknown;
+  }
+}
+
+DefaultStatusRetention DetermineRetentionStatus(
+    DefaultStatusAPIResult previous,
+    DefaultStatusAPIResult current) {
+  DUMP_WILL_BE_CHECK(previous != DefaultStatusAPIResult::kUnknown &&
+                     current != DefaultStatusAPIResult::kUnknown);
+  if (previous == DefaultStatusAPIResult::kIsDefault) {
+    if (current == DefaultStatusAPIResult::kIsDefault) {
+      return DefaultStatusRetention::kRemainedDefault;
+    } else {
+      return DefaultStatusRetention::kBecameNonDefault;
+    }
+  } else {
+    if (current == DefaultStatusAPIResult::kIsDefault) {
+      return DefaultStatusRetention::kBecameDefault;
+    } else {
+      return DefaultStatusRetention::kRemainedNonDefault;
+    }
+  }
+}
+
+void QueryDefaultStatusIfReadyAndLogResults(PrefService* local_state) {
+  if (@available(iOS 18.4, *)) {
+    int cohort_number = local_state->GetInteger(kDefaultStatusAPICohort);
+    DUMP_WILL_BE_CHECK(cohort_number > 0 && cohort_number <= kCohortCount);
+
+    base::Time now = base::Time::Now();
+    base::Time next_retry = local_state->GetTime(kDefaultStatusAPINextRetry);
+    if (now < next_retry) {
+      return;
+    }
+
+    NSError* error = nil;
+    UIApplicationCategoryDefaultStatus default_status = MakeSystemCall(&error);
+
+    if (error) {
+      if ([error code] == UIApplicationCategoryDefaultErrorRateLimited) {
+        DUMP_WILL_BE_CHECK(
+            error.userInfo
+                [UIApplicationCategoryDefaultRetryAvailabilityDateErrorKey] !=
+            nil);
+        RecordDefaultStatusAPIOutcomeType(
+            DefaultStatusAPIOutcomeType::kCooldownError);
+        next_retry = base::Time::FromNSDate(
+            error.userInfo
+                [UIApplicationCategoryDefaultRetryAvailabilityDateErrorKey]);
+        local_state->SetTime(kDefaultStatusAPINextRetry, next_retry);
+        int days_left = (next_retry - now).InDays();
+        RecordCooldownErrorDaysLeft(days_left);
+      } else {
+        RecordDefaultStatusAPIOutcomeType(
+            DefaultStatusAPIOutcomeType::kOtherError);
+      }
+    } else {
+      RecordDefaultStatusAPIOutcomeType(DefaultStatusAPIOutcomeType::kSuccess);
+
+      DefaultStatusAPIResult current_result = SystemToLocalEnum(default_status);
+      DUMP_WILL_BE_CHECK(current_result != DefaultStatusAPIResult::kUnknown);
+
+      base::Time cohort_start = GetCohortNextStartDate(cohort_number);
+      int next_cohort_number = cohort_number % kCohortCount + 1;
+      base::Time next_cohort_start = GetCohortNextStartDate(next_cohort_number);
+      bool is_within_cohort_window =
+          now >= cohort_start && now < next_cohort_start;
+      bool is_default = current_result == DefaultStatusAPIResult::kIsDefault;
+      RecordDefaultStatusAPIResult(is_default, cohort_number,
+                                   is_within_cohort_window);
+      RecordHeuristicAssessments(is_default);
+
+      DefaultStatusAPIResult previous_result =
+          static_cast<DefaultStatusAPIResult>(
+              local_state->GetInteger(kDefaultStatusAPIResult));
+      if (previous_result != DefaultStatusAPIResult::kUnknown) {
+        base::Time last_successful_call =
+            local_state->GetTime(kDefaultStatusAPILastSuccessfulCall);
+        RecordDaysSinceLastSuccessfulCall(
+            (now - last_successful_call).InDays());
+        RecordDefaultStatusRetention(
+            DetermineRetentionStatus(previous_result, current_result));
+      }
+
+      local_state->SetTime(kDefaultStatusAPILastSuccessfulCall, now);
+      local_state->SetInteger(kDefaultStatusAPIResult,
+                              static_cast<int>(current_result));
+      next_retry =
+          GetCohortNextStartDate(cohort_number, /*after_current_month=*/true);
+      local_state->SetTime(kDefaultStatusAPINextRetry, next_retry);
+    }
+  }
+}
+
+void OverrideSystemCallForTesting(  // IN-TEST
+    base::OnceCallback<UIApplicationCategoryDefaultStatus(NSError**)> cb)
+    API_AVAILABLE(ios(18.4)) {
+  CHECK_IS_TEST();
+  internal::GetSystemCallForTesting() = std::move(cb);
+}
+
+}  // namespace internal
+
+void TriggerDefaultStatusCheck() {
+  if (!IsRunDefaultStatusCheckEnabled()) {
+    return;
+  }
+
+  if (!@available(iOS 18.4, *)) {
+    return;
+  }
+
+  if (internal::IsNewClient()) {
+    return;
+  }
+
+  ApplicationContext* applicationContext = GetApplicationContext();
+  PrefService* localState = applicationContext->GetLocalState();
+  internal::AssignNewCohortIfNeeded(localState);
+  internal::QueryDefaultStatusIfReadyAndLogResults(localState);
+}
+
+}  // namespace default_status
diff --git a/ios/chrome/browser/default_browser/model/default_status/default_status_helper_constants.h b/ios/chrome/browser/default_browser/model/default_status/default_status_helper_constants.h
new file mode 100644
index 0000000..64c09465
--- /dev/null
+++ b/ios/chrome/browser/default_browser/model/default_status/default_status_helper_constants.h
@@ -0,0 +1,12 @@
+// Copyright 2025 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_DEFAULT_BROWSER_MODEL_DEFAULT_STATUS_DEFAULT_STATUS_HELPER_CONSTANTS_H_
+#define IOS_CHROME_BROWSER_DEFAULT_BROWSER_MODEL_DEFAULT_STATUS_DEFAULT_STATUS_HELPER_CONSTANTS_H_
+
+// The number of reporting cohorts for the default status API call. See the
+// comments in `default_status_helper.h` for more info.
+extern const int kCohortCount;
+
+#endif  // IOS_CHROME_BROWSER_DEFAULT_BROWSER_MODEL_DEFAULT_STATUS_DEFAULT_STATUS_HELPER_CONSTANTS_H_
diff --git a/ios/chrome/browser/default_browser/model/default_status/default_status_helper_constants.mm b/ios/chrome/browser/default_browser/model/default_status/default_status_helper_constants.mm
new file mode 100644
index 0000000..021b3d0
--- /dev/null
+++ b/ios/chrome/browser/default_browser/model/default_status/default_status_helper_constants.mm
@@ -0,0 +1,18 @@
+// Copyright 2025 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/default_browser/model/default_status/default_status_helper_constants.h"
+
+// The cohorts are monthly, aligned with the Gregorian calendar. The logic in
+// this file should work for any cohort count between 1 and 12. Anything higher
+// than that means the cohorts are no longer aligned to calendar months, so
+// logic needs to be revisited.
+//
+// Changing this value requires updating the
+// IOS.DefaultStatusAPI.IsDefaultBrowser.Cohort{Number} histogram to make sure
+// there is a variant for each cohort.
+//
+// LINT.IfChange(kCohortCount)
+const int kCohortCount = 4;
+// LINT.ThenChange(/tools/metrics/histograms/metadata/ios/histograms.xml:CohortNumber)
diff --git a/ios/chrome/browser/default_browser/model/default_status/default_status_helper_metrics.h b/ios/chrome/browser/default_browser/model/default_status/default_status_helper_metrics.h
new file mode 100644
index 0000000..460f37b
--- /dev/null
+++ b/ios/chrome/browser/default_browser/model/default_status/default_status_helper_metrics.h
@@ -0,0 +1,51 @@
+// Copyright 2025 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_DEFAULT_BROWSER_MODEL_DEFAULT_STATUS_DEFAULT_STATUS_HELPER_METRICS_H_
+#define IOS_CHROME_BROWSER_DEFAULT_BROWSER_MODEL_DEFAULT_STATUS_DEFAULT_STATUS_HELPER_METRICS_H_
+
+namespace default_status {
+
+enum class DefaultStatusAPIOutcomeType;
+enum class DefaultStatusHeuristicAssessment;
+enum class DefaultStatusRetention;
+
+// Do not call these functions; they are exposed for unit testing only.
+namespace internal {
+
+// Helper function to compare the heuristic and the system results. Exposed for
+// unit testing.
+DefaultStatusHeuristicAssessment AssessHeuristic(bool system_is_default,
+                                                 bool heuristic_is_default);
+}  // namespace internal
+
+// Records the number of days left in the system call cooldown period.
+void RecordCooldownErrorDaysLeft(int days_left);
+
+// Records how many days have passed since the last successful default status
+// API call.
+void RecordDaysSinceLastSuccessfulCall(int days);
+
+// Helper to record the type of outcome (error or success) of the default status
+// API call.
+void RecordDefaultStatusAPIOutcomeType(
+    DefaultStatusAPIOutcomeType outcome_type);
+
+// Helper to record the result of a successful default status API call to
+// various histograms based on the client's cohort.
+void RecordDefaultStatusAPIResult(bool is_default,
+                                  int cohort_number,
+                                  bool is_within_cohort_window);
+
+// Records the default status retention since the last successful default status
+// API call.
+void RecordDefaultStatusRetention(DefaultStatusRetention retention);
+
+// Records the difference between the provided default status according to the
+// system API, and Chrome's heuristics.
+void RecordHeuristicAssessments(bool is_default_in_system_api);
+
+}  // namespace default_status
+
+#endif  // IOS_CHROME_BROWSER_DEFAULT_BROWSER_MODEL_DEFAULT_STATUS_DEFAULT_STATUS_HELPER_METRICS_H_
diff --git a/ios/chrome/browser/default_browser/model/default_status/default_status_helper_metrics.mm b/ios/chrome/browser/default_browser/model/default_status/default_status_helper_metrics.mm
new file mode 100644
index 0000000..a92f3fc
--- /dev/null
+++ b/ios/chrome/browser/default_browser/model/default_status/default_status_helper_metrics.mm
@@ -0,0 +1,110 @@
+// Copyright 2025 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/default_browser/model/default_status/default_status_helper_metrics.h"
+
+#import "base/metrics/histogram_functions.h"
+#import "base/strings/stringprintf.h"
+#import "ios/chrome/browser/default_browser/model/default_status/default_status_helper_constants.h"
+#import "ios/chrome/browser/default_browser/model/default_status/default_status_helper_types.h"
+#import "ios/chrome/browser/default_browser/model/utils.h"
+
+namespace default_status {
+
+namespace internal {
+
+DefaultStatusHeuristicAssessment AssessHeuristic(bool system_is_default,
+                                                 bool heuristic_is_default) {
+  if (heuristic_is_default) {
+    if (system_is_default) {
+      return DefaultStatusHeuristicAssessment::kTruePositive;
+    } else {
+      return DefaultStatusHeuristicAssessment::kFalsePositive;
+    }
+  } else {
+    if (system_is_default) {
+      return DefaultStatusHeuristicAssessment::kFalseNegative;
+    } else {
+      return DefaultStatusHeuristicAssessment::kTrueNegative;
+    }
+  }
+}
+
+}  // namespace internal
+
+void RecordCooldownErrorDaysLeft(int days_left) {
+  base::UmaHistogramCounts1000("IOS.DefaultStatusAPI.CooldownError.DaysLeft",
+                               days_left);
+}
+
+void RecordDaysSinceLastSuccessfulCall(int days) {
+  base::UmaHistogramCounts1000(
+      "IOS.DefaultStatusAPI.DaysSinceLastSuccessfulCall", days);
+}
+
+void RecordDefaultStatusAPIOutcomeType(
+    DefaultStatusAPIOutcomeType outcome_type) {
+  base::UmaHistogramEnumeration("IOS.DefaultStatusAPI.OutcomeType",
+                                outcome_type);
+}
+
+void RecordDefaultStatusAPIResult(bool is_default,
+                                  int cohort_number,
+                                  bool is_within_cohort_window) {
+  DUMP_WILL_BE_CHECK(cohort_number >= 1 && cohort_number <= kCohortCount);
+
+  base::UmaHistogramBoolean("IOS.DefaultStatusAPI.IsDefaultBrowser",
+                            is_default);
+
+  base::UmaHistogramBoolean(
+      base::StringPrintf("IOS.DefaultStatusAPI.IsDefaultBrowser.Cohort%d",
+                         cohort_number),
+      is_default);
+  if (is_within_cohort_window) {
+    base::UmaHistogramBoolean(
+        "IOS.DefaultStatusAPI.IsDefaultBrowser.StrictCohorts", is_default);
+  }
+}
+
+void RecordDefaultStatusRetention(DefaultStatusRetention retention) {
+  base::UmaHistogramEnumeration("IOS.DefaultStatusAPI.DefaultStatusRetention",
+                                retention);
+}
+
+void RecordHeuristicAssessments(bool is_default_in_system_api) {
+  DefaultStatusHeuristicAssessment assessment = internal::AssessHeuristic(
+      is_default_in_system_api, IsChromeLikelyDefaultBrowserXDays(1));
+  base::UmaHistogramEnumeration("IOS.DefaultStatusAPI.HeuristicAssessment1",
+                                assessment);
+  assessment = internal::AssessHeuristic(is_default_in_system_api,
+                                         IsChromeLikelyDefaultBrowserXDays(3));
+  base::UmaHistogramEnumeration("IOS.DefaultStatusAPI.HeuristicAssessment3",
+                                assessment);
+  assessment = internal::AssessHeuristic(is_default_in_system_api,
+                                         IsChromeLikelyDefaultBrowserXDays(7));
+  base::UmaHistogramEnumeration("IOS.DefaultStatusAPI.HeuristicAssessment7",
+                                assessment);
+  assessment = internal::AssessHeuristic(is_default_in_system_api,
+                                         IsChromeLikelyDefaultBrowserXDays(14));
+  base::UmaHistogramEnumeration("IOS.DefaultStatusAPI.HeuristicAssessment14",
+                                assessment);
+  assessment = internal::AssessHeuristic(is_default_in_system_api,
+                                         IsChromeLikelyDefaultBrowserXDays(21));
+  base::UmaHistogramEnumeration("IOS.DefaultStatusAPI.HeuristicAssessment21",
+                                assessment);
+  assessment = internal::AssessHeuristic(is_default_in_system_api,
+                                         IsChromeLikelyDefaultBrowserXDays(28));
+  base::UmaHistogramEnumeration("IOS.DefaultStatusAPI.HeuristicAssessment28",
+                                assessment);
+  assessment = internal::AssessHeuristic(is_default_in_system_api,
+                                         IsChromeLikelyDefaultBrowserXDays(35));
+  base::UmaHistogramEnumeration("IOS.DefaultStatusAPI.HeuristicAssessment35",
+                                assessment);
+  assessment = internal::AssessHeuristic(is_default_in_system_api,
+                                         IsChromeLikelyDefaultBrowserXDays(42));
+  base::UmaHistogramEnumeration("IOS.DefaultStatusAPI.HeuristicAssessment42",
+                                assessment);
+}
+
+}  // namespace default_status
diff --git a/ios/chrome/browser/default_browser/model/default_status/default_status_helper_metrics_unittest.mm b/ios/chrome/browser/default_browser/model/default_status/default_status_helper_metrics_unittest.mm
new file mode 100644
index 0000000..8661ba6
--- /dev/null
+++ b/ios/chrome/browser/default_browser/model/default_status/default_status_helper_metrics_unittest.mm
@@ -0,0 +1,787 @@
+// Copyright 2025 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/default_browser/model/default_status/default_status_helper_metrics.h"
+
+#import "base/test/metrics/histogram_tester.h"
+#import "ios/chrome/browser/default_browser/model/default_status/default_status_helper_types.h"
+#import "ios/chrome/browser/default_browser/model/utils.h"
+#import "ios/chrome/browser/shared/public/features/features.h"
+#import "testing/platform_test.h"
+
+namespace default_status {
+
+// Storage key for recording the last time an http link was opened via Chrome,
+// which indicates that it's set as default browser.
+NSString* const kLastHTTPURLOpenTime = @"lastHTTPURLOpenTime";
+
+// Test fixture for testing the default status metrics.
+class DefaultStatusHelperMetricsTest : public PlatformTest {};
+
+// Tests that the heuristic assessment returns the correct value.
+TEST_F(DefaultStatusHelperMetricsTest, HeuristicAssessment) {
+  EXPECT_EQ(internal::AssessHeuristic(/*system_is_default=*/false,
+                                      /*heuristic_is_default=*/false),
+            DefaultStatusHeuristicAssessment::kTrueNegative);
+  EXPECT_EQ(internal::AssessHeuristic(/*system_is_default=*/false,
+                                      /*heuristic_is_default=*/true),
+            DefaultStatusHeuristicAssessment::kFalsePositive);
+  EXPECT_EQ(internal::AssessHeuristic(/*system_is_default=*/true,
+                                      /*heuristic_is_default=*/false),
+            DefaultStatusHeuristicAssessment::kFalseNegative);
+  EXPECT_EQ(internal::AssessHeuristic(/*system_is_default=*/true,
+                                      /*heuristic_is_default=*/true),
+            DefaultStatusHeuristicAssessment::kTruePositive);
+}
+
+// Tests that the RecordCooldownErrorDaysLeft function records the correct
+// values.
+TEST_F(DefaultStatusHelperMetricsTest, RecordCooldownErrorDaysLeft) {
+  base::HistogramTester histogram_tester;
+
+  RecordCooldownErrorDaysLeft(15);
+  histogram_tester.ExpectBucketCount(
+      "IOS.DefaultStatusAPI.CooldownError.DaysLeft", 15, 1);
+
+  RecordCooldownErrorDaysLeft(1005);
+  RecordCooldownErrorDaysLeft(1105);
+  histogram_tester.ExpectBucketCount(
+      "IOS.DefaultStatusAPI.CooldownError.DaysLeft", 1000, 2);
+
+  histogram_tester.ExpectTotalCount(
+      "IOS.DefaultStatusAPI.CooldownError.DaysLeft", 3);
+}
+
+// Tests that the RecordDaysSinceLastSuccessfulCall function records the
+// correct values.
+TEST_F(DefaultStatusHelperMetricsTest, RecordDaysSinceLastSuccessfulCall) {
+  base::HistogramTester histogram_tester;
+
+  RecordDaysSinceLastSuccessfulCall(15);
+  histogram_tester.ExpectBucketCount(
+      "IOS.DefaultStatusAPI.DaysSinceLastSuccessfulCall", 15, 1);
+
+  RecordDaysSinceLastSuccessfulCall(1005);
+  RecordDaysSinceLastSuccessfulCall(1105);
+  histogram_tester.ExpectBucketCount(
+      "IOS.DefaultStatusAPI.DaysSinceLastSuccessfulCall", 1000, 2);
+
+  histogram_tester.ExpectTotalCount(
+      "IOS.DefaultStatusAPI.DaysSinceLastSuccessfulCall", 3);
+}
+
+// Tests that the RecordDefaultStatusAPIOutcomeType function records the
+// correct values.
+TEST_F(DefaultStatusHelperMetricsTest, RecordDefaultStatusAPIOutcomeType) {
+  base::HistogramTester histogram_tester;
+
+  RecordDefaultStatusAPIOutcomeType(DefaultStatusAPIOutcomeType::kSuccess);
+  histogram_tester.ExpectBucketCount("IOS.DefaultStatusAPI.OutcomeType",
+                                     DefaultStatusAPIOutcomeType::kSuccess, 1);
+
+  RecordDefaultStatusAPIOutcomeType(
+      DefaultStatusAPIOutcomeType::kCooldownError);
+  histogram_tester.ExpectBucketCount(
+      "IOS.DefaultStatusAPI.OutcomeType",
+      DefaultStatusAPIOutcomeType::kCooldownError, 1);
+
+  RecordDefaultStatusAPIOutcomeType(DefaultStatusAPIOutcomeType::kOtherError);
+  histogram_tester.ExpectBucketCount("IOS.DefaultStatusAPI.OutcomeType",
+                                     DefaultStatusAPIOutcomeType::kOtherError,
+                                     1);
+
+  histogram_tester.ExpectTotalCount("IOS.DefaultStatusAPI.OutcomeType", 3);
+}
+
+// Tests that the RecordDefaultStatusAPIResult function records the correct
+// values.
+TEST_F(DefaultStatusHelperMetricsTest, RecordDefaultStatusAPIResult) {
+  base::HistogramTester histogram_tester;
+
+  // Cohort 1.
+  RecordDefaultStatusAPIResult(/*is_default=*/true, /*cohort_number=*/1,
+                               /*is_within_cohort_window=*/true);
+  histogram_tester.ExpectBucketCount("IOS.DefaultStatusAPI.IsDefaultBrowser",
+                                     true, 1);
+  histogram_tester.ExpectBucketCount(
+      "IOS.DefaultStatusAPI.IsDefaultBrowser.StrictCohorts", true, 1);
+  histogram_tester.ExpectBucketCount(
+      "IOS.DefaultStatusAPI.IsDefaultBrowser.Cohort1", true, 1);
+
+  RecordDefaultStatusAPIResult(/*is_default=*/false, /*cohort_number=*/1,
+                               /*is_within_cohort_window=*/true);
+  histogram_tester.ExpectBucketCount("IOS.DefaultStatusAPI.IsDefaultBrowser",
+                                     false, 1);
+  histogram_tester.ExpectBucketCount(
+      "IOS.DefaultStatusAPI.IsDefaultBrowser.StrictCohorts", false, 1);
+  histogram_tester.ExpectBucketCount(
+      "IOS.DefaultStatusAPI.IsDefaultBrowser.Cohort1", false, 1);
+
+  RecordDefaultStatusAPIResult(/*is_default=*/true, /*cohort_number=*/1,
+                               /*is_within_cohort_window=*/false);
+  histogram_tester.ExpectBucketCount("IOS.DefaultStatusAPI.IsDefaultBrowser",
+                                     true, 2);
+  histogram_tester.ExpectBucketCount(
+      "IOS.DefaultStatusAPI.IsDefaultBrowser.Cohort1", true, 2);
+
+  RecordDefaultStatusAPIResult(/*is_default=*/false, /*cohort_number=*/1,
+                               /*is_within_cohort_window=*/false);
+  histogram_tester.ExpectBucketCount("IOS.DefaultStatusAPI.IsDefaultBrowser",
+                                     false, 2);
+  histogram_tester.ExpectBucketCount(
+      "IOS.DefaultStatusAPI.IsDefaultBrowser.Cohort1", false, 2);
+
+  // Cohort 2.
+  RecordDefaultStatusAPIResult(/*is_default=*/true, /*cohort_number=*/2,
+                               /*is_within_cohort_window=*/true);
+  histogram_tester.ExpectBucketCount("IOS.DefaultStatusAPI.IsDefaultBrowser",
+                                     true, 3);
+  histogram_tester.ExpectBucketCount(
+      "IOS.DefaultStatusAPI.IsDefaultBrowser.StrictCohorts", true, 2);
+  histogram_tester.ExpectBucketCount(
+      "IOS.DefaultStatusAPI.IsDefaultBrowser.Cohort2", true, 1);
+
+  RecordDefaultStatusAPIResult(/*is_default=*/false, /*cohort_number=*/2,
+                               /*is_within_cohort_window=*/true);
+  histogram_tester.ExpectBucketCount("IOS.DefaultStatusAPI.IsDefaultBrowser",
+                                     false, 3);
+  histogram_tester.ExpectBucketCount(
+      "IOS.DefaultStatusAPI.IsDefaultBrowser.StrictCohorts", false, 2);
+  histogram_tester.ExpectBucketCount(
+      "IOS.DefaultStatusAPI.IsDefaultBrowser.Cohort2", false, 1);
+
+  RecordDefaultStatusAPIResult(/*is_default=*/true, /*cohort_number=*/2,
+                               /*is_within_cohort_window=*/false);
+  histogram_tester.ExpectBucketCount("IOS.DefaultStatusAPI.IsDefaultBrowser",
+                                     true, 4);
+  histogram_tester.ExpectBucketCount(
+      "IOS.DefaultStatusAPI.IsDefaultBrowser.Cohort2", true, 2);
+
+  RecordDefaultStatusAPIResult(/*is_default=*/false, /*cohort_number=*/2,
+                               /*is_within_cohort_window=*/false);
+  histogram_tester.ExpectBucketCount("IOS.DefaultStatusAPI.IsDefaultBrowser",
+                                     false, 4);
+  histogram_tester.ExpectBucketCount(
+      "IOS.DefaultStatusAPI.IsDefaultBrowser.Cohort2", false, 2);
+
+  // Cohort 3.
+  RecordDefaultStatusAPIResult(/*is_default=*/true, /*cohort_number=*/3,
+                               /*is_within_cohort_window=*/true);
+  histogram_tester.ExpectBucketCount("IOS.DefaultStatusAPI.IsDefaultBrowser",
+                                     true, 5);
+  histogram_tester.ExpectBucketCount(
+      "IOS.DefaultStatusAPI.IsDefaultBrowser.StrictCohorts", true, 3);
+  histogram_tester.ExpectBucketCount(
+      "IOS.DefaultStatusAPI.IsDefaultBrowser.Cohort3", true, 1);
+
+  RecordDefaultStatusAPIResult(/*is_default=*/false, /*cohort_number=*/3,
+                               /*is_within_cohort_window=*/true);
+  histogram_tester.ExpectBucketCount("IOS.DefaultStatusAPI.IsDefaultBrowser",
+                                     false, 5);
+  histogram_tester.ExpectBucketCount(
+      "IOS.DefaultStatusAPI.IsDefaultBrowser.StrictCohorts", false, 3);
+  histogram_tester.ExpectBucketCount(
+      "IOS.DefaultStatusAPI.IsDefaultBrowser.Cohort3", false, 1);
+
+  RecordDefaultStatusAPIResult(/*is_default=*/true, /*cohort_number=*/3,
+                               /*is_within_cohort_window=*/false);
+  histogram_tester.ExpectBucketCount("IOS.DefaultStatusAPI.IsDefaultBrowser",
+                                     true, 6);
+  histogram_tester.ExpectBucketCount(
+      "IOS.DefaultStatusAPI.IsDefaultBrowser.Cohort3", true, 2);
+
+  RecordDefaultStatusAPIResult(/*is_default=*/false, /*cohort_number=*/3,
+                               /*is_within_cohort_window=*/false);
+  histogram_tester.ExpectBucketCount("IOS.DefaultStatusAPI.IsDefaultBrowser",
+                                     false, 6);
+  histogram_tester.ExpectBucketCount(
+      "IOS.DefaultStatusAPI.IsDefaultBrowser.Cohort3", false, 2);
+
+  // Cohort 4.
+  RecordDefaultStatusAPIResult(/*is_default=*/true, /*cohort_number=*/4,
+                               /*is_within_cohort_window=*/true);
+  histogram_tester.ExpectBucketCount("IOS.DefaultStatusAPI.IsDefaultBrowser",
+                                     true, 7);
+  histogram_tester.ExpectBucketCount(
+      "IOS.DefaultStatusAPI.IsDefaultBrowser.StrictCohorts", true, 4);
+  histogram_tester.ExpectBucketCount(
+      "IOS.DefaultStatusAPI.IsDefaultBrowser.Cohort4", true, 1);
+
+  RecordDefaultStatusAPIResult(/*is_default=*/false, /*cohort_number=*/4,
+                               /*is_within_cohort_window=*/true);
+  histogram_tester.ExpectBucketCount("IOS.DefaultStatusAPI.IsDefaultBrowser",
+                                     false, 7);
+  histogram_tester.ExpectBucketCount(
+      "IOS.DefaultStatusAPI.IsDefaultBrowser.StrictCohorts", false, 4);
+  histogram_tester.ExpectBucketCount(
+      "IOS.DefaultStatusAPI.IsDefaultBrowser.Cohort4", false, 1);
+
+  RecordDefaultStatusAPIResult(/*is_default=*/true, /*cohort_number=*/4,
+                               /*is_within_cohort_window=*/false);
+  histogram_tester.ExpectBucketCount("IOS.DefaultStatusAPI.IsDefaultBrowser",
+                                     true, 8);
+  histogram_tester.ExpectBucketCount(
+      "IOS.DefaultStatusAPI.IsDefaultBrowser.Cohort4", true, 2);
+
+  RecordDefaultStatusAPIResult(/*is_default=*/false, /*cohort_number=*/4,
+                               /*is_within_cohort_window=*/false);
+  histogram_tester.ExpectBucketCount("IOS.DefaultStatusAPI.IsDefaultBrowser",
+                                     false, 8);
+  histogram_tester.ExpectBucketCount(
+      "IOS.DefaultStatusAPI.IsDefaultBrowser.Cohort4", false, 2);
+
+  // Summary.
+  histogram_tester.ExpectTotalCount("IOS.DefaultStatusAPI.IsDefaultBrowser",
+                                    16);
+  histogram_tester.ExpectTotalCount(
+      "IOS.DefaultStatusAPI.IsDefaultBrowser.StrictCohorts", 8);
+  histogram_tester.ExpectTotalCount(
+      "IOS.DefaultStatusAPI.IsDefaultBrowser.Cohort1", 4);
+  histogram_tester.ExpectTotalCount(
+      "IOS.DefaultStatusAPI.IsDefaultBrowser.Cohort2", 4);
+  histogram_tester.ExpectTotalCount(
+      "IOS.DefaultStatusAPI.IsDefaultBrowser.Cohort3", 4);
+  histogram_tester.ExpectTotalCount(
+      "IOS.DefaultStatusAPI.IsDefaultBrowser.Cohort4", 4);
+}
+
+// Tests that the RecordDefaultStatusRetention function records the correct
+// values.
+TEST_F(DefaultStatusHelperMetricsTest, RecordDefaultStatusRetention) {
+  base::HistogramTester histogram_tester;
+
+  RecordDefaultStatusRetention(DefaultStatusRetention::kBecameDefault);
+  histogram_tester.ExpectBucketCount(
+      "IOS.DefaultStatusAPI.DefaultStatusRetention",
+      DefaultStatusRetention::kBecameDefault, 1);
+
+  RecordDefaultStatusRetention(DefaultStatusRetention::kBecameNonDefault);
+  histogram_tester.ExpectBucketCount(
+      "IOS.DefaultStatusAPI.DefaultStatusRetention",
+      DefaultStatusRetention::kBecameNonDefault, 1);
+
+  RecordDefaultStatusRetention(DefaultStatusRetention::kRemainedDefault);
+  histogram_tester.ExpectBucketCount(
+      "IOS.DefaultStatusAPI.DefaultStatusRetention",
+      DefaultStatusRetention::kRemainedDefault, 1);
+
+  RecordDefaultStatusRetention(DefaultStatusRetention::kRemainedNonDefault);
+  histogram_tester.ExpectBucketCount(
+      "IOS.DefaultStatusAPI.DefaultStatusRetention",
+      DefaultStatusRetention::kRemainedNonDefault, 1);
+
+  histogram_tester.ExpectTotalCount(
+      "IOS.DefaultStatusAPI.DefaultStatusRetention", 4);
+}
+
+// Tests that the RecordHeuristicAssessments function records the correct
+// values.
+TEST_F(DefaultStatusHelperMetricsTest, RecordHeuristicAssessments) {
+  base::HistogramTester histogram_tester;
+
+  NSDate* under_one_day_ago =
+      (base::Time::Now() - base::Days(1) + base::Minutes(10)).ToNSDate();
+  SetObjectIntoStorageForKey(kLastHTTPURLOpenTime, under_one_day_ago);
+  RecordHeuristicAssessments(/*is_default_in_system_api=*/true);
+  histogram_tester.ExpectBucketCount(
+      "IOS.DefaultStatusAPI.HeuristicAssessment1",
+      DefaultStatusHeuristicAssessment::kTruePositive, 1);
+  histogram_tester.ExpectBucketCount(
+      "IOS.DefaultStatusAPI.HeuristicAssessment3",
+      DefaultStatusHeuristicAssessment::kTruePositive, 1);
+  histogram_tester.ExpectBucketCount(
+      "IOS.DefaultStatusAPI.HeuristicAssessment7",
+      DefaultStatusHeuristicAssessment::kTruePositive, 1);
+  histogram_tester.ExpectBucketCount(
+      "IOS.DefaultStatusAPI.HeuristicAssessment14",
+      DefaultStatusHeuristicAssessment::kTruePositive, 1);
+  histogram_tester.ExpectBucketCount(
+      "IOS.DefaultStatusAPI.HeuristicAssessment21",
+      DefaultStatusHeuristicAssessment::kTruePositive, 1);
+  histogram_tester.ExpectBucketCount(
+      "IOS.DefaultStatusAPI.HeuristicAssessment28",
+      DefaultStatusHeuristicAssessment::kTruePositive, 1);
+  histogram_tester.ExpectBucketCount(
+      "IOS.DefaultStatusAPI.HeuristicAssessment35",
+      DefaultStatusHeuristicAssessment::kTruePositive, 1);
+  histogram_tester.ExpectBucketCount(
+      "IOS.DefaultStatusAPI.HeuristicAssessment42",
+      DefaultStatusHeuristicAssessment::kTruePositive, 1);
+  RecordHeuristicAssessments(/*is_default_in_system_api=*/false);
+  histogram_tester.ExpectBucketCount(
+      "IOS.DefaultStatusAPI.HeuristicAssessment1",
+      DefaultStatusHeuristicAssessment::kFalsePositive, 1);
+  histogram_tester.ExpectBucketCount(
+      "IOS.DefaultStatusAPI.HeuristicAssessment3",
+      DefaultStatusHeuristicAssessment::kFalsePositive, 1);
+  histogram_tester.ExpectBucketCount(
+      "IOS.DefaultStatusAPI.HeuristicAssessment7",
+      DefaultStatusHeuristicAssessment::kFalsePositive, 1);
+  histogram_tester.ExpectBucketCount(
+      "IOS.DefaultStatusAPI.HeuristicAssessment14",
+      DefaultStatusHeuristicAssessment::kFalsePositive, 1);
+  histogram_tester.ExpectBucketCount(
+      "IOS.DefaultStatusAPI.HeuristicAssessment21",
+      DefaultStatusHeuristicAssessment::kFalsePositive, 1);
+  histogram_tester.ExpectBucketCount(
+      "IOS.DefaultStatusAPI.HeuristicAssessment28",
+      DefaultStatusHeuristicAssessment::kFalsePositive, 1);
+  histogram_tester.ExpectBucketCount(
+      "IOS.DefaultStatusAPI.HeuristicAssessment35",
+      DefaultStatusHeuristicAssessment::kFalsePositive, 1);
+  histogram_tester.ExpectBucketCount(
+      "IOS.DefaultStatusAPI.HeuristicAssessment42",
+      DefaultStatusHeuristicAssessment::kFalsePositive, 1);
+
+  NSDate* under_three_days_ago =
+      (base::Time::Now() - base::Days(3) + base::Minutes(10)).ToNSDate();
+  SetObjectIntoStorageForKey(kLastHTTPURLOpenTime, under_three_days_ago);
+  RecordHeuristicAssessments(/*is_default_in_system_api=*/true);
+  histogram_tester.ExpectBucketCount(
+      "IOS.DefaultStatusAPI.HeuristicAssessment1",
+      DefaultStatusHeuristicAssessment::kFalseNegative, 1);
+  histogram_tester.ExpectBucketCount(
+      "IOS.DefaultStatusAPI.HeuristicAssessment3",
+      DefaultStatusHeuristicAssessment::kTruePositive, 2);
+  histogram_tester.ExpectBucketCount(
+      "IOS.DefaultStatusAPI.HeuristicAssessment7",
+      DefaultStatusHeuristicAssessment::kTruePositive, 2);
+  histogram_tester.ExpectBucketCount(
+      "IOS.DefaultStatusAPI.HeuristicAssessment14",
+      DefaultStatusHeuristicAssessment::kTruePositive, 2);
+  histogram_tester.ExpectBucketCount(
+      "IOS.DefaultStatusAPI.HeuristicAssessment21",
+      DefaultStatusHeuristicAssessment::kTruePositive, 2);
+  histogram_tester.ExpectBucketCount(
+      "IOS.DefaultStatusAPI.HeuristicAssessment28",
+      DefaultStatusHeuristicAssessment::kTruePositive, 2);
+  histogram_tester.ExpectBucketCount(
+      "IOS.DefaultStatusAPI.HeuristicAssessment35",
+      DefaultStatusHeuristicAssessment::kTruePositive, 2);
+  histogram_tester.ExpectBucketCount(
+      "IOS.DefaultStatusAPI.HeuristicAssessment42",
+      DefaultStatusHeuristicAssessment::kTruePositive, 2);
+  RecordHeuristicAssessments(/*is_default_in_system_api=*/false);
+  histogram_tester.ExpectBucketCount(
+      "IOS.DefaultStatusAPI.HeuristicAssessment1",
+      DefaultStatusHeuristicAssessment::kTrueNegative, 1);
+  histogram_tester.ExpectBucketCount(
+      "IOS.DefaultStatusAPI.HeuristicAssessment3",
+      DefaultStatusHeuristicAssessment::kFalsePositive, 2);
+  histogram_tester.ExpectBucketCount(
+      "IOS.DefaultStatusAPI.HeuristicAssessment7",
+      DefaultStatusHeuristicAssessment::kFalsePositive, 2);
+  histogram_tester.ExpectBucketCount(
+      "IOS.DefaultStatusAPI.HeuristicAssessment14",
+      DefaultStatusHeuristicAssessment::kFalsePositive, 2);
+  histogram_tester.ExpectBucketCount(
+      "IOS.DefaultStatusAPI.HeuristicAssessment21",
+      DefaultStatusHeuristicAssessment::kFalsePositive, 2);
+  histogram_tester.ExpectBucketCount(
+      "IOS.DefaultStatusAPI.HeuristicAssessment28",
+      DefaultStatusHeuristicAssessment::kFalsePositive, 2);
+  histogram_tester.ExpectBucketCount(
+      "IOS.DefaultStatusAPI.HeuristicAssessment35",
+      DefaultStatusHeuristicAssessment::kFalsePositive, 2);
+  histogram_tester.ExpectBucketCount(
+      "IOS.DefaultStatusAPI.HeuristicAssessment42",
+      DefaultStatusHeuristicAssessment::kFalsePositive, 2);
+
+  NSDate* under_seven_days_ago =
+      (base::Time::Now() - base::Days(7) + base::Minutes(10)).ToNSDate();
+  SetObjectIntoStorageForKey(kLastHTTPURLOpenTime, under_seven_days_ago);
+  RecordHeuristicAssessments(/*is_default_in_system_api=*/true);
+  histogram_tester.ExpectBucketCount(
+      "IOS.DefaultStatusAPI.HeuristicAssessment1",
+      DefaultStatusHeuristicAssessment::kFalseNegative, 2);
+  histogram_tester.ExpectBucketCount(
+      "IOS.DefaultStatusAPI.HeuristicAssessment3",
+      DefaultStatusHeuristicAssessment::kFalseNegative, 1);
+  histogram_tester.ExpectBucketCount(
+      "IOS.DefaultStatusAPI.HeuristicAssessment7",
+      DefaultStatusHeuristicAssessment::kTruePositive, 3);
+  histogram_tester.ExpectBucketCount(
+      "IOS.DefaultStatusAPI.HeuristicAssessment14",
+      DefaultStatusHeuristicAssessment::kTruePositive, 3);
+  histogram_tester.ExpectBucketCount(
+      "IOS.DefaultStatusAPI.HeuristicAssessment21",
+      DefaultStatusHeuristicAssessment::kTruePositive, 3);
+  histogram_tester.ExpectBucketCount(
+      "IOS.DefaultStatusAPI.HeuristicAssessment28",
+      DefaultStatusHeuristicAssessment::kTruePositive, 3);
+  histogram_tester.ExpectBucketCount(
+      "IOS.DefaultStatusAPI.HeuristicAssessment35",
+      DefaultStatusHeuristicAssessment::kTruePositive, 3);
+  histogram_tester.ExpectBucketCount(
+      "IOS.DefaultStatusAPI.HeuristicAssessment42",
+      DefaultStatusHeuristicAssessment::kTruePositive, 3);
+  RecordHeuristicAssessments(/*is_default_in_system_api=*/false);
+  histogram_tester.ExpectBucketCount(
+      "IOS.DefaultStatusAPI.HeuristicAssessment1",
+      DefaultStatusHeuristicAssessment::kTrueNegative, 2);
+  histogram_tester.ExpectBucketCount(
+      "IOS.DefaultStatusAPI.HeuristicAssessment3",
+      DefaultStatusHeuristicAssessment::kTrueNegative, 1);
+  histogram_tester.ExpectBucketCount(
+      "IOS.DefaultStatusAPI.HeuristicAssessment7",
+      DefaultStatusHeuristicAssessment::kFalsePositive, 3);
+  histogram_tester.ExpectBucketCount(
+      "IOS.DefaultStatusAPI.HeuristicAssessment14",
+      DefaultStatusHeuristicAssessment::kFalsePositive, 3);
+  histogram_tester.ExpectBucketCount(
+      "IOS.DefaultStatusAPI.HeuristicAssessment21",
+      DefaultStatusHeuristicAssessment::kFalsePositive, 3);
+  histogram_tester.ExpectBucketCount(
+      "IOS.DefaultStatusAPI.HeuristicAssessment28",
+      DefaultStatusHeuristicAssessment::kFalsePositive, 3);
+  histogram_tester.ExpectBucketCount(
+      "IOS.DefaultStatusAPI.HeuristicAssessment35",
+      DefaultStatusHeuristicAssessment::kFalsePositive, 3);
+  histogram_tester.ExpectBucketCount(
+      "IOS.DefaultStatusAPI.HeuristicAssessment42",
+      DefaultStatusHeuristicAssessment::kFalsePositive, 3);
+
+  NSDate* under_fourteen_days_ago =
+      (base::Time::Now() - base::Days(14) + base::Minutes(10)).ToNSDate();
+  SetObjectIntoStorageForKey(kLastHTTPURLOpenTime, under_fourteen_days_ago);
+  RecordHeuristicAssessments(/*is_default_in_system_api=*/true);
+  histogram_tester.ExpectBucketCount(
+      "IOS.DefaultStatusAPI.HeuristicAssessment1",
+      DefaultStatusHeuristicAssessment::kFalseNegative, 3);
+  histogram_tester.ExpectBucketCount(
+      "IOS.DefaultStatusAPI.HeuristicAssessment3",
+      DefaultStatusHeuristicAssessment::kFalseNegative, 2);
+  histogram_tester.ExpectBucketCount(
+      "IOS.DefaultStatusAPI.HeuristicAssessment7",
+      DefaultStatusHeuristicAssessment::kFalseNegative, 1);
+  histogram_tester.ExpectBucketCount(
+      "IOS.DefaultStatusAPI.HeuristicAssessment14",
+      DefaultStatusHeuristicAssessment::kTruePositive, 4);
+  histogram_tester.ExpectBucketCount(
+      "IOS.DefaultStatusAPI.HeuristicAssessment21",
+      DefaultStatusHeuristicAssessment::kTruePositive, 4);
+  histogram_tester.ExpectBucketCount(
+      "IOS.DefaultStatusAPI.HeuristicAssessment28",
+      DefaultStatusHeuristicAssessment::kTruePositive, 4);
+  histogram_tester.ExpectBucketCount(
+      "IOS.DefaultStatusAPI.HeuristicAssessment35",
+      DefaultStatusHeuristicAssessment::kTruePositive, 4);
+  histogram_tester.ExpectBucketCount(
+      "IOS.DefaultStatusAPI.HeuristicAssessment42",
+      DefaultStatusHeuristicAssessment::kTruePositive, 4);
+  RecordHeuristicAssessments(/*is_default_in_system_api=*/false);
+  histogram_tester.ExpectBucketCount(
+      "IOS.DefaultStatusAPI.HeuristicAssessment1",
+      DefaultStatusHeuristicAssessment::kTrueNegative, 3);
+  histogram_tester.ExpectBucketCount(
+      "IOS.DefaultStatusAPI.HeuristicAssessment3",
+      DefaultStatusHeuristicAssessment::kTrueNegative, 2);
+  histogram_tester.ExpectBucketCount(
+      "IOS.DefaultStatusAPI.HeuristicAssessment7",
+      DefaultStatusHeuristicAssessment::kTrueNegative, 1);
+  histogram_tester.ExpectBucketCount(
+      "IOS.DefaultStatusAPI.HeuristicAssessment14",
+      DefaultStatusHeuristicAssessment::kFalsePositive, 4);
+  histogram_tester.ExpectBucketCount(
+      "IOS.DefaultStatusAPI.HeuristicAssessment21",
+      DefaultStatusHeuristicAssessment::kFalsePositive, 4);
+  histogram_tester.ExpectBucketCount(
+      "IOS.DefaultStatusAPI.HeuristicAssessment28",
+      DefaultStatusHeuristicAssessment::kFalsePositive, 4);
+  histogram_tester.ExpectBucketCount(
+      "IOS.DefaultStatusAPI.HeuristicAssessment35",
+      DefaultStatusHeuristicAssessment::kFalsePositive, 4);
+  histogram_tester.ExpectBucketCount(
+      "IOS.DefaultStatusAPI.HeuristicAssessment42",
+      DefaultStatusHeuristicAssessment::kFalsePositive, 4);
+
+  NSDate* under_twenty_one_days_ago =
+      (base::Time::Now() - base::Days(21) + base::Minutes(10)).ToNSDate();
+  SetObjectIntoStorageForKey(kLastHTTPURLOpenTime, under_twenty_one_days_ago);
+  RecordHeuristicAssessments(/*is_default_in_system_api=*/true);
+  histogram_tester.ExpectBucketCount(
+      "IOS.DefaultStatusAPI.HeuristicAssessment1",
+      DefaultStatusHeuristicAssessment::kFalseNegative, 4);
+  histogram_tester.ExpectBucketCount(
+      "IOS.DefaultStatusAPI.HeuristicAssessment3",
+      DefaultStatusHeuristicAssessment::kFalseNegative, 3);
+  histogram_tester.ExpectBucketCount(
+      "IOS.DefaultStatusAPI.HeuristicAssessment7",
+      DefaultStatusHeuristicAssessment::kFalseNegative, 2);
+  histogram_tester.ExpectBucketCount(
+      "IOS.DefaultStatusAPI.HeuristicAssessment14",
+      DefaultStatusHeuristicAssessment::kFalseNegative, 1);
+  histogram_tester.ExpectBucketCount(
+      "IOS.DefaultStatusAPI.HeuristicAssessment21",
+      DefaultStatusHeuristicAssessment::kTruePositive, 5);
+  histogram_tester.ExpectBucketCount(
+      "IOS.DefaultStatusAPI.HeuristicAssessment28",
+      DefaultStatusHeuristicAssessment::kTruePositive, 5);
+  histogram_tester.ExpectBucketCount(
+      "IOS.DefaultStatusAPI.HeuristicAssessment35",
+      DefaultStatusHeuristicAssessment::kTruePositive, 5);
+  histogram_tester.ExpectBucketCount(
+      "IOS.DefaultStatusAPI.HeuristicAssessment42",
+      DefaultStatusHeuristicAssessment::kTruePositive, 5);
+  RecordHeuristicAssessments(/*is_default_in_system_api=*/false);
+  histogram_tester.ExpectBucketCount(
+      "IOS.DefaultStatusAPI.HeuristicAssessment1",
+      DefaultStatusHeuristicAssessment::kTrueNegative, 4);
+  histogram_tester.ExpectBucketCount(
+      "IOS.DefaultStatusAPI.HeuristicAssessment3",
+      DefaultStatusHeuristicAssessment::kTrueNegative, 3);
+  histogram_tester.ExpectBucketCount(
+      "IOS.DefaultStatusAPI.HeuristicAssessment7",
+      DefaultStatusHeuristicAssessment::kTrueNegative, 2);
+  histogram_tester.ExpectBucketCount(
+      "IOS.DefaultStatusAPI.HeuristicAssessment14",
+      DefaultStatusHeuristicAssessment::kTrueNegative, 1);
+  histogram_tester.ExpectBucketCount(
+      "IOS.DefaultStatusAPI.HeuristicAssessment21",
+      DefaultStatusHeuristicAssessment::kFalsePositive, 5);
+  histogram_tester.ExpectBucketCount(
+      "IOS.DefaultStatusAPI.HeuristicAssessment28",
+      DefaultStatusHeuristicAssessment::kFalsePositive, 5);
+  histogram_tester.ExpectBucketCount(
+      "IOS.DefaultStatusAPI.HeuristicAssessment35",
+      DefaultStatusHeuristicAssessment::kFalsePositive, 5);
+  histogram_tester.ExpectBucketCount(
+      "IOS.DefaultStatusAPI.HeuristicAssessment42",
+      DefaultStatusHeuristicAssessment::kFalsePositive, 5);
+
+  NSDate* under_twenty_eight_days_ago =
+      (base::Time::Now() - base::Days(28) + base::Minutes(10)).ToNSDate();
+  SetObjectIntoStorageForKey(kLastHTTPURLOpenTime, under_twenty_eight_days_ago);
+  RecordHeuristicAssessments(/*is_default_in_system_api=*/true);
+  histogram_tester.ExpectBucketCount(
+      "IOS.DefaultStatusAPI.HeuristicAssessment1",
+      DefaultStatusHeuristicAssessment::kFalseNegative, 5);
+  histogram_tester.ExpectBucketCount(
+      "IOS.DefaultStatusAPI.HeuristicAssessment3",
+      DefaultStatusHeuristicAssessment::kFalseNegative, 4);
+  histogram_tester.ExpectBucketCount(
+      "IOS.DefaultStatusAPI.HeuristicAssessment7",
+      DefaultStatusHeuristicAssessment::kFalseNegative, 3);
+  histogram_tester.ExpectBucketCount(
+      "IOS.DefaultStatusAPI.HeuristicAssessment14",
+      DefaultStatusHeuristicAssessment::kFalseNegative, 2);
+  histogram_tester.ExpectBucketCount(
+      "IOS.DefaultStatusAPI.HeuristicAssessment21",
+      DefaultStatusHeuristicAssessment::kFalseNegative, 1);
+  histogram_tester.ExpectBucketCount(
+      "IOS.DefaultStatusAPI.HeuristicAssessment28",
+      DefaultStatusHeuristicAssessment::kTruePositive, 6);
+  histogram_tester.ExpectBucketCount(
+      "IOS.DefaultStatusAPI.HeuristicAssessment35",
+      DefaultStatusHeuristicAssessment::kTruePositive, 6);
+  histogram_tester.ExpectBucketCount(
+      "IOS.DefaultStatusAPI.HeuristicAssessment42",
+      DefaultStatusHeuristicAssessment::kTruePositive, 6);
+  RecordHeuristicAssessments(/*is_default_in_system_api=*/false);
+  histogram_tester.ExpectBucketCount(
+      "IOS.DefaultStatusAPI.HeuristicAssessment1",
+      DefaultStatusHeuristicAssessment::kTrueNegative, 5);
+  histogram_tester.ExpectBucketCount(
+      "IOS.DefaultStatusAPI.HeuristicAssessment3",
+      DefaultStatusHeuristicAssessment::kTrueNegative, 4);
+  histogram_tester.ExpectBucketCount(
+      "IOS.DefaultStatusAPI.HeuristicAssessment7",
+      DefaultStatusHeuristicAssessment::kTrueNegative, 3);
+  histogram_tester.ExpectBucketCount(
+      "IOS.DefaultStatusAPI.HeuristicAssessment14",
+      DefaultStatusHeuristicAssessment::kTrueNegative, 2);
+  histogram_tester.ExpectBucketCount(
+      "IOS.DefaultStatusAPI.HeuristicAssessment21",
+      DefaultStatusHeuristicAssessment::kTrueNegative, 1);
+  histogram_tester.ExpectBucketCount(
+      "IOS.DefaultStatusAPI.HeuristicAssessment28",
+      DefaultStatusHeuristicAssessment::kFalsePositive, 6);
+  histogram_tester.ExpectBucketCount(
+      "IOS.DefaultStatusAPI.HeuristicAssessment35",
+      DefaultStatusHeuristicAssessment::kFalsePositive, 6);
+  histogram_tester.ExpectBucketCount(
+      "IOS.DefaultStatusAPI.HeuristicAssessment42",
+      DefaultStatusHeuristicAssessment::kFalsePositive, 6);
+
+  NSDate* under_thirty_five_days_ago =
+      (base::Time::Now() - base::Days(35) + base::Minutes(10)).ToNSDate();
+  SetObjectIntoStorageForKey(kLastHTTPURLOpenTime, under_thirty_five_days_ago);
+  RecordHeuristicAssessments(/*is_default_in_system_api=*/true);
+  histogram_tester.ExpectBucketCount(
+      "IOS.DefaultStatusAPI.HeuristicAssessment1",
+      DefaultStatusHeuristicAssessment::kFalseNegative, 6);
+  histogram_tester.ExpectBucketCount(
+      "IOS.DefaultStatusAPI.HeuristicAssessment3",
+      DefaultStatusHeuristicAssessment::kFalseNegative, 5);
+  histogram_tester.ExpectBucketCount(
+      "IOS.DefaultStatusAPI.HeuristicAssessment7",
+      DefaultStatusHeuristicAssessment::kFalseNegative, 4);
+  histogram_tester.ExpectBucketCount(
+      "IOS.DefaultStatusAPI.HeuristicAssessment14",
+      DefaultStatusHeuristicAssessment::kFalseNegative, 3);
+  histogram_tester.ExpectBucketCount(
+      "IOS.DefaultStatusAPI.HeuristicAssessment21",
+      DefaultStatusHeuristicAssessment::kFalseNegative, 2);
+  histogram_tester.ExpectBucketCount(
+      "IOS.DefaultStatusAPI.HeuristicAssessment28",
+      DefaultStatusHeuristicAssessment::kFalseNegative, 1);
+  histogram_tester.ExpectBucketCount(
+      "IOS.DefaultStatusAPI.HeuristicAssessment35",
+      DefaultStatusHeuristicAssessment::kTruePositive, 7);
+  histogram_tester.ExpectBucketCount(
+      "IOS.DefaultStatusAPI.HeuristicAssessment42",
+      DefaultStatusHeuristicAssessment::kTruePositive, 7);
+  RecordHeuristicAssessments(/*is_default_in_system_api=*/false);
+  histogram_tester.ExpectBucketCount(
+      "IOS.DefaultStatusAPI.HeuristicAssessment1",
+      DefaultStatusHeuristicAssessment::kTrueNegative, 6);
+  histogram_tester.ExpectBucketCount(
+      "IOS.DefaultStatusAPI.HeuristicAssessment3",
+      DefaultStatusHeuristicAssessment::kTrueNegative, 5);
+  histogram_tester.ExpectBucketCount(
+      "IOS.DefaultStatusAPI.HeuristicAssessment7",
+      DefaultStatusHeuristicAssessment::kTrueNegative, 4);
+  histogram_tester.ExpectBucketCount(
+      "IOS.DefaultStatusAPI.HeuristicAssessment14",
+      DefaultStatusHeuristicAssessment::kTrueNegative, 3);
+  histogram_tester.ExpectBucketCount(
+      "IOS.DefaultStatusAPI.HeuristicAssessment21",
+      DefaultStatusHeuristicAssessment::kTrueNegative, 2);
+  histogram_tester.ExpectBucketCount(
+      "IOS.DefaultStatusAPI.HeuristicAssessment28",
+      DefaultStatusHeuristicAssessment::kTrueNegative, 1);
+  histogram_tester.ExpectBucketCount(
+      "IOS.DefaultStatusAPI.HeuristicAssessment35",
+      DefaultStatusHeuristicAssessment::kFalsePositive, 7);
+  histogram_tester.ExpectBucketCount(
+      "IOS.DefaultStatusAPI.HeuristicAssessment42",
+      DefaultStatusHeuristicAssessment::kFalsePositive, 7);
+
+  NSDate* under_forty_two_days_ago =
+      (base::Time::Now() - base::Days(42) + base::Minutes(10)).ToNSDate();
+  SetObjectIntoStorageForKey(kLastHTTPURLOpenTime, under_forty_two_days_ago);
+  RecordHeuristicAssessments(/*is_default_in_system_api=*/true);
+  histogram_tester.ExpectBucketCount(
+      "IOS.DefaultStatusAPI.HeuristicAssessment1",
+      DefaultStatusHeuristicAssessment::kFalseNegative, 7);
+  histogram_tester.ExpectBucketCount(
+      "IOS.DefaultStatusAPI.HeuristicAssessment3",
+      DefaultStatusHeuristicAssessment::kFalseNegative, 6);
+  histogram_tester.ExpectBucketCount(
+      "IOS.DefaultStatusAPI.HeuristicAssessment7",
+      DefaultStatusHeuristicAssessment::kFalseNegative, 5);
+  histogram_tester.ExpectBucketCount(
+      "IOS.DefaultStatusAPI.HeuristicAssessment14",
+      DefaultStatusHeuristicAssessment::kFalseNegative, 4);
+  histogram_tester.ExpectBucketCount(
+      "IOS.DefaultStatusAPI.HeuristicAssessment21",
+      DefaultStatusHeuristicAssessment::kFalseNegative, 3);
+  histogram_tester.ExpectBucketCount(
+      "IOS.DefaultStatusAPI.HeuristicAssessment28",
+      DefaultStatusHeuristicAssessment::kFalseNegative, 2);
+  histogram_tester.ExpectBucketCount(
+      "IOS.DefaultStatusAPI.HeuristicAssessment35",
+      DefaultStatusHeuristicAssessment::kFalseNegative, 1);
+  histogram_tester.ExpectBucketCount(
+      "IOS.DefaultStatusAPI.HeuristicAssessment42",
+      DefaultStatusHeuristicAssessment::kTruePositive, 8);
+  RecordHeuristicAssessments(/*is_default_in_system_api=*/false);
+  histogram_tester.ExpectBucketCount(
+      "IOS.DefaultStatusAPI.HeuristicAssessment1",
+      DefaultStatusHeuristicAssessment::kTrueNegative, 7);
+  histogram_tester.ExpectBucketCount(
+      "IOS.DefaultStatusAPI.HeuristicAssessment3",
+      DefaultStatusHeuristicAssessment::kTrueNegative, 6);
+  histogram_tester.ExpectBucketCount(
+      "IOS.DefaultStatusAPI.HeuristicAssessment7",
+      DefaultStatusHeuristicAssessment::kTrueNegative, 5);
+  histogram_tester.ExpectBucketCount(
+      "IOS.DefaultStatusAPI.HeuristicAssessment14",
+      DefaultStatusHeuristicAssessment::kTrueNegative, 4);
+  histogram_tester.ExpectBucketCount(
+      "IOS.DefaultStatusAPI.HeuristicAssessment21",
+      DefaultStatusHeuristicAssessment::kTrueNegative, 3);
+  histogram_tester.ExpectBucketCount(
+      "IOS.DefaultStatusAPI.HeuristicAssessment28",
+      DefaultStatusHeuristicAssessment::kTrueNegative, 2);
+  histogram_tester.ExpectBucketCount(
+      "IOS.DefaultStatusAPI.HeuristicAssessment35",
+      DefaultStatusHeuristicAssessment::kTrueNegative, 1);
+  histogram_tester.ExpectBucketCount(
+      "IOS.DefaultStatusAPI.HeuristicAssessment42",
+      DefaultStatusHeuristicAssessment::kFalsePositive, 8);
+
+  NSDate* over_forty_two_days_ago =
+      (base::Time::Now() - base::Days(45)).ToNSDate();
+  SetObjectIntoStorageForKey(kLastHTTPURLOpenTime, over_forty_two_days_ago);
+  RecordHeuristicAssessments(/*is_default_in_system_api=*/true);
+  histogram_tester.ExpectBucketCount(
+      "IOS.DefaultStatusAPI.HeuristicAssessment1",
+      DefaultStatusHeuristicAssessment::kFalseNegative, 8);
+  histogram_tester.ExpectBucketCount(
+      "IOS.DefaultStatusAPI.HeuristicAssessment3",
+      DefaultStatusHeuristicAssessment::kFalseNegative, 7);
+  histogram_tester.ExpectBucketCount(
+      "IOS.DefaultStatusAPI.HeuristicAssessment7",
+      DefaultStatusHeuristicAssessment::kFalseNegative, 6);
+  histogram_tester.ExpectBucketCount(
+      "IOS.DefaultStatusAPI.HeuristicAssessment14",
+      DefaultStatusHeuristicAssessment::kFalseNegative, 5);
+  histogram_tester.ExpectBucketCount(
+      "IOS.DefaultStatusAPI.HeuristicAssessment21",
+      DefaultStatusHeuristicAssessment::kFalseNegative, 4);
+  histogram_tester.ExpectBucketCount(
+      "IOS.DefaultStatusAPI.HeuristicAssessment28",
+      DefaultStatusHeuristicAssessment::kFalseNegative, 3);
+  histogram_tester.ExpectBucketCount(
+      "IOS.DefaultStatusAPI.HeuristicAssessment35",
+      DefaultStatusHeuristicAssessment::kFalseNegative, 2);
+  histogram_tester.ExpectBucketCount(
+      "IOS.DefaultStatusAPI.HeuristicAssessment42",
+      DefaultStatusHeuristicAssessment::kFalseNegative, 1);
+  RecordHeuristicAssessments(/*is_default_in_system_api=*/false);
+  histogram_tester.ExpectBucketCount(
+      "IOS.DefaultStatusAPI.HeuristicAssessment1",
+      DefaultStatusHeuristicAssessment::kTrueNegative, 8);
+  histogram_tester.ExpectBucketCount(
+      "IOS.DefaultStatusAPI.HeuristicAssessment3",
+      DefaultStatusHeuristicAssessment::kTrueNegative, 7);
+  histogram_tester.ExpectBucketCount(
+      "IOS.DefaultStatusAPI.HeuristicAssessment7",
+      DefaultStatusHeuristicAssessment::kTrueNegative, 6);
+  histogram_tester.ExpectBucketCount(
+      "IOS.DefaultStatusAPI.HeuristicAssessment14",
+      DefaultStatusHeuristicAssessment::kTrueNegative, 5);
+  histogram_tester.ExpectBucketCount(
+      "IOS.DefaultStatusAPI.HeuristicAssessment21",
+      DefaultStatusHeuristicAssessment::kTrueNegative, 4);
+  histogram_tester.ExpectBucketCount(
+      "IOS.DefaultStatusAPI.HeuristicAssessment28",
+      DefaultStatusHeuristicAssessment::kTrueNegative, 3);
+  histogram_tester.ExpectBucketCount(
+      "IOS.DefaultStatusAPI.HeuristicAssessment35",
+      DefaultStatusHeuristicAssessment::kTrueNegative, 2);
+  histogram_tester.ExpectBucketCount(
+      "IOS.DefaultStatusAPI.HeuristicAssessment42",
+      DefaultStatusHeuristicAssessment::kTrueNegative, 1);
+
+  histogram_tester.ExpectTotalCount("IOS.DefaultStatusAPI.HeuristicAssessment1",
+                                    18);
+  histogram_tester.ExpectTotalCount("IOS.DefaultStatusAPI.HeuristicAssessment3",
+                                    18);
+  histogram_tester.ExpectTotalCount("IOS.DefaultStatusAPI.HeuristicAssessment7",
+                                    18);
+  histogram_tester.ExpectTotalCount(
+      "IOS.DefaultStatusAPI.HeuristicAssessment14", 18);
+  histogram_tester.ExpectTotalCount(
+      "IOS.DefaultStatusAPI.HeuristicAssessment21", 18);
+  histogram_tester.ExpectTotalCount(
+      "IOS.DefaultStatusAPI.HeuristicAssessment28", 18);
+  histogram_tester.ExpectTotalCount(
+      "IOS.DefaultStatusAPI.HeuristicAssessment35", 18);
+  histogram_tester.ExpectTotalCount(
+      "IOS.DefaultStatusAPI.HeuristicAssessment42", 18);
+}
+
+}  // namespace default_status
diff --git a/ios/chrome/browser/default_browser/model/default_status/default_status_helper_prefs.h b/ios/chrome/browser/default_browser/model/default_status/default_status_helper_prefs.h
new file mode 100644
index 0000000..039196b
--- /dev/null
+++ b/ios/chrome/browser/default_browser/model/default_status/default_status_helper_prefs.h
@@ -0,0 +1,34 @@
+// Copyright 2025 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_DEFAULT_BROWSER_MODEL_DEFAULT_STATUS_DEFAULT_STATUS_HELPER_PREFS_H_
+#define IOS_CHROME_BROWSER_DEFAULT_BROWSER_MODEL_DEFAULT_STATUS_DEFAULT_STATUS_HELPER_PREFS_H_
+
+class PrefRegistrySimple;
+
+namespace default_status {
+
+// The cohort this client belongs to for the purpose of reporting the result of
+// the default status system API call.
+extern const char kDefaultStatusAPICohort[];
+
+// The timestsamp of the last successful call to the default status API call. A
+// successful call is one that does not yield an error, regardless of the
+// default status result.
+extern const char kDefaultStatusAPILastSuccessfulCall[];
+
+// The timestamp after which the default status API should be called again for
+// this client.
+extern const char kDefaultStatusAPINextRetry[];
+
+// The result of the last successful default status API call. The value is of
+// type DefaultStatusCheckResult and defaults to kUnknown.
+extern const char kDefaultStatusAPIResult[];
+
+// Registers the prefs associated with the default status helper.
+void RegisterDefaultStatusPrefs(PrefRegistrySimple* registry);
+
+}  // namespace default_status
+
+#endif  // IOS_CHROME_BROWSER_DEFAULT_BROWSER_MODEL_DEFAULT_STATUS_DEFAULT_STATUS_HELPER_PREFS_H_
diff --git a/ios/chrome/browser/default_browser/model/default_status/default_status_helper_prefs.mm b/ios/chrome/browser/default_browser/model/default_status/default_status_helper_prefs.mm
new file mode 100644
index 0000000..8f078cea3
--- /dev/null
+++ b/ios/chrome/browser/default_browser/model/default_status/default_status_helper_prefs.mm
@@ -0,0 +1,28 @@
+// Copyright 2025 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/default_browser/model/default_status/default_status_helper_prefs.h"
+
+#import "base/time/time.h"
+#import "components/prefs/pref_registry_simple.h"
+#import "ios/chrome/browser/default_browser/model/default_status/default_status_helper_types.h"
+
+namespace default_status {
+
+const char kDefaultStatusAPICohort[] = "DefaultStatusAPICohort";
+const char kDefaultStatusAPILastSuccessfulCall[] =
+    "DefaultStatusAPILastSuccessfulCall";
+const char kDefaultStatusAPINextRetry[] = "DefaultStatusAPINextRetry";
+const char kDefaultStatusAPIResult[] = "DefaultStatusAPIResult";
+
+void RegisterDefaultStatusPrefs(PrefRegistrySimple* registry) {
+  registry->RegisterIntegerPref(kDefaultStatusAPICohort, 0);
+  registry->RegisterTimePref(kDefaultStatusAPILastSuccessfulCall, base::Time());
+  registry->RegisterTimePref(kDefaultStatusAPINextRetry, base::Time());
+  registry->RegisterIntegerPref(
+      kDefaultStatusAPIResult,
+      static_cast<int>(DefaultStatusAPIResult::kUnknown));
+}
+
+}  // namespace default_status
diff --git a/ios/chrome/browser/default_browser/model/default_status/default_status_helper_types.h b/ios/chrome/browser/default_browser/model/default_status/default_status_helper_types.h
new file mode 100644
index 0000000..76cd632
--- /dev/null
+++ b/ios/chrome/browser/default_browser/model/default_status/default_status_helper_types.h
@@ -0,0 +1,61 @@
+// Copyright 2025 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_DEFAULT_BROWSER_MODEL_DEFAULT_STATUS_DEFAULT_STATUS_HELPER_TYPES_H_
+#define IOS_CHROME_BROWSER_DEFAULT_BROWSER_MODEL_DEFAULT_STATUS_DEFAULT_STATUS_HELPER_TYPES_H_
+
+namespace default_status {
+
+// The possible outcomes of querying the system default status API. This enum
+// must match the UMA histogram enum IOSDefaultStatusAPIOutcomeType.
+//
+// LINT.IfChange(DefaultStatusAPIOutcomeType)
+enum class DefaultStatusAPIOutcomeType {
+  kSuccess = 1,
+  kCooldownError = 2,
+  kOtherError = 3,
+  kMaxValue = kOtherError
+};
+// LINT.ThenChange(/tools/metrics/histograms/metadata/ios/enums.xml:IOSDefaultStatusAPIOutcomeType)
+
+// The true / false result of querying the system default status API, with an
+// added "unknown" value to distinguish cases where no successful call has been
+// made yet.
+enum class DefaultStatusAPIResult {
+  kUnknown = 0,
+  kIsDefault = 1,
+  kIsNotDefault = 2,
+  kMaxValue = kIsNotDefault
+};
+
+// The assessment of the value returned by the IsChromeLikelyDefaultBrowserXDays
+// heuristic based on the actual result from the default status system API. This
+// enum must match the UMA histogram enum IOSDefaultStatusHeuristicAssessment.
+//
+// LINT.IfChange(DefaultStatusHeuristicAssessment)
+enum class DefaultStatusHeuristicAssessment {
+  kTrueNegative = 0,
+  kFalseNegative = 1,
+  kTruePositive = 2,
+  kFalsePositive = 3,
+  kMaxValue = kFalsePositive
+};
+// LINT.ThenChange(/tools/metrics/histograms/metadata/ios/enums.xml:IOSDefaultStatusHeuristicAssessment)
+
+// The possible changes in default status year over year. This enum must match
+// the UMA histogram enum IOSDefaultStatusRetention.
+//
+// LINT.IfChange(DefaultStatusRetention)
+enum class DefaultStatusRetention {
+  kBecameDefault = 0,
+  kBecameNonDefault = 1,
+  kRemainedDefault = 2,
+  kRemainedNonDefault = 3,
+  kMaxValue = kRemainedNonDefault
+};
+// LINT.ThenChange(/tools/metrics/histograms/metadata/ios/enums.xml:IOSDefaultStatusRetention)
+
+}  // namespace default_status
+
+#endif  // IOS_CHROME_BROWSER_DEFAULT_BROWSER_MODEL_DEFAULT_STATUS_DEFAULT_STATUS_HELPER_TYPES_H_
diff --git a/ios/chrome/browser/default_browser/model/default_status/default_status_helper_unittest.mm b/ios/chrome/browser/default_browser/model/default_status/default_status_helper_unittest.mm
new file mode 100644
index 0000000..e18cc9d
--- /dev/null
+++ b/ios/chrome/browser/default_browser/model/default_status/default_status_helper_unittest.mm
@@ -0,0 +1,932 @@
+// Copyright 2025 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/default_browser/model/default_status/default_status_helper.h"
+
+#import <UIKit/UIKit.h>
+
+#import "base/test/metrics/histogram_tester.h"
+#import "base/test/scoped_feature_list.h"
+#import "base/time/time_override.h"
+#import "components/prefs/testing_pref_service.h"
+#import "ios/chrome/browser/default_browser/model/default_status/default_status_helper_constants.h"
+#import "ios/chrome/browser/default_browser/model/default_status/default_status_helper_prefs.h"
+#import "ios/chrome/browser/default_browser/model/default_status/default_status_helper_types.h"
+#import "ios/chrome/browser/default_browser/model/utils.h"
+#import "ios/chrome/browser/shared/model/utils/first_run_test_util.h"
+#import "ios/chrome/browser/shared/public/features/features.h"
+#import "testing/platform_test.h"
+
+namespace default_status {
+
+namespace {
+
+void ExpectSuccessHistogramsToHaveNoSamples(
+    const base::HistogramTester& histogram_tester) {
+  histogram_tester.ExpectTotalCount(
+      "IOS.DefaultStatusAPI.DaysSinceLastSuccessfulCall", 0);
+  histogram_tester.ExpectTotalCount("IOS.DefaultStatusAPI.IsDefaultBrowser", 0);
+  histogram_tester.ExpectTotalCount(
+      "IOS.DefaultStatusAPI.IsDefaultBrowser.Cohort1", 0);
+  histogram_tester.ExpectTotalCount(
+      "IOS.DefaultStatusAPI.IsDefaultBrowser.Cohort2", 0);
+  histogram_tester.ExpectTotalCount(
+      "IOS.DefaultStatusAPI.IsDefaultBrowser.Cohort3", 0);
+  histogram_tester.ExpectTotalCount(
+      "IOS.DefaultStatusAPI.IsDefaultBrowser.StrictCohorts", 0);
+  histogram_tester.ExpectTotalCount(
+      "IOS.DefaultStatusAPI.DefaultStatusRetention", 0);
+  histogram_tester.ExpectTotalCount("IOS.DefaultStatusAPI.HeuristicAssessment1",
+                                    0);
+  histogram_tester.ExpectTotalCount("IOS.DefaultStatusAPI.HeuristicAssessment3",
+                                    0);
+  histogram_tester.ExpectTotalCount("IOS.DefaultStatusAPI.HeuristicAssessment7",
+                                    0);
+  histogram_tester.ExpectTotalCount(
+      "IOS.DefaultStatusAPI.HeuristicAssessment14", 0);
+  histogram_tester.ExpectTotalCount(
+      "IOS.DefaultStatusAPI.HeuristicAssessment21", 0);
+  histogram_tester.ExpectTotalCount(
+      "IOS.DefaultStatusAPI.HeuristicAssessment28", 0);
+  histogram_tester.ExpectTotalCount(
+      "IOS.DefaultStatusAPI.HeuristicAssessment35", 0);
+  histogram_tester.ExpectTotalCount(
+      "IOS.DefaultStatusAPI.HeuristicAssessment42", 0);
+}
+
+}  // namespace
+
+// Test fixture for testing the default status helper.
+class DefaultStatusHelperTest : public PlatformTest {
+ public:
+  DefaultStatusHelperTest() {
+    scoped_feature_list_.InitWithFeatures(
+        /* enabled_features */ {kRunDefaultStatusCheck},
+        /* disabled_features */ {});
+    RegisterPrefs();
+  }
+
+  static base::Time NowOverride() { return now_override_; }
+
+  static void SetNowOverride(base::Time now_override) {
+    now_override_ = now_override;
+  }
+
+ protected:
+  void RegisterPrefs() { RegisterDefaultStatusPrefs(prefs()->registry()); }
+
+  TestingPrefServiceSimple* prefs() { return &pref_service_; }
+
+  base::test::ScopedFeatureList scoped_feature_list_;
+  TestingPrefServiceSimple pref_service_;
+  static base::Time now_override_;
+};
+
+base::Time DefaultStatusHelperTest::now_override_;
+
+// Tests IsNewClient.
+TEST_F(DefaultStatusHelperTest, IsNewClient) {
+  ResetFirstRunSentinel();
+  EXPECT_TRUE(internal::IsNewClient());
+  ForceFirstRunRecency(27);
+  EXPECT_TRUE(internal::IsNewClient());
+  ForceFirstRunRecency(29);
+  EXPECT_FALSE(internal::IsNewClient());
+}
+
+// Tests HasCohortAssigned.
+TEST_F(DefaultStatusHelperTest, HasCohortAssigned) {
+  prefs()->ClearPref(kDefaultStatusAPICohort);
+  EXPECT_FALSE(internal::HasCohortAssigned(prefs()));
+  prefs()->SetInteger(kDefaultStatusAPICohort, 0);
+  EXPECT_FALSE(internal::HasCohortAssigned(prefs()));
+  prefs()->SetInteger(kDefaultStatusAPICohort, 1);
+  EXPECT_TRUE(internal::HasCohortAssigned(prefs()));
+}
+
+// Tests GenerateRandomCohort.
+TEST_F(DefaultStatusHelperTest, GenerateRandomCohort) {
+  for (int i = 0; i < 1000; ++i) {
+    int cohort = internal::GenerateRandomCohort();
+    EXPECT_TRUE(cohort > 0 && cohort <= kCohortCount);
+  }
+}
+
+// Tests GetCohortNextStartDate.
+TEST_F(DefaultStatusHelperTest, GetCohortNextStartDate) {
+  base::subtle::ScopedTimeClockOverrides time_override(
+      &DefaultStatusHelperTest::NowOverride, nullptr, nullptr);
+  base::Time now_override;
+  base::Time expected;
+
+  // January.
+  EXPECT_TRUE(base::Time::FromUTCString("2025-01-15", &now_override));
+  SetNowOverride(now_override);
+
+  EXPECT_TRUE(base::Time::FromUTCString("2025-01-01", &expected));
+  EXPECT_EQ(internal::GetCohortNextStartDate(/*cohort_number=*/1), expected);
+  EXPECT_TRUE(base::Time::FromUTCString("2025-02-01", &expected));
+  EXPECT_EQ(internal::GetCohortNextStartDate(/*cohort_number=*/2), expected);
+  EXPECT_TRUE(base::Time::FromUTCString("2025-03-01", &expected));
+  EXPECT_EQ(internal::GetCohortNextStartDate(/*cohort_number=*/3), expected);
+  EXPECT_TRUE(base::Time::FromUTCString("2025-04-01", &expected));
+  EXPECT_EQ(internal::GetCohortNextStartDate(/*cohort_number=*/4), expected);
+
+  EXPECT_TRUE(base::Time::FromUTCString("2025-05-01", &expected));
+  EXPECT_EQ(internal::GetCohortNextStartDate(/*cohort_number=*/1,
+                                             /*after_current_month=*/true),
+            expected);
+  EXPECT_TRUE(base::Time::FromUTCString("2025-02-01", &expected));
+  EXPECT_EQ(internal::GetCohortNextStartDate(/*cohort_number=*/2,
+                                             /*after_current_month=*/true),
+            expected);
+  EXPECT_TRUE(base::Time::FromUTCString("2025-03-01", &expected));
+  EXPECT_EQ(internal::GetCohortNextStartDate(/*cohort_number=*/3,
+                                             /*after_current_month=*/true),
+            expected);
+  EXPECT_TRUE(base::Time::FromUTCString("2025-04-01", &expected));
+  EXPECT_EQ(internal::GetCohortNextStartDate(/*cohort_number=*/4,
+                                             /*after_current_month=*/true),
+            expected);
+
+  // February.
+  EXPECT_TRUE(base::Time::FromUTCString("2025-02-15", &now_override));
+  SetNowOverride(now_override);
+
+  EXPECT_TRUE(base::Time::FromUTCString("2025-05-01", &expected));
+  EXPECT_EQ(internal::GetCohortNextStartDate(/*cohort_number=*/1), expected);
+  EXPECT_TRUE(base::Time::FromUTCString("2025-02-01", &expected));
+  EXPECT_EQ(internal::GetCohortNextStartDate(/*cohort_number=*/2), expected);
+  EXPECT_TRUE(base::Time::FromUTCString("2025-03-01", &expected));
+  EXPECT_EQ(internal::GetCohortNextStartDate(/*cohort_number=*/3), expected);
+  EXPECT_TRUE(base::Time::FromUTCString("2025-04-01", &expected));
+  EXPECT_EQ(internal::GetCohortNextStartDate(/*cohort_number=*/4), expected);
+
+  EXPECT_TRUE(base::Time::FromUTCString("2025-05-01", &expected));
+  EXPECT_EQ(internal::GetCohortNextStartDate(/*cohort_number=*/1,
+                                             /*after_current_month=*/true),
+            expected);
+  EXPECT_TRUE(base::Time::FromUTCString("2025-06-01", &expected));
+  EXPECT_EQ(internal::GetCohortNextStartDate(/*cohort_number=*/2,
+                                             /*after_current_month=*/true),
+            expected);
+  EXPECT_TRUE(base::Time::FromUTCString("2025-03-01", &expected));
+  EXPECT_EQ(internal::GetCohortNextStartDate(/*cohort_number=*/3,
+                                             /*after_current_month=*/true),
+            expected);
+  EXPECT_TRUE(base::Time::FromUTCString("2025-04-01", &expected));
+  EXPECT_EQ(internal::GetCohortNextStartDate(/*cohort_number=*/4,
+                                             /*after_current_month=*/true),
+            expected);
+
+  // March.
+  EXPECT_TRUE(base::Time::FromUTCString("2025-03-15", &now_override));
+  SetNowOverride(now_override);
+
+  EXPECT_TRUE(base::Time::FromUTCString("2025-05-01", &expected));
+  EXPECT_EQ(internal::GetCohortNextStartDate(/*cohort_number=*/1), expected);
+  EXPECT_TRUE(base::Time::FromUTCString("2025-06-01", &expected));
+  EXPECT_EQ(internal::GetCohortNextStartDate(/*cohort_number=*/2), expected);
+  EXPECT_TRUE(base::Time::FromUTCString("2025-03-01", &expected));
+  EXPECT_EQ(internal::GetCohortNextStartDate(/*cohort_number=*/3), expected);
+  EXPECT_TRUE(base::Time::FromUTCString("2025-04-01", &expected));
+  EXPECT_EQ(internal::GetCohortNextStartDate(/*cohort_number=*/4), expected);
+
+  EXPECT_TRUE(base::Time::FromUTCString("2025-05-01", &expected));
+  EXPECT_EQ(internal::GetCohortNextStartDate(/*cohort_number=*/1,
+                                             /*after_current_month=*/true),
+            expected);
+  EXPECT_TRUE(base::Time::FromUTCString("2025-06-01", &expected));
+  EXPECT_EQ(internal::GetCohortNextStartDate(/*cohort_number=*/2,
+                                             /*after_current_month=*/true),
+            expected);
+  EXPECT_TRUE(base::Time::FromUTCString("2025-07-01", &expected));
+  EXPECT_EQ(internal::GetCohortNextStartDate(/*cohort_number=*/3,
+                                             /*after_current_month=*/true),
+            expected);
+  EXPECT_TRUE(base::Time::FromUTCString("2025-04-01", &expected));
+  EXPECT_EQ(internal::GetCohortNextStartDate(/*cohort_number=*/4,
+                                             /*after_current_month=*/true),
+            expected);
+
+  // April.
+  EXPECT_TRUE(base::Time::FromUTCString("2025-04-15", &now_override));
+  SetNowOverride(now_override);
+
+  EXPECT_TRUE(base::Time::FromUTCString("2025-05-01", &expected));
+  EXPECT_EQ(internal::GetCohortNextStartDate(/*cohort_number=*/1), expected);
+  EXPECT_TRUE(base::Time::FromUTCString("2025-06-01", &expected));
+  EXPECT_EQ(internal::GetCohortNextStartDate(/*cohort_number=*/2), expected);
+  EXPECT_TRUE(base::Time::FromUTCString("2025-07-01", &expected));
+  EXPECT_EQ(internal::GetCohortNextStartDate(/*cohort_number=*/3), expected);
+  EXPECT_TRUE(base::Time::FromUTCString("2025-04-01", &expected));
+  EXPECT_EQ(internal::GetCohortNextStartDate(/*cohort_number=*/4), expected);
+
+  EXPECT_TRUE(base::Time::FromUTCString("2025-05-01", &expected));
+  EXPECT_EQ(internal::GetCohortNextStartDate(/*cohort_number=*/1,
+                                             /*after_current_month=*/true),
+            expected);
+  EXPECT_TRUE(base::Time::FromUTCString("2025-06-01", &expected));
+  EXPECT_EQ(internal::GetCohortNextStartDate(/*cohort_number=*/2,
+                                             /*after_current_month=*/true),
+            expected);
+  EXPECT_TRUE(base::Time::FromUTCString("2025-07-01", &expected));
+  EXPECT_EQ(internal::GetCohortNextStartDate(/*cohort_number=*/3,
+                                             /*after_current_month=*/true),
+            expected);
+  EXPECT_TRUE(base::Time::FromUTCString("2025-08-01", &expected));
+  EXPECT_EQ(internal::GetCohortNextStartDate(/*cohort_number=*/4,
+                                             /*after_current_month=*/true),
+            expected);
+
+  // May.
+  EXPECT_TRUE(base::Time::FromUTCString("2025-05-15", &now_override));
+  SetNowOverride(now_override);
+
+  EXPECT_TRUE(base::Time::FromUTCString("2025-05-01", &expected));
+  EXPECT_EQ(internal::GetCohortNextStartDate(/*cohort_number=*/1), expected);
+  EXPECT_TRUE(base::Time::FromUTCString("2025-06-01", &expected));
+  EXPECT_EQ(internal::GetCohortNextStartDate(/*cohort_number=*/2), expected);
+  EXPECT_TRUE(base::Time::FromUTCString("2025-07-01", &expected));
+  EXPECT_EQ(internal::GetCohortNextStartDate(/*cohort_number=*/3), expected);
+  EXPECT_TRUE(base::Time::FromUTCString("2025-08-01", &expected));
+  EXPECT_EQ(internal::GetCohortNextStartDate(/*cohort_number=*/4), expected);
+
+  EXPECT_TRUE(base::Time::FromUTCString("2025-09-01", &expected));
+  EXPECT_EQ(internal::GetCohortNextStartDate(/*cohort_number=*/1,
+                                             /*after_current_month=*/true),
+            expected);
+  EXPECT_TRUE(base::Time::FromUTCString("2025-06-01", &expected));
+  EXPECT_EQ(internal::GetCohortNextStartDate(/*cohort_number=*/2,
+                                             /*after_current_month=*/true),
+            expected);
+  EXPECT_TRUE(base::Time::FromUTCString("2025-07-01", &expected));
+  EXPECT_EQ(internal::GetCohortNextStartDate(/*cohort_number=*/3,
+                                             /*after_current_month=*/true),
+            expected);
+  EXPECT_TRUE(base::Time::FromUTCString("2025-08-01", &expected));
+  EXPECT_EQ(internal::GetCohortNextStartDate(/*cohort_number=*/4,
+                                             /*after_current_month=*/true),
+            expected);
+
+  // June.
+  EXPECT_TRUE(base::Time::FromUTCString("2025-06-15", &now_override));
+  SetNowOverride(now_override);
+
+  EXPECT_TRUE(base::Time::FromUTCString("2025-09-01", &expected));
+  EXPECT_EQ(internal::GetCohortNextStartDate(/*cohort_number=*/1), expected);
+  EXPECT_TRUE(base::Time::FromUTCString("2025-06-01", &expected));
+  EXPECT_EQ(internal::GetCohortNextStartDate(/*cohort_number=*/2), expected);
+  EXPECT_TRUE(base::Time::FromUTCString("2025-07-01", &expected));
+  EXPECT_EQ(internal::GetCohortNextStartDate(/*cohort_number=*/3), expected);
+  EXPECT_TRUE(base::Time::FromUTCString("2025-08-01", &expected));
+  EXPECT_EQ(internal::GetCohortNextStartDate(/*cohort_number=*/4), expected);
+
+  EXPECT_TRUE(base::Time::FromUTCString("2025-09-01", &expected));
+  EXPECT_EQ(internal::GetCohortNextStartDate(/*cohort_number=*/1,
+                                             /*after_current_month=*/true),
+            expected);
+  EXPECT_TRUE(base::Time::FromUTCString("2025-10-01", &expected));
+  EXPECT_EQ(internal::GetCohortNextStartDate(/*cohort_number=*/2,
+                                             /*after_current_month=*/true),
+            expected);
+  EXPECT_TRUE(base::Time::FromUTCString("2025-07-01", &expected));
+  EXPECT_EQ(internal::GetCohortNextStartDate(/*cohort_number=*/3,
+                                             /*after_current_month=*/true),
+            expected);
+  EXPECT_TRUE(base::Time::FromUTCString("2025-08-01", &expected));
+  EXPECT_EQ(internal::GetCohortNextStartDate(/*cohort_number=*/4,
+                                             /*after_current_month=*/true),
+            expected);
+
+  // July.
+  EXPECT_TRUE(base::Time::FromUTCString("2025-07-15", &now_override));
+  SetNowOverride(now_override);
+
+  EXPECT_TRUE(base::Time::FromUTCString("2025-09-01", &expected));
+  EXPECT_EQ(internal::GetCohortNextStartDate(/*cohort_number=*/1), expected);
+  EXPECT_TRUE(base::Time::FromUTCString("2025-10-01", &expected));
+  EXPECT_EQ(internal::GetCohortNextStartDate(/*cohort_number=*/2), expected);
+  EXPECT_TRUE(base::Time::FromUTCString("2025-07-01", &expected));
+  EXPECT_EQ(internal::GetCohortNextStartDate(/*cohort_number=*/3), expected);
+  EXPECT_TRUE(base::Time::FromUTCString("2025-08-01", &expected));
+  EXPECT_EQ(internal::GetCohortNextStartDate(/*cohort_number=*/4), expected);
+
+  EXPECT_TRUE(base::Time::FromUTCString("2025-09-01", &expected));
+  EXPECT_EQ(internal::GetCohortNextStartDate(/*cohort_number=*/1,
+                                             /*after_current_month=*/true),
+            expected);
+  EXPECT_TRUE(base::Time::FromUTCString("2025-10-01", &expected));
+  EXPECT_EQ(internal::GetCohortNextStartDate(/*cohort_number=*/2,
+                                             /*after_current_month=*/true),
+            expected);
+  EXPECT_TRUE(base::Time::FromUTCString("2025-11-01", &expected));
+  EXPECT_EQ(internal::GetCohortNextStartDate(/*cohort_number=*/3,
+                                             /*after_current_month=*/true),
+            expected);
+  EXPECT_TRUE(base::Time::FromUTCString("2025-08-01", &expected));
+  EXPECT_EQ(internal::GetCohortNextStartDate(/*cohort_number=*/4,
+                                             /*after_current_month=*/true),
+            expected);
+
+  // August.
+  EXPECT_TRUE(base::Time::FromUTCString("2025-08-15", &now_override));
+  SetNowOverride(now_override);
+
+  EXPECT_TRUE(base::Time::FromUTCString("2025-09-01", &expected));
+  EXPECT_EQ(internal::GetCohortNextStartDate(/*cohort_number=*/1), expected);
+  EXPECT_TRUE(base::Time::FromUTCString("2025-10-01", &expected));
+  EXPECT_EQ(internal::GetCohortNextStartDate(/*cohort_number=*/2), expected);
+  EXPECT_TRUE(base::Time::FromUTCString("2025-11-01", &expected));
+  EXPECT_EQ(internal::GetCohortNextStartDate(/*cohort_number=*/3), expected);
+  EXPECT_TRUE(base::Time::FromUTCString("2025-08-01", &expected));
+  EXPECT_EQ(internal::GetCohortNextStartDate(/*cohort_number=*/4), expected);
+
+  EXPECT_TRUE(base::Time::FromUTCString("2025-09-01", &expected));
+  EXPECT_EQ(internal::GetCohortNextStartDate(/*cohort_number=*/1,
+                                             /*after_current_month=*/true),
+            expected);
+  EXPECT_TRUE(base::Time::FromUTCString("2025-10-01", &expected));
+  EXPECT_EQ(internal::GetCohortNextStartDate(/*cohort_number=*/2,
+                                             /*after_current_month=*/true),
+            expected);
+  EXPECT_TRUE(base::Time::FromUTCString("2025-11-01", &expected));
+  EXPECT_EQ(internal::GetCohortNextStartDate(/*cohort_number=*/3,
+                                             /*after_current_month=*/true),
+            expected);
+  EXPECT_TRUE(base::Time::FromUTCString("2025-12-01", &expected));
+  EXPECT_EQ(internal::GetCohortNextStartDate(/*cohort_number=*/4,
+                                             /*after_current_month=*/true),
+            expected);
+
+  // September.
+  EXPECT_TRUE(base::Time::FromUTCString("2025-09-15", &now_override));
+  SetNowOverride(now_override);
+
+  EXPECT_TRUE(base::Time::FromUTCString("2025-09-01", &expected));
+  EXPECT_EQ(internal::GetCohortNextStartDate(/*cohort_number=*/1), expected);
+  EXPECT_TRUE(base::Time::FromUTCString("2025-10-01", &expected));
+  EXPECT_EQ(internal::GetCohortNextStartDate(/*cohort_number=*/2), expected);
+  EXPECT_TRUE(base::Time::FromUTCString("2025-11-01", &expected));
+  EXPECT_EQ(internal::GetCohortNextStartDate(/*cohort_number=*/3), expected);
+  EXPECT_TRUE(base::Time::FromUTCString("2025-12-01", &expected));
+  EXPECT_EQ(internal::GetCohortNextStartDate(/*cohort_number=*/4), expected);
+
+  EXPECT_TRUE(base::Time::FromUTCString("2026-01-01", &expected));
+  EXPECT_EQ(internal::GetCohortNextStartDate(/*cohort_number=*/1,
+                                             /*after_current_month=*/true),
+            expected);
+  EXPECT_TRUE(base::Time::FromUTCString("2025-10-01", &expected));
+  EXPECT_EQ(internal::GetCohortNextStartDate(/*cohort_number=*/2,
+                                             /*after_current_month=*/true),
+            expected);
+  EXPECT_TRUE(base::Time::FromUTCString("2025-11-01", &expected));
+  EXPECT_EQ(internal::GetCohortNextStartDate(/*cohort_number=*/3,
+                                             /*after_current_month=*/true),
+            expected);
+  EXPECT_TRUE(base::Time::FromUTCString("2025-12-01", &expected));
+  EXPECT_EQ(internal::GetCohortNextStartDate(/*cohort_number=*/4,
+                                             /*after_current_month=*/true),
+            expected);
+
+  // October.
+  EXPECT_TRUE(base::Time::FromUTCString("2025-10-15", &now_override));
+  SetNowOverride(now_override);
+
+  EXPECT_TRUE(base::Time::FromUTCString("2026-01-01", &expected));
+  EXPECT_EQ(internal::GetCohortNextStartDate(/*cohort_number=*/1), expected);
+  EXPECT_TRUE(base::Time::FromUTCString("2025-10-01", &expected));
+  EXPECT_EQ(internal::GetCohortNextStartDate(/*cohort_number=*/2), expected);
+  EXPECT_TRUE(base::Time::FromUTCString("2025-11-01", &expected));
+  EXPECT_EQ(internal::GetCohortNextStartDate(/*cohort_number=*/3), expected);
+  EXPECT_TRUE(base::Time::FromUTCString("2025-12-01", &expected));
+  EXPECT_EQ(internal::GetCohortNextStartDate(/*cohort_number=*/4), expected);
+
+  EXPECT_TRUE(base::Time::FromUTCString("2026-01-01", &expected));
+  EXPECT_EQ(internal::GetCohortNextStartDate(/*cohort_number=*/1,
+                                             /*after_current_month=*/true),
+            expected);
+  EXPECT_TRUE(base::Time::FromUTCString("2026-02-01", &expected));
+  EXPECT_EQ(internal::GetCohortNextStartDate(/*cohort_number=*/2,
+                                             /*after_current_month=*/true),
+            expected);
+  EXPECT_TRUE(base::Time::FromUTCString("2025-11-01", &expected));
+  EXPECT_EQ(internal::GetCohortNextStartDate(/*cohort_number=*/3,
+                                             /*after_current_month=*/true),
+            expected);
+  EXPECT_TRUE(base::Time::FromUTCString("2025-12-01", &expected));
+  EXPECT_EQ(internal::GetCohortNextStartDate(/*cohort_number=*/4,
+                                             /*after_current_month=*/true),
+            expected);
+
+  // November.
+  EXPECT_TRUE(base::Time::FromUTCString("2025-11-15", &now_override));
+  SetNowOverride(now_override);
+
+  EXPECT_TRUE(base::Time::FromUTCString("2026-01-01", &expected));
+  EXPECT_EQ(internal::GetCohortNextStartDate(/*cohort_number=*/1), expected);
+  EXPECT_TRUE(base::Time::FromUTCString("2026-02-01", &expected));
+  EXPECT_EQ(internal::GetCohortNextStartDate(/*cohort_number=*/2), expected);
+  EXPECT_TRUE(base::Time::FromUTCString("2025-11-01", &expected));
+  EXPECT_EQ(internal::GetCohortNextStartDate(/*cohort_number=*/3), expected);
+  EXPECT_TRUE(base::Time::FromUTCString("2025-12-01", &expected));
+  EXPECT_EQ(internal::GetCohortNextStartDate(/*cohort_number=*/4), expected);
+
+  EXPECT_TRUE(base::Time::FromUTCString("2026-01-01", &expected));
+  EXPECT_EQ(internal::GetCohortNextStartDate(/*cohort_number=*/1,
+                                             /*after_current_month=*/true),
+            expected);
+  EXPECT_TRUE(base::Time::FromUTCString("2026-02-01", &expected));
+  EXPECT_EQ(internal::GetCohortNextStartDate(/*cohort_number=*/2,
+                                             /*after_current_month=*/true),
+            expected);
+  EXPECT_TRUE(base::Time::FromUTCString("2026-03-01", &expected));
+  EXPECT_EQ(internal::GetCohortNextStartDate(/*cohort_number=*/3,
+                                             /*after_current_month=*/true),
+            expected);
+  EXPECT_TRUE(base::Time::FromUTCString("2025-12-01", &expected));
+  EXPECT_EQ(internal::GetCohortNextStartDate(/*cohort_number=*/4,
+                                             /*after_current_month=*/true),
+            expected);
+
+  // December.
+  EXPECT_TRUE(base::Time::FromUTCString("2025-12-15", &now_override));
+  SetNowOverride(now_override);
+
+  EXPECT_TRUE(base::Time::FromUTCString("2026-01-01", &expected));
+  EXPECT_EQ(internal::GetCohortNextStartDate(/*cohort_number=*/1), expected);
+  EXPECT_TRUE(base::Time::FromUTCString("2026-02-01", &expected));
+  EXPECT_EQ(internal::GetCohortNextStartDate(/*cohort_number=*/2), expected);
+  EXPECT_TRUE(base::Time::FromUTCString("2026-03-01", &expected));
+  EXPECT_EQ(internal::GetCohortNextStartDate(/*cohort_number=*/3), expected);
+  EXPECT_TRUE(base::Time::FromUTCString("2025-12-01", &expected));
+  EXPECT_EQ(internal::GetCohortNextStartDate(/*cohort_number=*/4), expected);
+
+  EXPECT_TRUE(base::Time::FromUTCString("2026-01-01", &expected));
+  EXPECT_EQ(internal::GetCohortNextStartDate(/*cohort_number=*/1,
+                                             /*after_current_month=*/true),
+            expected);
+  EXPECT_TRUE(base::Time::FromUTCString("2026-02-01", &expected));
+  EXPECT_EQ(internal::GetCohortNextStartDate(/*cohort_number=*/2,
+                                             /*after_current_month=*/true),
+            expected);
+  EXPECT_TRUE(base::Time::FromUTCString("2026-03-01", &expected));
+  EXPECT_EQ(internal::GetCohortNextStartDate(/*cohort_number=*/3,
+                                             /*after_current_month=*/true),
+            expected);
+  EXPECT_TRUE(base::Time::FromUTCString("2026-04-01", &expected));
+  EXPECT_EQ(internal::GetCohortNextStartDate(/*cohort_number=*/4,
+                                             /*after_current_month=*/true),
+            expected);
+}
+
+// Tests AssignNewCohortIfNeeded.
+TEST_F(DefaultStatusHelperTest, AssignNewCohortIfNeeded) {
+  if (!@available(iOS 18.4, *)) {
+    // If the iOS version condition isn't met, no cohort should be assigned even
+    // if the other conditions are met.
+    prefs()->SetInteger(kDefaultStatusAPICohort, 0);
+    prefs()->SetTime(kDefaultStatusAPINextRetry, base::Time());
+    ForceFirstRunRecency(100);
+    internal::AssignNewCohortIfNeeded(prefs());
+    EXPECT_EQ(prefs()->GetInteger(kDefaultStatusAPICohort), 0);
+    EXPECT_EQ(prefs()->GetTime(kDefaultStatusAPINextRetry), base::Time());
+    return;
+  }
+
+  // If this is a new client, no cohort should be assigned even if the other
+  // conditions are met.
+  prefs()->SetInteger(kDefaultStatusAPICohort, 0);
+  prefs()->SetTime(kDefaultStatusAPINextRetry, base::Time());
+  ForceFirstRunRecency(27);
+  internal::AssignNewCohortIfNeeded(prefs());
+  EXPECT_EQ(prefs()->GetInteger(kDefaultStatusAPICohort), 0);
+  EXPECT_EQ(prefs()->GetTime(kDefaultStatusAPINextRetry), base::Time());
+
+  // If a cohort is already assigned, the cohort and next availability should
+  // not be changed, even if the other conditions are met.
+  prefs()->SetInteger(kDefaultStatusAPICohort, 1000);
+  base::Time time;
+  EXPECT_TRUE(base::Time::FromUTCString("2000-01-15", &time));
+  prefs()->SetTime(kDefaultStatusAPINextRetry, time);
+  ForceFirstRunRecency(100);
+  internal::AssignNewCohortIfNeeded(prefs());
+  EXPECT_EQ(prefs()->GetInteger(kDefaultStatusAPICohort), 1000);
+  EXPECT_EQ(prefs()->GetTime(kDefaultStatusAPINextRetry), time);
+
+  // If all conditions are met, a new cohort should be assigned and the next
+  // availability should be set.
+  prefs()->SetInteger(kDefaultStatusAPICohort, 0);
+  prefs()->SetTime(kDefaultStatusAPINextRetry, base::Time());
+  ForceFirstRunRecency(100);
+  internal::AssignNewCohortIfNeeded(prefs());
+  EXPECT_NE(prefs()->GetInteger(kDefaultStatusAPICohort), 0);
+  EXPECT_NE(prefs()->GetTime(kDefaultStatusAPINextRetry), base::Time());
+}
+
+// Tests SystemToLocalEnum.
+TEST_F(DefaultStatusHelperTest, SystemToLocalEnum) API_AVAILABLE(ios(18.4)) {
+  DefaultStatusAPIResult result =
+      internal::SystemToLocalEnum(UIApplicationCategoryDefaultStatusIsDefault);
+  EXPECT_EQ(result, DefaultStatusAPIResult::kIsDefault);
+  result =
+      internal::SystemToLocalEnum(UIApplicationCategoryDefaultStatusNotDefault);
+  EXPECT_EQ(result, DefaultStatusAPIResult::kIsNotDefault);
+  result =
+      internal::SystemToLocalEnum((UIApplicationCategoryDefaultStatus)1000);
+  EXPECT_EQ(result, DefaultStatusAPIResult::kUnknown);
+}
+
+// Tests DetermineRetentionStatus.
+TEST_F(DefaultStatusHelperTest, DetermineRetentionStatus) {
+  DefaultStatusRetention result = internal::DetermineRetentionStatus(
+      /*previous=*/DefaultStatusAPIResult::kIsNotDefault,
+      /*current=*/DefaultStatusAPIResult::kIsDefault);
+  EXPECT_EQ(result, DefaultStatusRetention::kBecameDefault);
+  result = internal::DetermineRetentionStatus(
+      /*previous=*/DefaultStatusAPIResult::kIsDefault,
+      /*current=*/DefaultStatusAPIResult::kIsNotDefault);
+  EXPECT_EQ(result, DefaultStatusRetention::kBecameNonDefault);
+  result = internal::DetermineRetentionStatus(
+      /*previous=*/DefaultStatusAPIResult::kIsDefault,
+      /*current=*/DefaultStatusAPIResult::kIsDefault);
+  EXPECT_EQ(result, DefaultStatusRetention::kRemainedDefault);
+  result = internal::DetermineRetentionStatus(
+      /*previous=*/DefaultStatusAPIResult::kIsNotDefault,
+      /*current=*/DefaultStatusAPIResult::kIsNotDefault);
+  EXPECT_EQ(result, DefaultStatusRetention::kRemainedNonDefault);
+}
+
+// Tests that QueryDefaultStatusIfReadyAndLogResults doesn't do anything if the
+// system isn't running on the minimum version.
+TEST_F(DefaultStatusHelperTest, QueryDefaultStatusAPINotAvailable)
+API_AVAILABLE(ios(18.4)) {
+  if (@available(iOS 18.4, *)) {
+    return;
+  }
+
+  base::HistogramTester histogram_tester;
+  histogram_tester.ExpectTotalCount("IOS.DefaultStatusAPI.OutcomeType", 0);
+  prefs()->SetInteger(kDefaultStatusAPICohort, 1);
+  prefs()->SetTime(kDefaultStatusAPINextRetry, base::Time());
+  internal::OverrideSystemCallForTesting(base::BindOnce([](NSError** error) {
+    // This should not be called.
+    EXPECT_TRUE(false);
+    return UIApplicationCategoryDefaultStatusUnavailable;
+  }));
+
+  internal::QueryDefaultStatusIfReadyAndLogResults(prefs());
+
+  histogram_tester.ExpectTotalCount("IOS.DefaultStatusAPI.OutcomeType", 0);
+  histogram_tester.ExpectTotalCount(
+      "IOS.DefaultStatusAPI.CooldownError.DaysLeft", 0);
+  ExpectSuccessHistogramsToHaveNoSamples(histogram_tester);
+}
+
+// Test that QueryDefaultStatusIfReadyAndLogResults does not do anything if
+// the client is still on cooldown according to local prefs.
+TEST_F(DefaultStatusHelperTest, QueryDefaultStatusAPIOnLocalCooldown)
+API_AVAILABLE(ios(18.4)) {
+  if (!@available(iOS 18.4, *)) {
+    return;
+  }
+
+  base::HistogramTester histogram_tester;
+  histogram_tester.ExpectTotalCount("IOS.DefaultStatusAPI.OutcomeType", 0);
+  prefs()->SetInteger(kDefaultStatusAPICohort, 1);
+  base::Time some_day_in_future =
+      base::subtle::TimeNowIgnoringOverride() + base::Days(100);
+  prefs()->SetTime(kDefaultStatusAPINextRetry, some_day_in_future);
+  internal::OverrideSystemCallForTesting(base::BindOnce([](NSError** error) {
+    // This should not be called.
+    EXPECT_TRUE(false);
+    return UIApplicationCategoryDefaultStatusUnavailable;
+  }));
+
+  internal::QueryDefaultStatusIfReadyAndLogResults(prefs());
+
+  histogram_tester.ExpectTotalCount("IOS.DefaultStatusAPI.OutcomeType", 0);
+  histogram_tester.ExpectTotalCount(
+      "IOS.DefaultStatusAPI.CooldownError.DaysLeft", 0);
+  ExpectSuccessHistogramsToHaveNoSamples(histogram_tester);
+}
+
+// Test that QueryDefaultStatusIfReadyAndLogResults correctly handles cooldown
+// errors returned by the default status system API.
+TEST_F(DefaultStatusHelperTest, QueryDefaultStatusAPISystemCooldownError)
+API_AVAILABLE(ios(18.4)) {
+  if (!@available(iOS 18.4, *)) {
+    return;
+  }
+
+  base::HistogramTester histogram_tester;
+  histogram_tester.ExpectTotalCount("IOS.DefaultStatusAPI.OutcomeType", 0);
+  prefs()->SetInteger(kDefaultStatusAPICohort, 1);
+  prefs()->SetTime(kDefaultStatusAPINextRetry, base::Time());
+  base::Time next_retry =
+      base::subtle::TimeNowIgnoringOverride() + base::Days(100);
+  internal::OverrideSystemCallForTesting(base::BindOnce(
+      [](base::Time retry_date, NSError** error) {
+        NSDictionary* errorInfo = @{
+          UIApplicationCategoryDefaultRetryAvailabilityDateErrorKey :
+              retry_date.ToNSDate()
+        };
+        *error = [NSError
+            errorWithDomain:UIApplicationCategoryDefaultErrorDomain
+                       code:UIApplicationCategoryDefaultErrorRateLimited
+                   userInfo:errorInfo];
+        return UIApplicationCategoryDefaultStatusUnavailable;
+      },
+      next_retry));
+
+  internal::QueryDefaultStatusIfReadyAndLogResults(prefs());
+
+  histogram_tester.ExpectTotalCount("IOS.DefaultStatusAPI.OutcomeType", 1);
+  histogram_tester.ExpectBucketCount(
+      "IOS.DefaultStatusAPI.OutcomeType",
+      DefaultStatusAPIOutcomeType::kCooldownError, 1);
+  EXPECT_EQ(prefs()->GetTime(kDefaultStatusAPINextRetry), next_retry);
+  histogram_tester.ExpectTotalCount(
+      "IOS.DefaultStatusAPI.CooldownError.DaysLeft", 1);
+  histogram_tester.ExpectBucketCount(
+      "IOS.DefaultStatusAPI.CooldownError.DaysLeft", 100, 1);
+  ExpectSuccessHistogramsToHaveNoSamples(histogram_tester);
+}
+
+// Test that QueryDefaultStatusIfReadyAndLogResults can handle unknown error
+// types returned by the default status system API.
+TEST_F(DefaultStatusHelperTest, QueryDefaultStatusAPISystemUnknownError)
+API_AVAILABLE(ios(18.4)) {
+  if (!@available(iOS 18.4, *)) {
+    return;
+  }
+
+  base::HistogramTester histogram_tester;
+  histogram_tester.ExpectTotalCount("IOS.DefaultStatusAPI.OutcomeType", 0);
+  prefs()->SetInteger(kDefaultStatusAPICohort, 1);
+  base::Time some_days_ago =
+      base::subtle::TimeNowIgnoringOverride() - base::Days(10);
+  prefs()->SetTime(kDefaultStatusAPINextRetry, some_days_ago);
+  internal::OverrideSystemCallForTesting(base::BindOnce([](NSError** error) {
+    NSDictionary* errorInfo = @{@"Fake" : @42};
+    *error = [NSError errorWithDomain:UIApplicationCategoryDefaultErrorDomain
+                                 code:123456
+                             userInfo:errorInfo];
+    return UIApplicationCategoryDefaultStatusUnavailable;
+  }));
+
+  internal::QueryDefaultStatusIfReadyAndLogResults(prefs());
+
+  histogram_tester.ExpectTotalCount("IOS.DefaultStatusAPI.OutcomeType", 1);
+  histogram_tester.ExpectBucketCount("IOS.DefaultStatusAPI.OutcomeType",
+                                     DefaultStatusAPIOutcomeType::kOtherError,
+                                     1);
+  EXPECT_EQ(prefs()->GetTime(kDefaultStatusAPINextRetry), some_days_ago);
+  histogram_tester.ExpectTotalCount(
+      "IOS.DefaultStatusAPI.CooldownError.DaysLeft", 0);
+  ExpectSuccessHistogramsToHaveNoSamples(histogram_tester);
+}
+
+// Tests the success case of QueryDefaultStatusIfReadyAndLogResults for the
+// following scenario:
+//    - Cohort 1
+//    - Within cohort 1 reporting window
+//    - Is default
+//    - No previous default status API result
+TEST_F(DefaultStatusHelperTest,
+       QueryDefaultStatusAPIStrictCohort1IsDefaultFirstTime)
+API_AVAILABLE(ios(18.4)) {
+  if (!@available(iOS 18.4, *)) {
+    return;
+  }
+
+  base::subtle::ScopedTimeClockOverrides time_override(
+      &DefaultStatusHelperTest::NowOverride, nullptr, nullptr);
+  base::Time now_override;
+  EXPECT_TRUE(base::Time::FromUTCString("2025-01-15", &now_override));
+  SetNowOverride(now_override);
+
+  base::HistogramTester histogram_tester;
+  histogram_tester.ExpectTotalCount("IOS.DefaultStatusAPI.OutcomeType", 0);
+  prefs()->SetInteger(kDefaultStatusAPICohort, 1);
+  prefs()->SetTime(kDefaultStatusAPILastSuccessfulCall, base::Time());
+  prefs()->SetTime(kDefaultStatusAPINextRetry, base::Time());
+  prefs()->SetInteger(kDefaultStatusAPIResult,
+                      static_cast<int>(DefaultStatusAPIResult::kUnknown));
+  internal::OverrideSystemCallForTesting(base::BindOnce([](NSError** error) {
+    return UIApplicationCategoryDefaultStatusIsDefault;
+  }));
+
+  internal::QueryDefaultStatusIfReadyAndLogResults(prefs());
+
+  histogram_tester.ExpectTotalCount("IOS.DefaultStatusAPI.OutcomeType", 1);
+  histogram_tester.ExpectBucketCount("IOS.DefaultStatusAPI.OutcomeType",
+                                     DefaultStatusAPIOutcomeType::kSuccess, 1);
+  base::Time expected_next;
+  EXPECT_TRUE(base::Time::FromUTCString("2025-05-01", &expected_next));
+  EXPECT_EQ(prefs()->GetTime(kDefaultStatusAPINextRetry), expected_next);
+  histogram_tester.ExpectTotalCount(
+      "IOS.DefaultStatusAPI.CooldownError.DaysLeft", 0);
+  histogram_tester.ExpectTotalCount(
+      "IOS.DefaultStatusAPI.DaysSinceLastSuccessfulCall", 0);
+  histogram_tester.ExpectTotalCount("IOS.DefaultStatusAPI.IsDefaultBrowser", 1);
+  histogram_tester.ExpectBucketCount("IOS.DefaultStatusAPI.IsDefaultBrowser",
+                                     true, 1);
+  histogram_tester.ExpectTotalCount(
+      "IOS.DefaultStatusAPI.IsDefaultBrowser.Cohort1", 1);
+  histogram_tester.ExpectBucketCount(
+      "IOS.DefaultStatusAPI.IsDefaultBrowser.Cohort1", true, 1);
+  histogram_tester.ExpectTotalCount(
+      "IOS.DefaultStatusAPI.IsDefaultBrowser.Cohort2", 0);
+  histogram_tester.ExpectTotalCount(
+      "IOS.DefaultStatusAPI.IsDefaultBrowser.Cohort3", 0);
+  histogram_tester.ExpectTotalCount(
+      "IOS.DefaultStatusAPI.IsDefaultBrowser.StrictCohorts", 1);
+  histogram_tester.ExpectBucketCount(
+      "IOS.DefaultStatusAPI.IsDefaultBrowser.StrictCohorts", true, 1);
+  histogram_tester.ExpectTotalCount(
+      "IOS.DefaultStatusAPI.DefaultStatusRetention", 0);
+  histogram_tester.ExpectTotalCount("IOS.DefaultStatusAPI.HeuristicAssessment1",
+                                    1);
+  histogram_tester.ExpectTotalCount("IOS.DefaultStatusAPI.HeuristicAssessment3",
+                                    1);
+  histogram_tester.ExpectTotalCount("IOS.DefaultStatusAPI.HeuristicAssessment7",
+                                    1);
+  histogram_tester.ExpectTotalCount(
+      "IOS.DefaultStatusAPI.HeuristicAssessment14", 1);
+  histogram_tester.ExpectTotalCount(
+      "IOS.DefaultStatusAPI.HeuristicAssessment21", 1);
+  histogram_tester.ExpectTotalCount(
+      "IOS.DefaultStatusAPI.HeuristicAssessment28", 1);
+  histogram_tester.ExpectTotalCount(
+      "IOS.DefaultStatusAPI.HeuristicAssessment35", 1);
+  histogram_tester.ExpectTotalCount(
+      "IOS.DefaultStatusAPI.HeuristicAssessment42", 1);
+}
+
+// Tests the success case of QueryDefaultStatusIfReadyAndLogResults for the
+// following scenario:
+//    - Cohort 2
+//    - Outside cohort 2 reporting window
+//    - Is not default
+//    - Has a previous default status API result of "is default"
+TEST_F(DefaultStatusHelperTest,
+       QueryDefaultStatusAPICohort2IsNotDefaultRecurring)
+API_AVAILABLE(ios(18.4)) {
+  if (!@available(iOS 18.4, *)) {
+    return;
+  }
+
+  base::subtle::ScopedTimeClockOverrides time_override(
+      &DefaultStatusHelperTest::NowOverride, nullptr, nullptr);
+  base::Time now_override;
+  EXPECT_TRUE(base::Time::FromUTCString("2025-03-15", &now_override));
+  SetNowOverride(now_override);
+
+  base::HistogramTester histogram_tester;
+  histogram_tester.ExpectTotalCount("IOS.DefaultStatusAPI.OutcomeType", 0);
+  prefs()->SetInteger(kDefaultStatusAPICohort, 2);
+  base::Time last_successful_call = base::Time::Now() - base::Days(100);
+  prefs()->SetTime(kDefaultStatusAPILastSuccessfulCall, last_successful_call);
+  prefs()->SetTime(kDefaultStatusAPINextRetry, base::Time());
+  prefs()->SetInteger(kDefaultStatusAPIResult,
+                      static_cast<int>(DefaultStatusAPIResult::kIsDefault));
+  internal::OverrideSystemCallForTesting(base::BindOnce([](NSError** error) {
+    return UIApplicationCategoryDefaultStatusNotDefault;
+  }));
+
+  internal::QueryDefaultStatusIfReadyAndLogResults(prefs());
+
+  histogram_tester.ExpectTotalCount("IOS.DefaultStatusAPI.OutcomeType", 1);
+  histogram_tester.ExpectBucketCount("IOS.DefaultStatusAPI.OutcomeType",
+                                     DefaultStatusAPIOutcomeType::kSuccess, 1);
+  base::Time expected_next;
+  EXPECT_TRUE(base::Time::FromUTCString("2025-06-01", &expected_next));
+  EXPECT_EQ(prefs()->GetTime(kDefaultStatusAPINextRetry), expected_next);
+  histogram_tester.ExpectTotalCount(
+      "IOS.DefaultStatusAPI.CooldownError.DaysLeft", 0);
+  histogram_tester.ExpectTotalCount(
+      "IOS.DefaultStatusAPI.DaysSinceLastSuccessfulCall", 1);
+  histogram_tester.ExpectBucketCount(
+      "IOS.DefaultStatusAPI.DaysSinceLastSuccessfulCall", 100, 1);
+  histogram_tester.ExpectTotalCount("IOS.DefaultStatusAPI.IsDefaultBrowser", 1);
+  histogram_tester.ExpectBucketCount("IOS.DefaultStatusAPI.IsDefaultBrowser",
+                                     false, 1);
+  histogram_tester.ExpectTotalCount(
+      "IOS.DefaultStatusAPI.IsDefaultBrowser.Cohort1", 0);
+  histogram_tester.ExpectTotalCount(
+      "IOS.DefaultStatusAPI.IsDefaultBrowser.Cohort2", 1);
+  histogram_tester.ExpectBucketCount(
+      "IOS.DefaultStatusAPI.IsDefaultBrowser.Cohort2", false, 1);
+  histogram_tester.ExpectTotalCount(
+      "IOS.DefaultStatusAPI.IsDefaultBrowser.Cohort3", 0);
+  histogram_tester.ExpectTotalCount(
+      "IOS.DefaultStatusAPI.IsDefaultBrowser.StrictCohorts", 0);
+  histogram_tester.ExpectTotalCount(
+      "IOS.DefaultStatusAPI.DefaultStatusRetention", 1);
+  histogram_tester.ExpectBucketCount(
+      "IOS.DefaultStatusAPI.DefaultStatusRetention",
+      DefaultStatusRetention::kBecameNonDefault, 1);
+  histogram_tester.ExpectTotalCount("IOS.DefaultStatusAPI.HeuristicAssessment1",
+                                    1);
+  histogram_tester.ExpectTotalCount("IOS.DefaultStatusAPI.HeuristicAssessment3",
+                                    1);
+  histogram_tester.ExpectTotalCount("IOS.DefaultStatusAPI.HeuristicAssessment7",
+                                    1);
+  histogram_tester.ExpectTotalCount(
+      "IOS.DefaultStatusAPI.HeuristicAssessment14", 1);
+  histogram_tester.ExpectTotalCount(
+      "IOS.DefaultStatusAPI.HeuristicAssessment21", 1);
+  histogram_tester.ExpectTotalCount(
+      "IOS.DefaultStatusAPI.HeuristicAssessment28", 1);
+  histogram_tester.ExpectTotalCount(
+      "IOS.DefaultStatusAPI.HeuristicAssessment35", 1);
+  histogram_tester.ExpectTotalCount(
+      "IOS.DefaultStatusAPI.HeuristicAssessment42", 1);
+}
+
+// Tests the success case of QueryDefaultStatusIfReadyAndLogResults for the
+// following scenario:
+//    - Cohort 3
+//    - Inside cohort 3 reporting window
+//    - Is default
+//    - Has a previous default status API result of "is default"
+TEST_F(DefaultStatusHelperTest,
+       QueryDefaultStatusAPIStrictCohort3IsDefaultRecurring)
+API_AVAILABLE(ios(18.4)) {
+  if (!@available(iOS 18.4, *)) {
+    return;
+  }
+
+  base::subtle::ScopedTimeClockOverrides time_override(
+      &DefaultStatusHelperTest::NowOverride, nullptr, nullptr);
+  base::Time now_override;
+  EXPECT_TRUE(base::Time::FromUTCString("2025-07-15", &now_override));
+  SetNowOverride(now_override);
+
+  base::HistogramTester histogram_tester;
+  histogram_tester.ExpectTotalCount("IOS.DefaultStatusAPI.OutcomeType", 0);
+  prefs()->SetInteger(kDefaultStatusAPICohort, 3);
+  base::Time last_successful_call = base::Time::Now() - base::Days(150);
+  prefs()->SetTime(kDefaultStatusAPILastSuccessfulCall, last_successful_call);
+  prefs()->SetTime(kDefaultStatusAPINextRetry, base::Time());
+  prefs()->SetInteger(kDefaultStatusAPIResult,
+                      static_cast<int>(DefaultStatusAPIResult::kIsDefault));
+  internal::OverrideSystemCallForTesting(base::BindOnce([](NSError** error) {
+    return UIApplicationCategoryDefaultStatusIsDefault;
+  }));
+
+  internal::QueryDefaultStatusIfReadyAndLogResults(prefs());
+
+  histogram_tester.ExpectTotalCount("IOS.DefaultStatusAPI.OutcomeType", 1);
+  histogram_tester.ExpectBucketCount("IOS.DefaultStatusAPI.OutcomeType",
+                                     DefaultStatusAPIOutcomeType::kSuccess, 1);
+  base::Time expected_next;
+  EXPECT_TRUE(base::Time::FromUTCString("2025-11-01", &expected_next));
+  EXPECT_EQ(prefs()->GetTime(kDefaultStatusAPINextRetry), expected_next);
+  histogram_tester.ExpectTotalCount(
+      "IOS.DefaultStatusAPI.CooldownError.DaysLeft", 0);
+  histogram_tester.ExpectTotalCount(
+      "IOS.DefaultStatusAPI.DaysSinceLastSuccessfulCall", 1);
+  histogram_tester.ExpectBucketCount(
+      "IOS.DefaultStatusAPI.DaysSinceLastSuccessfulCall", 150, 1);
+  histogram_tester.ExpectTotalCount("IOS.DefaultStatusAPI.IsDefaultBrowser", 1);
+  histogram_tester.ExpectBucketCount("IOS.DefaultStatusAPI.IsDefaultBrowser",
+                                     true, 1);
+  histogram_tester.ExpectTotalCount(
+      "IOS.DefaultStatusAPI.IsDefaultBrowser.Cohort1", 0);
+  histogram_tester.ExpectTotalCount(
+      "IOS.DefaultStatusAPI.IsDefaultBrowser.Cohort2", 0);
+  histogram_tester.ExpectTotalCount(
+      "IOS.DefaultStatusAPI.IsDefaultBrowser.Cohort3", 1);
+  histogram_tester.ExpectBucketCount(
+      "IOS.DefaultStatusAPI.IsDefaultBrowser.Cohort3", true, 1);
+  histogram_tester.ExpectTotalCount(
+      "IOS.DefaultStatusAPI.IsDefaultBrowser.StrictCohorts", 1);
+  histogram_tester.ExpectBucketCount(
+      "IOS.DefaultStatusAPI.IsDefaultBrowser.StrictCohorts", true, 1);
+  histogram_tester.ExpectTotalCount(
+      "IOS.DefaultStatusAPI.DefaultStatusRetention", 1);
+  histogram_tester.ExpectBucketCount(
+      "IOS.DefaultStatusAPI.DefaultStatusRetention",
+      DefaultStatusRetention::kRemainedDefault, 1);
+  histogram_tester.ExpectTotalCount("IOS.DefaultStatusAPI.HeuristicAssessment1",
+                                    1);
+  histogram_tester.ExpectTotalCount("IOS.DefaultStatusAPI.HeuristicAssessment3",
+                                    1);
+  histogram_tester.ExpectTotalCount("IOS.DefaultStatusAPI.HeuristicAssessment7",
+                                    1);
+  histogram_tester.ExpectTotalCount(
+      "IOS.DefaultStatusAPI.HeuristicAssessment14", 1);
+  histogram_tester.ExpectTotalCount(
+      "IOS.DefaultStatusAPI.HeuristicAssessment21", 1);
+  histogram_tester.ExpectTotalCount(
+      "IOS.DefaultStatusAPI.HeuristicAssessment28", 1);
+  histogram_tester.ExpectTotalCount(
+      "IOS.DefaultStatusAPI.HeuristicAssessment35", 1);
+  histogram_tester.ExpectTotalCount(
+      "IOS.DefaultStatusAPI.HeuristicAssessment42", 1);
+}
+
+}  // namespace default_status
diff --git a/ios/chrome/browser/home_customization/utils/home_customization_helper.mm b/ios/chrome/browser/home_customization/utils/home_customization_helper.mm
index ee36f6e..e16178c8 100644
--- a/ios/chrome/browser/home_customization/utils/home_customization_helper.mm
+++ b/ios/chrome/browser/home_customization/utils/home_customization_helper.mm
@@ -47,7 +47,7 @@
       } else if (commerce::kShopCardVariation.Get() ==
                  commerce::kShopCardArm2) {
         return l10n_util::GetNSString(
-            IDS_IOS_CONTENT_SUGGESTIONS_SHOPCARD_REVIEWS_ALT_TITLE);
+            IDS_IOS_CONTENT_SUGGESTIONS_SHOPCARD_REVIEWS_CUSTOMIZE_CARDS_ALT_2);
       }
       return @"";
   }
diff --git a/ios/chrome/browser/intelligence/enhanced_calendar/coordinator/BUILD.gn b/ios/chrome/browser/intelligence/enhanced_calendar/coordinator/BUILD.gn
new file mode 100644
index 0000000..48c39167
--- /dev/null
+++ b/ios/chrome/browser/intelligence/enhanced_calendar/coordinator/BUILD.gn
@@ -0,0 +1,22 @@
+# Copyright 2025 The Chromium Authors
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+source_set("coordinator") {
+  sources = [
+    "enhanced_calendar_coordinator.h",
+    "enhanced_calendar_coordinator.mm",
+    "enhanced_calendar_mediator.h",
+    "enhanced_calendar_mediator.mm",
+  ]
+
+  deps = [
+    "//base",
+    "//ios/chrome/browser/intelligence/enhanced_calendar/ui",
+    "//ios/chrome/browser/shared/coordinator/chrome_coordinator",
+    "//ios/chrome/browser/shared/model/browser",
+    "//ios/chrome/browser/shared/model/web_state_list",
+    "//ios/public/provider/chrome/browser/add_to_calendar:add_to_calendar_api",
+    "//ios/web/public",
+  ]
+}
diff --git a/ios/chrome/browser/intelligence/enhanced_calendar/coordinator/enhanced_calendar_coordinator.h b/ios/chrome/browser/intelligence/enhanced_calendar/coordinator/enhanced_calendar_coordinator.h
new file mode 100644
index 0000000..64b8fd9
--- /dev/null
+++ b/ios/chrome/browser/intelligence/enhanced_calendar/coordinator/enhanced_calendar_coordinator.h
@@ -0,0 +1,30 @@
+// Copyright 2025 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_INTELLIGENCE_ENHANCED_CALENDAR_COORDINATOR_ENHANCED_CALENDAR_COORDINATOR_H_
+#define IOS_CHROME_BROWSER_INTELLIGENCE_ENHANCED_CALENDAR_COORDINATOR_ENHANCED_CALENDAR_COORDINATOR_H_
+
+#import "ios/chrome/browser/shared/coordinator/chrome_coordinator/chrome_coordinator.h"
+
+namespace ios::provider {
+enum class AddToCalendarIntegrationProvider;
+}  // namespace ios::provider
+
+// The coordinator for the Enhanced Calendar feature's UI.
+@interface EnhancedCalendarCoordinator : ChromeCoordinator
+
+- (instancetype)initWithBaseViewController:(UIViewController*)viewController
+                                   browser:(Browser*)browser NS_UNAVAILABLE;
+
+// `integrationProvider` is the "add to calendar" integration provider to be
+// shown to the user.
+- (instancetype)initWithBaseViewController:(UIViewController*)viewController
+                                   browser:(Browser*)browser
+                       integrationProvider:
+                           (ios::provider::AddToCalendarIntegrationProvider)
+                               integrationProvider NS_DESIGNATED_INITIALIZER;
+
+@end
+
+#endif  // IOS_CHROME_BROWSER_INTELLIGENCE_ENHANCED_CALENDAR_COORDINATOR_ENHANCED_CALENDAR_COORDINATOR_H_
diff --git a/ios/chrome/browser/intelligence/enhanced_calendar/coordinator/enhanced_calendar_coordinator.mm b/ios/chrome/browser/intelligence/enhanced_calendar/coordinator/enhanced_calendar_coordinator.mm
new file mode 100644
index 0000000..6a1625a
--- /dev/null
+++ b/ios/chrome/browser/intelligence/enhanced_calendar/coordinator/enhanced_calendar_coordinator.mm
@@ -0,0 +1,61 @@
+// Copyright 2025 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/intelligence/enhanced_calendar/coordinator/enhanced_calendar_coordinator.h"
+
+#import "ios/chrome/browser/intelligence/enhanced_calendar/coordinator/enhanced_calendar_mediator.h"
+#import "ios/chrome/browser/intelligence/enhanced_calendar/ui/enhanced_calendar_view_controller.h"
+#import "ios/chrome/browser/shared/model/browser/browser.h"
+#import "ios/chrome/browser/shared/model/web_state_list/web_state_list.h"
+#import "ios/public/provider/chrome/browser/add_to_calendar/add_to_calendar_api.h"
+
+@implementation EnhancedCalendarCoordinator {
+  // The integration provider for the "add to calendar" experience.
+  ios::provider::AddToCalendarIntegrationProvider _integrationProvider;
+
+  // The mediator for handling the Enhanced Calendar service and its model
+  // request(s).
+  EnhancedCalendarMediator* _mediator;
+
+  // The view controller presented as the Enhanced Calendar interstitial.
+  EnhancedCalendarViewController* _viewController;
+}
+
+- (instancetype)initWithBaseViewController:(UIViewController*)viewController
+                                   browser:(Browser*)browser
+                       integrationProvider:
+                           (ios::provider::AddToCalendarIntegrationProvider)
+                               integrationProvider {
+  self = [super initWithBaseViewController:viewController browser:browser];
+  if (self) {
+    _integrationProvider = integrationProvider;
+  }
+  return self;
+}
+
+#pragma mark - ChromeCoordinator
+
+- (void)start {
+  _viewController = [[EnhancedCalendarViewController alloc] init];
+
+  _mediator = [[EnhancedCalendarMediator alloc]
+         initWithWebState:self.browser->GetWebStateList()->GetActiveWebState()
+      integrationProvider:_integrationProvider];
+
+  _viewController.mutator = _mediator;
+
+  [self.baseViewController presentViewController:_viewController
+                                        animated:YES
+                                      completion:nil];
+}
+
+- (void)stop {
+  [_mediator disconnect];
+  [self.baseViewController dismissViewControllerAnimated:YES completion:nil];
+
+  _mediator = nil;
+  _viewController = nil;
+}
+
+@end
diff --git a/ios/chrome/browser/intelligence/enhanced_calendar/coordinator/enhanced_calendar_mediator.h b/ios/chrome/browser/intelligence/enhanced_calendar/coordinator/enhanced_calendar_mediator.h
new file mode 100644
index 0000000..60825694
--- /dev/null
+++ b/ios/chrome/browser/intelligence/enhanced_calendar/coordinator/enhanced_calendar_mediator.h
@@ -0,0 +1,35 @@
+// Copyright 2025 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_INTELLIGENCE_ENHANCED_CALENDAR_COORDINATOR_ENHANCED_CALENDAR_MEDIATOR_H_
+#define IOS_CHROME_BROWSER_INTELLIGENCE_ENHANCED_CALENDAR_COORDINATOR_ENHANCED_CALENDAR_MEDIATOR_H_
+
+#import <Foundation/Foundation.h>
+
+#import "ios/chrome/browser/intelligence/enhanced_calendar/ui/enhanced_calendar_mutator.h"
+
+namespace web {
+class WebState;
+}  // namespace web
+
+namespace ios::provider {
+enum class AddToCalendarIntegrationProvider;
+}  // namespace ios::provider
+
+// The mediator for the Enhanced Calendar feature's UI.
+@interface EnhancedCalendarMediator : NSObject <EnhancedCalendarMutator>
+
+- (instancetype)initWithWebState:(web::WebState*)webState
+             integrationProvider:
+                 (ios::provider::AddToCalendarIntegrationProvider)
+                     integrationProvider NS_DESIGNATED_INITIALIZER;
+
+- (instancetype)init NS_UNAVAILABLE;
+
+// Disconnect the mediator.
+- (void)disconnect;
+
+@end
+
+#endif  // IOS_CHROME_BROWSER_INTELLIGENCE_ENHANCED_CALENDAR_COORDINATOR_ENHANCED_CALENDAR_MEDIATOR_H_
diff --git a/ios/chrome/browser/intelligence/enhanced_calendar/coordinator/enhanced_calendar_mediator.mm b/ios/chrome/browser/intelligence/enhanced_calendar/coordinator/enhanced_calendar_mediator.mm
new file mode 100644
index 0000000..f9b4b9f
--- /dev/null
+++ b/ios/chrome/browser/intelligence/enhanced_calendar/coordinator/enhanced_calendar_mediator.mm
@@ -0,0 +1,43 @@
+// Copyright 2025 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/intelligence/enhanced_calendar/coordinator/enhanced_calendar_mediator.h"
+
+#import "base/memory/raw_ptr.h"
+#import "ios/public/provider/chrome/browser/add_to_calendar/add_to_calendar_api.h"
+#import "ios/web/public/web_state.h"
+
+@implementation EnhancedCalendarMediator {
+  // The web state that this mediator is associated with.
+  raw_ptr<web::WebState> _webState;
+
+  // The integration provider for the "add to calendar" experience.
+  ios::provider::AddToCalendarIntegrationProvider _integrationProvider;
+}
+
+- (instancetype)initWithWebState:(web::WebState*)webState
+             integrationProvider:
+                 (ios::provider::AddToCalendarIntegrationProvider)
+                     integrationProvider {
+  self = [super init];
+  if (self) {
+    _webState = webState;
+    _integrationProvider = integrationProvider;
+  }
+  return self;
+}
+
+- (void)disconnect {
+  _webState = nullptr;
+  // TODO(crbug.com/405195613): Cancel any in-flight requests.
+}
+
+#pragma mark - EnhancedCalendarMutator
+
+- (void)cancelEnhancedCalendarRequestAndDismissBottomSheet {
+  // TODO(crbug.com/405195613): Cancel any in-flight requests, and dismiss the
+  // bottom sheet.
+}
+
+@end
diff --git a/ios/chrome/browser/intelligence/enhanced_calendar/ui/BUILD.gn b/ios/chrome/browser/intelligence/enhanced_calendar/ui/BUILD.gn
new file mode 100644
index 0000000..0044d3a6
--- /dev/null
+++ b/ios/chrome/browser/intelligence/enhanced_calendar/ui/BUILD.gn
@@ -0,0 +1,20 @@
+# Copyright 2025 The Chromium Authors
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+source_set("ui") {
+  sources = [
+    "enhanced_calendar_mutator.h",
+    "enhanced_calendar_view_controller.h",
+    "enhanced_calendar_view_controller.mm",
+  ]
+
+  deps = [
+    "//ios/chrome/app/strings:ios_strings_grit",
+    "//ios/chrome/browser/shared/ui/bottom_sheet:bottom_sheet_view_controller",
+    "//ios/chrome/browser/shared/ui/symbols",
+    "//ios/chrome/common/ui/confirmation_alert",
+    "//ios/public/provider/chrome/browser/add_to_calendar:add_to_calendar_api",
+    "//ui/base",
+  ]
+}
diff --git a/ios/chrome/browser/intelligence/enhanced_calendar/ui/enhanced_calendar_mutator.h b/ios/chrome/browser/intelligence/enhanced_calendar/ui/enhanced_calendar_mutator.h
new file mode 100644
index 0000000..928ce7a
--- /dev/null
+++ b/ios/chrome/browser/intelligence/enhanced_calendar/ui/enhanced_calendar_mutator.h
@@ -0,0 +1,18 @@
+// Copyright 2025 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_INTELLIGENCE_ENHANCED_CALENDAR_UI_ENHANCED_CALENDAR_MUTATOR_H_
+#define IOS_CHROME_BROWSER_INTELLIGENCE_ENHANCED_CALENDAR_UI_ENHANCED_CALENDAR_MUTATOR_H_
+
+// Mutator protocol for the view controller to communicate with the
+// EnhancedCalendarMediator.
+@protocol EnhancedCalendarMutator
+
+// Cancels any in-flight EnhancedCalendar requests and dismisses the bottom
+// sheet. User-triggered.
+- (void)cancelEnhancedCalendarRequestAndDismissBottomSheet;
+
+@end
+
+#endif  // IOS_CHROME_BROWSER_INTELLIGENCE_ENHANCED_CALENDAR_UI_ENHANCED_CALENDAR_MUTATOR_H_
diff --git a/ios/chrome/browser/intelligence/enhanced_calendar/ui/enhanced_calendar_view_controller.h b/ios/chrome/browser/intelligence/enhanced_calendar/ui/enhanced_calendar_view_controller.h
new file mode 100644
index 0000000..3e2d83e3
--- /dev/null
+++ b/ios/chrome/browser/intelligence/enhanced_calendar/ui/enhanced_calendar_view_controller.h
@@ -0,0 +1,25 @@
+// Copyright 2025 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_INTELLIGENCE_ENHANCED_CALENDAR_UI_ENHANCED_CALENDAR_VIEW_CONTROLLER_H_
+#define IOS_CHROME_BROWSER_INTELLIGENCE_ENHANCED_CALENDAR_UI_ENHANCED_CALENDAR_VIEW_CONTROLLER_H_
+
+#import <UIKit/UIKit.h>
+
+#import "ios/chrome/browser/shared/ui/bottom_sheet/bottom_sheet_view_controller.h"
+#import "ios/chrome/common/ui/confirmation_alert/confirmation_alert_action_handler.h"
+
+@protocol EnhancedCalendarMutator;
+
+// The view controller for the Enhanced Calendar feature's UI, presented as a
+// bottom sheet.
+@interface EnhancedCalendarViewController
+    : BottomSheetViewController <ConfirmationAlertActionHandler>
+
+// The mutator for this view controller to communicate to the mediator.
+@property(nonatomic, weak) id<EnhancedCalendarMutator> mutator;
+
+@end
+
+#endif  // IOS_CHROME_BROWSER_INTELLIGENCE_ENHANCED_CALENDAR_UI_ENHANCED_CALENDAR_VIEW_CONTROLLER_H_
diff --git a/ios/chrome/browser/intelligence/enhanced_calendar/ui/enhanced_calendar_view_controller.mm b/ios/chrome/browser/intelligence/enhanced_calendar/ui/enhanced_calendar_view_controller.mm
new file mode 100644
index 0000000..e85102b
--- /dev/null
+++ b/ios/chrome/browser/intelligence/enhanced_calendar/ui/enhanced_calendar_view_controller.mm
@@ -0,0 +1,64 @@
+// Copyright 2025 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/intelligence/enhanced_calendar/ui/enhanced_calendar_view_controller.h"
+
+#import "ios/chrome/browser/intelligence/enhanced_calendar/ui/enhanced_calendar_mutator.h"
+#import "ios/chrome/browser/shared/ui/symbols/symbols.h"
+#import "ios/chrome/grit/ios_strings.h"
+#import "ui/base/l10n/l10n_util_mac.h"
+
+namespace {
+
+// The header image SF Symbol name.
+constexpr NSString* kHeaderImageName = @"wand.and.sparkles";
+
+// The header image size.
+const CGFloat kHeaderImageSize = 80.0;
+
+}  // namespace
+
+@implementation EnhancedCalendarViewController
+
+- (void)viewDidLoad {
+  self.actionHandler = self;
+
+  // Configuration.
+  self.scrollEnabled = NO;
+  self.showDismissBarButton = NO;
+
+  // Titles.
+  self.titleString =
+      l10n_util::GetNSString(IDS_IOS_ENHANCED_CALENDAR_BOTTOM_SHEET_TITLE);
+  self.subtitleString =
+      l10n_util::GetNSString(IDS_IOS_ENHANCED_CALENDAR_BOTTOM_SHEET_SUBTITLE);
+  self.primaryActionString = l10n_util::GetNSString(
+      IDS_IOS_ENHANCED_CALENDAR_BOTTOM_SHEET_CANCEL_BUTTON);
+
+  // Header image.
+  self.imageHasFixedSize = YES;
+  self.image = DefaultSymbolWithPointSize(kHeaderImageName, kHeaderImageSize);
+
+  // Loading throbber.
+  UIActivityIndicatorView* loadingThrobber = [[UIActivityIndicatorView alloc]
+      initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleLarge];
+  loadingThrobber.translatesAutoresizingMaskIntoConstraints = NO;
+  self.underTitleView = loadingThrobber;
+  [loadingThrobber startAnimating];
+
+  [super viewDidLoad];
+}
+
+- (void)viewDidDisappear:(BOOL)animated {
+  [self.mutator cancelEnhancedCalendarRequestAndDismissBottomSheet];
+  [super viewDidDisappear:animated];
+}
+
+#pragma mark - ConfirmationAlertActionHandler
+
+- (void)confirmationAlertPrimaryAction {
+  [self.mutator cancelEnhancedCalendarRequestAndDismissBottomSheet];
+}
+
+@end
diff --git a/ios/chrome/browser/intelligence/proto_wrappers/page_context_wrapper.mm b/ios/chrome/browser/intelligence/proto_wrappers/page_context_wrapper.mm
index 3935012..3d565deb 100644
--- a/ios/chrome/browser/intelligence/proto_wrappers/page_context_wrapper.mm
+++ b/ios/chrome/browser/intelligence/proto_wrappers/page_context_wrapper.mm
@@ -159,7 +159,6 @@
 
 // Retrieve WebState snapshot. The barrier's callback will be executed for all
 // codepaths in this method.
-
 - (void)processSnapshotWithBarrier:(base::RepeatingClosure)barrier {
   __weak PageContextWrapper* weakSelf = self;
   auto callback = ^(UIImage* image) {
diff --git a/ios/chrome/browser/lens_overlay/coordinator/lens_overlay_coordinator.mm b/ios/chrome/browser/lens_overlay/coordinator/lens_overlay_coordinator.mm
index 4136a11..1c6df444 100644
--- a/ios/chrome/browser/lens_overlay/coordinator/lens_overlay_coordinator.mm
+++ b/ios/chrome/browser/lens_overlay/coordinator/lens_overlay_coordinator.mm
@@ -416,13 +416,13 @@
     [_selectionViewController start];
 
     if (self.isResultsBottomSheetCreated) {
-      // Only show the bottom sheet when in selection. For translate, build the
-      // necessary infrastructure but don't show it, effectively starting it
-      // hidden.
       [self buildResultsBottomSheetPresentation];
-      if (!_selectionViewController.translateFilterActive) {
-        [self showResultsBottomSheet];
-      }
+      [self showResultsBottomSheet];
+    } else if (_selectionViewController.translateFilterActive) {
+      [self startResultPage];
+      [_resultsPagePresenter
+          showInfoMessage:LensOverlayBottomSheetInfoMessageType::
+                              kImageTranslatedIndication];
     } else {
       [self scheduleTooltipHintDisplayIfNecessary];
     }
@@ -736,6 +736,12 @@
   }
 }
 
+- (void)lensOverlayMediatorDidFailDetectingTranslatableText {
+  [self startResultPage];
+  [_resultsPagePresenter showInfoMessage:LensOverlayBottomSheetInfoMessageType::
+                                             kNoTranslatableTextWarning];
+}
+
 - (void)prepareLensUIForBackgroundTabChange {
   if (!_associatedTabHelper || !self.isUICreated) {
     return;
@@ -761,11 +767,7 @@
   [_metricsRecorder
       recordResultLoadedWithTextSelection:_mediator.currentLensResult
                                               .isTextSelection];
-
-  if (!_resultMediator) {
-    [self startResultPage];
-  }
-
+  [self startResultPage];
   [_resultMediator loadResultsURL:url];
 }
 
@@ -782,9 +784,7 @@
 }
 
 - (void)handleSlowRequestHasStarted {
-  if (!_resultMediator) {
-    [self startResultPage];
-  }
+  [self startResultPage];
   [_resultMediator handleSlowRequestHasStarted];
 }
 
@@ -1029,6 +1029,10 @@
 
 // Creates and displays the results bottom sheet.
 - (void)startResultPage {
+  if (_resultMediator) {
+    return;
+  }
+
   Browser* browser = self.browser;
   ProfileIOS* profile = browser->GetProfile();
 
diff --git a/ios/chrome/browser/lens_overlay/coordinator/lens_overlay_mediator.mm b/ios/chrome/browser/lens_overlay/coordinator/lens_overlay_mediator.mm
index f1ca8b6d..79d82d0 100644
--- a/ios/chrome/browser/lens_overlay/coordinator/lens_overlay_mediator.mm
+++ b/ios/chrome/browser/lens_overlay/coordinator/lens_overlay_mediator.mm
@@ -239,15 +239,15 @@
       // bottom sheet hidden, as no auto selection happens at this stage.
       [self.lensHandler resetSelectionAreaToInitialPosition:^{
       }];
-      [self.presentationDelegate hideBottomSheet];
+      [self.presentationDelegate
+          showInfoMessage:LensOverlayBottomSheetInfoMessageType::
+                              kImageTranslatedIndication];
     } else if (noSelectionInTranslate) {
       // A missing selection without a switch in modes indicates the user
       // intended to dismiss the current selection.
-      [self.presentationDelegate hideBottomSheet];
-    } else if (switchToSelection || willUseTranslate) {
-      // When transitioning to selection the bottom sheet might be hidden. As
-      // auto selection might be on we need to restore it if hidden.
-      [self.presentationDelegate revealBottomSheetIfHidden];
+      [self.presentationDelegate
+          showInfoMessage:LensOverlayBottomSheetInfoMessageType::
+                              kImageTranslatedIndication];
     }
   }
 
@@ -258,8 +258,22 @@
 
 // The lens overlay search request produced an error.
 - (void)lensOverlayDidReceiveError:(id<ChromeLensOverlay>)lensOverlay {
-  [self.resultConsumer handleSearchRequestErrored];
+  __weak id<LensOverlayResultConsumer> weakResultConsumer = self.resultConsumer;
+  auto completion = ^{
+    [weakResultConsumer handleSearchRequestErrored];
+  };
   [self.toolbarConsumer setOmniboxEnabled:YES];
+  // Make sure the bottom sheet is dismissed before triggering any alert.
+  if (self.presentationDelegate) {
+    [self.presentationDelegate hideBottomSheetWithCompletion:completion];
+  } else {
+    completion();
+  }
+}
+
+- (void)lensOverlayDidFailDetectingTranslatableText:
+    (id<ChromeLensOverlay>)lensOverlay {
+  [_delegate lensOverlayMediatorDidFailDetectingTranslatableText];
 }
 
 // The lens overlay search request produced a valid result.
diff --git a/ios/chrome/browser/lens_overlay/coordinator/lens_overlay_mediator_delegate.h b/ios/chrome/browser/lens_overlay/coordinator/lens_overlay_mediator_delegate.h
index eb5a07b..84c47be 100644
--- a/ios/chrome/browser/lens_overlay/coordinator/lens_overlay_mediator_delegate.h
+++ b/ios/chrome/browser/lens_overlay/coordinator/lens_overlay_mediator_delegate.h
@@ -19,6 +19,10 @@
 /// Called when an URL needs to be opened in a new tab.
 - (void)lensOverlayMediatorOpenURLInNewTabRequsted:(GURL)url;
 
+/// Called when a translation failed due to no or unprocessable text in the
+/// given image.
+- (void)lensOverlayMediatorDidFailDetectingTranslatableText;
+
 @end
 
 #endif  // IOS_CHROME_BROWSER_LENS_OVERLAY_COORDINATOR_LENS_OVERLAY_MEDIATOR_DELEGATE_H_
diff --git a/ios/chrome/browser/lens_overlay/model/lens_overlay_detents_manager.mm b/ios/chrome/browser/lens_overlay/model/lens_overlay_detents_manager.mm
index 9968a38..e2886f9 100644
--- a/ios/chrome/browser/lens_overlay/model/lens_overlay_detents_manager.mm
+++ b/ios/chrome/browser/lens_overlay/model/lens_overlay_detents_manager.mm
@@ -19,9 +19,16 @@
 // The identifier for the peak detent.
 NSString* const kPeakSheetDetentIdentifier = @"kPeakSheetDetentIdentifier";
 
+// The identifier for the info message state.
+NSString* const kInfoMessageSheetDetentIdentifier =
+    @"kInfoMessageSheetDetentIdentifier";
+
 // The detent height in points for the 'peak' state of the bottom sheet.
 const CGFloat kPeakDetentHeight = 100.0;
 
+// The detent height in points for the error message state of the bottom sheet.
+const CGFloat kinfoMessageDetentHeight = 160.0;
+
 // The percentage of the screen that will be covered by the bottom sheet in
 // translate mode.
 const CGFloat kTranslateSheetHeightRatio = 0.33;
@@ -130,16 +137,17 @@
     return SheetDimensionState::kConsent;
   }
 
+  if ([identifier isEqualToString:kInfoMessageSheetDetentIdentifier]) {
+    return SheetDimensionState::kInfoMessage;
+  }
+
   return SheetDimensionState::kHidden;
 }
 
 - (void)setPresentationStrategy:
     (SheetDetentPresentationStategy)presentationStrategy {
   _presentationStrategy = presentationStrategy;
-  if ([self isInMediumDetent] || [self isInLargeDetent]) {
-    // Refresh the detents presentation for the unrestricted state.
-    [self adjustDetentsForState:SheetDetentStateUnrestrictedMovement];
-  }
+  [self adjustDetentsForState:SheetDetentStateUnrestrictedMovement];
 }
 
 #pragma mark - Public methods
@@ -226,6 +234,11 @@
       _sheet.largestUndimmedDetentIdentifier = kConsentSheetDetentIdentifier;
       _sheet.selectedDetentIdentifier = kConsentSheetDetentIdentifier;
       break;
+    case SheetDetentStateInfoMessage:
+      _sheet.detents = @[ [self infoMessageDetent] ];
+      _sheet.largestUndimmedDetentIdentifier =
+          kInfoMessageSheetDetentIdentifier;
+      _sheet.selectedDetentIdentifier = kInfoMessageSheetDetentIdentifier;
   }
 
   [self reportDimensionChangeIfNeeded:NO];
@@ -291,4 +304,14 @@
                         resolver:peakHeightResolver];
 }
 
+- (UISheetPresentationControllerDetent*)infoMessageDetent {
+  auto infoMessageHeightResolver = ^CGFloat(
+      id<UISheetPresentationControllerDetentResolutionContext> context) {
+    return kinfoMessageDetentHeight;
+  };
+  return [UISheetPresentationControllerDetent
+      customDetentWithIdentifier:kInfoMessageSheetDetentIdentifier
+                        resolver:infoMessageHeightResolver];
+}
+
 @end
diff --git a/ios/chrome/browser/lens_overlay/model/lens_overlay_sheet_detent_state.h b/ios/chrome/browser/lens_overlay/model/lens_overlay_sheet_detent_state.h
index ffc7175..1f58469 100644
--- a/ios/chrome/browser/lens_overlay/model/lens_overlay_sheet_detent_state.h
+++ b/ios/chrome/browser/lens_overlay/model/lens_overlay_sheet_detent_state.h
@@ -13,6 +13,8 @@
   SheetDetentStateConsentDialog,
   // The bottom sheet is in the peak state.
   SheetDetentStatePeakEnabled,
+  // The bottom sheet is showing an info message.
+  SheetDetentStateInfoMessage,
 };
 
 // Indicates the state of the bottom sheet dimension.
@@ -31,7 +33,9 @@
   kPeaking = 3,
   // The bottom sheet is displayed with the fixed custom dimensions for consent.
   kConsent = 4,
-  kMaxValue = kConsent,
+  // The bottom sheet is showing an info message.
+  kInfoMessage = 5,
+  kMaxValue = kInfoMessage,
 };
 // LINT.ThenChange(//tools/metrics/histograms/metadata/lens/enums.xml:SheetDimensionState)
 
diff --git a/ios/chrome/browser/lens_overlay/ui/BUILD.gn b/ios/chrome/browser/lens_overlay/ui/BUILD.gn
index dc4b299e..f9f20a9a 100644
--- a/ios/chrome/browser/lens_overlay/ui/BUILD.gn
+++ b/ios/chrome/browser/lens_overlay/ui/BUILD.gn
@@ -156,6 +156,7 @@
     ":protocols",
     "//base",
     "//ios/chrome/app/strings:ios_strings_grit",
+    "//ios/chrome/browser/lens_overlay/model",
     "//ui/base",
     "//url",
   ]
diff --git a/ios/chrome/browser/lens_overlay/ui/lens_overlay_bottom_sheet_presentation_delegate.h b/ios/chrome/browser/lens_overlay/ui/lens_overlay_bottom_sheet_presentation_delegate.h
index b70920e..a0fe1a8 100644
--- a/ios/chrome/browser/lens_overlay/ui/lens_overlay_bottom_sheet_presentation_delegate.h
+++ b/ios/chrome/browser/lens_overlay/ui/lens_overlay_bottom_sheet_presentation_delegate.h
@@ -7,6 +7,14 @@
 
 #import <Foundation/Foundation.h>
 
+// Possible info messages to be shown in the bottom sheets.
+enum class LensOverlayBottomSheetInfoMessageType {
+  // Informs the user that there was an error detecting the text in the image
+  kNoTranslatableTextWarning,
+  // Informs the user that a translation was executed on the given image.
+  kImageTranslatedIndication,
+};
+
 // Presentation delegate for the bottom sheet.
 // Bottom sheet content may request the container to be maximized or minimized,
 // e.g. when the user selects a result that opens an image viewer.
@@ -25,11 +33,14 @@
 - (void)didLoadTranslateResult;
 
 // Hides the bottom sheet without destroying the presentation.
-- (void)hideBottomSheet;
+- (void)hideBottomSheetWithCompletion:(void (^)(void))completion;
 
 // Reveals the hidden bottom sheet.
 - (void)revealBottomSheetIfHidden;
 
+// Hides the results UI and shows the given informational message.
+- (void)showInfoMessage:(LensOverlayBottomSheetInfoMessageType)infoMessageType;
+
 @end
 
 #endif  // IOS_CHROME_BROWSER_LENS_OVERLAY_UI_LENS_OVERLAY_BOTTOM_SHEET_PRESENTATION_DELEGATE_H_
diff --git a/ios/chrome/browser/lens_overlay/ui/lens_overlay_results_page_presenter.mm b/ios/chrome/browser/lens_overlay/ui/lens_overlay_results_page_presenter.mm
index a85b571c..0dac9a4d 100644
--- a/ios/chrome/browser/lens_overlay/ui/lens_overlay_results_page_presenter.mm
+++ b/ios/chrome/browser/lens_overlay/ui/lens_overlay_results_page_presenter.mm
@@ -29,11 +29,24 @@
 // by the bottom sheet presentation.
 const CGFloat kVisibleAreaMediumDetentThreshold = 100.0f;
 
+// The duration of the opacity transition in the results page.
+const CGFloat kOpacityAnimationDuration = 0.4;
+
 }  // namespace
 
+// Animator responsible for handling the hiding and showing fading transition of
+// the informational message views.
+@interface InfoMessageAnimator
+    : NSObject <UIViewControllerAnimatedTransitioning>
+
+- (instancetype)initWithOperation:(UINavigationControllerOperation)operation;
+
+@end
+
 @interface LensOverlayResultsPagePresenter () <
     LensOverlayPanTrackerDelegate,
-    LensOverlayDetentsManagerDelegate>
+    LensOverlayDetentsManagerDelegate,
+    UINavigationControllerDelegate>
 @end
 
 @implementation LensOverlayResultsPagePresenter {
@@ -41,7 +54,7 @@
   __weak UIViewController* _baseViewController;
 
   /// The results view controller to present.
-  __weak UIViewController* _resultViewController;
+  __weak LensResultPageViewController* _resultViewController;
 
   /// Orchestrates the change in detents of the associated bottom sheet.
   LensOverlayDetentsManager* _detentsManager;
@@ -63,6 +76,13 @@
 
   // The layout guide that defines the unobstructed area by the presentation.
   UILayoutGuide* _visibleAreaLayoutGuide;
+
+  // TODO(crbug.com/405199044): Consider using a custom container for the UI
+  // orchestration.
+  //
+  // The navigation controller orchestrating the change between the results page
+  // and the informational messages.
+  UINavigationController* _presentationNavigationController;
 }
 
 - (instancetype)initWithBaseViewController:(UIViewController*)baseViewController
@@ -72,6 +92,12 @@
   if (self) {
     _baseViewController = baseViewController;
     _resultViewController = resultViewController;
+    _presentationNavigationController = [[UINavigationController alloc]
+        initWithRootViewController:resultViewController];
+    _presentationNavigationController.toolbarHidden = YES;
+    _presentationNavigationController.navigationBarHidden = YES;
+    _presentationNavigationController.delegate = self;
+
     _visibleAreaLayoutGuide = [[UILayoutGuide alloc] init];
   }
 
@@ -80,7 +106,8 @@
 
 - (BOOL)isResultPageVisible {
   return _baseViewController.presentedViewController != nil &&
-         _baseViewController.presentedViewController == _resultViewController;
+         _baseViewController.presentedViewController ==
+             _presentationNavigationController;
 }
 
 - (SheetDimensionState)sheetDimension {
@@ -140,7 +167,7 @@
   }
 
   UISheetPresentationController* sheet =
-      _resultViewController.sheetPresentationController;
+      _presentationNavigationController.sheetPresentationController;
   sheet.prefersEdgeAttachedInCompactHeight = YES;
   sheet.prefersGrabberVisible = YES;
   sheet.preferredCornerRadius = kPreferredCornerRadius;
@@ -191,7 +218,7 @@
 
   __weak __typeof(self) weakSelf = self;
   [_baseViewController
-      presentViewController:_resultViewController
+      presentViewController:_presentationNavigationController
                    animated:animated
                  completion:^{
                    [weakSelf didFinishPresentingResultsPage];
@@ -222,7 +249,18 @@
   }
 }
 
-- (void)hideBottomSheet {
+- (void)showInfoMessage:(LensOverlayBottomSheetInfoMessageType)infoMessageType {
+  [_detentsManager adjustDetentsForState:SheetDetentStateInfoMessage];
+
+  // TODO(crbug.com/405199044): Replace with the real UI once built.
+  UIViewController* infoMessageViewController = [[UIViewController alloc] init];
+  infoMessageViewController.view.backgroundColor = [UIColor whiteColor];
+  [_presentationNavigationController
+      pushViewController:infoMessageViewController
+                animated:YES];
+}
+
+- (void)hideBottomSheetWithCompletion:(void (^)(void))completion {
   [_displayLink invalidate];
   [self sheetPresentationHeightChanged:0];
   [_windowPanTracker stopTracking];
@@ -230,7 +268,7 @@
   _detentsManager = nil;
 
   UIViewController* presentedVC = _baseViewController.presentedViewController;
-  [presentedVC dismissViewControllerAnimated:YES completion:nil];
+  [presentedVC dismissViewControllerAnimated:YES completion:completion];
 }
 
 - (void)dismissResultsPageAnimated:(BOOL)animated
@@ -273,17 +311,17 @@
     return;
   }
 
+  UIView* presentedView = _baseViewController.presentedViewController.view;
+
   // Early return if the bottom sheet is not displayed yet.
-  CALayer* presentationLayer =
-      _resultViewController.view.layer.presentationLayer;
+  CALayer* presentationLayer = presentedView.layer.presentationLayer;
   if (!presentationLayer) {
     return;
   }
 
-  CGRect presentedFrame = _resultViewController.view.frame;
-  CGRect newFrame =
-      [_resultViewController.view convertRect:presentedFrame
-                                       toView:_baseViewController.view];
+  CGRect presentedFrame = presentedView.frame;
+  CGRect newFrame = [presentedView convertRect:presentedFrame
+                                        toView:_baseViewController.view];
   CGFloat containerHeight = _baseViewController.view.frame.size.height;
   CGFloat currentSheetHeight = containerHeight - newFrame.origin.y;
 
@@ -306,9 +344,11 @@
       currentSheetHeight <= kThresholdHeightForClosingSheet;
   BOOL userTouchesTheScreen = _windowPanTracker.isPanning;
 
-  BOOL shouldDestroyLensUI = sheetClosedThresholdReached &&
-                             !userTouchesTheScreen &&
-                             !_presentingAnimationInProgress;
+  BOOL infoMessageShown =
+      _detentsManager.sheetDimension == SheetDimensionState::kInfoMessage;
+  BOOL shouldDestroyLensUI =
+      sheetClosedThresholdReached && !userTouchesTheScreen &&
+      !_presentingAnimationInProgress && !infoMessageShown;
   if (shouldDestroyLensUI) {
     [_displayLink invalidate];
     [self sheetPresentationHeightChanged:0];
@@ -408,6 +448,7 @@
     case SheetDimensionState::kHidden:
       return YES;
     case SheetDimensionState::kPeaking:
+    case SheetDimensionState::kInfoMessage:
     case SheetDimensionState::kLarge:
       return NO;
     case SheetDimensionState::kMedium:
@@ -439,13 +480,86 @@
 - (void)didLoadSelectionResult {
   _detentsManager.presentationStrategy =
       SheetDetentPresentationStategySelection;
+  [_presentationNavigationController popToRootViewControllerAnimated:YES];
   [self adjustSelectionOcclusionInsets];
 }
 
 - (void)didLoadTranslateResult {
   _detentsManager.presentationStrategy =
       SheetDetentPresentationStategyTranslate;
+  [_presentationNavigationController popToRootViewControllerAnimated:YES];
   [self adjustSelectionOcclusionInsets];
 }
 
+#pragma mark - UINavigationControllerDelegate
+
+- (id<UIViewControllerAnimatedTransitioning>)
+               navigationController:
+                   (UINavigationController*)navigationController
+    animationControllerForOperation:(UINavigationControllerOperation)operation
+                 fromViewController:(UIViewController*)fromVC
+                   toViewController:(UIViewController*)toVC {
+  return [[InfoMessageAnimator alloc] initWithOperation:operation];
+}
+
+@end
+
+@implementation InfoMessageAnimator {
+  // The navigation operation for the animator.
+  UINavigationControllerOperation _operation;
+}
+
+- (instancetype)initWithOperation:(UINavigationControllerOperation)operation {
+  self = [super init];
+  if (self) {
+    _operation = operation;
+  }
+
+  return self;
+}
+
+- (NSTimeInterval)transitionDuration:
+    (id<UIViewControllerContextTransitioning>)transitionContext {
+  return kOpacityAnimationDuration;
+}
+
+- (void)animateTransition:
+    (id<UIViewControllerContextTransitioning>)transitionContext {
+  UIViewController* toViewController = [transitionContext
+      viewControllerForKey:UITransitionContextToViewControllerKey];
+  UIViewController* fromViewController = [transitionContext
+      viewControllerForKey:UITransitionContextFromViewControllerKey];
+  NSTimeInterval duration = [self transitionDuration:transitionContext];
+  auto animationFinished = ^(BOOL finished) {
+    [transitionContext
+        completeTransition:![transitionContext transitionWasCancelled]];
+  };
+
+  if (_operation == UINavigationControllerOperationPush) {
+    [[transitionContext containerView] addSubview:toViewController.view];
+    toViewController.view.alpha = 0.0;
+    [UIView animateWithDuration:duration
+                     animations:^{
+                       toViewController.view.alpha = 1.0;
+                     }
+                     completion:animationFinished];
+
+    return;
+  }
+
+  if (_operation == UINavigationControllerOperationPop) {
+    [[transitionContext containerView] insertSubview:toViewController.view
+                                        belowSubview:fromViewController.view];
+
+    [UIView animateWithDuration:duration
+                     animations:^{
+                       fromViewController.view.alpha = 0.0;
+                     }
+                     completion:animationFinished];
+    return;
+  }
+
+  animationFinished(YES);
+}
+
 @end
diff --git a/ios/chrome/browser/settings/ui_bundled/autofill/BUILD.gn b/ios/chrome/browser/settings/ui_bundled/autofill/BUILD.gn
index b703b91..9c0f95d3 100644
--- a/ios/chrome/browser/settings/ui_bundled/autofill/BUILD.gn
+++ b/ios/chrome/browser/settings/ui_bundled/autofill/BUILD.gn
@@ -161,6 +161,7 @@
     "//ios/chrome/browser/authentication/ui_bundled:eg_test_support+eg2",
     "//ios/chrome/browser/autofill/ui_bundled:eg_test_support+eg2",
     "//ios/chrome/browser/autofill/ui_bundled/address_editor:constants",
+    "//ios/chrome/browser/autofill/ui_bundled/bottom_sheet:constants",
     "//ios/chrome/browser/metrics/model:eg_test_support+eg2",
     "//ios/chrome/browser/policy/model:eg_test_support+eg2",
     "//ios/chrome/browser/settings/ui_bundled:settings_root_constants",
diff --git a/ios/chrome/browser/settings/ui_bundled/autofill/autofill_add_address_manually_egtest.mm b/ios/chrome/browser/settings/ui_bundled/autofill/autofill_add_address_manually_egtest.mm
index 044a310..2cc694a 100644
--- a/ios/chrome/browser/settings/ui_bundled/autofill/autofill_add_address_manually_egtest.mm
+++ b/ios/chrome/browser/settings/ui_bundled/autofill/autofill_add_address_manually_egtest.mm
@@ -9,6 +9,7 @@
 #import "ios/chrome/browser/authentication/ui_bundled/signin_earl_grey.h"
 #import "ios/chrome/browser/authentication/ui_bundled/signin_earl_grey_ui_test_util.h"
 #import "ios/chrome/browser/autofill/ui_bundled/autofill_app_interface.h"
+#import "ios/chrome/browser/autofill/ui_bundled/bottom_sheet/bottom_sheet_constants.h"
 #import "ios/chrome/browser/signin/model/fake_system_identity.h"
 #import "ios/chrome/grit/ios_strings.h"
 #import "ios/chrome/test/earl_grey/chrome_earl_grey.h"
@@ -22,6 +23,7 @@
 using chrome_test_util::ButtonWithAccessibilityLabelId;
 using chrome_test_util::SettingsDoneButton;
 using chrome_test_util::SettingsMenuBackButton;
+using chrome_test_util::SettingsProfileMatcher;
 using chrome_test_util::SettingsToolbarAddButton;
 
 namespace {
@@ -56,6 +58,11 @@
                     grey_kindOfClass([UITextField class]), nil);
 }
 
+// Matcher for the "add address" bottom sheet.
+id<GREYMatcher> EditProfileBottomSheet() {
+  return grey_accessibilityID(kEditProfileBottomSheetViewIdentfier);
+}
+
 // Helper to open the address settings page.
 void OpenAddressSettings() {
   [ChromeEarlGreyUI openSettingsMenu];
@@ -117,7 +124,7 @@
       performAction:grey_replaceText(@"H3H 1H1")];
 }
 
-// Helper to open the add address view.
+// Helper to open the "add address" bottom sheet.
 - (void)openAddAddressView:(BOOL)signIn {
   if (signIn) {
     [SigninEarlGreyUI
@@ -131,6 +138,10 @@
   // Tap the "Add" button.
   [[EarlGrey selectElementWithMatcher:SettingsToolbarAddButton()]
       performAction:grey_tap()];
+
+  // Verify the "add address" bottom sheet is visible.
+  [[EarlGrey selectElementWithMatcher:EditProfileBottomSheet()]
+      assertWithMatcher:grey_sufficientlyVisible()];
 }
 
 #pragma mark - Tests
@@ -182,11 +193,23 @@
   [SigninEarlGreyUI signOut];
 }
 
-// Tests that tapping the `Cancel` button triggers the dismissal of the add
-// address bottom sheet.
+// Tests that tapping the `Cancel` button triggers the dismissal of the "add
+// address" bottom sheet.
 - (void)testCancelButton {
-  // TODO(crbug.com/406799169): EGTest for checking if the `Cancel` button works
-  // as expected.
+  [self openAddAddressView:NO];
+
+  // Tap "Cancel".
+  [[EarlGrey
+      selectElementWithMatcher:grey_allOf(
+                                   grey_accessibilityID(
+                                       kEditProfileBottomSheetCancelButton),
+                                   grey_accessibilityTrait(
+                                       UIAccessibilityTraitButton),
+                                   nil)] performAction:grey_tap()];
+
+  // Verify the address settings opened.
+  [[EarlGrey selectElementWithMatcher:SettingsProfileMatcher()]
+      assertWithMatcher:grey_sufficientlyVisible()];
 }
 
 // Tests the 'Save' button enabled state when manually adding an address to the
diff --git a/ios/chrome/browser/shared/model/prefs/BUILD.gn b/ios/chrome/browser/shared/model/prefs/BUILD.gn
index 85837ed7..70a59c0 100644
--- a/ios/chrome/browser/shared/model/prefs/BUILD.gn
+++ b/ios/chrome/browser/shared/model/prefs/BUILD.gn
@@ -140,6 +140,10 @@
     "//ios/web/common:features",
     "//ui/base",
   ]
+
+  if (target_environment != "catalyst") {
+    deps += [ "//ios/chrome/browser/default_browser/model/default_status" ]
+  }
 }
 
 source_set("unit_tests") {
diff --git a/ios/chrome/browser/shared/model/prefs/browser_prefs.mm b/ios/chrome/browser/shared/model/prefs/browser_prefs.mm
index ab5d0cf6e..1055b7f 100644
--- a/ios/chrome/browser/shared/model/prefs/browser_prefs.mm
+++ b/ios/chrome/browser/shared/model/prefs/browser_prefs.mm
@@ -126,6 +126,10 @@
 #import "ios/web/common/features.h"
 #import "ui/base/l10n/l10n_util.h"
 
+#if !BUILDFLAG(IS_IOS_MACCATALYST)
+#import "ios/chrome/browser/default_browser/model/default_status/default_status_helper_prefs.h"
+#endif  // !BUILDFLAG(IS_IOS_MACCATALYST)
+
 namespace {
 
 // Deprecated 05/2024.
@@ -404,6 +408,10 @@
   auto_deletion::AutoDeletionService::RegisterLocalStatePrefs(registry);
   push_notification_prefs::RegisterLocalStatePrefs(registry);
 
+#if !BUILDFLAG(IS_IOS_MACCATALYST)
+  default_status::RegisterDefaultStatusPrefs(registry);
+#endif  // !BUILDFLAG(IS_IOS_MACCATALYST)
+
   // Preferences related to the profile manager.
   registry->RegisterStringPref(prefs::kLastUsedProfile, std::string());
   registry->RegisterBooleanPref(prefs::kLegacyProfileHidden, false);
diff --git a/ios/chrome/browser/shared/public/commands/BUILD.gn b/ios/chrome/browser/shared/public/commands/BUILD.gn
index 5f2806d..a4956b4 100644
--- a/ios/chrome/browser/shared/public/commands/BUILD.gn
+++ b/ios/chrome/browser/shared/public/commands/BUILD.gn
@@ -25,6 +25,7 @@
     "credential_provider_promo_commands.h",
     "docking_promo_commands.h",
     "drive_file_picker_commands.h",
+    "enhanced_calendar_commands.h",
     "feed_commands.h",
     "find_in_page_commands.h",
     "generate_qr_code_command.h",
diff --git a/ios/chrome/browser/shared/public/commands/enhanced_calendar_commands.h b/ios/chrome/browser/shared/public/commands/enhanced_calendar_commands.h
new file mode 100644
index 0000000..2cc7c50
--- /dev/null
+++ b/ios/chrome/browser/shared/public/commands/enhanced_calendar_commands.h
@@ -0,0 +1,25 @@
+// Copyright 2025 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_SHARED_PUBLIC_COMMANDS_ENHANCED_CALENDAR_COMMANDS_H_
+#define IOS_CHROME_BROWSER_SHARED_PUBLIC_COMMANDS_ENHANCED_CALENDAR_COMMANDS_H_
+
+namespace ios::provider {
+enum class AddToCalendarIntegrationProvider;
+}  // namespace ios::provider
+
+// Commands to show/hide the Enhanced Calendar bottom sheet.
+@protocol EnhancedCalendarCommands <NSObject>
+
+// Shows the Enhanced Calendar bottom sheet for the current WebState.
+- (void)showEnhancedCalendarBottomSheetWithIntegrationProvider:
+    (ios::provider::AddToCalendarIntegrationProvider)integrationProvider;
+
+// Hides the Enhanced Calendar bottom sheet. Will also cancel any in-flight
+// Enhanced Calendar model requests
+- (void)hideEnhancedCalendarBottomSheet;
+
+@end
+
+#endif  // IOS_CHROME_BROWSER_SHARED_PUBLIC_COMMANDS_ENHANCED_CALENDAR_COMMANDS_H_
diff --git a/ios/chrome/browser/shared/public/features/features.h b/ios/chrome/browser/shared/public/features/features.h
index e2560d7..9a33ad0 100644
--- a/ios/chrome/browser/shared/public/features/features.h
+++ b/ios/chrome/browser/shared/public/features/features.h
@@ -966,7 +966,7 @@
 extern const std::string_view
     kFRESignInSecondaryActionLabelUpdateParamStaySignedOut;
 
-// Returns whether 'kFRESignInSecondaryActionLabelUpdate' is enabled
+// Returns whether 'kFRESignInSecondaryActionLabelUpdate' is enabled.
 bool FRESignInSecondaryActionLabelUpdate();
 
 // Enables passkey syncing follow-up features.
@@ -1094,4 +1094,11 @@
 // Checks if background customization is enabled on the NTP.
 bool IsNTPBackgroundCustomizationEnabled();
 
+// Feature flag to control whether default status API check and reporting are
+// enabled.
+BASE_DECLARE_FEATURE(kRunDefaultStatusCheck);
+
+// Returns whether `kRunDefaultStatusCheck` is enabled.
+bool IsRunDefaultStatusCheckEnabled();
+
 #endif  // IOS_CHROME_BROWSER_SHARED_PUBLIC_FEATURES_FEATURES_H_
diff --git a/ios/chrome/browser/shared/public/features/features.mm b/ios/chrome/browser/shared/public/features/features.mm
index 90810282..9f114bd 100644
--- a/ios/chrome/browser/shared/public/features/features.mm
+++ b/ios/chrome/browser/shared/public/features/features.mm
@@ -1345,3 +1345,11 @@
 bool IsNTPBackgroundCustomizationEnabled() {
   return base::FeatureList::IsEnabled(kNTPBackgroundCustomization);
 }
+
+BASE_FEATURE(kRunDefaultStatusCheck,
+             "RunDefaultStatusCheck",
+             base::FEATURE_DISABLED_BY_DEFAULT);
+
+bool IsRunDefaultStatusCheckEnabled() {
+  return base::FeatureList::IsEnabled(kRunDefaultStatusCheck);
+}
diff --git a/ios/chrome/credential_provider_extension/BUILD.gn b/ios/chrome/credential_provider_extension/BUILD.gn
index a655b34..bb276da 100644
--- a/ios/chrome/credential_provider_extension/BUILD.gn
+++ b/ios/chrome/credential_provider_extension/BUILD.gn
@@ -168,6 +168,18 @@
   frameworks = [ "Foundation.framework" ]
 }
 
+source_set("favicon_util") {
+  sources = [
+    "favicon_util.h",
+    "favicon_util.mm",
+  ]
+  deps = [
+    "//ios/chrome/common/app_group",
+    "//ios/chrome/common/ui/favicon",
+  ]
+  frameworks = [ "Foundation.framework" ]
+}
+
 source_set("passkey_request_details") {
   sources = [
     "passkey_request_details.h",
diff --git a/ios/chrome/credential_provider_extension/credential_provider_view_controller.mm b/ios/chrome/credential_provider_extension/credential_provider_view_controller.mm
index e9304c6..82bbb5c 100644
--- a/ios/chrome/credential_provider_extension/credential_provider_view_controller.mm
+++ b/ios/chrome/credential_provider_extension/credential_provider_view_controller.mm
@@ -1017,16 +1017,34 @@
                    completion:nil];
 }
 
+// Returns the favicon associated with the rpId if it exists.
+// Returns nil otherwise.
+- (NSString*)faviconForRpId:(NSString*)rpId {
+  // Verify if a favicon already exists for the provided rpId.
+  NSArray<id<Credential>>* credentials = self.credentialStore.credentials;
+  NSUInteger credentialIndex =
+      [credentials indexOfObjectPassingTest:^BOOL(id<Credential> credential,
+                                                  NSUInteger idx, BOOL* stop) {
+        return [credential.rpId isEqualToString:rpId] &&
+               credential.favicon.length > 0;
+      }];
+  return credentialIndex != NSNotFound ? credentials[credentialIndex].favicon
+                                       : nil;
+}
+
 // Shows a confirmation dialog to the user before performing passkey creation.
 - (void)showMultiProfilePasskeyCreationDialogWithDetails:
             (PasskeyRequestDetails*)passkeyRequestDetails
                                                     gaia:(NSString*)gaia {
+  NSString* favicon =
+      [self faviconForRpId:passkeyRequestDetails.relyingPartyIdentifier];
   MultiProfilePasskeyCreationViewController*
       multiProfilePasskeyCreationViewController =
           [[MultiProfilePasskeyCreationViewController alloc]
                       initWithDetails:passkeyRequestDetails
                                  gaia:gaia
                             userEmail:[self userEmail]
+                              favicon:favicon
               navigationItemTitleView:self.passkeyNavigationItemTitleView
                              delegate:self];
 
diff --git a/ios/chrome/credential_provider_extension/favicon_util.h b/ios/chrome/credential_provider_extension/favicon_util.h
new file mode 100644
index 0000000..1283f6ed
--- /dev/null
+++ b/ios/chrome/credential_provider_extension/favicon_util.h
@@ -0,0 +1,19 @@
+// Copyright 2025 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_CREDENTIAL_PROVIDER_EXTENSION_FAVICON_UTIL_H_
+#define IOS_CHROME_CREDENTIAL_PROVIDER_EXTENSION_FAVICON_UTIL_H_
+
+#import <Foundation/Foundation.h>
+
+@class FaviconAttributes;
+
+typedef void (^BlockWithFaviconAttributes)(FaviconAttributes*);
+
+// Fetches the favicon attributes of the provided favicon and calls the
+// completion block with the favicon attributes.
+void FetchFaviconAsync(NSString* favicon,
+                       BlockWithFaviconAttributes completion);
+
+#endif  // IOS_CHROME_CREDENTIAL_PROVIDER_EXTENSION_FAVICON_UTIL_H_
diff --git a/ios/chrome/credential_provider_extension/favicon_util.mm b/ios/chrome/credential_provider_extension/favicon_util.mm
new file mode 100644
index 0000000..4983140b
--- /dev/null
+++ b/ios/chrome/credential_provider_extension/favicon_util.mm
@@ -0,0 +1,29 @@
+// Copyright 2025 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/credential_provider_extension/favicon_util.h"
+
+#import "ios/chrome/common/app_group/app_group_constants.h"
+#import "ios/chrome/common/ui/favicon/favicon_attributes.h"
+
+void FetchFaviconAsync(NSString* favicon,
+                       BlockWithFaviconAttributes completion) {
+  dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
+    NSURL* filePath = [app_group::SharedFaviconAttributesFolder()
+        URLByAppendingPathComponent:favicon
+                        isDirectory:NO];
+    NSError* error = nil;
+    NSData* data = [NSData dataWithContentsOfURL:filePath
+                                         options:0
+                                           error:&error];
+    FaviconAttributes* attributes = nil;
+    if (data && !error) {
+      NSKeyedUnarchiver* unarchiver =
+          [[NSKeyedUnarchiver alloc] initForReadingFromData:data error:nil];
+      unarchiver.requiresSecureCoding = NO;
+      attributes = [unarchiver decodeObjectForKey:NSKeyedArchiveRootObjectKey];
+    }
+    completion(attributes);
+  });
+}
diff --git a/ios/chrome/credential_provider_extension/ui/BUILD.gn b/ios/chrome/credential_provider_extension/ui/BUILD.gn
index e8f4f4ac..41c9f74 100644
--- a/ios/chrome/credential_provider_extension/ui/BUILD.gn
+++ b/ios/chrome/credential_provider_extension/ui/BUILD.gn
@@ -82,6 +82,7 @@
     "//ios/chrome/common/ui/table_view",
     "//ios/chrome/common/ui/table_view:cells_constants",
     "//ios/chrome/common/ui/util",
+    "//ios/chrome/credential_provider_extension:favicon_util",
     "//ios/chrome/credential_provider_extension:metrics_util",
     "//ios/chrome/credential_provider_extension:passkey_request_details",
     "//ios/chrome/credential_provider_extension:passkey_util",
diff --git a/ios/chrome/credential_provider_extension/ui/credential_list_view_controller.mm b/ios/chrome/credential_provider_extension/ui/credential_list_view_controller.mm
index 6b5f0e1a..8c2c4c7 100644
--- a/ios/chrome/credential_provider_extension/ui/credential_list_view_controller.mm
+++ b/ios/chrome/credential_provider_extension/ui/credential_list_view_controller.mm
@@ -15,6 +15,7 @@
 #import "ios/chrome/common/ui/favicon/favicon_view.h"
 #import "ios/chrome/common/ui/table_view/favicon_table_view_cell.h"
 #import "ios/chrome/common/ui/util/pointer_interaction_util.h"
+#import "ios/chrome/credential_provider_extension/favicon_util.h"
 #import "ios/chrome/credential_provider_extension/metrics_util.h"
 #import "ios/chrome/credential_provider_extension/ui/credential_list_global_header_view.h"
 #import "ios/chrome/credential_provider_extension/ui/credential_list_header_view.h"
@@ -237,33 +238,19 @@
   id<Credential> credential = [self credentialForIndexPath:indexPath];
   DCHECK(credential);
   DCHECK(cell);
-  CredentialListCell* credentialCell =
+  __weak CredentialListCell* credentialCell =
       base::apple::ObjCCastStrict<CredentialListCell>(cell);
   NSString* serviceIdentifier = credential.serviceIdentifier;
 
-  dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
-    NSURL* filePath = [app_group::SharedFaviconAttributesFolder()
-        URLByAppendingPathComponent:credential.favicon
-                        isDirectory:NO];
-    NSError* error = nil;
-    NSData* data = [NSData dataWithContentsOfURL:filePath
-                                         options:0
-                                           error:&error];
-    if (data && !error) {
-      NSKeyedUnarchiver* unarchiver =
-          [[NSKeyedUnarchiver alloc] initForReadingFromData:data error:nil];
-      unarchiver.requiresSecureCoding = NO;
-      FaviconAttributes* attributes =
-          [unarchiver decodeObjectForKey:NSKeyedArchiveRootObjectKey];
-      // Only set favicon if the cell hasn't been reused.
-      if ([credentialCell.uniqueIdentifier isEqualToString:serviceIdentifier]) {
-        // Update the UI on the main thread.
-        dispatch_async(dispatch_get_main_queue(), ^{
-          if (attributes) {
-            [credentialCell.faviconView configureWithAttributes:attributes];
-          }
-        });
-      }
+  FetchFaviconAsync(credential.favicon, ^(FaviconAttributes* attributes) {
+    // Only set favicon if the cell hasn't been reused.
+    if ([credentialCell.uniqueIdentifier isEqualToString:serviceIdentifier]) {
+      // Update the UI on the main thread.
+      dispatch_async(dispatch_get_main_queue(), ^{
+        if (attributes) {
+          [credentialCell.faviconView configureWithAttributes:attributes];
+        }
+      });
     }
   });
 }
diff --git a/ios/chrome/credential_provider_extension/ui/multi_profile_passkey_creation_view_controller.h b/ios/chrome/credential_provider_extension/ui/multi_profile_passkey_creation_view_controller.h
index 64ee356..bdcf5ee3 100644
--- a/ios/chrome/credential_provider_extension/ui/multi_profile_passkey_creation_view_controller.h
+++ b/ios/chrome/credential_provider_extension/ui/multi_profile_passkey_creation_view_controller.h
@@ -38,6 +38,7 @@
             initWithDetails:(PasskeyRequestDetails*)passkeyRequestDetails
                        gaia:(NSString*)gaia
                   userEmail:(NSString*)userEmail
+                    favicon:(NSString*)favicon
     navigationItemTitleView:(UIView*)navigationItemTitleView
                    delegate:
                        (id<MultiProfilePasskeyCreationViewControllerDelegate>)
diff --git a/ios/chrome/credential_provider_extension/ui/multi_profile_passkey_creation_view_controller.mm b/ios/chrome/credential_provider_extension/ui/multi_profile_passkey_creation_view_controller.mm
index d4e814f9..a1a828d 100644
--- a/ios/chrome/credential_provider_extension/ui/multi_profile_passkey_creation_view_controller.mm
+++ b/ios/chrome/credential_provider_extension/ui/multi_profile_passkey_creation_view_controller.mm
@@ -8,6 +8,7 @@
 #import "ios/chrome/common/ui/favicon/favicon_container_view.h"
 #import "ios/chrome/common/ui/favicon/favicon_view.h"
 #import "ios/chrome/common/ui/util/constraints_ui_util.h"
+#import "ios/chrome/credential_provider_extension/favicon_util.h"
 #import "ios/chrome/credential_provider_extension/passkey_request_details.h"
 
 namespace {
@@ -83,6 +84,12 @@
   // Information about a passkey credential request.
   PasskeyRequestDetails* _passkeyRequestDetails;
 
+  // The favicon view shown by this view controller's view.
+  FaviconView* _faviconView;
+
+  // The favicon attributes, if a favicon is available. Nil otherwise.
+  FaviconAttributes* _faviconAttributes;
+
   // The gaia ID associated with the current account
   NSString* _gaia;
 
@@ -95,6 +102,7 @@
             initWithDetails:(PasskeyRequestDetails*)passkeyRequestDetails
                        gaia:(NSString*)gaia
                   userEmail:(NSString*)userEmail
+                    favicon:(NSString*)favicon
     navigationItemTitleView:(UIView*)navigationItemTitleView
                    delegate:
                        (id<MultiProfilePasskeyCreationViewControllerDelegate>)
@@ -106,6 +114,14 @@
     _gaia = gaia;
     _multiProfilePasskeyCreationViewControllerDelegate = delegate;
     _navigationItemTitleView = navigationItemTitleView;
+
+    // Attempt to fetch the favicon.
+    if (favicon) {
+      __weak __typeof(self) weakSelf = self;
+      FetchFaviconAsync(favicon, ^(FaviconAttributes* attributes) {
+        [weakSelf setFaviconAttributes:attributes];
+      });
+    }
   }
   return self;
 }
@@ -134,6 +150,8 @@
 
   self.view.backgroundColor = GetBackgroundColor();
   self.navigationItem.titleView = _navigationItemTitleView;
+
+  [_faviconView configureWithAttributes:_faviconAttributes];
 }
 
 #pragma mark - PromoStyleViewController
@@ -187,6 +205,11 @@
   return specificContentView;
 }
 
+// Sets the favicon attributes.
+- (void)setFaviconAttributes:(FaviconAttributes*)attributes {
+  _faviconAttributes = attributes;
+}
+
 // Creates the favicon view for the passkey information view.
 - (UIView*)iconView {
   FaviconContainerView* faviconContainerView =
@@ -197,10 +220,10 @@
               imageWithTintColor:[UIColor colorNamed:kTextQuaternaryColor]
                    renderingMode:UIImageRenderingModeAlwaysOriginal]];
 
-  // TODO(crbug.com/382479915): Verify if the favicon for the current URL is
-  // available before using the default icon.
-  [faviconContainerView.faviconView
-      configureWithAttributes:defaultWorldIconAttributes];
+  // Use the default world icon as the default favicon.
+  _faviconView = faviconContainerView.faviconView;
+  [_faviconView configureWithAttributes:defaultWorldIconAttributes];
+
   [faviconContainerView setFaviconBackgroundColor:GetBackgroundColor()];
   [faviconContainerView setFaviconBorderColor:[UIColor clearColor]];
   return faviconContainerView;
diff --git a/ios/chrome/credential_provider_extension/ui/multi_profile_passkey_creation_view_controller_unittest.mm b/ios/chrome/credential_provider_extension/ui/multi_profile_passkey_creation_view_controller_unittest.mm
index 044eb9c..db9674b 100644
--- a/ios/chrome/credential_provider_extension/ui/multi_profile_passkey_creation_view_controller_unittest.mm
+++ b/ios/chrome/credential_provider_extension/ui/multi_profile_passkey_creation_view_controller_unittest.mm
@@ -25,6 +25,7 @@
                 initWithDetails:details
                            gaia:nil
                       userEmail:@"peter.parker@gmail.com"
+                        favicon:nil
         navigationItemTitleView:navigationView
                        delegate:nil];
   }
diff --git a/ios/chrome/test/BUILD.gn b/ios/chrome/test/BUILD.gn
index 1eeb5d2..119055d 100644
--- a/ios/chrome/test/BUILD.gn
+++ b/ios/chrome/test/BUILD.gn
@@ -537,6 +537,12 @@
     "//ios/testing/earl_grey:unit_tests",
   ]
 
+  if (target_environment != "catalyst") {
+    deps += [
+      "//ios/chrome/browser/default_browser/model/default_status:unit_tests",
+    ]
+  }
+
   if (ios_enable_widget_kit_extension) {
     deps += [ "//ios/chrome/browser/widget_kit/model:unit_tests" ]
   }
diff --git a/ios/google_internal/frameworks/ChromeExtensionKeychainInternal.framework.dSYM.ios.zip.sha1 b/ios/google_internal/frameworks/ChromeExtensionKeychainInternal.framework.dSYM.ios.zip.sha1
index 0b7b8a2..2bb78d30 100644
--- a/ios/google_internal/frameworks/ChromeExtensionKeychainInternal.framework.dSYM.ios.zip.sha1
+++ b/ios/google_internal/frameworks/ChromeExtensionKeychainInternal.framework.dSYM.ios.zip.sha1
@@ -1 +1 @@
-807569f612f36a60d65fe69c01dbb19cd4c29957
\ No newline at end of file
+a73e832bf0cff9d086dc49787ded093e85b2037c
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/ChromeExtensionKeychainInternal.framework.dSYM.ios_asan.zip.sha1 b/ios/google_internal/frameworks/ChromeExtensionKeychainInternal.framework.dSYM.ios_asan.zip.sha1
index 8b4f6cae..3eae749 100644
--- a/ios/google_internal/frameworks/ChromeExtensionKeychainInternal.framework.dSYM.ios_asan.zip.sha1
+++ b/ios/google_internal/frameworks/ChromeExtensionKeychainInternal.framework.dSYM.ios_asan.zip.sha1
@@ -1 +1 @@
-6f17a6acf2304bfb1c3f0f602029b14c1dd2e1a2
\ No newline at end of file
+2ee40abcb11ae7a9ee14934cde0b01e00166d989
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/ChromeInternal.framework.dSYM.ios.zip.sha1 b/ios/google_internal/frameworks/ChromeInternal.framework.dSYM.ios.zip.sha1
index 125925bb..0c5b2bc5 100644
--- a/ios/google_internal/frameworks/ChromeInternal.framework.dSYM.ios.zip.sha1
+++ b/ios/google_internal/frameworks/ChromeInternal.framework.dSYM.ios.zip.sha1
@@ -1 +1 @@
-83272d501d27dcecac07e220d79c13062471aa13
\ No newline at end of file
+88fb9435476c1919e18a8267dd8c119524d0602f
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/ChromeInternal.framework.dSYM.ios_asan.zip.sha1 b/ios/google_internal/frameworks/ChromeInternal.framework.dSYM.ios_asan.zip.sha1
index 059e1bdd..688cd3d 100644
--- a/ios/google_internal/frameworks/ChromeInternal.framework.dSYM.ios_asan.zip.sha1
+++ b/ios/google_internal/frameworks/ChromeInternal.framework.dSYM.ios_asan.zip.sha1
@@ -1 +1 @@
-090e4778dd96187ce2f338b4e6f494ce1cab0978
\ No newline at end of file
+7f525bd96fba6aa69d2f0ae69119dc4cb27f561d
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/ChromeSSOInternal.framework.dSYM.ios.zip.sha1 b/ios/google_internal/frameworks/ChromeSSOInternal.framework.dSYM.ios.zip.sha1
index 554d260..8a269a8 100644
--- a/ios/google_internal/frameworks/ChromeSSOInternal.framework.dSYM.ios.zip.sha1
+++ b/ios/google_internal/frameworks/ChromeSSOInternal.framework.dSYM.ios.zip.sha1
@@ -1 +1 @@
-5ac896cff40a70b67786d9f77ee48c8bd34c9488
\ No newline at end of file
+40226924cc1e43a85336a4d791a3b4b7611db449
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/ChromeSSOInternal.framework.dSYM.ios_asan.zip.sha1 b/ios/google_internal/frameworks/ChromeSSOInternal.framework.dSYM.ios_asan.zip.sha1
index e940e9dc..d1d629c 100644
--- a/ios/google_internal/frameworks/ChromeSSOInternal.framework.dSYM.ios_asan.zip.sha1
+++ b/ios/google_internal/frameworks/ChromeSSOInternal.framework.dSYM.ios_asan.zip.sha1
@@ -1 +1 @@
-2cfe04a6c81c2a8e6e6ce85e3d3d57b73aa479ac
\ No newline at end of file
+018107616cc0ebe23fc03e6664f576376c214a15
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/chrome_extension_keychain_internal_dynamic_framework.ios.zip.sha1 b/ios/google_internal/frameworks/chrome_extension_keychain_internal_dynamic_framework.ios.zip.sha1
index ee46a45e..5a11586f 100644
--- a/ios/google_internal/frameworks/chrome_extension_keychain_internal_dynamic_framework.ios.zip.sha1
+++ b/ios/google_internal/frameworks/chrome_extension_keychain_internal_dynamic_framework.ios.zip.sha1
@@ -1 +1 @@
-b953b39f06630981601349fac27d79f61902e377
\ No newline at end of file
+7389d5af2f632249076839617cf18d276a99defa
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/chrome_extension_keychain_internal_dynamic_framework.ios_asan.zip.sha1 b/ios/google_internal/frameworks/chrome_extension_keychain_internal_dynamic_framework.ios_asan.zip.sha1
index ddee2bb..591a0464 100644
--- a/ios/google_internal/frameworks/chrome_extension_keychain_internal_dynamic_framework.ios_asan.zip.sha1
+++ b/ios/google_internal/frameworks/chrome_extension_keychain_internal_dynamic_framework.ios_asan.zip.sha1
@@ -1 +1 @@
-9cca9b246e48548e1d467fa149c8d29aca49ccc1
\ No newline at end of file
+11b6735e4997a291acb8b07cf3a934bb4a46ac8d
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/chrome_extension_keychain_internal_dynamic_framework.iossimulator.zip.sha1 b/ios/google_internal/frameworks/chrome_extension_keychain_internal_dynamic_framework.iossimulator.zip.sha1
index 2b716820..d13708f67 100644
--- a/ios/google_internal/frameworks/chrome_extension_keychain_internal_dynamic_framework.iossimulator.zip.sha1
+++ b/ios/google_internal/frameworks/chrome_extension_keychain_internal_dynamic_framework.iossimulator.zip.sha1
@@ -1 +1 @@
-2cdae6f09473b3572b58534e9ea05f11e8b92e77
\ No newline at end of file
+93f9e18ce7fe172f1115902ceaecaff769ca3ed5
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/chrome_extension_keychain_internal_dynamic_framework.iossimulator_asan.zip.sha1 b/ios/google_internal/frameworks/chrome_extension_keychain_internal_dynamic_framework.iossimulator_asan.zip.sha1
index 9724d6f..b98cb482 100644
--- a/ios/google_internal/frameworks/chrome_extension_keychain_internal_dynamic_framework.iossimulator_asan.zip.sha1
+++ b/ios/google_internal/frameworks/chrome_extension_keychain_internal_dynamic_framework.iossimulator_asan.zip.sha1
@@ -1 +1 @@
-513d9a59436279e89252b4407dadbd209608b38e
\ No newline at end of file
+7c2fe928108858d551b345d6e0bfc5db50e4a69c
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/chrome_internal_dynamic_framework.ios.zip.sha1 b/ios/google_internal/frameworks/chrome_internal_dynamic_framework.ios.zip.sha1
index daa8e07..e0b9a7a76 100644
--- a/ios/google_internal/frameworks/chrome_internal_dynamic_framework.ios.zip.sha1
+++ b/ios/google_internal/frameworks/chrome_internal_dynamic_framework.ios.zip.sha1
@@ -1 +1 @@
-4563fffb11431871a48d3f18a1913d4847150825
\ No newline at end of file
+421ae1af3163e208c4762169bb339aacbef622da
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/chrome_internal_dynamic_framework.ios_asan.zip.sha1 b/ios/google_internal/frameworks/chrome_internal_dynamic_framework.ios_asan.zip.sha1
index cbded24..0b1c955 100644
--- a/ios/google_internal/frameworks/chrome_internal_dynamic_framework.ios_asan.zip.sha1
+++ b/ios/google_internal/frameworks/chrome_internal_dynamic_framework.ios_asan.zip.sha1
@@ -1 +1 @@
-5ddf3ca6d77629e80c274f20ec57e1ac2b2985ea
\ No newline at end of file
+32b7e7c416b5c408abd4b0eae914b6ccc82b2f31
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/chrome_internal_dynamic_framework.iossimulator.zip.sha1 b/ios/google_internal/frameworks/chrome_internal_dynamic_framework.iossimulator.zip.sha1
index 15263b4..8f7ad26 100644
--- a/ios/google_internal/frameworks/chrome_internal_dynamic_framework.iossimulator.zip.sha1
+++ b/ios/google_internal/frameworks/chrome_internal_dynamic_framework.iossimulator.zip.sha1
@@ -1 +1 @@
-8031381d3b201929057be5979cfcc4056357d6fb
\ No newline at end of file
+d52760af6895c48662fcae97f98dc0eecaff64b9
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/chrome_internal_dynamic_framework.iossimulator_asan.zip.sha1 b/ios/google_internal/frameworks/chrome_internal_dynamic_framework.iossimulator_asan.zip.sha1
index 8d15ada2..87225b14c 100644
--- a/ios/google_internal/frameworks/chrome_internal_dynamic_framework.iossimulator_asan.zip.sha1
+++ b/ios/google_internal/frameworks/chrome_internal_dynamic_framework.iossimulator_asan.zip.sha1
@@ -1 +1 @@
-0531c619cf3122db3498c26476e3c16d461a5fe2
\ No newline at end of file
+1a47a9666faa95931e8390f1d1abec9a1a31cc1f
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.ios.zip.sha1 b/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.ios.zip.sha1
index 5d062283..e3bf9c5 100644
--- a/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.ios.zip.sha1
+++ b/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.ios.zip.sha1
@@ -1 +1 @@
-00bd025e2852879efcf0e9241570cd0a91d046e7
\ No newline at end of file
+8f869a375ba890c2eea187ec00c2523a6d9ae768
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.ios_asan.zip.sha1 b/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.ios_asan.zip.sha1
index 215549784..d788c31c 100644
--- a/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.ios_asan.zip.sha1
+++ b/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.ios_asan.zip.sha1
@@ -1 +1 @@
-da4d79cdf3a7ae2f8d1fef4bb3ee3b67ef527ff4
\ No newline at end of file
+d8e6b10f36cac3bc8021e30af1d198426140dd02
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.iossimulator.zip.sha1 b/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.iossimulator.zip.sha1
index 74f7d53..327ef68 100644
--- a/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.iossimulator.zip.sha1
+++ b/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.iossimulator.zip.sha1
@@ -1 +1 @@
-bb6ab974ff12942145fb7bb3cabe4283267662a0
\ No newline at end of file
+53f920d21fa33b396809716bb7d4d97c19db724a
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.iossimulator_asan.zip.sha1 b/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.iossimulator_asan.zip.sha1
index 92d8636..d110983 100644
--- a/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.iossimulator_asan.zip.sha1
+++ b/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.iossimulator_asan.zip.sha1
@@ -1 +1 @@
-92160005ddfd341cdad494d3fd462173083fbdb8
\ No newline at end of file
+ccaf4c4c8506f89ab7b50888cf9b3afc9da693e3
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/chrome_test_internal_dynamic_framework.ios.zip.sha1 b/ios/google_internal/frameworks/chrome_test_internal_dynamic_framework.ios.zip.sha1
index ef748f1..c2a032f 100644
--- a/ios/google_internal/frameworks/chrome_test_internal_dynamic_framework.ios.zip.sha1
+++ b/ios/google_internal/frameworks/chrome_test_internal_dynamic_framework.ios.zip.sha1
@@ -1 +1 @@
-93711d8d82de0703ab7b79aee8df1a2b776375d8
\ No newline at end of file
+f605232993eaec81fbaba751dd20a3acb3a70ff0
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/chrome_test_internal_dynamic_framework.iossimulator.zip.sha1 b/ios/google_internal/frameworks/chrome_test_internal_dynamic_framework.iossimulator.zip.sha1
index d7c417e..ae1a5be 100644
--- a/ios/google_internal/frameworks/chrome_test_internal_dynamic_framework.iossimulator.zip.sha1
+++ b/ios/google_internal/frameworks/chrome_test_internal_dynamic_framework.iossimulator.zip.sha1
@@ -1 +1 @@
-631c8f8ee1b61d5cef25ff04dd1801be9c4b1578
\ No newline at end of file
+3c55549fc3a25f66c2cb9da948ba7678d0e45686
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/remoting_internal_dynamic_framework.ios.zip.sha1 b/ios/google_internal/frameworks/remoting_internal_dynamic_framework.ios.zip.sha1
index b4e9d62..7f0bb13 100644
--- a/ios/google_internal/frameworks/remoting_internal_dynamic_framework.ios.zip.sha1
+++ b/ios/google_internal/frameworks/remoting_internal_dynamic_framework.ios.zip.sha1
@@ -1 +1 @@
-d26b40a34a2a02bad885cf7f2c9f10edb4b5e29e
\ No newline at end of file
+fc89cb25a2ce7caca3d4d16f4e2ad64edb82a1f3
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/remoting_internal_dynamic_framework.ios_asan.zip.sha1 b/ios/google_internal/frameworks/remoting_internal_dynamic_framework.ios_asan.zip.sha1
index cb6d44a..d62d619 100644
--- a/ios/google_internal/frameworks/remoting_internal_dynamic_framework.ios_asan.zip.sha1
+++ b/ios/google_internal/frameworks/remoting_internal_dynamic_framework.ios_asan.zip.sha1
@@ -1 +1 @@
-1ddbf42e592cdd2de2b42df8fd963b90a584d39f
\ No newline at end of file
+b6abea6d6e78fb9f523e3fc61f1d799707c42576
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/remoting_internal_dynamic_framework.iossimulator.zip.sha1 b/ios/google_internal/frameworks/remoting_internal_dynamic_framework.iossimulator.zip.sha1
index b9505a4b..8a3b82c 100644
--- a/ios/google_internal/frameworks/remoting_internal_dynamic_framework.iossimulator.zip.sha1
+++ b/ios/google_internal/frameworks/remoting_internal_dynamic_framework.iossimulator.zip.sha1
@@ -1 +1 @@
-219b07096aab2fa0cd908f6bc11d713a593fbaf2
\ No newline at end of file
+1c1c75508a7114c03003bc0eaa55ddc8cbcf5dff
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/remoting_internal_dynamic_framework.iossimulator_asan.zip.sha1 b/ios/google_internal/frameworks/remoting_internal_dynamic_framework.iossimulator_asan.zip.sha1
index 72223dc..94613503 100644
--- a/ios/google_internal/frameworks/remoting_internal_dynamic_framework.iossimulator_asan.zip.sha1
+++ b/ios/google_internal/frameworks/remoting_internal_dynamic_framework.iossimulator_asan.zip.sha1
@@ -1 +1 @@
-af794b1e13244cb58f09fc027e36a81efcae62e2
\ No newline at end of file
+c42011166bce12d7ebb6eb1926511e9779811ea3
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.ios.zip.sha1 b/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.ios.zip.sha1
index a06c9a3..d0968db 100644
--- a/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.ios.zip.sha1
+++ b/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.ios.zip.sha1
@@ -1 +1 @@
-cfc73db3a3cca4a940fea73b1aea9012ba07829a
\ No newline at end of file
+1e26110643582e1439de807927063ee70b73e1e4
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.ios_asan.zip.sha1 b/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.ios_asan.zip.sha1
index 1bea27c..3edbee1 100644
--- a/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.ios_asan.zip.sha1
+++ b/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.ios_asan.zip.sha1
@@ -1 +1 @@
-268a96e855e20604a7717aa7a4adf0c32158fc61
\ No newline at end of file
+375af3fefaf6f73dc78b2eb215af45de78203feb
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.iossimulator.zip.sha1 b/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.iossimulator.zip.sha1
index 66d6a41..f08dbe58 100644
--- a/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.iossimulator.zip.sha1
+++ b/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.iossimulator.zip.sha1
@@ -1 +1 @@
-2da352799973738d3f2f16afb15aee28b9719084
\ No newline at end of file
+c410fa282c6a056aca7a590075ac0cf7469f909d
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.iossimulator_asan.zip.sha1 b/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.iossimulator_asan.zip.sha1
index 2a802c7c..a55d33f 100644
--- a/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.iossimulator_asan.zip.sha1
+++ b/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.iossimulator_asan.zip.sha1
@@ -1 +1 @@
-189e83b800dbfb72704658ce28930591cc62bcdc
\ No newline at end of file
+cb553149bbc91abcc362a666447aa9d4ce106577
\ No newline at end of file
diff --git a/ios/public/provider/chrome/browser/lens/lens_overlay_api.h b/ios/public/provider/chrome/browser/lens/lens_overlay_api.h
index c047328..c6afebdb 100644
--- a/ios/public/provider/chrome/browser/lens/lens_overlay_api.h
+++ b/ios/public/provider/chrome/browser/lens/lens_overlay_api.h
@@ -45,6 +45,11 @@
 // The lens overlay has deferred a gesture.
 - (void)lensOverlayDidDeferGesture:(id<ChromeLensOverlay>)lensOverlay;
 
+@optional
+// The lens overlay failed to detect translatable text.
+- (void)lensOverlayDidFailDetectingTranslatableText:
+    (id<ChromeLensOverlay>)lensOverlay;
+
 @end
 
 // Defines the interface for interacting with a Chrome Lens Overlay.
diff --git a/ios/web_view/internal/cwv_user_content_controller.mm b/ios/web_view/internal/cwv_user_content_controller.mm
index 9dbecbe..61deb30 100644
--- a/ios/web_view/internal/cwv_user_content_controller.mm
+++ b/ios/web_view/internal/cwv_user_content_controller.mm
@@ -55,12 +55,12 @@
 
 - (void)addUserScript:(nonnull CWVUserScript*)userScript {
   [_userScripts addObject:userScript];
-  [self updateEarlyPageScript];
+  [self updatePageScripts];
 }
 
 - (void)removeAllUserScripts {
   [_userScripts removeAllObjects];
-  [self updateEarlyPageScript];
+  [self updatePageScripts];
 }
 
 - (nonnull NSArray<CWVUserScript*>*)userScripts {
@@ -69,13 +69,23 @@
 
 // Updates the early page script associated with the BrowserState with the
 // content of _userScripts.
-- (void)updateEarlyPageScript {
+- (void)updatePageScripts {
   NSMutableString* joinedAllFramesScript = [[NSMutableString alloc] init];
   NSMutableString* joinedMainFrameScript = [[NSMutableString alloc] init];
+  NSMutableString* joinedAllFramesDocEndScript = [[NSMutableString alloc] init];
+  NSMutableString* joinedMainFrameDocEndScript = [[NSMutableString alloc] init];
   for (CWVUserScript* script in _userScripts) {
     // Inserts "\n" between scripts to make it safer to join multiple scripts,
     // in case the first script doesn't end with ";" or "\n".
-    if (script.isForMainFrameOnly) {
+    if (script.injectionTime == CWVUserScriptInjectionTimeAtDocumentEnd) {
+      if (script.isForMainFrameOnly) {
+        [joinedMainFrameDocEndScript appendString:script.source];
+        [joinedMainFrameDocEndScript appendString:@"\n"];
+      } else {
+        [joinedAllFramesDocEndScript appendString:script.source];
+        [joinedAllFramesDocEndScript appendString:@"\n"];
+      }
+    } else if (script.isForMainFrameOnly) {
       [joinedMainFrameScript appendString:script.source];
       [joinedMainFrameScript appendString:@"\n"];
     } else {
@@ -85,7 +95,9 @@
   }
   WebViewScriptsJavaScriptFeature::FromBrowserState(_configuration.browserState)
       ->SetScripts(base::SysNSStringToUTF8(joinedAllFramesScript),
-                   base::SysNSStringToUTF8(joinedMainFrameScript));
+                   base::SysNSStringToUTF8(joinedMainFrameScript),
+                   base::SysNSStringToUTF8(joinedAllFramesDocEndScript),
+                   base::SysNSStringToUTF8(joinedMainFrameDocEndScript));
 }
 
 - (void)addMessageHandler:(void (^)(NSDictionary* payload))handler
diff --git a/ios/web_view/internal/cwv_user_script.mm b/ios/web_view/internal/cwv_user_script.mm
index 6dbf30d..60555f9 100644
--- a/ios/web_view/internal/cwv_user_script.mm
+++ b/ios/web_view/internal/cwv_user_script.mm
@@ -7,8 +7,8 @@
 @implementation CWVUserScript
 
 @synthesize source = _source;
-
 @synthesize forMainFrameOnly = _forMainFrameOnly;
+@synthesize injectionTime = _injectionTime;
 
 - (nonnull instancetype)initWithSource:(nonnull NSString*)source {
   return [self initWithSource:source forMainFrameOnly:true];
@@ -16,10 +16,19 @@
 
 - (nonnull instancetype)initWithSource:(nonnull NSString*)source
                       forMainFrameOnly:(BOOL)forMainFrameOnly {
+  return [self initWithSource:source
+             forMainFrameOnly:forMainFrameOnly
+                injectionTime:CWVUserScriptInjectionTimeAtDocumentStart];
+}
+
+- (instancetype)initWithSource:(NSString*)source
+              forMainFrameOnly:(BOOL)forMainFrameOnly
+                 injectionTime:(CWVUserScriptInjectionTime)injectionTime {
   self = [super init];
   if (self) {
     _source = [source copy];
     _forMainFrameOnly = forMainFrameOnly;
+    _injectionTime = injectionTime;
   }
   return self;
 }
diff --git a/ios/web_view/internal/js_messaging/web_view_scripts_java_script_feature.h b/ios/web_view/internal/js_messaging/web_view_scripts_java_script_feature.h
index 5993464..1c5b7c73 100644
--- a/ios/web_view/internal/js_messaging/web_view_scripts_java_script_feature.h
+++ b/ios/web_view/internal/js_messaging/web_view_scripts_java_script_feature.h
@@ -32,7 +32,9 @@
       web::BrowserState* browser_state);
 
   void SetScripts(std::optional<std::string> all_frames_script,
-                  std::optional<std::string> main_frame_script);
+                  std::optional<std::string> main_frame_script,
+                  std::optional<std::string> all_frames_script_doc_end,
+                  std::optional<std::string> main_frame_script_doc_end);
 
  private:
   std::vector<FeatureScript> GetScripts() const override;
@@ -42,6 +44,8 @@
 
   std::optional<std::string> all_frames_script_;
   std::optional<std::string> main_frame_script_;
+  std::optional<std::string> all_frames_script_doc_end_;
+  std::optional<std::string> main_frame_script_doc_end_;
 };
 
 #endif  // IOS_WEB_VIEW_INTERNAL_JS_MESSAGING_WEB_VIEW_SCRIPTS_JAVA_SCRIPT_FEATURE_H_
diff --git a/ios/web_view/internal/js_messaging/web_view_scripts_java_script_feature.mm b/ios/web_view/internal/js_messaging/web_view_scripts_java_script_feature.mm
index 4c9bae7..94dca32 100644
--- a/ios/web_view/internal/js_messaging/web_view_scripts_java_script_feature.mm
+++ b/ios/web_view/internal/js_messaging/web_view_scripts_java_script_feature.mm
@@ -40,9 +40,13 @@
 
 void WebViewScriptsJavaScriptFeature::SetScripts(
     std::optional<std::string> all_frames_script,
-    std::optional<std::string> main_frame_script) {
+    std::optional<std::string> main_frame_script,
+    std::optional<std::string> all_frames_script_doc_end,
+    std::optional<std::string> main_frame_script_doc_end) {
   all_frames_script_ = all_frames_script;
   main_frame_script_ = main_frame_script;
+  all_frames_script_doc_end_ = all_frames_script_doc_end;
+  main_frame_script_doc_end_ = main_frame_script_doc_end;
 
   // Feature scripts must be explicitly updated after they change.
   web::WKWebViewConfigurationProvider& config_provider =
@@ -67,5 +71,19 @@
             JavaScriptFeature::FeatureScript::InjectionTime::kDocumentStart,
             JavaScriptFeature::FeatureScript::TargetFrames::kMainFrame));
   }
+  if (all_frames_script_doc_end_) {
+    feature_scripts.push_back(
+        JavaScriptFeature::FeatureScript::CreateWithString(
+            all_frames_script_doc_end_.value(),
+            JavaScriptFeature::FeatureScript::InjectionTime::kDocumentEnd,
+            JavaScriptFeature::FeatureScript::TargetFrames::kAllFrames));
+  }
+  if (main_frame_script_doc_end_) {
+    feature_scripts.push_back(
+        JavaScriptFeature::FeatureScript::CreateWithString(
+            main_frame_script_doc_end_.value(),
+            JavaScriptFeature::FeatureScript::InjectionTime::kDocumentEnd,
+            JavaScriptFeature::FeatureScript::TargetFrames::kMainFrame));
+  }
   return feature_scripts;
 }
diff --git a/ios/web_view/public/cwv_user_script.h b/ios/web_view/public/cwv_user_script.h
index 92ed22cf..e37c230 100644
--- a/ios/web_view/public/cwv_user_script.h
+++ b/ios/web_view/public/cwv_user_script.h
@@ -11,9 +11,12 @@
 
 NS_ASSUME_NONNULL_BEGIN
 
-// User Script to be injected into a webpage after window.document is created,
-// but before other content is loaded (i.e., at the same timing as
-// WKUserScriptInjectionTimeAtDocumentStart).
+typedef NS_ENUM(NSInteger, CWVUserScriptInjectionTime) {
+  CWVUserScriptInjectionTimeAtDocumentStart,
+  CWVUserScriptInjectionTimeAtDocumentEnd
+};
+
+// User Script to be injected into a webpage.
 CWV_EXPORT
 @interface CWVUserScript : NSObject
 
@@ -23,16 +26,26 @@
 // Whether the script should be injected into all frames or just the main frame.
 @property(nonatomic, readonly, getter=isForMainFrameOnly) BOOL forMainFrameOnly;
 
+// The time at which the script should be injected: at the start or end of the
+// document load.
+@property(nonatomic, readonly) CWVUserScriptInjectionTime injectionTime;
+
 - (instancetype)init NS_UNAVAILABLE;
 
-// Creates a user script which should be injected into the main frame only.
+// Creates a user script which should be injected into the main frame only at document start time.
 - (instancetype)initWithSource:(NSString*)source;
 
 // Creates a user script which should be injected into all frames or just the
-// main frame.
+// main frame at document start time.
 - (instancetype)initWithSource:(NSString*)source
               forMainFrameOnly:(BOOL)forMainFrameOnly;
 
+// Creates a user script which will be injected into all frames or just the
+// main frame, and at the specified injection time.
+- (instancetype)initWithSource:(NSString*)source
+              forMainFrameOnly:(BOOL)forMainFrameOnly
+                 injectionTime:(CWVUserScriptInjectionTime)injectionTime;
+
 @end
 
 NS_ASSUME_NONNULL_END
diff --git a/ios_internal b/ios_internal
index ab1e6c4..a4b8373 160000
--- a/ios_internal
+++ b/ios_internal
@@ -1 +1 @@
-Subproject commit ab1e6c45e8b0ceb3f070f0a39c5271766fc7aa12
+Subproject commit a4b8373a27c133f175d81ed8044474c23cfb164b
diff --git a/ipc/BUILD.gn b/ipc/BUILD.gn
index eef379f1..c9262e8 100644
--- a/ipc/BUILD.gn
+++ b/ipc/BUILD.gn
@@ -30,6 +30,8 @@
     "param_traits_read_macros.h",
     "param_traits_write_macros.h",
     "struct_constructor_macros.h",
+    "tracing_helpers.h",
+    "tracing_helpers_internal.h",
   ]
 
   if (use_blink) {
@@ -299,6 +301,7 @@
       "ipc_sync_message_unittest.cc",
       "ipc_sync_message_unittest.h",
       "sync_socket_unittest.cc",
+      "tracing_helpers_unittest.cc",
     ]
 
     if (is_posix || is_fuchsia) {
diff --git a/ipc/ipc_message_macros.h b/ipc/ipc_message_macros.h
index 3f14a993..81e4c4b 100644
--- a/ipc/ipc_message_macros.h
+++ b/ipc/ipc_message_macros.h
@@ -199,11 +199,11 @@
 #include <tuple>
 
 #include "base/export_template.h"
-#include "base/hash/md5_constexpr.h"
 #include "base/task/common/task_annotator.h"
 #include "ipc/ipc_message_templates.h"
 #include "ipc/ipc_message_utils.h"
 #include "ipc/param_traits_macros.h"
+#include "ipc/tracing_helpers.h"
 
 // Convenience macro for defining structs without inheritance. Should not need
 // to be subsequently redefined.
@@ -324,7 +324,7 @@
 // associated with the incoming IPC message that caused them to be posted.
 #define IPC_TASK_ANNOTATOR_CONTEXT(msg_class)                            \
   static constexpr uint32_t kMessageHash =                               \
-      base::MD5Hash32Constexpr(IPC_TASK_ANNOTATOR_STRINGIFY(msg_class)); \
+      ipc::GetLegacyIpcTraceId(IPC_TASK_ANNOTATOR_STRINGIFY(msg_class)); \
   base::TaskAnnotator::ScopedSetIpcHash scoped_ipc_hash(kMessageHash);
 
 #define IPC_BEGIN_MESSAGE_MAP(class_name, msg)                   \
diff --git a/ipc/tracing_helpers.h b/ipc/tracing_helpers.h
new file mode 100644
index 0000000..e40ec79
--- /dev/null
+++ b/ipc/tracing_helpers.h
@@ -0,0 +1,21 @@
+// Copyright 2019 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef IPC_TRACING_HELPERS_H_
+#define IPC_TRACING_HELPERS_H_
+
+#include <string_view>
+
+#include "ipc/tracing_helpers_internal.h"
+
+namespace ipc {
+
+// Calculates the first 32 bits of the MD5 digest of the provided data. Used for
+// calculating an ID used for tracing legacy IPC messages. Do not use this in
+// new code.
+constexpr uint32_t GetLegacyIpcTraceId(std::string_view string);
+
+}  // namespace ipc
+
+#endif  // IPC_TRACING_HELPERS_H_
diff --git a/base/hash/md5_constexpr_internal.h b/ipc/tracing_helpers_internal.h
similarity index 92%
rename from base/hash/md5_constexpr_internal.h
rename to ipc/tracing_helpers_internal.h
index cf9534e..1993ff97 100644
--- a/base/hash/md5_constexpr_internal.h
+++ b/ipc/tracing_helpers_internal.h
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef BASE_HASH_MD5_CONSTEXPR_INTERNAL_H_
-#define BASE_HASH_MD5_CONSTEXPR_INTERNAL_H_
+#ifndef IPC_TRACING_HELPERS_INTERNAL_H_
+#define IPC_TRACING_HELPERS_INTERNAL_H_
 
 #include <stdint.h>
 
@@ -13,7 +13,7 @@
 #include "base/check_op.h"
 #include "base/numerics/safe_conversions.h"
 
-namespace base {
+namespace ipc {
 namespace internal {
 
 // The implementation here is based on the pseudocode provided by Wikipedia:
@@ -217,7 +217,7 @@
   // Processes an entire message.
   static constexpr IntermediateData ProcessMessage(std::string_view message) {
     const uint32_t m =
-        GetPaddedMessageLength(checked_cast<uint32_t>(message.size()));
+        GetPaddedMessageLength(base::checked_cast<uint32_t>(message.size()));
     IntermediateData intermediate0 = kInitialIntermediateData;
     for (uint32_t offset = 0; offset < m; offset += 64) {
       RoundData data = GetRoundData(message, m, offset);
@@ -241,12 +241,6 @@
   //////////////////////////////////////////////////////////////////////////////
   // WRAPPER FUNCTIONS
 
-  static constexpr uint64_t Hash64(std::string_view data) {
-    IntermediateData intermediate = ProcessMessage(data);
-    return (static_cast<uint64_t>(SwapEndian(intermediate.a)) << 32) |
-           static_cast<uint64_t>(SwapEndian(intermediate.b));
-  }
-
   static constexpr uint32_t Hash32(std::string_view data) {
     IntermediateData intermediate = ProcessMessage(data);
     return SwapEndian(intermediate.a);
@@ -257,14 +251,10 @@
 
 // Implementations of the functions exposed in the public header.
 
-constexpr uint64_t MD5Hash64Constexpr(std::string_view string) {
-  return internal::MD5CE::Hash64(string);
-}
-
-constexpr uint32_t MD5Hash32Constexpr(std::string_view string) {
+constexpr uint32_t GetLegacyIpcTraceId(std::string_view string) {
   return internal::MD5CE::Hash32(string);
 }
 
-}  // namespace base
+}  // namespace ipc
 
-#endif  // BASE_HASH_MD5_CONSTEXPR_INTERNAL_H_
+#endif  // IPC_TRACING_HELPERS_INTERNAL_H_
diff --git a/ipc/tracing_helpers_unittest.cc b/ipc/tracing_helpers_unittest.cc
new file mode 100644
index 0000000..1ff8dffb
--- /dev/null
+++ b/ipc/tracing_helpers_unittest.cc
@@ -0,0 +1,21 @@
+// Copyright 2019 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ipc/tracing_helpers.h"
+
+namespace ipc {
+
+// Ensure that everything works at compile-time by comparing to a few
+// reference hashes.
+constexpr char kMessage0[] = "message digest";
+
+static_assert(GetLegacyIpcTraceId(kMessage0) == 0xF96B697Dul,
+              "incorrect MD5Hash32 implementation");
+
+constexpr char kMessage1[] = "The quick brown fox jumps over the lazy dog";
+
+static_assert(GetLegacyIpcTraceId(kMessage1) == 0x9E107D9Dul,
+              "incorrect MD5Hash32 implementation");
+
+}  // namespace ipc
diff --git a/mojo/public/cpp/bindings/interface_endpoint_client.h b/mojo/public/cpp/bindings/interface_endpoint_client.h
index 2d796ce..f683fea 100644
--- a/mojo/public/cpp/bindings/interface_endpoint_client.h
+++ b/mojo/public/cpp/bindings/interface_endpoint_client.h
@@ -102,7 +102,8 @@
   AssociatedGroup* associated_group();
 
   scoped_refptr<ThreadSafeProxy> CreateThreadSafeProxy(
-      scoped_refptr<ThreadSafeProxy::Target> target);
+      scoped_refptr<ThreadSafeProxy::Target> target,
+      const base::Location& location);
 
   // Sets a MessageFilter which can filter a message after validation but
   // before dispatch.
diff --git a/mojo/public/cpp/bindings/lib/associated_interface_ptr_state.h b/mojo/public/cpp/bindings/lib/associated_interface_ptr_state.h
index dbb4a86..c76a345 100644
--- a/mojo/public/cpp/bindings/lib/associated_interface_ptr_state.h
+++ b/mojo/public/cpp/bindings/lib/associated_interface_ptr_state.h
@@ -15,6 +15,7 @@
 #include "base/component_export.h"
 #include "base/functional/bind.h"
 #include "base/functional/callback_forward.h"
+#include "base/location.h"
 #include "base/memory/ptr_util.h"
 #include "base/memory/scoped_refptr.h"
 #include "base/task/sequenced_task_runner.h"
@@ -76,8 +77,9 @@
   }
 
   scoped_refptr<ThreadSafeProxy> CreateThreadSafeProxy(
-      scoped_refptr<ThreadSafeProxy::Target> target) {
-    return endpoint_client_->CreateThreadSafeProxy(std::move(target));
+      scoped_refptr<ThreadSafeProxy::Target> target,
+      const base::Location& location) {
+    return endpoint_client_->CreateThreadSafeProxy(std::move(target), location);
   }
 
  protected:
diff --git a/mojo/public/cpp/bindings/lib/interface_endpoint_client.cc b/mojo/public/cpp/bindings/lib/interface_endpoint_client.cc
index 9615abc7..0ecfee48 100644
--- a/mojo/public/cpp/bindings/lib/interface_endpoint_client.cc
+++ b/mojo/public/cpp/bindings/lib/interface_endpoint_client.cc
@@ -59,11 +59,13 @@
       base::WeakPtr<InterfaceEndpointClient> endpoint,
       scoped_refptr<ThreadSafeProxy::Target> target,
       const AssociatedGroup& associated_group,
-      scoped_refptr<base::SequencedTaskRunner> task_runner)
+      scoped_refptr<base::SequencedTaskRunner> task_runner,
+      const base::Location& location)
       : endpoint_(std::move(endpoint)),
         target_(std::move(target)),
         associated_group_(associated_group),
-        task_runner_(std::move(task_runner)) {}
+        task_runner_(std::move(task_runner)),
+        location_(location) {}
 
   ThreadSafeInterfaceEndpointClientProxy(
       const ThreadSafeInterfaceEndpointClientProxy&) = delete;
@@ -154,12 +156,14 @@
 
   class ForwardToCallingThread : public MessageReceiver {
    public:
-    explicit ForwardToCallingThread(std::unique_ptr<MessageReceiver> responder)
+    explicit ForwardToCallingThread(std::unique_ptr<MessageReceiver> responder,
+                                    const base::Location& location)
         : responder_(std::move(responder)),
-          caller_task_runner_(base::SequencedTaskRunner::GetCurrentDefault()) {}
+          caller_task_runner_(base::SequencedTaskRunner::GetCurrentDefault()),
+          location_(location) {}
 
     ~ForwardToCallingThread() override {
-      caller_task_runner_->DeleteSoon(FROM_HERE, std::move(responder_));
+      caller_task_runner_->DeleteSoon(location_, std::move(responder_));
     }
 
    private:
@@ -167,7 +171,7 @@
       // `this` will be deleted immediately after this method returns. We must
       // relinquish ownership of `responder_` so it doesn't get deleted.
       caller_task_runner_->PostTask(
-          FROM_HERE,
+          location_,
           base::BindOnce(&ForwardToCallingThread::CallAcceptAndDeleteResponder,
                          std::move(responder_), std::move(*message)));
       return true;
@@ -181,6 +185,7 @@
 
     std::unique_ptr<MessageReceiver> responder_;
     scoped_refptr<base::SequencedTaskRunner> caller_task_runner_;
+    const base::Location location_;
   };
 
   class ForwardSameThreadResponder : public MessageReceiver {
@@ -231,6 +236,7 @@
   const scoped_refptr<base::SequencedTaskRunner> task_runner_;
   const scoped_refptr<InProgressSyncCalls> sync_calls_{
       base::MakeRefCounted<InProgressSyncCalls>()};
+  const base::Location location_;
 };
 
 void DetermineIfEndpointIsConnected(
@@ -381,8 +387,8 @@
   // Async messages are always posted (even if `task_runner_` runs tasks on
   // this sequence) to guarantee that two async calls can't be reordered.
   if (!message.has_flag(Message::kFlagIsSync)) {
-    auto reply_forwarder =
-        std::make_unique<ForwardToCallingThread>(std::move(responder));
+    auto reply_forwarder = std::make_unique<ForwardToCallingThread>(
+        std::move(responder), location_);
     task_runner_->PostTask(
         FROM_HERE,
         base::BindOnce(&ThreadSafeInterfaceEndpointClientProxy ::
@@ -510,10 +516,11 @@
 }
 
 scoped_refptr<ThreadSafeProxy> InterfaceEndpointClient::CreateThreadSafeProxy(
-    scoped_refptr<ThreadSafeProxy::Target> target) {
+    scoped_refptr<ThreadSafeProxy::Target> target,
+    const base::Location& location) {
   return base::MakeRefCounted<ThreadSafeInterfaceEndpointClientProxy>(
       weak_ptr_factory_.GetWeakPtr(), std::move(target), *associated_group_,
-      task_runner_);
+      task_runner_, location);
 }
 
 ScopedInterfaceEndpointHandle InterfaceEndpointClient::PassHandle() {
diff --git a/mojo/public/cpp/bindings/lib/interface_ptr_state.h b/mojo/public/cpp/bindings/lib/interface_ptr_state.h
index b6b88ee..860de777 100644
--- a/mojo/public/cpp/bindings/lib/interface_ptr_state.h
+++ b/mojo/public/cpp/bindings/lib/interface_ptr_state.h
@@ -17,6 +17,7 @@
 #include "base/dcheck_is_on.h"
 #include "base/functional/bind.h"
 #include "base/functional/callback.h"
+#include "base/location.h"
 #include "base/memory/scoped_refptr.h"
 #include "base/task/sequenced_task_runner.h"
 #include "base/time/time.h"
@@ -72,8 +73,9 @@
   }
 
   scoped_refptr<ThreadSafeProxy> CreateThreadSafeProxy(
-      scoped_refptr<ThreadSafeProxy::Target> target) {
-    return endpoint_client_->CreateThreadSafeProxy(std::move(target));
+      scoped_refptr<ThreadSafeProxy::Target> target,
+      const base::Location& location) {
+    return endpoint_client_->CreateThreadSafeProxy(std::move(target), location);
   }
 
 #if DCHECK_IS_ON()
diff --git a/mojo/public/cpp/bindings/receiver_set.h b/mojo/public/cpp/bindings/receiver_set.h
index 41b31247..8d3062f4 100644
--- a/mojo/public/cpp/bindings/receiver_set.h
+++ b/mojo/public/cpp/bindings/receiver_set.h
@@ -340,6 +340,24 @@
     return pending_receivers;
   }
 
+  // Similar to the method above, but it also includes the receiver's context.
+  std::vector<std::pair<PendingType, Context>> TakeReceiversWithContext() {
+    static_assert(ContextTraits::SupportsContext(),
+                  "TakeReceiversWithContext() requires non-void context type.");
+
+    ReceiverSetState::EntryMap entries;
+    std::swap(state_.entries(), entries);
+    std::vector<std::pair<PendingType, Context>> pending_receivers;
+    for (auto& entry : entries) {
+      ReceiverEntry& receiver =
+          static_cast<ReceiverEntry&>(entry.second->receiver());
+      pending_receivers.emplace_back(
+          receiver.Unbind(),
+          std::move(*static_cast<Context*>(receiver.GetContext())));
+    }
+    return pending_receivers;
+  }
+
   // Removes all receivers from the set, effectively closing all of them. This
   // ReceiverSet will not schedule or execute any further method invocations or
   // disconnection notifications until a new receiver is added to the set.
diff --git a/mojo/public/cpp/bindings/shared_associated_remote.h b/mojo/public/cpp/bindings/shared_associated_remote.h
index 5a72c19f..e9f5833f 100644
--- a/mojo/public/cpp/bindings/shared_associated_remote.h
+++ b/mojo/public/cpp/bindings/shared_associated_remote.h
@@ -7,6 +7,7 @@
 
 #include <tuple>
 
+#include "base/location.h"
 #include "base/memory/scoped_refptr.h"
 #include "base/task/sequenced_task_runner.h"
 #include "mojo/public/cpp/bindings/associated_remote.h"
@@ -47,9 +48,10 @@
   explicit SharedAssociatedRemote(
       PendingAssociatedRemote<Interface> pending_remote,
       scoped_refptr<base::SequencedTaskRunner> bind_task_runner =
-          base::SequencedTaskRunner::GetCurrentDefault()) {
+          base::SequencedTaskRunner::GetCurrentDefault(),
+      const base::Location& location = base::Location::Current()) {
     if (pending_remote.is_valid())
-      Bind(std::move(pending_remote), std::move(bind_task_runner));
+      Bind(std::move(pending_remote), std::move(bind_task_runner), location);
   }
 
   bool is_bound() const { return remote_ != nullptr; }
@@ -83,20 +85,22 @@
   // one of them, on `task_runner`. The other is returned as a receiver.
   mojo::PendingAssociatedReceiver<Interface> BindNewEndpointAndPassReceiver(
       scoped_refptr<base::SequencedTaskRunner> bind_task_runner =
-          base::SequencedTaskRunner::GetCurrentDefault()) {
+          base::SequencedTaskRunner::GetCurrentDefault(),
+      const base::Location& location = base::Location::Current()) {
     if (!internal::GetRuntimeFeature_ExpectEnabled<Interface>()) {
       return PendingAssociatedReceiver<Interface>();
     }
     mojo::PendingAssociatedRemote<Interface> remote;
     auto receiver = remote.InitWithNewEndpointAndPassReceiver();
-    Bind(std::move(remote), std::move(bind_task_runner));
+    Bind(std::move(remote), std::move(bind_task_runner), location);
     return receiver;
   }
 
   // Binds to `pending_remote` on `bind_task_runner`.
   void Bind(PendingAssociatedRemote<Interface> pending_remote,
             scoped_refptr<base::SequencedTaskRunner> bind_task_runner =
-                base::SequencedTaskRunner::GetCurrentDefault()) {
+                base::SequencedTaskRunner::GetCurrentDefault(),
+            const base::Location& location = base::Location::Current()) {
     DCHECK(!remote_);
     DCHECK(pending_remote.is_valid());
     if (!internal::GetRuntimeFeature_ExpectEnabled<Interface>()) {
@@ -104,7 +108,7 @@
       return;
     }
     remote_ = SharedRemoteBase<AssociatedRemote<Interface>>::Create(
-        std::move(pending_remote), std::move(bind_task_runner));
+        std::move(pending_remote), std::move(bind_task_runner), location);
   }
 
  private:
diff --git a/mojo/public/cpp/bindings/shared_remote.h b/mojo/public/cpp/bindings/shared_remote.h
index ded9752..a1fef2e 100644
--- a/mojo/public/cpp/bindings/shared_remote.h
+++ b/mojo/public/cpp/bindings/shared_remote.h
@@ -9,6 +9,7 @@
 #include <tuple>
 
 #include "base/functional/bind.h"
+#include "base/location.h"
 #include "base/memory/ptr_util.h"
 #include "base/memory/ref_counted.h"
 #include "base/synchronization/waitable_event.h"
@@ -116,10 +117,11 @@
     RemoteWrapper(const RemoteWrapper&) = delete;
     RemoteWrapper& operator=(const RemoteWrapper&) = delete;
 
-    std::unique_ptr<ThreadSafeForwarder<InterfaceType>> CreateForwarder() {
+    std::unique_ptr<ThreadSafeForwarder<InterfaceType>> CreateForwarder(
+        const base::Location& location) {
       return std::make_unique<ThreadSafeForwarder<InterfaceType>>(
           remote_.internal_state()->CreateThreadSafeProxy(
-              base::MakeRefCounted<ProxyTarget>(this)));
+              base::MakeRefCounted<ProxyTarget>(this), location));
     }
 
     void set_disconnect_handler(
@@ -204,16 +206,21 @@
     }
   };
 
-  explicit SharedRemoteBase(scoped_refptr<RemoteWrapper> wrapper)
-      : wrapper_(std::move(wrapper)), forwarder_(wrapper_->CreateForwarder()) {}
+  explicit SharedRemoteBase(scoped_refptr<RemoteWrapper> wrapper,
+                            const base::Location& location)
+      : wrapper_(std::move(wrapper)),
+        forwarder_(wrapper_->CreateForwarder(location)) {}
 
   // Creates a SharedRemoteBase bound to `pending_remote`. All messages sent
   // through the SharedRemote will first bounce through `task_runner`.
   static scoped_refptr<SharedRemoteBase> Create(
       PendingType pending_remote,
-      scoped_refptr<base::SequencedTaskRunner> task_runner) {
-    return new SharedRemoteBase(base::MakeRefCounted<RemoteWrapper>(
-        std::move(pending_remote), std::move(task_runner)));
+      scoped_refptr<base::SequencedTaskRunner> task_runner,
+      const base::Location& location) {
+    return new SharedRemoteBase(
+        base::MakeRefCounted<RemoteWrapper>(std::move(pending_remote),
+                                            std::move(task_runner)),
+        location);
   }
 
   ~SharedRemoteBase() = default;
@@ -248,15 +255,18 @@
 
   // Constructs a SharedRemote bound to `pending_remote` on the calling
   // sequence. See `Bind()` below for more details.
-  explicit SharedRemote(PendingRemote<Interface> pending_remote) {
-    Bind(std::move(pending_remote), nullptr);
+  explicit SharedRemote(
+      PendingRemote<Interface> pending_remote,
+      const base::Location& location = base::Location::Current()) {
+    Bind(std::move(pending_remote), nullptr, location);
   }
 
   // Constructs a SharedRemote bound to `pending_remote` on the sequence given
   // by `bind_task_runner`. See `Bind()` below for more details.
   SharedRemote(PendingRemote<Interface> pending_remote,
-               scoped_refptr<base::SequencedTaskRunner> bind_task_runner) {
-    Bind(std::move(pending_remote), std::move(bind_task_runner));
+               scoped_refptr<base::SequencedTaskRunner> bind_task_runner,
+               const base::Location& location = base::Location::Current()) {
+    Bind(std::move(pending_remote), std::move(bind_task_runner), location);
   }
 
   // SharedRemote supports both copy and move construction and assignment. These
@@ -311,31 +321,33 @@
   // this call and re-bound to `pending_remote`. Any prior copies made are NOT
   // affected and will retain their reference to the original Remote.
   void Bind(PendingRemote<Interface> pending_remote,
-            scoped_refptr<base::SequencedTaskRunner> bind_task_runner) {
+            scoped_refptr<base::SequencedTaskRunner> bind_task_runner,
+            const base::Location& location = base::Location::Current()) {
     if (!internal::GetRuntimeFeature_ExpectEnabled<Interface>()) {
       remote_.reset();
       return;
     }
     if (bind_task_runner && pending_remote) {
       remote_ = SharedRemoteBase<Remote<Interface>>::Create(
-          std::move(pending_remote), std::move(bind_task_runner));
+          std::move(pending_remote), std::move(bind_task_runner), location);
     } else if (pending_remote) {
       remote_ = SharedRemoteBase<Remote<Interface>>::Create(
           std::move(pending_remote),
-          base::SequencedTaskRunner::GetCurrentDefault());
+          base::SequencedTaskRunner::GetCurrentDefault(), location);
     }
   }
 
   // Creates a new pipe, binding this SharedRemote to one end on
   // `bind_task_runner` and returning the other end as a PendingReceiver.
   PendingReceiver<Interface> BindNewPipeAndPassReceiver(
-      scoped_refptr<base::SequencedTaskRunner> bind_task_runner = nullptr) {
+      scoped_refptr<base::SequencedTaskRunner> bind_task_runner = nullptr,
+      const base::Location& location = base::Location::Current()) {
     if (!internal::GetRuntimeFeature_ExpectEnabled<Interface>()) {
       return PendingReceiver<Interface>();
     }
     PendingRemote<Interface> remote;
     auto receiver = remote.InitWithNewPipeAndPassReceiver();
-    Bind(std::move(remote), std::move(bind_task_runner));
+    Bind(std::move(remote), std::move(bind_task_runner), location);
     return receiver;
   }
 
diff --git a/mojo/public/cpp/bindings/tests/BUILD.gn b/mojo/public/cpp/bindings/tests/BUILD.gn
index f99e76a..9b53648 100644
--- a/mojo/public/cpp/bindings/tests/BUILD.gn
+++ b/mojo/public/cpp/bindings/tests/BUILD.gn
@@ -49,6 +49,7 @@
     "report_bad_message_unittest.cc",
     "request_response_unittest.cc",
     "result_response_unittest.cc",
+    "result_response_unittest_mojom_traits.h",
     "router_test_util.cc",
     "router_test_util.h",
     "sample_service_unittest.cc",
@@ -213,6 +214,19 @@
       ]
       traits_headers = [ "default_construct_unittest_mojom_traits.h" ]
     },
+    {
+      types = [
+        {
+          mojom = "mojo.test.mojom.ResultValue"
+          cpp = "::mojo::test::MappedResultValue"
+        },
+        {
+          mojom = "mojo.test.mojom.ResultError"
+          cpp = "::mojo::test::MappedResultError"
+        },
+      ]
+      traits_headers = [ "result_response_unittest_mojom_traits.h" ]
+    },
   ]
 }
 
diff --git a/mojo/public/cpp/bindings/tests/result_response.test-mojom b/mojo/public/cpp/bindings/tests/result_response.test-mojom
index 1a118648..4d4cdd7 100644
--- a/mojo/public/cpp/bindings/tests/result_response.test-mojom
+++ b/mojo/public/cpp/bindings/tests/result_response.test-mojom
@@ -5,9 +5,26 @@
 module mojo.test.mojom;
 
 interface TestResultInterface {
-    // Echos the input value with success.
-    TestSuccess(int32 value) => result<int32, bool>;
+  // Echos the input value with success.
+  TestSuccess(int32 value) => result<int32, bool>;
 
-    // Echos the failure message with failure.
-    TestFailure(string failure) => result<bool, string>;
+  // Echos the failure message with failure.
+  TestFailure(string failure) => result<bool, string>;
+};
+
+struct ResultValue {
+  int32 value;
+};
+
+struct ResultError {
+  bool is_catastrophic;
+  string msg;
+};
+
+interface TestResultInterfaceWithTrait {
+  // Returns a success with a value of '1'.
+  TestSuccess() => result<ResultValue, ResultError>;
+
+  // Returns a failure with a a nuclear metldown.
+  TestFailure() => result<ResultValue, ResultError>;
 };
diff --git a/mojo/public/cpp/bindings/tests/result_response_unittest.cc b/mojo/public/cpp/bindings/tests/result_response_unittest.cc
index 36897e8..e1e43ea 100644
--- a/mojo/public/cpp/bindings/tests/result_response_unittest.cc
+++ b/mojo/public/cpp/bindings/tests/result_response_unittest.cc
@@ -10,6 +10,7 @@
 #include "mojo/public/cpp/bindings/remote.h"
 #include "mojo/public/cpp/bindings/tests/bindings_test_base.h"
 #include "mojo/public/cpp/bindings/tests/result_response.test-mojom.h"
+#include "result_response_unittest_mojom_traits.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 namespace mojo::test {
@@ -37,6 +38,31 @@
   mojo::Receiver<mojom::TestResultInterface> receiver_;
 };
 
+class TraitInterfaceImpl : public mojom::TestResultInterfaceWithTrait {
+ public:
+  explicit TraitInterfaceImpl(
+      mojo::PendingReceiver<mojom::TestResultInterfaceWithTrait> receiver)
+      : receiver_(this, std::move(receiver)) {}
+  TraitInterfaceImpl(const TraitInterfaceImpl&) = delete;
+  TraitInterfaceImpl& operator=(const TraitInterfaceImpl&) = delete;
+  ~TraitInterfaceImpl() override = default;
+
+  // mojom::TestResultTraitInterface
+  void TestSuccess(TestSuccessCallback cb) override {
+    std::move(cb).Run(base::ok(MappedResultValue{1}));
+  }
+
+  void TestFailure(TestFailureCallback cb) override {
+    MappedResultError err;
+    err.is_game_over_ = true;
+    err.reason_ = "meltdown!";
+    std::move(cb).Run(base::unexpected(err));
+  }
+
+ private:
+  mojo::Receiver<mojom::TestResultInterfaceWithTrait> receiver_;
+};
+
 using ResultResponseTest = BindingsTestBase;
 
 TEST_P(ResultResponseTest, TestResult) {
@@ -66,6 +92,33 @@
   loop.Run();
 }
 
+TEST_P(ResultResponseTest, TestSuccessTrait) {
+  mojo::Remote<mojom::TestResultInterfaceWithTrait> remote;
+  TraitInterfaceImpl impl(remote.BindNewPipeAndPassReceiver());
+
+  base::RunLoop loop;
+  remote->TestSuccess(base::BindLambdaForTesting(
+      [&](base::expected<MappedResultValue, MappedResultError> result) {
+        ASSERT_EQ(1, result.value().magic_value);
+        loop.Quit();
+      }));
+  loop.Run();
+}
+
+TEST_P(ResultResponseTest, TestFailureTrait) {
+  mojo::Remote<mojom::TestResultInterfaceWithTrait> remote;
+  TraitInterfaceImpl impl(remote.BindNewPipeAndPassReceiver());
+
+  base::RunLoop loop;
+  remote->TestFailure(base::BindLambdaForTesting(
+      [&](base::expected<MappedResultValue, MappedResultError> result) {
+        ASSERT_TRUE(result.error().is_game_over_);
+        ASSERT_EQ(result.error().reason_, "meltdown!");
+        loop.Quit();
+      }));
+  loop.Run();
+}
+
 INSTANTIATE_MOJO_BINDINGS_TEST_SUITE_P(ResultResponseTest);
 
 }  // namespace
diff --git a/mojo/public/cpp/bindings/tests/result_response_unittest_mojom_traits.h b/mojo/public/cpp/bindings/tests/result_response_unittest_mojom_traits.h
new file mode 100644
index 0000000..d633f32c
--- /dev/null
+++ b/mojo/public/cpp/bindings/tests/result_response_unittest_mojom_traits.h
@@ -0,0 +1,68 @@
+// Copyright 2025 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef MOJO_PUBLIC_CPP_BINDINGS_TESTS_RESULT_RESPONSE_UNITTEST_MOJOM_TRAITS_H_
+#define MOJO_PUBLIC_CPP_BINDINGS_TESTS_RESULT_RESPONSE_UNITTEST_MOJOM_TRAITS_H_
+
+#include <string>
+
+#include "mojo/public/cpp/bindings/string_data_view.h"
+#include "mojo/public/cpp/bindings/struct_traits.h"
+#include "mojo/public/cpp/bindings/tests/result_response.test-mojom-shared.h"
+
+namespace mojo {
+namespace test {
+
+struct MappedResultValue {
+  int magic_value;
+};
+
+class MappedResultError {
+ public:
+  MappedResultError() = default;
+  virtual ~MappedResultError() = default;
+  MappedResultError(const MappedResultError&) = default;
+  MappedResultError& operator=(const MappedResultError&) = default;
+
+  bool is_game_over_;
+  std::string reason_;
+};
+
+}  //  namespace test
+
+template <>
+struct StructTraits<test::mojom::ResultValueDataView, test::MappedResultValue> {
+  static int value(const test::MappedResultValue& in) { return in.magic_value; }
+
+  static bool Read(test::mojom::ResultValueDataView in,
+                   test::MappedResultValue* out) {
+    out->magic_value = in.value();
+    return true;
+  }
+};
+
+template <>
+struct StructTraits<test::mojom::ResultErrorDataView, test::MappedResultError> {
+  static bool is_catastrophic(const test::MappedResultError& in) {
+    return in.is_game_over_;
+  }
+
+  static std::string_view msg(const test::MappedResultError& in) {
+    return in.reason_;
+  }
+
+  static bool Read(test::mojom::ResultErrorDataView in,
+                   test::MappedResultError* out) {
+    out->is_game_over_ = in.is_catastrophic();
+
+    mojo::StringDataView s_view;
+    in.GetMsgDataView(&s_view);
+    out->reason_ = s_view.value();
+    return true;
+  }
+};
+
+}  // namespace mojo
+
+#endif  // MOJO_PUBLIC_CPP_BINDINGS_TESTS_RESULT_RESPONSE_UNITTEST_MOJOM_TRAITS_H_
diff --git a/mojo/public/tools/bindings/generators/cpp_templates/module.cc.tmpl b/mojo/public/tools/bindings/generators/cpp_templates/module.cc.tmpl
index 3e55f4a..a8e46ca 100644
--- a/mojo/public/tools/bindings/generators/cpp_templates/module.cc.tmpl
+++ b/mojo/public/tools/bindings/generators/cpp_templates/module.cc.tmpl
@@ -17,7 +17,6 @@
 #include <utility>
 
 #include "base/debug/alias.h"
-#include "base/hash/md5_constexpr.h"
 #include "base/run_loop.h"
 #include "base/strings/string_number_conversions.h"
 #include "base/task/thread_pool/thread_pool_instance.h"
diff --git a/net/http/http_network_transaction.cc b/net/http/http_network_transaction.cc
index 15ff8c7..1de7d7f 100644
--- a/net/http/http_network_transaction.cc
+++ b/net/http/http_network_transaction.cc
@@ -219,6 +219,9 @@
 int HttpNetworkTransaction::Start(const HttpRequestInfo* request_info,
                                   CompletionOnceCallback callback,
                                   const NetLogWithSource& net_log) {
+  TRACE_EVENT("net", "HttpNetworkTransaction::Start",
+              NetLogWithSourceToFlow(net_log), "url", request_info->url);
+
   if (request_info->load_flags & LOAD_ONLY_FROM_CACHE)
     return ERR_CACHE_MISS;
 
@@ -278,6 +281,9 @@
   DCHECK(!stream_request_.get());
   DCHECK_EQ(STATE_NONE, next_state_);
 
+  TRACE_EVENT("net", "HttpNetworkTransaction::RestartIgnoringLastError",
+              NetLogWithSourceToFlow(net_log_));
+
   if (!CheckMaxRestarts())
     return ERR_TOO_MANY_RETRIES;
 
@@ -304,6 +310,9 @@
   DCHECK(!stream_.get());
   DCHECK_EQ(STATE_NONE, next_state_);
 
+  TRACE_EVENT("net", "HttpNetworkTransaction::RestartWithCertificate",
+              NetLogWithSourceToFlow(net_log_));
+
   if (!CheckMaxRestarts())
     return ERR_TOO_MANY_RETRIES;
 
@@ -333,6 +342,9 @@
 
 int HttpNetworkTransaction::RestartWithAuth(const AuthCredentials& credentials,
                                             CompletionOnceCallback callback) {
+  TRACE_EVENT("net", "HttpNetworkTransaction::RestartWithAuth",
+              NetLogWithSourceToFlow(net_log_));
+
   if (!CheckMaxRestarts())
     return ERR_TOO_MANY_RETRIES;
 
@@ -429,6 +441,8 @@
       // Close the stream and mark it as not_reusable.  Even in the
       // keep_alive case, we've determined that the stream_ is not
       // reusable if new_stream is NULL.
+      TRACE_EVENT("net", "HttpNetworkTransaction::DidDrainBodyForAuthRestart",
+                  NetLogWithSourceToFlow(net_log_));
       stream_->Close(true);
       next_state_ = STATE_CREATE_STREAM;
     } else {
@@ -649,6 +663,8 @@
 }
 
 int HttpNetworkTransaction::ResumeNetworkStart() {
+  TRACE_EVENT("net", "HttpNetworkTransaction::ResumeNetworkStart",
+              NetLogWithSourceToFlow(net_log_));
   DCHECK_EQ(next_state_, STATE_CREATE_STREAM);
   return DoLoop(OK);
 }
@@ -946,6 +962,8 @@
 }
 
 int HttpNetworkTransaction::DoNotifyBeforeCreateStream() {
+  TRACE_EVENT("net", "HttpNetworkTransaction::NotifyBeforeCreateStream",
+              NetLogWithSourceToFlow(net_log_));
   next_state_ = STATE_CREATE_STREAM;
   bool defer = false;
   if (!before_network_start_callback_.is_null())
@@ -957,7 +975,8 @@
 
 int HttpNetworkTransaction::DoCreateStream() {
   TRACE_EVENT("net", "HttpNetworkTransaction::CreateStream",
-              NetLogWithSourceToFlow(net_log_));
+              NetLogWithSourceToFlow(net_log_), "retry_attempts",
+              retry_attempts_, "num_restarts", num_restarts_);
   response_.network_accessed = true;
 
   next_state_ = STATE_CREATE_STREAM_COMPLETE;
@@ -1382,7 +1401,9 @@
 
 int HttpNetworkTransaction::DoReadHeadersComplete(int result) {
   TRACE_EVENT("net", "HttpNetworkTransaction::ReadHeadersComplete",
-              NetLogWithSourceToFlow(net_log_), "result", result);
+              NetLogWithSourceToFlow(net_log_), "result", result,
+              "response_code",
+              response_.headers ? response_.headers->response_code() : -1);
   // We can get a ERR_SSL_CLIENT_AUTH_CERT_NEEDED here due to SSL renegotiation.
   // Server certificate errors are impossible. Rather than reverify the new
   // server certificate, BoringSSL forbids server certificates from changing.
@@ -2099,6 +2120,10 @@
 
 void HttpNetworkTransaction::ResetConnectionAndRequestForResend(
     RetryReason retry_reason) {
+  TRACE_EVENT("net",
+              "HttpNetworkTransaction::ResetConnectionAndRequestForResend",
+              NetLogWithSourceToFlow(net_log_), "retry_reason", retry_reason);
+
   reset_connection_and_request_for_resend_start_time_ = base::TimeTicks::Now();
 
   // TODO:(crbug.com/1495705): Remove this CHECK after fixing the bug.
diff --git a/services/on_device_model/public/mojom/on_device_model.mojom b/services/on_device_model/public/mojom/on_device_model.mojom
index 6a8b9f0..6ab0c489 100644
--- a/services/on_device_model/public/mojom/on_device_model.mojom
+++ b/services/on_device_model/public/mojom/on_device_model.mojom
@@ -207,6 +207,10 @@
   uint32? top_k;
   // Deprecated: use SessionParams.temperature instead.
   float? temperature;
+
+  // A JSON schema defining structured output requirements for the response.
+  // Passed as an opaque JSON blob to llguidance.
+  [MinVersion=1] string? response_json_schema;
 };
 
 // A session for a model that allows adding context and then executing an input
diff --git a/testing/buildbot/chromium.perf.json b/testing/buildbot/chromium.perf.json
index 727f446..38ddac9f 100644
--- a/testing/buildbot/chromium.perf.json
+++ b/testing/buildbot/chromium.perf.json
@@ -651,6 +651,141 @@
       }
     ]
   },
+  "android-pixel9-perf": {
+    "isolated_scripts": [
+      {
+        "args": [
+          "-v",
+          "-v",
+          "--browser=android-trichrome-chrome-google-64-32-bundle",
+          "--upload-results",
+          "--test-shard-map-filename=android-pixel9-perf_map.json",
+          "--ignore-benchmark-exit-code"
+        ],
+        "merge": {
+          "script": "//tools/perf/process_perf_results.py"
+        },
+        "name": "performance_test_suite_android_trichrome_chrome_google_64_32_bundle",
+        "resultdb": {
+          "enable": true
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimensions": {
+            "device_os": "B",
+            "device_os_flavor": "google",
+            "device_type": "tokay",
+            "os": "Android",
+            "pool": "chrome.tests.perf"
+          },
+          "expiration": 7200,
+          "hard_timeout": 14400,
+          "io_timeout": 21600,
+          "service_account": "chrome-tester@chops-service-accounts.iam.gserviceaccount.com",
+          "shards": 4
+        },
+        "test": "performance_test_suite_android_trichrome_chrome_google_64_32_bundle",
+        "trigger_script": {
+          "args": [
+            "--multiple-dimension-script-verbose",
+            "True"
+          ],
+          "requires_simultaneous_shard_dispatch": true,
+          "script": "//testing/trigger_scripts/perf_device_trigger.py"
+        }
+      }
+    ]
+  },
+  "android-pixel9-pro-perf": {
+    "isolated_scripts": [
+      {
+        "args": [
+          "-v",
+          "-v",
+          "--browser=android-trichrome-chrome-google-64-32-bundle",
+          "--upload-results",
+          "--test-shard-map-filename=android-pixel9-pro-perf_map.json",
+          "--ignore-benchmark-exit-code"
+        ],
+        "merge": {
+          "script": "//tools/perf/process_perf_results.py"
+        },
+        "name": "performance_test_suite_android_trichrome_chrome_google_64_32_bundle",
+        "resultdb": {
+          "enable": true
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimensions": {
+            "device_os": "B",
+            "device_os_flavor": "google",
+            "device_type": "caiman",
+            "os": "Android",
+            "pool": "chrome.tests.perf"
+          },
+          "expiration": 7200,
+          "hard_timeout": 14400,
+          "io_timeout": 21600,
+          "service_account": "chrome-tester@chops-service-accounts.iam.gserviceaccount.com",
+          "shards": 4
+        },
+        "test": "performance_test_suite_android_trichrome_chrome_google_64_32_bundle",
+        "trigger_script": {
+          "args": [
+            "--multiple-dimension-script-verbose",
+            "True"
+          ],
+          "requires_simultaneous_shard_dispatch": true,
+          "script": "//testing/trigger_scripts/perf_device_trigger.py"
+        }
+      }
+    ]
+  },
+  "android-pixel9-pro-xl-perf": {
+    "isolated_scripts": [
+      {
+        "args": [
+          "-v",
+          "-v",
+          "--browser=android-trichrome-chrome-google-64-32-bundle",
+          "--upload-results",
+          "--test-shard-map-filename=android-pixel9-pro-xl-perf_map.json",
+          "--ignore-benchmark-exit-code"
+        ],
+        "merge": {
+          "script": "//tools/perf/process_perf_results.py"
+        },
+        "name": "performance_test_suite_android_trichrome_chrome_google_64_32_bundle",
+        "resultdb": {
+          "enable": true
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimensions": {
+            "device_os": "B",
+            "device_os_flavor": "google",
+            "device_type": "komodo",
+            "os": "Android",
+            "pool": "chrome.tests.perf"
+          },
+          "expiration": 7200,
+          "hard_timeout": 14400,
+          "io_timeout": 21600,
+          "service_account": "chrome-tester@chops-service-accounts.iam.gserviceaccount.com",
+          "shards": 4
+        },
+        "test": "performance_test_suite_android_trichrome_chrome_google_64_32_bundle",
+        "trigger_script": {
+          "args": [
+            "--multiple-dimension-script-verbose",
+            "True"
+          ],
+          "requires_simultaneous_shard_dispatch": true,
+          "script": "//testing/trigger_scripts/perf_device_trigger.py"
+        }
+      }
+    ]
+  },
   "android_arm64-builder-perf": {
     "isolated_scripts": [
       {
diff --git a/testing/buildbot/chromium.perf.pinpoint.json b/testing/buildbot/chromium.perf.pinpoint.json
index 2571f87..b1d0900ef 100644
--- a/testing/buildbot/chromium.perf.pinpoint.json
+++ b/testing/buildbot/chromium.perf.pinpoint.json
@@ -570,6 +570,141 @@
       }
     ]
   },
+  "android-pixel9-perf": {
+    "isolated_scripts": [
+      {
+        "args": [
+          "-v",
+          "-v",
+          "--browser=android-trichrome-chrome-google-64-32-bundle",
+          "--upload-results",
+          "--test-shard-map-filename=android-pixel9-perf_map.json",
+          "--ignore-benchmark-exit-code"
+        ],
+        "merge": {
+          "script": "//tools/perf/process_perf_results.py"
+        },
+        "name": "performance_test_suite_android_trichrome_chrome_google_64_32_bundle",
+        "resultdb": {
+          "enable": true
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimensions": {
+            "device_os": "B",
+            "device_os_flavor": "google",
+            "device_type": "tokay",
+            "os": "Android",
+            "pool": "chrome.tests.perf"
+          },
+          "expiration": 7200,
+          "hard_timeout": 14400,
+          "io_timeout": 21600,
+          "service_account": "chrome-tester@chops-service-accounts.iam.gserviceaccount.com",
+          "shards": 4
+        },
+        "test": "performance_test_suite_android_trichrome_chrome_google_64_32_bundle",
+        "trigger_script": {
+          "args": [
+            "--multiple-dimension-script-verbose",
+            "True"
+          ],
+          "requires_simultaneous_shard_dispatch": true,
+          "script": "//testing/trigger_scripts/perf_device_trigger.py"
+        }
+      }
+    ]
+  },
+  "android-pixel9-pro-perf": {
+    "isolated_scripts": [
+      {
+        "args": [
+          "-v",
+          "-v",
+          "--browser=android-trichrome-chrome-google-64-32-bundle",
+          "--upload-results",
+          "--test-shard-map-filename=android-pixel9-pro-perf_map.json",
+          "--ignore-benchmark-exit-code"
+        ],
+        "merge": {
+          "script": "//tools/perf/process_perf_results.py"
+        },
+        "name": "performance_test_suite_android_trichrome_chrome_google_64_32_bundle",
+        "resultdb": {
+          "enable": true
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimensions": {
+            "device_os": "B",
+            "device_os_flavor": "google",
+            "device_type": "caiman",
+            "os": "Android",
+            "pool": "chrome.tests.perf"
+          },
+          "expiration": 7200,
+          "hard_timeout": 14400,
+          "io_timeout": 21600,
+          "service_account": "chrome-tester@chops-service-accounts.iam.gserviceaccount.com",
+          "shards": 4
+        },
+        "test": "performance_test_suite_android_trichrome_chrome_google_64_32_bundle",
+        "trigger_script": {
+          "args": [
+            "--multiple-dimension-script-verbose",
+            "True"
+          ],
+          "requires_simultaneous_shard_dispatch": true,
+          "script": "//testing/trigger_scripts/perf_device_trigger.py"
+        }
+      }
+    ]
+  },
+  "android-pixel9-pro-xl-perf": {
+    "isolated_scripts": [
+      {
+        "args": [
+          "-v",
+          "-v",
+          "--browser=android-trichrome-chrome-google-64-32-bundle",
+          "--upload-results",
+          "--test-shard-map-filename=android-pixel9-pro-xl-perf_map.json",
+          "--ignore-benchmark-exit-code"
+        ],
+        "merge": {
+          "script": "//tools/perf/process_perf_results.py"
+        },
+        "name": "performance_test_suite_android_trichrome_chrome_google_64_32_bundle",
+        "resultdb": {
+          "enable": true
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimensions": {
+            "device_os": "B",
+            "device_os_flavor": "google",
+            "device_type": "komodo",
+            "os": "Android",
+            "pool": "chrome.tests.perf"
+          },
+          "expiration": 7200,
+          "hard_timeout": 14400,
+          "io_timeout": 21600,
+          "service_account": "chrome-tester@chops-service-accounts.iam.gserviceaccount.com",
+          "shards": 4
+        },
+        "test": "performance_test_suite_android_trichrome_chrome_google_64_32_bundle",
+        "trigger_script": {
+          "args": [
+            "--multiple-dimension-script-verbose",
+            "True"
+          ],
+          "requires_simultaneous_shard_dispatch": true,
+          "script": "//testing/trigger_scripts/perf_device_trigger.py"
+        }
+      }
+    ]
+  },
   "android_arm64-builder-perf": {},
   "android_arm64-builder-perf-pgo": {},
   "linux-builder-perf": {},
diff --git a/testing/variations/fieldtrial_testing_config.json b/testing/variations/fieldtrial_testing_config.json
index 62deb259..fdad623f 100644
--- a/testing/variations/fieldtrial_testing_config.json
+++ b/testing/variations/fieldtrial_testing_config.json
@@ -2222,7 +2222,6 @@
                 {
                     "name": "Enabled",
                     "enable_features": [
-                        "AutofillFixFormTracking",
                         "AutofillPreferSavedFormAsSubmittedForm",
                         "AutofillUseSubmittedFormInHtmlSubmission"
                     ]
@@ -7923,6 +7922,29 @@
             ]
         }
     ],
+    "DesktopOmniboxRichAutocompletionMinChar": [
+        {
+            "platforms": [
+                "chromeos",
+                "chromeos_lacros",
+                "linux",
+                "mac",
+                "windows"
+            ],
+            "experiments": [
+                {
+                    "name": "Enabled_1_v1",
+                    "params": {
+                        "RichAutocompletionAutocompleteShortcutTextMinChar": "1",
+                        "RichAutocompletionAutocompleteTitlesMinChar": "1"
+                    },
+                    "enable_features": [
+                        "OmniboxRichAutocompletion"
+                    ]
+                }
+            ]
+        }
+    ],
     "DesktopOmniboxShortcutBoost": [
         {
             "platforms": [
@@ -17763,7 +17785,6 @@
                     "name": "Enabled",
                     "enable_features": [
                         "DedicatedWorkerAblationStudyEnabled",
-                        "PlzDedicatedWorker",
                         "ServiceWorkerClientIdAlignedWithSpec"
                     ]
                 }
@@ -22717,6 +22738,28 @@
             ]
         }
     ],
+    "SharedTabGroupsTeamfood": [
+        {
+            "platforms": [
+                "android"
+            ],
+            "experiments": [
+                {
+                    "name": "Enabled",
+                    "params": {
+                        "show_send_feedback": "true"
+                    },
+                    "enable_features": [
+                        "DataSharing",
+                        "DeferMediaLoadInBackgroundTab",
+                        "EnableTabTitleSanitization",
+                        "EnableUrlRestriction",
+                        "SyncSharedTabGroupDataInTransportMode"
+                    ]
+                }
+            ]
+        }
+    ],
     "SharedWorkerBlobURLFix": [
         {
             "platforms": [
diff --git a/third_party/android_deps/autorolled/VERSION.txt b/third_party/android_deps/autorolled/VERSION.txt
index 74d3103..a555e61 100644
--- a/third_party/android_deps/autorolled/VERSION.txt
+++ b/third_party/android_deps/autorolled/VERSION.txt
@@ -1 +1 @@
-88efd2a99f52198
\ No newline at end of file
+151a6dedf2f706a
\ No newline at end of file
diff --git a/third_party/android_deps/autorolled/bill_of_materials.json b/third_party/android_deps/autorolled/bill_of_materials.json
index a0ba705..8bb3305 100644
--- a/third_party/android_deps/autorolled/bill_of_materials.json
+++ b/third_party/android_deps/autorolled/bill_of_materials.json
@@ -1647,12 +1647,12 @@
     {
         "name": "kotlinx-serialization-bom",
         "group": "org.jetbrains.kotlinx",
-        "version": "1.7.2"
+        "version": "1.7.3"
     },
     {
         "name": "kotlinx-serialization-core",
         "group": "org.jetbrains.kotlinx",
-        "version": "1.6.3"
+        "version": "1.7.3"
     },
     {
         "name": "kotlinx-serialization-core-jvm",
diff --git a/third_party/android_deps/autorolled/build.gradle b/third_party/android_deps/autorolled/build.gradle
index 2511eda..a41e020 100644
--- a/third_party/android_deps/autorolled/build.gradle
+++ b/third_party/android_deps/autorolled/build.gradle
@@ -343,8 +343,8 @@
 versionCache['org.jetbrains.kotlinx:kotlinx-coroutines-rx3'] = '1.7.3'
 versionCache['org.jetbrains.kotlinx:kotlinx-coroutines-test'] = '1.7.3'
 versionCache['org.jetbrains.kotlinx:kotlinx-coroutines-test-jvm'] = '1.7.3'
-versionCache['org.jetbrains.kotlinx:kotlinx-serialization-bom'] = '1.7.2'
-versionCache['org.jetbrains.kotlinx:kotlinx-serialization-core'] = '1.6.3'
+versionCache['org.jetbrains.kotlinx:kotlinx-serialization-bom'] = '1.7.3'
+versionCache['org.jetbrains.kotlinx:kotlinx-serialization-core'] = '1.7.3'
 versionCache['org.jetbrains.kotlinx:kotlinx-serialization-core-jvm'] = '1.7.2'
 versionCache['org.jetbrains:annotations'] = '23.0.0'
 versionCache['org.jsoup:jsoup'] = '1.15.1'
diff --git a/third_party/android_sdk/cipd/system_images/android-34/android-automotive/x86_64.yaml b/third_party/android_sdk/cipd/system_images/android-34/android-automotive/x86_64.yaml
new file mode 100644
index 0000000..8850f69
--- /dev/null
+++ b/third_party/android_sdk/cipd/system_images/android-34/android-automotive/x86_64.yaml
@@ -0,0 +1,9 @@
+# Copyright 2025 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+package: chromium/third_party/android_sdk/public/system-images/android-34/android-automotive/x86_64
+description: system_images;android-34-ext9;android-automotive;x86_64
+root: ../../../../public/
+data:
+  - dir: system-images/android-34-ext9/android-automotive/x86_64
diff --git a/third_party/angle b/third_party/angle
index b64aa6d..f56c8e0 160000
--- a/third_party/angle
+++ b/third_party/angle
@@ -1 +1 @@
-Subproject commit b64aa6d5c5bdf4acf7de56dcb52594027f29a3ec
+Subproject commit f56c8e02ce8b75bba76782d1b13200f0bde6cc63
diff --git a/third_party/blink/common/features.cc b/third_party/blink/common/features.cc
index 5922af74..785722d 100644
--- a/third_party/blink/common/features.cc
+++ b/third_party/blink/common/features.cc
@@ -2011,12 +2011,6 @@
              "Path2DPaintCache",
              base::FEATURE_DISABLED_BY_DEFAULT);
 
-// Enable browser-initiated dedicated worker script loading
-// (PlzDedicatedWorker). https://crbug.com/906991
-BASE_FEATURE(kPlzDedicatedWorker,
-             "PlzDedicatedWorker",
-             base::FEATURE_ENABLED_BY_DEFAULT);
-
 BASE_FEATURE(kDedicatedWorkerAblationStudyEnabled,
              "DedicatedWorkerAblationStudyEnabled",
              base::FEATURE_DISABLED_BY_DEFAULT);
@@ -2395,7 +2389,7 @@
              base::FEATURE_DISABLED_BY_DEFAULT);
 
 // If disabled, client_id and resultingClientId behavior keeps the old
-// Chromium behavior even after the PlzDedicatedWorker is enabled.
+// Chromium behavior.
 // This is workaround for crbug.com/1520512 until the fix gets ready.
 BASE_FEATURE(kServiceWorkerClientIdAlignedWithSpec,
              "ServiceWorkerClientIdAlignedWithSpec",
diff --git a/third_party/blink/common/service_worker/service_worker_loader_helpers.cc b/third_party/blink/common/service_worker/service_worker_loader_helpers.cc
index 33a4baff..4632bd0b 100644
--- a/third_party/blink/common/service_worker/service_worker_loader_helpers.cc
+++ b/third_party/blink/common/service_worker/service_worker_loader_helpers.cc
@@ -10,7 +10,6 @@
 #include <utility>
 #include <vector>
 
-#include "base/feature_list.h"
 #include "base/strings/stringprintf.h"
 #include "mojo/public/cpp/bindings/self_owned_receiver.h"
 #include "net/http/http_util.h"
@@ -21,7 +20,6 @@
 #include "services/network/public/mojom/fetch_api.mojom-shared.h"
 #include "services/network/public/mojom/url_response_head.mojom.h"
 #include "third_party/blink/public/common/blob/blob_utils.h"
-#include "third_party/blink/public/common/features.h"
 #include "third_party/blink/public/common/loader/resource_type_util.h"
 #include "ui/base/page_transition_types.h"
 
@@ -194,10 +192,10 @@
 // static
 bool ServiceWorkerLoaderHelpers::IsMainRequestDestination(
     network::mojom::RequestDestination destination) {
-  // When PlzDedicatedWorker is enabled, a dedicated worker script is considered
-  // to be a main resource.
-  if (destination == network::mojom::RequestDestination::kWorker)
-    return base::FeatureList::IsEnabled(features::kPlzDedicatedWorker);
+  // A dedicated worker script is considered to be a main resource.
+  if (destination == network::mojom::RequestDestination::kWorker) {
+    return true;
+  }
   return IsRequestDestinationFrame(destination) ||
          destination == network::mojom::RequestDestination::kSharedWorker;
 }
diff --git a/third_party/blink/public/common/features.h b/third_party/blink/public/common/features.h
index bf446497..4db920f2 100644
--- a/third_party/blink/public/common/features.h
+++ b/third_party/blink/public/common/features.h
@@ -1334,8 +1334,6 @@
 
 BLINK_COMMON_EXPORT BASE_DECLARE_FEATURE(kPartitionVisitedLinkDatabase);
 
-BLINK_COMMON_EXPORT BASE_DECLARE_FEATURE(kPlzDedicatedWorker);
-
 BLINK_COMMON_EXPORT BASE_DECLARE_FEATURE(kDedicatedWorkerAblationStudyEnabled);
 BLINK_COMMON_EXPORT BASE_DECLARE_FEATURE_PARAM(int,
                                                kDedicatedWorkerStartDelayInMs);
@@ -1865,8 +1863,6 @@
 
 BLINK_COMMON_EXPORT bool IsParkableImagesToDiskEnabled();
 
-BLINK_COMMON_EXPORT bool IsPlzDedicatedWorkerEnabled();
-
 BLINK_COMMON_EXPORT bool IsSetIntervalWithoutClampEnabled();
 
 // Returns if unload handlers are considered as a blocklisted reason for
diff --git a/third_party/blink/public/mojom/ai/ai_language_model.mojom b/third_party/blink/public/mojom/ai/ai_language_model.mojom
index 83194bed..ed8f29d5 100644
--- a/third_party/blink/public/mojom/ai/ai_language_model.mojom
+++ b/third_party/blink/public/mojom/ai/ai_language_model.mojom
@@ -122,8 +122,11 @@
 // A session for prompting a model with inputs to obtain streaming responses.
 interface AILanguageModel {
   // Prompts the model with arbitrary content provided via the JavaScript API.
+  // A JSON schema can be provided to define structured output requirements
+  // for the response. The schema is optional, and is expected to be valid JSON.
   Prompt(
     array<AILanguageModelPrompt> prompts,
+    string? response_json_schema,
     pending_remote<ModelStreamingResponder> pending_responder
   );
   // Creates a new session with the same configuration and existing context
diff --git a/third_party/blink/public/platform/web_dedicated_or_shared_worker_fetch_context.h b/third_party/blink/public/platform/web_dedicated_or_shared_worker_fetch_context.h
index b7424bbe..0f9d13c4 100644
--- a/third_party/blink/public/platform/web_dedicated_or_shared_worker_fetch_context.h
+++ b/third_party/blink/public/platform/web_dedicated_or_shared_worker_fetch_context.h
@@ -63,16 +63,9 @@
       mojo::PendingRemote<mojom::ResourceLoadInfoNotifier>
           pending_resource_load_info_notifier);
 
-  // Clones this fetch context for a nested worker.
-  // For non-PlzDedicatedWorker. This will be removed once PlzDedicatedWorker is
-  // enabled by default.
-  virtual scoped_refptr<WebDedicatedOrSharedWorkerFetchContext>
-  CloneForNestedWorkerDeprecated(
-      scoped_refptr<base::SingleThreadTaskRunner> task_runner) = 0;
-
-  // For PlzDedicatedWorker. The cloned fetch context does not inherit some
-  // fields (e.g., blink::WebServiceWorkerProviderContext) from this fetch
-  // context, and instead that takes values passed from the browser process.
+  // The cloned fetch context does not inherit some fields (e.g.,
+  // blink::WebServiceWorkerProviderContext) from this fetch context, and
+  // instead that takes values passed from the browser process.
   virtual scoped_refptr<WebDedicatedOrSharedWorkerFetchContext>
   CloneForNestedWorker(
       WebServiceWorkerProviderContext* service_worker_provider_context,
diff --git a/third_party/blink/renderer/core/editing/finder/find_buffer.cc b/third_party/blink/renderer/core/editing/finder/find_buffer.cc
index 4a98e6f..a5ef6356 100644
--- a/third_party/blink/renderer/core/editing/finder/find_buffer.cc
+++ b/third_party/blink/renderer/core/editing/finder/find_buffer.cc
@@ -132,21 +132,33 @@
   return !style->IsInert();
 }
 
-// Returns the next/previous node after |start_node| (including start node) that
-// is a text node and is searchable and visible.
+// Returns the next/previous node after |start_node| (including start node and
+// in the search range) that is a text node and is searchable and visible.
 template <class Direction>
-Node* GetVisibleTextNode(Node& start_node) {
+Node* GetVisibleTextNode(Node& start_node, Node* past_last_node = nullptr) {
   Node* node = &start_node;
+
+  // Move the end node to a visible subtree. Since we'll be testing node against
+  // it, it must be searchable otherwise node might skip past it.
+  if (past_last_node) {
+    while (Node* ancestor =
+               GetOutermostNonSearchableAncestor(*past_last_node)) {
+      past_last_node = Direction::NextSkippingSubtree(*ancestor);
+      if (!past_last_node) {
+        break;
+      }
+    }
+  }
+
   // Move to outside display none subtree if we're inside one.
   while (Node* ancestor = GetOutermostNonSearchableAncestor(*node)) {
-    if (!ancestor)
-      return nullptr;
     node = Direction::NextSkippingSubtree(*ancestor);
     if (!node)
       return nullptr;
   }
+
   // Move to first text node that's visible.
-  while (node) {
+  while (node && node != past_last_node) {
     const ComputedStyle* style = EnsureComputedStyleForFind(*node);
     if (FindBuffer::ShouldIgnoreContents(*node) ||
         (style && style->Display() == EDisplay::kNone)) {
@@ -179,6 +191,9 @@
   while (node && !node->isSameNode(&end)) {
     node = FlatTreeTraversal::Next(*node);
   }
+  if (!node) {
+    return false;
+  }
   return node->isSameNode(&end);
 }
 
@@ -341,7 +356,8 @@
   return true;
 }
 
-Node* FindBuffer::ForwardVisibleTextNode(Node& start_node) {
+Node* FindBuffer::ForwardVisibleTextNode(Node& start_node,
+                                         Node* past_last_node) {
   struct ForwardDirection {
     static Node* Next(const Node& node) {
       return FlatTreeTraversal::Next(node);
@@ -350,7 +366,7 @@
       return FlatTreeTraversal::NextSkippingChildren(node);
     }
   };
-  return GetVisibleTextNode<ForwardDirection>(start_node);
+  return GetVisibleTextNode<ForwardDirection>(start_node, past_last_node);
 }
 
 Node* FindBuffer::BackwardVisibleTextNode(Node& start_node) {
@@ -394,9 +410,12 @@
   const Node* const first_node = range.StartPosition().NodeAsRangeFirstNode();
   if (!first_node)
     return;
+
   // Get first visible text node from |start_position|.
-  Node* node =
-      ForwardVisibleTextNode(*range.StartPosition().NodeAsRangeFirstNode());
+  // Make sure the node stays within the search range.
+  Node* past_last_node = range.EndPosition().NodeAsRangePastLastNode();
+  Node* node = ForwardVisibleTextNode(
+      *range.StartPosition().NodeAsRangeFirstNode(), past_last_node);
   if (!node || !node->isConnected())
     return;
 
@@ -412,8 +431,6 @@
 
   // Used for checking if we reached a new block.
   Node* last_added_text_node = nullptr;
-
-  // We will also stop if we encountered/passed |end_node|.
   Node* end_node = range.EndPosition().NodeAsRangeLastNode();
 
   if (ruby_support == RubySupport::kEnabledForcefully ||
diff --git a/third_party/blink/renderer/core/editing/finder/find_buffer.h b/third_party/blink/renderer/core/editing/finder/find_buffer.h
index 382d73c..66c8610 100644
--- a/third_party/blink/renderer/core/editing/finder/find_buffer.h
+++ b/third_party/blink/renderer/core/editing/finder/find_buffer.h
@@ -57,7 +57,8 @@
                                          const Node& end_node);
 
   // See |GetVisibleTextNode|.
-  static Node* ForwardVisibleTextNode(Node& start_node);
+  static Node* ForwardVisibleTextNode(Node& start_node,
+                                      Node* past_last_node = nullptr);
   static Node* BackwardVisibleTextNode(Node& start_node);
 
   static bool ShouldIgnoreContents(const Node& node);
diff --git a/third_party/blink/renderer/core/editing/selection_modifier_line.cc b/third_party/blink/renderer/core/editing/selection_modifier_line.cc
index 33c6de0..90cd29c 100644
--- a/third_party/blink/renderer/core/editing/selection_modifier_line.cc
+++ b/third_party/blink/renderer/core/editing/selection_modifier_line.cc
@@ -61,8 +61,8 @@
     if (cursor_.Current().IsEmptyLineBox())
       return false;
     const PhysicalSize physical_size = cursor_.Current().Size();
-    const LogicalSize logical_size = physical_size.ConvertToLogical(
-        cursor_.Current().Style().GetWritingMode());
+    const LogicalSize logical_size = ToLogicalSize(
+        physical_size, cursor_.Current().Style().GetWritingMode());
     if (!logical_size.block_size)
       return false;
     for (InlineCursor cursor(cursor_); cursor; cursor.MoveToNext()) {
diff --git a/third_party/blink/renderer/core/fragment_directive/text_fragment_finder.cc b/third_party/blink/renderer/core/fragment_directive/text_fragment_finder.cc
index 5d77869..2235045 100644
--- a/third_party/blink/renderer/core/fragment_directive/text_fragment_finder.cc
+++ b/third_party/blink/renderer/core/fragment_directive/text_fragment_finder.cc
@@ -375,7 +375,20 @@
                                        const TextFragmentSelector& selector,
                                        Document* document,
                                        FindBufferRunnerType runner_type)
-    : client_(client), selector_(selector), document_(document) {
+    : TextFragmentFinder(client,
+                         selector,
+                         document->createRange(),
+                         runner_type) {
+  if (document->body()) {
+    range_->selectNode(document->body());
+  }
+}
+
+TextFragmentFinder::TextFragmentFinder(Client& client,
+                                       const TextFragmentSelector& selector,
+                                       Range* range,
+                                       FindBufferRunnerType runner_type)
+    : client_(client), selector_(selector), range_(range) {
   DCHECK(!selector_.Start().empty());
   DCHECK(selector_.Type() != TextFragmentSelector::SelectorType::kInvalid);
   if (runner_type == TextFragmentFinder::FindBufferRunnerType::kAsynchronous) {
@@ -393,24 +406,22 @@
 void TextFragmentFinder::FindMatch() {
   Cancel();
 
-  auto forced_lock_scope =
-      document_->GetDisplayLockDocumentState().GetScopedForceActivatableLocks();
-  document_->UpdateStyleAndLayout(DocumentUpdateReason::kFindInPage);
+  auto forced_lock_scope = range_->OwnerDocument()
+                               .GetDisplayLockDocumentState()
+                               .GetScopedForceActivatableLocks();
+  range_->OwnerDocument().UpdateStyleAndLayout(
+      DocumentUpdateReason::kFindInPage);
 
   first_match_.Clear();
-  FindMatchFromPosition(PositionInFlatTree::FirstPositionInNode(*document_));
+
+  PositionInFlatTree search_start =
+      ToPositionInFlatTree(range_->StartPosition());
+  FindMatchFromPosition(search_start);
 }
 
 void TextFragmentFinder::FindMatchFromPosition(
     PositionInFlatTree search_start) {
-  PositionInFlatTree search_end;
-  if (document_->documentElement() &&
-      document_->documentElement()->lastChild()) {
-    search_end = PositionInFlatTree::AfterNode(
-        *document_->documentElement()->lastChild());
-  } else {
-    search_end = PositionInFlatTree::LastPositionInNode(*document_);
-  }
+  PositionInFlatTree search_end = ToPositionInFlatTree(range_->EndPosition());
   search_range_ =
       MakeGarbageCollected<RangeInFlatTree>(search_start, search_end);
   match_range_ =
@@ -436,7 +447,7 @@
 }
 
 void TextFragmentFinder::Trace(Visitor* visitor) const {
-  visitor->Trace(document_);
+  visitor->Trace(range_);
   visitor->Trace(range_end_search_start_);
   visitor->Trace(potential_match_);
   visitor->Trace(prefix_match_);
diff --git a/third_party/blink/renderer/core/fragment_directive/text_fragment_finder.h b/third_party/blink/renderer/core/fragment_directive/text_fragment_finder.h
index ca3b48c..7d0970e 100644
--- a/third_party/blink/renderer/core/fragment_directive/text_fragment_finder.h
+++ b/third_party/blink/renderer/core/fragment_directive/text_fragment_finder.h
@@ -56,6 +56,10 @@
                      const TextFragmentSelector& selector,
                      Document* document,
                      FindBufferRunnerType runner_type);
+  TextFragmentFinder(Client& client,
+                     const TextFragmentSelector& selector,
+                     Range* range,
+                     FindBufferRunnerType runner_type);
   virtual ~TextFragmentFinder() = default;
 
   // Begins searching in the given top-level document.
@@ -70,8 +74,6 @@
   const TextFragmentSelector& GetSelector() const { return selector_; }
 
  protected:
-  friend class TextFragmentFinderTest;
-  FRIEND_TEST_ALL_PREFIXES(TextFragmentFinderTest, DOMMutation);
   void FindPrefix();
   void FindTextStart();
   void FindTextEnd();
@@ -88,6 +90,10 @@
   SelectorMatchStep step_ = kMatchPrefix;
 
  private:
+  friend class MockTextFragmentFinder;
+  friend class TextFragmentFinderTest;
+  FRIEND_TEST_ALL_PREFIXES(TextFragmentFinderTest, DOMMutation);
+
   void FindMatchFromPosition(PositionInFlatTree search_start);
 
   void OnFindMatchInRangeComplete(String search_text,
@@ -117,7 +123,7 @@
 
   Client& client_;
   const TextFragmentSelector selector_;
-  Member<Document> document_;
+  Member<Range> range_;
 
   // Start positions for the next text end |FindTask|, this is separate as the
   // search for end might move the position, which should be discarded.
diff --git a/third_party/blink/renderer/core/fragment_directive/text_fragment_finder_test.cc b/third_party/blink/renderer/core/fragment_directive/text_fragment_finder_test.cc
index 9ad7fd2e..e94b2aa 100644
--- a/third_party/blink/renderer/core/fragment_directive/text_fragment_finder_test.cc
+++ b/third_party/blink/renderer/core/fragment_directive/text_fragment_finder_test.cc
@@ -16,11 +16,29 @@
   MockTextFragmentFinder(Client& client,
                          const TextFragmentSelector& selector,
                          Document* document,
-                         FindBufferRunnerType runner_type)
-      : TextFragmentFinder(client, selector, document, runner_type) {}
+                         FindBufferRunnerType runner_type,
+                         bool manual_step_through = false)
+      : TextFragmentFinder(client, selector, document, runner_type) {
+    manual_step_through_ = manual_step_through;
+  }
+
+  MockTextFragmentFinder(Client& client,
+                         const TextFragmentSelector& selector,
+                         Range* range,
+                         FindBufferRunnerType runner_type,
+                         bool manual_step_through = false)
+      : TextFragmentFinder(client, selector, range, runner_type) {
+    manual_step_through_ = manual_step_through;
+  }
 
  private:
-  void GoToStep(SelectorMatchStep step) override { step_ = step; }
+  bool manual_step_through_;
+  void GoToStep(SelectorMatchStep step) override {
+    step_ = step;
+    if (!manual_step_through_) {
+      TextFragmentFinder::GoToStep(step);
+    }
+  }
 };
 
 class MockTextFragmentFinderClient : public TextFragmentFinder::Client {
@@ -40,6 +58,13 @@
   }
 };
 
+MATCHER_P(RangeContainedBy, element, "") {
+  return FlatTreeTraversal::Contains(
+             *element, *arg.StartPosition().ComputeContainerNode()) &&
+         FlatTreeTraversal::Contains(*element,
+                                     *arg.EndPosition().ComputeContainerNode());
+}
+
 // Tests that Find tasks will fail gracefully when DOM mutations invalidate the
 // Find task properties.
 TEST_F(TextFragmentFinderTest, DOMMutation) {
@@ -59,7 +84,8 @@
 
   MockTextFragmentFinder* finder = MakeGarbageCollected<MockTextFragmentFinder>(
       client, selector, &GetDocument(),
-      TextFragmentFinder::FindBufferRunnerType::kSynchronous);
+      TextFragmentFinder::FindBufferRunnerType::kSynchronous,
+      /*manual_step_through=*/true);
   EXPECT_CALL(client, DidFindMatch(_, _)).Times(0);
 
   {
@@ -86,4 +112,358 @@
   }
 }
 
+// Tests that a text match is found in the document.
+TEST_F(TextFragmentFinderTest, TextMatchInDocument) {
+  SimRequest request("https://example.com/test.html", "text/html");
+  LoadURL("https://example.com/test.html");
+  request.Complete(R"HTML(
+    <!DOCTYPE html>
+    <p id='first'>First paragraph start text to unique snippet of text.</p>
+  )HTML");
+
+  TextFragmentSelector selector(TextFragmentSelector::SelectorType::kExact,
+                                "First paragraph", "", "", "");
+
+  MockTextFragmentFinderClient client;
+
+  MockTextFragmentFinder* finder = MakeGarbageCollected<MockTextFragmentFinder>(
+      client, selector, &GetDocument(),
+      TextFragmentFinder::FindBufferRunnerType::kSynchronous);
+
+  EXPECT_CALL(client, NoMatchFound()).Times(0);
+  EXPECT_CALL(client, DidFindMatch(_, true)).Times(1);
+  finder->FindMatch();
+  Mock::VerifyAndClearExpectations(&client);
+}
+
+// Tests that a text match is found in the given range.
+TEST_F(TextFragmentFinderTest, TextMatchInRange) {
+  SimRequest request("https://example.com/test.html", "text/html");
+  LoadURL("https://example.com/test.html");
+  request.Complete(R"HTML(
+    <!DOCTYPE html>
+    <div>
+      <p id='p1'>First paragraph start text to unique snippet of text.</p>
+      <p id='p2'>Second paragraph start text to unique snippet of text.</p>
+      <p id='p3'>Third paragraph start text to unique snippet of text.</p>
+    </div>
+  )HTML");
+
+  Element* const p1 = GetDocument().getElementById(AtomicString("p1"));
+  Element* const p3 = GetDocument().getElementById(AtomicString("p3"));
+
+  auto* new_range = MakeGarbageCollected<Range>(GetDocument(), Position(p1, 0),
+                                                Position(p3, 0));
+  EXPECT_EQ(
+      "First paragraph start text to unique snippet of text.\n\n"
+      "Second paragraph start text to unique snippet of text.\n\n",
+      new_range->GetText());
+
+  TextFragmentSelector selector(TextFragmentSelector::SelectorType::kExact,
+                                "First paragraph start text",
+                                "Second paragraph", "", "");
+  MockTextFragmentFinderClient client;
+
+  MockTextFragmentFinder* finder = MakeGarbageCollected<MockTextFragmentFinder>(
+      client, selector, new_range,
+      TextFragmentFinder::FindBufferRunnerType::kSynchronous);
+
+  EXPECT_CALL(client, NoMatchFound()).Times(0);
+  EXPECT_CALL(client, DidFindMatch(_, true)).Times(1);
+  finder->FindMatch();
+  Mock::VerifyAndClearExpectations(&client);
+}
+
+// Tests that a selector whose start term is in the search range but end term
+// is not does not match.
+TEST_F(TextFragmentFinderTest, TextEndMatchNotInRange) {
+  SimRequest request("https://example.com/test.html", "text/html");
+  LoadURL("https://example.com/test.html");
+  request.Complete(R"HTML(
+    <!DOCTYPE html>
+    <div>
+      <p id='p1'>First paragraph start text to unique snippet of text.</p>
+      <p id='p2'>Second paragraph start text to unique snippet of text.</p>
+      <p id='p3'>Third paragraph start text to unique snippet of text.</p>
+    </div>
+  )HTML");
+
+  Element* const p2 = GetDocument().getElementById(AtomicString("p2"));
+  Element* const p3 = GetDocument().getElementById(AtomicString("p3"));
+
+  auto* new_range = MakeGarbageCollected<Range>(GetDocument(), Position(p2, 0),
+                                                Position(p3, 0));
+  EXPECT_EQ("Second paragraph start text to unique snippet of text.\n\n",
+            new_range->GetText());
+
+  TextFragmentSelector selector(TextFragmentSelector::SelectorType::kExact,
+                                "Second paragraph", "Third paragraph", "", "");
+  MockTextFragmentFinderClient client;
+
+  MockTextFragmentFinder* finder = MakeGarbageCollected<MockTextFragmentFinder>(
+      client, selector, new_range,
+      TextFragmentFinder::FindBufferRunnerType::kSynchronous);
+
+  EXPECT_CALL(client, NoMatchFound()).Times(1);
+  EXPECT_CALL(client, DidFindMatch(_, _)).Times(0);
+  finder->FindMatch();
+  Mock::VerifyAndClearExpectations(&client);
+}
+
+// Tests that a text match is not found in the given range even though it is in
+// the document, but it is before the range.
+TEST_F(TextFragmentFinderTest, TextMatchNotFoundBeforeRange) {
+  SimRequest request("https://example.com/test.html", "text/html");
+  LoadURL("https://example.com/test.html");
+  request.Complete(R"HTML(
+    <!DOCTYPE html>
+    <div>
+      <p id='p1'>First paragraph start text to unique snippet of text.</p>
+      <p id='p2'>Second paragraph start text to unique snippet of text.</p>
+      <p id='p3'>Third paragraph start text to unique snippet of text.</p>
+    </div>
+  )HTML");
+
+  Element* const p2 = GetDocument().getElementById(AtomicString("p2"));
+  Element* const p3 = GetDocument().getElementById(AtomicString("p3"));
+
+  auto* range = MakeGarbageCollected<Range>(GetDocument(), Position(p2, 0),
+                                            Position(p3, 0));
+  EXPECT_EQ("Second paragraph start text to unique snippet of text.\n\n",
+            range->GetText());
+
+  TextFragmentSelector selector(TextFragmentSelector::SelectorType::kExact,
+                                "First paragraph start text", "", "", "");
+  MockTextFragmentFinderClient client;
+
+  MockTextFragmentFinder* finder = MakeGarbageCollected<MockTextFragmentFinder>(
+      client, selector, range,
+      TextFragmentFinder::FindBufferRunnerType::kSynchronous);
+
+  EXPECT_CALL(client, NoMatchFound()).Times(1);
+  EXPECT_CALL(client, DidFindMatch(_, _)).Times(0);
+  finder->FindMatch();
+  Mock::VerifyAndClearExpectations(&client);
+}
+
+// Tests that a text match is not found in the given range even though it is in
+// the document, but it is after the range.
+TEST_F(TextFragmentFinderTest, TextMatchNotFoundAfterRange) {
+  SimRequest request("https://example.com/test.html", "text/html");
+  LoadURL("https://example.com/test.html");
+  request.Complete(R"HTML(
+    <!DOCTYPE html>
+    <div>
+      <p id='p1'>First paragraph start text to unique snippet of text.</p>
+      <p id='p2'>Second paragraph start text to unique snippet of text.</p>
+      <p id='p3'>Third paragraph start text to unique snippet of text.</p>
+    </div>
+  )HTML");
+
+  Element* const p2 = GetDocument().getElementById(AtomicString("p2"));
+  Element* const p3 = GetDocument().getElementById(AtomicString("p3"));
+
+  auto* range = MakeGarbageCollected<Range>(GetDocument(), Position(p2, 0),
+                                            Position(p3, 0));
+  EXPECT_EQ("Second paragraph start text to unique snippet of text.\n\n",
+            range->GetText());
+
+  TextFragmentSelector selector(TextFragmentSelector::SelectorType::kExact,
+                                "Third paragraph start text", "", "", "");
+  MockTextFragmentFinderClient client;
+
+  MockTextFragmentFinder* finder = MakeGarbageCollected<MockTextFragmentFinder>(
+      client, selector, range,
+      TextFragmentFinder::FindBufferRunnerType::kSynchronous);
+
+  EXPECT_CALL(client, NoMatchFound()).Times(1);
+  EXPECT_CALL(client, DidFindMatch(_, _)).Times(0);
+  finder->FindMatch();
+  Mock::VerifyAndClearExpectations(&client);
+}
+
+// Tests that when there are multiple text matches in the page, and the first
+// match is returned in the given range.
+TEST_F(TextFragmentFinderTest, TextDidFindMatchInRange) {
+  SimRequest request("https://example.com/test.html", "text/html");
+  LoadURL("https://example.com/test.html");
+  request.Complete(R"HTML(
+    <!DOCTYPE html>
+    <div>
+      <p id='p1'>Paragraph start text 1 to unique snippet of text.</p>
+      <p id='p2'>Paragraph start text 2 to unique snippet of text.</p>
+      <p id='p3'>Paragraph start text 3 to unique snippet of text.</p>
+    </div>
+  )HTML");
+
+  Element* const p1 = GetDocument().getElementById(AtomicString("p1"));
+  Element* const p3 = GetDocument().getElementById(AtomicString("p3"));
+
+  auto* range = MakeGarbageCollected<Range>(GetDocument(), Position(p1, 0),
+                                            Position(p3, 0));
+  EXPECT_EQ(
+      "Paragraph start text 1 to unique snippet of text.\n\n"
+      "Paragraph start text 2 to unique snippet of text.\n\n",
+      range->GetText());
+
+  TextFragmentSelector selector(TextFragmentSelector::SelectorType::kExact,
+                                "Paragraph start text", "", "", "");
+  MockTextFragmentFinderClient client;
+
+  MockTextFragmentFinder* finder = MakeGarbageCollected<MockTextFragmentFinder>(
+      client, selector, range,
+      TextFragmentFinder::FindBufferRunnerType::kSynchronous);
+
+  EXPECT_CALL(client, NoMatchFound()).Times(0);
+  bool is_unique_expected = false;
+  EXPECT_CALL(client, DidFindMatch(RangeContainedBy(p1), is_unique_expected))
+      .Times(1);
+  finder->FindMatch();
+  Mock::VerifyAndClearExpectations(&client);
+}
+
+// Tests that a text match is found within a defined suffix in the given range.
+TEST_F(TextFragmentFinderTest, TextMatchWithSuffixInRange) {
+  SimRequest request("https://example.com/test.html", "text/html");
+  LoadURL("https://example.com/test.html");
+  request.Complete(R"HTML(
+    <!DOCTYPE html>
+    <div>
+      <p id='p1'>First paragraph start text to unique snippet of text.</p>
+      <p id='p2'>Second paragraph start text to unique snippet of text.</p>
+      <p id='p3'>Third paragraph start text to unique snippet of text.</p>
+    </div>
+  )HTML");
+
+  Element* const p1 = GetDocument().getElementById(AtomicString("p1"));
+  Element* const p2 = GetDocument().getElementById(AtomicString("p2"));
+
+  auto* new_range = MakeGarbageCollected<Range>(GetDocument(), Position(p1, 0),
+                                                Position(p2, 0));
+  EXPECT_EQ("First paragraph start text to unique snippet of text.\n\n",
+            new_range->GetText());
+
+  TextFragmentSelector selector(TextFragmentSelector::SelectorType::kExact,
+                                "First paragraph", "", "", "start text");
+  MockTextFragmentFinderClient client;
+
+  MockTextFragmentFinder* finder = MakeGarbageCollected<MockTextFragmentFinder>(
+      client, selector, new_range,
+      TextFragmentFinder::FindBufferRunnerType::kSynchronous);
+
+  EXPECT_CALL(client, NoMatchFound()).Times(0);
+  EXPECT_CALL(client, DidFindMatch(_, true)).Times(1);
+  finder->FindMatch();
+  Mock::VerifyAndClearExpectations(&client);
+}
+
+// Tests that a text match is not found when the defined prefix is not in the
+// given range.
+TEST_F(TextFragmentFinderTest, TextMatchWithPrefixNotInRange) {
+  SimRequest request("https://example.com/test.html", "text/html");
+  LoadURL("https://example.com/test.html");
+  request.Complete(R"HTML(
+    <!DOCTYPE html>
+    <div>
+      <p id='p1'>First paragraph start text to unique snippet of text.</p>
+      <p id='p2'>Second paragraph start text to unique snippet of text.</p>
+      <p id='p3'>Third paragraph start text to unique snippet of text.</p>
+    </div>
+  )HTML");
+
+  Element* const p2 = GetDocument().getElementById(AtomicString("p2"));
+  Element* const p3 = GetDocument().getElementById(AtomicString("p3"));
+
+  auto* new_range = MakeGarbageCollected<Range>(GetDocument(), Position(p2, 0),
+                                                Position(p3, 0));
+  EXPECT_EQ("Second paragraph start text to unique snippet of text.\n\n",
+            new_range->GetText());
+
+  TextFragmentSelector selector(TextFragmentSelector::SelectorType::kExact,
+                                "Second paragraph", "", "snippet of text",
+                                "start text");
+  MockTextFragmentFinderClient client;
+
+  MockTextFragmentFinder* finder = MakeGarbageCollected<MockTextFragmentFinder>(
+      client, selector, new_range,
+      TextFragmentFinder::FindBufferRunnerType::kSynchronous);
+
+  EXPECT_CALL(client, NoMatchFound()).Times(1);
+  EXPECT_CALL(client, DidFindMatch(_, _)).Times(0);
+  finder->FindMatch();
+  Mock::VerifyAndClearExpectations(&client);
+}
+
+// Tests that a text match is not found before the defined surfix which is not
+// in the given range.
+TEST_F(TextFragmentFinderTest, TextMatchWithSuffixNotInRange) {
+  SimRequest request("https://example.com/test.html", "text/html");
+  LoadURL("https://example.com/test.html");
+  request.Complete(R"HTML(
+    <!DOCTYPE html>
+    <div>
+      <p id='p1'>First paragraph start text to unique snippet of text.</p>
+      <p id='p2'>Second paragraph start text to unique snippet of text.</p>
+      <p id='p3'>Third paragraph start text to unique snippet of text.</p>
+    </div>
+  )HTML");
+
+  Element* const p1 = GetDocument().getElementById(AtomicString("p1"));
+  Element* const p2 = GetDocument().getElementById(AtomicString("p2"));
+
+  auto* new_range = MakeGarbageCollected<Range>(GetDocument(), Position(p1, 0),
+                                                Position(p2, 0));
+  EXPECT_EQ("First paragraph start text to unique snippet of text.\n\n",
+            new_range->GetText());
+
+  TextFragmentSelector selector(TextFragmentSelector::SelectorType::kExact,
+                                "snippet of text", "", "", "Second paragraph");
+  MockTextFragmentFinderClient client;
+
+  MockTextFragmentFinder* finder = MakeGarbageCollected<MockTextFragmentFinder>(
+      client, selector, new_range,
+      TextFragmentFinder::FindBufferRunnerType::kSynchronous);
+
+  EXPECT_CALL(client, NoMatchFound()).Times(1);
+  EXPECT_CALL(client, DidFindMatch(_, _)).Times(0);
+  finder->FindMatch();
+  Mock::VerifyAndClearExpectations(&client);
+}
+
+// Test that if the range contains partial text of a node, the text should match
+// in the range not the full text of the node.
+TEST_F(TextFragmentFinderTest, TextMatchPartialNodeNotInRange) {
+  SimRequest request("https://example.com/test.html", "text/html");
+  LoadURL("https://example.com/test.html");
+  request.Complete(R"HTML(
+    <!DOCTYPE html>
+    <div>
+      <span id='p1'>First paragraph start text to unique snippet of text 1.</span>
+      <span id='p2'>Second paragraph start text to unique snippet of text 2.</span>
+      <span id='p3'>Third paragraph start text to unique snippet of text 3.</span>
+    </div>
+  )HTML");
+
+  Element* const p1 = GetDocument().getElementById(AtomicString("p1"));
+  Element* const p2 = GetDocument().getElementById(AtomicString("p2"));
+
+  auto* new_range = MakeGarbageCollected<Range>(
+      GetDocument(), Position(*p1->firstChild(), 6), Position(p2, 0));
+  EXPECT_EQ("paragraph start text to unique snippet of text 1. ",
+            new_range->GetText());
+
+  TextFragmentSelector selector(TextFragmentSelector::SelectorType::kExact,
+                                "First paragraph", "", "", "");
+  MockTextFragmentFinderClient client;
+
+  MockTextFragmentFinder* finder = MakeGarbageCollected<MockTextFragmentFinder>(
+      client, selector, new_range,
+      TextFragmentFinder::FindBufferRunnerType::kSynchronous);
+
+  EXPECT_CALL(client, NoMatchFound()).Times(1);
+  EXPECT_CALL(client, DidFindMatch(_, _)).Times(0);
+  finder->FindMatch();
+  Mock::VerifyAndClearExpectations(&client);
+}
+
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/html/resources/html.css b/third_party/blink/renderer/core/html/resources/html.css
index 0d8dbdf..6138a60e 100644
--- a/third_party/blink/renderer/core/html/resources/html.css
+++ b/third_party/blink/renderer/core/html/resources/html.css
@@ -727,13 +727,6 @@
     color: ButtonText;
 }
 
-@supports not blink-feature(ButtonNoAlignItems) {
-  input[type="button" i], input[type="submit" i], input[type="reset" i],
-  input[type="file" i]::-webkit-file-upload-button, button {
-    align-items: flex-start;
-  }
-}
-
 input[type="checkbox" i]:disabled,
 input[type="file" i]:disabled,
 input[type="hidden" i]:disabled,
diff --git a/third_party/blink/renderer/core/layout/anchor_evaluator_impl.cc b/third_party/blink/renderer/core/layout/anchor_evaluator_impl.cc
index 529209d..85509ba 100644
--- a/third_party/blink/renderer/core/layout/anchor_evaluator_impl.cc
+++ b/third_party/blink/renderer/core/layout/anchor_evaluator_impl.cc
@@ -371,7 +371,7 @@
     WritingMode self_writing_mode) const {
   const PhysicalSize& physical_size = reference.rect.size;
   LogicalSize logical_size =
-      physical_size.ConvertToLogical(container_writing_mode);
+      ToLogicalSize(physical_size, container_writing_mode);
 
   switch (anchor_size_value) {
     case CSSAnchorSizeValue::kInline:
diff --git a/third_party/blink/renderer/core/layout/block_node.cc b/third_party/blink/renderer/core/layout/block_node.cc
index b070652..9ac6041 100644
--- a/third_party/blink/renderer/core/layout/block_node.cc
+++ b/third_party/blink/renderer/core/layout/block_node.cc
@@ -1549,8 +1549,8 @@
     const PhysicalNaturalSizingInfo legacy_sizing_info =
         To<LayoutReplaced>(*box_).ComputeNaturalSizingInfo();
     if (!legacy_sizing_info.aspect_ratio.IsEmpty()) {
-      return legacy_sizing_info.aspect_ratio.ConvertToLogical(
-          Style().GetWritingMode());
+      return ToLogicalSize(legacy_sizing_info.aspect_ratio,
+                           Style().GetWritingMode());
     }
   }
 
@@ -1794,7 +1794,7 @@
   BoxStrut margins = ComputePhysicalMargins(constraint_space, Style())
                          .ConvertToLogical({writing_mode, TextDirection::kLtr});
   shape_outside->SetReferenceBoxLogicalSize(
-      box_size.ConvertToLogical(writing_mode),
+      ToLogicalSize(box_size, writing_mode),
       LogicalSize(margins.InlineSum(), margins.BlockSum()));
   shape_outside->SetPercentageResolutionInlineSize(
       constraint_space.PercentageResolutionInlineSize());
diff --git a/third_party/blink/renderer/core/layout/column_layout_algorithm.cc b/third_party/blink/renderer/core/layout/column_layout_algorithm.cc
index 19838d16f..191dbe6 100644
--- a/third_party/blink/renderer/core/layout/column_layout_algorithm.cc
+++ b/third_party/blink/renderer/core/layout/column_layout_algorithm.cc
@@ -434,8 +434,7 @@
   WritingMode writing_mode = parent_space.GetWritingMode();
   DCHECK(previous_column.IsColumnBox());
   const BlockBreakToken* break_token = previous_column.GetBreakToken();
-  LogicalSize column_size =
-      previous_column.Size().ConvertToLogical(writing_mode);
+  LogicalSize column_size = ToLogicalSize(previous_column.Size(), writing_mode);
   ConstraintSpace child_space = CreateConstraintSpaceForFragmentainer(
       parent_space, kFragmentColumn, column_size,
       /*percentage_resolution_size=*/column_size, /*balance_columns=*/false,
@@ -1660,7 +1659,7 @@
   for (auto& child : container_builder_.Children()) {
     if (child.fragment->IsFragmentainerBox()) {
       LayoutUnit fragmentainer_block_size =
-          child.fragment->Size().ConvertToLogical(writing_mode).block_size;
+          ToLogicalSize(child.fragment->Size(), writing_mode).block_size;
       total_block_size +=
           ClampedToValidFragmentainerCapacity(fragmentainer_block_size);
     }
diff --git a/third_party/blink/renderer/core/layout/fragment_builder.cc b/third_party/blink/renderer/core/layout/fragment_builder.cc
index 2798ae7..19f7804 100644
--- a/third_party/blink/renderer/core/layout/fragment_builder.cc
+++ b/third_party/blink/renderer/core/layout/fragment_builder.cc
@@ -211,7 +211,7 @@
     // Set the child's `anchor-name` before propagating its descendants', so
     // that ancestors have precedence over their descendants.
     LogicalRect logical_rect(child_offset,
-                             child.Size().ConvertToLogical(GetWritingMode()));
+                             ToLogicalSize(child.Size(), GetWritingMode()));
     const WritingModeConverter converter(GetWritingDirection(), Size());
     PhysicalRect rect = converter.ToPhysical(logical_rect);
     options = AnchorQuerySetOptions(
diff --git a/third_party/blink/renderer/core/layout/fragmentation_utils.cc b/third_party/blink/renderer/core/layout/fragmentation_utils.cc
index 619269b8..d94eaa98 100644
--- a/third_party/blink/renderer/core/layout/fragmentation_utils.cc
+++ b/third_party/blink/renderer/core/layout/fragmentation_utils.cc
@@ -1587,7 +1587,7 @@
     // other kind of monolithic content.
     WritingMode writing_mode = container_writing_direction.GetWritingMode();
     LogicalSize logical_size =
-        result.GetPhysicalFragment().Size().ConvertToLogical(writing_mode);
+        ToLogicalSize(result.GetPhysicalFragment().Size(), writing_mode);
     block_size = logical_size.block_size;
 
     // Then remove any block-end trimming, since it shouldn't take up space in
diff --git a/third_party/blink/renderer/core/layout/geometry/physical_size.h b/third_party/blink/renderer/core/layout/geometry/physical_size.h
index dfdb0183..a0a08460 100644
--- a/third_party/blink/renderer/core/layout/geometry/physical_size.h
+++ b/third_party/blink/renderer/core/layout/geometry/physical_size.h
@@ -43,11 +43,6 @@
   LayoutUnit width;
   LayoutUnit height;
 
-  LogicalSize ConvertToLogical(WritingMode mode) const {
-    return mode == WritingMode::kHorizontalTb ? LogicalSize(width, height)
-                                              : LogicalSize(height, width);
-  }
-
   PhysicalSize operator+(const PhysicalSize& other) const {
     return PhysicalSize{this->width + other.width, this->height + other.height};
   }
@@ -140,8 +135,13 @@
 
 CORE_EXPORT std::ostream& operator<<(std::ostream&, const PhysicalSize&);
 
+inline LogicalSize ToLogicalSize(const PhysicalSize& size, WritingMode mode) {
+  return IsHorizontalWritingMode(mode) ? LogicalSize(size.width, size.height)
+                                       : LogicalSize(size.height, size.width);
+}
+
 inline PhysicalSize ToPhysicalSize(const LogicalSize& other, WritingMode mode) {
-  return mode == WritingMode::kHorizontalTb
+  return IsHorizontalWritingMode(mode)
              ? PhysicalSize(other.inline_size, other.block_size)
              : PhysicalSize(other.block_size, other.inline_size);
 }
diff --git a/third_party/blink/renderer/core/layout/geometry/writing_mode_converter.h b/third_party/blink/renderer/core/layout/geometry/writing_mode_converter.h
index 95f153c1..1578cb8 100644
--- a/third_party/blink/renderer/core/layout/geometry/writing_mode_converter.h
+++ b/third_party/blink/renderer/core/layout/geometry/writing_mode_converter.h
@@ -111,7 +111,7 @@
 
 inline LogicalSize WritingModeConverter::ToLogical(
     const PhysicalSize& size) const {
-  return size.ConvertToLogical(GetWritingMode());
+  return ToLogicalSize(size, GetWritingMode());
 }
 
 inline PhysicalSize WritingModeConverter::ToPhysical(
diff --git a/third_party/blink/renderer/core/layout/inline/fragment_items_builder.cc b/third_party/blink/renderer/core/layout/inline/fragment_items_builder.cc
index f5540fa7..1a98e700 100644
--- a/third_party/blink/renderer/core/layout/inline/fragment_items_builder.cc
+++ b/third_party/blink/renderer/core/layout/inline/fragment_items_builder.cc
@@ -315,7 +315,7 @@
 
     last_break_token = break_token;
     container_builder->AddChild(*line_fragment, item_offset);
-    used_block_size += item.Size().ConvertToLogical(writing_mode).block_size;
+    used_block_size += ToLogicalSize(item.Size(), writing_mode).block_size;
 
     items_.emplace_back(item_offset, item);
     const PhysicalRect line_box_bounds = item.RectInContainerFragment();
diff --git a/third_party/blink/renderer/core/layout/inline/inline_cursor.cc b/third_party/blink/renderer/core/layout/inline/inline_cursor.cc
index 3d7da36..9f10c70 100644
--- a/third_party/blink/renderer/core/layout/inline/inline_cursor.cc
+++ b/third_party/blink/renderer/core/layout/inline/inline_cursor.cc
@@ -640,8 +640,7 @@
       // Hitting on line bottom doesn't count, to match legacy behavior.
       const LayoutUnit child_block_end_offset =
           child_block_offset +
-          child_item->Size()
-              .ConvertToLogical(writing_direction.GetWritingMode())
+          ToLogicalSize(child_item->Size(), writing_direction.GetWritingMode())
               .block_size;
       if (point_block_offset >= child_block_end_offset) {
         if (child_block_end_offset > closest_line_after_block_offset) {
@@ -765,8 +764,7 @@
     }
     const LayoutUnit child_inline_end_offset =
         child_inline_offset +
-        child_item->Size()
-            .ConvertToLogical(writing_direction.GetWritingMode())
+        ToLogicalSize(child_item->Size(), writing_direction.GetWritingMode())
             .inline_size;
     if (point_inline_offset >= child_inline_end_offset) {
       if (child_item->IsFloating())
diff --git a/third_party/blink/renderer/core/layout/layout_box.cc b/third_party/blink/renderer/core/layout/layout_box.cc
index 3d253197..b3f58dcb 100644
--- a/third_party/blink/renderer/core/layout/layout_box.cc
+++ b/third_party/blink/renderer/core/layout/layout_box.cc
@@ -299,7 +299,7 @@
   PhysicalSize size(
       WebThemeEngineHelper::GetNativeThemeEngine()->GetSize(part));
   size.Scale(style.EffectiveZoom());
-  return size.ConvertToLogical(style.GetWritingMode());
+  return ToLogicalSize(size, style.GetWritingMode());
 }
 
 LayoutUnit ListBoxDefaultItemHeight(const LayoutBox& box) {
diff --git a/third_party/blink/renderer/core/layout/layout_box_utils.cc b/third_party/blink/renderer/core/layout/layout_box_utils.cc
index 6b79152a..08baf5a 100644
--- a/third_party/blink/renderer/core/layout/layout_box_utils.cc
+++ b/third_party/blink/renderer/core/layout/layout_box_utils.cc
@@ -23,9 +23,8 @@
 
   // TODO(almaher): We can't assume all fragments will have the same inline
   // size.
-  return box.GetPhysicalFragment(0u)
-      ->Size()
-      .ConvertToLogical(box.StyleRef().GetWritingMode())
+  return ToLogicalSize(box.GetPhysicalFragment(0u)->Size(),
+                       box.StyleRef().GetWritingMode())
       .inline_size;
 }
 
@@ -38,9 +37,8 @@
   LayoutUnit total_block_size;
   while (num_fragments > 0) {
     LayoutUnit block_size =
-        box.GetPhysicalFragment(num_fragments - 1)
-            ->Size()
-            .ConvertToLogical(box.StyleRef().GetWritingMode())
+        ToLogicalSize(box.GetPhysicalFragment(num_fragments - 1)->Size(),
+                      box.StyleRef().GetWritingMode())
             .block_size;
     if (block_size > LayoutUnit()) {
       total_block_size += block_size;
diff --git a/third_party/blink/renderer/core/layout/layout_multi_column_set.cc b/third_party/blink/renderer/core/layout/layout_multi_column_set.cc
index 3421389..11f21797 100644
--- a/third_party/blink/renderer/core/layout/layout_multi_column_set.cc
+++ b/third_party/blink/renderer/core/layout/layout_multi_column_set.cc
@@ -396,7 +396,7 @@
                    border_padding_scrollbar.VerticalSum());
   LogicalSize logical_size;
   logical_size.inline_size =
-      content_size.ConvertToLogical(writing_mode).inline_size;
+      ToLogicalSize(content_size, writing_mode).inline_size;
 
   // TODO(layout-dev): Ideally we should not depend on the layout tree structure
   // because it may be different from the tree for the physical fragments.
diff --git a/third_party/blink/renderer/core/layout/length_utils.cc b/third_party/blink/renderer/core/layout/length_utils.cc
index 93ce044..8bbd239 100644
--- a/third_party/blink/renderer/core/layout/length_utils.cc
+++ b/third_party/blink/renderer/core/layout/length_utils.cc
@@ -959,7 +959,7 @@
   const auto& style = node.Style();
   PhysicalSize natural_size(LayoutUnit(300), LayoutUnit(150));
   natural_size.Scale(style.EffectiveZoom());
-  return natural_size.ConvertToLogical(style.GetWritingMode());
+  return ToLogicalSize(natural_size, style.GetWritingMode());
 }
 
 // This takes the aspect-ratio, and natural-sizes and normalizes them returning
@@ -1293,7 +1293,7 @@
   PhysicalSize container_size(svg_root->GetContainerSize());
   if (!container_size.IsEmpty()) {
     LogicalSize size =
-        container_size.ConvertToLogical(node.Style().GetWritingMode());
+        ToLogicalSize(container_size, node.Style().GetWritingMode());
     size.inline_size += border_padding.InlineSum();
     size.block_size += border_padding.BlockSum();
     return size;
@@ -1672,7 +1672,7 @@
     }
 
     const auto size = node.InitialContainingBlockSize();
-    return {size.ConvertToLogical(style.GetWritingMode()), {}, {}, {}};
+    return {ToLogicalSize(size, style.GetWritingMode()), {}, {}, {}};
   }
 
   const auto border = ComputeBorders(space, node);
diff --git a/third_party/blink/renderer/core/layout/list/unpositioned_list_marker.cc b/third_party/blink/renderer/core/layout/list/unpositioned_list_marker.cc
index 4d4327d..f22409f 100644
--- a/third_party/blink/renderer/core/layout/list/unpositioned_list_marker.cc
+++ b/third_party/blink/renderer/core/layout/list/unpositioned_list_marker.cc
@@ -134,7 +134,7 @@
   // When there are no line boxes, marker is top-aligned to the list item.
   // https://github.com/w3c/csswg-drafts/issues/2417
   LogicalSize marker_size =
-      marker_physical_fragment.Size().ConvertToLogical(space.GetWritingMode());
+      ToLogicalSize(marker_physical_fragment.Size(), space.GetWritingMode());
   LogicalOffset offset(InlineOffset(marker_size.inline_size), LayoutUnit());
 
   DCHECK(container_builder);
diff --git a/third_party/blink/renderer/core/layout/logical_fragment.h b/third_party/blink/renderer/core/layout/logical_fragment.h
index 9bcb640..ca464f7 100644
--- a/third_party/blink/renderer/core/layout/logical_fragment.h
+++ b/third_party/blink/renderer/core/layout/logical_fragment.h
@@ -33,8 +33,8 @@
                                              : physical_fragment_.Size().width;
   }
   LogicalSize Size() const {
-    return physical_fragment_.Size().ConvertToLogical(
-        writing_direction_.GetWritingMode());
+    return ToLogicalSize(physical_fragment_.Size(),
+                         writing_direction_.GetWritingMode());
   }
   WritingDirectionMode GetWritingDirection() const {
     return writing_direction_;
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 152820e..f717556 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
@@ -471,7 +471,7 @@
           ? PhysicalSize(frame_view->Size())
           : PhysicalSize(frame_view->LayoutViewport()->ExcludeScrollbars(
                 frame_view->Size()));
-  return size.ConvertToLogical(container.Style().GetWritingMode());
+  return ToLogicalSize(size, container.Style().GetWritingMode());
 }
 
 OutOfFlowLayoutPart::OutOfFlowLayoutPart(BoxFragmentBuilder* container_builder)
@@ -825,8 +825,8 @@
 
       const auto writing_direction =
           containing_block->StyleRef().GetWritingDirection();
-      LogicalSize size = containing_block_fragment->Size().ConvertToLogical(
-          writing_direction.GetWritingMode());
+      LogicalSize size = ToLogicalSize(containing_block_fragment->Size(),
+                                       writing_direction.GetWritingMode());
       size.block_size = BoxTotalBlockSize(*To<LayoutBox>(containing_block));
 
       // TODO(1079031): This should eventually include scrollbar and border.
@@ -1060,8 +1060,8 @@
         container_converter.ToLogical(end_rect.offset, end_rect.size);
 
     // Add in the size of the fragment to get the logical end of the fragment.
-    end_offset += end_rect.size.ConvertToLogical(
-        container_writing_direction.GetWritingMode());
+    end_offset += ToLogicalSize(end_rect.size,
+                                container_writing_direction.GetWritingMode());
 
     // Make sure we subtract the inline borders, we don't need to do this in the
     // inline direction if the blocks are in opposite directions.
@@ -1273,8 +1273,8 @@
           multicol_box_fragment->Borders().ConvertToLogical(writing_direction) +
           multicol_box_fragment->Padding().ConvertToLogical(writing_direction);
       LayoutUnit available_inline_size =
-          multicol_box_fragment->Size()
-              .ConvertToLogical(writing_direction.GetWritingMode())
+          ToLogicalSize(multicol_box_fragment->Size(),
+                        writing_direction.GetWritingMode())
               .inline_size -
           border_padding.InlineSum();
       column_inline_progression =
@@ -1666,9 +1666,8 @@
           if (!column_balancing_info_) {
             fragment = &GetChildFragment(index);
             fragmentainer_consumed_block_size_ +=
-                fragment->Size()
-                    .ConvertToLogical(
-                        container_builder_->Style().GetWritingMode())
+                ToLogicalSize(fragment->Size(),
+                              container_builder_->Style().GetWritingMode())
                     .block_size;
           }
         }
@@ -2482,8 +2481,8 @@
 
   offset_info.block_estimate = node_dimensions.size.block_size;
   offset_info.container_content_size =
-      container_physical_content_size.ConvertToLogical(
-          candidate_writing_direction.GetWritingMode());
+      ToLogicalSize(container_physical_content_size,
+                    candidate_writing_direction.GetWritingMode());
 
   // Calculate the offsets.
   const BoxStrut inset =
@@ -2633,7 +2632,7 @@
   PhysicalSize physical_size =
       ToPhysicalSize(logical_size, style.GetWritingMode());
   LogicalSize available_size =
-      physical_size.ConvertToLogical(GetConstraintSpace().GetWritingMode());
+      ToLogicalSize(physical_size, GetConstraintSpace().GetWritingMode());
   bool is_repeatable = false;
 
   ConstraintSpaceBuilder builder(GetConstraintSpace(),
@@ -2999,7 +2998,7 @@
   const WritingMode container_writing_mode =
       container_builder_->Style().GetWritingMode();
   LogicalSize fragmentainer_size =
-      fragment.Size().ConvertToLogical(container_writing_mode);
+      ToLogicalSize(fragment.Size(), container_writing_mode);
   LogicalSize percentage_resolution_size =
       LogicalSize(fragmentainer_size.inline_size,
                   container_builder_->ChildAvailableSize().block_size);
@@ -3054,9 +3053,8 @@
   for (; child_index < ChildCount(); child_index++) {
     const PhysicalBoxFragment& child_fragment = GetChildFragment(child_index);
     if (child_fragment.IsFragmentainerBox()) {
-      fragmentainer_block_size = child_fragment.Size()
-                                     .ConvertToLogical(default_writing_mode)
-                                     .block_size;
+      fragmentainer_block_size =
+          ToLogicalSize(child_fragment.Size(), default_writing_mode).block_size;
       fragmentainer_block_size =
           ClampedToValidFragmentainerCapacity(fragmentainer_block_size);
       current_max_block_size += fragmentainer_block_size;
diff --git a/third_party/blink/renderer/core/layout/page_container_layout_algorithm.cc b/third_party/blink/renderer/core/layout/page_container_layout_algorithm.cc
index 64ad876..9d4cff2 100644
--- a/third_party/blink/renderer/core/layout/page_container_layout_algorithm.cc
+++ b/third_party/blink/renderer/core/layout/page_container_layout_algorithm.cc
@@ -505,8 +505,8 @@
     // Need to lay out for block-sizes.
     main_axis_is_auto = child.Style().LogicalHeight().IsAuto();
     const LayoutResult* result = child.Layout(child_space);
-    LogicalSize size = result->GetPhysicalFragment().Size().ConvertToLogical(
-        child_space.GetWritingMode());
+    LogicalSize size = ToLogicalSize(result->GetPhysicalFragment().Size(),
+                                     child_space.GetWritingMode());
     minmax.min_size = size.block_size;
     minmax.max_size = size.block_size;
     margin_sum = margins.BlockSum();
@@ -528,7 +528,7 @@
   }
 
   LogicalSize available_logical_size =
-      available_physical_size.ConvertToLogical(Style().GetWritingMode());
+      ToLogicalSize(available_physical_size, Style().GetWritingMode());
   std::array<PreferredSizeInfo, 3> preferred_main_axis_sizes;
   LayoutUnit total_max_size_for_auto;
   bool has_auto_sized_box = false;
@@ -724,7 +724,7 @@
                                        child.Style().GetWritingDirection(),
                                        /*is_new_fc=*/true);
   LogicalSize available_size =
-      edge_rect.size.ConvertToLogical(Style().GetWritingMode());
+      ToLogicalSize(edge_rect.size, Style().GetWritingMode());
   bool main_axis_is_inline =
       IsHorizontal(dir) == Style().IsHorizontalWritingMode();
   if (main_axis_is_inline) {
diff --git a/third_party/blink/renderer/core/layout/pagination_utils.cc b/third_party/blink/renderer/core/layout/pagination_utils.cc
index a9e1dedf..a439062d 100644
--- a/third_party/blink/renderer/core/layout/pagination_utils.cc
+++ b/third_party/blink/renderer/core/layout/pagination_utils.cc
@@ -46,7 +46,7 @@
                                                     LogicalSize layout_size) {
   DCHECK(ShouldCenterPageOnPaper(document.GetFrame()->GetPrintParams()));
   LogicalSize target_size =
-      PageBoxDefaultSize(document).ConvertToLogical(style.GetWritingMode());
+      ToLogicalSize(PageBoxDefaultSize(document), style.GetWritingMode());
   if (layout_size.inline_size != layout_size.block_size &&
       (target_size.inline_size > target_size.block_size) !=
           (layout_size.inline_size > layout_size.block_size)) {
@@ -121,7 +121,7 @@
     }
   }
 
-  return layout_size.ConvertToLogical(style.GetWritingMode());
+  return ToLogicalSize(layout_size, style.GetWritingMode());
 }
 
 void ResolvePageBoxGeometry(const BlockNode& page_box,
@@ -228,7 +228,7 @@
                          &geometry, &margins);
   LogicalSize source_size = geometry.border_box_size + margins;
   LogicalSize target_size =
-      page_container.Size().ConvertToLogical(style.GetWritingMode());
+      ToLogicalSize(page_container.Size(), style.GetWritingMode());
 
   return layout_scale * TargetShrinkScaleFactor(target_size, source_size);
 }
diff --git a/third_party/blink/renderer/core/layout/physical_box_fragment.cc b/third_party/blink/renderer/core/layout/physical_box_fragment.cc
index 3bc10d6..d460a1c4 100644
--- a/third_party/blink/renderer/core/layout/physical_box_fragment.cc
+++ b/third_party/blink/renderer/core/layout/physical_box_fragment.cc
@@ -669,7 +669,7 @@
     stitched_offset.block_offset = incoming_break_token->ConsumedBlockSize();
   LogicalRect logical_fragment_rect(
       stitched_offset,
-      Size().ConvertToLogical(writing_direction.GetWritingMode()));
+      ToLogicalSize(Size(), writing_direction.GetWritingMode()));
   PhysicalRect physical_fragment_rect =
       converter.ToPhysical(logical_fragment_rect);
 
@@ -1509,9 +1509,8 @@
     bool check_no_fragmentation) const {
   DCHECK_EQ(layout_object_, other.layout_object_);
 
-  LogicalSize size = size_.ConvertToLogical(Style().GetWritingMode());
-  LogicalSize other_size =
-      other.size_.ConvertToLogical(Style().GetWritingMode());
+  LogicalSize size = ToLogicalSize(size_, Style().GetWritingMode());
+  LogicalSize other_size = ToLogicalSize(other.size_, Style().GetWritingMode());
   DCHECK_EQ(size.inline_size, other_size.inline_size);
   if (check_same_block_size)
     DCHECK_EQ(size.block_size, other_size.block_size);
diff --git a/third_party/blink/renderer/core/layout/svg/layout_svg_foreign_object.cc b/third_party/blink/renderer/core/layout/svg/layout_svg_foreign_object.cc
index 2429fe7..9f49efb 100644
--- a/third_party/blink/renderer/core/layout/svg/layout_svg_foreign_object.cc
+++ b/third_party/blink/renderer/core/layout/svg/layout_svg_foreign_object.cc
@@ -121,9 +121,10 @@
   // This is necessary for external/wpt/inert/inert-on-non-html.html.
   // See FullyClipsContents() in fully_clipped_state_stack.cc.
   const float zoom = style.EffectiveZoom();
-  LogicalSize zoomed_size = PhysicalSize(LayoutUnit(viewport_.width() * zoom),
-                                         LayoutUnit(viewport_.height() * zoom))
-                                .ConvertToLogical(style.GetWritingMode());
+  LogicalSize zoomed_size =
+      ToLogicalSize(PhysicalSize(LayoutUnit(viewport_.width() * zoom),
+                                 LayoutUnit(viewport_.height() * zoom)),
+                    style.GetWritingMode());
 
   // Use the zoomed version of the viewport as the location, because we will
   // interpose a transform that "unzooms" the effective zoom to let the children
diff --git a/third_party/blink/renderer/core/loader/document_load_timing.h b/third_party/blink/renderer/core/loader/document_load_timing.h
index 02ef4c31..ad707da2 100644
--- a/third_party/blink/renderer/core/loader/document_load_timing.h
+++ b/third_party/blink/renderer/core/loader/document_load_timing.h
@@ -34,8 +34,7 @@
 #include "third_party/blink/renderer/core/core_export.h"
 #include "third_party/blink/renderer/platform/heap/garbage_collected.h"
 #include "third_party/blink/renderer/platform/heap/member.h"
-#include "third_party/blink/renderer/platform/instrumentation/tracing/traced_value.h"
-#include "third_party/perfetto/include/perfetto/tracing/traced_value_forward.h"
+#include "third_party/perfetto/include/perfetto/tracing/traced_value.h"
 
 namespace base {
 class Clock;
diff --git a/third_party/blink/renderer/core/paint/box_fragment_painter.cc b/third_party/blink/renderer/core/paint/box_fragment_painter.cc
index 7e63aa9..0f3773c 100644
--- a/third_party/blink/renderer/core/paint/box_fragment_painter.cc
+++ b/third_party/blink/renderer/core/paint/box_fragment_painter.cc
@@ -1349,10 +1349,10 @@
   BoxSide box_side = BoxSideFromGridDirection(style, track_direction);
 
   Color rule_color;
-  EBorderStyle rule_style;
-  LayoutUnit rule_thickness;
   RuleBreak rule_break;
   Length rule_outset;
+  GapDataList<EBorderStyle> rule_styles;
+  GapDataList<int> rule_widths;
 
   // TODO(crbug.com/357648037): We are currently only painting gaps with a
   // single color, but we should update this to paint with all values
@@ -1360,21 +1360,22 @@
   if (track_direction == kForColumns) {
     rule_color =
         LayoutObject::ResolveColor(style, GetCSSPropertyColumnRuleColor());
-    rule_style = ComputedStyle::CollapsedBorderStyle(
-        style.ColumnRuleStyle().GetLegacyValue());
-    rule_thickness = LayoutUnit(style.ColumnRuleWidth().GetLegacyValue());
+    rule_styles = style.ColumnRuleStyle();
+    rule_widths = style.ColumnRuleWidth();
     rule_break = style.ColumnRuleBreak();
     rule_outset = style.ColumnRuleOutset();
   } else {
     rule_color =
         LayoutObject::ResolveColor(style, GetCSSPropertyRowRuleColor());
-    rule_style = ComputedStyle::CollapsedBorderStyle(
-        style.RowRuleStyle().GetLegacyValue());
-    rule_thickness = LayoutUnit(style.RowRuleWidth().GetLegacyValue());
+    rule_styles = style.RowRuleStyle();
+    rule_widths = style.RowRuleWidth();
     rule_break = style.RowRuleBreak();
     rule_outset = style.RowRuleOutset();
   }
 
+  rule_styles.ExpandValues();
+  rule_widths.ExpandValues();
+
   // Determines if the `end_index` should advance when determining pairs for gap
   // decorations. For `kSpanningItem` rule break, decorations break only at "T"
   // intersections, so we simply check that the intersection isn't blocked
@@ -1516,6 +1517,10 @@
       LayoutUnit decoration_end_offset =
           LayoutUnit(end_width / 2.0f) - end_outset;
 
+      EBorderStyle rule_style =
+          rule_styles.GetGapDecorationForGapIndex(gap_index, gaps.size());
+      LayoutUnit rule_thickness = LayoutUnit(
+          rule_widths.GetGapDecorationForGapIndex(gap_index, gaps.size()));
       if (track_direction == kForColumns) {
         // For columns, paint a vertical strip at the center of the gap.
         const LayoutUnit center = gap[start].inline_offset;
diff --git a/third_party/blink/renderer/core/paint/fieldset_painter.cc b/third_party/blink/renderer/core/paint/fieldset_painter.cc
index d7c8c62b..988adea6 100644
--- a/third_party/blink/renderer/core/paint/fieldset_painter.cc
+++ b/third_party/blink/renderer/core/paint/fieldset_painter.cc
@@ -50,12 +50,12 @@
     // However we don't need them.
     const WritingDirectionMode writing_direction = style.GetWritingDirection();
     const LogicalSize logical_fieldset_content_size =
-        (fieldset_size -
-         PhysicalSize(fieldset_borders.HorizontalSum(),
-                      fieldset_borders.VerticalSum()) -
-         PhysicalSize(fragment.Padding().HorizontalSum(),
-                      fragment.Padding().VerticalSum()))
-            .ConvertToLogical(writing_direction.GetWritingMode());
+        ToLogicalSize(fieldset_size -
+                          PhysicalSize(fieldset_borders.HorizontalSum(),
+                                       fieldset_borders.VerticalSum()) -
+                          PhysicalSize(fragment.Padding().HorizontalSum(),
+                                       fragment.Padding().VerticalSum()),
+                      writing_direction.GetWritingMode());
     LogicalOffset relative_offset = ComputeRelativeOffset(
         (*legend)->Style(), writing_direction, logical_fieldset_content_size);
     LogicalOffset legend_logical_offset =
diff --git a/third_party/blink/renderer/core/style/computed_style.h b/third_party/blink/renderer/core/style/computed_style.h
index 635406ed..30b5ff0 100644
--- a/third_party/blink/renderer/core/style/computed_style.h
+++ b/third_party/blink/renderer/core/style/computed_style.h
@@ -585,8 +585,7 @@
 
   // column-rule-width
   GapDataList<int> ColumnRuleWidth() const {
-    if (ColumnRuleStyle().GetLegacyValue() == EBorderStyle::kNone ||
-        ColumnRuleStyle().GetLegacyValue() == EBorderStyle::kHidden) {
+    if (!BorderStyleIsVisible(ColumnRuleStyle())) {
       return GapDataList<int>(0);
     }
     return ColumnRuleWidthInternal();
@@ -1000,8 +999,8 @@
                                 Display() != EDisplay::kFlex)) [[likely]] {
       return false;
     }
-    return ColumnRuleWidth().GetLegacyValue() && !ColumnRuleIsTransparent() &&
-           BorderStyleIsVisible(ColumnRuleStyle().GetLegacyValue());
+    return HasRuleWidth(ColumnRuleWidth()) && !ColumnRuleIsTransparent() &&
+           BorderStyleIsVisible(ColumnRuleStyle());
   }
 
   bool RowRuleIsTransparent() const {
@@ -1011,8 +1010,8 @@
         .IsFullyTransparent();
   }
   bool HasRowRule() const {
-    return RowRuleWidth().GetLegacyValue() && !RowRuleIsTransparent() &&
-           BorderStyleIsVisible(RowRuleStyle().GetLegacyValue());
+    return HasRuleWidth(RowRuleWidth()) && !RowRuleIsTransparent() &&
+           BorderStyleIsVisible(RowRuleStyle());
   }
 
   bool HasGapRule() const { return HasColumnRule() || HasRowRule(); }
@@ -2248,6 +2247,49 @@
   static bool BorderStyleIsVisible(EBorderStyle style) {
     return style != EBorderStyle::kNone && style != EBorderStyle::kHidden;
   }
+
+  static bool BorderStyleIsVisible(GapDataList<EBorderStyle> styles) {
+    for (const auto& style : styles.GetGapDataList()) {
+      if (!style.IsRepeaterData()) {
+        // Simple single value, check directly.
+        if (BorderStyleIsVisible(style.GetValue())) {
+          return true;
+        }
+      } else {
+        // Repeater value, check each repeated value.
+        for (const auto& repeated_style :
+             style.GetValueRepeater()->RepeatedValues()) {
+          if (BorderStyleIsVisible(repeated_style)) {
+            return true;
+          }
+        }
+      }
+    }
+
+    return false;
+  }
+
+  static bool HasRuleWidth(GapDataList<int> widths) {
+    for (const auto& width : widths.GetGapDataList()) {
+      if (!width.IsRepeaterData()) {
+        // Simple single value, check directly.
+        if (width.GetValue() != 0) {
+          return true;
+        }
+      } else {
+        // Repeater value, check each repeated value.
+        for (const auto& repeated_width :
+             width.GetValueRepeater()->RepeatedValues()) {
+          if (repeated_width != 0) {
+            return true;
+          }
+        }
+      }
+    }
+
+    return false;
+  }
+
   bool BorderObscuresBackground() const;
   void GetBorderEdgeInfo(
       BorderEdgeArray& edges,
@@ -2423,7 +2465,7 @@
 
   LogicalSize LogicalAspectRatio() const {
     DCHECK_NE(AspectRatio().GetType(), EAspectRatioType::kAuto);
-    return AspectRatio().GetLayoutRatio().ConvertToLogical(GetWritingMode());
+    return ToLogicalSize(AspectRatio().GetLayoutRatio(), GetWritingMode());
   }
 
   EBoxSizing BoxSizingForAspectRatio() const {
diff --git a/third_party/blink/renderer/core/style/gap_data_list.h b/third_party/blink/renderer/core/style/gap_data_list.h
index 176dcb5..93635e6 100644
--- a/third_party/blink/renderer/core/style/gap_data_list.h
+++ b/third_party/blink/renderer/core/style/gap_data_list.h
@@ -19,6 +19,8 @@
 class CORE_EXPORT GapDataList {
   DISALLOW_NEW();
 
+  using VectorType = ValueRepeater<T>::VectorType;
+
  public:
   using GapDataVector = HeapVector<GapData<T>, 1>;
   GapDataList() = default;
@@ -45,7 +47,10 @@
     gap_data_list_.emplace_back(GapData<T>(value));
   }
 
-  void Trace(Visitor* visitor) const { visitor->Trace(gap_data_list_); }
+  void Trace(Visitor* visitor) const {
+    visitor->Trace(gap_data_list_);
+    TraceIfNeeded<VectorType>::Trace(visitor, expanded_values_);
+  }
 
   const GapDataVector& GetGapDataList() const { return gap_data_list_; }
 
@@ -57,10 +62,100 @@
   }
 
   const T GetLegacyValue() const {
-    CHECK_EQ(gap_data_list_.size(), 1U);
     return gap_data_list_[0].GetValue();
   }
 
+  // TODO(samomekarajr): Potential optimization. We might not need to expand
+  // values. Instead, we can adopt a method similar to the grid's implementation
+  // by using sets. This way, when given a particular gap index, we can
+  // translate it to a specific item in the set. This approach allows us to
+  // avoid expanding values and storing them in a vector, especially when an
+  // integer repeater has a very large count. For this to be worthwhie, we need
+  // to be able to get a given decoration value for a gap index in less than
+  // O(n) time.
+
+  // Expands `gap_data_list_` into `expanded_values_` by evaluating the
+  // repeaters and storing the values in the order they are specified.
+  // When an auto repeater is present, it stores the range of the auto
+  // repeated values as [`auto_repeat_start_`, `auto_repeat_end_`).
+  void ExpandValues() {
+    expanded_values_.clear();
+    auto_repeat_start_ = kNotFound;
+    auto_repeat_end_ = kNotFound;
+
+    for (const auto& gap_data : gap_data_list_) {
+      if (!gap_data.IsRepeaterData()) {
+        // Simple single value, add to `expanded_values_`.
+        expanded_values_.push_back(gap_data.GetValue());
+      } else {
+        const ValueRepeater<T>* repeater = gap_data.GetValueRepeater();
+
+        if (repeater->IsAutoRepeater()) {
+          // Only one auto repeater is allowed, so store the range of the auto
+          // repeated values.
+          CHECK_EQ(auto_repeat_start_, kNotFound);
+          CHECK_EQ(auto_repeat_end_, kNotFound);
+          auto_repeat_start_ = expanded_values_.size();
+          for (const auto& value : repeater->RepeatedValues()) {
+            expanded_values_.push_back(value);
+          }
+          auto_repeat_end_ = expanded_values_.size();
+        } else {
+          // Integer repeater, add values `count` times.
+          wtf_size_t count = repeater->RepeatCount();
+
+          for (size_t i = 0; i < count; ++i) {
+            for (const auto& value : repeater->RepeatedValues()) {
+              expanded_values_.push_back(value);
+            }
+          }
+        }
+      }
+    }
+  }
+
+  // Returns the gap decoration value for a given gap index. It uses the
+  // `auto_repeat_start_` and `auto_repeat_end_` to partition the
+  // `expanded_values_` into leading, auto repeat, and trailing sections. The
+  // value at the given index is then determined based on the section it falls
+  // into.
+  T GetGapDecorationForGapIndex(wtf_size_t gap_index,
+                                wtf_size_t total_gaps) const {
+    if (auto_repeat_start_ == kNotFound) {
+      // No auto repeaters are present, so we need to return the value at the
+      // valid index. The size of `expanded_values_` might be less than the
+      // `total_gaps`, so we apply values cyclically.
+      return expanded_values_[gap_index % expanded_values_.size()];
+    }
+
+    // Leading section ends at the start of the auto repeat section.
+    const wtf_size_t leading_section_end = auto_repeat_start_;
+
+    // Trailing section starts after the auto repeat section so it'll be the
+    // number of gaps minus the number of trailing values.
+    const wtf_size_t trailing_section_start =
+        total_gaps - (expanded_values_.size() - auto_repeat_end_);
+
+    if (gap_index < leading_section_end) {
+      // Leading values can be indexed directly.
+      return expanded_values_[gap_index];
+    } else if (gap_index >= trailing_section_start) {
+      // Get the index of the gap relative to the trailing section and return
+      // expanded value at that index. Note: `trailing_index` indicates the
+      // index of the gap relative to trailing values, hence we need to add
+      // `auto_repeat_end_` to get the actual index in the expanded values.
+      wtf_size_t trailing_index = gap_index - trailing_section_start;
+      return expanded_values_[auto_repeat_end_ + trailing_index];
+    } else {
+      // For auto repeat values, we get the index of the gap relative to the
+      // auto section and return the expanded value at that index.
+      wtf_size_t auto_section_length = auto_repeat_end_ - auto_repeat_start_;
+      wtf_size_t gap_index_in_auto_section =
+          (gap_index - leading_section_end) % auto_section_length;
+      return expanded_values_[auto_repeat_start_ + gap_index_in_auto_section];
+    }
+  }
+
   bool operator==(const GapDataList& o) const {
     return gap_data_list_ == o.gap_data_list_;
   }
@@ -69,6 +164,16 @@
 
  private:
   GapDataVector gap_data_list_;
+
+  // Holds the expanded values of `gap_data_list_` in the order they are
+  // specified. This is used to get the gap decoration value for a given gap
+  // index. If the `gap_data_list_` contains an auto repeater, the repeated
+  // values are stored in the order they are specified, with
+  // `auto_repeat_start_` and `auto_repeat_end_` indicating the range of the
+  // auto repeated values.
+  VectorType expanded_values_;
+  wtf_size_t auto_repeat_start_ = kNotFound;
+  wtf_size_t auto_repeat_end_ = kNotFound;
 };
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/workers/dedicated_worker.cc b/third_party/blink/renderer/core/workers/dedicated_worker.cc
index b499db4b..a0c372a 100644
--- a/third_party/blink/renderer/core/workers/dedicated_worker.cc
+++ b/third_party/blink/renderer/core/workers/dedicated_worker.cc
@@ -15,10 +15,8 @@
 #include "base/trace_event/typed_macros.h"
 #include "base/unguessable_token.h"
 #include "mojo/public/cpp/bindings/pending_remote.h"
-#include "services/network/public/cpp/cross_origin_embedder_policy.h"
 #include "services/network/public/mojom/fetch_api.mojom-blink.h"
 #include "third_party/blink/public/common/blob/blob_utils.h"
-#include "third_party/blink/public/common/features.h"
 #include "third_party/blink/public/mojom/browser_interface_broker.mojom-blink.h"
 #include "third_party/blink/public/mojom/fetch/fetch_api_request.mojom-blink.h"
 #include "third_party/blink/public/mojom/script/script_type.mojom-blink.h"
@@ -301,50 +299,6 @@
   // Continue in OnScriptLoadStarted() or OnScriptLoadStartFailed().
 }
 
-void DedicatedWorker::OnHostCreated(
-    mojo::PendingRemote<network::mojom::blink::URLLoaderFactory>
-        blob_url_loader_factory,
-    const network::CrossOriginEmbedderPolicy& parent_coep,
-    CrossVariantMojoRemote<
-        mojom::blink::BackForwardCacheControllerHostInterfaceBase>
-        back_forward_cache_controller_host) {
-  DCHECK(!base::FeatureList::IsEnabled(features::kPlzDedicatedWorker));
-  const RejectCoepUnsafeNone reject_coep_unsafe_none(
-      network::CompatibleWithCrossOriginIsolated(parent_coep));
-  if (options_->type() == script_type_names::kClassic) {
-    // Legacy code path (to be deprecated, see https://crbug.com/835717):
-    // A worker thread will start after scripts are fetched on the current
-    // thread.
-    classic_script_loader_ = MakeGarbageCollected<WorkerClassicScriptLoader>();
-    classic_script_loader_->LoadTopLevelScriptAsynchronously(
-        *GetExecutionContext(), GetExecutionContext()->Fetcher(),
-        script_request_url_, nullptr /* worker_main_script_load_params */,
-        mojom::blink::RequestContextType::WORKER,
-        network::mojom::RequestDestination::kWorker,
-        network::mojom::RequestMode::kSameOrigin,
-        network::mojom::CredentialsMode::kSameOrigin,
-        WTF::BindOnce(&DedicatedWorker::OnResponse, WrapPersistent(this)),
-        WTF::BindOnce(&DedicatedWorker::OnFinished, WrapPersistent(this),
-                      std::move(back_forward_cache_controller_host)),
-        reject_coep_unsafe_none, std::move(blob_url_loader_factory));
-    return;
-  }
-  if (options_->type() == script_type_names::kModule) {
-    // Specify empty source code etc. here because scripts will be fetched on
-    // the worker thread.
-    ContinueStart(script_request_url_,
-                  nullptr /* worker_main_script_load_params */,
-                  network::mojom::ReferrerPolicy::kDefault,
-                  Vector<network::mojom::blink::ContentSecurityPolicyPtr>(),
-                  String() /* source_code */, reject_coep_unsafe_none,
-                  std::move(back_forward_cache_controller_host),
-                  /*coep_reporting_observer=*/mojo::NullReceiver(),
-                  /*dip_reporting_observer=*/mojo::NullReceiver());
-    return;
-  }
-  NOTREACHED() << "Invalid type: " << IDLEnumAsString(options_->type());
-}
-
 void DedicatedWorker::terminate() {
   DCHECK(!GetExecutionContext() || GetExecutionContext()->IsContextThread());
   context_proxy_->TerminateGlobalScope();
@@ -390,10 +344,6 @@
         coep_reporting_observer,
     CrossVariantMojoReceiver<mojom::blink::ReportingObserverInterfaceBase>
         dip_reporting_observer) {
-  DCHECK(base::FeatureList::IsEnabled(features::kPlzDedicatedWorker));
-  TRACE_EVENT_NESTABLE_ASYNC_END0("blink.worker",
-                                  "PlzDedicatedWorker Specific Setup",
-                                  TRACE_ID_LOCAL(this));
   TRACE_EVENT("blink.worker", "DedicatedWorker::OnScriptLoadStarted");
   // Specify empty source code here because scripts will be fetched on the
   // worker thread.
@@ -407,10 +357,6 @@
 }
 
 void DedicatedWorker::OnScriptLoadStartFailed() {
-  DCHECK(base::FeatureList::IsEnabled(features::kPlzDedicatedWorker));
-  TRACE_EVENT_NESTABLE_ASYNC_END0("blink.worker",
-                                  "PlzDedicatedWorker Specific Setup",
-                                  TRACE_ID_LOCAL(this));
   TRACE_EVENT("blink.worker", "DedicatedWorker::OnScriptLoadStartFailed");
   // Specify empty source code here because scripts will be fetched on the
   context_proxy_->DidFailToFetchScript();
@@ -677,13 +623,9 @@
   if (auto* window = DynamicTo<LocalDOMWindow>(GetExecutionContext())) {
     scoped_refptr<WebWorkerFetchContext> web_worker_fetch_context;
     LocalFrame* frame = window->GetFrame();
-    if (base::FeatureList::IsEnabled(features::kPlzDedicatedWorker)) {
-      web_worker_fetch_context =
-          frame->Client()->CreateWorkerFetchContextForPlzDedicatedWorker(
-              factory_client_.get());
-    } else {
-      web_worker_fetch_context = frame->Client()->CreateWorkerFetchContext();
-    }
+    web_worker_fetch_context =
+        frame->Client()->CreateWorkerFetchContextForPlzDedicatedWorker(
+            factory_client_.get());
     web_worker_fetch_context->SetIsOnSubframe(!frame->IsOutermostMainFrame());
     return web_worker_fetch_context;
   }
diff --git a/third_party/blink/renderer/core/workers/dedicated_worker.h b/third_party/blink/renderer/core/workers/dedicated_worker.h
index 080ae5a..db3e50f 100644
--- a/third_party/blink/renderer/core/workers/dedicated_worker.h
+++ b/third_party/blink/renderer/core/workers/dedicated_worker.h
@@ -12,7 +12,6 @@
 #include "base/memory/scoped_refptr.h"
 #include "mojo/public/cpp/bindings/pending_remote.h"
 #include "services/network/public/mojom/content_security_policy.mojom-blink-forward.h"
-#include "services/network/public/mojom/url_loader_factory.mojom-blink.h"
 #include "third_party/blink/public/common/loader/worker_main_script_load_parameters.h"
 #include "third_party/blink/public/common/tokens/tokens.h"
 #include "third_party/blink/public/mojom/browser_interface_broker.mojom-blink-forward.h"
@@ -194,14 +193,6 @@
   // May return nullptr.
   std::unique_ptr<WebContentSettingsClient> CreateWebContentSettingsClient();
 
-  void OnHostCreated(
-      mojo::PendingRemote<network::mojom::blink::URLLoaderFactory>
-          blob_url_loader_factory,
-      const network::CrossOriginEmbedderPolicy& parent_coep,
-      CrossVariantMojoRemote<
-          mojom::blink::BackForwardCacheControllerHostInterfaceBase>
-          back_forward_cache_controller_host);
-
   // Callbacks for |classic_script_loader_|.
   void OnResponse();
   void OnFinished(
diff --git a/third_party/blink/renderer/core/workers/dedicated_worker_global_scope.cc b/third_party/blink/renderer/core/workers/dedicated_worker_global_scope.cc
index f4c5a07..c07697b 100644
--- a/third_party/blink/renderer/core/workers/dedicated_worker_global_scope.cc
+++ b/third_party/blink/renderer/core/workers/dedicated_worker_global_scope.cc
@@ -40,7 +40,6 @@
 #include "base/trace_event/typed_macros.h"
 #include "base/types/pass_key.h"
 #include "net/storage_access_api/status.h"
-#include "third_party/blink/public/common/features.h"
 #include "third_party/blink/public/common/loader/worker_main_script_load_parameters.h"
 #include "third_party/blink/public/mojom/fetch/fetch_api_request.mojom-blink.h"
 #include "third_party/blink/public/mojom/frame/back_forward_cache_controller.mojom-blink.h"
@@ -302,7 +301,6 @@
     const FetchClientSettingsObjectSnapshot& outside_settings_object,
     WorkerResourceTimingNotifier& outside_resource_timing_notifier,
     const v8_inspector::V8StackTraceId& stack_id) {
-  DCHECK(base::FeatureList::IsEnabled(features::kPlzDedicatedWorker));
   DCHECK(!IsContextPaused());
   TRACE_EVENT("blink.worker",
               "DedicatedWorkerGlobalScope::FetchAndRunClassicScript",
@@ -385,13 +383,9 @@
 }
 
 bool DedicatedWorkerGlobalScope::IsOffMainThreadScriptFetchDisabled() {
-  // The top-level dedicated worker script is loaded on the main thread when the
-  // script type is classic and PlzDedicatedWorker (off-the-main-thread script
-  // fetch) is disabled.
-  // TODO(https://crbug.com/835717): Remove this function after dedicated
+  // TODO(https://crbug.com/835717): Remove this function now that dedicated
   // workers support off-the-main-thread script fetch by default.
-  return GetScriptType() == mojom::blink::ScriptType::kClassic &&
-         !base::FeatureList::IsEnabled(features::kPlzDedicatedWorker);
+  return false;
 }
 
 const String DedicatedWorkerGlobalScope::name() const {
@@ -452,7 +446,6 @@
 void DedicatedWorkerGlobalScope::DidReceiveResponseForClassicScript(
     WorkerClassicScriptLoader* classic_script_loader) {
   DCHECK(IsContextThread());
-  DCHECK(base::FeatureList::IsEnabled(features::kPlzDedicatedWorker));
   probe::DidReceiveScriptResponse(this, classic_script_loader->Identifier());
 }
 
@@ -461,7 +454,6 @@
     WorkerClassicScriptLoader* classic_script_loader,
     const v8_inspector::V8StackTraceId& stack_id) {
   DCHECK(IsContextThread());
-  DCHECK(base::FeatureList::IsEnabled(features::kPlzDedicatedWorker));
   TRACE_EVENT("blink.worker",
               "DedicatedWorkerGlobalScope::DidFetchClassicScript");
   TRACE_EVENT_NESTABLE_ASYNC_END0(
@@ -545,8 +537,8 @@
   // `back_forward_cache_controller_host_` might not be bound when non-
   // PlzDedicatedWorker is used. Non-PlzDedicatedWorker will be removed in near
   // future.
-  // TODO(hajimehoshi): Remove this 'if' branch after non-PlzDedicatedWorker is
-  // removed.
+  // TODO(crbug.com/40093136): Remove this 'if' branch now that
+  // PlzDedicatedWorker has been removed.
   if (!back_forward_cache_controller_host_.is_bound()) {
     return;
   }
diff --git a/third_party/blink/renderer/core/workers/dedicated_worker_global_scope.h b/third_party/blink/renderer/core/workers/dedicated_worker_global_scope.h
index 2ecf753b..550ab33 100644
--- a/third_party/blink/renderer/core/workers/dedicated_worker_global_scope.h
+++ b/third_party/blink/renderer/core/workers/dedicated_worker_global_scope.h
@@ -229,6 +229,8 @@
   bool cross_origin_isolated_capability_;
   bool is_isolated_context_;
   Member<WorkerAnimationFrameProvider> animation_frame_provider_;
+  // TODO(crbug.com/40093136): Remove this now that PlzDedicatedWorker has
+  // launched.
   RejectCoepUnsafeNone reject_coep_unsafe_none_ = RejectCoepUnsafeNone(false);
 
   HeapMojoRemote<mojom::blink::DedicatedWorkerHost> dedicated_worker_host_{
diff --git a/third_party/blink/renderer/core/workers/dedicated_worker_messaging_proxy.cc b/third_party/blink/renderer/core/workers/dedicated_worker_messaging_proxy.cc
index 04e31222..034948b 100644
--- a/third_party/blink/renderer/core/workers/dedicated_worker_messaging_proxy.cc
+++ b/third_party/blink/renderer/core/workers/dedicated_worker_messaging_proxy.cc
@@ -5,10 +5,8 @@
 #include "third_party/blink/renderer/core/workers/dedicated_worker_messaging_proxy.h"
 
 #include <memory>
-#include "base/feature_list.h"
 #include "base/trace_event/typed_macros.h"
 #include "services/network/public/mojom/fetch_api.mojom-blink.h"
-#include "third_party/blink/public/common/features.h"
 #include "third_party/blink/public/common/tokens/tokens.h"
 #include "third_party/blink/public/mojom/v8_cache_options.mojom-blink.h"
 #include "third_party/blink/public/mojom/worker/dedicated_worker_host.mojom-blink-forward.h"
@@ -81,6 +79,8 @@
     const KURL& script_url,
     const FetchClientSettingsObjectSnapshot& outside_settings_object,
     const v8_inspector::V8StackTraceId& stack_id,
+    // TODO(crbug.com/40093136): Remove this now that PlzDedicatedWorker has
+    // launched.
     const String& source_code,
     RejectCoepUnsafeNone reject_coep_unsafe_none,
     const blink::DedicatedWorkerToken& token,
@@ -112,20 +112,14 @@
     // destination, and inside settings."
     UseCounter::Count(GetExecutionContext(),
                       WebFeature::kClassicDedicatedWorker);
-    if (base::FeatureList::IsEnabled(features::kPlzDedicatedWorker)) {
-      auto* resource_timing_notifier =
-          WorkerResourceTimingNotifierImpl::CreateForOutsideResourceFetcher(
-              *GetExecutionContext());
-      // TODO(crbug.com/1177199): pass a proper policy container
-      GetWorkerThread()->FetchAndRunClassicScript(
-          script_url, std::move(worker_main_script_load_params),
-          /*policy_container=*/nullptr, outside_settings_object.CopyData(),
-          resource_timing_notifier, stack_id);
-    } else {
-      // Legacy code path (to be deprecated, see https://crbug.com/835717):
-      GetWorkerThread()->EvaluateClassicScript(
-          script_url, source_code, nullptr /* cached_meta_data */, stack_id);
-    }
+    auto* resource_timing_notifier =
+        WorkerResourceTimingNotifierImpl::CreateForOutsideResourceFetcher(
+            *GetExecutionContext());
+    // TODO(crbug.com/1177199): pass a proper policy container
+    GetWorkerThread()->FetchAndRunClassicScript(
+        script_url, std::move(worker_main_script_load_params),
+        /*policy_container=*/nullptr, outside_settings_object.CopyData(),
+        resource_timing_notifier, stack_id);
   } else if (options->type() == script_type_names::kModule) {
     // "module: Fetch a module worker script graph given url, outside settings,
     // destination, the value of the credentials member of options, and inside
diff --git a/third_party/blink/renderer/core/workers/dedicated_worker_test.cc b/third_party/blink/renderer/core/workers/dedicated_worker_test.cc
index 6666696..3c7217f 100644
--- a/third_party/blink/renderer/core/workers/dedicated_worker_test.cc
+++ b/third_party/blink/renderer/core/workers/dedicated_worker_test.cc
@@ -15,7 +15,6 @@
 #include "base/test/bind.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
-#include "third_party/blink/public/common/features.h"
 #include "third_party/blink/public/mojom/v8_cache_options.mojom-blink.h"
 #include "third_party/blink/public/mojom/worker/dedicated_worker_host.mojom-blink.h"
 #include "third_party/blink/public/platform/task_type.h"
@@ -313,14 +312,13 @@
             WorkerBackingThreadStartupData::AtomicsWaitMode::kAllow),
         WorkerObjectProxy().token());
 
-    if (base::FeatureList::IsEnabled(features::kPlzDedicatedWorker)) {
-      PostCrossThreadTask(
-          *GetDedicatedWorkerThread()->GetTaskRunner(TaskType::kInternalTest),
-          FROM_HERE,
-          CrossThreadBindOnce(
-              &DedicatedWorkerThreadForTest::InitializeGlobalScope,
-              CrossThreadUnretained(GetDedicatedWorkerThread()), script_url_));
-    }
+    PostCrossThreadTask(
+        *GetDedicatedWorkerThread()->GetTaskRunner(TaskType::kInternalTest),
+        FROM_HERE,
+        CrossThreadBindOnce(
+            &DedicatedWorkerThreadForTest::InitializeGlobalScope,
+            CrossThreadUnretained(GetDedicatedWorkerThread()), script_url_));
+
   }
 
   void EvaluateClassicScript(const String& source) {
diff --git a/third_party/blink/renderer/core/workers/worker_or_worklet_global_scope.cc b/third_party/blink/renderer/core/workers/worker_or_worklet_global_scope.cc
index 0d9c84b..78bcbea 100644
--- a/third_party/blink/renderer/core/workers/worker_or_worklet_global_scope.cc
+++ b/third_party/blink/renderer/core/workers/worker_or_worklet_global_scope.cc
@@ -586,13 +586,6 @@
   // parser metadata is "not-parser-inserted,
   ParserDisposition parser_state = kNotParserInserted;
 
-  RejectCoepUnsafeNone reject_coep_unsafe_none(false);
-  if (ShouldRejectCoepUnsafeNoneTopModuleScript() &&
-      destination == network::mojom::RequestDestination::kWorker) {
-    DCHECK(!base::FeatureList::IsEnabled(features::kPlzDedicatedWorker));
-    reject_coep_unsafe_none = RejectCoepUnsafeNone(true);
-  }
-
   // credentials mode is credentials mode, and referrer policy is the empty
   // string.
   // Module worker scripts are fetched with fetchpriority kAuto.
@@ -600,7 +593,7 @@
       nonce, IntegrityMetadataSet(), integrity_attribute, parser_state,
       credentials_mode, network::mojom::ReferrerPolicy::kDefault,
       mojom::blink::FetchPriorityHint::kAuto,
-      RenderBlockingBehavior::kNonBlocking, reject_coep_unsafe_none);
+      RenderBlockingBehavior::kNonBlocking);
 
   Modulator* modulator = Modulator::From(ScriptController()->GetScriptState());
   // Step 3. "Perform the internal module script graph fetching procedure ..."
diff --git a/third_party/blink/renderer/core/workers/worker_or_worklet_global_scope.h b/third_party/blink/renderer/core/workers/worker_or_worklet_global_scope.h
index cfe2005..cfa740b 100644
--- a/third_party/blink/renderer/core/workers/worker_or_worklet_global_scope.h
+++ b/third_party/blink/renderer/core/workers/worker_or_worklet_global_scope.h
@@ -124,7 +124,8 @@
 
   // Returns true when we should reject a response without
   // cross-origin-embedder-policy: require-corp.
-  // TODO(crbug.com/1064920): Remove this once PlzDedicatedWorker ships.
+  // TODO(crbug.com/1064920): Remove this now that PlzDedicatedWorker has
+  // shipped.
   virtual RejectCoepUnsafeNone ShouldRejectCoepUnsafeNoneTopModuleScript()
       const {
     return RejectCoepUnsafeNone(false);
diff --git a/third_party/blink/renderer/modules/ai/exception_helpers.cc b/third_party/blink/renderer/modules/ai/exception_helpers.cc
index bf4d4c7..ff8ad711 100644
--- a/third_party/blink/renderer/modules/ai/exception_helpers.cc
+++ b/third_party/blink/renderer/modules/ai/exception_helpers.cc
@@ -60,6 +60,9 @@
     "initialPrompts.";
 const char kExceptionMessageUnsupportedLanguages[] =
     "The specified languages are not supported.";
+const char kExceptionMessageInvalidResponseJsonSchema[] =
+    "Response json schema is invalid - it should be an object that can be "
+    "stringified into a JSON string.";
 
 void ThrowInvalidContextException(ExceptionState& exception_state) {
   exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError,
@@ -105,6 +108,23 @@
   return false;
 }
 
+String ValidateAndStringifyObject(const ScriptValue& input,
+                                  ScriptState* script_state,
+                                  ExceptionState& exception_state) {
+  v8::Local<v8::String> value;
+  if (!input.V8Value()->IsObject() ||
+      !v8::JSON::Stringify(script_state->GetContext(),
+                           input.V8Value().As<v8::Object>())
+           .ToLocal(&value)) {
+    exception_state.ThrowDOMException(
+        DOMExceptionCode::kNotSupportedError,
+        kExceptionMessageInvalidResponseJsonSchema);
+    return WTF::String();
+  }
+  return ToBlinkString<String>(script_state->GetIsolate(), value,
+                               kDoNotExternalize);
+}
+
 namespace {
 // Create an UnknownError exception, include `error` in the exception
 // message. This is intended for handling values of
diff --git a/third_party/blink/renderer/modules/ai/exception_helpers.h b/third_party/blink/renderer/modules/ai/exception_helpers.h
index b16251f..3363ae2 100644
--- a/third_party/blink/renderer/modules/ai/exception_helpers.h
+++ b/third_party/blink/renderer/modules/ai/exception_helpers.h
@@ -27,6 +27,7 @@
 extern const char kExceptionMessageSystemPromptIsDefinedMultipleTimes[];
 extern const char kExceptionMessageSystemPromptIsNotTheFirst[];
 extern const char kExceptionMessageUnsupportedLanguages[];
+extern const char kExceptionMessageInvalidResponseJsonSchema[];
 
 void ThrowInvalidContextException(ExceptionState& exception_state);
 void ThrowSessionDestroyedException(ExceptionState& exception_state);
@@ -49,6 +50,12 @@
                        ScriptState* script_state,
                        ExceptionState& exception_state);
 
+// Validates and stringifies the responseJSONSchema option if provided.
+// Throws an exception if an unsupported schema is detected.
+String ValidateAndStringifyObject(const ScriptValue& input,
+                                  ScriptState* script_state,
+                                  ExceptionState& exception_state);
+
 }  // namespace blink
 
 #endif  // THIRD_PARTY_BLINK_RENDERER_MODULES_AI_EXCEPTION_HELPERS_H_
diff --git a/third_party/blink/renderer/modules/ai/language_model.cc b/third_party/blink/renderer/modules/ai/language_model.cc
index e5957af..68bf245 100644
--- a/third_party/blink/renderer/modules/ai/language_model.cc
+++ b/third_party/blink/renderer/modules/ai/language_model.cc
@@ -37,6 +37,7 @@
 #include "third_party/blink/renderer/modules/webaudio/audio_buffer.h"
 #include "third_party/blink/renderer/platform/audio/audio_bus.h"
 #include "third_party/blink/renderer/platform/bindings/script_state.h"
+#include "third_party/blink/renderer/platform/bindings/to_blink_string.h"
 #include "third_party/blink/renderer/platform/blob/blob_data.h"
 #include "third_party/blink/renderer/platform/heap/garbage_collected.h"
 #include "third_party/blink/renderer/platform/heap/persistent.h"
@@ -476,6 +477,19 @@
     return promise;
   }
 
+  String stringified_schema;
+  if (RuntimeEnabledFeatures::AIPromptAPIStructuredOutputEnabled()) {
+    if (options->hasResponseJSONSchema()) {
+      stringified_schema = ValidateAndStringifyObject(
+          options->responseJSONSchema(), script_state, exception_state);
+      if (!stringified_schema) {
+        // ValidateAndStringifyObject throws an exception, which automatically
+        // rejects the promise, when it returns a null string.
+        return promise;
+      }
+    }
+  }
+
   auto pending_remote = CreateModelExecutionResponder(
       script_state, signal, resolver, task_runner_,
       AIMetrics::AISessionType::kLanguageModel,
@@ -484,6 +498,7 @@
       WTF::BindRepeating(&LanguageModel::OnQuotaOverflow,
                          WrapWeakPersistent(this)));
   language_model_remote_->Prompt(std::move(prompts).value(),
+                                 std::move(stringified_schema),
                                  std::move(pending_remote));
   return promise;
 }
@@ -537,6 +552,19 @@
     return nullptr;
   }
 
+  String stringified_schema;
+  if (RuntimeEnabledFeatures::AIPromptAPIStructuredOutputEnabled()) {
+    if (options->hasResponseJSONSchema()) {
+      stringified_schema = ValidateAndStringifyObject(
+          options->responseJSONSchema(), script_state, exception_state);
+      if (!stringified_schema) {
+        // ValidateAndStringifyObject throws an exception when it returns
+        // nullopt.
+        return nullptr;
+      }
+    }
+  }
+
   auto [readable_stream, pending_remote] =
       CreateModelExecutionStreamingResponder(
           script_state, signal, task_runner_,
@@ -547,6 +575,7 @@
                              WrapWeakPersistent(this)));
 
   language_model_remote_->Prompt(std::move(prompts).value(),
+                                 std::move(stringified_schema),
                                  std::move(pending_remote));
   return readable_stream;
 }
diff --git a/third_party/blink/renderer/modules/ai/language_model.idl b/third_party/blink/renderer/modules/ai/language_model.idl
index a85be3d5..e6f55677 100644
--- a/third_party/blink/renderer/modules/ai/language_model.idl
+++ b/third_party/blink/renderer/modules/ai/language_model.idl
@@ -9,6 +9,7 @@
 };
 
 dictionary LanguageModelPromptOptions {
+  object responseJSONSchema;
   AbortSignal signal;
 };
 
diff --git a/third_party/blink/renderer/modules/canvas/canvas2d/base_rendering_context_2d.cc b/third_party/blink/renderer/modules/canvas/canvas2d/base_rendering_context_2d.cc
index 65631e206..834e4b45 100644
--- a/third_party/blink/renderer/modules/canvas/canvas2d/base_rendering_context_2d.cc
+++ b/third_party/blink/renderer/modules/canvas/canvas2d/base_rendering_context_2d.cc
@@ -1465,7 +1465,7 @@
 
   // If that did not work (e.g., the canvas host does not yet exist), we can
   // return the preferred canvas format.
-  return FromDawnEnum(GPU::preferred_canvas_format());
+  return FromDawnEnum(GPU::GetPreferredCanvasFormat());
 }
 
 GPUTexture* BaseRenderingContext2D::transferToGPUTexture(
@@ -1651,7 +1651,7 @@
 
   // If the caller explicitly destroyed the WebGPU access texture, there is
   // nothing to transfer.
-  if (webgpu_access_texture_->Destroyed()) {
+  if (webgpu_access_texture_->IsDestroyed()) {
     exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError,
                                       "The texture has been destroyed.");
     webgpu_access_texture_ = nullptr;
diff --git a/third_party/blink/renderer/modules/credentialmanagement/authentication_credentials_container.cc b/third_party/blink/renderer/modules/credentialmanagement/authentication_credentials_container.cc
index 75189b5..05bd35e 100644
--- a/third_party/blink/renderer/modules/credentialmanagement/authentication_credentials_container.cc
+++ b/third_party/blink/renderer/modules/credentialmanagement/authentication_credentials_container.cc
@@ -2267,20 +2267,6 @@
 
   mojom::blink::RpMode rp_mode = mojom::blink::RpMode::kPassive;
   auto v8_rp_mode = identity_options.mode();
-  // TODO(crbug.com/372198646): remove the debugging aid enums after shipping
-  // active mode.
-  if (v8_rp_mode ==
-          blink::V8IdentityCredentialRequestOptionsMode::Enum::kWidget ||
-      v8_rp_mode ==
-          blink::V8IdentityCredentialRequestOptionsMode::Enum::kButton) {
-    resolver->GetExecutionContext()->AddConsoleMessage(
-        MakeGarbageCollected<ConsoleMessage>(
-            mojom::blink::ConsoleMessageSource::kJavaScript,
-            mojom::blink::ConsoleMessageLevel::kWarning,
-            "The mode button/widget are renamed to active/passive "
-            "respectively and will be deprecated soon."));
-  }
-
   rp_mode = mojo::ConvertTo<mojom::blink::RpMode>(v8_rp_mode);
   if (rp_mode == mojom::blink::RpMode::kActive) {
     if (identity_provider_ptrs.size() > 1u) {
diff --git a/third_party/blink/renderer/modules/credentialmanagement/credential_manager_type_converters.cc b/third_party/blink/renderer/modules/credentialmanagement/credential_manager_type_converters.cc
index dfd1369..fc0e601 100644
--- a/third_party/blink/renderer/modules/credentialmanagement/credential_manager_type_converters.cc
+++ b/third_party/blink/renderer/modules/credentialmanagement/credential_manager_type_converters.cc
@@ -1041,10 +1041,6 @@
       return RpMode::kPassive;
     case blink::V8IdentityCredentialRequestOptionsMode::Enum::kActive:
       return RpMode::kActive;
-    case blink::V8IdentityCredentialRequestOptionsMode::Enum::kWidget:
-      return RpMode::kPassive;
-    case blink::V8IdentityCredentialRequestOptionsMode::Enum::kButton:
-      return RpMode::kActive;
   }
 }
 
diff --git a/third_party/blink/renderer/modules/credentialmanagement/identity_credential_request_options.idl b/third_party/blink/renderer/modules/credentialmanagement/identity_credential_request_options.idl
index 1a61d26..d0942cd 100644
--- a/third_party/blink/renderer/modules/credentialmanagement/identity_credential_request_options.idl
+++ b/third_party/blink/renderer/modules/credentialmanagement/identity_credential_request_options.idl
@@ -12,10 +12,7 @@
 
 enum IdentityCredentialRequestOptionsMode {
   "active",
-  "passive",
-  // TODO(crbug.com/372198646): remove the debugging aid enums after shipping active mode.
-  "button",
-  "widget"
+  "passive"
 };
 
 // https://fedidcg.github.io/FedCM/#dictdef-identitycredentialrequestoptions
diff --git a/third_party/blink/renderer/modules/service_worker/service_worker_global_scope.cc b/third_party/blink/renderer/modules/service_worker/service_worker_global_scope.cc
index 311a1b3..3547d82 100644
--- a/third_party/blink/renderer/modules/service_worker/service_worker_global_scope.cc
+++ b/third_party/blink/renderer/modules/service_worker/service_worker_global_scope.cc
@@ -48,7 +48,6 @@
 #include "services/network/public/mojom/cookie_manager.mojom-blink.h"
 #include "services/network/public/mojom/cross_origin_embedder_policy.mojom.h"
 #include "services/network/public/mojom/url_loader_factory.mojom-blink.h"
-#include "third_party/blink/public/common/features.h"
 #include "third_party/blink/public/mojom/fetch/fetch_api_request.mojom-blink.h"
 #include "third_party/blink/public/mojom/notifications/notification.mojom-blink.h"
 #include "third_party/blink/public/mojom/push_messaging/push_messaging.mojom-blink.h"
@@ -1551,8 +1550,8 @@
   // main resource load -> only resultingClientId.
   // sub resource load -> only clientId.
   // worker script load -> only clientId. (treated as subresource)
-  // * PlzDecicatedWorker makes this as main resource load.
-  //   We should fix this.
+  // * TODO(crbug.com/1064920): PlzDedicatedWorker makes this as main resource
+  //   load. We should fix this.
   //
   // Expected behavior:
   // main resource load -> clientId and resultingClientId.
@@ -1572,7 +1571,6 @@
     if (is_main_resource_load &&
         params->request->destination ==
             network::mojom::RequestDestination::kWorker) {
-      CHECK(base::FeatureList::IsEnabled(features::kPlzDedicatedWorker));
       is_main_resource_load = false;
     }
     event_init->setClientId(is_main_resource_load ? String()
diff --git a/third_party/blink/renderer/modules/webgl/webgl_rendering_context_base.cc b/third_party/blink/renderer/modules/webgl/webgl_rendering_context_base.cc
index ee7c32c..c439247 100644
--- a/third_party/blink/renderer/modules/webgl/webgl_rendering_context_base.cc
+++ b/third_party/blink/renderer/modules/webgl/webgl_rendering_context_base.cc
@@ -293,7 +293,7 @@
 }
 
 gfx::Rect WebGLRenderingContextBase::TexImageParams::GetSourceRect(
-    gfx::Size source_size) {
+    gfx::Size source_size) const {
   gfx::Rect rect(unpack_skip_pixels, unpack_skip_rows,
                  width.value_or(source_size.width()),
                  height.value_or(source_size.height()));
@@ -318,10 +318,15 @@
 }
 
 GrSurfaceOrigin
-WebGLRenderingContextBase::TexImageParams::GetDestinationOrigin() {
+WebGLRenderingContextBase::TexImageParams::GetDestinationOrigin() const {
   return unpack_flip_y ? kBottomLeft_GrSurfaceOrigin : kTopLeft_GrSurfaceOrigin;
 }
 
+SkAlphaType WebGLRenderingContextBase::TexImageParams::GetDestinationAlphaType()
+    const {
+  return unpack_premultiply_alpha ? kPremul_SkAlphaType : kUnpremul_SkAlphaType;
+}
+
 void WebGLRenderingContextBase::InitializeWebGLContextLimits(
     WebGraphicsContext3DProvider* context_provider) {
   base::AutoLock locker(WebGLContextLimitLock());
@@ -5350,9 +5355,8 @@
     // during readPixels, if needed. If the input is opaque, do not change it
     // (readPixels fails if the source is opaque and the destination is not).
     if (converted_info.alphaType() != kOpaque_SkAlphaType) {
-      converted_info = converted_info.makeAlphaType(
-          params.unpack_premultiply_alpha ? kPremul_SkAlphaType
-                                          : kUnpremul_SkAlphaType);
+      converted_info =
+          converted_info.makeAlphaType(params.GetDestinationAlphaType());
     }
 
     // Set the color type to perform pixel format conversion during readPixels,
@@ -5393,11 +5397,11 @@
   // the source and the requested premultiplication format.
   WebGLImageConversion::AlphaOp alpha_op =
       WebGLImageConversion::kAlphaDoNothing;
-  if (params.unpack_premultiply_alpha &&
+  if (params.GetDestinationAlphaType() == kPremul_SkAlphaType &&
       pixmap.alphaType() == kUnpremul_SkAlphaType) {
     alpha_op = WebGLImageConversion::kAlphaDoPremultiply;
   }
-  if (!params.unpack_premultiply_alpha &&
+  if (params.GetDestinationAlphaType() == kUnpremul_SkAlphaType &&
       pixmap.alphaType() == kPremul_SkAlphaType) {
     alpha_op = WebGLImageConversion::kAlphaDoUnmultiply;
   }
@@ -5842,7 +5846,7 @@
   }
 
   ImageExtractor image_extractor(
-      image_for_render.get(), params.unpack_premultiply_alpha,
+      image_for_render.get(), params.GetDestinationAlphaType(),
       params.unpack_colorspace_conversion
           ? PredefinedColorSpaceToSkColorSpace(unpack_color_space_)
           : nullptr);
@@ -5984,14 +5988,14 @@
     if (source_image) {
       source_image->CopyToTexture(
           ContextGL(), params.target, target_texture, params.level,
-          params.unpack_premultiply_alpha, params.GetDestinationOrigin(),
+          params.GetDestinationAlphaType(), params.GetDestinationOrigin(),
           gfx::Point(params.xoffset, params.yoffset), source_sub_rectangle);
     } else {
       WebGLRenderingContextBase* gl = source_canvas_webgl_context;
       ScopedTexture2DRestorer inner_restorer(gl);
       if (!gl->GetDrawingBuffer()->CopyToPlatformTexture(
               ContextGL(), params.target, target_texture, params.level,
-              params.unpack_premultiply_alpha, params.GetDestinationOrigin(),
+              params.GetDestinationAlphaType(), params.GetDestinationOrigin(),
               gfx::Point(params.xoffset, params.yoffset), source_sub_rectangle,
               kBackBuffer)) {
         NOTREACHED();
diff --git a/third_party/blink/renderer/modules/webgl/webgl_rendering_context_base.h b/third_party/blink/renderer/modules/webgl/webgl_rendering_context_base.h
index d2ee763a..9d3e103 100644
--- a/third_party/blink/renderer/modules/webgl/webgl_rendering_context_base.h
+++ b/third_party/blink/renderer/modules/webgl/webgl_rendering_context_base.h
@@ -1256,14 +1256,17 @@
 
     // Returns sub rect of the source based on `unpack_*` params above. This
     // rect is always in top-left coordinate space.
-    gfx::Rect GetSourceRect(gfx::Size source_size);
+    gfx::Rect GetSourceRect(gfx::Size source_size) const;
 
     // Returns GrSurfaceOrigin of the destination texture for this operation.
     // Note, that textures don't have persistent orientation (e.g unlike
     // SharedImages), so this value doesn't affect the data in the texture
     // before the operation, only operation itself. This is helper because
     // everything else operates in terms of GrSurfaceOrigin.
-    GrSurfaceOrigin GetDestinationOrigin();
+    GrSurfaceOrigin GetDestinationOrigin() const;
+
+    // Returns desired alpha type of the uploaded data.
+    SkAlphaType GetDestinationAlphaType() const;
 
     // The source's height for 3D copies. If we are doing a 3D copy, then we
     // interpret the 2D source as 3D by treating it as vertical sequence of
diff --git a/third_party/blink/renderer/modules/webgpu/dawn_object.cc b/third_party/blink/renderer/modules/webgpu/dawn_object.cc
index 0879b77a..bdc6a43 100644
--- a/third_party/blink/renderer/modules/webgpu/dawn_object.cc
+++ b/third_party/blink/renderer/modules/webgpu/dawn_object.cc
@@ -24,7 +24,7 @@
 
 void DawnObjectBase::setLabel(const String& value) {
   label_ = value;
-  setLabelImpl(value);
+  SetLabelImpl(value);
 }
 
 void DawnObjectBase::EnsureFlush(scheduler::EventLoop& event_loop) {
diff --git a/third_party/blink/renderer/modules/webgpu/dawn_object.h b/third_party/blink/renderer/modules/webgpu/dawn_object.h
index 5106afcc..b9fdd478 100644
--- a/third_party/blink/renderer/modules/webgpu/dawn_object.h
+++ b/third_party/blink/renderer/modules/webgpu/dawn_object.h
@@ -57,7 +57,7 @@
   const String& label() const { return label_; }
   void setLabel(const String& value);
 
-  virtual void setLabelImpl(const String& value) = 0;
+  virtual void SetLabelImpl(const String& value) = 0;
 
  private:
   scoped_refptr<DawnControlClientHolder> dawn_control_client_;
diff --git a/third_party/blink/renderer/modules/webgpu/gpu.cc b/third_party/blink/renderer/modules/webgpu/gpu.cc
index 5a5fcde..16956221 100644
--- a/third_party/blink/renderer/modules/webgpu/gpu.cc
+++ b/third_party/blink/renderer/modules/webgpu/gpu.cc
@@ -449,10 +449,10 @@
 }
 
 V8GPUTextureFormat GPU::getPreferredCanvasFormat() {
-  return FromDawnEnum(preferred_canvas_format());
+  return FromDawnEnum(GetPreferredCanvasFormat());
 }
 
-wgpu::TextureFormat GPU::preferred_canvas_format() {
+wgpu::TextureFormat GPU::GetPreferredCanvasFormat() {
 #if BUILDFLAG(IS_ANDROID)
   return wgpu::TextureFormat::RGBA8Unorm;
 #else
diff --git a/third_party/blink/renderer/modules/webgpu/gpu.h b/third_party/blink/renderer/modules/webgpu/gpu.h
index dbee3cc..e953b76 100644
--- a/third_party/blink/renderer/modules/webgpu/gpu.h
+++ b/third_party/blink/renderer/modules/webgpu/gpu.h
@@ -81,14 +81,15 @@
   // ExecutionContextLifecycleObserver overrides
   void ContextDestroyed() override;
 
-  // gpu.idl
+  // gpu.idl {{{
   ScriptPromise<IDLNullable<GPUAdapter>> requestAdapter(
       ScriptState* script_state,
       const GPURequestAdapterOptions* options);
   V8GPUTextureFormat getPreferredCanvasFormat();
   WGSLLanguageFeatures* wgslLanguageFeatures() const;
+  // }}} End of WebIDL binding implementation.
 
-  static wgpu::TextureFormat preferred_canvas_format();
+  static wgpu::TextureFormat GetPreferredCanvasFormat();
 
   // Store the buffer in a weak hash set so we can destroy it when the
   // context is destroyed.
@@ -97,7 +98,7 @@
   // destroyed.
   void UntrackMappableBuffer(GPUBuffer* buffer);
 
-  BoxedMappableWGPUBufferHandles* mappable_buffer_handles() const {
+  BoxedMappableWGPUBufferHandles* GetMappableBufferHandles() const {
     return mappable_buffer_handles_.get();
   }
 
diff --git a/third_party/blink/renderer/modules/webgpu/gpu_adapter.cc b/third_party/blink/renderer/modules/webgpu/gpu_adapter.cc
index 0e5a9862..b0b2ff1 100644
--- a/third_party/blink/renderer/modules/webgpu/gpu_adapter.cc
+++ b/third_party/blink/renderer/modules/webgpu/gpu_adapter.cc
@@ -283,7 +283,7 @@
     HashSet<wgpu::FeatureName> required_features_set;
     for (const V8GPUFeatureName& f : descriptor->requiredFeatures()) {
       // If the feature is not a valid feature reject with a type error.
-      if (!features_->has(f.AsEnum())) {
+      if (!features_->Has(f.AsEnum())) {
         resolver->RejectWithTypeError(
             String::Format("Unsupported feature: %s", f.AsCStr()));
         return promise;
diff --git a/third_party/blink/renderer/modules/webgpu/gpu_adapter.h b/third_party/blink/renderer/modules/webgpu/gpu_adapter.h
index 21fe98f..aaf3570 100644
--- a/third_party/blink/renderer/modules/webgpu/gpu_adapter.h
+++ b/third_party/blink/renderer/modules/webgpu/gpu_adapter.h
@@ -59,7 +59,7 @@
 
   GPUAdapterInfo* CreateAdapterInfoForAdapter();
 
-  bool isXRCompatible() const { return is_xr_compatible_; }
+  bool IsXRCompatible() const { return is_xr_compatible_; }
 
  private:
   void OnRequestDeviceCallback(GPUDevice* device,
@@ -69,7 +69,7 @@
                                wgpu::Device dawn_device,
                                wgpu::StringView error_message);
 
-  void setLabelImpl(const String&) override {
+  void SetLabelImpl(const String&) override {
     // There isn't a wgpu::Adapter::SetLabel, just skip.
   }
 
diff --git a/third_party/blink/renderer/modules/webgpu/gpu_adapter_info.h b/third_party/blink/renderer/modules/webgpu/gpu_adapter_info.h
index 78ebedce..2d76a57 100644
--- a/third_party/blink/renderer/modules/webgpu/gpu_adapter_info.h
+++ b/third_party/blink/renderer/modules/webgpu/gpu_adapter_info.h
@@ -38,7 +38,7 @@
 
   void AppendMemoryHeapInfo(GPUMemoryHeapInfo*);
 
-  // gpu_adapter_info.idl
+  // gpu_adapter_info.idl {{{
   const String& vendor() const;
   const String& architecture() const;
   const String& device() const;
@@ -52,6 +52,7 @@
   const HeapVector<Member<GPUMemoryHeapInfo>>& memoryHeaps() const;
   const std::optional<uint32_t>& d3dShaderModel() const;
   const std::optional<uint32_t>& vkDriverVersion() const;
+  // }}} End of WebIDL binding implementation.
 
   void Trace(Visitor*) const override;
 
diff --git a/third_party/blink/renderer/modules/webgpu/gpu_bind_group.h b/third_party/blink/renderer/modules/webgpu/gpu_bind_group.h
index 3de0b6c2..bef615b 100644
--- a/third_party/blink/renderer/modules/webgpu/gpu_bind_group.h
+++ b/third_party/blink/renderer/modules/webgpu/gpu_bind_group.h
@@ -26,7 +26,7 @@
   GPUBindGroup(const GPUBindGroup&) = delete;
   GPUBindGroup& operator=(const GPUBindGroup&) = delete;
 
-  void setLabelImpl(const String& value) override {
+  void SetLabelImpl(const String& value) override {
     std::string utf8_label = value.Utf8();
     GetHandle().SetLabel(utf8_label.c_str());
   }
diff --git a/third_party/blink/renderer/modules/webgpu/gpu_bind_group_layout.h b/third_party/blink/renderer/modules/webgpu/gpu_bind_group_layout.h
index 641683a6..b260a71 100644
--- a/third_party/blink/renderer/modules/webgpu/gpu_bind_group_layout.h
+++ b/third_party/blink/renderer/modules/webgpu/gpu_bind_group_layout.h
@@ -27,7 +27,7 @@
   GPUBindGroupLayout(const GPUBindGroupLayout&) = delete;
   GPUBindGroupLayout& operator=(const GPUBindGroupLayout&) = delete;
 
-  void setLabelImpl(const String& value) override {
+  void SetLabelImpl(const String& value) override {
     std::string utf8_label = value.Utf8();
     GetHandle().SetLabel(utf8_label.c_str());
   }
diff --git a/third_party/blink/renderer/modules/webgpu/gpu_buffer.cc b/third_party/blink/renderer/modules/webgpu/gpu_buffer.cc
index efca1fa..a3d1ef2 100644
--- a/third_party/blink/renderer/modules/webgpu/gpu_buffer.cc
+++ b/third_party/blink/renderer/modules/webgpu/gpu_buffer.cc
@@ -159,7 +159,7 @@
     GPU* gpu = device->adapter()->gpu();
     gpu->TrackMappableBuffer(buffer);
     device->TrackMappableBuffer(buffer);
-    buffer->mappable_buffer_handles_ = gpu->mappable_buffer_handles();
+    buffer->mappable_buffer_handles_ = gpu->GetMappableBufferHandles();
   }
 
   return buffer;
diff --git a/third_party/blink/renderer/modules/webgpu/gpu_buffer.h b/third_party/blink/renderer/modules/webgpu/gpu_buffer.h
index 374dfed..7766688 100644
--- a/third_party/blink/renderer/modules/webgpu/gpu_buffer.h
+++ b/third_party/blink/renderer/modules/webgpu/gpu_buffer.h
@@ -40,7 +40,7 @@
 
   void Trace(Visitor* visitor) const override;
 
-  // gpu_buffer.idl
+  // gpu_buffer.idl {{{
   ScriptPromise<IDLUndefined> mapAsync(ScriptState* script_state,
                                        uint32_t mode,
                                        uint64_t offset,
@@ -62,6 +62,7 @@
   uint64_t size() const;
   uint32_t usage() const;
   V8GPUBufferMapState mapState() const;
+  // }}} End of WebIDL binding implementation.
 
   void DetachMappedArrayBuffers(v8::Isolate* isolate);
 
@@ -85,7 +86,7 @@
                                                  size_t data_length);
   void ResetMappingState(v8::Isolate* isolate);
 
-  void setLabelImpl(const String& value) override {
+  void SetLabelImpl(const String& value) override {
     std::string utf8_label = value.Utf8();
     GetHandle().SetLabel(utf8_label.c_str());
   }
diff --git a/third_party/blink/renderer/modules/webgpu/gpu_buffer_usage.h b/third_party/blink/renderer/modules/webgpu/gpu_buffer_usage.h
index 67d1dec..8be1d50d 100644
--- a/third_party/blink/renderer/modules/webgpu/gpu_buffer_usage.h
+++ b/third_party/blink/renderer/modules/webgpu/gpu_buffer_usage.h
@@ -14,19 +14,6 @@
   DEFINE_WRAPPERTYPEINFO();
 
  public:
-  // gpu_buffer_usage.idl
-  static constexpr uint32_t kMapRead = V8GPUBufferUsage::Constant::kMapRead;
-  static constexpr uint32_t kMapWrite = V8GPUBufferUsage::Constant::kMapWrite;
-  static constexpr uint32_t kCopySrc = V8GPUBufferUsage::Constant::kCopySrc;
-  static constexpr uint32_t kCopyDst = V8GPUBufferUsage::Constant::kCopyDst;
-  static constexpr uint32_t kIndex = V8GPUBufferUsage::Constant::kIndex;
-  static constexpr uint32_t kVertex = V8GPUBufferUsage::Constant::kVertex;
-  static constexpr uint32_t kUniform = V8GPUBufferUsage::Constant::kUniform;
-  static constexpr uint32_t kStorage = V8GPUBufferUsage::Constant::kStorage;
-  static constexpr uint32_t kIndirect = V8GPUBufferUsage::Constant::kIndirect;
-  static constexpr uint32_t kQueryResolve =
-      V8GPUBufferUsage::Constant::kQueryResolve;
-
   GPUBufferUsage(const GPUBufferUsage&) = delete;
   GPUBufferUsage& operator=(const GPUBufferUsage&) = delete;
 };
diff --git a/third_party/blink/renderer/modules/webgpu/gpu_canvas_context.cc b/third_party/blink/renderer/modules/webgpu/gpu_canvas_context.cc
index 76ff610..2cf7fac 100644
--- a/third_party/blink/renderer/modules/webgpu/gpu_canvas_context.cc
+++ b/third_party/blink/renderer/modules/webgpu/gpu_canvas_context.cc
@@ -167,12 +167,12 @@
     return nullptr;
   }
 
-  if (device_->destroyed()) {
+  if (device_->IsDestroyed()) {
     return MakeFallbackStaticBitmapImage(alpha_mode_);
   }
 
   // If there is a current texture, create a snapshot from it.
-  if (texture_ && !texture_->Destroyed()) {
+  if (texture_ && !texture_->IsDestroyed()) {
     return SnapshotInternal(texture_->GetHandle(), swap_buffers_->Size());
   } else if (swap_texture_) {
     return SnapshotInternal(swap_texture_->GetHandle(), swap_buffers_->Size());
@@ -208,7 +208,7 @@
     return false;
   }
 
-  if (device_->destroyed()) {
+  if (device_->IsDestroyed()) {
     SkColor4f color = alpha_mode_ == V8GPUCanvasAlphaMode::Enum::kOpaque
                           ? SkColors::kBlack
                           : SkColors::kTransparent;
@@ -336,7 +336,6 @@
           std::move(release_callback)));
 }
 
-// gpu_presentation_context.idl
 V8UnionHTMLCanvasElementOrOffscreenCanvas*
 GPUCanvasContext::getHTMLOrOffscreenCanvas() const {
   if (Host()->IsOffscreenCanvas()) {
@@ -442,9 +441,9 @@
   // about not using the preferred format.
   suppress_preferred_format_warning_ = false;
   if (copy_to_swap_texture_required_ &&
-      GPU::preferred_canvas_format() == wgpu::TextureFormat::BGRA8Unorm &&
+      GPU::GetPreferredCanvasFormat() == wgpu::TextureFormat::BGRA8Unorm &&
       texture_descriptor_.usage & wgpu::TextureUsage::StorageBinding &&
-      !device_->adapter()->features()->has(
+      !device_->adapter()->features()->Has(
           V8GPUFeatureName::Enum::kBgra8UnormStorage)) {
     suppress_preferred_format_warning_ = true;
   }
@@ -508,7 +507,7 @@
 
     // In cases where a copy is necessary the swap buffers will always use the
     // preferred canvas format.
-    swap_texture_descriptor_.format = GPU::preferred_canvas_format();
+    swap_texture_descriptor_.format = GPU::GetPreferredCanvasFormat();
 
     // The swap buffer texture doesn't need any view formats.
     swap_texture_descriptor_.viewFormats = nullptr;
@@ -758,7 +757,7 @@
   DCHECK(texture_);
   DCHECK(swap_texture_);
 
-  if (copy_to_swap_texture_required_ && texture_ && !texture_->Destroyed()) {
+  if (copy_to_swap_texture_required_ && texture_ && !texture_->IsDestroyed()) {
     CopyToSwapTexture();
     texture_->ClearBeforeDestroyCallback();
     texture_->destroy();
@@ -780,7 +779,7 @@
 }
 
 bool GPUCanvasContext::IsGPUDeviceDestroyed() {
-  return device_->destroyed();
+  return device_->IsDestroyed();
 }
 
 void GPUCanvasContext::CopyToSwapTexture() {
diff --git a/third_party/blink/renderer/modules/webgpu/gpu_canvas_context.h b/third_party/blink/renderer/modules/webgpu/gpu_canvas_context.h
index 2656ada..37cae74 100644
--- a/third_party/blink/renderer/modules/webgpu/gpu_canvas_context.h
+++ b/third_party/blink/renderer/modules/webgpu/gpu_canvas_context.h
@@ -98,13 +98,13 @@
     return false;
   }
 
-  // gpu_presentation_context.idl
+  // gpu_canvas_context.idl {{{
   V8UnionHTMLCanvasElementOrOffscreenCanvas* getHTMLOrOffscreenCanvas() const;
-
   void configure(const GPUCanvasConfiguration* descriptor, ExceptionState&);
   void unconfigure();
   GPUCanvasConfiguration* getConfiguration();
   GPUTexture* getCurrentTexture(ScriptState*, ExceptionState&);
+  // }}} End of WebIDL binding implementation.
 
   // WebGPUSwapBufferProvider::Client implementation
   void OnTextureTransferred() override;
diff --git a/third_party/blink/renderer/modules/webgpu/gpu_color_write.h b/third_party/blink/renderer/modules/webgpu/gpu_color_write.h
index 43d2f9e3..33ae70f1 100644
--- a/third_party/blink/renderer/modules/webgpu/gpu_color_write.h
+++ b/third_party/blink/renderer/modules/webgpu/gpu_color_write.h
@@ -14,13 +14,6 @@
   DEFINE_WRAPPERTYPEINFO();
 
  public:
-  // gpu_color_write.idl
-  static constexpr uint32_t kRed = V8GPUColorWrite::Constant::kRed;
-  static constexpr uint32_t kGreen = V8GPUColorWrite::Constant::kGreen;
-  static constexpr uint32_t kBlue = V8GPUColorWrite::Constant::kBlue;
-  static constexpr uint32_t kAlpha = V8GPUColorWrite::Constant::kAlpha;
-  static constexpr uint32_t kAll = V8GPUColorWrite::Constant::kAll;
-
   GPUColorWrite(const GPUColorWrite&) = delete;
   GPUColorWrite& operator=(const GPUColorWrite&) = delete;
 };
diff --git a/third_party/blink/renderer/modules/webgpu/gpu_command_buffer.h b/third_party/blink/renderer/modules/webgpu/gpu_command_buffer.h
index a12d0237..bc6c704 100644
--- a/third_party/blink/renderer/modules/webgpu/gpu_command_buffer.h
+++ b/third_party/blink/renderer/modules/webgpu/gpu_command_buffer.h
@@ -21,7 +21,7 @@
   GPUCommandBuffer& operator=(const GPUCommandBuffer&) = delete;
 
  private:
-  void setLabelImpl(const String& value) override {
+  void SetLabelImpl(const String& value) override {
     std::string utf8_label = value.Utf8();
     GetHandle().SetLabel(utf8_label.c_str());
   }
diff --git a/third_party/blink/renderer/modules/webgpu/gpu_command_encoder.cc b/third_party/blink/renderer/modules/webgpu/gpu_command_encoder.cc
index 06be734..55452b3 100644
--- a/third_party/blink/renderer/modules/webgpu/gpu_command_encoder.cc
+++ b/third_party/blink/renderer/modules/webgpu/gpu_command_encoder.cc
@@ -396,12 +396,12 @@
                                        ExceptionState& exception_state) {
   V8GPUFeatureName::Enum requiredFeatureEnum =
       V8GPUFeatureName::Enum::kTimestampQuery;
-  if (!device_->features()->has(requiredFeatureEnum)) {
+  if (!device_->features()->Has(requiredFeatureEnum)) {
     exception_state.ThrowTypeError(
         String::Format("Use of the writeTimestamp() method requires the '%s' "
                        "feature to be enabled on %s.",
                        V8GPUFeatureName(requiredFeatureEnum).AsCStr(),
-                       device_->formattedLabel().c_str()));
+                       device_->GetFormattedLabel().c_str()));
     return;
   }
   GetHandle().WriteTimestamp(querySet->GetHandle(), queryIndex);
diff --git a/third_party/blink/renderer/modules/webgpu/gpu_command_encoder.h b/third_party/blink/renderer/modules/webgpu/gpu_command_encoder.h
index 71ad9bc..5a52373 100644
--- a/third_party/blink/renderer/modules/webgpu/gpu_command_encoder.h
+++ b/third_party/blink/renderer/modules/webgpu/gpu_command_encoder.h
@@ -37,7 +37,7 @@
   GPUCommandEncoder(const GPUCommandEncoder&) = delete;
   GPUCommandEncoder& operator=(const GPUCommandEncoder&) = delete;
 
-  // gpu_command_encoder.idl
+  // gpu_command_encoder.idl {{{
   GPURenderPassEncoder* beginRenderPass(
       const GPURenderPassDescriptor* descriptor,
       ExceptionState& exception_state);
@@ -134,8 +134,9 @@
     GetHandle().ClearBuffer(buffer->GetHandle(), offset, size);
   }
   GPUCommandBuffer* finish(const GPUCommandBufferDescriptor* descriptor);
+  // }}} End of WebIDL binding implementation.
 
-  void setLabelImpl(const String& value) override {
+  void SetLabelImpl(const String& value) override {
     std::string utf8_label = value.Utf8();
     GetHandle().SetLabel(utf8_label.c_str());
   }
diff --git a/third_party/blink/renderer/modules/webgpu/gpu_compute_pass_encoder.cc b/third_party/blink/renderer/modules/webgpu/gpu_compute_pass_encoder.cc
index 5afccf10..54ae405 100644
--- a/third_party/blink/renderer/modules/webgpu/gpu_compute_pass_encoder.cc
+++ b/third_party/blink/renderer/modules/webgpu/gpu_compute_pass_encoder.cc
@@ -58,12 +58,12 @@
     ExceptionState& exception_state) {
   V8GPUFeatureName::Enum requiredFeatureEnum =
       V8GPUFeatureName::Enum::kChromiumExperimentalTimestampQueryInsidePasses;
-  if (!device_->features()->has(requiredFeatureEnum)) {
+  if (!device_->features()->Has(requiredFeatureEnum)) {
     exception_state.ThrowTypeError(String::Format(
         "Use of the writeTimestamp() method on compute pass requires the '%s' "
         "feature to be enabled on %s.",
         V8GPUFeatureName(requiredFeatureEnum).AsCStr(),
-        device_->formattedLabel().c_str()));
+        device_->GetFormattedLabel().c_str()));
     return;
   }
   GetHandle().WriteTimestamp(querySet->GetHandle(), queryIndex);
diff --git a/third_party/blink/renderer/modules/webgpu/gpu_compute_pass_encoder.h b/third_party/blink/renderer/modules/webgpu/gpu_compute_pass_encoder.h
index d41a99b..5dfdf89 100644
--- a/third_party/blink/renderer/modules/webgpu/gpu_compute_pass_encoder.h
+++ b/third_party/blink/renderer/modules/webgpu/gpu_compute_pass_encoder.h
@@ -25,7 +25,7 @@
   GPUComputePassEncoder(const GPUComputePassEncoder&) = delete;
   GPUComputePassEncoder& operator=(const GPUComputePassEncoder&) = delete;
 
-  // gpu_compute_pass_encoder.idl
+  // gpu_compute_pass_encoder.idl {{{
   void setBindGroup(uint32_t index,
                     const DawnObject<wgpu::BindGroup>* bindGroup) {
     GetHandle().SetBindGroup(
@@ -69,8 +69,9 @@
                       uint32_t queryIndex,
                       ExceptionState& exception_state);
   void end() { GetHandle().End(); }
+  // }}} End of WebIDL binding implementation.
 
-  void setLabelImpl(const String& value) override {
+  void SetLabelImpl(const String& value) override {
     std::string utf8_label = value.Utf8();
     GetHandle().SetLabel(utf8_label.c_str());
   }
diff --git a/third_party/blink/renderer/modules/webgpu/gpu_compute_pipeline.h b/third_party/blink/renderer/modules/webgpu/gpu_compute_pipeline.h
index 7b361b1f..354b25f2 100644
--- a/third_party/blink/renderer/modules/webgpu/gpu_compute_pipeline.h
+++ b/third_party/blink/renderer/modules/webgpu/gpu_compute_pipeline.h
@@ -36,7 +36,7 @@
 
   GPUBindGroupLayout* getBindGroupLayout(uint32_t index);
 
-  void setLabelImpl(const String& value) override {
+  void SetLabelImpl(const String& value) override {
     std::string utf8_label = value.Utf8();
     GetHandle().SetLabel(utf8_label.c_str());
   }
diff --git a/third_party/blink/renderer/modules/webgpu/gpu_device.cc b/third_party/blink/renderer/modules/webgpu/gpu_device.cc
index fff0854..32a55cc 100644
--- a/third_party/blink/renderer/modules/webgpu/gpu_device.cc
+++ b/third_party/blink/renderer/modules/webgpu/gpu_device.cc
@@ -196,6 +196,17 @@
   }
 }
 
+bool GPUDevice::IsDestroyed() const {
+  return destroyed_;
+}
+
+std::string GPUDevice::GetFormattedLabel() const {
+  std::string deviceLabel =
+      label().empty() ? "[Device]" : "[Device \"" + label().Utf8() + "\"]";
+
+  return deviceLabel;
+}
+
 void GPUDevice::InjectError(wgpu::ErrorType type, const char* message) {
   GetHandle().InjectError(type, message);
 }
@@ -203,9 +214,11 @@
 void GPUDevice::AddConsoleWarning(wgpu::StringView message) {
   AddConsoleWarning(StringFromASCIIAndUTF8(message));
 }
+
 void GPUDevice::AddConsoleWarning(const char* message) {
   AddConsoleWarning(StringFromASCIIAndUTF8(message));
 }
+
 void GPUDevice::AddConsoleWarning(const String& message) {
   ExecutionContext* execution_context = GetExecutionContext();
   if (execution_context && allowed_console_warnings_remaining_ > 0) {
@@ -237,7 +250,7 @@
         message =
             "WebGPU canvas configured with a different format than is "
             "preferred by this device (\"" +
-            FromDawnEnum(GPU::preferred_canvas_format()).AsString() +
+            FromDawnEnum(GPU::GetPreferredCanvasFormat()).AsString() +
             "\"). This requires an extra copy, which may impact performance.";
         break;
       case GPUSingletonWarning::kDepthKey:
@@ -274,7 +287,7 @@
 
   V8GPUFeatureName::Enum requiredFeatureEnum = requiredFeatureOptional.value();
 
-  if (features_->has(requiredFeatureEnum)) {
+  if (features_->Has(requiredFeatureEnum)) {
     return true;
   }
 
@@ -283,17 +296,10 @@
   exception_state.ThrowTypeError(String::Format(
       "Use of the '%s' texture format requires the '%s' feature "
       "to be enabled on %s.",
-      format.AsCStr(), requiredFeature.AsCStr(), formattedLabel().c_str()));
+      format.AsCStr(), requiredFeature.AsCStr(), GetFormattedLabel().c_str()));
   return false;
 }
 
-std::string GPUDevice::formattedLabel() const {
-  std::string deviceLabel =
-      label().empty() ? "[Device]" : "[Device \"" + label().Utf8() + "\"]";
-
-  return deviceLabel;
-}
-
 // Validates that any features required for the given blend factor are enabled
 // for this device. If not, throw a TypeError to ensure consistency with
 // browsers that haven't yet implemented the feature.
@@ -308,7 +314,7 @@
 
   V8GPUFeatureName::Enum requiredFeatureEnum = requiredFeatureOptional.value();
 
-  if (features_->has(requiredFeatureEnum)) {
+  if (features_->Has(requiredFeatureEnum)) {
     return true;
   }
 
@@ -318,7 +324,7 @@
       String::Format("Use of the '%s' blend factor requires the '%s' feature "
                      "to be enabled on %s.",
                      blend_factor.AsCStr(), requiredFeature.AsCStr(),
-                     formattedLabel().c_str()));
+                     GetFormattedLabel().c_str()));
   return false;
 }
 
@@ -496,10 +502,6 @@
   return queue_.Get();
 }
 
-bool GPUDevice::destroyed() const {
-  return destroyed_;
-}
-
 void GPUDevice::destroy(v8::Isolate* isolate) {
   destroyed_ = true;
   external_texture_cache_->Destroy();
@@ -638,14 +640,14 @@
   const V8GPUFeatureName::Enum kTimestampQueryInsidePasses =
       V8GPUFeatureName::Enum::kChromiumExperimentalTimestampQueryInsidePasses;
   if (descriptor->type() == V8GPUQueryType::Enum::kTimestamp &&
-      !features_->has(kTimestampQuery) &&
-      !features_->has(kTimestampQueryInsidePasses)) {
+      !features_->Has(kTimestampQuery) &&
+      !features_->Has(kTimestampQueryInsidePasses)) {
     exception_state.ThrowTypeError(
         String::Format("Use of timestamp queries requires the '%s' or '%s' "
                        "feature to be enabled on %s.",
                        V8GPUFeatureName(kTimestampQuery).AsCStr(),
                        V8GPUFeatureName(kTimestampQueryInsidePasses).AsCStr(),
-                       formattedLabel().c_str()));
+                       GetFormattedLabel().c_str()));
     return nullptr;
   }
   return GPUQuerySet::Create(this, descriptor);
diff --git a/third_party/blink/renderer/modules/webgpu/gpu_device.h b/third_party/blink/renderer/modules/webgpu/gpu_device.h
index 3931c28..c88db32 100644
--- a/third_party/blink/renderer/modules/webgpu/gpu_device.h
+++ b/third_party/blink/renderer/modules/webgpu/gpu_device.h
@@ -100,15 +100,15 @@
 
   void Trace(Visitor* visitor) const override;
 
-  // gpu_device.idl
+  // gpu_device.idl {{{
   GPUAdapter* adapter() const;
   GPUSupportedFeatures* features() const;
   GPUSupportedLimits* limits() const { return limits_.Get(); }
   GPUAdapterInfo* adapterInfo() const;
   ScriptPromise<GPUDeviceLostInfo> lost(ScriptState* script_state);
+  // }}} End of WebIDL binding implementation.
 
   GPUQueue* queue();
-  bool destroyed() const;
 
   void destroy(v8::Isolate* isolate);
 
@@ -164,6 +164,8 @@
   const AtomicString& InterfaceName() const override;
   ExecutionContext* GetExecutionContext() const override;
 
+  bool IsDestroyed() const;
+  std::string GetFormattedLabel() const;
   void InjectError(wgpu::ErrorType type, const char* message);
   void AddConsoleWarning(const String& message);
   void AddConsoleWarning(const char* message);
@@ -178,8 +180,6 @@
   bool ValidateBlendFactor(V8GPUBlendFactor blend_factor,
                            ExceptionState& exception_state);
 
-  std::string formattedLabel() const;
-
   // Store the buffer in a weak hash set so we can unmap it when the
   // device is destroyed.
   void TrackMappableBuffer(GPUBuffer* buffer);
@@ -230,7 +230,7 @@
       wgpu::ComputePipeline compute_pipeline,
       wgpu::StringView message);
 
-  void setLabelImpl(const String& value) override {
+  void SetLabelImpl(const String& value) override {
     std::string utf8_label = value.Utf8();
     GetHandle().SetLabel(utf8_label.c_str());
   }
diff --git a/third_party/blink/renderer/modules/webgpu/gpu_device_lost_info.h b/third_party/blink/renderer/modules/webgpu/gpu_device_lost_info.h
index 312d096..72976029 100644
--- a/third_party/blink/renderer/modules/webgpu/gpu_device_lost_info.h
+++ b/third_party/blink/renderer/modules/webgpu/gpu_device_lost_info.h
@@ -25,9 +25,10 @@
   GPUDeviceLostInfo(const GPUDeviceLostInfo&) = delete;
   GPUDeviceLostInfo& operator=(const GPUDeviceLostInfo&) = delete;
 
-  // gpu_device_lost_info.idl
+  // gpu_device_lost_info.idl {{{
   V8GPUDeviceLostReason reason() const;
   const String& message() const;
+  // }}} End of WebIDL binding implementation.
 
  private:
   V8GPUDeviceLostReason::Enum reason_;
diff --git a/third_party/blink/renderer/modules/webgpu/gpu_error.h b/third_party/blink/renderer/modules/webgpu/gpu_error.h
index 93ad03cc..d5c4437f 100644
--- a/third_party/blink/renderer/modules/webgpu/gpu_error.h
+++ b/third_party/blink/renderer/modules/webgpu/gpu_error.h
@@ -20,8 +20,9 @@
   GPUError(const GPUError&) = delete;
   GPUError& operator=(const GPUError&) = delete;
 
-  // gpu_error.idl
+  // gpu_error.idl {{{
   const String& message() const;
+  // }}} End of WebIDL binding implementation.
 
  private:
   String message_;
diff --git a/third_party/blink/renderer/modules/webgpu/gpu_external_texture.cc b/third_party/blink/renderer/modules/webgpu/gpu_external_texture.cc
index 149014e1..e06bc88 100644
--- a/third_party/blink/renderer/modules/webgpu/gpu_external_texture.cc
+++ b/third_party/blink/renderer/modules/webgpu/gpu_external_texture.cc
@@ -28,7 +28,7 @@
     ExceptionState& exception_state) {
   // Ensure the GPUExternalTexture created from a destroyed GPUDevice will be
   // expired immediately.
-  if (device()->destroyed()) {
+  if (device()->IsDestroyed()) {
     return GPUExternalTexture::CreateExpired(this, descriptor, exception_state);
   }
 
@@ -347,7 +347,7 @@
 void GPUExternalTexture::Refresh() {
   CHECK(status_ != Status::Destroyed);
 
-  if (active()) {
+  if (IsActive()) {
     return;
   }
 
@@ -356,7 +356,7 @@
 }
 
 void GPUExternalTexture::Expire() {
-  if (expired() || destroyed()) {
+  if (IsExpired() || IsDestroyed()) {
     return;
   }
 
@@ -365,13 +365,13 @@
 }
 
 void GPUExternalTexture::Destroy() {
-  DCHECK(!destroyed());
+  DCHECK(!IsDestroyed());
   DCHECK(mailbox_texture_);
 
   // One copy path finished video frame access after GPUExternalTexture
   // construction. Zero copy path needs to ensure all gpu commands
   // execution finished before destroy.
-  if (isZeroCopy() && isReadLockFenceEnabled()) {
+  if (isZeroCopy() && IsReadLockFenceEnabled()) {
     cache_->ReferenceUntilGPUIsFinished(std::move(mailbox_texture_));
   }
 
@@ -398,7 +398,7 @@
 
   // If GPUExternalTexture is used in current task scope, don't do
   // reimport until current task scope finished.
-  if (active()) {
+  if (IsActive()) {
     return false;
   }
 
@@ -480,8 +480,9 @@
 void GPUExternalTexture::OnVideoFrameClosed() {
   CHECK(task_runner_);
 
-  if (destroyed())
+  if (IsDestroyed()) {
     return;
+  }
 
   // Expire the GPUExternalTexture here in the main thread to prevent it from
   // being used again (because WebGPU runs on the main thread). Expiring the
@@ -502,11 +503,11 @@
                              WrapCrossThreadWeakPersistent(this))));
 }
 
-bool GPUExternalTexture::active() const {
+bool GPUExternalTexture::IsActive() const {
   return status_ == Status::Active;
 }
 
-bool GPUExternalTexture::expired() const {
+bool GPUExternalTexture::IsExpired() const {
   return status_ == Status::Expired;
 }
 
@@ -514,11 +515,11 @@
   return is_zero_copy_;
 }
 
-bool GPUExternalTexture::isReadLockFenceEnabled() const {
+bool GPUExternalTexture::IsReadLockFenceEnabled() const {
   return read_lock_fences_enabled_;
 }
 
-bool GPUExternalTexture::destroyed() const {
+bool GPUExternalTexture::IsDestroyed() const {
   return status_ == Status::Destroyed;
 }
 
diff --git a/third_party/blink/renderer/modules/webgpu/gpu_external_texture.h b/third_party/blink/renderer/modules/webgpu/gpu_external_texture.h
index 1350fd7..ddfb18e 100644
--- a/third_party/blink/renderer/modules/webgpu/gpu_external_texture.h
+++ b/third_party/blink/renderer/modules/webgpu/gpu_external_texture.h
@@ -114,9 +114,11 @@
   GPUExternalTexture(const GPUExternalTexture&) = delete;
   GPUExternalTexture& operator=(const GPUExternalTexture&) = delete;
 
+  // gpu_external_texture.idl {{{
   bool isZeroCopy() const;
-  bool isReadLockFenceEnabled() const;
+  // }}} End of WebIDL binding implementation.
 
+  bool IsReadLockFenceEnabled() const;
   void Destroy();
   void Expire();
   void Refresh();
@@ -160,7 +162,7 @@
       std::optional<media::VideoFrame::ID> media_video_frame_unique_id,
       ExceptionState& exception_state);
 
-  void setLabelImpl(const String& value) override {
+  void SetLabelImpl(const String& value) override {
     std::string utf8_label = value.Utf8();
     GetHandle().SetLabel(utf8_label.c_str());
   }
@@ -177,9 +179,9 @@
   // frame multiple time cases.
   void RemoveFromCache();
 
-  bool active() const;
-  bool expired() const;
-  bool destroyed() const;
+  bool IsActive() const;
+  bool IsExpired() const;
+  bool IsDestroyed() const;
 
   scoped_refptr<WebGPUMailboxTexture> mailbox_texture_;
   bool is_zero_copy_ = false;
diff --git a/third_party/blink/renderer/modules/webgpu/gpu_heap_property.h b/third_party/blink/renderer/modules/webgpu/gpu_heap_property.h
index d13a073..202aa498 100644
--- a/third_party/blink/renderer/modules/webgpu/gpu_heap_property.h
+++ b/third_party/blink/renderer/modules/webgpu/gpu_heap_property.h
@@ -14,18 +14,6 @@
   DEFINE_WRAPPERTYPEINFO();
 
  public:
-  // gpu_heap_property.idl
-  static constexpr uint32_t kDeviceLocal =
-      V8GPUHeapProperty::Constant::kDeviceLocal;
-  static constexpr uint32_t kHostVisible =
-      V8GPUHeapProperty::Constant::kHostVisible;
-  static constexpr uint32_t kHostCoherent =
-      V8GPUHeapProperty::Constant::kHostCoherent;
-  static constexpr uint32_t kHostUncached =
-      V8GPUHeapProperty::Constant::kHostUncached;
-  static constexpr uint32_t kHostCached =
-      V8GPUHeapProperty::Constant::kHostCached;
-
   GPUHeapProperty(const GPUHeapProperty&) = delete;
   GPUHeapProperty& operator=(const GPUHeapProperty&) = delete;
 };
diff --git a/third_party/blink/renderer/modules/webgpu/gpu_map_mode.h b/third_party/blink/renderer/modules/webgpu/gpu_map_mode.h
index fea5bf1d..f2c2b76 100644
--- a/third_party/blink/renderer/modules/webgpu/gpu_map_mode.h
+++ b/third_party/blink/renderer/modules/webgpu/gpu_map_mode.h
@@ -14,10 +14,6 @@
   DEFINE_WRAPPERTYPEINFO();
 
  public:
-  // gpu_map_mode.idl
-  static constexpr uint32_t kRead = V8GPUMapMode::Constant::kRead;
-  static constexpr uint32_t kWrite = V8GPUMapMode::Constant::kWrite;
-
   GPUMapMode(const GPUMapMode&) = delete;
   GPUMapMode& operator=(const GPUMapMode&) = delete;
 };
diff --git a/third_party/blink/renderer/modules/webgpu/gpu_memory_heap_info.h b/third_party/blink/renderer/modules/webgpu/gpu_memory_heap_info.h
index 90665a0..6828882 100644
--- a/third_party/blink/renderer/modules/webgpu/gpu_memory_heap_info.h
+++ b/third_party/blink/renderer/modules/webgpu/gpu_memory_heap_info.h
@@ -19,9 +19,10 @@
   GPUMemoryHeapInfo(const GPUMemoryHeapInfo&) = delete;
   GPUMemoryHeapInfo& operator=(const GPUMemoryHeapInfo&) = delete;
 
-  // gpu_memory_heap_info.idl
+  // gpu_memory_heap_info.idl {{{
   uint64_t size() const;
   uint32_t properties() const;
+  // }}} End of WebIDL binding implementation.
 
  private:
   wgpu::MemoryHeapInfo info_;
diff --git a/third_party/blink/renderer/modules/webgpu/gpu_pipeline_layout.h b/third_party/blink/renderer/modules/webgpu/gpu_pipeline_layout.h
index 9a68f8d..76444a6c 100644
--- a/third_party/blink/renderer/modules/webgpu/gpu_pipeline_layout.h
+++ b/third_party/blink/renderer/modules/webgpu/gpu_pipeline_layout.h
@@ -26,7 +26,7 @@
   GPUPipelineLayout& operator=(const GPUPipelineLayout&) = delete;
 
  private:
-  void setLabelImpl(const String& value) override {
+  void SetLabelImpl(const String& value) override {
     std::string utf8_label = value.Utf8();
     GetHandle().SetLabel(utf8_label.c_str());
   }
diff --git a/third_party/blink/renderer/modules/webgpu/gpu_query_set.h b/third_party/blink/renderer/modules/webgpu/gpu_query_set.h
index 371f1d6c..92b911e 100644
--- a/third_party/blink/renderer/modules/webgpu/gpu_query_set.h
+++ b/third_party/blink/renderer/modules/webgpu/gpu_query_set.h
@@ -26,13 +26,14 @@
   GPUQuerySet(const GPUQuerySet&) = delete;
   GPUQuerySet& operator=(const GPUQuerySet&) = delete;
 
-  // gpu_queryset.idl
+  // gpu_queryset.idl {{{
   void destroy();
   V8GPUQueryType type() const;
   uint32_t count() const;
+  // }}} End of WebIDL binding implementation.
 
  private:
-  void setLabelImpl(const String& value) override {
+  void SetLabelImpl(const String& value) override {
     std::string utf8_label = value.Utf8();
     GetHandle().SetLabel(utf8_label.c_str());
   }
diff --git a/third_party/blink/renderer/modules/webgpu/gpu_queue.cc b/third_party/blink/renderer/modules/webgpu/gpu_queue.cc
index 4310102..66485c40 100644
--- a/third_party/blink/renderer/modules/webgpu/gpu_queue.cc
+++ b/third_party/blink/renderer/modules/webgpu/gpu_queue.cc
@@ -282,7 +282,9 @@
     // generate
     //   required results.
     ImageExtractor image_extractor(image_for_canvas.get(),
-                                   external_image_dst_info.premultiplied_alpha,
+                                   external_image_dst_info.premultiplied_alpha
+                                       ? kPremul_SkAlphaType
+                                       : kUnpremul_SkAlphaType,
                                    PredefinedColorSpaceToSkColorSpace(
                                        external_image_dst_info.color_space));
     sk_image = image_extractor.GetSkImage();
diff --git a/third_party/blink/renderer/modules/webgpu/gpu_queue.h b/third_party/blink/renderer/modules/webgpu/gpu_queue.h
index 4f9c3b4..e6e75af 100644
--- a/third_party/blink/renderer/modules/webgpu/gpu_queue.h
+++ b/third_party/blink/renderer/modules/webgpu/gpu_queue.h
@@ -38,7 +38,7 @@
   GPUQueue(const GPUQueue&) = delete;
   GPUQueue& operator=(const GPUQueue&) = delete;
 
-  // gpu_queue.idl
+  // gpu_queue.idl {{{
   void submit(ScriptState* script_state,
               const HeapVector<Member<GPUCommandBuffer>>& buffers);
   ScriptPromise<IDLUndefined> onSubmittedWorkDone(ScriptState* script_state);
@@ -84,6 +84,7 @@
                                   GPUImageCopyTextureTagged* destination,
                                   const V8GPUExtent3D* copySize,
                                   ExceptionState& exception_state);
+  // }}} End of WebIDL binding implementation.
 
  private:
   void CopyFromVideoElement(const ExternalTextureSource source,
@@ -116,7 +117,7 @@
                         const V8GPUExtent3D* write_size,
                         ExceptionState& exception_state);
 
-  void setLabelImpl(const String& value) override {
+  void SetLabelImpl(const String& value) override {
     std::string utf8_label = value.Utf8();
     GetHandle().SetLabel(utf8_label.c_str());
   }
diff --git a/third_party/blink/renderer/modules/webgpu/gpu_render_bundle.h b/third_party/blink/renderer/modules/webgpu/gpu_render_bundle.h
index 3383c7ed..ce05443 100644
--- a/third_party/blink/renderer/modules/webgpu/gpu_render_bundle.h
+++ b/third_party/blink/renderer/modules/webgpu/gpu_render_bundle.h
@@ -23,7 +23,7 @@
   GPURenderBundle& operator=(const GPURenderBundle&) = delete;
 
  private:
-  void setLabelImpl(const String& value) override {
+  void SetLabelImpl(const String& value) override {
     std::string utf8_label = value.Utf8();
     GetHandle().SetLabel(utf8_label.c_str());
   }
diff --git a/third_party/blink/renderer/modules/webgpu/gpu_render_bundle_encoder.h b/third_party/blink/renderer/modules/webgpu/gpu_render_bundle_encoder.h
index a4752294..79bfad1 100644
--- a/third_party/blink/renderer/modules/webgpu/gpu_render_bundle_encoder.h
+++ b/third_party/blink/renderer/modules/webgpu/gpu_render_bundle_encoder.h
@@ -35,7 +35,7 @@
   GPURenderBundleEncoder(const GPURenderBundleEncoder&) = delete;
   GPURenderBundleEncoder& operator=(const GPURenderBundleEncoder&) = delete;
 
-  // gpu_render_bundle_encoder.idl
+  // gpu_render_bundle_encoder.idl {{{
   void setBindGroup(uint32_t index, DawnObject<wgpu::BindGroup>* bindGroup) {
     GetHandle().SetBindGroup(
         index, bindGroup ? bindGroup->GetHandle() : wgpu::BindGroup(nullptr), 0,
@@ -62,7 +62,6 @@
   void setPipeline(const DawnObject<wgpu::RenderPipeline>* pipeline) {
     GetHandle().SetPipeline(pipeline->GetHandle());
   }
-
   void setIndexBuffer(const DawnObject<wgpu::Buffer>* buffer,
                       const V8GPUIndexFormat& format,
                       uint64_t offset) {
@@ -112,10 +111,10 @@
     GetHandle().DrawIndexedIndirect(indirectBuffer->GetHandle(),
                                     indirectOffset);
   }
-
   GPURenderBundle* finish(const GPURenderBundleDescriptor* webgpu_desc);
+  // }}} End of WebIDL binding implementation.
 
-  void setLabelImpl(const String& value) override {
+  void SetLabelImpl(const String& value) override {
     std::string utf8_label = value.Utf8();
     GetHandle().SetLabel(utf8_label.c_str());
   }
diff --git a/third_party/blink/renderer/modules/webgpu/gpu_render_pass_encoder.cc b/third_party/blink/renderer/modules/webgpu/gpu_render_pass_encoder.cc
index b9af52f..1d2773e5 100644
--- a/third_party/blink/renderer/modules/webgpu/gpu_render_pass_encoder.cc
+++ b/third_party/blink/renderer/modules/webgpu/gpu_render_pass_encoder.cc
@@ -93,13 +93,13 @@
   V8GPUFeatureName::Enum requiredFeatureEnum =
       V8GPUFeatureName::Enum::kChromiumExperimentalMultiDrawIndirect;
 
-  if (!device_->features()->has(requiredFeatureEnum)) {
+  if (!device_->features()->Has(requiredFeatureEnum)) {
     exception_state.ThrowTypeError(
         String::Format("Use of the multiDrawIndirect() method on render pass "
                        "requires the '%s' "
                        "feature to be enabled on %s.",
                        V8GPUFeatureName(requiredFeatureEnum).AsCStr(),
-                       device_->formattedLabel().c_str()));
+                       device_->GetFormattedLabel().c_str()));
     return;
   }
   GetHandle().MultiDrawIndirect(
@@ -137,13 +137,13 @@
   V8GPUFeatureName::Enum requiredFeatureEnum =
       V8GPUFeatureName::Enum::kChromiumExperimentalMultiDrawIndirect;
 
-  if (!device_->features()->has(requiredFeatureEnum)) {
+  if (!device_->features()->Has(requiredFeatureEnum)) {
     exception_state.ThrowTypeError(String::Format(
         "Use of the multiDrawIndexedIndirect() method on render pass "
         "requires the '%s' "
         "feature to be enabled on %s.",
         V8GPUFeatureName(requiredFeatureEnum).AsCStr(),
-        device_->formattedLabel().c_str()));
+        device_->GetFormattedLabel().c_str()));
     return;
   }
   GetHandle().MultiDrawIndexedIndirect(
@@ -166,12 +166,12 @@
   V8GPUFeatureName::Enum requiredFeatureEnum =
       V8GPUFeatureName::Enum::kChromiumExperimentalTimestampQueryInsidePasses;
 
-  if (!device_->features()->has(requiredFeatureEnum)) {
+  if (!device_->features()->Has(requiredFeatureEnum)) {
     exception_state.ThrowTypeError(String::Format(
         "Use of the writeTimestamp() method on render pass requires the '%s' "
         "feature to be enabled on %s.",
         V8GPUFeatureName(requiredFeatureEnum).AsCStr(),
-        device_->formattedLabel().c_str()));
+        device_->GetFormattedLabel().c_str()));
     return;
   }
   GetHandle().WriteTimestamp(querySet->GetHandle(), queryIndex);
diff --git a/third_party/blink/renderer/modules/webgpu/gpu_render_pass_encoder.h b/third_party/blink/renderer/modules/webgpu/gpu_render_pass_encoder.h
index 6be7b682..5183b23 100644
--- a/third_party/blink/renderer/modules/webgpu/gpu_render_pass_encoder.h
+++ b/third_party/blink/renderer/modules/webgpu/gpu_render_pass_encoder.h
@@ -29,7 +29,7 @@
   GPURenderPassEncoder(const GPURenderPassEncoder&) = delete;
   GPURenderPassEncoder& operator=(const GPURenderPassEncoder&) = delete;
 
-  // gpu_render_pass_encoder.idl
+  // gpu_render_pass_encoder.idl {{{
   void setBindGroup(uint32_t index, DawnObject<wgpu::BindGroup>* bindGroup) {
     GetHandle().SetBindGroup(
         index, bindGroup ? bindGroup->GetHandle() : wgpu::BindGroup(nullptr));
@@ -55,7 +55,6 @@
   void setPipeline(const DawnObject<wgpu::RenderPipeline>* pipeline) {
     GetHandle().SetPipeline(pipeline->GetHandle());
   }
-
   void setBlendConstant(const V8GPUColor* color,
                         ExceptionState& exception_state);
   void setStencilReference(uint32_t reference) {
@@ -160,8 +159,9 @@
                       uint32_t queryIndex,
                       ExceptionState& exception_state);
   void end() { GetHandle().End(); }
+  // }}} End of WebIDL binding implementation.
 
-  void setLabelImpl(const String& value) override {
+  void SetLabelImpl(const String& value) override {
     std::string utf8_label = value.Utf8();
     GetHandle().SetLabel(utf8_label.c_str());
   }
diff --git a/third_party/blink/renderer/modules/webgpu/gpu_render_pipeline.h b/third_party/blink/renderer/modules/webgpu/gpu_render_pipeline.h
index ee856da..158ad26 100644
--- a/third_party/blink/renderer/modules/webgpu/gpu_render_pipeline.h
+++ b/third_party/blink/renderer/modules/webgpu/gpu_render_pipeline.h
@@ -92,7 +92,7 @@
   GPUBindGroupLayout* getBindGroupLayout(uint32_t index);
 
  private:
-  void setLabelImpl(const String& value) override {
+  void SetLabelImpl(const String& value) override {
     std::string utf8_label = value.Utf8();
     GetHandle().SetLabel(utf8_label.c_str());
   }
diff --git a/third_party/blink/renderer/modules/webgpu/gpu_sampler.h b/third_party/blink/renderer/modules/webgpu/gpu_sampler.h
index e4d2653..1d925db 100644
--- a/third_party/blink/renderer/modules/webgpu/gpu_sampler.h
+++ b/third_party/blink/renderer/modules/webgpu/gpu_sampler.h
@@ -25,7 +25,7 @@
   GPUSampler& operator=(const GPUSampler&) = delete;
 
  private:
-  void setLabelImpl(const String& value) override {
+  void SetLabelImpl(const String& value) override {
     std::string utf8_label = value.Utf8();
     GetHandle().SetLabel(utf8_label.c_str());
   }
diff --git a/third_party/blink/renderer/modules/webgpu/gpu_shader_module.h b/third_party/blink/renderer/modules/webgpu/gpu_shader_module.h
index 25353c47..0d98e34 100644
--- a/third_party/blink/renderer/modules/webgpu/gpu_shader_module.h
+++ b/third_party/blink/renderer/modules/webgpu/gpu_shader_module.h
@@ -38,7 +38,7 @@
       wgpu::CompilationInfoRequestStatus status,
       const wgpu::CompilationInfo* info);
 
-  void setLabelImpl(const String& value) override {
+  void SetLabelImpl(const String& value) override {
     std::string utf8_label = value.Utf8();
     GetHandle().SetLabel(utf8_label.c_str());
   }
diff --git a/third_party/blink/renderer/modules/webgpu/gpu_shader_stage.h b/third_party/blink/renderer/modules/webgpu/gpu_shader_stage.h
index 06a0252..6e94a40 100644
--- a/third_party/blink/renderer/modules/webgpu/gpu_shader_stage.h
+++ b/third_party/blink/renderer/modules/webgpu/gpu_shader_stage.h
@@ -14,11 +14,6 @@
   DEFINE_WRAPPERTYPEINFO();
 
  public:
-  // gpu_shader_stage.idl
-  static constexpr uint32_t kVertex = V8GPUShaderStage::Constant::kVertex;
-  static constexpr uint32_t kFragment = V8GPUShaderStage::Constant::kFragment;
-  static constexpr uint32_t kCompute = V8GPUShaderStage::Constant::kCompute;
-
   GPUShaderStage(const GPUShaderStage&) = delete;
   GPUShaderStage& operator=(const GPUShaderStage&) = delete;
 };
diff --git a/third_party/blink/renderer/modules/webgpu/gpu_supported_features.cc b/third_party/blink/renderer/modules/webgpu/gpu_supported_features.cc
index d66fb8ed..d7b1e739 100644
--- a/third_party/blink/renderer/modules/webgpu/gpu_supported_features.cc
+++ b/third_party/blink/renderer/modules/webgpu/gpu_supported_features.cc
@@ -78,19 +78,15 @@
   features_bitset_.set(static_cast<size_t>(feature_name.AsEnum()));
 }
 
-bool GPUSupportedFeatures::has(const V8GPUFeatureName::Enum feature) const {
+bool GPUSupportedFeatures::Has(const V8GPUFeatureName::Enum feature) const {
   return features_bitset_.test(static_cast<size_t>(feature));
 }
 
-bool GPUSupportedFeatures::has(const String& feature) const {
-  return features_.Contains(feature);
-}
-
 bool GPUSupportedFeatures::hasForBinding(
     ScriptState* script_state,
     const String& feature,
     ExceptionState& exception_state) const {
-  return has(feature);
+  return features_.Contains(feature);
 }
 
 GPUSupportedFeatures::IterationSource::IterationSource(
diff --git a/third_party/blink/renderer/modules/webgpu/gpu_supported_features.h b/third_party/blink/renderer/modules/webgpu/gpu_supported_features.h
index f2e7bfc1..bfaf95c 100644
--- a/third_party/blink/renderer/modules/webgpu/gpu_supported_features.h
+++ b/third_party/blink/renderer/modules/webgpu/gpu_supported_features.h
@@ -30,18 +30,17 @@
 
   void AddFeatureName(const V8GPUFeatureName feature_name);
 
-  bool has(const String& feature) const;
+  const HashSet<String>& FeatureNameSet() const { return features_; }
+  // Fast path, it allows to avoid hash computation from string for
+  // checking features.
+  bool Has(const V8GPUFeatureName::Enum feature) const;
+
+  // gpu_supported_features.idl {{{
   bool hasForBinding(ScriptState* script_state,
                      const String& feature,
                      ExceptionState& exception_state) const;
-
-  // Fast path, it allows to avoid hash computation from string for
-  // checking features.
-  bool has(const V8GPUFeatureName::Enum feature) const;
-
   unsigned size() const { return features_.size(); }
-
-  const HashSet<String>& FeatureNameSet() const { return features_; }
+  // }}} End of WebIDL binding implementation.
 
  private:
   HashSet<String> features_;
diff --git a/third_party/blink/renderer/modules/webgpu/gpu_texture.h b/third_party/blink/renderer/modules/webgpu/gpu_texture.h
index d1178df..29ad245 100644
--- a/third_party/blink/renderer/modules/webgpu/gpu_texture.h
+++ b/third_party/blink/renderer/modules/webgpu/gpu_texture.h
@@ -43,7 +43,7 @@
   GPUTexture(const GPUTexture&) = delete;
   GPUTexture& operator=(const GPUTexture&) = delete;
 
-  // gpu_texture.idl
+  // gpu_texture.idl {{{
   GPUTextureView* createView(const GPUTextureViewDescriptor* webgpu_desc,
                              ExceptionState& exception_state);
   void destroy();
@@ -55,11 +55,12 @@
   V8GPUTextureDimension dimension() const;
   V8GPUTextureFormat format() const;
   uint32_t usage() const;
+  // }}} End of WebIDL binding implementation.
 
   wgpu::TextureDimension Dimension() { return dimension_; }
   wgpu::TextureFormat Format() { return format_; }
   wgpu::TextureUsage Usage() { return usage_; }
-  bool Destroyed() { return destroyed_; }
+  bool IsDestroyed() { return destroyed_; }
 
   void DissociateMailbox();
 
@@ -73,7 +74,7 @@
   void ClearBeforeDestroyCallback();
 
  private:
-  void setLabelImpl(const String& value) override {
+  void SetLabelImpl(const String& value) override {
     std::string utf8_label = value.Utf8();
     GetHandle().SetLabel(utf8_label.c_str());
   }
diff --git a/third_party/blink/renderer/modules/webgpu/gpu_texture_usage.h b/third_party/blink/renderer/modules/webgpu/gpu_texture_usage.h
index 2c211e9..bb9319c 100644
--- a/third_party/blink/renderer/modules/webgpu/gpu_texture_usage.h
+++ b/third_party/blink/renderer/modules/webgpu/gpu_texture_usage.h
@@ -15,16 +15,6 @@
   DEFINE_WRAPPERTYPEINFO();
 
  public:
-  // gpu_texture_usage.idl
-  static constexpr uint32_t kCopySrc = V8GPUTextureUsage::Constant::kCopySrc;
-  static constexpr uint32_t kCopyDst = V8GPUTextureUsage::Constant::kCopyDst;
-  static constexpr uint32_t kTextureBinding =
-      V8GPUTextureUsage::Constant::kTextureBinding;
-  static constexpr uint32_t kStorageBinding =
-      V8GPUTextureUsage::Constant::kStorageBinding;
-  static constexpr uint32_t kRenderAttachment =
-      V8GPUTextureUsage::Constant::kRenderAttachment;
-
   GPUTextureUsage(const GPUTextureUsage&) = delete;
   GPUTextureUsage& operator=(const GPUTextureUsage&) = delete;
 };
diff --git a/third_party/blink/renderer/modules/webgpu/gpu_texture_view.h b/third_party/blink/renderer/modules/webgpu/gpu_texture_view.h
index 3fa27eaf..3eefba6 100644
--- a/third_party/blink/renderer/modules/webgpu/gpu_texture_view.h
+++ b/third_party/blink/renderer/modules/webgpu/gpu_texture_view.h
@@ -21,7 +21,7 @@
   GPUTextureView& operator=(const GPUTextureView&) = delete;
 
  private:
-  void setLabelImpl(const String& value) override {
+  void SetLabelImpl(const String& value) override {
     std::string utf8_label = value.Utf8();
     GetHandle().SetLabel(utf8_label.c_str());
   }
diff --git a/third_party/blink/renderer/modules/webgpu/gpu_uncaptured_error_event.h b/third_party/blink/renderer/modules/webgpu/gpu_uncaptured_error_event.h
index 3269e5bb..8582744 100644
--- a/third_party/blink/renderer/modules/webgpu/gpu_uncaptured_error_event.h
+++ b/third_party/blink/renderer/modules/webgpu/gpu_uncaptured_error_event.h
@@ -26,8 +26,9 @@
 
   void Trace(Visitor*) const override;
 
-  // gpu_uncaptured_error_event.idl
+  // gpu_uncaptured_error_event.idl {{{
   GPUError* error();
+  // }}} End of WebIDL binding implementation.
 
  private:
   Member<GPUError> error_;
diff --git a/third_party/blink/renderer/modules/webgpu/wgsl_language_features.cc b/third_party/blink/renderer/modules/webgpu/wgsl_language_features.cc
index 272c0b3..b47d7778a 100644
--- a/third_party/blink/renderer/modules/webgpu/wgsl_language_features.cc
+++ b/third_party/blink/renderer/modules/webgpu/wgsl_language_features.cc
@@ -17,15 +17,11 @@
   }
 }
 
-bool WGSLLanguageFeatures::has(const String& feature) const {
-  return features_.Contains(feature);
-}
-
 bool WGSLLanguageFeatures::hasForBinding(
     ScriptState* script_state,
     const String& feature,
     ExceptionState& exception_state) const {
-  return has(feature);
+  return features_.Contains(feature);
 }
 
 WGSLLanguageFeatures::IterationSource::IterationSource(
diff --git a/third_party/blink/renderer/modules/webgpu/wgsl_language_features.h b/third_party/blink/renderer/modules/webgpu/wgsl_language_features.h
index f2a161ccb..e164a83 100644
--- a/third_party/blink/renderer/modules/webgpu/wgsl_language_features.h
+++ b/third_party/blink/renderer/modules/webgpu/wgsl_language_features.h
@@ -23,14 +23,14 @@
   explicit WGSLLanguageFeatures(
       const std::vector<wgpu::WGSLLanguageFeatureName>& features);
 
-  bool has(const String& feature) const;
+  const HashSet<String>& FeatureNameSet() const { return features_; }
+
+  // wgsl_language_features.idl {{{
   bool hasForBinding(ScriptState* script_state,
                      const String& feature,
                      ExceptionState& exception_state) const;
-
   unsigned size() const { return features_.size(); }
-
-  const HashSet<String>& FeatureNameSet() const { return features_; }
+  // }}} End of WebIDL binding implementation.
 
  private:
   HashSet<String> features_;
diff --git a/third_party/blink/renderer/modules/xr/xr_gpu_binding.cc b/third_party/blink/renderer/modules/xr/xr_gpu_binding.cc
index 13078601..0e5be45 100644
--- a/third_party/blink/renderer/modules/xr/xr_gpu_binding.cc
+++ b/third_party/blink/renderer/modules/xr/xr_gpu_binding.cc
@@ -54,14 +54,14 @@
     return nullptr;
   }
 
-  if (device->destroyed()) {
+  if (device->IsDestroyed()) {
     exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError,
                                       "Cannot create an XRGPUBinding with a "
                                       "destroyed WebGPU device.");
     return nullptr;
   }
 
-  if (!device->adapter()->isXRCompatible()) {
+  if (!device->adapter()->IsXRCompatible()) {
     exception_state.ThrowDOMException(
         DOMExceptionCode::kInvalidStateError,
         "WebGPU device must be created by an XR compatible adapter in order to "
@@ -216,7 +216,7 @@
 V8GPUTextureFormat XRGPUBinding::getPreferredColorFormat() {
   // TODO(crbug.com/5818595): Ensure the backend swap chain format matches this.
   // Till then the copy between formats is done in XRGPUTextureArraySwapChain.
-  return FromDawnEnum(GPU::preferred_canvas_format());
+  return FromDawnEnum(GPU::GetPreferredCanvasFormat());
 }
 
 bool XRGPUBinding::CanCreateLayer(ExceptionState& exception_state) {
@@ -227,7 +227,7 @@
     return false;
   }
 
-  if (device_->destroyed()) {
+  if (device_->IsDestroyed()) {
     exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError,
                                       "Cannot create a new layer with a "
                                       "destroyed WebGPU device.");
diff --git a/third_party/blink/renderer/platform/graphics/accelerated_static_bitmap_image.cc b/third_party/blink/renderer/platform/graphics/accelerated_static_bitmap_image.cc
index f4cf0643..f6c996b 100644
--- a/third_party/blink/renderer/platform/graphics/accelerated_static_bitmap_image.cc
+++ b/third_party/blink/renderer/platform/graphics/accelerated_static_bitmap_image.cc
@@ -170,7 +170,7 @@
     GLenum dest_target,
     GLuint dest_texture_id,
     GLint dest_level,
-    bool unpack_premultiply_alpha,
+    SkAlphaType dest_alpha_type,
     GrSurfaceOrigin destination_origin,
     const gfx::Point& dest_point,
     const gfx::Rect& src_rect) {
@@ -188,9 +188,9 @@
   auto source_scoped_si_access = source_si_texture->BeginAccess(
       mailbox_ref_->sync_token(), /*readonly=*/true);
   const bool do_alpha_multiply = GetAlphaType() == kUnpremul_SkAlphaType &&
-                                 unpack_premultiply_alpha == true;
+                                 dest_alpha_type == kPremul_SkAlphaType;
   const bool do_alpha_unmultiply = GetAlphaType() == kPremul_SkAlphaType &&
-                                   unpack_premultiply_alpha == false;
+                                   dest_alpha_type == kUnpremul_SkAlphaType;
 
   // `src_rect` here is always in top-left coordinate space, but
   // CopySubTextureCHROMIUM source rect is in texture coordinate space, so we
diff --git a/third_party/blink/renderer/platform/graphics/accelerated_static_bitmap_image.h b/third_party/blink/renderer/platform/graphics/accelerated_static_bitmap_image.h
index b483dff..494df22 100644
--- a/third_party/blink/renderer/platform/graphics/accelerated_static_bitmap_image.h
+++ b/third_party/blink/renderer/platform/graphics/accelerated_static_bitmap_image.h
@@ -97,7 +97,7 @@
                      GLenum dest_target,
                      GLuint dest_texture_id,
                      GLint dest_level,
-                     bool unpack_premultiply_alpha,
+                     SkAlphaType dest_alpha_type,
                      GrSurfaceOrigin destination_origin,
                      const gfx::Point& dest_point,
                      const gfx::Rect& src_rect) override;
diff --git a/third_party/blink/renderer/platform/graphics/accelerated_static_bitmap_image_test.cc b/third_party/blink/renderer/platform/graphics/accelerated_static_bitmap_image_test.cc
index 2490fbe..1237f67 100644
--- a/third_party/blink/renderer/platform/graphics/accelerated_static_bitmap_image_test.cc
+++ b/third_party/blink/renderer/platform/graphics/accelerated_static_bitmap_image_test.cc
@@ -121,8 +121,8 @@
   gfx::Rect source_sub_rectangle(0, 0, 10, 10);
   ASSERT_TRUE(bitmap->CopyToTexture(
       &destination_gl, GL_TEXTURE_2D, /*dest_texture_id=*/1,
-      /*dest_texture_level=*/0, /*unpack_premultiply_alpha=*/false,
-      kTopLeft_GrSurfaceOrigin, dest_point, source_sub_rectangle));
+      /*dest_level=*/0, kUnpremul_SkAlphaType, kTopLeft_GrSurfaceOrigin,
+      dest_point, source_sub_rectangle));
 
   testing::Mock::VerifyAndClearExpectations(&gl_);
   testing::Mock::VerifyAndClearExpectations(&destination_gl);
diff --git a/third_party/blink/renderer/platform/graphics/gpu/drawing_buffer.cc b/third_party/blink/renderer/platform/graphics/gpu/drawing_buffer.cc
index e26102b..7031793 100644
--- a/third_party/blink/renderer/platform/graphics/gpu/drawing_buffer.cc
+++ b/third_party/blink/renderer/platform/graphics/gpu/drawing_buffer.cc
@@ -1099,8 +1099,8 @@
                                           GLenum dst_texture_target,
                                           GLuint dst_texture,
                                           GLint dst_level,
-                                          bool premultiply_alpha,
-                                          GrSurfaceOrigin destination_origin,
+                                          SkAlphaType dst_alpha_type,
+                                          GrSurfaceOrigin dst_origin,
                                           const gfx::Point& dst_texture_offset,
                                           const gfx::Rect& src_rect,
                                           SourceDrawingBuffer src_buffer) {
@@ -1112,7 +1112,7 @@
           const gpu::SyncToken& produce_sync_token, SkAlphaType src_alpha_type,
           const gfx::Size&) -> std::optional<gpu::SyncToken> {
     // If origin doesn't match, we need to flip.
-    bool do_flip_y = src_shared_image->surface_origin() != destination_origin;
+    bool do_flip_y = src_shared_image->surface_origin() != dst_origin;
 
     // `src_rect` here is always in top-left coordinate space, but
     // CopySubTextureCHROMIUM source rect is in texture coordinate space, so we
@@ -1124,9 +1124,11 @@
 
     GLboolean unpack_premultiply_alpha_needed = GL_FALSE;
     GLboolean unpack_unpremultiply_alpha_needed = GL_FALSE;
-    if (src_alpha_type == kPremul_SkAlphaType && !premultiply_alpha) {
+    if (src_alpha_type == kPremul_SkAlphaType &&
+        dst_alpha_type == kUnpremul_SkAlphaType) {
       unpack_unpremultiply_alpha_needed = GL_TRUE;
-    } else if (src_alpha_type == kUnpremul_SkAlphaType && premultiply_alpha) {
+    } else if (src_alpha_type == kUnpremul_SkAlphaType &&
+               dst_alpha_type == kPremul_SkAlphaType) {
       unpack_premultiply_alpha_needed = GL_TRUE;
     }
 
@@ -1144,8 +1146,8 @@
     src_si_texture.reset();
     return sync_token;
   };
-  return CopyToPlatformInternal(dst_gl, !premultiply_alpha, src_buffer,
-                                copy_function);
+  return CopyToPlatformInternal(dst_gl, dst_alpha_type == kUnpremul_SkAlphaType,
+                                src_buffer, copy_function);
 }
 
 bool DrawingBuffer::CopyToPlatformMailbox(
diff --git a/third_party/blink/renderer/platform/graphics/gpu/drawing_buffer.h b/third_party/blink/renderer/platform/graphics/gpu/drawing_buffer.h
index c59430ab..54278e5 100644
--- a/third_party/blink/renderer/platform/graphics/gpu/drawing_buffer.h
+++ b/third_party/blink/renderer/platform/graphics/gpu/drawing_buffer.h
@@ -273,8 +273,8 @@
                              GLenum dst_target,
                              GLuint dst_texture,
                              GLint dst_level,
-                             bool premultiply_alpha,
-                             GrSurfaceOrigin destination_origin,
+                             SkAlphaType dst_alpha_type,
+                             GrSurfaceOrigin dst_origin,
                              const gfx::Point& dst_texture_offset,
                              const gfx::Rect& src_rect,
                              SourceDrawingBuffer);
diff --git a/third_party/blink/renderer/platform/graphics/gpu/image_extractor.cc b/third_party/blink/renderer/platform/graphics/gpu/image_extractor.cc
index c2adaee..5690356 100644
--- a/third_party/blink/renderer/platform/graphics/gpu/image_extractor.cc
+++ b/third_party/blink/renderer/platform/graphics/gpu/image_extractor.cc
@@ -28,7 +28,7 @@
 }  // anonymous namespace
 
 ImageExtractor::ImageExtractor(Image* image,
-                               bool premultiply_alpha,
+                               SkAlphaType target_alpha_type,
                                sk_sp<SkColorSpace> target_color_space) {
   if (!image) {
     return;
@@ -80,7 +80,7 @@
       // bother re-decoding if premultiply alpha was requested, because we will
       // do that lossy conversion later.
       if (skia_image->alphaType() == kPremul_SkAlphaType &&
-          !premultiply_alpha) {
+          target_alpha_type != kPremul_SkAlphaType) {
         needs_redecode = true;
       }
 
diff --git a/third_party/blink/renderer/platform/graphics/gpu/image_extractor.h b/third_party/blink/renderer/platform/graphics/gpu/image_extractor.h
index d4507cc..420b464 100644
--- a/third_party/blink/renderer/platform/graphics/gpu/image_extractor.h
+++ b/third_party/blink/renderer/platform/graphics/gpu/image_extractor.h
@@ -22,7 +22,7 @@
   // nullptr. Otherwise, `target_color_space` should be set to the color space
   // that the image will ultimately be converted to.
   ImageExtractor(Image*,
-                 bool premultiply_alpha,
+                 SkAlphaType target_alpha_type,
                  sk_sp<SkColorSpace> target_color_space);
   ImageExtractor(const ImageExtractor&) = delete;
   ImageExtractor& operator=(const ImageExtractor&) = delete;
diff --git a/third_party/blink/renderer/platform/graphics/static_bitmap_image.h b/third_party/blink/renderer/platform/graphics/static_bitmap_image.h
index 0e957e3..9f1323d 100644
--- a/third_party/blink/renderer/platform/graphics/static_bitmap_image.h
+++ b/third_party/blink/renderer/platform/graphics/static_bitmap_image.h
@@ -66,7 +66,7 @@
                              GLenum dest_target,
                              GLuint dest_texture_id,
                              GLint dest_level,
-                             bool unpack_premultiply_alpha,
+                             SkAlphaType dest_alpha_type,
                              GrSurfaceOrigin destination_origin,
                              const gfx::Point& dest_point,
                              const gfx::Rect& src_rect) {
diff --git a/third_party/blink/renderer/platform/loader/fetch/resource_loader.cc b/third_party/blink/renderer/platform/loader/fetch/resource_loader.cc
index 8a85767d..d325448 100644
--- a/third_party/blink/renderer/platform/loader/fetch/resource_loader.cc
+++ b/third_party/blink/renderer/platform/loader/fetch/resource_loader.cc
@@ -934,20 +934,6 @@
     return;
   }
 
-  // https://wicg.github.io/cross-origin-embedder-policy/#integration-html
-  // TODO(crbug.com/1064920): Remove this once PlzDedicatedWorker ships.
-  if (options.reject_coep_unsafe_none &&
-      !network::CompatibleWithCrossOriginIsolated(
-          response.GetCrossOriginEmbedderPolicy()) &&
-      !response.CurrentRequestUrl().ProtocolIsData() &&
-      !response.CurrentRequestUrl().ProtocolIs("blob")) {
-    DCHECK(!base::FeatureList::IsEnabled(features::kPlzDedicatedWorker));
-    HandleError(ResourceError::BlockedByResponse(
-        response.CurrentRequestUrl(), network::mojom::BlockedByResponseReason::
-                                          kCoepFrameResourceNeedsCoepHeader));
-    return;
-  }
-
   // Redirect information for possible post-request checks below.
   const std::optional<ResourceRequest::RedirectInfo>& previous_redirect_info =
       request.GetRedirectInfo();
diff --git a/third_party/blink/renderer/platform/loader/fetch/url_loader/dedicated_or_shared_worker_fetch_context_impl.cc b/third_party/blink/renderer/platform/loader/fetch/url_loader/dedicated_or_shared_worker_fetch_context_impl.cc
index 66b4eb0..5b3c0da 100644
--- a/third_party/blink/renderer/platform/loader/fetch/url_loader/dedicated_or_shared_worker_fetch_context_impl.cc
+++ b/third_party/blink/renderer/platform/loader/fetch/url_loader/dedicated_or_shared_worker_fetch_context_impl.cc
@@ -14,7 +14,6 @@
 #include "base/task/thread_pool.h"
 #include "services/network/public/cpp/resource_request.h"
 #include "services/network/public/cpp/wrapper_shared_url_loader_factory.h"
-#include "third_party/blink/public/common/features.h"
 #include "third_party/blink/public/common/loader/loader_constants.h"
 #include "third_party/blink/public/mojom/service_worker/service_worker_fetch_handler_bypass_option.mojom-blink-forward.h"
 #include "third_party/blink/public/mojom/service_worker/service_worker_object.mojom.h"
@@ -189,51 +188,6 @@
           std::move(pending_resource_load_info_notifier)) {}
 
 scoped_refptr<WebDedicatedOrSharedWorkerFetchContext>
-DedicatedOrSharedWorkerFetchContextImpl::CloneForNestedWorkerDeprecated(
-    scoped_refptr<base::SingleThreadTaskRunner> task_runner) {
-  DCHECK(!base::FeatureList::IsEnabled(features::kPlzDedicatedWorker));
-
-  mojo::PendingReceiver<mojom::blink::ServiceWorkerWorkerClient>
-      service_worker_client_receiver;
-  mojo::PendingRemote<mojom::blink::ServiceWorkerWorkerClientRegistry>
-      service_worker_worker_client_registry;
-  if (service_worker_worker_client_registry_) {
-    mojo::PendingRemote<mojom::blink::ServiceWorkerWorkerClient>
-        service_worker_client;
-    service_worker_client_receiver =
-        service_worker_client.InitWithNewPipeAndPassReceiver();
-    service_worker_worker_client_registry_->RegisterWorkerClient(
-        std::move(service_worker_client));
-    service_worker_worker_client_registry_->CloneWorkerClientRegistry(
-        service_worker_worker_client_registry.InitWithNewPipeAndPassReceiver());
-  }
-
-  CrossVariantMojoRemote<mojom::ServiceWorkerContainerHostInterfaceBase>
-      cloned_service_worker_container_host;
-  if (service_worker_container_host_) {
-    std::tie(service_worker_container_host_,
-             cloned_service_worker_container_host) =
-        Platform::Current()->CloneServiceWorkerContainerHost(
-            std::move(service_worker_container_host_));
-  }
-
-  // |pending_subresource_loader_updater| is not used for
-  // non-PlzDedicatedWorker.
-  scoped_refptr<DedicatedOrSharedWorkerFetchContextImpl> new_context =
-      CloneForNestedWorkerInternal(
-          std::move(service_worker_client_receiver),
-          std::move(service_worker_worker_client_registry),
-          std::move(cloned_service_worker_container_host),
-          loader_factory_->Clone(), fallback_factory_->Clone(),
-          /*pending_subresource_loader_updater=*/mojo::NullReceiver(),
-          std::move(task_runner));
-  new_context->controller_service_worker_mode_ =
-      controller_service_worker_mode_;
-
-  return new_context;
-}
-
-scoped_refptr<WebDedicatedOrSharedWorkerFetchContext>
 DedicatedOrSharedWorkerFetchContextImpl::CloneForNestedWorker(
     WebServiceWorkerProviderContext* service_worker_provider_context,
     std::unique_ptr<network::PendingSharedURLLoaderFactory>
@@ -243,7 +197,6 @@
     CrossVariantMojoReceiver<mojom::SubresourceLoaderUpdaterInterfaceBase>
         pending_subresource_loader_updater,
     scoped_refptr<base::SingleThreadTaskRunner> task_runner) {
-  DCHECK(base::FeatureList::IsEnabled(features::kPlzDedicatedWorker));
   DCHECK(pending_loader_factory);
   DCHECK(pending_fallback_factory);
   DCHECK(task_runner);
diff --git a/third_party/blink/renderer/platform/loader/fetch/url_loader/dedicated_or_shared_worker_fetch_context_impl.h b/third_party/blink/renderer/platform/loader/fetch/url_loader/dedicated_or_shared_worker_fetch_context_impl.h
index 697d22458..d185043 100644
--- a/third_party/blink/renderer/platform/loader/fetch/url_loader/dedicated_or_shared_worker_fetch_context_impl.h
+++ b/third_party/blink/renderer/platform/loader/fetch/url_loader/dedicated_or_shared_worker_fetch_context_impl.h
@@ -77,15 +77,9 @@
           pending_resource_load_info_notifier);
 
   // WebDedicatedOrSharedWorkerFetchContext implementation:
-  // Clones this fetch context for a nested worker.
-  // For non-PlzDedicatedWorker. This will be removed once PlzDedicatedWorker is
-  // enabled by default.
-  scoped_refptr<WebDedicatedOrSharedWorkerFetchContext>
-  CloneForNestedWorkerDeprecated(
-      scoped_refptr<base::SingleThreadTaskRunner> task_runner) override;
-  // For PlzDedicatedWorker. The cloned fetch context does not inherit some
-  // fields (e.g., blink::WebServiceWorkerProviderContext) from this fetch
-  // context, and instead that takes values passed from the browser process.
+  // The cloned fetch context does not inherit some fields (e.g.,
+  // blink::WebServiceWorkerProviderContext) from this fetch context, and
+  // instead that takes values passed from the browser process.
   scoped_refptr<WebDedicatedOrSharedWorkerFetchContext> CloneForNestedWorker(
       WebServiceWorkerProviderContext* service_worker_provider_context,
       std::unique_ptr<network::PendingSharedURLLoaderFactory>
@@ -140,13 +134,8 @@
   void OnControllerChanged(mojom::ControllerServiceWorkerMode) override;
 
   // Sets the controller service worker mode.
-  // - For dedicated workers (non-PlzDedicatedWorker), they depend on the
-  //   controller of the ancestor frame (directly for non-nested workers, or
-  //   indirectly via its parent worker for nested workers), and inherit its
-  //   controller mode.
-  // - For dedicated workers (PlzDedicatedWorker) and shared workers, the
-  //   controller mode is passed from the browser processw when starting the
-  //   worker.
+  // - For dedicated workers and shared workers, the controller mode is passed
+  // from the browser processw when starting the worker.
   void set_controller_service_worker_mode(
       mojom::ControllerServiceWorkerMode mode);
 
@@ -243,11 +232,8 @@
 
   // Initialized on the worker thread when InitializeOnWorkerThread() is called.
   // Used to reconnect to the Network Service after the Network Service crash.
-  // This is only used for dedicated workers when PlzDedicatedWorker is enabled.
-  // When PlzDedicatedWorker is disabled, the ancestor render frame updates the
-  // loaders via Host/TrackedChildURLLoaderFactoryBundle. For shared workers,
-  // the renderer process detects the crash, and terminates the worker instead
-  // of recovery.
+  // This is only used for dedicated workers. For shared workers, the renderer
+  // process detects the crash, and terminates the worker instead of recovery.
   mojo::PendingReceiver<mojom::blink::SubresourceLoaderUpdater>
       pending_subresource_loader_updater_;
   mojo::Receiver<mojom::blink::SubresourceLoaderUpdater>
diff --git a/third_party/blink/renderer/platform/loader/fetch/url_loader/worker_main_script_loader_unittest.cc b/third_party/blink/renderer/platform/loader/fetch/url_loader/worker_main_script_loader_unittest.cc
index 2033bf3..63dbe45 100644
--- a/third_party/blink/renderer/platform/loader/fetch/url_loader/worker_main_script_loader_unittest.cc
+++ b/third_party/blink/renderer/platform/loader/fetch/url_loader/worker_main_script_loader_unittest.cc
@@ -5,7 +5,6 @@
 #include "third_party/blink/renderer/platform/loader/fetch/url_loader/worker_main_script_loader.h"
 
 #include "base/containers/span.h"
-#include "base/test/scoped_feature_list.h"
 #include "base/test/task_environment.h"
 #include "build/build_config.h"
 #include "mojo/public/cpp/bindings/remote.h"
@@ -13,7 +12,6 @@
 #include "net/http/http_util.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
-#include "third_party/blink/public/common/features.h"
 #include "third_party/blink/public/mojom/fetch/fetch_api_request.mojom-blink.h"
 #include "third_party/blink/public/mojom/loader/resource_load_info_notifier.mojom.h"
 #include "third_party/blink/public/mojom/navigation/renderer_eviction_reason.mojom-blink.h"
@@ -42,10 +40,7 @@
  public:
   WorkerMainScriptLoaderTest()
       : fake_loader_(pending_remote_loader_.InitWithNewPipeAndPassReceiver()),
-        client_(MakeGarbageCollected<TestClient>()) {
-    scoped_feature_list_.InitWithFeatureState(
-        blink::features::kPlzDedicatedWorker, true);
-  }
+        client_(MakeGarbageCollected<TestClient>()) {}
   ~WorkerMainScriptLoaderTest() override {
     // Forced GC in order to finalize objects depending on MockResourceObserver,
     // see details https://crbug.com/1132634.
@@ -218,7 +213,6 @@
   FakeURLLoader fake_loader_;
 
   Persistent<TestClient> client_;
-  base::test::ScopedFeatureList scoped_feature_list_;
 };
 
 TEST_F(WorkerMainScriptLoaderTest, ResponseWithSucessThenOnComplete) {
diff --git a/third_party/blink/renderer/platform/runtime_enabled_features.json5 b/third_party/blink/renderer/platform/runtime_enabled_features.json5
index cbf2451..405d946 100644
--- a/third_party/blink/renderer/platform/runtime_enabled_features.json5
+++ b/third_party/blink/renderer/platform/runtime_enabled_features.json5
@@ -318,7 +318,7 @@
       name: "AIPromptAPI",
       public: true,
       status: "test",
-      implied_by: ["AIPromptAPIForWebPlatform", "AIPromptAPIMultimodalInput"],
+      implied_by: ["AIPromptAPIForWebPlatform", "AIPromptAPIMultimodalInput", "AIPromptAPIStructuredOutput"],
     },
     {
       name: "AIPromptAPIForExtension",
@@ -340,6 +340,11 @@
       status: "test",
     },
     {
+      name: "AIPromptAPIStructuredOutput",
+      public: true,
+      status: "test",
+    },
+    {
       name: "AIRewriterAPI",
       public: true,
       status: "test",
@@ -650,11 +655,6 @@
       ]
     },
     {
-      // crbug.com/40681980
-      name: "ButtonNoAlignItems",
-      status: "stable",
-    },
-    {
       // Enables bypassing CSP checks when we consume a preload.
       // https://crbug.com/348442535
       name: "BypassCSPForPreloads",
@@ -1273,17 +1273,20 @@
       depends_on: ["PseudoElementsFocusable"],
     },
     {
-      // TODO(crbug.com/40932006): Non-standard 'reading-flow' keyword
-      // for reading of grid and flexbox items.
+      // TODO(crbug.com/40932006): CSS 'reading-flow' property for reading order
+      // of grid, flexbox and block items.
       // https://drafts.csswg.org/css-display-4/#reading-flow
+      // This should ship in M137, and it can be removed after M139.
       name: "CSSReadingFlow",
-      status: "experimental",
+      status: "stable",
     },
     {
-      // TODO(crbug.com/393550130): Non-standard 'reading-order' keyword
-      // to overwrite reading-flow order.
+      // TODO(crbug.com/393550130): CSS 'reading-order' property to overwrite
+      // reading-flow order.
+      // https://drafts.csswg.org/css-display-4/#reading-order
+      // This should ship in M137, and it can be removed after M139.
       name: "CSSReadingOrder",
-      status: "experimental",
+      status: "stable",
       depends_on: ["CSSReadingFlow"],
     },
     {
@@ -3981,10 +3984,13 @@
       depends_on: ["CustomizableSelect"],
     },
     {
-      // Implement Selection API across shadow DOM
-      // See https://w3c.github.io/selection-api/
+      // Implement Selection API across shadow DOM: direction and
+      // getComposedRanges().
+      // https://w3c.github.io/selection-api/#dom-selection-getcomposedranges
+      // https://w3c.github.io/selection-api/#dom-selection-direction
+      // This should ship in M137, and it can be removed after M139.
       name: "SelectionAcrossShadowDOM",
-      status: "experimental",
+      status: "stable",
     },
     {
       // See https://crbug.com/395544401
diff --git a/third_party/blink/tools/blinkpy/tool/commands/rebaseline.py b/third_party/blink/tools/blinkpy/tool/commands/rebaseline.py
index ff184d0..d8a437a 100644
--- a/third_party/blink/tools/blinkpy/tool/commands/rebaseline.py
+++ b/third_party/blink/tools/blinkpy/tool/commands/rebaseline.py
@@ -166,7 +166,7 @@
 
     def _test_can_have_suffix(self, test_name: str,
                               suffix: BaselineSuffix) -> bool:
-        wpt_type = self._get_wpt_type(test_name)
+        wpt_type = self._host_port.get_wpt_type(test_name)
         # Only legacy reftests can dump text output, not WPT reftests.
         if wpt_type in {'testharness', 'wdspec'} and suffix == 'txt':
             return True
@@ -179,13 +179,6 @@
         # No other WPT-suffix combinations are allowed.
         return not wpt_type
 
-    def _get_wpt_type(self, test_name: str) -> Optional[str]:
-        wpt_dir, url_from_wpt_dir = self._host_port.split_wpt_dir(test_name)
-        if not wpt_dir:
-            return None  # Not a WPT.
-        manifest = self._host_port.wpt_manifest(wpt_dir)
-        return manifest.get_test_type(url_from_wpt_dir)
-
 
 class ChangeSet(object):
     """A record of TestExpectation lines to remove.
diff --git a/third_party/blink/tools/blinkpy/w3c/wpt_expectations_updater.py b/third_party/blink/tools/blinkpy/w3c/wpt_expectations_updater.py
index 57b30ac..d45e9a2e 100644
--- a/third_party/blink/tools/blinkpy/w3c/wpt_expectations_updater.py
+++ b/third_party/blink/tools/blinkpy/w3c/wpt_expectations_updater.py
@@ -306,16 +306,11 @@
     def filter_results_for_update(
         self,
         test_results: WebTestResults,
-        min_attempts_for_update: int = 3,
     ) -> Tuple[Set[str], WebTestResults]:
         """Filters for failing test results that need TestExpectations.
 
         Arguments:
-            min_attempts_for_update: Threshold for the number of attempts at
-                which a test's expectations are updated. This prevents excessive
-                expectation creation due to infrastructure issues or flakiness.
-                Note that this threshold is necessary for updating without
-                `--include-unexpected-pass`, but sufficient otherwise.
+            test_results: All web test results on a given build step.
 
         Returns:
             * A set of tests that should be handled by rebaselining, which
@@ -328,14 +323,11 @@
         if not self.options.include_unexpected_pass:
             # TODO(crbug.com/1149035): Consider suppressing flaky passes.
             for result in test_results.didnt_run_as_expected_results():
-                if (result.attempts >= min_attempts_for_update
-                        and not result.did_pass()):
+                if not result.did_pass():
                     failing_results.append(result)
         else:
             for result in test_results.didnt_run_as_expected_results():
-                if (result.attempts >= min_attempts_for_update
-                        or result.did_pass()):
-                    failing_results.append(result)
+                failing_results.append(result)
 
         failing_results = [
             result for result in failing_results
@@ -701,23 +693,21 @@
 
     def can_rebaseline(self, result: WebTestResult) -> bool:
         """Checks if a test can be rebaselined."""
-        if self.is_reference_test(result.test_name()):
+        if not self.port.get_wpt_type(
+                result.test_name()) in {'testharness', 'wdspec'}:
             return False
         statuses = set(result.actual_results())
         if {ResultType.Pass, ResultType.Failure} <= statuses:
             return False  # Has nondeterministic output; cannot be rebaselined.
-        return not (statuses & {ResultType.Crash, ResultType.Timeout})
+        return ResultType.Failure in statuses
 
     def is_reference_test(self, test_name: str) -> bool:
         """Checks whether a given test is a reference test."""
         return bool(self.port.reference_files(test_name))
 
     @memoized
-    def _get_try_bots(self, flag_specific: Optional[str] = None):
-        builders = self.host.builders.filter_builders(
-            is_try=True,
-            exclude_specifiers={'android'},
-            flag_specific=flag_specific)
+    def _get_try_bots(self):
+        builders = self.host.builders.filter_builders(is_try=True)
         # Exclude CQ builders like `win-rel`.
         return sorted(
             set(builders) & self.host.builders.builders_for_rebaselining())
diff --git a/third_party/blink/tools/blinkpy/w3c/wpt_expectations_updater_unittest.py b/third_party/blink/tools/blinkpy/w3c/wpt_expectations_updater_unittest.py
index 22bdd3ce..831d990 100644
--- a/third_party/blink/tools/blinkpy/w3c/wpt_expectations_updater_unittest.py
+++ b/third_party/blink/tools/blinkpy/w3c/wpt_expectations_updater_unittest.py
@@ -448,17 +448,19 @@
         _, filtered_results = updater.filter_results_for_update(results)
         self.assertEqual(0, len(filtered_results))
 
-    def test_filter_results_for_update_not_retried_test(self):
+    def test_not_filter_results_for_update_not_retried_test(self):
+        # Wptrunner will not retry when some subtests unexpectedly pass.
+        # Such tests should still be rebaselined.
         host = self.mock_host()
         results = WebTestResults.from_rdb_responses(
-            {'external/wpt/reftest.html': [{
+            {'external/wpt/test/zzzz.html': [{
                 'status': 'FAIL',
             }]},
             step_name='blink_wpt_tests',
             build=Build('MOCK Try Mac10.10'))
         updater = WPTExpectationsUpdater(host)
-        _, filtered_results = updater.filter_results_for_update(results)
-        self.assertEqual(0, len(filtered_results))
+        tests_to_rebaseline, _ = updater.filter_results_for_update(results)
+        self.assertEqual(1, len(tests_to_rebaseline))
 
     def test_remove_configurations(self):
         host = self.mock_host()
@@ -1066,17 +1068,17 @@
         host = self.mock_host()
         updater = WPTExpectationsUpdater(host)
         raw_results = WebTestResults.from_rdb_responses({
-            'external/wpt/x/y.html': [{
+            'external/wpt/test/path.html': [{
                 'status': 'PASS',
             }] * 3,
-            'external/wpt/x/z.html': [{
+            'external/wpt/test/zzzz.html': [{
                 'status': 'FAIL',
             }] * 3,
         })
         tests_to_rebaseline, results = updater.filter_results_for_update(
             raw_results)
         self.assertEqual(len(results), 0)
-        self.assertEqual(tests_to_rebaseline, {'external/wpt/x/z.html'})
+        self.assertEqual(tests_to_rebaseline, {'external/wpt/test/zzzz.html'})
 
     def test_get_test_to_rebaseline_does_not_return_ref_tests(self):
         host = self.mock_host()
@@ -1104,20 +1106,22 @@
         host = self.mock_host()
         updater = WPTExpectationsUpdater(host)
         raw_results = WebTestResults.from_rdb_responses({
-            'external/wpt/x/y.html': [{
+            'external/wpt/test/path.html': [{
                 'status': 'FAIL',
             }] * 3,
-            'external/wpt/x/z.html': [{
+            'external/wpt/reftest.html': [{
                 'status': 'ABORT',
             }] * 3,
         })
 
-        tests_to_rebaseline, (
-            result, ) = updater.filter_results_for_update(raw_results)
-        self.assertEqual(tests_to_rebaseline, {'external/wpt/x/y.html'})
+        tests_to_rebaseline, results = updater.filter_results_for_update(
+            raw_results)
+        self.assertEqual(tests_to_rebaseline, {'external/wpt/test/path.html'})
         # The record for the builder with a timeout is kept, but not with a text mismatch,
         # since that should be covered by downloading a new baseline.
-        self.assertEqual(result.test_name(), 'external/wpt/x/z.html')
+        self.assertEqual(len(results), 1)
+        (result, ) = results
+        self.assertEqual(result.test_name(), 'external/wpt/reftest.html')
         # The original container isn't modified.
         self.assertEqual(len(raw_results), 2)
 
diff --git a/third_party/blink/tools/blinkpy/web_tests/port/base.py b/third_party/blink/tools/blinkpy/web_tests/port/base.py
index c7e1607..4e38d40e 100644
--- a/third_party/blink/tools/blinkpy/web_tests/port/base.py
+++ b/third_party/blink/tools/blinkpy/web_tests/port/base.py
@@ -1604,6 +1604,16 @@
             return self._filesystem.isfile(self.abspath_for_test(path))
         return False
 
+    def get_wpt_type(self, test_name: str) -> Optional[str]:
+        """Returns the test type of a web platform test."""
+
+        base_test = self.lookup_virtual_test_base(test_name) or test_name
+        wpt_dir, url_from_wpt_dir = self.split_wpt_dir(base_test)
+        if not wpt_dir:
+            return None  # Not a WPT.
+        manifest = self.wpt_manifest(wpt_dir)
+        return manifest.get_test_type(url_from_wpt_dir)
+
     def is_wpt_crash_test(self, test_name):
         """Returns whether a WPT test is a crashtest.
 
diff --git a/third_party/blink/web_tests/CfTTestExpecations b/third_party/blink/web_tests/CfTTestExpecations
index 818a625..f3c38d151 100644
--- a/third_party/blink/web_tests/CfTTestExpecations
+++ b/third_party/blink/web_tests/CfTTestExpecations
@@ -5,4 +5,8 @@
 # This file is used for tests that only need to be suppressed on
 # the Chromium Blink CfT bots.
 
-crbug.com/380778943 external/wpt/ai/translator/* [ Skip ]
\ No newline at end of file
+crbug.com/380778943 external/wpt/ai/translator/* [ Skip ]
+
+# These tests require the component updater and thus do not run on CfT bots.
+external/wpt/speech-api/SpeechRecognition-installOnDevice.https.html [ Skip Timeout ]
+virtual/speech-with-unified-autoplay/external/wpt/speech-api/SpeechRecognition-installOnDevice.https.html [ Skip Timeout ]
\ No newline at end of file
diff --git a/third_party/blink/web_tests/TestExpectations b/third_party/blink/web_tests/TestExpectations
index 713eb0c9..a5bed9d 100644
--- a/third_party/blink/web_tests/TestExpectations
+++ b/third_party/blink/web_tests/TestExpectations
@@ -7394,9 +7394,6 @@
 crbug.com/1464614 [ Win11-arm64 ] virtual/pna-workers-enabled/external/wpt/fetch/private-network-access/redirect.tentative.https.window.html [ Failure Pass Timeout ] # Flaky
 crbug.com/1464614 [ Win11-arm64 ] http/tests/media/video-play-stall-before-meta-data.html [ Failure Pass Timeout ] # Flaky
 
-# Gardener 2023-07-26
-crbug.com/1364187 http/tests/misc/scroll-cross-origin-iframes.html [ Failure Pass ]
-
 # Gardener 2023-07-31
 crbug.com/1468996 fast/events/touch/gesture/gesture-tap-active-state.html [ Failure Pass ] # flaky
 crbug.com/1468996 fast/events/touch/gesture/gesture-tap-active-state-hidden-iframe.html [ Failure Pass ] # flaky
diff --git a/third_party/blink/web_tests/external/wpt/WebCryptoAPI/import_export/okp_importKey_Ed25519.https.any-expected.txt b/third_party/blink/web_tests/external/wpt/WebCryptoAPI/import_export/okp_importKey_Ed25519.https.any-expected.txt
deleted file mode 100644
index be7b16f..0000000
--- a/third_party/blink/web_tests/external/wpt/WebCryptoAPI/import_export/okp_importKey_Ed25519.https.any-expected.txt
+++ /dev/null
@@ -1,44 +0,0 @@
-This is a testharness.js-based test.
-Found 20 FAIL, 0 TIMEOUT, 0 NOTRUN.
-[FAIL] Good parameters with JWK alg Ed25519: Ed25519 (jwk, object(kty, crv, x), {name: Ed25519}, true, [verify])
-  assert_unreached: Threw an unexpected error: DataError: The JWK "alg" member was inconsistent with that specified by the Web Crypto call Reached unreachable code
-[FAIL] Good parameters with JWK alg EdDSA: Ed25519 (jwk, object(kty, crv, x), {name: Ed25519}, true, [verify])
-  assert_equals: Correct number of JWK members expected 6 but got 5
-[FAIL] Good parameters with JWK alg Ed25519: Ed25519 (jwk, object(kty, crv, x), Ed25519, true, [verify])
-  assert_unreached: Threw an unexpected error: DataError: The JWK "alg" member was inconsistent with that specified by the Web Crypto call Reached unreachable code
-[FAIL] Good parameters with JWK alg EdDSA: Ed25519 (jwk, object(kty, crv, x), Ed25519, true, [verify])
-  assert_equals: Correct number of JWK members expected 6 but got 5
-[FAIL] Good parameters with JWK alg Ed25519: Ed25519 (jwk, object(kty, crv, x), {name: Ed25519}, true, [])
-  assert_unreached: Threw an unexpected error: DataError: The JWK "alg" member was inconsistent with that specified by the Web Crypto call Reached unreachable code
-[FAIL] Good parameters with JWK alg EdDSA: Ed25519 (jwk, object(kty, crv, x), {name: Ed25519}, true, [])
-  assert_equals: Correct number of JWK members expected 6 but got 5
-[FAIL] Good parameters with JWK alg Ed25519: Ed25519 (jwk, object(kty, crv, x), Ed25519, true, [])
-  assert_unreached: Threw an unexpected error: DataError: The JWK "alg" member was inconsistent with that specified by the Web Crypto call Reached unreachable code
-[FAIL] Good parameters with JWK alg EdDSA: Ed25519 (jwk, object(kty, crv, x), Ed25519, true, [])
-  assert_equals: Correct number of JWK members expected 6 but got 5
-[FAIL] Good parameters with JWK alg Ed25519: Ed25519 (jwk, object(kty, crv, x), {name: Ed25519}, true, [verify, verify])
-  assert_unreached: Threw an unexpected error: DataError: The JWK "alg" member was inconsistent with that specified by the Web Crypto call Reached unreachable code
-[FAIL] Good parameters with JWK alg EdDSA: Ed25519 (jwk, object(kty, crv, x), {name: Ed25519}, true, [verify, verify])
-  assert_equals: Correct number of JWK members expected 6 but got 5
-[FAIL] Good parameters with JWK alg Ed25519: Ed25519 (jwk, object(kty, crv, x), Ed25519, true, [verify, verify])
-  assert_unreached: Threw an unexpected error: DataError: The JWK "alg" member was inconsistent with that specified by the Web Crypto call Reached unreachable code
-[FAIL] Good parameters with JWK alg EdDSA: Ed25519 (jwk, object(kty, crv, x), Ed25519, true, [verify, verify])
-  assert_equals: Correct number of JWK members expected 6 but got 5
-[FAIL] Good parameters with JWK alg Ed25519: Ed25519 (jwk, object(crv, d, x, kty), {name: Ed25519}, true, [sign])
-  assert_unreached: Threw an unexpected error: DataError: The JWK "alg" member was inconsistent with that specified by the Web Crypto call Reached unreachable code
-[FAIL] Good parameters with JWK alg EdDSA: Ed25519 (jwk, object(crv, d, x, kty), {name: Ed25519}, true, [sign])
-  assert_equals: Correct number of JWK members expected 7 but got 6
-[FAIL] Good parameters with JWK alg Ed25519: Ed25519 (jwk, object(crv, d, x, kty), Ed25519, true, [sign])
-  assert_unreached: Threw an unexpected error: DataError: The JWK "alg" member was inconsistent with that specified by the Web Crypto call Reached unreachable code
-[FAIL] Good parameters with JWK alg EdDSA: Ed25519 (jwk, object(crv, d, x, kty), Ed25519, true, [sign])
-  assert_equals: Correct number of JWK members expected 7 but got 6
-[FAIL] Good parameters with JWK alg Ed25519: Ed25519 (jwk, object(crv, d, x, kty), {name: Ed25519}, true, [sign, sign])
-  assert_unreached: Threw an unexpected error: DataError: The JWK "alg" member was inconsistent with that specified by the Web Crypto call Reached unreachable code
-[FAIL] Good parameters with JWK alg EdDSA: Ed25519 (jwk, object(crv, d, x, kty), {name: Ed25519}, true, [sign, sign])
-  assert_equals: Correct number of JWK members expected 7 but got 6
-[FAIL] Good parameters with JWK alg Ed25519: Ed25519 (jwk, object(crv, d, x, kty), Ed25519, true, [sign, sign])
-  assert_unreached: Threw an unexpected error: DataError: The JWK "alg" member was inconsistent with that specified by the Web Crypto call Reached unreachable code
-[FAIL] Good parameters with JWK alg EdDSA: Ed25519 (jwk, object(crv, d, x, kty), Ed25519, true, [sign, sign])
-  assert_equals: Correct number of JWK members expected 7 but got 6
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/external/wpt/WebCryptoAPI/import_export/okp_importKey_Ed25519.https.any.worker-expected.txt b/third_party/blink/web_tests/external/wpt/WebCryptoAPI/import_export/okp_importKey_Ed25519.https.any.worker-expected.txt
deleted file mode 100644
index be7b16f..0000000
--- a/third_party/blink/web_tests/external/wpt/WebCryptoAPI/import_export/okp_importKey_Ed25519.https.any.worker-expected.txt
+++ /dev/null
@@ -1,44 +0,0 @@
-This is a testharness.js-based test.
-Found 20 FAIL, 0 TIMEOUT, 0 NOTRUN.
-[FAIL] Good parameters with JWK alg Ed25519: Ed25519 (jwk, object(kty, crv, x), {name: Ed25519}, true, [verify])
-  assert_unreached: Threw an unexpected error: DataError: The JWK "alg" member was inconsistent with that specified by the Web Crypto call Reached unreachable code
-[FAIL] Good parameters with JWK alg EdDSA: Ed25519 (jwk, object(kty, crv, x), {name: Ed25519}, true, [verify])
-  assert_equals: Correct number of JWK members expected 6 but got 5
-[FAIL] Good parameters with JWK alg Ed25519: Ed25519 (jwk, object(kty, crv, x), Ed25519, true, [verify])
-  assert_unreached: Threw an unexpected error: DataError: The JWK "alg" member was inconsistent with that specified by the Web Crypto call Reached unreachable code
-[FAIL] Good parameters with JWK alg EdDSA: Ed25519 (jwk, object(kty, crv, x), Ed25519, true, [verify])
-  assert_equals: Correct number of JWK members expected 6 but got 5
-[FAIL] Good parameters with JWK alg Ed25519: Ed25519 (jwk, object(kty, crv, x), {name: Ed25519}, true, [])
-  assert_unreached: Threw an unexpected error: DataError: The JWK "alg" member was inconsistent with that specified by the Web Crypto call Reached unreachable code
-[FAIL] Good parameters with JWK alg EdDSA: Ed25519 (jwk, object(kty, crv, x), {name: Ed25519}, true, [])
-  assert_equals: Correct number of JWK members expected 6 but got 5
-[FAIL] Good parameters with JWK alg Ed25519: Ed25519 (jwk, object(kty, crv, x), Ed25519, true, [])
-  assert_unreached: Threw an unexpected error: DataError: The JWK "alg" member was inconsistent with that specified by the Web Crypto call Reached unreachable code
-[FAIL] Good parameters with JWK alg EdDSA: Ed25519 (jwk, object(kty, crv, x), Ed25519, true, [])
-  assert_equals: Correct number of JWK members expected 6 but got 5
-[FAIL] Good parameters with JWK alg Ed25519: Ed25519 (jwk, object(kty, crv, x), {name: Ed25519}, true, [verify, verify])
-  assert_unreached: Threw an unexpected error: DataError: The JWK "alg" member was inconsistent with that specified by the Web Crypto call Reached unreachable code
-[FAIL] Good parameters with JWK alg EdDSA: Ed25519 (jwk, object(kty, crv, x), {name: Ed25519}, true, [verify, verify])
-  assert_equals: Correct number of JWK members expected 6 but got 5
-[FAIL] Good parameters with JWK alg Ed25519: Ed25519 (jwk, object(kty, crv, x), Ed25519, true, [verify, verify])
-  assert_unreached: Threw an unexpected error: DataError: The JWK "alg" member was inconsistent with that specified by the Web Crypto call Reached unreachable code
-[FAIL] Good parameters with JWK alg EdDSA: Ed25519 (jwk, object(kty, crv, x), Ed25519, true, [verify, verify])
-  assert_equals: Correct number of JWK members expected 6 but got 5
-[FAIL] Good parameters with JWK alg Ed25519: Ed25519 (jwk, object(crv, d, x, kty), {name: Ed25519}, true, [sign])
-  assert_unreached: Threw an unexpected error: DataError: The JWK "alg" member was inconsistent with that specified by the Web Crypto call Reached unreachable code
-[FAIL] Good parameters with JWK alg EdDSA: Ed25519 (jwk, object(crv, d, x, kty), {name: Ed25519}, true, [sign])
-  assert_equals: Correct number of JWK members expected 7 but got 6
-[FAIL] Good parameters with JWK alg Ed25519: Ed25519 (jwk, object(crv, d, x, kty), Ed25519, true, [sign])
-  assert_unreached: Threw an unexpected error: DataError: The JWK "alg" member was inconsistent with that specified by the Web Crypto call Reached unreachable code
-[FAIL] Good parameters with JWK alg EdDSA: Ed25519 (jwk, object(crv, d, x, kty), Ed25519, true, [sign])
-  assert_equals: Correct number of JWK members expected 7 but got 6
-[FAIL] Good parameters with JWK alg Ed25519: Ed25519 (jwk, object(crv, d, x, kty), {name: Ed25519}, true, [sign, sign])
-  assert_unreached: Threw an unexpected error: DataError: The JWK "alg" member was inconsistent with that specified by the Web Crypto call Reached unreachable code
-[FAIL] Good parameters with JWK alg EdDSA: Ed25519 (jwk, object(crv, d, x, kty), {name: Ed25519}, true, [sign, sign])
-  assert_equals: Correct number of JWK members expected 7 but got 6
-[FAIL] Good parameters with JWK alg Ed25519: Ed25519 (jwk, object(crv, d, x, kty), Ed25519, true, [sign, sign])
-  assert_unreached: Threw an unexpected error: DataError: The JWK "alg" member was inconsistent with that specified by the Web Crypto call Reached unreachable code
-[FAIL] Good parameters with JWK alg EdDSA: Ed25519 (jwk, object(crv, d, x, kty), Ed25519, true, [sign, sign])
-  assert_equals: Correct number of JWK members expected 7 but got 6
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/external/wpt/ai/language_detection/detector.https.tentative.any.js b/third_party/blink/web_tests/external/wpt/ai/language_detection/detector.https.tentative.any.js
index 57c98ab..8e4bedd 100644
--- a/third_party/blink/web_tests/external/wpt/ai/language_detection/detector.https.tentative.any.js
+++ b/third_party/blink/web_tests/external/wpt/ai/language_detection/detector.https.tentative.any.js
@@ -23,25 +23,7 @@
 }, 'Simple LanguageDetector.detect() call');
 
 promise_test(async t => {
-  let createResult = undefined;
-  const progressEvents = [];
-  let options = {};
-  const downloadComplete = new Promise(resolve => {
-    options.monitor = (m) => {
-      m.addEventListener("downloadprogress", e => {
-        assert_equals(createResult, undefined);
-        assert_equals(e.total, 1);
-        progressEvents.push(e);
-        if (e.loaded == 1) { resolve(); }
-      });
-    };
-  });
-
-  createResult = await LanguageDetector.create(options);
-  await downloadComplete;
-  assert_greater_than_equal(progressEvents.length, 2);
-  assert_equals(progressEvents.at(0).loaded, 0);
-  assert_equals(progressEvents.at(-1).loaded, 1);
+  testMonitor(LanguageDetector.create);
 }, 'LanguageDetector.create() notifies its monitor on downloadprogress');
 
 promise_test(async t => {
@@ -114,9 +96,8 @@
 }, 'Aborting LanguageDetector.measureInputUsage().');
 
 promise_test(async () => {
-  const expected_languages = ['en', 'es'];
-  const detector = await languageDetector.create({
-    expectedInputLanguages: expected_languages
-  });
-  assert_array_equals(detector.expectedInputLanguages(), expected_languages);
+  const expectedLanguages = ['en', 'es'];
+  const detector = await LanguageDetector.create(
+      {expectedInputLanguages: expectedLanguages});
+  assert_array_equals(detector.expectedInputLanguages, expectedLanguages);
 }, 'Creating LanguageDetector with expectedInputLanguages');
diff --git a/third_party/blink/web_tests/external/wpt/ai/resources/util.js b/third_party/blink/web_tests/external/wpt/ai/resources/util.js
index 49d677e..6433404 100644
--- a/third_party/blink/web_tests/external/wpt/ai/resources/util.js
+++ b/third_party/blink/web_tests/external/wpt/ai/resources/util.js
@@ -25,3 +25,33 @@
     await promise_rejects_exactly(t, err, anotherPromise);
   }
 };
+
+async function testMonitor(createFunc, options = {}) {
+  let created = false;
+  const progressEvents = [];
+  function monitor(m) {
+    m.addEventListener('downloadprogress', e => {
+      // No progress events should be fired after `createFunc` resolves.
+      assert_false(created);
+
+      progressEvents.push(e);
+    });
+  }
+
+  await createFunc({...options, monitor});
+  created = true;
+
+  assert_greater_than_equal(progressEvents.length, 2);
+  assert_equals(progressEvents.at(0).loaded, 0);
+  assert_equals(progressEvents.at(-1).loaded, 1);
+
+  let lastProgressEventLoaded = -1;
+  for (const progressEvent of progressEvents) {
+    assert_equals(progressEvent.total, 1);
+    assert_less_than_equal(progressEvent.loaded, progressEvent.total);
+
+    // Progress events should have monotonically increasing `loaded` values.
+    assert_greater_than(progressEvent.loaded, lastProgressEventLoaded);
+    lastProgressEventLoaded = progressEvent.loaded;
+  }
+}
diff --git a/third_party/blink/web_tests/external/wpt/ai/translator/translator_translate.tentative.https.any.js b/third_party/blink/web_tests/external/wpt/ai/translator/translator_translate.tentative.https.any.js
index 1c58675..a8aad5e 100644
--- a/third_party/blink/web_tests/external/wpt/ai/translator/translator_translate.tentative.https.any.js
+++ b/third_party/blink/web_tests/external/wpt/ai/translator/translator_translate.tentative.https.any.js
@@ -108,25 +108,8 @@
 }, 'Aborting Translator.translate().');
 
 promise_test(async t => {
-  let createResult = undefined;
-  const progressEvents = [];
-  let options = {sourceLanguage: 'en', targetLanguage: 'ja'};
-  const downloadComplete = new Promise(resolve => {
-    options.monitor = (m) => {
-      m.addEventListener("downloadprogress", e => {
-        assert_equals(createResult, undefined);
-        assert_equals(e.total, 1);
-        progressEvents.push(e);
-        if (e.loaded == 1) { resolve(); }
-      });
-    };
-  });
-
-  createResult = await createTranslator(options);
-  await downloadComplete;
-  assert_greater_than_equal(progressEvents.length, 2);
-  assert_equals(progressEvents.at(0).loaded, 0);
-  assert_equals(progressEvents.at(-1).loaded, 1);
+  await testMonitor(
+      createTranslator, {sourceLanguage: 'en', targetLanguage: 'ja'});
 }, 'Translator.create() notifies its monitor on downloadprogress');
 
 promise_test(async t => {
diff --git a/third_party/blink/web_tests/external/wpt/css/css-gaps/tentative/grid/grid-gap-decorations-016-ref.html b/third_party/blink/web_tests/external/wpt/css/css-gaps/tentative/grid/grid-gap-decorations-016-ref.html
new file mode 100644
index 0000000..1d368ab
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-gaps/tentative/grid/grid-gap-decorations-016-ref.html
@@ -0,0 +1,111 @@
+<!DOCTYPE html>
+<link rel="help" href="https://drafts.csswg.org/css-gaps-1/#lists-repeat">
+<link rel="author" title="Sam Davis Omekara Jr." href="mailto:samomekarajr@microsoft.com">
+<style>
+  body {
+    margin: 0px;
+  }
+
+  .grid-container {
+    display: grid;
+    grid-gap: 10px;
+    grid-template-columns: repeat(5, 100px);
+    height: 540px;
+    width: 540px;
+  }
+
+  .row-gap {
+    height: 0px;
+    width: 540px;
+    position: absolute;
+  }
+
+  .row-gap1 {
+    border-bottom: 5px dashed red;
+    top: 102.5px;
+  }
+
+  .row-gap2 {
+    border-bottom: 5px groove red;
+    top: 212.5px;
+  }
+
+  .row-gap3 {
+    border-bottom: 5px ridge red;
+    top: 322.5px;
+  }
+
+  .row-gap4 {
+    border-bottom: 5px dashed red;
+    top: 432.5px;
+  }
+
+  .col-gap {
+    width: 0px;
+    height: 540px;
+    position: absolute;
+    top: 0px;
+  }
+
+  .col-gap1 {
+    border-left: 5px solid blue;
+    left: 102.5px;
+  }
+
+  .col-gap2 {
+    border-left: 5px dotted blue;
+    left: 212.5px;
+  }
+
+  .col-gap3 {
+    border-left: 5px double blue;
+    left: 322.5px;
+  }
+
+  .col-gap4 {
+    border-left: 5px solid blue;
+    left: 432.5px;
+  }
+
+  .item {
+    background: gray;
+    opacity: 0.5;
+  }
+</style>
+<div class="grid-container">
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+</div>
+
+<div class="row-gap row-gap1"> </div>
+<div class="row-gap row-gap2"> </div>
+<div class="row-gap row-gap3"> </div>
+<div class="row-gap row-gap4"> </div>
+
+<div class="col-gap col-gap1"> </div>
+<div class="col-gap col-gap2"> </div>
+<div class="col-gap col-gap3"> </div>
+<div class="col-gap col-gap4"> </div>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-gaps/tentative/grid/grid-gap-decorations-016.html b/third_party/blink/web_tests/external/wpt/css/css-gaps/tentative/grid/grid-gap-decorations-016.html
new file mode 100644
index 0000000..a0618e44
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-gaps/tentative/grid/grid-gap-decorations-016.html
@@ -0,0 +1,60 @@
+<!DOCTYPE html>
+<title>
+  CSS Gap Decorations: Grid gaps are painted with multiple line-style values for *-rule-style.
+</title>
+<link rel="help" href="https://drafts.csswg.org/css-gaps-1/#lists-repeat">
+<link rel="match" href="grid-gap-decorations-016-ref.html">
+<link rel="author" title="Sam Davis Omekara Jr." href="mailto:samomekarajr@microsoft.com">
+<style>
+  body {
+    margin: 0px;
+  }
+
+  .grid-container {
+    display: grid;
+    grid-gap: 10px;
+    grid-template-columns: repeat(5, 100px);
+    height: 540px;
+    width: 540px;
+
+    column-rule-color: blue;
+    column-rule-style: solid dotted double;
+    column-rule-width: 5px;
+
+    row-rule-color: red;
+    row-rule-style: dashed groove ridge;
+    row-rule-width: 5px;
+  }
+
+  .item {
+    background: gray;
+    opacity: 0.5;
+  }
+</style>
+<div class="grid-container">
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+</div>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-gaps/tentative/grid/grid-gap-decorations-017-ref.html b/third_party/blink/web_tests/external/wpt/css/css-gaps/tentative/grid/grid-gap-decorations-017-ref.html
new file mode 100644
index 0000000..2aa226eb
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-gaps/tentative/grid/grid-gap-decorations-017-ref.html
@@ -0,0 +1,135 @@
+<!DOCTYPE html>
+<link rel="help" href="https://drafts.csswg.org/css-gaps-1/#lists-repeat">
+<link rel="author" title="Sam Davis Omekara Jr." href="mailto:samomekarajr@microsoft.com">
+<style>
+  body {
+    margin: 0px;
+  }
+
+  .grid-container {
+    display: grid;
+    grid-gap: 10px;
+    grid-template-columns: repeat(6, 100px);
+    height: 650px;
+    width: 650px;
+  }
+
+  .row-gap {
+    height: 0px;
+    width: 650px;
+    position: absolute;
+
+  }
+
+  .row-gap1 {
+    border-bottom: 5px double red;
+    top: 102.5px;
+  }
+
+  .row-gap2 {
+    border-bottom: 5px solid red;
+    top: 212.5px;
+  }
+
+  .row-gap3 {
+    border-bottom: 5px dotted red;
+    top: 322.5px;
+  }
+
+  .row-gap4 {
+    border-bottom: 5px ridge red;
+    top: 432.5px;
+  }
+
+  .row-gap5 {
+    border-bottom: 5px ridge red;
+    top: 542.5px;
+  }
+
+  .col-gap {
+    width: 0px;
+    height: 650px;
+    position: absolute;
+    top: 0px;
+  }
+
+  .col-gap1 {
+    border-left: 5px solid blue;
+    left: 102.5px;
+  }
+
+  .col-gap2 {
+    border-left: 5px groove blue;
+    left: 212.5px;
+  }
+
+  .col-gap3 {
+    border-left: 5px double blue;
+    left: 322.5px;
+  }
+
+  .col-gap4 {
+    border-left: 5px dotted blue;
+    left: 432.5px;
+  }
+
+  .col-gap5 {
+    border-left: 5px dotted blue;
+    left: 542.5px;
+  }
+
+  .item {
+    background: gray;
+    opacity: 0.5;
+  }
+</style>
+<div class="grid-container">
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+</div>
+
+<div class="row-gap row-gap1"> </div>
+<div class="row-gap row-gap2"> </div>
+<div class="row-gap row-gap3"> </div>
+<div class="row-gap row-gap4"> </div>
+<div class="row-gap row-gap5"> </div>
+
+<div class="col-gap col-gap1"> </div>
+<div class="col-gap col-gap2"> </div>
+<div class="col-gap col-gap3"> </div>
+<div class="col-gap col-gap4"> </div>
+<div class="col-gap col-gap5"> </div>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-gaps/tentative/grid/grid-gap-decorations-017.html b/third_party/blink/web_tests/external/wpt/css/css-gaps/tentative/grid/grid-gap-decorations-017.html
new file mode 100644
index 0000000..be0f7fc
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-gaps/tentative/grid/grid-gap-decorations-017.html
@@ -0,0 +1,69 @@
+<!DOCTYPE html>
+<title>
+  CSS Gap Decorations: Grid gaps are painted with multiple line-style values for *-rule-style.
+</title>
+<link rel="help" href="https://drafts.csswg.org/css-gaps-1/#lists-repeat">
+<link rel="match" href="grid-gap-decorations-017-ref.html">
+<link rel="author" title="Sam Davis Omekara Jr." href="mailto:samomekarajr@microsoft.com">
+<style>
+  body {
+    margin: 0px;
+  }
+  .grid-container {
+    display: grid;
+    grid-gap: 10px;
+    grid-template-columns: repeat(6, 100px);
+    height: 650px;
+    width: 650px;
+
+    column-rule-color: blue;
+    column-rule-style: solid repeat(auto, groove double) repeat(2, dotted);
+    column-rule-width: 5px;
+
+    row-rule-color: red;
+    row-rule-style: repeat(auto, double solid) dotted repeat(2, ridge);
+    row-rule-width: 5px;
+  }
+  .item {
+    background: gray;
+    opacity: 0.5;
+  }
+</style>
+<div class="grid-container">
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+</div>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-gaps/tentative/grid/grid-gap-decorations-018-ref.html b/third_party/blink/web_tests/external/wpt/css/css-gaps/tentative/grid/grid-gap-decorations-018-ref.html
new file mode 100644
index 0000000..6fcc175
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-gaps/tentative/grid/grid-gap-decorations-018-ref.html
@@ -0,0 +1,134 @@
+<!DOCTYPE html>
+<link rel="help" href="https://drafts.csswg.org/css-gaps-1/#lists-repeat">
+<link rel="author" title="Sam Davis Omekara Jr." href="mailto:samomekarajr@microsoft.com">
+<style>
+  body {
+    margin: 0px;
+  }
+
+  .grid-container {
+    display: grid;
+    grid-gap: 10px;
+    grid-template-columns: repeat(6, 100px);
+    height: 650px;
+    width: 650px;
+  }
+
+  .row-gap {
+    height: 0px;
+    width: 650px;
+    position: absolute;
+  }
+
+  .row-gap1 {
+    border-bottom: 8px solid red;
+    top: 101px;
+  }
+
+  .row-gap2 {
+    border-bottom: 5px solid red;
+    top: 212.5px;
+  }
+
+  .row-gap3 {
+    border-bottom: 2px solid red;
+    top: 324px;
+  }
+
+  .row-gap4 {
+    border-bottom: 8px solid red;
+    top: 431px;
+  }
+
+  .row-gap5 {
+    border-bottom: 5px solid red;
+    top: 542.5px;
+  }
+
+  .col-gap {
+    width: 0px;
+    height: 650px;
+    position: absolute;
+    top: 0px;
+  }
+
+  .col-gap1 {
+    border-left: 2px solid blue;
+    left: 104px;
+  }
+
+  .col-gap2 {
+    border-left: 5px solid blue;
+    left: 212.5px;
+  }
+
+  .col-gap3 {
+    border-left: 8px solid blue;
+    left: 321px;
+  }
+
+  .col-gap4 {
+    border-left: 2px solid blue;
+    left: 434px;
+  }
+
+  .col-gap5 {
+    border-left: 5px solid blue;
+    left: 542.5px;
+  }
+
+  .item {
+    background: gray;
+    opacity: 0.5;
+  }
+</style>
+<div class="grid-container">
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+</div>
+
+<div class="row-gap row-gap1"> </div>
+<div class="row-gap row-gap2"> </div>
+<div class="row-gap row-gap3"> </div>
+<div class="row-gap row-gap4"> </div>
+<div class="row-gap row-gap5"> </div>
+
+<div class="col-gap col-gap1"> </div>
+<div class="col-gap col-gap2"> </div>
+<div class="col-gap col-gap3"> </div>
+<div class="col-gap col-gap4"> </div>
+<div class="col-gap col-gap5"> </div>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-gaps/tentative/grid/grid-gap-decorations-018.html b/third_party/blink/web_tests/external/wpt/css/css-gaps/tentative/grid/grid-gap-decorations-018.html
new file mode 100644
index 0000000..dc5e2e3
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-gaps/tentative/grid/grid-gap-decorations-018.html
@@ -0,0 +1,71 @@
+<!DOCTYPE html>
+<title>
+  CSS Gap Decorations: Grid gaps are painted with multiple line-width values for *-rule-width.
+</title>
+<link rel="help" href="https://drafts.csswg.org/css-gaps-1/#lists-repeat">
+<link rel="match" href="grid-gap-decorations-018-ref.html">
+<link rel="author" title="Sam Davis Omekara Jr." href="mailto:samomekarajr@microsoft.com">
+<style>
+  body {
+    margin: 0px;
+  }
+
+  .grid-container {
+    display: grid;
+    grid-gap: 10px;
+    grid-template-columns: repeat(6, 100px);
+    height: 650px;
+    width: 650px;
+
+    column-rule-color: blue;
+    column-rule-style: solid;
+    column-rule-width: 2px 5px 8px;
+
+    row-rule-color: red;
+    row-rule-style: solid;
+    row-rule-width: 8px 5px 2px;
+  }
+
+  .item {
+    background: gray;
+    opacity: 0.5;
+  }
+</style>
+<div class="grid-container">
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+</div>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-gaps/tentative/grid/grid-gap-decorations-019-ref.html b/third_party/blink/web_tests/external/wpt/css/css-gaps/tentative/grid/grid-gap-decorations-019-ref.html
new file mode 100644
index 0000000..0705ba07
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-gaps/tentative/grid/grid-gap-decorations-019-ref.html
@@ -0,0 +1,137 @@
+<!DOCTYPE html>
+<link rel="help" href="https://drafts.csswg.org/css-gaps-1/#lists-repeat">
+<link rel="author" title="Sam Davis Omekara Jr." href="mailto:samomekarajr@microsoft.com">
+<style>
+  body {
+    margin: 0px;
+  }
+
+  .grid-container {
+    display: grid;
+    grid-gap: 10px;
+    grid-template-columns: repeat(6, 100px);
+    height: 650px;
+    width: 650px;
+  }
+
+  .row-gap {
+    height: 0px;
+    width: 650px;
+    position: absolute;
+
+  }
+
+  .row-gap1 {
+    border-bottom: 10px solid red;
+    top: 100px;
+  }
+
+  .row-gap2 {
+    border-bottom: 8px solid red;
+    top: 211px;
+  }
+
+  .row-gap3 {
+    border-bottom: 2px solid red;
+    top: 324px;
+  }
+
+  .row-gap4 {
+    border-bottom: 2px solid red;
+    top: 434px;
+  }
+
+  .row-gap5 {
+    border-bottom: 5px solid red;
+    top: 542.5px;
+  }
+
+  .col-gap {
+    width: 0px;
+    height: 650px;
+    position: absolute;
+    top: 0px;
+  }
+
+  .col-gap1 {
+    border-left: 2px solid blue;
+    left: 104px;
+  }
+
+  .col-gap2 {
+    border-left: 5px solid blue;
+    left: 212.5px;
+  }
+
+  .col-gap3 {
+    border-left: 2px solid blue;
+    left: 324px;
+  }
+
+  .col-gap4 {
+    border-left: 10px solid blue;
+    left: 430px;
+  }
+
+  .col-gap5 {
+    border-left: 10px solid blue;
+    left: 540px;
+  }
+
+  .item {
+    background: gray;
+    opacity: 0.5;
+  }
+</style>
+<div class="grid-container">
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+</div>
+
+
+<div class="row-gap row-gap1"> </div>
+<div class="row-gap row-gap2"> </div>
+<div class="row-gap row-gap3"> </div>
+<div class="row-gap row-gap4"> </div>
+<div class="row-gap row-gap5"> </div>
+
+
+<div class="col-gap col-gap1"> </div>
+<div class="col-gap col-gap2"> </div>
+<div class="col-gap col-gap3"> </div>
+<div class="col-gap col-gap4"> </div>
+<div class="col-gap col-gap5"> </div>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-gaps/tentative/grid/grid-gap-decorations-019.html b/third_party/blink/web_tests/external/wpt/css/css-gaps/tentative/grid/grid-gap-decorations-019.html
new file mode 100644
index 0000000..637e0467a
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-gaps/tentative/grid/grid-gap-decorations-019.html
@@ -0,0 +1,71 @@
+<!DOCTYPE html>
+<title>
+  CSS Gap Decorations: Grid gaps are painted with multiple line-width values for *-rule-width.
+</title>
+<link rel="help" href="https://drafts.csswg.org/css-gaps-1/">
+<link rel="match" href="grid-gap-decorations-019-ref.html">
+<link rel="author" title="Sam Davis Omekara Jr." href="mailto:samomekarajr@microsoft.com">
+<style>
+  body {
+    margin: 0px;
+  }
+
+  .grid-container {
+    display: grid;
+    grid-gap: 10px;
+    grid-template-columns: repeat(6, 100px);
+    height: 650px;
+    width: 650px;
+
+    column-rule-color: blue;
+    column-rule-style: solid;
+    column-rule-width: 2px repeat(auto, 5px 2px) repeat(2, 10px);
+
+    row-rule-color: red;
+    row-rule-style: solid;
+    row-rule-width: repeat(auto, 10px 8px) repeat(2, 2px) 5px;
+  }
+
+  .item {
+    background: gray;
+    opacity: 0.5;
+  }
+</style>
+<div class="grid-container">
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+  <div class="item"></div>
+</div>
diff --git a/third_party/blink/web_tests/external/wpt/dom/events/scrolling/scroll-cross-origin-iframes.html b/third_party/blink/web_tests/external/wpt/dom/events/scrolling/scroll-cross-origin-iframes.html
new file mode 100644
index 0000000..4c87313
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/dom/events/scrolling/scroll-cross-origin-iframes.html
@@ -0,0 +1,77 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>Verify that two sibling cross-origin iframes both correctly scroll on MouseWheel events, as per https://crbug.com/675695.</title>
+<script src="/common/get-host-info.sub.js"></script>
+<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/testdriver-actions.js"></script>
+<script src="scroll_support.js"></script>
+<body>
+  <iframe id="target-iframe1"
+      style="height: 100px; width: 100px; overflow-y: scroll; position: absolute; left: 50px; top: 50px">
+  </iframe>
+  <iframe id="target-iframe2"
+      style="height: 100px; width: 100px; overflow-y: scroll; position: absolute; left: 50px; top: 200px">
+  </iframe>
+  <script>
+    "use strict";
+
+    function waitForFrameLoadAsync(frame) {
+      return new Promise(async (resolve) => {
+        frame.addEventListener('load', resolve, { once: true });
+      });
+    }
+
+    function waitForMessageAsync(expected_frame_id, expected_command) {
+      return new Promise((resolve) => {
+        window.addEventListener('message', (event) => {
+          assert_equals(event.data.command, expected_command);
+          assert_equals(event.data.frame_id, expected_frame_id);
+          resolve(event.data.scrollTop);
+        }, { once: true });
+      });
+    }
+
+    function waitForCrossOriginFrameSetupAsync(frame) {
+      return new Promise(async (resolve) => {
+        const setup_ack_waiter = waitForMessageAsync(frame.id, 'setup');
+        const load_ack_waiter = waitForFrameLoadAsync(frame);
+
+        const host = get_host_info();
+        frame.src = host.HTTP_NOTSAMESITE_ORIGIN +
+            "/dom/events/scrolling/scroll-cross-origin-iframes.sub.html";
+        await load_ack_waiter;
+        await frame.contentWindow.postMessage({
+          command: 'setup',
+          frame_id: frame.id
+        }, "*");
+        const scroll_top = await setup_ack_waiter;
+        resolve(scroll_top);
+      });
+    }
+
+    promise_test(async () => {
+      var frame_map = new Map();
+      for (const frame of document.querySelectorAll('iframe')) {
+        const scroll_top_before = await waitForCrossOriginFrameSetupAsync(frame);
+        frame_map.set(frame.id, scroll_top_before);
+      }
+      assert_equals(frame_map.size, 2);
+
+      for (const [frame_id, scroll_top_before] of frame_map) {
+        const frame = document.getElementById(frame_id);
+        const scrollend_ack_waiter = waitForMessageAsync(frame_id, 'onscrollend');
+        await new test_driver.Actions()
+            .scroll(/*cursor_x=*/0, /*cursor_y=*/0,
+                    /*delta_x=*/0, /*delta_y=*/50,
+                    {origin: frame})
+            .send();
+        const scroll_top_after = await scrollend_ack_waiter;
+        assert_greater_than(scroll_top_after, scroll_top_before,
+                            'Failed to advance scrollTop target');
+      }
+    }, "Verify sibling cross-origin iframes can wheel-scroll.");
+  </script>
+</body>
diff --git a/third_party/blink/web_tests/external/wpt/dom/events/scrolling/scroll-cross-origin-iframes.sub.html b/third_party/blink/web_tests/external/wpt/dom/events/scrolling/scroll-cross-origin-iframes.sub.html
new file mode 100644
index 0000000..e6d1279
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/dom/events/scrolling/scroll-cross-origin-iframes.sub.html
@@ -0,0 +1,51 @@
+<!DOCTYPE html>
+<div style="height: 300px"></div>
+<script>
+var is_setup = false;
+var port;
+var frame_id;
+
+function postReplyMessage(command) {
+  const message_target = (port ? port : parent);
+  message_target.postMessage({
+    command: command,
+    frame_id: frame_id,
+    scrollTop: document.documentElement.scrollTop
+  }, "*");
+}
+
+function handleMessage(event) {
+  switch (event.data.command) {
+    case 'setup':
+      is_setup = false;
+      port = event.source;
+      frame_id = event.data.frame_id;
+      if (document.scrollingElement.scrollLeft != 0 ||
+          document.scrollingElement.scrollTop != 0) {
+        document.scrollingElement.scrollTo(0, 0);
+      } else {
+        is_setup = true;
+        postReplyMessage('setup');
+      }
+      break;
+    default:
+      break;
+  }
+}
+
+function handleScrollend() {
+  var command = 'onscrollend';
+
+  if (!is_setup &&
+      document.scrollingElement.scrollTop == 0 &&
+      document.scrollingElement.scrollLeft == 0) {
+    is_setup = true;
+    command = 'setup';
+  }
+
+  postReplyMessage(command);
+}
+
+window.addEventListener('message', handleMessage);
+window.addEventListener('scrollend', handleScrollend);
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/speech-api/SpeechRecognition-installOnDevice.https.html b/third_party/blink/web_tests/external/wpt/speech-api/SpeechRecognition-installOnDevice.https.html
index 08d370d..1d1dd35ed 100644
--- a/third_party/blink/web_tests/external/wpt/speech-api/SpeechRecognition-installOnDevice.https.html
+++ b/third_party/blink/web_tests/external/wpt/speech-api/SpeechRecognition-installOnDevice.https.html
@@ -29,6 +29,32 @@
     "installOnDevice should resolve with `true` when called with a supported language code."
   );
 
+  // Verify that the newly installed language pack is available.
+  const availableOnDeviceResultPromise = SpeechRecognition.availableOnDevice(validLang);
+  assert_true(
+    availableOnDeviceResultPromise instanceof Promise,
+    "availableOnDevice should return a Promise."
+  );
+
+  const availableOnDeviceResult = await availableOnDeviceResultPromise;
+  assert_true(
+    typeof availableOnDeviceResult === "string",
+    "The resolved value of the availableOnDevice promise should be a string."
+  );
+
+  assert_true(availableOnDeviceResult === "available",
+    "The resolved value of the availableOnDevice promise should be available."
+  );
+
+  // Verify that installing an already installed language pack resolves to true.
+  const secondResultPromise = SpeechRecognition.installOnDevice(validLang);
+  const secondResult = await secondResultPromise;
+  assert_equals(
+    secondResult,
+    true,
+    "installOnDevice should resolve with `true` if the language is already installed."
+  );
+
   // Test that it returns a promise.
   const invalidResultPromise = SpeechRecognition.installOnDevice(invalidLang);
   const invalidResult = await invalidResultPromise;
diff --git a/third_party/blink/web_tests/http/tests/misc/scroll-cross-origin-iframes.html b/third_party/blink/web_tests/http/tests/misc/scroll-cross-origin-iframes.html
deleted file mode 100644
index e7ac09ac..0000000
--- a/third_party/blink/web_tests/http/tests/misc/scroll-cross-origin-iframes.html
+++ /dev/null
@@ -1,46 +0,0 @@
-<!DOCTYPE html>
-<script src="/resources/testharness.js"></script>
-<script src='/resources/testharnessreport.js'></script>
-<iframe id="target-iframe1" src="http://localhost:8080/misc/resources/cross-origin-subframe-for-scrolling.html" style="height: 100px; width: 100px; overflow-y: scroll; position: absolute; left: 50px; top: 50px"></iframe>
-<iframe id="target-iframe2" src="http://localhost:8080/misc/resources/cross-origin-subframe-for-scrolling.html" style="height: 100px; width: 100px; overflow-y: scroll; position: absolute; left: 50px; top: 200px"></iframe>
-
-<script>
-var scroll_test = async_test("Verify that two sibling cross-origin iframes both correctly scroll on MouseWheel events, as per https://crbug.com/675695.");
-
-var last_frame_scrolled;
-var iframe1 = document.getElementById("target-iframe1");
-var iframe2 = document.getElementById("target-iframe2");
-
-function handleMessage(event) {
-  if (event.data.scrolled == 1 && last_frame_scrolled == 1) {
-    scroll_test.step(() => { assert_greater_than(event.data.scrollTop, 0, "iframe1 scroll offset greater than 0.")} );
-    last_frame_scrolled = 2;
-    scrollSubframe(iframe2, 2);
-  } else if (event.data.scrolled == 2) {
-    scroll_test.step(() => { assert_greater_than(event.data.scrollTop, 0, "iframe2 scroll offset greater than 0.")} );
-    scroll_test.done();
-  } else if (event.data.scrolled != 1){
-    assert_unreached("Received invalid postMessage from subframe.");
-  }
-}
-
-window.addEventListener("message", handleMessage);
-
-iframe1.onload = (() => {
-  last_frame_scrolled = 1;
-  scrollSubframe(iframe1, 1);
-});
-
-function scrollSubframe(iframe_element, frame_id) {
-  iframe_element.contentWindow.postMessage(
-      {expectScrollBy: 1, frame_id: frame_id}, "*");
-  var rect = iframe_element.getBoundingClientRect();
-  chrome.gpuBenchmarking.smoothScrollByXY(
-      0, 10,
-      /* resolve */ undefined,
-      rect.x + 5, rect.y + 5,
-      chrome.gpuBenchmarking.MOUSE_INPUT,
-      /* SPEED_INSTANT */ 400000);
-}
-
-</script>
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 0245fdb..cb0fdfc 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
@@ -368,6 +368,8 @@
 quotes
 r
 range
+readingFlow
+readingOrder
 removeProperty
 resize
 right
diff --git a/third_party/blink/web_tests/virtual/stable/webexposed/css-property-listing-expected.txt b/third_party/blink/web_tests/virtual/stable/webexposed/css-property-listing-expected.txt
index 1906b24c..7344beb 100644
--- a/third_party/blink/web_tests/virtual/stable/webexposed/css-property-listing-expected.txt
+++ b/third_party/blink/web_tests/virtual/stable/webexposed/css-property-listing-expected.txt
@@ -318,6 +318,8 @@
     print-color-adjust
     quotes
     r
+    reading-flow
+    reading-order
     resize
     right
     rotate
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 4661884e..398e74a0 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
@@ -8742,6 +8742,7 @@
     getter anchorOffset
     getter baseNode
     getter baseOffset
+    getter direction
     getter extentNode
     getter extentOffset
     getter focusNode
@@ -8758,6 +8759,7 @@
     method deleteFromDocument
     method empty
     method extend
+    method getComposedRanges
     method getRangeAt
     method modify
     method removeAllRanges
diff --git a/third_party/blink/web_tests/wpt_internal/ai/language-model-api-response-json-schema.https.any.js b/third_party/blink/web_tests/wpt_internal/ai/language-model-api-response-json-schema.https.any.js
new file mode 100644
index 0000000..7a952855
--- /dev/null
+++ b/third_party/blink/web_tests/wpt_internal/ai/language-model-api-response-json-schema.https.any.js
@@ -0,0 +1,33 @@
+// META: script=resources/utils.js
+// META: script=resources/workaround-for-382640509.js
+
+promise_test(async t => {
+  await ensureLanguageModel();
+  const session = await LanguageModel.create();
+  // Circular reference is not valid.
+  const invalidRepsonseJsonSchema = {};
+  invalidRepsonseJsonSchema.self = invalidRepsonseJsonSchema;
+  await promise_rejects_dom(t, 'NotSupportedError',
+    session.prompt(kTestPrompt, { responseJSONSchema: invalidRepsonseJsonSchema }),
+    'Response json schema is invalid - it should be an object that can be stringified into a JSON string.');
+}, 'Prompt API should fail if an invalid response json schema is provided');
+
+promise_test(async t => {
+  await ensureLanguageModel();
+  const session = await LanguageModel.create();
+  const validRepsonseJsonSchema = {
+    type: "object",
+    required: ["Rating"],
+    additionalProperties: false,
+    properties: {
+      Rating: {
+        type: "number",
+        minimum: 0,
+        maximum: 5,
+      },
+    },
+  };
+  const promptPromise = session.prompt(kTestPrompt, { responseJSONSchema : validRepsonseJsonSchema });
+  const result = await promptPromise;
+  assert_true(typeof result === "string");
+}, 'Prompt API should work when a valid response json schema is provided.');
diff --git a/third_party/boringssl/src b/third_party/boringssl/src
index c86127e..f0ab911 160000
--- a/third_party/boringssl/src
+++ b/third_party/boringssl/src
@@ -1 +1 @@
-Subproject commit c86127e656ae8b95622c79ba3d0fb35415502d53
+Subproject commit f0ab91129d1a234dc127335765b794d62dc9ed4d
diff --git a/third_party/dawn b/third_party/dawn
index 8571704..ad16623 160000
--- a/third_party/dawn
+++ b/third_party/dawn
@@ -1 +1 @@
-Subproject commit 85717040cc75f7b29ca714b18a31087d2a2b6382
+Subproject commit ad166239934f9105fdfbbfe8ea46f8e05ca43181
diff --git a/third_party/lit/v3_0/cr_lit_element.ts b/third_party/lit/v3_0/cr_lit_element.ts
index 09d3f3b..40200ba 100644
--- a/third_party/lit/v3_0/cr_lit_element.ts
+++ b/third_party/lit/v3_0/cr_lit_element.ts
@@ -39,8 +39,9 @@
     this.$ = new Proxy({}, {
       get(cache: ElementCache, id: string): HTMLElement|SVGElement {
         if (!self.hasUpdated && !self.isConnected) {
-          throw new Error(`CrLitElement ${
-              self.tagName} $ dictionary accessed before element is connected at least once.`);
+          const description = self.tagName + (self.id ? `#${self.id}` : '');
+          throw new Error(`CrLitElement ${description} accessed '$.${
+              id}' before connected at least once.`);
         }
 
         if (!self.hasUpdated) {
@@ -48,8 +49,9 @@
           // performUpdate() call below will cause an endless recursion. Local
           // DOM nodes should not be accessed within willUpdate() anyway.
           if (self.willUpdatePending_) {
-            throw new Error(`CrLitElement ${
-                self.tagName} tried to access this.$ within willUpdate().`);
+            const description = self.tagName + (self.id ? `#${self.id}` : '');
+            throw new Error(`CrLitElement ${description} accessed '$.${
+                id}' within willUpdate().`);
           }
 
           // See Case3 in `ensureInitialRender` docs.
diff --git a/third_party/protobuf/README.chromium b/third_party/protobuf/README.chromium
index b3e505e..28e9e4b6 100644
--- a/third_party/protobuf/README.chromium
+++ b/third_party/protobuf/README.chromium
@@ -241,3 +241,9 @@
   Add the compatability dir to .gitignore as it's unnecessary and contains files
   that trip CheckForTooLargeFiles presubmit.
 
+- 0054-use-full-types-in-java-context.patch
+
+  Uses full struct definitions in java/context.h via #include, instead
+  of forward declarations to work around known issue with protobuf +
+  newer abseil version,
+  https://github.com/protocolbuffers/protobuf/issues/20331
diff --git a/third_party/protobuf/patches/0054-use-full-types-in-java-context.patch b/third_party/protobuf/patches/0054-use-full-types-in-java-context.patch
new file mode 100644
index 0000000..c693b32
--- /dev/null
+++ b/third_party/protobuf/patches/0054-use-full-types-in-java-context.patch
@@ -0,0 +1,21 @@
+diff --git a/src/google/protobuf/compiler/java/context.h b/src/google/protobuf/compiler/java/context.h
+index 993301bbd796b..8117ce67821bd 100644
+--- a//src/google/protobuf/compiler/java/context.h
++++ b//src/google/protobuf/compiler/java/context.h
+@@ -12,1 +12,1 @@
+ #include <vector>
+
+ #include "absl/container/flat_hash_map.h"
++#include "google/protobuf/compiler/java/field_common.h"
+ #include "google/protobuf/compiler/java/helpers.h"
+ #include "google/protobuf/compiler/java/options.h"
+ #include "google/protobuf/port.h"
+@@ -36,2 +37,2 @@ namespace protobuf {
+ namespace compiler {
+ namespace java {
+
+-struct FieldGeneratorInfo;
+-struct OneofGeneratorInfo;
+ // A context object holds the information that is shared among all code
+ // generators.
+ class Context {
diff --git a/third_party/protobuf/src/google/protobuf/compiler/java/context.h b/third_party/protobuf/src/google/protobuf/compiler/java/context.h
index 993301bb..8117ce67 100644
--- a/third_party/protobuf/src/google/protobuf/compiler/java/context.h
+++ b/third_party/protobuf/src/google/protobuf/compiler/java/context.h
@@ -12,6 +12,7 @@
 #include <vector>
 
 #include "absl/container/flat_hash_map.h"
+#include "google/protobuf/compiler/java/field_common.h"
 #include "google/protobuf/compiler/java/helpers.h"
 #include "google/protobuf/compiler/java/options.h"
 #include "google/protobuf/port.h"
@@ -36,8 +37,6 @@
 namespace compiler {
 namespace java {
 
-struct FieldGeneratorInfo;
-struct OneofGeneratorInfo;
 // A context object holds the information that is shared among all code
 // generators.
 class Context {
diff --git a/third_party/rust/chromium_crates_io/Cargo.lock b/third_party/rust/chromium_crates_io/Cargo.lock
index 8631995..bd47b437 100644
--- a/third_party/rust/chromium_crates_io/Cargo.lock
+++ b/third_party/rust/chromium_crates_io/Cargo.lock
@@ -111,6 +111,12 @@
  "font-types",
  "hex",
  "icu_capi",
+ "icu_casemap",
+ "icu_experimental",
+ "icu_locale_core",
+ "icu_normalizer",
+ "icu_properties",
+ "icu_provider",
  "lazy_static",
  "libc",
  "log",
diff --git a/third_party/rust/chromium_crates_io/Cargo.toml b/third_party/rust/chromium_crates_io/Cargo.toml
index 25de3c43..772ce73 100644
--- a/third_party/rust/chromium_crates_io/Cargo.toml
+++ b/third_party/rust/chromium_crates_io/Cargo.toml
@@ -50,6 +50,25 @@
 default-features = false
 features = ["calendar", "compiled_data", "experimental", "casemap"]
 
+[dependencies.icu_casemap]
+version = "2.0.0-beta2"
+default-features = false
+[dependencies.icu_experimental]
+version = "0.3.0-beta2"
+default-features = false
+[dependencies.icu_locale_core]
+version = "2.0.0-beta2"
+default-features = false
+[dependencies.icu_normalizer]
+version = "2.0.0-beta2"
+default-features = false
+[dependencies.icu_properties]
+version = "2.0.0-beta2"
+default-features = false
+[dependencies.icu_provider]
+version = "2.0.0-beta2"
+default-features = false
+
 [dependencies.tinyvec]
 version = "1.6.0"
 features = ["rustc_1_55"]
diff --git a/third_party/rust/chromium_crates_io/patches/icu_experimental/0001-temporarily-remove-any-lower.patch b/third_party/rust/chromium_crates_io/patches/icu_experimental/0001-temporarily-remove-any-lower.patch
new file mode 100644
index 0000000..bbfab5b
--- /dev/null
+++ b/third_party/rust/chromium_crates_io/patches/icu_experimental/0001-temporarily-remove-any-lower.patch
@@ -0,0 +1,53 @@
+From e9609b53306e6a142ea0859a1d8e7ebe26df42b5 Mon Sep 17 00:00:00 2001
+From: Frank Tang <ftang@chromium.org>
+Date: Tue, 1 Apr 2025 16:36:45 -0700
+Subject: [PATCH] Prototype using ICU4X Transliterator
+
+Add a flag enable_rust_translit default to false for using ICU4X
+gn args dir
+and put
+enable_rust_translit = true
+
+to configure the build to build with the ICU4X transliterator
+
+Change-Id: Id011e6b3b000378609c52c3ad50ac9d03853b4a8
+Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/6332970
+Reviewed-by: Daniel Cheng <dcheng@chromium.org>
+Commit-Queue: Frank Tang <ftang@chromium.org>
+Cr-Commit-Position: refs/heads/main@{#1441255}
+---
+ .../icu_experimental-v0_3/src/transliterate/compile/pass2.rs   | 3 ++-
+ .../src/transliterate/transliterator/mod.rs                    | 3 ++-
+ 2 files changed, 4 insertions(+), 2 deletions(-)
+
+diff --git a/third_party/rust/chromium_crates_io/vendor/icu_experimental-v0_3/src/transliterate/compile/pass2.rs b/third_party/rust/chromium_crates_io/vendor/icu_experimental-v0_3/src/transliterate/compile/pass2.rs
+index eabb0f9e81417..f7f2eba7b169e 100644
+--- a/third_party/rust/chromium_crates_io/vendor/icu_experimental-v0_3/src/transliterate/compile/pass2.rs
++++ b/third_party/rust/chromium_crates_io/vendor/icu_experimental-v0_3/src/transliterate/compile/pass2.rs
+@@ -400,7 +400,8 @@ impl<'a, 'p> Pass2<'a, 'p> {
+                 | "any-nfkd"
+                 | "any-null"
+                 | "any-remove"
+-                | "any-lower"
++                // Comment out any-lower to allow adding any-lower
++                // | "any-lower"
+                 | "any-upper"
+                 | "any-title"
+                 | "any-hex/unicode"
+diff --git a/third_party/rust/chromium_crates_io/vendor/icu_experimental-v0_3/src/transliterate/transliterator/mod.rs b/third_party/rust/chromium_crates_io/vendor/icu_experimental-v0_3/src/transliterate/transliterator/mod.rs
+index 3fcc1de7c7cd1..8007361034f78 100644
+--- a/third_party/rust/chromium_crates_io/vendor/icu_experimental-v0_3/src/transliterate/transliterator/mod.rs
++++ b/third_party/rust/chromium_crates_io/vendor/icu_experimental-v0_3/src/transliterate/transliterator/mod.rs
+@@ -497,7 +497,8 @@ impl Transliterator {
+             ),
+             "any-null" => Some(Ok(InternalTransliterator::Null)),
+             "any-remove" => Some(Ok(InternalTransliterator::Remove)),
+-            "any-lower" => Some(Err(DataError::custom("any-lower not implemented"))),
++            // Comment out any-lower to allow adding any-lower
++            // "any-lower" => Some(Err(DataError::custom("any-lower not implemented"))),
+             "any-upper" => Some(Err(DataError::custom("any-upper not implemented"))),
+             "any-title" => Some(Err(DataError::custom("any-title not implemented"))),
+             "any-hex/unicode" => Some(Ok(InternalTransliterator::Hex(
+-- 
+2.49.0.472.ge94155a9ec-goog
+
diff --git a/third_party/rust/icu_casemap/v2/BUILD.gn b/third_party/rust/icu_casemap/v2/BUILD.gn
index 0719a61..81dadf7c 100644
--- a/third_party/rust/icu_casemap/v2/BUILD.gn
+++ b/third_party/rust/icu_casemap/v2/BUILD.gn
@@ -60,13 +60,4 @@
   rustflags = [
     "--cap-lints=allow",  # Suppress all warnings in crates.io crates
   ]
-
-  # Only for usage from third-party crates. Add the crate to
-  # //third_party/rust/chromium_crates_io/Cargo.toml to use
-  # it from first-party code.
-  visibility = [
-    "//base:i18n_transliterator_ffi",
-    "//base:i18n_transliterator_ffi_cxx_generated",
-    "//third_party/rust/*",
-  ]
 }
diff --git a/third_party/rust/icu_experimental/v0_3/BUILD.gn b/third_party/rust/icu_experimental/v0_3/BUILD.gn
index d8fbd6c7..ee7f3f9 100644
--- a/third_party/rust/icu_experimental/v0_3/BUILD.gn
+++ b/third_party/rust/icu_experimental/v0_3/BUILD.gn
@@ -157,13 +157,4 @@
   rustflags = [
     "--cap-lints=allow",  # Suppress all warnings in crates.io crates
   ]
-
-  # Only for usage from third-party crates. Add the crate to
-  # //third_party/rust/chromium_crates_io/Cargo.toml to use
-  # it from first-party code.
-  visibility = [
-    "//base:i18n_transliterator_ffi",
-    "//base:i18n_transliterator_ffi_cxx_generated",
-    "//third_party/rust/*",
-  ]
 }
diff --git a/third_party/rust/icu_locale_core/v2/BUILD.gn b/third_party/rust/icu_locale_core/v2/BUILD.gn
index ccd63595..8aa65f8 100644
--- a/third_party/rust/icu_locale_core/v2/BUILD.gn
+++ b/third_party/rust/icu_locale_core/v2/BUILD.gn
@@ -108,13 +108,4 @@
   rustflags = [
     "--cap-lints=allow",  # Suppress all warnings in crates.io crates
   ]
-
-  # Only for usage from third-party crates. Add the crate to
-  # //third_party/rust/chromium_crates_io/Cargo.toml to use
-  # it from first-party code.
-  visibility = [
-    "//base:i18n_transliterator_ffi",
-    "//base:i18n_transliterator_ffi_cxx_generated",
-    "//third_party/rust/*",
-  ]
 }
diff --git a/third_party/rust/icu_normalizer/v2/BUILD.gn b/third_party/rust/icu_normalizer/v2/BUILD.gn
index 8be5bb1..f72951db 100644
--- a/third_party/rust/icu_normalizer/v2/BUILD.gn
+++ b/third_party/rust/icu_normalizer/v2/BUILD.gn
@@ -47,13 +47,4 @@
   rustflags = [
     "--cap-lints=allow",  # Suppress all warnings in crates.io crates
   ]
-
-  # Only for usage from third-party crates. Add the crate to
-  # //third_party/rust/chromium_crates_io/Cargo.toml to use
-  # it from first-party code.
-  visibility = [
-    "//base:i18n_transliterator_ffi",
-    "//base:i18n_transliterator_ffi_cxx_generated",
-    "//third_party/rust/*",
-  ]
 }
diff --git a/third_party/rust/icu_properties/v2/BUILD.gn b/third_party/rust/icu_properties/v2/BUILD.gn
index 22d91c653..92521cf 100644
--- a/third_party/rust/icu_properties/v2/BUILD.gn
+++ b/third_party/rust/icu_properties/v2/BUILD.gn
@@ -59,13 +59,4 @@
   rustflags = [
     "--cap-lints=allow",  # Suppress all warnings in crates.io crates
   ]
-
-  # Only for usage from third-party crates. Add the crate to
-  # //third_party/rust/chromium_crates_io/Cargo.toml to use
-  # it from first-party code.
-  visibility = [
-    "//base:i18n_transliterator_ffi",
-    "//base:i18n_transliterator_ffi_cxx_generated",
-    "//third_party/rust/*",
-  ]
 }
diff --git a/third_party/rust/icu_provider/v2/BUILD.gn b/third_party/rust/icu_provider/v2/BUILD.gn
index c6cdb53..20cb03e 100644
--- a/third_party/rust/icu_provider/v2/BUILD.gn
+++ b/third_party/rust/icu_provider/v2/BUILD.gn
@@ -62,13 +62,4 @@
   rustflags = [
     "--cap-lints=allow",  # Suppress all warnings in crates.io crates
   ]
-
-  # Only for usage from third-party crates. Add the crate to
-  # //third_party/rust/chromium_crates_io/Cargo.toml to use
-  # it from first-party code.
-  visibility = [
-    "//base:i18n_transliterator_ffi",
-    "//base:i18n_transliterator_ffi_cxx_generated",
-    "//third_party/rust/*",
-  ]
 }
diff --git a/third_party/skia b/third_party/skia
index 5342f22..c106d78 160000
--- a/third_party/skia
+++ b/third_party/skia
@@ -1 +1 @@
-Subproject commit 5342f227267ef899adb5da0473fe3b513f3c6790
+Subproject commit c106d7831592fbc7245b6e032164506db3038f2a
diff --git a/third_party/vulkan-deps b/third_party/vulkan-deps
index a7952ef..07e305f 160000
--- a/third_party/vulkan-deps
+++ b/third_party/vulkan-deps
@@ -1 +1 @@
-Subproject commit a7952ef72c943513045046f4f1ab0f91998a9307
+Subproject commit 07e305fc66d3132b515ed22bc78dee79bf7eb9fc
diff --git a/third_party/vulkan-validation-layers/src b/third_party/vulkan-validation-layers/src
index 7c7fd77..fcc588f 160000
--- a/third_party/vulkan-validation-layers/src
+++ b/third_party/vulkan-validation-layers/src
@@ -1 +1 @@
-Subproject commit 7c7fd77a6ba3ff7699d042ee1d396aa5803850df
+Subproject commit fcc588f5171b007d433bf7c02184708dbc7d9d89
diff --git a/third_party/webgpu-cts/src b/third_party/webgpu-cts/src
index eb1d526..5aba8ba 160000
--- a/third_party/webgpu-cts/src
+++ b/third_party/webgpu-cts/src
@@ -1 +1 @@
-Subproject commit eb1d526134f6789e9ce6d5e3b654fbfc10043267
+Subproject commit 5aba8ba55eb2496852d622854a99fac74f074c65
diff --git a/tools/accessibility/rebase_dump_accessibility_tree_tests.py b/tools/accessibility/rebase_dump_accessibility_tree_tests.py
index 46abca9d..075a2385 100755
--- a/tools/accessibility/rebase_dump_accessibility_tree_tests.py
+++ b/tools/accessibility/rebase_dump_accessibility_tree_tests.py
@@ -55,11 +55,11 @@
       line = result.group(1)
   # For Android tests:
   if line[:2] == 'I ' or line[:2] == 'E ':
-    if result := re.search('[I|E].*run_tests_on_device\([^\)]+\)\s+(.*)', line):
+    if result := re.search(r'[IE].*run_tests_on_device\([^\)]+\)\s+(.*)', line):
       line = result.group(1)
   # For Android content_shell_test_apk tests:
   elif line[:2] == 'C ':
-    if result := re.search('C\s+\d+\.\d+s Main\s+([T|E|A|a|W|\+](.*))', line):
+    if result := re.search(r'C\s+\d+\.\d+s Main\s+([TEAaW+](.*))', line):
       line = result.group(1)
   return line
 
diff --git a/tools/android/avd/proto/android_32_google_apis_x64_foldable.textpb b/tools/android/avd/proto/android_32_google_apis_x64_foldable.textpb
index f4a66ed7a..b72bfd5a 100644
--- a/tools/android/avd/proto/android_32_google_apis_x64_foldable.textpb
+++ b/tools/android/avd/proto/android_32_google_apis_x64_foldable.textpb
@@ -17,8 +17,8 @@
 
 avd_package {
   package_name: "chromium/third_party/android_sdk/public/avds/android-32/google_apis/x86_64"
-  # Created in https://ci.chromium.org/ui/b/8735783234873256961
-  # Patched gmscore version 24.26.32 in https://crrev.com/c/5735581
-  version: "Uz9_H4xQPMkWRFtmUJKSvZGLuEVimRmDuJezRXxIk70C"
+  # Created in https://ci.chromium.org/ui/b/8719089370664121393
+  # Patched gmscore version 25.07.33 in https://crrev.com/c/6377466
+  version: "toKIcJSMVTa2LXqcfNjRNtLywQ1kuhmdkg6w-jTy1-kC"
 }
 avd_name: "android_32_google_apis_x64_foldable"
diff --git a/tools/android/avd/proto/android_32_google_apis_x64_foldable_landscape.textpb b/tools/android/avd/proto/android_32_google_apis_x64_foldable_landscape.textpb
index 692185a..763fc57f 100644
--- a/tools/android/avd/proto/android_32_google_apis_x64_foldable_landscape.textpb
+++ b/tools/android/avd/proto/android_32_google_apis_x64_foldable_landscape.textpb
@@ -18,8 +18,8 @@
 
 avd_package {
   package_name: "chromium/third_party/android_sdk/public/avds/android-32/google_apis/x86_64"
-  # Created in https://ci.chromium.org/ui/b/8735783234873256961
-  # Patched gmscore version 24.26.32 in https://crrev.com/c/5735581
-  version: "Ggu6SCWhbO7GLyFjZgU3xfOkziQGFE6e0hfWY9a8A9kC"
+  # Created in https://ci.chromium.org/ui/b/8719089370664121393
+  # Patched gmscore version 25.07.33 in https://crrev.com/c/6377466
+  version: "8suNvYCLZCZxPMUCgs0JOXpKVm5XIW8pfef4yETgfTkC"
 }
-avd_name: "android_32_google_apis_x64_foldable_landscape"
+avd_name: "android_32_google_apis_x64_foldable"
diff --git a/tools/android/avd/proto/android_32_google_apis_x64_foldable_landscape_local.textpb b/tools/android/avd/proto/android_32_google_apis_x64_foldable_landscape_local.textpb
index cebbbba..fc9fb27 100644
--- a/tools/android/avd/proto/android_32_google_apis_x64_foldable_landscape_local.textpb
+++ b/tools/android/avd/proto/android_32_google_apis_x64_foldable_landscape_local.textpb
@@ -18,8 +18,8 @@
 
 avd_package {
   package_name: "chromium/third_party/android_sdk/public/avds/android-32/google_apis/x86_64"
-  # Created in https://ci.chromium.org/ui/b/8734060994898362593
-  # Patched gmscore version 24.26.32 in https://crrev.com/c/5735581
-  version: "Vi7cavzXATzHa6M9We7TJautKBe11Pur-IoL49lF8FgC"
+  # Created in https://ci.chromium.org/ui/b/8719089370664121393
+  # Patched gmscore version 25.07.33 in https://crrev.com/c/6377466
+  version: "GD86_70Cb_Nx1rYa7qc5v6iA5CVZPXdA7StO26sD8w0C"
 }
 avd_name: "android_32_google_apis_x64_foldable"
diff --git a/tools/android/avd/proto/android_32_google_apis_x64_foldable_local.textpb b/tools/android/avd/proto/android_32_google_apis_x64_foldable_local.textpb
index 9a89379..26032d3 100644
--- a/tools/android/avd/proto/android_32_google_apis_x64_foldable_local.textpb
+++ b/tools/android/avd/proto/android_32_google_apis_x64_foldable_local.textpb
@@ -17,8 +17,8 @@
 
 avd_package {
   package_name: "chromium/third_party/android_sdk/public/avds/android-32/google_apis/x86_64"
-  # Created in https://ci.chromium.org/ui/b/8734060994898362593
-  # Patched gmscore version 24.26.32 in https://crrev.com/c/5735581
-  version: "tptXMzBoJL68sKlejiMi-dUMg2YdKcTLTozV6ohjF_UC"
+  # Created in https://ci.chromium.org/ui/b/8719089370664121393
+  # Patched gmscore version 25.07.33 in https://crrev.com/c/6377466
+  version: "V5d_dJhj7yKkb5LjQ_K8Gm7d9EiQvOnEbOsKhNUNMOsC"
 }
 avd_name: "android_32_google_apis_x64_foldable"
diff --git a/tools/android/avd/proto/android_33_google_apis_x64.textpb b/tools/android/avd/proto/android_33_google_apis_x64.textpb
index 5982db47..365357be 100644
--- a/tools/android/avd/proto/android_33_google_apis_x64.textpb
+++ b/tools/android/avd/proto/android_33_google_apis_x64.textpb
@@ -17,9 +17,9 @@
 
 avd_package {
   package_name: "chromium/third_party/android_sdk/public/avds/android-33/google_apis/x86_64"
-  # Created in https://ci.chromium.org/ui/b/8735783234873256961
-  # Patched gmscore version 24.26.32 in https://crrev.com/c/5735581
-  version: "Ee3kRDaGcJ0kQ4GP7TUNOGswwt_Xj6uGe9gUzP7axPwC"
+  # Created in https://ci.chromium.org/ui/b/8719089370664121393
+  # Patched gmscore version 25.07.33 in https://crrev.com/c/6377466
+  version: "6h6ubFZF3bm6whQnysgyfb7dq5C8j43pMHV8DXGVfZcC"
 }
 avd_name: "android_33_google_apis_x64"
 
diff --git a/tools/android/avd/proto/android_33_google_apis_x64_local.textpb b/tools/android/avd/proto/android_33_google_apis_x64_local.textpb
index 23c70ae..39a309c 100644
--- a/tools/android/avd/proto/android_33_google_apis_x64_local.textpb
+++ b/tools/android/avd/proto/android_33_google_apis_x64_local.textpb
@@ -17,9 +17,9 @@
 
 avd_package {
   package_name: "chromium/third_party/android_sdk/public/avds/android-33/google_apis/x86_64"
-  # Created in https://ci.chromium.org/ui/b/8734084655359483009
-  # Patched gmscore version 24.26.32 in https://crrev.com/c/5735581
-  version: "4hXIfKQVfT-w-WwOBofV9AOCoyovO6iKNTSJ_LoHcZ8C"
+  # Created in https://ci.chromium.org/ui/b/8719089370664121393
+  # Patched gmscore version 25.07.33 in https://crrev.com/c/6377466
+  version: "W4oP8iGij6Yq67thheoevC4aKehiCrqlVDcEbWozkfkC"
 }
 avd_name: "android_33_google_apis_x64"
 
diff --git a/tools/mb/mb.py b/tools/mb/mb.py
index 0879ce1..47fb735 100755
--- a/tools/mb/mb.py
+++ b/tools/mb/mb.py
@@ -128,13 +128,13 @@
         self.DumpInputFiles()
       return ret
     except KeyboardInterrupt:
-      self.Print('interrupted, exiting')
+      self.Print('interrupted, exiting', file=sys.stderr)
       return 130
     except Exception as e:
       self.DumpInputFiles()
       s = traceback.format_exc()
       for l in s.splitlines():
-        self.Print(l)
+        self.Print(l, file=sys.stderr)
       return getattr(e, 'retcode', _DEFAULT_ERROR_RETCODE)
 
   def ParseArgs(self, argv):
@@ -457,11 +457,12 @@
 
     def DumpContentsOfFilePassedTo(arg_name, path):
       if path and self.Exists(path):
-        self.Print("\n# To recreate the file passed to %s:" % arg_name)
-        self.Print("%% cat > %s <<EOF" % path)
+        self.Print("\n# To recreate the file passed to %s:" % arg_name,
+                   file=sys.stderr)
+        self.Print("%% cat > %s <<EOF" % path, file=sys.stderr)
         contents = self.ReadFile(path)
-        self.Print(contents)
-        self.Print("EOF\n%\n")
+        self.Print(contents, file=sys.stderr)
+        self.Print("EOF\n%\n", file=sys.stderr)
 
     if getattr(self.args, 'input_path', None):
       DumpContentsOfFilePassedTo(
@@ -483,7 +484,8 @@
   def CmdTrain(self):
     expectations_dir = self.args.expectations_dir
     if not self.Exists(expectations_dir):
-      self.Print('Expectations dir (%s) does not exist.' % expectations_dir)
+      self.Print('Expectations dir (%s) does not exist.' % expectations_dir,
+                 file=sys.stderr)
       return 1
     # Removing every expectation file then immediately re-generating them will
     # clear out deleted groups.
@@ -564,10 +566,12 @@
     # notice has been live for a few months.
     if not self.args.force:
       self.Print(
-          '`mb run` is deprecated in favor of the UTR. For more info, see '
-          'https://chromium.googlesource.com/chromium/src/+/main/tools/utr/README.md. '
-          'To skip this warning, re-run with "--force". Note that `mb run` '
-          'will be deleted sometime in 2025.')
+          ('`mb run` is deprecated in favor of the UTR. For more info, see '
+           'https://chromium.googlesource.com/chromium/src/+/main/tools/utr/README.md. '
+           'To skip this warning, re-run with "--force". Note that `mb run` '
+           'will be deleted sometime in 2025.'),
+          file=sys.stderr,
+      )
       return 1
     vals = self.GetConfig()
     if not vals:
@@ -864,7 +868,8 @@
       raise MBErr('mb config file not sorted:\n' + '\n'.join(errs))
 
     if print_ok:
-      self.Print('mb config file %s looks ok.' % self.args.config_file)
+      self.Print('mb config file %s looks ok.' % self.args.config_file,
+                 file=sys.stderr)
     return 0
 
   def _ValidateEach(self, errs, validate):
@@ -918,9 +923,10 @@
     toolchain_path = self.PathJoin(self.ToAbsPath(build_dir),
                                    'toolchain.ninja')
     if not self.Exists(toolchain_path):
-      self.Print('Must either specify a path to an existing GN build dir '
-                 'or pass in a -m/-b pair or a -c flag to specify the '
-                 'configuration')
+      self.Print(('Must either specify a path to an existing GN build dir '
+                  'or pass in a -m/-b pair or a -c flag to specify the '
+                  'configuration'),
+                 file=sys.stderr)
       return {}
 
     vals['gn_args'] = self.GNArgsFromDir(build_dir)
@@ -1169,7 +1175,7 @@
         self.WriteJSON({'output': output}, self.args.json_output)
       # If `gn gen` failed, we should exit early rather than trying to
       # generate isolates. Run() will have already logged any error output.
-      self.Print('GN gen failed: %d' % ret)
+      self.Print('GN gen failed: %d' % ret, file=sys.stderr)
       return ret
 
     if isolate_targets is not None:
@@ -1192,7 +1198,7 @@
     if ret != 0:
       # If `gn ls` failed, we should exit early rather than trying to
       # generate isolates.
-      self.Print('GN ls failed: %d' % ret)
+      self.Print('GN ls failed: %d' % ret, file=sys.stderr)
       return ret
 
     # Create a reverse map from isolate label to isolate dict.
@@ -1372,7 +1378,8 @@
       filtered_command.append('--test-launcher-filter-file=%s' %
                               filter_file_path)
       self.Print('added test selection filter file to command: %s' %
-                 filter_file_path)
+                 filter_file_path,
+                 file=sys.stderr)
       return filtered_command
     return None
 
@@ -1462,7 +1469,7 @@
     ret, out, _ = self.Call(cmd)
     if ret != 0:
       if out:
-        self.Print(out)
+        self.Print(out, file=sys.stderr)
       return ret
 
     runtime_deps = self._DedupDependencies(out.splitlines())
@@ -1580,8 +1587,9 @@
         err += '\n' + build_dir + '/' +  f
 
     if err:
-      self.Print('error: gn `data` items may not list generated directories; '
-                 'list files in directory instead for:' + err)
+      self.Print(('error: gn `data` items may not list generated directories; '
+                  'list files in directory instead for:' + err),
+                 file=sys.stderr)
       return 1
 
     isolate = {
@@ -1598,7 +1606,7 @@
       if target not in self.rts_banned_suites:
         rts_command = self.AddFilterFileArg(target, command)
         if rts_command:
-          self.Print('Adding RTS filter file to command.')
+          self.Print('Adding RTS filter file to command.', file=sys.stderr)
           isolate['variables']['rts_command'] = rts_command
 
     self.WriteFile(isolate_path, json.dumps(isolate, sort_keys=True) + '\n')
@@ -1913,17 +1921,18 @@
     inp = self.ReadInputJSON(['files', 'test_targets',
                               'additional_compile_targets'])
     if self.args.verbose:
-      self.Print()
-      self.Print('analyze input:')
-      self.PrintJSON(inp)
-      self.Print()
+      self.Print(file=sys.stderr)
+      self.Print('analyze input:', file=sys.stderr)
+      self.PrintJSON(inp, file=sys.stderr)
+      self.Print(file=sys.stderr)
 
 
     # This shouldn't normally happen, but could due to unusual race conditions,
     # like a try job that gets scheduled before a patch lands but runs after
     # the patch has landed.
     if not inp['files']:
-      self.Print('Warning: No files modified in patch, bailing out early.')
+      self.Print('Warning: No files modified in patch, bailing out early.',
+                 file=sys.stderr)
       self.WriteJSON({
             'status': 'No dependency',
             'compile_targets': [],
@@ -1962,8 +1971,9 @@
       try:
         gn_outp = json.loads(gn_outp_str)
       except Exception as e:
-        self.Print("Failed to parse the JSON string GN returned: %s\n%s"
-                   % (repr(gn_outp_str), str(e)))
+        self.Print(("Failed to parse the JSON string GN returned: %s\n%s" %
+                    (repr(gn_outp_str), str(e))),
+                   file=sys.stderr)
         raise
 
       outp = {}
@@ -1994,8 +2004,9 @@
         # and build everything. Probably the right thing to do here is
         # to have GN return the compile targets directly.
         if any("(" in target for target in outp['compile_targets']):
-          self.Print('WARNING: targets with non-default toolchains were '
-                     'found, building everything instead.')
+          self.Print(('WARNING: targets with non-default toolchains were '
+                      'found, building everything instead.'),
+                     file=sys.stderr)
           outp['compile_targets'] = all_input_compile_targets
         else:
           outp['compile_targets'] = [
@@ -2012,9 +2023,11 @@
         max_cmd_length_kb = 64 if platform.system() == 'Linux' else 7
 
         if len(' '.join(outp['compile_targets'])) > max_cmd_length_kb * 1024:
-          self.Print('WARNING: Too many compile targets were affected.')
-          self.Print('WARNING: Building everything instead to avoid '
-                     'command-line length issues.')
+          self.Print('WARNING: Too many compile targets were affected.',
+                     file=sys.stderr)
+          self.Print(('WARNING: Building everything instead to avoid '
+                      'command-line length issues.'),
+                     file=sys.stderr)
           outp['compile_targets'] = all_input_compile_targets
 
 
@@ -2023,10 +2036,10 @@
           labels_to_targets[label] for label in gn_outp['test_targets']]
 
       if self.args.verbose:
-        self.Print()
-        self.Print('analyze output:')
-        self.PrintJSON(outp)
-        self.Print()
+        self.Print(file=sys.stderr)
+        self.Print('analyze output:', file=sys.stderr)
+        self.PrintJSON(outp, file=sys.stderr)
+        self.Print(file=sys.stderr)
 
       self.WriteJSON(outp, output_path)
 
@@ -2070,7 +2083,7 @@
     except Exception as e:
       raise MBErr('Error %s writing to the output path "%s"' % (e, path)) from e
 
-  def PrintCmd(self, cmd):
+  def PrintCmd(self, cmd, **kwargs):
     if self.platform == 'win32':
       shell_quoter = QuoteForCmd
     else:
@@ -2078,10 +2091,10 @@
 
     if cmd[0] == self.executable:
       cmd = ['python'] + cmd[1:]
-    self.Print(*[shell_quoter(arg) for arg in cmd])
+    self.Print(*[shell_quoter(arg) for arg in cmd], **kwargs)
 
-  def PrintJSON(self, obj):
-    self.Print(json.dumps(obj, indent=2, sort_keys=True))
+  def PrintJSON(self, obj, **kwargs):
+    self.Print(json.dumps(obj, indent=2, sort_keys=True), **kwargs)
 
   def Build(self, target):
     build_dir = self.ToSrcRelPath(self.args.path)
@@ -2099,14 +2112,14 @@
   def Run(self, cmd, env=None, force_verbose=True, capture_output=True):
     # This function largely exists so it can be overridden for testing.
     if self.args.dryrun or self.args.verbose or force_verbose:
-      self.PrintCmd(cmd)
+      self.PrintCmd(cmd, file=sys.stderr)
     if self.args.dryrun:
       return 0, '', ''
 
     ret, out, err = self.Call(cmd, env=env, capture_output=capture_output)
     if self.args.verbose or force_verbose:
       if ret != 0:
-        self.Print('  -> returned %d' % ret)
+        self.Print('  -> returned %d' % ret, file=sys.stderr)
       if out:
         # This is the error seen on the logs
         self.Print(out, end='')
@@ -2201,7 +2214,8 @@
   def WriteFile(self, path, contents, force_verbose=False):
     # This function largely exists so it can be overriden for testing.
     if self.args.dryrun or self.args.verbose or force_verbose:
-      self.Print('\nWriting """\\\n%s""" to %s.\n' % (contents, path))
+      self.Print('\nWriting """\\\n%s""" to %s.\n' % (contents, path),
+                 file=sys.stderr)
     with open(path, 'w', encoding='utf-8', newline='') as fp:
       return fp.write(contents)
 
diff --git a/tools/mb/mb_unittest.py b/tools/mb/mb_unittest.py
index 9e8f00b..9cadd96b 100755
--- a/tools/mb/mb_unittest.py
+++ b/tools/mb/mb_unittest.py
@@ -528,7 +528,7 @@
     # Make sure we log both what is written to args.gn and the command line.
     self.assertIn('Writing """', mbw.out)
     self.assertIn('/fake_src/buildtools/linux64/gn gen //out/Default --check',
-                  mbw.out)
+                  mbw.err)
 
     mbw = self.fake_mbw(win32=True)
     self.check(['gen', '-c', 'debug_remoteexec', '//out/Debug'], mbw=mbw, ret=0)
@@ -537,7 +537,7 @@
                                'use_remoteexec = true\n'))
     self.assertIn(
         'c:\\fake_src\\buildtools\\win\\gn.exe gen //out/Debug '
-        '--check', mbw.out)
+        '--check', mbw.err)
 
     mbw = self.fake_mbw()
     self.check(['gen', '-m', 'fake_builder_group', '-b', 'fake_args_bot',
@@ -813,7 +813,7 @@
     expected_err = ('error: gn `data` items may not list generated directories;'
                     ' list files in directory instead for:\n'
                     '//out/Default/test_data/\n')
-    self.assertIn(expected_err, mbw.out)
+    self.assertIn(expected_err, mbw.err)
 
   def test_isolate_dir(self):
     files = {
@@ -835,8 +835,7 @@
         'isolate', '-c', 'debug_remoteexec', '//out/Default', 'base_unittests'
     ],
                mbw=mbw,
-               ret=0,
-               err='')
+               ret=0)
 
   def test_isolate_generated_dir(self):
     files = {
@@ -862,7 +861,7 @@
     ],
                mbw=mbw,
                ret=1)
-    self.assertEqual(mbw.out[-len(expected_err):], expected_err)
+    self.assertEqual(mbw.err[-len(expected_err):], expected_err)
 
   def test_run(self):
     files = {
@@ -896,7 +895,7 @@
     # command line in the call to `isolate`.
     self.assertIn(
         'relative-cwd out/Default -- vpython3 '
-        '../../testing/test_env.py', mbw.out)
+        '../../testing/test_env.py', mbw.err)
 
   def test_run_swarmed(self):
     files = {
@@ -1102,7 +1101,7 @@
                ret=1)
     self.assertIn(
         'MBErr: Must not specify a build --phase '
-        'for linux-official on chromium', mbw.out)
+        'for linux-official on chromium', mbw.err)
 
   def test_lookup_starlark_phased_gn_args(self):
     mbw = self.gen_starlark_gn_args_mbw(TEST_PHASED_GN_ARGS_JSON)
@@ -1129,7 +1128,7 @@
                ret=1)
     self.assertIn(
         'MBErr: Must specify a build --phase for linux-official on chromium',
-        mbw.out)
+        mbw.err)
 
   def test_lookup_starlark_phased_gn_args_wrong_phase(self):
     mbw = self.gen_starlark_gn_args_mbw(TEST_PHASED_GN_ARGS_JSON)
@@ -1140,7 +1139,7 @@
                ret=1)
     self.assertIn(
         'MBErr: Phase phase_3 doesn\'t exist for linux-official on chromium',
-        mbw.out)
+        mbw.err)
 
   def test_lookup_gn_args_with_non_existent_gn_args_location_file(self):
     files = {
@@ -1187,17 +1186,17 @@
     # Check that not passing a --phase to a multi-phase builder fails.
     mbw = self.check(['lookup', '-m', 'fake_builder_group', '-b',
                       'fake_multi_phase'], ret=1)
-    self.assertIn('Must specify a build --phase', mbw.out)
+    self.assertIn('Must specify a build --phase', mbw.err)
 
     # Check that passing a --phase to a single-phase builder fails.
     mbw = self.check(['lookup', '-m', 'fake_builder_group', '-b',
                       'fake_builder', '--phase', 'phase_1'], ret=1)
-    self.assertIn('Must not specify a build --phase', mbw.out)
+    self.assertIn('Must not specify a build --phase', mbw.err)
 
     # Check that passing a wrong phase key to a multi-phase builder fails.
     mbw = self.check(['lookup', '-m', 'fake_builder_group', '-b',
                       'fake_multi_phase', '--phase', 'wrong_phase'], ret=1)
-    self.assertIn('Phase wrong_phase doesn\'t exist', mbw.out)
+    self.assertIn('Phase wrong_phase doesn\'t exist', mbw.err)
 
     # Check that passing a correct phase key to a multi-phase builder passes.
     mbw = self.check(['lookup', '-m', 'fake_builder_group', '-b',
@@ -1247,7 +1246,7 @@
     self.assertIn(
         'Duplicate configs detected. When evaluated fully, the '
         'following configs are all equivalent: \'some_config\', '
-        '\'some_other_config\'.', mbw.out)
+        '\'some_other_config\'.', mbw.err)
 
   def test_good_expectations_validate(self):
     mbw = self.fake_mbw()
@@ -1266,7 +1265,7 @@
     mbw.files.pop(os.path.join(temp_dir, 'fake_builder_group.json'))
     # Now validating should fail.
     self.check(['validate', '--expectations-dir', temp_dir], mbw=mbw, ret=1)
-    self.assertIn('Expectations out of date', mbw.out)
+    self.assertIn('Expectations out of date', mbw.err)
 
   def test_build_command_unix(self):
     files = {
@@ -1323,7 +1322,7 @@
     )
     self.assertIn(
         'MBErr: Builder group name "non-existent-builder-group" not found',
-        mbw.out)
+        mbw.err)
 
   def test_lookup_non_existent_builder(self):
     """Ensure correct behavior when non-existent builder is specified.
@@ -1338,7 +1337,7 @@
         ret=2)
     self.assertIn(
         'MBErr: Builder name "non-existent-builder" not found under groups',
-        mbw.out)
+        mbw.err)
 
 
 if __name__ == '__main__':
diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml
index c740c7ed..a118ecf 100644
--- a/tools/metrics/histograms/enums.xml
+++ b/tools/metrics/histograms/enums.xml
@@ -11974,6 +11974,7 @@
   <int value="-884783216" label="ForceEnableFastCheckoutCapabilities:enabled"/>
   <int value="-884480360" label="ServiceWorkerAutoPreload:disabled"/>
   <int value="-884142134" label="TaskManagerDesktopRefresh:enabled"/>
+  <int value="-883752382" label="FeedHeaderRemoval:disabled"/>
   <int value="-883694393" label="SyncPseudoUSSSupervisedUsers:disabled"/>
   <int value="-883608641" label="enable-cros-action-recorder"/>
   <int value="-882434910" label="EnableAggregatedMlSearchRanking:enabled"/>
@@ -15087,6 +15088,7 @@
   <int value="325907076" label="SanitizerAPI:enabled"/>
   <int value="326414607" label="WallpaperFastRefresh:enabled"/>
   <int value="327045548" label="SafeSearchUrlReporting:enabled"/>
+  <int value="328355983" label="DisableSystemBlur:enabled"/>
   <int value="328722396" label="NTPCondensedLayout:disabled"/>
   <int value="329171361" label="DesktopPWAsDetailedInstallDialog:disabled"/>
   <int value="329891894" label="BookmarkPaneAndroid:disabled"/>
@@ -15252,6 +15254,7 @@
   <int value="384054552" label="EnableDrDcVulkan:disabled"/>
   <int value="384677240" label="AssistMultiWordLacrosSupport:enabled"/>
   <int value="384927690" label="AutofillEnableLocalIban:disabled"/>
+  <int value="385046753" label="DisableSystemBlur:disabled"/>
   <int value="385206456" label="ShowReadyToPayDebugInfo:enabled"/>
   <int value="385339273" label="TextBasedAudioDescription:disabled"/>
   <int value="385969127" label="disable-win32k-lockdown"/>
@@ -19779,6 +19782,7 @@
   <int value="2118374092" label="MessagesForAndroidReaderMode:disabled"/>
   <int value="2119575519" label="ComposeUiRefinement:enabled"/>
   <int value="2119964154" label="enable-download-resumption"/>
+  <int value="2120179658" label="FeedHeaderRemoval:enabled"/>
   <int value="2120363312" label="QuickDim:enabled"/>
   <int value="2120659210" label="BackForwardCache:enabled"/>
   <int value="2121056855" label="IncreaseInputAudioBufferSize:disabled"/>
diff --git a/tools/metrics/histograms/metadata/accessibility/histograms.xml b/tools/metrics/histograms/metadata/accessibility/histograms.xml
index 7186727..fee06e7 100644
--- a/tools/metrics/histograms/metadata/accessibility/histograms.xml
+++ b/tools/metrics/histograms/metadata/accessibility/histograms.xml
@@ -3796,6 +3796,17 @@
   </summary>
 </histogram>
 
+<histogram name="DomDistiller.Time.RunDistillationJavaScript" units="ms"
+    expires_after="2025-08-24">
+  <owner>wylieb@google.com</owner>
+  <owner>chrome-reader-mode-team@google.com</owner>
+  <summary>
+    The amount of time the distiller script spent running on the page. This is
+    includes loading the script onto the web page. Recorded after the user has
+    chosen to view a simplified version of the current page.
+  </summary>
+</histogram>
+
 <histogram name="DomDistiller.Time.TimeToProvideResultToAccumulator" units="ms"
     expires_after="2025-08-24">
   <owner>wylieb@google.com</owner>
@@ -3818,6 +3829,17 @@
   </summary>
 </histogram>
 
+<histogram name="DomDistiller.WordCount" units="count"
+    expires_after="2025-08-24">
+  <owner>wylieb@google.com</owner>
+  <owner>chrome-reader-mode-team@google.com</owner>
+  <summary>
+    Records the word count for the distilled page. Recorded when the page
+    distillation result is received. Recorded per-page even if the article has
+    multiple pages (e.g. a continue reading button).
+  </summary>
+</histogram>
+
 <histogram name="PumpkinInstaller.InstallationSuccess" enum="BooleanSuccess"
     expires_after="2025-05-04">
   <owner>akihiroota@chromium.org</owner>
diff --git a/tools/metrics/histograms/metadata/android/enums.xml b/tools/metrics/histograms/metadata/android/enums.xml
index 4e7e809..e0372ff 100644
--- a/tools/metrics/histograms/metadata/android/enums.xml
+++ b/tools/metrics/histograms/metadata/android/enums.xml
@@ -455,9 +455,10 @@
 
 <enum name="AndroidWebViewSupervisedUserUrlCheckResult">
   <int value="0" label="ALLOWED_SITE_LOADED"/>
-  <int value="1" label="ALLOWED_SITE_DETECTED"/>
+  <int value="1" label="(Obsolete) ALLOWED_SITE_DETECTED. Removed in 2025/02"/>
   <int value="2" label="DISALLOWED_SITE_BLOCKED"/>
-  <int value="3" label="DISALLOWED_SITE_DETECTED"/>
+  <int value="3"
+      label="(Obsolete) DISALLOWED_SITE_DETECTED. Removed in 2025/02"/>
   <int value="4" label="TIMEOUT"/>
   <int value="5" label="UNKNOWN_ERROR"/>
 </enum>
diff --git a/tools/metrics/histograms/metadata/android/histograms.xml b/tools/metrics/histograms/metadata/android/histograms.xml
index a01750b..e7ca93f80 100644
--- a/tools/metrics/histograms/metadata/android/histograms.xml
+++ b/tools/metrics/histograms/metadata/android/histograms.xml
@@ -7729,10 +7729,9 @@
   <owner>src/android_webview/OWNERS</owner>
   <summary>
     Records whether a given URL is allowed to be loaded by the current device
-    user. This is based on a combination of a call to GMS Core, which checks if
-    the url is allowed, and the setting
-    AwFeatures.WEBVIEW_SUPERVISED_USER_SITE_BLOCK, which determines if we
-    actually block urls or only log the results.
+    user. This is based on a call to GMS Core, which checks if the url is
+    allowed. This is recorded during each URL load, however only for the
+    resource types which undergo this type of URL check.
   </summary>
 </histogram>
 
@@ -7742,7 +7741,9 @@
   <owner>src/android_webview/OWNERS</owner>
   <summary>
     Records the time taken to return from the call to GMS Core that checks if a
-    given URL is allowed to be loaded by the current device user.
+    given URL is allowed to be loaded by the current device user. This is
+    recorded during each URL load, however only for the resource types which
+    undergo this type of URL check.
   </summary>
 </histogram>
 
@@ -7753,7 +7754,9 @@
   <summary>
     Records the time taken to return from the call to GMS Core that checks if a
     given URL is allowed to be loaded by the current device user. This metric is
-    only recorded on the first call to GMS Core.
+    only recorded on the first call to GMS Core. This is recorded only for the
+    first URL load, and only for the resource types which undergo this type of
+    URL check.
   </summary>
 </histogram>
 
@@ -7764,7 +7767,9 @@
   <summary>
     Records the time taken to return from the call to GMS Core that checks if a
     given URL is allowed to be loaded by the current device user. This metric is
-    not recorded on the first call, only on subsequent ones.
+    not recorded on the first call, only on subsequent ones. This is recorded
+    during each URL load after the initial load, however only for the resource
+    types which undergo this type of URL check.
   </summary>
 </histogram>
 
diff --git a/tools/metrics/histograms/metadata/autofill/enums.xml b/tools/metrics/histograms/metadata/autofill/enums.xml
index 86411584..013604a 100644
--- a/tools/metrics/histograms/metadata/autofill/enums.xml
+++ b/tools/metrics/histograms/metadata/autofill/enums.xml
@@ -2983,6 +2983,16 @@
 
 <!-- LINT.ThenChange(/components/autofill/core/browser/webdata/autofill_webdata_backend_impl.cc:Result) -->
 
+<!-- LINT.IfChange(BnplPopupWindowResult) -->
+
+<enum name="BnplPopupWindowResult">
+  <int value="0" label="Success"/>
+  <int value="1" label="Failure"/>
+  <int value="2" label="User closed before finishing"/>
+</enum>
+
+<!-- LINT.ThenChange(/components/autofill/core/browser/payments/payments_window_manager.h:BnplFlowResult) -->
+
 <enum name="BnplSuggestionNotShownReason">
   <int value="0" label="Amount extraction failure"/>
   <int value="1" label="Checkout amount not supported"/>
diff --git a/tools/metrics/histograms/metadata/autofill/histograms.xml b/tools/metrics/histograms/metadata/autofill/histograms.xml
index 94cbfcb0e3..45552d7 100644
--- a/tools/metrics/histograms/metadata/autofill/histograms.xml
+++ b/tools/metrics/histograms/metadata/autofill/histograms.xml
@@ -55,6 +55,12 @@
       summary="3DS (Three Domain Secure) authentication only"/>
 </variants>
 
+<variants name="Autofill.BnplIssuer">
+  <variant name="Affirm" summary="Buy-now-pay-later issued by Affirm"/>
+  <variant name="Afterpay" summary="Buy-now-pay-later issued by Afterpay"/>
+  <variant name="Zip" summary="Buy-now-pay-later issued by Zip"/>
+</variants>
+
 <variants name="Autofill.CardMetadataVisible">
   <variant name="ArtImageShown" summary="Only art image was visible"/>
   <variant name="MetadataNotShown" summary="No metadata was visible"/>
@@ -1599,6 +1605,30 @@
   </summary>
 </histogram>
 
+<histogram name="Autofill.Bnpl.PopupWindowResult.{Issuer}"
+    enum="BnplPopupWindowResult" expires_after="2025-07-01">
+  <owner>wilsonlow@google.com</owner>
+  <owner>vinnypersky@google.com</owner>
+  <owner>payments-autofill-team@google.com</owner>
+  <summary>
+    Tracks the result of the BNPL pop-up window for {Issuer}, i.e. Success,
+    Failure, or NotFinished. Logged once when the pop-up window is closed.
+  </summary>
+  <token key="Issuer" variants="Autofill.BnplIssuer"/>
+</histogram>
+
+<histogram name="Autofill.Bnpl.PopupWindowShown.{Issuer}" enum="BooleanShown"
+    expires_after="2025-07-01">
+  <owner>wilsonlow@google.com</owner>
+  <owner>vinnypersky@google.com</owner>
+  <owner>payments-autofill-team@google.com</owner>
+  <summary>
+    The count for the number of times the BNPL popup window for {Issuer} is
+    shown. Records true every time the popup window is shown.
+  </summary>
+  <token key="Issuer" variants="Autofill.BnplIssuer"/>
+</histogram>
+
 <histogram name="Autofill.Bnpl.SuggestionNotShownReason"
     enum="BnplSuggestionNotShownReason" expires_after="2025-07-01">
   <owner>wilsonlow@google.com</owner>
@@ -1612,19 +1642,15 @@
   </summary>
 </histogram>
 
-<histogram name="Autofill.Bnpl.TosDialogShown.{IssuerName}" enum="BooleanShown"
+<histogram name="Autofill.Bnpl.TosDialogShown.{Issuer}" enum="BooleanShown"
     expires_after="2025-07-01">
   <owner>longsheng@google.com</owner>
   <owner>payments-autofill-team@google.com</owner>
   <summary>
-    The count for the number of times the BNPL ToS Dialog is shown. Records true
-    every time the dialog is shown.
+    The count for the number of times the BNPL ToS Dialog is shown for {Issuer}.
+    Records true every time the dialog is shown.
   </summary>
-  <token key="IssuerName">
-    <variant name="Affirm"/>
-    <variant name="Afterpay"/>
-    <variant name="Zip"/>
-  </token>
+  <token key="Issuer" variants="Autofill.BnplIssuer"/>
 </histogram>
 
 <histogram name="Autofill.CardholderNameFixFlowPrompt.Events"
diff --git a/tools/metrics/histograms/metadata/collaboration_service/enums.xml b/tools/metrics/histograms/metadata/collaboration_service/enums.xml
index f5729a2..1beefc9 100644
--- a/tools/metrics/histograms/metadata/collaboration_service/enums.xml
+++ b/tools/metrics/histograms/metadata/collaboration_service/enums.xml
@@ -30,6 +30,11 @@
 
 <enum name="CollaborationServiceJoinEntryPoint">
   <int value="0" label="Unknown"/>
+  <int value="1" label="Link Click"/>
+  <int value="2" label="User Typed"/>
+  <int value="3" label="External Application"/>
+  <int value="4" label="Forward or Back Button"/>
+  <int value="5" label="Redirect"/>
 </enum>
 
 <!-- LINT.ThenChange(//components/collaboration/public/collaboration_flow_entry_point.h:CollaborationServiceJoinEntryPoint) -->
diff --git a/tools/metrics/histograms/metadata/extensions/histograms.xml b/tools/metrics/histograms/metadata/extensions/histograms.xml
index 84e2252a..5f7cca0 100644
--- a/tools/metrics/histograms/metadata/extensions/histograms.xml
+++ b/tools/metrics/histograms/metadata/extensions/histograms.xml
@@ -3819,20 +3819,6 @@
   </summary>
 </histogram>
 
-<histogram name="Extensions.LoadUserScript" units="units"
-    expires_after="2023-07-07">
-  <owner>rdevlin.cronin@chromium.org</owner>
-  <owner>extensions-core@chromium.org</owner>
-  <summary>
-    The number of converted user scripts loaded at profile open.
-
-    Histogram is in the process of being removed in favor of its incremented
-    version that emits only on profile open for &quot;user&quot; profiles
-    (profiles where people can install extensions, specifically profiles that
-    can have non-component extensions installed).
-  </summary>
-</histogram>
-
 <histogram name="Extensions.LoadUserScript2" units="units"
     expires_after="2025-04-20">
   <owner>rdevlin.cronin@chromium.org</owner>
diff --git a/tools/metrics/histograms/metadata/ios/enums.xml b/tools/metrics/histograms/metadata/ios/enums.xml
index 7462ba4f..164e763 100644
--- a/tools/metrics/histograms/metadata/ios/enums.xml
+++ b/tools/metrics/histograms/metadata/ios/enums.xml
@@ -343,6 +343,38 @@
   <int value="3" label="Tertiary Action Button"/>
 </enum>
 
+<!-- LINT.IfChange(IOSDefaultStatusAPIOutcomeType) -->
+
+<enum name="IOSDefaultStatusAPIOutcomeType">
+  <int value="1" label="Success"/>
+  <int value="2" label="Cooldown error"/>
+  <int value="3" label="Other error"/>
+</enum>
+
+<!-- LINT.ThenChange(/ios/chrome/browser/default_browser/model/default_status/default_status_helper_types.h:DefaultStatusAPIOutcomeType) -->
+
+<!-- LINT.IfChange(IOSDefaultStatusHeuristicAssessment) -->
+
+<enum name="IOSDefaultStatusHeuristicAssessment">
+  <int value="0" label="True negative"/>
+  <int value="1" label="False negative"/>
+  <int value="2" label="True positive"/>
+  <int value="3" label="False positive"/>
+</enum>
+
+<!-- LINT.ThenChange(/ios/chrome/browser/default_browser/model/default_status/default_status_helper_types.h:DefaultStatusHeuristicAssessment) -->
+
+<!-- LINT.IfChange(IOSDefaultStatusRetention) -->
+
+<enum name="IOSDefaultStatusRetention">
+  <int value="0" label="Became default"/>
+  <int value="1" label="Became non-default"/>
+  <int value="2" label="Remained default"/>
+  <int value="3" label="Remained non-default"/>
+</enum>
+
+<!-- LINT.ThenChange(/ios/chrome/browser/default_browser/model/default_status/default_status_helper_types.h:DefaultStatusRetention) -->
+
 <enum name="IOSDeleteAllSavedCredentialsActions">
   <int value="0" label="User confirmed delete all saved credentials"/>
   <int value="1" label="User cancelled delete all saved credentials"/>
diff --git a/tools/metrics/histograms/metadata/ios/histograms.xml b/tools/metrics/histograms/metadata/ios/histograms.xml
index f50a2baa..3abd085 100644
--- a/tools/metrics/histograms/metadata/ios/histograms.xml
+++ b/tools/metrics/histograms/metadata/ios/histograms.xml
@@ -1285,6 +1285,189 @@
   </summary>
 </histogram>
 
+<histogram name="IOS.DefaultStatusAPI.CooldownError.DaysLeft" units="Days"
+    expires_after="2026-02-25">
+  <owner>gujen@google.com</owner>
+  <owner>sebsg@chromium.org</owner>
+  <owner>rkgibson@google.com</owner>
+  <owner>rohitrao@chromium.org</owner>
+  <owner>chrome-metrics-team@google.com</owner>
+  <summary>
+    The number of days remaining before the default status API can be called
+    again. The number of days is calculated from the time span between now and
+    the date of next availability provided in the error status.
+
+    Recorded once per failed API call. An API call is made three times per year
+    at browser startup. Only recorded if the error is recognized as a cooldown
+    error (i.e. the API was called too soon). Only recorded on iOS 18.4+.
+  </summary>
+</histogram>
+
+<histogram name="IOS.DefaultStatusAPI.DaysSinceLastSuccessfulCall" units="Days"
+    expires_after="2026-02-25">
+  <owner>gujen@google.com</owner>
+  <owner>sebsg@chromium.org</owner>
+  <owner>rkgibson@google.com</owner>
+  <owner>rohitrao@chromium.org</owner>
+  <owner>chrome-metrics-team@google.com</owner>
+  <summary>
+    The number of days since the last successful call to the default status
+    system API.
+
+    Recorded once per successful API call. An API call is made three times per
+    year at browser startup. If the call is not successful (i.e. there was an
+    error), this histogram is not recorded. Only recorded on iOS 18.4+. Only
+    recorded starting from the second successful API call for this client.
+  </summary>
+</histogram>
+
+<histogram name="IOS.DefaultStatusAPI.DefaultStatusRetention"
+    enum="IOSDefaultStatusRetention" expires_after="2026-02-25">
+  <owner>gujen@google.com</owner>
+  <owner>sebsg@chromium.org</owner>
+  <owner>rkgibson@google.com</owner>
+  <owner>rohitrao@chromium.org</owner>
+  <owner>chrome-metrics-team@google.com</owner>
+  <summary>
+    Records the change in default status since the last successful call to the
+    default status system API for this client.
+
+    Recorded once per successful API call. An API call is made three times per
+    year at browser startup. If the call is not successful (i.e. there was an
+    error), this histogram is not recorded. Only recorded on iOS 18.4+. Only
+    recorded starting from the second successful API call for this client.
+  </summary>
+</histogram>
+
+<histogram name="IOS.DefaultStatusAPI.HeuristicAssessment{Days}"
+    enum="IOSDefaultStatusHeuristicAssessment" expires_after="2026-02-25">
+  <owner>gujen@google.com</owner>
+  <owner>sebsg@chromium.org</owner>
+  <owner>rkgibson@google.com</owner>
+  <owner>rohitrao@chromium.org</owner>
+  <owner>chrome-metrics-team@google.com</owner>
+  <summary>
+    Records the difference between the system reported default status, and the
+    custom, heuristic-based default status (see IOS.IsDefaultBrowser,
+    IOS.IsDefaultBrowser21, and IOS.IsDefaultBrowser[NumberOfDays]).
+
+    A true positive means both the system API and the heuristic had a default
+    status of true. A false positive means the heuristic had a default status of
+    true, but the system API reported false. A true negative means both reported
+    false, and a false negative means the heuristic reported false while the
+    system reported true.
+
+    Recorded once per successful API call. An API call is made three times per
+    year at browser startup. If the call is not successful (i.e. there was an
+    error), this histogram is not recorded. Only recorded on iOS 18.4+.
+
+    This histogram variant is for the {Days}-day heuristic.
+  </summary>
+  <token key="Days">
+    <variant name="1"/>
+    <variant name="3"/>
+    <variant name="7"/>
+    <variant name="14"/>
+    <variant name="21"/>
+    <variant name="28"/>
+    <variant name="35"/>
+    <variant name="42"/>
+  </token>
+</histogram>
+
+<!-- TODO(crbug.com/401551080) Mark this "expires-never" after validating it works as intended. -->
+
+<histogram name="IOS.DefaultStatusAPI.IsDefaultBrowser" enum="Boolean"
+    expires_after="2026-02-25">
+  <owner>gujen@google.com</owner>
+  <owner>sebsg@chromium.org</owner>
+  <owner>rkgibson@google.com</owner>
+  <owner>rohitrao@chromium.org</owner>
+  <owner>chrome-metrics-team@google.com</owner>
+  <summary>
+    Whether Chrome is the default browser or not according to the default status
+    system API.
+
+    Recorded once per successful API call. An API call is made three times per
+    year at browser startup. If the call is not successful (i.e. there was an
+    error), this histogram is not recorded. Only recorded on iOS 18.4+.
+  </summary>
+</histogram>
+
+<!-- LINT.IfChange(CohortNumber) -->
+
+<histogram name="IOS.DefaultStatusAPI.IsDefaultBrowser.Cohort{Number}"
+    enum="Boolean" expires_after="2026-02-25">
+  <owner>gujen@google.com</owner>
+  <owner>sebsg@chromium.org</owner>
+  <owner>rkgibson@google.com</owner>
+  <owner>rohitrao@chromium.org</owner>
+  <owner>chrome-metrics-team@google.com</owner>
+  <summary>
+    Whether Chrome is the default browser or not according to the default status
+    system API. Works the same as IOS.DefaultStatusAPI.IsDefaultBrowser, except
+    this histogram has a variant for each cohort, and clients record only to the
+    histogram variant corresponding to the cohort they are assigned to. The
+    timeline of this histogram can be used to monitor cohort drift.
+
+    Recorded once per successful API call. An API call is made three times per
+    year at browser startup. If the call is not successful (i.e. there was an
+    error), this histogram is not recorded. Only recorded on iOS 18.4+.
+
+    This histogram variant is for cohort {Number}.
+  </summary>
+  <token key="Number">
+    <variant name="1" summary="1 (January, May and September)"/>
+    <variant name="2" summary="2 (February, June, October)"/>
+    <variant name="3" summary="3 (March, July, November)"/>
+    <variant name="4" summary="4 (April, August, December)"/>
+  </token>
+</histogram>
+
+<!-- LINT.ThenChange(/ios/chrome/browser/default_browser/model/default_status/default_status_helper_constants.mm:kCohortCount) -->
+
+<!-- TODO(crbug.com/401551080) Mark this "expires-never" after validating it works as intended. -->
+
+<histogram name="IOS.DefaultStatusAPI.IsDefaultBrowser.StrictCohorts"
+    enum="Boolean" expires_after="2026-02-25">
+  <owner>gujen@google.com</owner>
+  <owner>sebsg@chromium.org</owner>
+  <owner>rkgibson@google.com</owner>
+  <owner>rohitrao@chromium.org</owner>
+  <owner>chrome-metrics-team@google.com</owner>
+  <summary>
+    Whether Chrome is the default browser or not according to the default status
+    system API, but only for clients who made the API call during their assigned
+    cohort's reporting window. Clients are split into 4 cohorts, each with 3
+    assigned months in the year. Depending on usage frequency, it's possible
+    some clients miss their window. When this happens, the API call is still
+    made even if the client is outside their reporting window so that the API
+    call opportunity is not wasted. However, those results will only be recorded
+    to IOS.DefaultStatusAPI.IsDefaultBrowser, not to this histogram.
+
+    Recorded once per successful API call. An API call is made three times per
+    year at browser startup. If the call is not successful (i.e. there was an
+    error), this histogram is not recorded. Only recorded on iOS 18.4+.
+  </summary>
+</histogram>
+
+<histogram name="IOS.DefaultStatusAPI.OutcomeType"
+    enum="IOSDefaultStatusAPIOutcomeType" expires_after="2026-02-25">
+  <owner>gujen@google.com</owner>
+  <owner>sebsg@chromium.org</owner>
+  <owner>rkgibson@google.com</owner>
+  <owner>rohitrao@chromium.org</owner>
+  <owner>chrome-metrics-team@google.com</owner>
+  <summary>
+    Whether a call to the default status system API successfully returned a
+    result or yielded an error. Errors are split into 2 buckets,
+    cooldown-related errors and all other errors.
+
+    Recorded once per API call. An API call is made three times per year at
+    browser startup. Only recorded on iOS 18.4+.
+  </summary>
+</histogram>
+
 <histogram name="IOS.Desktop.{PromoType}.Shown"
     enum="DesktopIOSPromoImpression" expires_after="2025-08-05">
   <owner>nicolasmacbeth@google.com</owner>
diff --git a/tools/metrics/histograms/metadata/lens/enums.xml b/tools/metrics/histograms/metadata/lens/enums.xml
index bfe5b336..c89d3ae35 100644
--- a/tools/metrics/histograms/metadata/lens/enums.xml
+++ b/tools/metrics/histograms/metadata/lens/enums.xml
@@ -219,6 +219,7 @@
   <int value="2" label="Medium"/>
   <int value="3" label="Peaking"/>
   <int value="4" label="Consent"/>
+  <int value="5" label="InfoMessage"/>
 </enum>
 
 <!-- LINT.ThenChange(//ios/chrome/browser/lens_overlay/model/lens_overlay_sheet_detent_state.h:SheetDimensionState) -->
diff --git a/tools/metrics/histograms/metadata/optimization/enums.xml b/tools/metrics/histograms/metadata/optimization/enums.xml
index 4310082..7d11e73 100644
--- a/tools/metrics/histograms/metadata/optimization/enums.xml
+++ b/tools/metrics/histograms/metadata/optimization/enums.xml
@@ -750,6 +750,7 @@
   <int value="88" label="BMO_CREDIT_CARD_WHOLESALE_CLUB_BENEFITS"/>
   <int value="90" label="GLIC_CONTEXTUAL_CUEING"/>
   <int value="91" label="GLIC_ZERO_STATE_SUGGESTIONS"/>
+  <int value="92" label="GLIC_ACTION_PAGE_BLOCK"/>
 </enum>
 
 <enum name="PageContentAnnotationsStorageStatus">
diff --git a/tools/metrics/histograms/metadata/optimization/histograms.xml b/tools/metrics/histograms/metadata/optimization/histograms.xml
index e73dba3..6427045 100644
--- a/tools/metrics/histograms/metadata/optimization/histograms.xml
+++ b/tools/metrics/histograms/metadata/optimization/histograms.xml
@@ -291,6 +291,8 @@
   <variant name="FormsAnnotations"
       summary="Provides information about whether a URL is eligible for
                generating user annotations derived from forms."/>
+  <variant name="GlicActionPageBlock"
+      summary="Provides information about whether GLIC may act on a page."/>
   <variant name="GlicContextualCueing"
       summary="Provides information about whether a page is eligible to cue
                for GLIC."/>
diff --git a/tools/perf/core/bot_platforms.py b/tools/perf/core/bot_platforms.py
index 80aaa6b..a782e7d 100644
--- a/tools/perf/core/bot_platforms.py
+++ b/tools/perf/core/bot_platforms.py
@@ -524,6 +524,16 @@
     ]),
 ])
 
+_CROSSBENCH_PIXEL9 = frozenset([
+    _crossbench_jetstream2(),
+    _crossbench_speedometer3_1(arguments=['--fileserver']),
+    _crossbench_motionmark1_3(),
+    _crossbench_loadline_phone(arguments=[
+        '--cool-down-threshold=moderate',
+        '--no-splash',
+    ]),
+])
+
 _CROSSBENCH_TANGOR = frozenset([
     _crossbench_loadline_tablet(arguments=[
         '--cool-down-threshold=moderate',
@@ -703,11 +713,11 @@
     _GetBenchmarkConfig('speedometer2'),
     _GetBenchmarkConfig('speedometer3'),
 ])
-_ANDROID_GO_WEBVIEW_BENCHMARK_CONFIGS = _ANDROID_GO_BENCHMARK_CONFIGS
-_ANDROID_PIXEL4_BENCHMARK_CONFIGS = PerfSuite(OFFICIAL_BENCHMARK_CONFIGS)
-_ANDROID_PIXEL4_EXECUTABLE_CONFIGS = frozenset([
+_ANDROID_DEFAULT_EXECUTABLE_CONFIGS = frozenset([
     _components_perftests(60),
 ])
+_ANDROID_GO_WEBVIEW_BENCHMARK_CONFIGS = _ANDROID_GO_BENCHMARK_CONFIGS
+_ANDROID_PIXEL4_BENCHMARK_CONFIGS = PerfSuite(OFFICIAL_BENCHMARK_CONFIGS)
 _ANDROID_PIXEL4_WEBVIEW_BENCHMARK_CONFIGS = PerfSuite(
     OFFICIAL_BENCHMARK_CONFIGS).Remove([
         'jetstream2',
@@ -732,15 +742,6 @@
         _GetBenchmarkConfig('speedometer2-minorms'),
         _GetBenchmarkConfig('speedometer3-minorms'),
     ])
-_ANDROID_PIXEL6_EXECUTABLE_CONFIGS = frozenset([
-    _components_perftests(60),
-])
-_ANDROID_PIXEL6_PGO_EXECUTABLE_CONFIGS = frozenset([
-    _components_perftests(60),
-])
-_ANDROID_PIXEL6_PRO_EXECUTABLE_CONFIGS = frozenset([
-    _components_perftests(60),
-])
 # Pixel fold
 _ANDROID_PIXEL_FOLD_BENCHMARK_CONFIGS = PerfSuite(
     OFFICIAL_BENCHMARK_CONFIGS).Add([
@@ -748,9 +749,6 @@
         _GetBenchmarkConfig('speedometer2-minorms'),
         _GetBenchmarkConfig('speedometer3-minorms'),
     ])
-_ANDROID_PIXEL_FOLD_EXECUTABLE_CONFIGS = frozenset([
-    _components_perftests(60),
-])
 # Pixel Tangor
 _ANDROID_PIXEL_TANGOR_BENCHMARK_CONFIGS = PerfSuite(
     OFFICIAL_BENCHMARK_CONFIGS).Add([
@@ -758,8 +756,6 @@
         _GetBenchmarkConfig('speedometer2-minorms'),
         _GetBenchmarkConfig('speedometer3-minorms')
     ])
-_ANDROID_PIXEL_TANGOR_EXECUTABLE_CONFIGS = frozenset(
-    [_components_perftests(60)])
 _CHROMEOS_KEVIN_FYI_BENCHMARK_CONFIGS = PerfSuite(
     [_GetBenchmarkConfig('rendering.desktop')])
 _FUCHSIA_PERF_SMARTDISPLAY_BENCHMARK_CONFIGS = PerfSuite([
@@ -940,14 +936,14 @@
                               _ANDROID_PIXEL4_BENCHMARK_CONFIGS,
                               44,
                               'android',
-                              executables=_ANDROID_PIXEL4_EXECUTABLE_CONFIGS)
+                              executables=_ANDROID_DEFAULT_EXECUTABLE_CONFIGS)
 ANDROID_PIXEL4_PGO = PerfPlatform(
     'android-pixel4-perf-pgo',
     'Android R',
     _ANDROID_PIXEL4_BENCHMARK_CONFIGS,
     28,
     'android',
-    executables=_ANDROID_PIXEL4_EXECUTABLE_CONFIGS,
+    executables=_ANDROID_DEFAULT_EXECUTABLE_CONFIGS,
     pinpoint_only=True)
 ANDROID_PIXEL4_WEBVIEW = PerfPlatform(
     'android-pixel4_webview-perf', 'Android R',
@@ -960,7 +956,7 @@
                               _ANDROID_PIXEL6_BENCHMARK_CONFIGS,
                               14,
                               'android',
-                              executables=_ANDROID_PIXEL6_EXECUTABLE_CONFIGS,
+                              executables=_ANDROID_DEFAULT_EXECUTABLE_CONFIGS,
                               crossbench=_CROSSBENCH_ANDROID)
 ANDROID_PIXEL6_PGO = PerfPlatform(
     'android-pixel6-perf-pgo',
@@ -968,7 +964,7 @@
     _ANDROID_PIXEL6_PGO_BENCHMARK_CONFIGS,
     8,
     'android',
-    executables=_ANDROID_PIXEL6_PGO_EXECUTABLE_CONFIGS,
+    executables=_ANDROID_DEFAULT_EXECUTABLE_CONFIGS,
     crossbench=_CROSSBENCH_ANDROID)
 ANDROID_PIXEL6_PRO = PerfPlatform(
     'android-pixel6-pro-perf',
@@ -976,14 +972,14 @@
     _ANDROID_PIXEL6_PRO_BENCHMARK_CONFIGS,
     10,
     'android',
-    executables=_ANDROID_PIXEL6_PRO_EXECUTABLE_CONFIGS)
+    executables=_ANDROID_DEFAULT_EXECUTABLE_CONFIGS)
 ANDROID_PIXEL6_PRO_PGO = PerfPlatform(
     'android-pixel6-pro-perf-pgo',
     'Android T',
     _ANDROID_PIXEL6_PRO_BENCHMARK_CONFIGS,
     16,
     'android',
-    executables=_ANDROID_PIXEL6_PRO_EXECUTABLE_CONFIGS,
+    executables=_ANDROID_DEFAULT_EXECUTABLE_CONFIGS,
     pinpoint_only=True)
 ANDROID_PIXEL_FOLD = PerfPlatform(
     'android-pixel-fold-perf',
@@ -991,21 +987,43 @@
     _ANDROID_PIXEL_FOLD_BENCHMARK_CONFIGS,
     15,
     'android',
-    executables=_ANDROID_PIXEL_FOLD_EXECUTABLE_CONFIGS)
+    executables=_ANDROID_DEFAULT_EXECUTABLE_CONFIGS)
 ANDROID_PIXEL_TANGOR = PerfPlatform(
     'android-pixel-tangor-perf',
     'Android U',
     _ANDROID_PIXEL_TANGOR_BENCHMARK_CONFIGS,
     8,
     'android',
-    executables=_ANDROID_PIXEL_TANGOR_EXECUTABLE_CONFIGS,
+    executables=_ANDROID_DEFAULT_EXECUTABLE_CONFIGS,
     crossbench=_CROSSBENCH_TANGOR)
 ANDROID_GO_WEMBLEY = PerfPlatform('android-go-wembley-perf', 'Android U',
                                   _ANDROID_GO_BENCHMARK_CONFIGS, 15, 'android')
 ANDROID_GO_WEMBLEY_WEBVIEW = PerfPlatform(
     'android-go-wembley_webview-perf', 'Android U',
     _ANDROID_GO_WEBVIEW_BENCHMARK_CONFIGS, 20, 'android')
-
+ANDROID_PIXEL9 = PerfPlatform('android-pixel9-perf',
+                              'Android B',
+                              PerfSuite([]),
+                              4,
+                              'android',
+                              executables=_ANDROID_DEFAULT_EXECUTABLE_CONFIGS,
+                              crossbench=_CROSSBENCH_PIXEL9)
+ANDROID_PIXEL9_PRO = PerfPlatform(
+    'android-pixel9-pro-perf',
+    'Android B',
+    PerfSuite([]),
+    4,
+    'android',
+    executables=_ANDROID_DEFAULT_EXECUTABLE_CONFIGS,
+    crossbench=_CROSSBENCH_PIXEL9)
+ANDROID_PIXEL9_PRO_XL = PerfPlatform(
+    'android-pixel9-pro-xl-perf',
+    'Android B',
+    PerfSuite([]),
+    4,
+    'android',
+    executables=_ANDROID_DEFAULT_EXECUTABLE_CONFIGS,
+    crossbench=_CROSSBENCH_PIXEL9)
 # Cros
 FUCHSIA_PERF_NELSON = PerfPlatform('fuchsia-perf-nsn',
                                    '',
diff --git a/tools/perf/core/perf_data_generator.py b/tools/perf/core/perf_data_generator.py
index 891f6ad..944cbb0 100755
--- a/tools/perf/core/perf_data_generator.py
+++ b/tools/perf/core/perf_data_generator.py
@@ -738,6 +738,51 @@
             'device_os_flavor': 'google',
         },
     },
+    'android-pixel9-perf': {
+        'tests': [{
+            'isolate':
+            'performance_test_suite_android_trichrome_chrome_google_64_32_bundle',
+        }],
+        'platform':
+        'android-trichrome-chrome-google-64-32-bundle',
+        'dimension': {
+            'pool': 'chrome.tests.perf',
+            'os': 'Android',
+            'device_type': 'tokay',
+            'device_os': 'B',
+            'device_os_flavor': 'google',
+        },
+    },
+    'android-pixel9-pro-perf': {
+        'tests': [{
+            'isolate':
+            'performance_test_suite_android_trichrome_chrome_google_64_32_bundle',
+        }],
+        'platform':
+        'android-trichrome-chrome-google-64-32-bundle',
+        'dimension': {
+            'pool': 'chrome.tests.perf',
+            'os': 'Android',
+            'device_type': 'caiman',
+            'device_os': 'B',
+            'device_os_flavor': 'google',
+        },
+    },
+    'android-pixel9-pro-xl-perf': {
+        'tests': [{
+            'isolate':
+            'performance_test_suite_android_trichrome_chrome_google_64_32_bundle',
+        }],
+        'platform':
+        'android-trichrome-chrome-google-64-32-bundle',
+        'dimension': {
+            'pool': 'chrome.tests.perf',
+            'os': 'Android',
+            'device_type': 'komodo',
+            'device_os': 'B',
+            'device_os_flavor': 'google',
+        },
+    },
     'android-go-processor-perf': {
         'platform': 'linux',
         'perf_processor': True,
diff --git a/tools/perf/core/shard_maps/android-pixel9-perf_map.json b/tools/perf/core/shard_maps/android-pixel9-perf_map.json
new file mode 100644
index 0000000..9658aba
--- /dev/null
+++ b/tools/perf/core/shard_maps/android-pixel9-perf_map.json
@@ -0,0 +1,62 @@
+{
+    "0": {
+        "executables": {
+            "components_perftests": {
+                "arguments": [
+                    "--xvfb"
+                ],
+                "path": "components_perftests"
+            }
+        },
+        "crossbench": {
+            "jetstream_2.2": {
+                "display_name": "jetstream2.crossbench",
+                "arguments": []
+            }
+        },
+        "benchmarks": {}
+    },
+    "1": {
+        "crossbench": {
+            "loadline-phone-fast": {
+                "display_name": "loadline_phone.crossbench",
+                "arguments": [
+                    "--cool-down-threshold=moderate",
+                    "--no-splash"
+                ]
+            }
+        },
+        "benchmarks": {}
+    },
+    "2": {
+        "crossbench": {
+            "motionmark_1.3": {
+                "display_name": "motionmark1.3.crossbench",
+                "arguments": []
+            }
+        },
+        "benchmarks": {}
+    },
+    "3": {
+        "crossbench": {
+            "speedometer_3.1": {
+                "display_name": "speedometer3.1.crossbench",
+                "arguments": [
+                    "--fileserver"
+                ]
+            }
+        },
+        "benchmarks": {}
+    },
+    "extra_infos": {
+        "num_stories": 5,
+        "predicted_min_shard_time": 60,
+        "predicted_min_shard_index": 3,
+        "predicted_max_shard_time": 7000,
+        "predicted_max_shard_index": 1,
+        "shard #0": 240.0,
+        "shard #1": 7000,
+        "shard #2": 360,
+        "shard #3": 60
+    }
+}
diff --git a/tools/perf/core/shard_maps/android-pixel9-pro-perf_map.json b/tools/perf/core/shard_maps/android-pixel9-pro-perf_map.json
new file mode 100644
index 0000000..9658aba
--- /dev/null
+++ b/tools/perf/core/shard_maps/android-pixel9-pro-perf_map.json
@@ -0,0 +1,62 @@
+{
+    "0": {
+        "executables": {
+            "components_perftests": {
+                "arguments": [
+                    "--xvfb"
+                ],
+                "path": "components_perftests"
+            }
+        },
+        "crossbench": {
+            "jetstream_2.2": {
+                "display_name": "jetstream2.crossbench",
+                "arguments": []
+            }
+        },
+        "benchmarks": {}
+    },
+    "1": {
+        "crossbench": {
+            "loadline-phone-fast": {
+                "display_name": "loadline_phone.crossbench",
+                "arguments": [
+                    "--cool-down-threshold=moderate",
+                    "--no-splash"
+                ]
+            }
+        },
+        "benchmarks": {}
+    },
+    "2": {
+        "crossbench": {
+            "motionmark_1.3": {
+                "display_name": "motionmark1.3.crossbench",
+                "arguments": []
+            }
+        },
+        "benchmarks": {}
+    },
+    "3": {
+        "crossbench": {
+            "speedometer_3.1": {
+                "display_name": "speedometer3.1.crossbench",
+                "arguments": [
+                    "--fileserver"
+                ]
+            }
+        },
+        "benchmarks": {}
+    },
+    "extra_infos": {
+        "num_stories": 5,
+        "predicted_min_shard_time": 60,
+        "predicted_min_shard_index": 3,
+        "predicted_max_shard_time": 7000,
+        "predicted_max_shard_index": 1,
+        "shard #0": 240.0,
+        "shard #1": 7000,
+        "shard #2": 360,
+        "shard #3": 60
+    }
+}
diff --git a/tools/perf/core/shard_maps/android-pixel9-pro-xl-perf_map.json b/tools/perf/core/shard_maps/android-pixel9-pro-xl-perf_map.json
new file mode 100644
index 0000000..9658aba
--- /dev/null
+++ b/tools/perf/core/shard_maps/android-pixel9-pro-xl-perf_map.json
@@ -0,0 +1,62 @@
+{
+    "0": {
+        "executables": {
+            "components_perftests": {
+                "arguments": [
+                    "--xvfb"
+                ],
+                "path": "components_perftests"
+            }
+        },
+        "crossbench": {
+            "jetstream_2.2": {
+                "display_name": "jetstream2.crossbench",
+                "arguments": []
+            }
+        },
+        "benchmarks": {}
+    },
+    "1": {
+        "crossbench": {
+            "loadline-phone-fast": {
+                "display_name": "loadline_phone.crossbench",
+                "arguments": [
+                    "--cool-down-threshold=moderate",
+                    "--no-splash"
+                ]
+            }
+        },
+        "benchmarks": {}
+    },
+    "2": {
+        "crossbench": {
+            "motionmark_1.3": {
+                "display_name": "motionmark1.3.crossbench",
+                "arguments": []
+            }
+        },
+        "benchmarks": {}
+    },
+    "3": {
+        "crossbench": {
+            "speedometer_3.1": {
+                "display_name": "speedometer3.1.crossbench",
+                "arguments": [
+                    "--fileserver"
+                ]
+            }
+        },
+        "benchmarks": {}
+    },
+    "extra_infos": {
+        "num_stories": 5,
+        "predicted_min_shard_time": 60,
+        "predicted_min_shard_index": 3,
+        "predicted_max_shard_time": 7000,
+        "predicted_max_shard_index": 1,
+        "shard #0": 240.0,
+        "shard #1": 7000,
+        "shard #2": 360,
+        "shard #3": 60
+    }
+}
diff --git a/tools/perf/core/shard_maps/timing_data/android-pixel9-perf_timing.json b/tools/perf/core/shard_maps/timing_data/android-pixel9-perf_timing.json
new file mode 100644
index 0000000..7a81fb3
--- /dev/null
+++ b/tools/perf/core/shard_maps/timing_data/android-pixel9-perf_timing.json
@@ -0,0 +1,6 @@
+[
+    {
+        "duration": "60.0",
+        "name": "components_perftests/_gtest_"
+    }
+]
diff --git a/tools/perf/core/shard_maps/timing_data/android-pixel9-pro-perf_timing.json b/tools/perf/core/shard_maps/timing_data/android-pixel9-pro-perf_timing.json
new file mode 100644
index 0000000..7a81fb3
--- /dev/null
+++ b/tools/perf/core/shard_maps/timing_data/android-pixel9-pro-perf_timing.json
@@ -0,0 +1,6 @@
+[
+    {
+        "duration": "60.0",
+        "name": "components_perftests/_gtest_"
+    }
+]
diff --git a/tools/perf/core/shard_maps/timing_data/android-pixel9-pro-xl-perf_timing.json b/tools/perf/core/shard_maps/timing_data/android-pixel9-pro-xl-perf_timing.json
new file mode 100644
index 0000000..7a81fb3
--- /dev/null
+++ b/tools/perf/core/shard_maps/timing_data/android-pixel9-pro-xl-perf_timing.json
@@ -0,0 +1,6 @@
+[
+    {
+        "duration": "60.0",
+        "name": "components_perftests/_gtest_"
+    }
+]
diff --git a/ui/android/java/res/layout/dropdown_item.xml b/ui/android/java/res/layout/dropdown_item.xml
index 71d015f..15be133 100644
--- a/ui/android/java/res/layout/dropdown_item.xml
+++ b/ui/android/java/res/layout/dropdown_item.xml
@@ -14,13 +14,6 @@
     android:orientation="horizontal" >
 
     <!-- These layout params are overwritten in DropdownAdapter.java -->
-    <ImageView
-        android:id="@+id/start_dropdown_icon"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_margin="@dimen/dropdown_icon_margin"
-        tools:ignore="ContentDescription" />
-
     <LinearLayout
         android:id="@+id/dropdown_label_wrapper"
         android:layout_width="0dp"
diff --git a/ui/android/java/src/org/chromium/ui/DropdownAdapter.java b/ui/android/java/src/org/chromium/ui/DropdownAdapter.java
index 684173857..8d6e8ff 100644
--- a/ui/android/java/src/org/chromium/ui/DropdownAdapter.java
+++ b/ui/android/java/src/org/chromium/ui/DropdownAdapter.java
@@ -136,15 +136,7 @@
             sublabelView.setVisibility(View.VISIBLE);
         }
 
-        ImageView iconViewStart = (ImageView) layout.findViewById(R.id.start_dropdown_icon);
-        ImageView iconViewEnd = (ImageView) layout.findViewById(R.id.end_dropdown_icon);
-        if (item.isIconAtStart()) {
-            iconViewEnd.setVisibility(View.GONE);
-        } else {
-            iconViewStart.setVisibility(View.GONE);
-        }
-
-        ImageView iconView = item.isIconAtStart() ? iconViewStart : iconViewEnd;
+        ImageView iconView = (ImageView) layout.findViewById(R.id.end_dropdown_icon);
         if (item.getIconId() == DropdownItem.NO_ICON) {
             iconView.setVisibility(View.GONE);
         } else {
diff --git a/ui/android/java/src/org/chromium/ui/DropdownItem.java b/ui/android/java/src/org/chromium/ui/DropdownItem.java
index 4eddfca..918605ab 100644
--- a/ui/android/java/src/org/chromium/ui/DropdownItem.java
+++ b/ui/android/java/src/org/chromium/ui/DropdownItem.java
@@ -69,12 +69,6 @@
     /** Returns resource ID of sublabel's font size. */
     int getSublabelFontSizeResId();
 
-    /**
-     * Returns whether the icon should be displayed at the start, before label
-     * and sublabel.
-     */
-    boolean isIconAtStart();
-
     /** Returns the resource ID of the icon's size, or 0 to use WRAP_CONTENT. */
     int getIconSizeResId();
 
diff --git a/ui/android/java/src/org/chromium/ui/DropdownItemBase.java b/ui/android/java/src/org/chromium/ui/DropdownItemBase.java
index 393dcccd..141a691f 100644
--- a/ui/android/java/src/org/chromium/ui/DropdownItemBase.java
+++ b/ui/android/java/src/org/chromium/ui/DropdownItemBase.java
@@ -72,11 +72,6 @@
     }
 
     @Override
-    public boolean isIconAtStart() {
-        return false;
-    }
-
-    @Override
     public int getIconSizeResId() {
         return 0;
     }
diff --git a/ui/gtk/gtk_ui.cc b/ui/gtk/gtk_ui.cc
index aba90916..76ec70f2 100644
--- a/ui/gtk/gtk_ui.cc
+++ b/ui/gtk/gtk_ui.cc
@@ -673,9 +673,7 @@
   gint size = 0;
   g_object_get(gtk_settings_get_default(), "gtk-cursor-theme-size", &size,
                nullptr);
-  if (GtkCheckVersion(4)) {
-    // GTK4 supports per-monitor scaling, so the gtk-cursor-theme-size is not
-    // premultiplied by the scale factor.
+  if (platform_->IncludeScaleInCursorSize()) {
     size *= display_config().primary_scale;
   }
   return size;
diff --git a/ui/gtk/gtk_ui_platform.h b/ui/gtk/gtk_ui_platform.h
index c14de10..e87ab685 100644
--- a/ui/gtk/gtk_ui_platform.h
+++ b/ui/gtk/gtk_ui_platform.h
@@ -60,6 +60,9 @@
   // If true, the device scale factor should be multiplied by the font scale. If
   // false, the font size should be multiplied by the font scale.
   virtual bool IncludeFontScaleInDeviceScale() const = 0;
+
+  // Returns true if the cursor size should be multiplied by the scale factor.
+  virtual bool IncludeScaleInCursorSize() const = 0;
 };
 
 }  // namespace gtk
diff --git a/ui/gtk/gtk_ui_platform_stub.cc b/ui/gtk/gtk_ui_platform_stub.cc
index fcc7e7fe..aa50fc2 100644
--- a/ui/gtk/gtk_ui_platform_stub.cc
+++ b/ui/gtk/gtk_ui_platform_stub.cc
@@ -50,4 +50,8 @@
   return false;
 }
 
+bool GtkUiPlatformStub::IncludeScaleInCursorSize() const {
+  return false;
+}
+
 }  // namespace gtk
diff --git a/ui/gtk/gtk_ui_platform_stub.h b/ui/gtk/gtk_ui_platform_stub.h
index 0feb75c9..ebdeadc 100644
--- a/ui/gtk/gtk_ui_platform_stub.h
+++ b/ui/gtk/gtk_ui_platform_stub.h
@@ -28,6 +28,7 @@
   std::unique_ptr<ui::LinuxInputMethodContext> CreateInputMethodContext(
       ui::LinuxInputMethodContextDelegate* delegate) const override;
   bool IncludeFontScaleInDeviceScale() const override;
+  bool IncludeScaleInCursorSize() const override;
 };
 
 }  // namespace gtk
diff --git a/ui/gtk/wayland/gtk_ui_platform_wayland.cc b/ui/gtk/wayland/gtk_ui_platform_wayland.cc
index af7181e..b9d1b2087 100644
--- a/ui/gtk/wayland/gtk_ui_platform_wayland.cc
+++ b/ui/gtk/wayland/gtk_ui_platform_wayland.cc
@@ -167,4 +167,8 @@
   return base::FeatureList::IsEnabled(features::kWaylandUiScale);
 }
 
+bool GtkUiPlatformWayland::IncludeScaleInCursorSize() const {
+  return false;
+}
+
 }  // namespace gtk
diff --git a/ui/gtk/wayland/gtk_ui_platform_wayland.h b/ui/gtk/wayland/gtk_ui_platform_wayland.h
index a294174..e805838 100644
--- a/ui/gtk/wayland/gtk_ui_platform_wayland.h
+++ b/ui/gtk/wayland/gtk_ui_platform_wayland.h
@@ -33,6 +33,7 @@
   std::unique_ptr<ui::LinuxInputMethodContext> CreateInputMethodContext(
       ui::LinuxInputMethodContextDelegate* delegate) const override;
   bool IncludeFontScaleInDeviceScale() const override;
+  bool IncludeScaleInCursorSize() const override;
 
  private:
   GdkDisplay* GetDefaultGdkDisplay();
diff --git a/ui/gtk/x/gtk_ui_platform_x11.cc b/ui/gtk/x/gtk_ui_platform_x11.cc
index bd5ac21c..d131bb7 100644
--- a/ui/gtk/x/gtk_ui_platform_x11.cc
+++ b/ui/gtk/x/gtk_ui_platform_x11.cc
@@ -120,4 +120,10 @@
   return true;
 }
 
+bool GtkUiPlatformX11::IncludeScaleInCursorSize() const {
+  // GTK4 supports per-monitor scaling, so the gtk-cursor-theme-size is not
+  // premultiplied by the scale factor.
+  return GtkCheckVersion(4);
+}
+
 }  // namespace gtk
diff --git a/ui/gtk/x/gtk_ui_platform_x11.h b/ui/gtk/x/gtk_ui_platform_x11.h
index 7425966..5634e06 100644
--- a/ui/gtk/x/gtk_ui_platform_x11.h
+++ b/ui/gtk/x/gtk_ui_platform_x11.h
@@ -36,6 +36,7 @@
   std::unique_ptr<ui::LinuxInputMethodContext> CreateInputMethodContext(
       ui::LinuxInputMethodContextDelegate* delegate) const override;
   bool IncludeFontScaleInDeviceScale() const override;
+  bool IncludeScaleInCursorSize() const override;
 
  private:
   GdkDisplay* GetGdkDisplay();
diff --git a/v8 b/v8
index 8688374..ebd5dc0 160000
--- a/v8
+++ b/v8
@@ -1 +1 @@
-Subproject commit 86883747d3c5aba2f21b1963c6c24931f3d00067
+Subproject commit ebd5dc00c889238ea48d84b19c1bc0a8233d41b1