diff --git a/DEPS b/DEPS
index 3236956f..08b694a 100644
--- a/DEPS
+++ b/DEPS
@@ -295,7 +295,7 @@
   # 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': '75e9277e77423a7d6d1d97d3d868ac0840932433',
+  'src_internal_revision': '56087f1ee64af85f055c3dffe1d540ea90626924',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling Skia
   # and whatever else without interference from each other.
@@ -303,11 +303,11 @@
   # 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': 'b61ffce56a3dd06638bdeda9524a831ab67ac741',
+  'v8_revision': '3e4d2e9bda03056d1e4f1434f95777aeb03fbe1f',
   # 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': 'ef31b3ed1c0ec1a9fbcfd8849c2ad001eb53abfb',
+  'angle_revision': '25fc50465713d5c524fc49dda81e80624fd676a5',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling SwiftShader
   # and whatever else without interference from each other.
@@ -391,7 +391,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling devtools-frontend
   # and whatever else without interference from each other.
-  'devtools_frontend_revision': 'aad6291d99a6187f851db465b533cc4e86a0bf65',
+  'devtools_frontend_revision': 'ef9aad93988e3ff4149c5ea6e337abf234dc74de',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling libprotobuf-mutator
   # and whatever else without interference from each other.
@@ -415,7 +415,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': 'aeb65a2a36257d72a85f57acee2f81b73c38d00a',
+  'dawn_revision': 'f7920e1b27c8803c054b22569bc27a96bc20f83f',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
@@ -519,11 +519,11 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling llvm-libc
   # and whatever else without interference from each other.
-  'llvm_libc_revision':    '644c27c66c4f297f86ef9adab0dcf2867dbaa572',
+  'llvm_libc_revision':    '39b4cdb3bd41dee179765787b2a5a0cc49f3fb98',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling llvm-libc
   # and whatever else without interference from each other.
-  'compiler_rt_revision': '735ec98fa1f709cfc3c650f6295d42e4ef4700f6',
+  'compiler_rt_revision': 'd1f4ff49d7d71c2501d32848c9eeb707f857bd4a',
 
   # If you change this, also update the libc++ revision in
   # //buildtools/deps_revisions.gni.
@@ -1521,7 +1521,7 @@
 
   'src/clank': {
     'url': Var('chrome_git') + '/clank/internal/apps.git' + '@' +
-    '725f65605ed6b3b5379a158a33842f299714dbf2',
+    'd7c11e788469c544db99807d9d272a651b39437f',
     'condition': 'checkout_android and checkout_src_internal',
   },
 
@@ -1680,7 +1680,7 @@
     'packages': [
       {
           'package': 'chromium/third_party/androidx',
-          'version': '5LQc1OKhb4L75pHXuARJhai6_7jocKgFKpxmqpImf9cC',
+          'version': 'n4weERZXzpYOkT04kRGYyT13dskz3U_s5fiRdSomD6kC',
       },
     ],
     'condition': 'checkout_android and non_git_source',
@@ -2561,7 +2561,7 @@
     Var('pdfium_git') + '/pdfium.git' + '@' +  Var('pdfium_revision'),
 
   'src/third_party/perfetto':
-    Var('chromium_git') + '/external/github.com/google/perfetto.git' + '@' + 'e6c9e81d08071f80004b089861406cb88ec05bae',
+    Var('chromium_git') + '/external/github.com/google/perfetto.git' + '@' + '557fafc38445fdde33d19b1cf2d784686e51d120',
 
   'src/base/tracing/test/data': {
     'bucket': 'perfetto',
@@ -2980,7 +2980,7 @@
       'packages': [
         {
           'package': 'skia/tools/goldctl/linux-amd64',
-          'version': 'bg1T2otEnMQQe-Lo1MLLZexZL1VsvAVAqfwuhhjSxQ4C',
+          'version': '292Pn0vLgzx9408oFYJWgwBQOo-DbERMVgSoEJ6UfwAC',
         },
       ],
       'dep_type': 'cipd',
@@ -2990,7 +2990,7 @@
       'packages': [
         {
           'package': 'skia/tools/goldctl/windows-amd64',
-          'version': 'RKUSAHdNBItBShnfuk9DnsnmBS-7kTAt7k2N_GzFzmsC',
+          'version': 'gPfWQkZSNWaJpihXXBc7SCvVDlFrPBXvtdq2BSa6OIoC',
         },
       ],
       'dep_type': 'cipd',
@@ -3012,7 +3012,7 @@
       'packages': [
         {
           'package': 'skia/tools/goldctl/mac-arm64',
-          'version': 'LNWlZ0vnIDglaN4JlNc530ujLuhm_WIlIjLZEbgNfOcC',
+          'version': 'i6y9edid-Os3HU7qmVuxE-DH00jNj4Pbl6td-HnHYnYC',
         },
       ],
       'dep_type': 'cipd',
@@ -3056,7 +3056,7 @@
     'packages': [
       {
         'package': 'chromeos_internal/apps/boca_app/app',
-        'version': 'aVkYNu-mq0aHDV_lqBaHxuTlW9yS3uYdeL5UlS3AxVoC',
+        'version': 'U_qH_D2ENsiMzQ2M1wYRkbfdmeDG9L0DzAXpeFSt3H8C',
       },
     ],
     'condition': 'checkout_chromeos and checkout_src_internal',
@@ -3111,7 +3111,7 @@
     'packages': [
       {
         'package': 'chromeos_internal/apps/projector_app/app',
-        'version': 'LvInJ1xdyRfQejk3qA9jNpd2Lq1nVlbTY1v-ZAsbzs0C',
+        'version': 'LQAo1vok5JXCHU0wrkMq6lVgUrGo6mB_XLK6sBV3aOgC',
       },
     ],
     'condition': 'checkout_chromeos and checkout_src_internal',
@@ -4720,7 +4720,7 @@
 
   'src/ios_internal':  {
       'url': Var('chrome_git') + '/chrome/ios_internal.git' + '@' +
-        '798f900e0762d708b11a6342d5b3a9099c7ae736',
+        'c98c23282a810917184ef42c8f5c8724474266e9',
       'condition': 'checkout_ios and checkout_src_internal',
   },
 
diff --git a/android_webview/BUILD.gn b/android_webview/BUILD.gn
index e5b79d9a..5138046 100644
--- a/android_webview/BUILD.gn
+++ b/android_webview/BUILD.gn
@@ -446,7 +446,6 @@
       "java/src/org/chromium/android_webview/AwDevToolsServer.java",
       "java/src/org/chromium/android_webview/AwDisplayCutoutController.java",
       "java/src/org/chromium/android_webview/AwDisplayModeController.java",
-      "java/src/org/chromium/android_webview/AwFeatureMap.java",
       "java/src/org/chromium/android_webview/AwFrameMetricsListener.java",
       "java/src/org/chromium/android_webview/AwGeolocationPermissions.java",
       "java/src/org/chromium/android_webview/AwHistogramRecorder.java",
@@ -754,6 +753,7 @@
 
   android_library("common_java") {
     sources = [
+      "java/src/org/chromium/android_webview/common/AwFeatureMap.java",
       "java/src/org/chromium/android_webview/common/AwResource.java",
       "java/src/org/chromium/android_webview/common/BackgroundThreadExecutor.java",
       "java/src/org/chromium/android_webview/common/BugTrackerConstants.java",
@@ -1150,6 +1150,7 @@
 
 generate_jni("common_jni") {
   sources = [
+    "java/src/org/chromium/android_webview/common/AwFeatureMap.java",
     "java/src/org/chromium/android_webview/common/AwResource.java",
     "java/src/org/chromium/android_webview/common/origin_trial/DisableOriginTrialsSafeModeUtils.java",
   ]
@@ -1172,7 +1173,6 @@
     "java/src/org/chromium/android_webview/AwCrashyClassUtils.java",
     "java/src/org/chromium/android_webview/AwDarkMode.java",
     "java/src/org/chromium/android_webview/AwDevToolsServer.java",
-    "java/src/org/chromium/android_webview/AwFeatureMap.java",
     "java/src/org/chromium/android_webview/AwHttpAuthHandler.java",
     "java/src/org/chromium/android_webview/AwInterfaceRegistrar.java",
     "java/src/org/chromium/android_webview/AwNetLogsConnection.java",
diff --git a/android_webview/browser/BUILD.gn b/android_webview/browser/BUILD.gn
index 5ba9a19..024caed 100644
--- a/android_webview/browser/BUILD.gn
+++ b/android_webview/browser/BUILD.gn
@@ -80,7 +80,6 @@
     "aw_feature_entries.h",
     "aw_feature_list_creator.cc",
     "aw_feature_list_creator.h",
-    "aw_feature_map.cc",
     "aw_field_trials.cc",
     "aw_field_trials.h",
     "aw_form_database_service.cc",
diff --git a/android_webview/browser/aw_browser_main_parts.h b/android_webview/browser/aw_browser_main_parts.h
index 476358ea..114ef90 100644
--- a/android_webview/browser/aw_browser_main_parts.h
+++ b/android_webview/browser/aw_browser_main_parts.h
@@ -13,6 +13,10 @@
 #include "base/task/single_thread_task_executor.h"
 #include "content/public/browser/browser_main_parts.h"
 
+namespace content {
+class SyntheticTrialSyncer;
+}
+
 namespace crash_reporter {
 class ChildExitObserver;
 }
diff --git a/android_webview/browser/network_service/aw_proxying_url_loader_factory.cc b/android_webview/browser/network_service/aw_proxying_url_loader_factory.cc
index f488d940..5b9cfe5 100644
--- a/android_webview/browser/network_service/aw_proxying_url_loader_factory.cc
+++ b/android_webview/browser/network_service/aw_proxying_url_loader_factory.cc
@@ -23,6 +23,7 @@
 #include "android_webview/browser/cookie_manager.h"
 #include "android_webview/browser/network_service/aw_web_resource_intercept_response.h"
 #include "android_webview/browser/network_service/net_helpers.h"
+#include "android_webview/browser/prefetch/aw_prefetch_manager.h"
 #include "android_webview/browser/renderer_host/auto_login_parser.h"
 #include "android_webview/common/aw_features.h"
 #include "android_webview/common/aw_switches.h"
@@ -65,6 +66,7 @@
 #include "services/network/public/mojom/early_hints.mojom.h"
 #include "services/network/public/mojom/fetch_api.mojom.h"
 #include "services/network/public/mojom/restricted_cookie_manager.mojom.h"
+#include "third_party/blink/public/common/navigation/preloading_headers.h"
 #include "third_party/blink/public/mojom/loader/resource_load_info.mojom-shared.h"
 #include "third_party/blink/public/mojom/origin_trials/origin_trial_feature.mojom-shared.h"
 #include "url/gurl.h"
@@ -611,8 +613,17 @@
 
 // logic for when not to invoke shouldInterceptRequest callback
 bool InterceptedRequest::ShouldNotInterceptRequest() {
-  if (request_was_redirected_)
+  bool should_skip_intercept_for_prefetch_enabled =
+      base::FeatureList::IsEnabled(features::kWebViewSkipInterceptsForPrefetch);
+  // Do not call shouldInterceptRequest callback for prefetch requests that are
+  // not also prerenders. This is because prerenders are treated as typical
+  // navigations in WebView and therefore should be intercepted as usual.
+  if (request_was_redirected_ ||
+      (should_skip_intercept_for_prefetch_enabled &&
+       AwPrefetchManager::IsPrefetchRequest(request_) &&
+       !AwPrefetchManager::IsPrerenderRequest(request_))) {
     return true;
+  }
 
   // Do not call shouldInterceptRequest callback for special android urls,
   // unless they fail to load on first attempt. Special android urls are urls
@@ -1037,6 +1048,15 @@
       intercept_response_received_args =
           std::make_unique<InterceptResponseReceivedArgs>();
 
+  bool should_skip_intercept_for_prefetch_enabled =
+      base::FeatureList::IsEnabled(features::kWebViewSkipInterceptsForPrefetch);
+  if (should_skip_intercept_for_prefetch_enabled &&
+      AwPrefetchManager::IsPrefetchRequest(request_)) {
+    // Skip the XRW check if this is a prefetch request.
+    InterceptResponseReceived(std::move(intercept_response_received_args));
+    return;
+  }
+
   CheckXrwOriginTrialAsync(
       browser_context_handle_, xrw_enabled, request_.url, frame_tree_node_id_,
       static_cast<blink::mojom::ResourceType>(request_.resource_type),
diff --git a/android_webview/browser/prefetch/aw_prefetch_manager.cc b/android_webview/browser/prefetch/aw_prefetch_manager.cc
index 4e7a25a..22eeae3 100644
--- a/android_webview/browser/prefetch/aw_prefetch_manager.cc
+++ b/android_webview/browser/prefetch/aw_prefetch_manager.cc
@@ -5,6 +5,8 @@
 
 #include <jni.h>
 
+#include <optional>
+
 #include "android_webview/browser/prefetch/aw_preloading_utils.h"
 #include "base/android/jni_string.h"
 #include "base/android/scoped_java_ref.h"
@@ -13,6 +15,7 @@
 
 // Has to come after all the FromJniType() / ToJniType() headers.
 #include "android_webview/browser_jni_headers/AwPrefetchManager_jni.h"
+#include "third_party/blink/public/common/navigation/preloading_headers.h"
 
 using content::BrowserThread;
 
@@ -80,6 +83,23 @@
 
 AwPrefetchManager::~AwPrefetchManager() = default;
 
+bool AwPrefetchManager::IsPrefetchRequest(
+    const network::ResourceRequest& resource_request) {
+  return AwPrefetchManager::IsSecPurposeForPrefetch(
+      resource_request.headers.GetHeader(blink::kSecPurposeHeaderName));
+}
+
+bool AwPrefetchManager::IsPrerenderRequest(
+    const network::ResourceRequest& resource_request) {
+  return blink::IsSecPurposeForPrerender(
+      resource_request.headers.GetHeader(blink::kSecPurposeHeaderName));
+}
+
+bool AwPrefetchManager::IsSecPurposeForPrefetch(
+    std::optional<std::string> sec_purpose_header_value) {
+  return blink::IsSecPurposeForPrefetch(sec_purpose_header_value);
+}
+
 int AwPrefetchManager::StartPrefetchRequest(
     JNIEnv* env,
     const std::string& url,
@@ -151,9 +171,8 @@
   }
 }
 
-bool AwPrefetchManager::GetIsPrefetchInCacheForTesting(
-    JNIEnv* env,
-    jint prefetch_key) {  // IN-TEST
+bool AwPrefetchManager::GetIsPrefetchInCacheForTesting(JNIEnv* env,
+                                                       jint prefetch_key) {
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
   return all_prefetches_map_.find(prefetch_key) != all_prefetches_map_.end();
 }
@@ -162,6 +181,12 @@
   return NO_PREFETCH_KEY;
 }
 
+jboolean JNI_AwPrefetchManager_IsSecPurposeForPrefetch(
+    JNIEnv* env,
+    std::string& sec_purpose_header_value) {
+  return AwPrefetchManager::IsSecPurposeForPrefetch(sec_purpose_header_value);
+}
+
 base::android::ScopedJavaLocalRef<jobject>
 AwPrefetchManager::GetJavaPrefetchManager() {
   if (!java_obj_) {
diff --git a/android_webview/browser/prefetch/aw_prefetch_manager.h b/android_webview/browser/prefetch/aw_prefetch_manager.h
index d46661c..699bd7b73 100644
--- a/android_webview/browser/prefetch/aw_prefetch_manager.h
+++ b/android_webview/browser/prefetch/aw_prefetch_manager.h
@@ -13,6 +13,7 @@
 #include "content/public/browser/prefetch_request_status_listener.h"
 #include "net/http/http_no_vary_search_data.h"
 #include "net/http/http_request_headers.h"
+#include "services/network/public/cpp/resource_request.h"
 #include "url/gurl.h"
 
 namespace android_webview {
@@ -48,6 +49,25 @@
 
   ~AwPrefetchManager();
 
+  // Returns `true` if the `resource_request` is also a prefetch request.
+  // NOTE: A prefetch request can also be a prerender request i.e.
+  // this method & `IsPrerenderRequest` can both return `true` for it,
+  // however this is not always the case.
+  static bool IsPrefetchRequest(
+      const network::ResourceRequest& resource_request);
+
+  // Returns `true` if the `resource_request` is also a prerender request.
+  // NOTE: A prerender request will always be a prefetch request i.e.
+  // this method & `IsPrefetchRequest` will always return `true` for it
+  // as prefetching a always required for prerendering.
+  static bool IsPrerenderRequest(
+      const network::ResourceRequest& resource_request);
+
+  // Returns `true` if the `blink::kSecPurposeHeaderName` header is associated
+  // with a prefetch request.
+  static bool IsSecPurposeForPrefetch(
+      std::optional<std::string> sec_purpose_header_value);
+
   // Returns the key associated with the outgoing prefetch request
   // and thus the prefetch handle inside of `all_prefetches_map_` (if
   // successful), otherwise returns `NO_PREFETCH_KEY`.
diff --git a/android_webview/common/BUILD.gn b/android_webview/common/BUILD.gn
index 02cdbe5..209fea5 100644
--- a/android_webview/common/BUILD.gn
+++ b/android_webview/common/BUILD.gn
@@ -10,6 +10,7 @@
     "aw_content_client.cc",
     "aw_content_client.h",
     "aw_descriptors.h",
+    "aw_feature_map.cc",
     "aw_features.cc",
     "aw_features.h",
     "aw_media_drm_bridge_client.cc",
@@ -39,8 +40,11 @@
     "//components/cdm/common",
     "//components/crash/core/app",
     "//components/crash/core/common:crash_key",
+    "//components/embedder_support/android/metrics:metrics",
     "//components/embedder_support/origin_trials",
     "//components/gwp_asan/common:crash_keys",
+    "//components/safe_browsing/core/common:features",
+    "//components/sensitive_content:features",
     "//components/services/heap_profiling/public/cpp",
     "//components/version_info",
     "//components/version_info:generate_version_info",
diff --git a/android_webview/common/DEPS b/android_webview/common/DEPS
index 3027294a..d292dfa 100644
--- a/android_webview/common/DEPS
+++ b/android_webview/common/DEPS
@@ -4,8 +4,17 @@
   "+android_webview/common_jni",
   "+components/cdm/common",
   "+components/embedder_support/origin_trials",
-  "+components/gwp_asan/common/crash_key_name.h"
+  "+components/gwp_asan/common/crash_key_name.h",
   "+components/printing/common",
   "+media/base/android",
   "+third_party/widevine/cdm/buildflags.h",
 ]
+specific_include_rules = {
+  # Feature map can depend on features files throughout the codebase.
+  "aw_feature_map.cc": [
+    "+components/embedder_support/android/metrics/features.h",
+    "+components/safe_browsing/core/common/features.h",
+    "+components/sensitive_content/features.h",
+    "+components/viz/common/features.h",
+  ]
+}
diff --git a/android_webview/browser/aw_feature_map.cc b/android_webview/common/aw_feature_map.cc
similarity index 94%
rename from android_webview/browser/aw_feature_map.cc
rename to android_webview/common/aw_feature_map.cc
index 6c2e8b9..c300f9c 100644
--- a/android_webview/browser/aw_feature_map.cc
+++ b/android_webview/common/aw_feature_map.cc
@@ -16,7 +16,7 @@
 #include "content/public/common/content_features.h"
 
 // Must come after all headers that specialize FromJniType() / ToJniType().
-#include "android_webview/browser_jni_headers/AwFeatureMap_jni.h"
+#include "android_webview/common_jni/AwFeatureMap_jni.h"
 
 namespace android_webview {
 
@@ -26,40 +26,42 @@
 // android_webview/common/aw_features.cc or in other locations in the code base
 // (e.g. content/, components/, etc).
 const base::Feature* const kFeaturesExposedToJava[] = {
+    // Ordered alphabetically on feature name.
+    &metrics::kAndroidMetricsAsyncMetricLogging,
+    &base::features::kCollectAndroidFrameTimelineMetrics,
+    &safe_browsing::kHashPrefixRealTimeLookups,
+    &base::features::kPostGetMyMemoryStateToBackground,
+    &::features::kPrefetchBrowserInitiatedTriggers,
+    &sensitive_content::features::kSensitiveContent,
     &features::kWebViewBackForwardCache,
+    &features::kWebViewCacheSizeLimitDerivedFromAppCacheQuota,
+    &features::kWebViewDisableCHIPS,
+    &features::kWebViewDoNotSendAccessibilityEventsOnGSU,
     &features::kWebViewDrainPrefetchQueueDuringInit,
+    &features::kWebViewEnableCrash,
     &features::kWebViewFileSystemAccess,
+    &features::kWebViewHyperlinkContextMenu,
     &features::kWebViewInvokeZoomPickerOnGSU,
     &features::kWebViewLazyFetchHandWritingIcon,
+    &features::kWebViewMediaIntegrityApiBlinkExtension,
     &features::kWebViewMixedContentAutoupgrades,
+    &features::kWebViewMuteAudio,
+    &features::kWebViewPrefetchNativeLibrary,
+    &features::kWebViewPreloadClasses,
+    &features::kWebViewQuicConnectionTimeout,
+    &features::kWebViewRecordAppCacheHistograms,
+    &features::kWebViewReduceUAAndroidVersionDeviceModel,
+    &features::kWebViewSafeAreaIncludesSystemBars,
+    &features::kWebViewSeparateResourceContext,
+    &features::kWebViewSkipInterceptsForPrefetch,
+    &features::kWebViewShortCircuitShouldInterceptRequest,
     &features::kWebViewTestFeature,
+    &features::kWebViewUseInitialNetworkStateAtStartup,
     &features::kWebViewUseMetricsUploadService,
     &features::kWebViewUseMetricsUploadServiceOnlySdkRuntime,
-    &features::kWebViewXRequestedWithHeaderControl,
-    &metrics::kAndroidMetricsAsyncMetricLogging,
-    &safe_browsing::kHashPrefixRealTimeLookups,
-    &base::features::kCollectAndroidFrameTimelineMetrics,
-    &features::kWebViewMediaIntegrityApiBlinkExtension,
-    &features::kWebViewSeparateResourceContext,
-    &features::kWebViewMuteAudio,
-    &features::kWebViewUseInitialNetworkStateAtStartup,
-    &features::kWebViewReduceUAAndroidVersionDeviceModel,
-    &features::kWebViewEnableCrash,
-    &features::kWebViewPreloadClasses,
-    &features::kWebViewPrefetchNativeLibrary,
-    &features::kWebViewDoNotSendAccessibilityEventsOnGSU,
-    &features::kWebViewHyperlinkContextMenu,
-    &features::kWebViewDisableCHIPS,
-    &features::kWebViewSafeAreaIncludesSystemBars,
-    &base::features::kPostGetMyMemoryStateToBackground,
-    &sensitive_content::features::kSensitiveContent,
-    &features::kWebViewWebauthn,
-    &::features::kPrefetchBrowserInitiatedTriggers,
-    &features::kWebViewShortCircuitShouldInterceptRequest,
     &features::kWebViewUseStartupTasksLogic,
-    &features::kWebViewRecordAppCacheHistograms,
-    &features::kWebViewQuicConnectionTimeout,
-    &features::kWebViewCacheSizeLimitDerivedFromAppCacheQuota,
+    &features::kWebViewWebauthn,
+    &features::kWebViewXRequestedWithHeaderControl,
 };
 
 // static
diff --git a/android_webview/common/aw_features.cc b/android_webview/common/aw_features.cc
index 225194d..e919f71 100644
--- a/android_webview/common/aw_features.cc
+++ b/android_webview/common/aw_features.cc
@@ -164,6 +164,12 @@
              "WebViewSeparateResourceContext",
              base::FEATURE_ENABLED_BY_DEFAULT);
 
+// Whether to skip shouldInterceptRequest and other checks for prefetch
+// requests.
+BASE_FEATURE(kWebViewSkipInterceptsForPrefetch,
+             "WebViewSkipInterceptsForPrefetch",
+             base::FEATURE_ENABLED_BY_DEFAULT);
+
 // Whether to use initial network state during initialization to speed up
 // startup.
 BASE_FEATURE(kWebViewUseInitialNetworkStateAtStartup,
diff --git a/android_webview/common/aw_features.h b/android_webview/common/aw_features.h
index 0074c4f..4ca4d1c 100644
--- a/android_webview/common/aw_features.h
+++ b/android_webview/common/aw_features.h
@@ -48,6 +48,7 @@
 extern const base::FeatureParam<bool> kWebViewPrefetchFromRenderer;
 BASE_DECLARE_FEATURE(kWebViewSafeAreaIncludesSystemBars);
 BASE_DECLARE_FEATURE(kWebViewSeparateResourceContext);
+BASE_DECLARE_FEATURE(kWebViewSkipInterceptsForPrefetch);
 BASE_DECLARE_FEATURE(kWebViewDoNotSendAccessibilityEventsOnGSU);
 BASE_DECLARE_FEATURE(kWebViewHyperlinkContextMenu);
 BASE_DECLARE_FEATURE(kCreateSpareRendererOnBrowserContextCreation);
diff --git a/android_webview/glue/java/src/com/android/webview/chromium/WebViewCachedFlags.java b/android_webview/glue/java/src/com/android/webview/chromium/WebViewCachedFlags.java
index f3d5818..fb18fd7 100644
--- a/android_webview/glue/java/src/com/android/webview/chromium/WebViewCachedFlags.java
+++ b/android_webview/glue/java/src/com/android/webview/chromium/WebViewCachedFlags.java
@@ -11,7 +11,7 @@
 import androidx.annotation.IntDef;
 import androidx.annotation.VisibleForTesting;
 
-import org.chromium.android_webview.AwFeatureMap;
+import org.chromium.android_webview.common.AwFeatureMap;
 import org.chromium.android_webview.common.AwFeatures;
 import org.chromium.base.metrics.RecordHistogram;
 import org.chromium.build.annotations.NullMarked;
diff --git a/android_webview/glue/java/src/com/android/webview/chromium/WebViewChromiumAwInit.java b/android_webview/glue/java/src/com/android/webview/chromium/WebViewChromiumAwInit.java
index 07695e1..a62ed4d 100644
--- a/android_webview/glue/java/src/com/android/webview/chromium/WebViewChromiumAwInit.java
+++ b/android_webview/glue/java/src/com/android/webview/chromium/WebViewChromiumAwInit.java
@@ -39,7 +39,6 @@
 import org.chromium.android_webview.AwCookieManager;
 import org.chromium.android_webview.AwCrashyClassUtils;
 import org.chromium.android_webview.AwDarkMode;
-import org.chromium.android_webview.AwFeatureMap;
 import org.chromium.android_webview.AwLocaleConfig;
 import org.chromium.android_webview.AwNetworkChangeNotifierRegistrationPolicy;
 import org.chromium.android_webview.AwProxyController;
@@ -49,6 +48,7 @@
 import org.chromium.android_webview.HttpAuthDatabase;
 import org.chromium.android_webview.R;
 import org.chromium.android_webview.WebViewChromiumRunQueue;
+import org.chromium.android_webview.common.AwFeatureMap;
 import org.chromium.android_webview.common.AwFeatures;
 import org.chromium.android_webview.common.AwResource;
 import org.chromium.android_webview.common.AwSwitches;
diff --git a/android_webview/java/src/org/chromium/android_webview/AwBrowserContext.java b/android_webview/java/src/org/chromium/android_webview/AwBrowserContext.java
index 5a0fbfc..db857886 100644
--- a/android_webview/java/src/org/chromium/android_webview/AwBrowserContext.java
+++ b/android_webview/java/src/org/chromium/android_webview/AwBrowserContext.java
@@ -18,6 +18,7 @@
 import org.jni_zero.JniType;
 import org.jni_zero.NativeMethods;
 
+import org.chromium.android_webview.common.AwFeatureMap;
 import org.chromium.android_webview.common.Lifetime;
 import org.chromium.android_webview.common.MediaIntegrityApiStatus;
 import org.chromium.android_webview.common.MediaIntegrityProvider;
diff --git a/android_webview/java/src/org/chromium/android_webview/AwBrowserProcess.java b/android_webview/java/src/org/chromium/android_webview/AwBrowserProcess.java
index 22de7f7..b44c478 100644
--- a/android_webview/java/src/org/chromium/android_webview/AwBrowserProcess.java
+++ b/android_webview/java/src/org/chromium/android_webview/AwBrowserProcess.java
@@ -24,6 +24,7 @@
 import org.jni_zero.JniType;
 import org.jni_zero.NativeMethods;
 
+import org.chromium.android_webview.common.AwFeatureMap;
 import org.chromium.android_webview.common.AwFeatures;
 import org.chromium.android_webview.common.AwSwitches;
 import org.chromium.android_webview.common.Lifetime;
diff --git a/android_webview/java/src/org/chromium/android_webview/AwClassPreloader.java b/android_webview/java/src/org/chromium/android_webview/AwClassPreloader.java
index 23467e23..ed654fec 100644
--- a/android_webview/java/src/org/chromium/android_webview/AwClassPreloader.java
+++ b/android_webview/java/src/org/chromium/android_webview/AwClassPreloader.java
@@ -4,6 +4,7 @@
 
 package org.chromium.android_webview;
 
+import org.chromium.android_webview.common.AwFeatureMap;
 import org.chromium.base.Log;
 import org.chromium.base.metrics.ScopedSysTraceEvent;
 import org.chromium.base.task.PostTask;
diff --git a/android_webview/java/src/org/chromium/android_webview/AwContents.java b/android_webview/java/src/org/chromium/android_webview/AwContents.java
index dec18c5..25fd675 100644
--- a/android_webview/java/src/org/chromium/android_webview/AwContents.java
+++ b/android_webview/java/src/org/chromium/android_webview/AwContents.java
@@ -62,6 +62,7 @@
 import org.jni_zero.NativeMethods;
 
 import org.chromium.android_webview.autofill.AndroidAutofillSafeModeAction;
+import org.chromium.android_webview.common.AwFeatureMap;
 import org.chromium.android_webview.common.AwFeatures;
 import org.chromium.android_webview.common.AwSwitches;
 import org.chromium.android_webview.common.Lifetime;
diff --git a/android_webview/java/src/org/chromium/android_webview/AwContentsClient.java b/android_webview/java/src/org/chromium/android_webview/AwContentsClient.java
index a81a8225..945336a 100644
--- a/android_webview/java/src/org/chromium/android_webview/AwContentsClient.java
+++ b/android_webview/java/src/org/chromium/android_webview/AwContentsClient.java
@@ -23,6 +23,7 @@
 import androidx.annotation.IntDef;
 import androidx.annotation.Nullable;
 
+import org.chromium.android_webview.common.AwFeatureMap;
 import org.chromium.android_webview.common.AwFeatures;
 import org.chromium.android_webview.common.Lifetime;
 import org.chromium.android_webview.permission.AwPermissionRequest;
diff --git a/android_webview/java/src/org/chromium/android_webview/AwCrashyClassUtils.java b/android_webview/java/src/org/chromium/android_webview/AwCrashyClassUtils.java
index 56b59d9..1415a55 100644
--- a/android_webview/java/src/org/chromium/android_webview/AwCrashyClassUtils.java
+++ b/android_webview/java/src/org/chromium/android_webview/AwCrashyClassUtils.java
@@ -9,6 +9,7 @@
 import org.jni_zero.JNINamespace;
 import org.jni_zero.NativeMethods;
 
+import org.chromium.android_webview.common.AwFeatureMap;
 import org.chromium.android_webview.common.AwFeatures;
 import org.chromium.android_webview.common.AwSwitches;
 import org.chromium.base.CommandLine;
diff --git a/android_webview/java/src/org/chromium/android_webview/AwDisplayCutoutController.java b/android_webview/java/src/org/chromium/android_webview/AwDisplayCutoutController.java
index 46ff8e2868..33a5b84 100644
--- a/android_webview/java/src/org/chromium/android_webview/AwDisplayCutoutController.java
+++ b/android_webview/java/src/org/chromium/android_webview/AwDisplayCutoutController.java
@@ -14,6 +14,7 @@
 import androidx.annotation.VisibleForTesting;
 import androidx.core.view.WindowInsetsCompat;
 
+import org.chromium.android_webview.common.AwFeatureMap;
 import org.chromium.android_webview.common.AwFeatures;
 import org.chromium.android_webview.common.Lifetime;
 import org.chromium.base.Log;
diff --git a/android_webview/java/src/org/chromium/android_webview/AwFrameMetricsListener.java b/android_webview/java/src/org/chromium/android_webview/AwFrameMetricsListener.java
index 1e995b1b..ae7528d 100644
--- a/android_webview/java/src/org/chromium/android_webview/AwFrameMetricsListener.java
+++ b/android_webview/java/src/org/chromium/android_webview/AwFrameMetricsListener.java
@@ -9,6 +9,7 @@
 
 import androidx.annotation.Nullable;
 
+import org.chromium.android_webview.common.AwFeatureMap;
 import org.chromium.base.BaseFeatures;
 import org.chromium.base.TimeUtils;
 import org.chromium.base.jank_tracker.FrameMetricsListener;
diff --git a/android_webview/java/src/org/chromium/android_webview/AwPrefetchManager.java b/android_webview/java/src/org/chromium/android_webview/AwPrefetchManager.java
index d639b21..f49a439 100644
--- a/android_webview/java/src/org/chromium/android_webview/AwPrefetchManager.java
+++ b/android_webview/java/src/org/chromium/android_webview/AwPrefetchManager.java
@@ -19,6 +19,7 @@
 import org.jni_zero.NativeMethods;
 
 import org.chromium.android_webview.AwPrefetchCallback.StatusCode;
+import org.chromium.android_webview.common.AwFeatureMap;
 import org.chromium.android_webview.common.Lifetime;
 import org.chromium.base.ThreadUtils;
 import org.chromium.base.TraceEvent;
@@ -64,6 +65,10 @@
         return new AwPrefetchManager(nativePrefetchManager);
     }
 
+    public static boolean isSecPurposeForPrefetch(String secPurposeHeaderValue) {
+        return AwPrefetchManagerJni.get().isSecPurposeForPrefetch(secPurposeHeaderValue);
+    }
+
     @Nullable
     private static Exception getStartPrefetchErrorOrNull(
             String url, AwPrefetchParameters prefetchParameters) {
@@ -267,6 +272,8 @@
 
         int getNoPrefetchKey();
 
+        boolean isSecPurposeForPrefetch(@JniType("std::string") String secPurposeHeaderValue);
+
         // Returns the prefetch key used to cancel the request.
         int startPrefetchRequest(
                 long nativeAwPrefetchManager,
diff --git a/android_webview/java/src/org/chromium/android_webview/AwSettings.java b/android_webview/java/src/org/chromium/android_webview/AwSettings.java
index a4e42a9..37eadbea 100644
--- a/android_webview/java/src/org/chromium/android_webview/AwSettings.java
+++ b/android_webview/java/src/org/chromium/android_webview/AwSettings.java
@@ -26,6 +26,7 @@
 import org.jni_zero.NativeMethods;
 
 import org.chromium.android_webview.client_hints.AwUserAgentMetadata;
+import org.chromium.android_webview.common.AwFeatureMap;
 import org.chromium.android_webview.common.AwFeatures;
 import org.chromium.android_webview.common.AwSwitches;
 import org.chromium.android_webview.common.Lifetime;
diff --git a/android_webview/java/src/org/chromium/android_webview/FileModeConversionHelper.java b/android_webview/java/src/org/chromium/android_webview/FileModeConversionHelper.java
index 1e043f0..92db88f 100644
--- a/android_webview/java/src/org/chromium/android_webview/FileModeConversionHelper.java
+++ b/android_webview/java/src/org/chromium/android_webview/FileModeConversionHelper.java
@@ -6,6 +6,7 @@
 
 import android.webkit.WebChromeClient;
 
+import org.chromium.android_webview.common.AwFeatureMap;
 import org.chromium.android_webview.common.AwFeatures;
 import org.chromium.blink.mojom.FileChooserParams;
 import org.chromium.build.annotations.NullMarked;
diff --git a/android_webview/java/src/org/chromium/android_webview/ShouldInterceptRequestMediator.java b/android_webview/java/src/org/chromium/android_webview/ShouldInterceptRequestMediator.java
index 0ca340a..bf04579f 100644
--- a/android_webview/java/src/org/chromium/android_webview/ShouldInterceptRequestMediator.java
+++ b/android_webview/java/src/org/chromium/android_webview/ShouldInterceptRequestMediator.java
@@ -16,6 +16,7 @@
 import org.jni_zero.JNINamespace;
 import org.jni_zero.JniType;
 
+import org.chromium.android_webview.common.AwFeatureMap;
 import org.chromium.android_webview.common.AwFeatures;
 import org.chromium.android_webview.common.Lifetime;
 import org.chromium.base.JniOnceCallback;
diff --git a/android_webview/java/src/org/chromium/android_webview/AwFeatureMap.java b/android_webview/java/src/org/chromium/android_webview/common/AwFeatureMap.java
similarity index 90%
rename from android_webview/java/src/org/chromium/android_webview/AwFeatureMap.java
rename to android_webview/java/src/org/chromium/android_webview/common/AwFeatureMap.java
index d9cc1b10..4c0efb7 100644
--- a/android_webview/java/src/org/chromium/android_webview/AwFeatureMap.java
+++ b/android_webview/java/src/org/chromium/android_webview/common/AwFeatureMap.java
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-package org.chromium.android_webview;
+package org.chromium.android_webview.common;
 
 import org.jni_zero.JNINamespace;
 import org.jni_zero.NativeMethods;
@@ -19,7 +19,9 @@
     // Do not instantiate this class.
     private AwFeatureMap() {}
 
-    /** @return the singleton UiAndroidFeatureMap. */
+    /**
+     * @return the singleton UiAndroidFeatureMap.
+     */
     public static AwFeatureMap getInstance() {
         return sInstance;
     }
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 a7aaab0..fc8242e 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
@@ -868,6 +868,9 @@
                 AwFeatures.WEBVIEW_SEPARATE_RESOURCE_CONTEXT,
                 "Use WebView's own Context for Resources rather than the embedding app's"),
         Flag.baseFeature(
+                AwFeatures.WEBVIEW_SKIP_INTERCEPTS_FOR_PREFETCH,
+                "Skip shouldInterceptRequest and other checks for prefetch requests."),
+        Flag.baseFeature(
                 BlinkFeatures.STANDARDIZED_BROWSER_ZOOM,
                 "Enable conformance to the new HTML specification for CSS zoom."),
         Flag.baseFeature("UseContextSnapshot"),
diff --git a/android_webview/javatests/src/org/chromium/android_webview/test/AwPrefetchTest.java b/android_webview/javatests/src/org/chromium/android_webview/test/AwPrefetchTest.java
index 38d75c3..02e32f4 100644
--- a/android_webview/javatests/src/org/chromium/android_webview/test/AwPrefetchTest.java
+++ b/android_webview/javatests/src/org/chromium/android_webview/test/AwPrefetchTest.java
@@ -9,9 +9,9 @@
 
 import androidx.annotation.Keep;
 import androidx.annotation.Nullable;
-import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.LargeTest;
 import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
 
 import org.junit.Assert;
 import org.junit.Before;
@@ -60,6 +60,7 @@
     private static final String BASIC_PREFETCH_URL = "/android_webview/test/data/hello_world.html";
 
     private final TestAwContentsClient mContentsClient;
+    private AwEmbeddedTestServer mTestServer;
     private String mPrefetchUrl;
 
     public AwPrefetchTest(AwSettingsMutation param) {
@@ -73,7 +74,7 @@
     public void setUp() throws Exception {
         mActivityTestRule.startBrowserProcess();
 
-        AwEmbeddedTestServer mTestServer =
+        mTestServer =
                 AwEmbeddedTestServer.createAndStartHTTPSServer(
                         InstrumentationRegistry.getInstrumentation().getContext(),
                         ServerCertificate.CERT_TEST_NAMES);
@@ -492,6 +493,32 @@
         loadUrlLatch.countDown();
     }
 
+    @Test
+    @LargeTest
+    @Feature({"AndroidWebView"})
+    @CommandLineFlags.Add({ContentSwitches.HOST_RESOLVER_RULES + "=MAP * 127.0.0.1"})
+    public void testPrefetchHasExpectedSecHeaderPurposeHeaderValue() throws Throwable {
+        // Prepare PrefetchParameters
+        Map<String, String> additionalHeaders = new HashMap<>();
+        additionalHeaders.put("foo", "bar");
+        additionalHeaders.put("lorem", "ipsum");
+        AwNoVarySearchData expectedNoVarySearch =
+                new AwNoVarySearchData(false, false, new String[] {"ts", "uid"}, null);
+        AwPrefetchParameters prefetchParameters =
+                new AwPrefetchParameters(additionalHeaders, expectedNoVarySearch, true);
+
+        // Do the prefetch request.
+        TestAwPrefetchCallback callback = startPrefetchingAndWait(mPrefetchUrl, prefetchParameters);
+
+        // wait then do the checks
+        callback.mOnStatusUpdatedHelper.waitForNext();
+        HashMap<String, String> prefetchHeaders =
+                mTestServer.getRequestHeadersForUrl(BASIC_PREFETCH_URL);
+        String secPurposeHeaderValue = prefetchHeaders.get("Sec-Purpose");
+        Assert.assertNotNull(secPurposeHeaderValue);
+        Assert.assertTrue(AwPrefetchManager.isSecPurposeForPrefetch(secPurposeHeaderValue));
+    }
+
     private static AwPrefetchParameters getAwPrefetchParameters() {
         AwNoVarySearchData expectedNoVarySearch =
                 new AwNoVarySearchData(false, false, new String[] {"ts", "uid"}, null);
diff --git a/android_webview/javatests/src/org/chromium/android_webview/test/AwPrerenderTest.java b/android_webview/javatests/src/org/chromium/android_webview/test/AwPrerenderTest.java
index b6793387..bbaeb101 100644
--- a/android_webview/javatests/src/org/chromium/android_webview/test/AwPrerenderTest.java
+++ b/android_webview/javatests/src/org/chromium/android_webview/test/AwPrerenderTest.java
@@ -26,12 +26,12 @@
 import org.junit.runners.Parameterized.UseParametersRunnerFactory;
 
 import org.chromium.android_webview.AwContents;
-import org.chromium.android_webview.AwFeatureMap;
 import org.chromium.android_webview.AwNoVarySearchData;
 import org.chromium.android_webview.AwPrefetchCallback;
 import org.chromium.android_webview.AwPrefetchParameters;
 import org.chromium.android_webview.AwWebResourceRequest;
 import org.chromium.android_webview.ScriptHandler;
+import org.chromium.android_webview.common.AwFeatureMap;
 import org.chromium.android_webview.common.AwFeatures;
 import org.chromium.android_webview.settings.SpeculativeLoadingAllowedFlags;
 import org.chromium.base.Callback;
diff --git a/android_webview/javatests/src/org/chromium/android_webview/test/AwSettingsTest.java b/android_webview/javatests/src/org/chromium/android_webview/test/AwSettingsTest.java
index 0c9d4c11..45e30d4 100644
--- a/android_webview/javatests/src/org/chromium/android_webview/test/AwSettingsTest.java
+++ b/android_webview/javatests/src/org/chromium/android_webview/test/AwSettingsTest.java
@@ -27,11 +27,11 @@
 import org.junit.runner.RunWith;
 
 import org.chromium.android_webview.AwContents;
-import org.chromium.android_webview.AwFeatureMap;
 import org.chromium.android_webview.AwSettings;
 import org.chromium.android_webview.AwSettings.LayoutAlgorithm;
 import org.chromium.android_webview.AwWebResourceRequest;
 import org.chromium.android_webview.ManifestMetadataUtil;
+import org.chromium.android_webview.common.AwFeatureMap;
 import org.chromium.android_webview.common.AwFeatures;
 import org.chromium.android_webview.test.AwActivityTestRule.TestDependencyFactory;
 import org.chromium.android_webview.test.TestAwContentsClient.DoUpdateVisitedHistoryHelper;
diff --git a/android_webview/javatests/src/org/chromium/android_webview/test/NavigationHistoryTest.java b/android_webview/javatests/src/org/chromium/android_webview/test/NavigationHistoryTest.java
index 8adf7bac..0e55225 100644
--- a/android_webview/javatests/src/org/chromium/android_webview/test/NavigationHistoryTest.java
+++ b/android_webview/javatests/src/org/chromium/android_webview/test/NavigationHistoryTest.java
@@ -21,7 +21,7 @@
 import org.junit.runners.Parameterized.UseParametersRunnerFactory;
 
 import org.chromium.android_webview.AwContents;
-import org.chromium.android_webview.AwFeatureMap;
+import org.chromium.android_webview.common.AwFeatureMap;
 import org.chromium.android_webview.common.AwFeatures;
 import org.chromium.android_webview.test.AwActivityTestRule.PopupInfo;
 import org.chromium.android_webview.test.util.CommonResources;
diff --git a/android_webview/javatests/src/org/chromium/android_webview/test/NavigationListenerTest.java b/android_webview/javatests/src/org/chromium/android_webview/test/NavigationListenerTest.java
index 46612cc..ca2feea 100644
--- a/android_webview/javatests/src/org/chromium/android_webview/test/NavigationListenerTest.java
+++ b/android_webview/javatests/src/org/chromium/android_webview/test/NavigationListenerTest.java
@@ -21,9 +21,9 @@
 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.AwFeatureMap;
 import org.chromium.android_webview.common.AwFeatures;
 import org.chromium.android_webview.test.TestAwContentsClient.OnReceivedTitleHelper;
 import org.chromium.base.test.util.Batch;
diff --git a/android_webview/javatests/src/org/chromium/android_webview/test/VariationsTest.java b/android_webview/javatests/src/org/chromium/android_webview/test/VariationsTest.java
index 4b95503..7e82323 100644
--- a/android_webview/javatests/src/org/chromium/android_webview/test/VariationsTest.java
+++ b/android_webview/javatests/src/org/chromium/android_webview/test/VariationsTest.java
@@ -15,7 +15,7 @@
 import org.junit.runners.Parameterized;
 import org.junit.runners.Parameterized.UseParametersRunnerFactory;
 
-import org.chromium.android_webview.AwFeatureMap;
+import org.chromium.android_webview.common.AwFeatureMap;
 import org.chromium.android_webview.common.AwFeatures;
 import org.chromium.android_webview.common.variations.VariationsUtils;
 import org.chromium.android_webview.test.util.VariationsTestUtils;
diff --git a/ash/scanner/scanner_controller.cc b/ash/scanner/scanner_controller.cc
index 4ff7ecb5..677785c9 100644
--- a/ash/scanner/scanner_controller.cc
+++ b/ash/scanner/scanner_controller.cc
@@ -54,6 +54,7 @@
 #include "components/account_id/account_id.h"
 #include "components/feedback/feedback_constants.h"
 #include "components/manta/proto/scanner.pb.h"
+#include "components/prefs/pref_registry_simple.h"
 #include "ui/base/l10n/l10n_util.h"
 #include "ui/message_center/message_center.h"
 #include "ui/message_center/public/cpp/notification.h"
diff --git a/base/android/java/src/org/chromium/base/process_launcher/ChildProcessConnection.java b/base/android/java/src/org/chromium/base/process_launcher/ChildProcessConnection.java
index 4bd3199..ca23115 100644
--- a/base/android/java/src/org/chromium/base/process_launcher/ChildProcessConnection.java
+++ b/base/android/java/src/org/chromium/base/process_launcher/ChildProcessConnection.java
@@ -1328,10 +1328,10 @@
         assert isRunningOnLauncherThread();
         synchronized (mBindingStateLock) {
             // This will handle all processes with only a WAIVED binding, and
-            // the last visible tab, which covers all renderers (W or WV), but
+            // the last visible tab, which covers all renderers (W or WN), but
             // excludes the GPU process (WS).
             if (mBindingState != ChildBindingState.WAIVED
-                    && mBindingState != ChildBindingState.VISIBLE) return;
+                    && mBindingState != ChildBindingState.NOT_PERCEPTIBLE) return;
         }
         if (mService == null) return;
         try {
diff --git a/base/android/pre_freeze_background_memory_trimmer.cc b/base/android/pre_freeze_background_memory_trimmer.cc
index 9cf70d0..da07a41 100644
--- a/base/android/pre_freeze_background_memory_trimmer.cc
+++ b/base/android/pre_freeze_background_memory_trimmer.cc
@@ -31,16 +31,14 @@
 #include "base/trace_event/named_trigger.h"  // no-presubmit-check
 
 namespace base::android {
-BASE_FEATURE(kShouldFreezeSelf,
-             "ShouldFreezeSelf",
-             FEATURE_DISABLED_BY_DEFAULT);
+BASE_FEATURE(kShouldFreezeSelf, "ShouldFreezeSelf", FEATURE_ENABLED_BY_DEFAULT);
 
 // Max amount of compaction to do in each chunk, measured in MiB.
 BASE_FEATURE_PARAM(size_t,
                    kShouldFreezeSelfMaxSize,
                    &kShouldFreezeSelf,
                    "max_chunk_size",
-                   10);
+                   100);
 
 // Delay between running pre-freeze tasks and doing self-freeze, measured in s.
 BASE_FEATURE_PARAM(size_t,
diff --git a/build/android/gyp/create_unwind_table.py b/build/android/gyp/create_unwind_table.py
index 83cd73d..912eb582 100755
--- a/build/android/gyp/create_unwind_table.py
+++ b/build/android/gyp/create_unwind_table.py
@@ -1068,7 +1068,7 @@
   args = parser.parse_args()
   proc = subprocess.Popen(['./' + args.dump_syms_path, args.input_path, '-v'],
                           stdout=subprocess.PIPE,
-                          encoding='ascii')
+                          encoding='utf-8')
 
   function_cfis = ReadFunctionCfi(proc.stdout)
   function_unwinds = GenerateUnwinds(function_cfis, parsers=ALL_PARSERS)
diff --git a/build/config/compiler/compiler.gni b/build/config/compiler/compiler.gni
index a47aa1c..e80fb44f 100644
--- a/build/config/compiler/compiler.gni
+++ b/build/config/compiler/compiler.gni
@@ -280,10 +280,7 @@
 # If it wasn't manually set, set to an appropriate default.
 assert(symbol_level >= -1 && symbol_level <= 2, "Invalid symbol_level")
 if (symbol_level == -1) {
-  if (is_android && !is_component_build && !use_debug_fission) {
-    # Prefer faster & smaller release builds.
-    symbol_level = 1
-  } else if (is_chromeos_device) {
+  if (is_chromeos_device) {
     # Use lower symbol level in Simple Chrome build for faster link time.
     # For Simple Chrome, this should take precedence over is_official_build,
     # turned on by --internal.
@@ -295,17 +292,26 @@
     } else {
       symbol_level = 1
     }
+  } else if (is_official_build) {
+    # We want the best debug info we can get for in-the-wild crashes.
+    symbol_level = 2
+  } else if (is_android) {
+    # Prefer faster & smaller build times by default. gdb/lldb do not work well
+    # on Android, and many devs touch only Java, so this is a good default.
+    # symbol_level = 0 is even faster, but crashes then have incomplete stacks
+    # (missing frames and source lines).
+    symbol_level = 1
   } else if (using_sanitizer) {
     # Sanitizers need line table info for stack traces. They don't need type
     # info or variable info, so we can leave that out to speed up the build.
     # Sanitizers also require symbols for filename suppressions to work.
     symbol_level = 1
   } else if ((!is_nacl && !is_linux && !is_chromeos && !is_fuchsia &&
-              current_os != "aix") || is_debug || is_official_build ||
-             is_castos || is_cast_android) {
+              current_os != "aix") || is_debug || is_castos ||
+             is_cast_android) {
     # Linux builds slower by having symbols as part of the target binary,
     # whereas Mac and Windows have them separate, so in Release Linux, default
-    # them off, but keep them on for Official builds and Chromecast builds.
+    # them off, but keep them on for Chromecast builds.
     symbol_level = 2
   } else {
     symbol_level = 0
diff --git a/chrome/VERSION b/chrome/VERSION
index 65b42d6..b47a6f8 100644
--- a/chrome/VERSION
+++ b/chrome/VERSION
@@ -1,4 +1,4 @@
 MAJOR=139
 MINOR=0
-BUILD=7221
+BUILD=7222
 PATCH=0
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 6b6ab44..485f8ba 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
@@ -16,6 +16,7 @@
 import org.chromium.chrome.browser.keyboard_accessory.data.PropertyProvider;
 import org.chromium.components.autofill.AutofillDelegate;
 import org.chromium.components.autofill.AutofillSuggestion;
+import org.chromium.components.autofill.AutofillSuggestion.Payload;
 import org.chromium.components.autofill.SuggestionType;
 import org.chromium.ui.DropdownItem;
 import org.chromium.ui.base.WindowAndroid;
@@ -171,7 +172,8 @@
             @JniType("std::string") String featureForIph,
             @JniType("std::u16string") String iphDescriptionText,
             GURL customIconUrl,
-            boolean applyDeactivatedStyle) {
+            boolean applyDeactivatedStyle,
+            @Nullable Payload payload) {
         int drawableId = iconId == 0 ? DropdownItem.NO_ICON : iconId;
         return new AutofillSuggestion.Builder()
                 .setLabel(label)
@@ -183,6 +185,7 @@
                 .setIphDescriptionText(iphDescriptionText)
                 .setCustomIconUrl(customIconUrl)
                 .setApplyDeactivatedStyle(applyDeactivatedStyle)
+                .setPayload(payload)
                 .build();
     }
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ntp/NewTabPageLayout.java b/chrome/android/java/src/org/chromium/chrome/browser/ntp/NewTabPageLayout.java
index a26f7ad..f2daeb67 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ntp/NewTabPageLayout.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ntp/NewTabPageLayout.java
@@ -146,7 +146,7 @@
     private View mFakeSearchBoxLayout;
     private TextView mFakeSearchBoxEditText;
     private Callback<Logo> mOnLogoAvailableCallback;
-    private final boolean mIsComposeplateEnabled;
+    private boolean mIsComposeplateEnabled;
     private OnClickListener mVoiceSearchButtonClickListener;
     private OnClickListener mLensButtonClickListener;
     private @Nullable ComposeplateCoordinator mComposeplateCoordinator;
@@ -165,7 +165,6 @@
                 getResources()
                         .getDimensionPixelOffset(
                                 org.chromium.chrome.R.dimen.tile_view_padding_edge_tablet);
-        mIsComposeplateEnabled = ChromeFeatureList.sAndroidComposeplate.isEnabled();
     }
 
     @Override
@@ -230,6 +229,7 @@
         mWindowAndroid = windowAndroid;
         mIsTablet = isTablet;
         mTabStripHeightSupplier = tabStripHeightSupplier;
+        mIsComposeplateEnabled = ChromeFeatureList.sAndroidComposeplate.isEnabled() && !mIsTablet;
 
         if (mIsTablet) {
             mDisplayStyleObserver = this::onDisplayStyleChanged;
@@ -901,7 +901,9 @@
 
         boolean showLensButton = !shouldShowComposeplateButton && shouldShowLensButton;
         mSearchBoxCoordinator.setLensButtonVisibility(showLensButton);
-        LensMetrics.recordShown(LensEntryPoint.NEW_TAB_PAGE, showLensButton);
+        // The lens button will be shown either in the fake search box or the composeplate view.
+        LensMetrics.recordShown(
+                LensEntryPoint.NEW_TAB_PAGE, showLensButton || shouldShowComposeplateButton);
 
         mSearchBoxCoordinator.setComposeplateButtonVisibility(shouldShowComposeplateButton);
         mComposeplateCoordinator.setVisibility(shouldShowComposeplateButton);
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/customtabs/CustomTabPrivacySandboxDialogTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/customtabs/CustomTabPrivacySandboxDialogTest.java
index 024313b9..7455633 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/customtabs/CustomTabPrivacySandboxDialogTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/customtabs/CustomTabPrivacySandboxDialogTest.java
@@ -136,7 +136,6 @@
     @Test
     @SmallTest
     @EnableFeatures({
-        ChromeFeatureList.PRIVACY_SANDBOX_ADS_NOTICE_CCT,
         ChromeFeatureList.PRIVACY_SANDBOX_SETTINGS_4
                 + ":force-show-notice-row-for-testing/true/notice-required/true"
     })
@@ -144,7 +143,14 @@
         HistogramWatcher watcher =
                 HistogramWatcher.newSingleRecordWatcher(
                         "Startup.Android.PrivacySandbox.ShouldShowAdsNoticeCCT", true);
-        mCustomTabActivityTestRule.startCustomTabActivityWithIntent(createMinimalCustomTabIntent());
+        Intent intent = createMinimalCustomTabIntent();
+        var token = SessionHolder.getSessionHolderFromIntent(intent);
+        CustomTabsConnection connection = CustomTabsConnection.getInstance();
+        connection.newSession(token.getSessionAsCustomTab());
+        connection.overridePackageNameForSessionForTesting(
+                token, "com.google.android.googlequicksearchbox");
+
+        mCustomTabActivityTestRule.startCustomTabActivityWithIntent(intent);
         onViewWaiting(withId(R.id.privacy_sandbox_dialog)).check(matches(isDisplayed()));
         watcher.assertExpected();
     }
@@ -192,7 +198,6 @@
     @Restriction(DeviceRestriction.RESTRICTION_TYPE_NON_AUTO)
     @EnableFeatures({
         ChromeFeatureList.CCT_RESIZABLE_FOR_THIRD_PARTIES,
-        ChromeFeatureList.PRIVACY_SANDBOX_ADS_NOTICE_CCT,
         ChromeFeatureList.PRIVACY_SANDBOX_SETTINGS_4
                 + ":force-show-notice-row-for-testing/true/notice-required/true"
     })
@@ -286,7 +291,6 @@
     @Test
     @SmallTest
     @EnableFeatures({
-        ChromeFeatureList.PRIVACY_SANDBOX_ADS_NOTICE_CCT,
         ChromeFeatureList.PRIVACY_SANDBOX_SETTINGS_4
                 + ":force-show-notice-row-for-testing/true/notice-required/true"
     })
@@ -301,7 +305,6 @@
     @Test
     @SmallTest
     @EnableFeatures({
-        ChromeFeatureList.PRIVACY_SANDBOX_ADS_NOTICE_CCT,
         ChromeFeatureList.PRIVACY_SANDBOX_SETTINGS_4
                 + ":force-show-notice-row-for-testing/true/notice-required/true"
     })
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/incognito/IncognitoDownloadLeakageTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/incognito/IncognitoDownloadLeakageTest.java
index a1e1183..abc1fe8b 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/incognito/IncognitoDownloadLeakageTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/incognito/IncognitoDownloadLeakageTest.java
@@ -212,7 +212,7 @@
     @LargeTest
     @UseMethodParameter(IncognitoDataTestUtils.TestParams.IncognitoToRegular.class)
     @DisabledTest(message = "crbug.com/422225234")
-    public void testIncognitoDowloadEntriesNotVisibleInRegular(
+    public void testIncognitoDownloadEntriesNotVisibleInRegular(
             String incognitoActivityType, String regularActivityType) throws Exception {
         IncognitoDataTestUtils.ActivityType incognitoActivity =
                 IncognitoDataTestUtils.ActivityType.valueOf(incognitoActivityType);
@@ -258,7 +258,7 @@
     @LargeTest
     @UseMethodParameter(IncognitoDataTestUtils.TestParams.IncognitoToIncognito.class)
     @DisabledTest(message = "crbug.com/391749002, crbug.com/40935094")
-    public void testIncognitoDowloadEntriesNotVisibleInAnotherIncognito(
+    public void testIncognitoDownloadEntriesNotVisibleInAnotherIncognito(
             String incognitoActivityType1, String incognitoActivityType2) throws Exception {
         IncognitoDataTestUtils.ActivityType incognitoActivity1 =
                 IncognitoDataTestUtils.ActivityType.valueOf(incognitoActivityType1);
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/incognito/IncognitoNotificationServiceTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/incognito/IncognitoNotificationServiceTest.java
index bd4f52ed..4774a70 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/incognito/IncognitoNotificationServiceTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/incognito/IncognitoNotificationServiceTest.java
@@ -96,7 +96,6 @@
                             Matchers.greaterThanOrEqualTo(1));
                 });
 
-        boolean isIncognitoNotificationDisplayed = false;
         CriteriaHelper.pollInstrumentationThread(
                 () -> {
                     List<? extends StatusBarNotificationProxy> activeNotifications =
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/LocationBarTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/LocationBarTest.java
index 032de346..06615606 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/LocationBarTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/LocationBarTest.java
@@ -14,6 +14,7 @@
 import static org.hamcrest.Matchers.not;
 import static org.junit.Assert.assertFalse;
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.Mockito.doReturn;
 
 import android.content.Intent;
@@ -54,6 +55,7 @@
 import org.chromium.chrome.browser.lifecycle.InflationObserver;
 import org.chromium.chrome.browser.locale.LocaleManager;
 import org.chromium.chrome.browser.locale.LocaleManagerDelegate;
+import org.chromium.chrome.browser.omnibox.status.StatusProperties.StatusIconResource;
 import org.chromium.chrome.browser.omnibox.voice.VoiceRecognitionHandler;
 import org.chromium.chrome.browser.search_engines.TemplateUrlServiceFactory;
 import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
@@ -121,6 +123,9 @@
                 () -> {
                     TemplateUrlServiceFactory.setInstanceForTesting(mTemplateUrlService);
                     LocaleManager.getInstance().setDelegateForTest(mLocaleManagerDelegate);
+                    doReturn(new StatusIconResource(null))
+                            .when(mSearchEngineUtils)
+                            .getSearchEngineLogo(anyInt());
                 });
     }
 
@@ -190,6 +195,15 @@
                     doReturn(isGoogle ? mGoogleSearchEngine : mNonGoogleSearchEngine)
                             .when(mTemplateUrlService)
                             .getDefaultSearchEngineTemplateUrl();
+
+                    StatusIconResource logo =
+                            new StatusIconResource(
+                                    isGoogle
+                                            ? R.drawable.ic_logo_googleg_20dp
+                                            : R.drawable.ic_search,
+                                    0);
+
+                    doReturn(logo).when(mSearchEngineUtils).getSearchEngineLogo(anyInt());
                 });
     }
 
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/page_info/PageInfoDiscoverabilityTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/page_info/PageInfoDiscoverabilityTest.java
index 1874142..e109bc2 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/page_info/PageInfoDiscoverabilityTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/page_info/PageInfoDiscoverabilityTest.java
@@ -23,7 +23,6 @@
 import org.mockito.junit.MockitoRule;
 
 import org.chromium.base.ThreadUtils;
-import org.chromium.base.supplier.ObservableSupplierImpl;
 import org.chromium.base.supplier.OneshotSupplierImpl;
 import org.chromium.base.test.params.ParameterAnnotations;
 import org.chromium.base.test.params.ParameterProvider;
@@ -45,7 +44,6 @@
 import org.chromium.chrome.browser.permissions.RuntimePermissionTestUtils;
 import org.chromium.chrome.browser.profiles.Profile;
 import org.chromium.chrome.browser.profiles.ProfileManager;
-import org.chromium.chrome.browser.search_engines.TemplateUrlServiceFactory;
 import org.chromium.chrome.test.ChromeJUnit4RunnerDelegate;
 import org.chromium.chrome.test.batch.BlankCTATabInitialStateRule;
 import org.chromium.components.content_settings.ContentSettingValues;
@@ -221,8 +219,6 @@
                 () -> {
                     mModel = new PropertyModel(StatusProperties.ALL_KEYS);
                     mTemplateUrlServiceSupplier = new OneshotSupplierImpl<>();
-                    mTemplateUrlServiceSupplier.set(mTemplateUrlService);
-                    TemplateUrlServiceFactory.setInstanceForTesting(mTemplateUrlService);
                     mMediator =
                             new StatusMediator(
                                     mModel,
@@ -232,10 +228,11 @@
                                     mLocationBarDataProvider,
                                     mPermissionDialogController,
                                     mTemplateUrlServiceSupplier,
-                                    new ObservableSupplierImpl(mProfile),
+                                    () -> mProfile,
                                     mPageInfoIphController,
                                     sPermissionTestRule.getActivity().getWindowAndroid(),
                                     null);
+                    mTemplateUrlServiceSupplier.set(mTemplateUrlService);
                 });
     }
 
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/privacy_sandbox/PrivacySandboxSurveyControllerIntegrationTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/privacy_sandbox/PrivacySandboxSurveyControllerIntegrationTest.java
index d6bf3230..5b3a9ca 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/privacy_sandbox/PrivacySandboxSurveyControllerIntegrationTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/privacy_sandbox/PrivacySandboxSurveyControllerIntegrationTest.java
@@ -239,7 +239,6 @@
                 + TestSurveyUtils.TEST_TRIGGER_ID_FOO,
         ChromeFeatureList.PRIVACY_SANDBOX_SETTINGS_4
                 + ":force-show-notice-row-for-testing/true/notice-required/true",
-        ChromeFeatureList.PRIVACY_SANDBOX_ADS_NOTICE_CCT,
     })
     // TODO(crbug.com/391968140): Re-enable tests when supporting tablets
     @DisableIf.Device(DeviceFormFactor.TABLET)
@@ -267,7 +266,6 @@
                 + TestSurveyUtils.TEST_TRIGGER_ID_FOO,
         ChromeFeatureList.PRIVACY_SANDBOX_SETTINGS_4
                 + ":force-show-notice-row-for-testing/true/notice-required/true",
-        ChromeFeatureList.PRIVACY_SANDBOX_ADS_NOTICE_CCT,
     })
     // TODO(crbug.com/391968140): Re-enable tests when supporting tablets
     @DisableIf.Device(DeviceFormFactor.TABLET)
@@ -299,7 +297,6 @@
                 + TestSurveyUtils.TEST_TRIGGER_ID_FOO,
         ChromeFeatureList.PRIVACY_SANDBOX_SETTINGS_4
                 + ":force-show-notice-row-for-testing/true/notice-required/true",
-        ChromeFeatureList.PRIVACY_SANDBOX_ADS_NOTICE_CCT,
     })
     // TODO(crbug.com/391968140): Re-enable tests when supporting tablets
     @DisableIf.Device(DeviceFormFactor.TABLET)
@@ -317,7 +314,6 @@
     @Features.EnableFeatures({
         ChromeFeatureList.PRIVACY_SANDBOX_SETTINGS_4
                 + ":force-show-notice-row-for-testing/true/notice-required/true",
-        ChromeFeatureList.PRIVACY_SANDBOX_ADS_NOTICE_CCT
     })
     @DisableFeatures({ChromeFeatureList.PRIVACY_SANDBOX_CCT_ADS_NOTICE_SURVEY})
     // TODO(crbug.com/391968140): Re-enable tests when supporting tablets
@@ -340,7 +336,6 @@
                 + TestSurveyUtils.TEST_TRIGGER_ID_FOO,
         ChromeFeatureList.PRIVACY_SANDBOX_SETTINGS_4
                 + ":force-show-consent-for-testing/true/consent-required/true",
-        ChromeFeatureList.PRIVACY_SANDBOX_ADS_NOTICE_CCT,
     })
     // TODO(crbug.com/391968140): Re-enable tests when supporting tablets
     @DisableIf.Device(DeviceFormFactor.TABLET)
@@ -369,7 +364,6 @@
                 + TestSurveyUtils.TEST_TRIGGER_ID_FOO,
         ChromeFeatureList.PRIVACY_SANDBOX_SETTINGS_4
                 + ":force-show-consent-for-testing/true/consent-required/true",
-        ChromeFeatureList.PRIVACY_SANDBOX_ADS_NOTICE_CCT,
     })
     // TODO(crbug.com/391968140): Re-enable tests when supporting tablets
     @DisableIf.Device(DeviceFormFactor.TABLET)
@@ -404,7 +398,6 @@
                 + TestSurveyUtils.TEST_TRIGGER_ID_FOO,
         ChromeFeatureList.PRIVACY_SANDBOX_SETTINGS_4
                 + ":force-show-consent-for-testing/true/consent-required/true",
-        ChromeFeatureList.PRIVACY_SANDBOX_ADS_NOTICE_CCT,
     })
     // TODO(crbug.com/391968140): Re-enable tests when supporting tablets
     @DisableIf.Device(DeviceFormFactor.TABLET)
@@ -428,7 +421,6 @@
                 + TestSurveyUtils.TEST_TRIGGER_ID_FOO,
         ChromeFeatureList.PRIVACY_SANDBOX_SETTINGS_4
                 + ":force-show-consent-for-testing/true/consent-required/true",
-        ChromeFeatureList.PRIVACY_SANDBOX_ADS_NOTICE_CCT,
     })
     // TODO(crbug.com/391968140): Re-enable tests when supporting tablets
     @DisableIf.Device(DeviceFormFactor.TABLET)
@@ -452,7 +444,6 @@
                 + TestSurveyUtils.TEST_TRIGGER_ID_FOO,
         ChromeFeatureList.PRIVACY_SANDBOX_SETTINGS_4
                 + ":force-show-consent-for-testing/true/consent-required/true",
-        ChromeFeatureList.PRIVACY_SANDBOX_ADS_NOTICE_CCT,
     })
     // TODO(crbug.com/391968140): Re-enable tests when supporting tablets
     @DisableIf.Device(DeviceFormFactor.TABLET)
@@ -481,7 +472,6 @@
                 + TestSurveyUtils.TEST_TRIGGER_ID_FOO,
         ChromeFeatureList.PRIVACY_SANDBOX_SETTINGS_4
                 + ":force-show-consent-for-testing/true/consent-required/true",
-        ChromeFeatureList.PRIVACY_SANDBOX_ADS_NOTICE_CCT,
     })
     // TODO(crbug.com/391968140): Re-enable tests when supporting tablets
     @DisableIf.Device(DeviceFormFactor.TABLET)
@@ -510,7 +500,6 @@
                 + TestSurveyUtils.TEST_TRIGGER_ID_FOO,
         ChromeFeatureList.PRIVACY_SANDBOX_SETTINGS_4
                 + ":force-show-consent-for-testing/true/consent-required/true",
-        ChromeFeatureList.PRIVACY_SANDBOX_ADS_NOTICE_CCT,
     })
     // TODO(crbug.com/391968140): Re-enable tests when supporting tablets
     @DisableIf.Device(DeviceFormFactor.TABLET)
@@ -540,7 +529,6 @@
                 + TestSurveyUtils.TEST_TRIGGER_ID_FOO,
         ChromeFeatureList.PRIVACY_SANDBOX_SETTINGS_4
                 + ":force-show-consent-for-testing/true/consent-required/true",
-        ChromeFeatureList.PRIVACY_SANDBOX_ADS_NOTICE_CCT,
     })
     // TODO(crbug.com/391968140): Re-enable tests when supporting tablets
     @DisableIf.Device(DeviceFormFactor.TABLET)
@@ -591,7 +579,6 @@
     @Features.EnableFeatures({
         ChromeFeatureList.PRIVACY_SANDBOX_SETTINGS_4
                 + ":force-show-consent-for-testing/true/consent-required/true",
-        ChromeFeatureList.PRIVACY_SANDBOX_ADS_NOTICE_CCT
     })
     @DisableFeatures({ChromeFeatureList.PRIVACY_SANDBOX_CCT_ADS_NOTICE_SURVEY})
     // TODO(crbug.com/391968140): Re-enable tests when supporting tablets
@@ -777,7 +764,6 @@
         ChromeFeatureList.PRIVACY_SANDBOX_SETTINGS_4
                 + ":force-show-consent-for-testing/true/consent-required/true"
                 + "/force-show-notice-row-for-testing/true/notice-required/true",
-        ChromeFeatureList.PRIVACY_SANDBOX_ADS_NOTICE_CCT
     })
     public void adsCctSurveyForControlSurveyNotShownWhenAdsNoticeCctFeatureEnabled() {
         mCustomTabActivityTestRule.startCustomTabActivityWithIntent(createMinimalCustomTabIntent());
diff --git a/chrome/android/javatests/src/org/chromium/chrome/test/smoke/ChromeTabSwitcherTest.java b/chrome/android/javatests/src/org/chromium/chrome/test/smoke/ChromeTabSwitcherTest.java
index 32aa6a75..5293cff 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/test/smoke/ChromeTabSwitcherTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/test/smoke/ChromeTabSwitcherTest.java
@@ -114,12 +114,8 @@
         Log.i(TAG, "Waiting for omnibox to show URL");
         assert url.startsWith("http://");
         String urlWithoutScheme = url.substring(7);
-        IUi2Locator omniboxLocator = Ui2Locators.withText(urlWithoutScheme);
-        UiAutomatorUtils.getInstance().getLocatorHelper().verifyOnScreen(omniboxLocator);
-
-        Log.i(TAG, "Waiting for expected page contents");
-        IUi2Locator webContentsLocator = Ui2Locators.withText("Hello Smoke Test");
-        UiAutomatorUtils.getInstance().getLocatorHelper().verifyOnScreen(webContentsLocator);
+        IUi2Locator dataUrlText = Ui2Locators.withText(urlWithoutScheme);
+        UiAutomatorUtils.getInstance().getLocatorHelper().verifyOnScreen(dataUrlText);
 
         Log.i(TAG, "Waiting 5 seconds to ensure background logic does not crash");
         Thread.sleep(5000);
@@ -129,12 +125,6 @@
         UiAutomatorUtils.getInstance().waitUntilAnyVisible(mHubToolbar);
         UiAutomatorUtils.getInstance().getLocatorHelper().verifyOnScreen(mTabList);
 
-        Log.i(TAG, "Tapping on new tab icon.");
-        UiAutomatorUtils.getInstance().click(mNewTabButton);
-
-        Log.i(TAG, "Waiting for URL bar to be on screen");
-        UiAutomatorUtils.getInstance().waitUntilAnyVisible(mNtpOmnibox);
-
         Log.i(TAG, "Test complete.");
     }
 }
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/offlinepages/OfflinePageBridgeUnitTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/offlinepages/OfflinePageBridgeUnitTest.java
index 581424a3..761acb7 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/offlinepages/OfflinePageBridgeUnitTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/offlinepages/OfflinePageBridgeUnitTest.java
@@ -159,7 +159,6 @@
 
         answerGetPagesByClientIds(itemCount);
         Callback<List<OfflinePageItem>> callback = createMultipleItemCallback(itemCount);
-        ClientId secondClientId = new ClientId(TEST_NAMESPACE, "id number two");
         List<ClientId> list = new ArrayList<>();
         mBridge.getPagesByClientIds(list, callback);
 
@@ -195,7 +194,6 @@
 
         answerDeletePagesByClientIds(itemCount);
         Callback<Integer> callback = createDeletePageCallback();
-        ClientId secondClientId = new ClientId(TEST_NAMESPACE, "id number two");
         List<ClientId> list = new ArrayList<>();
         mBridge.deletePagesByClientId(list, callback);
 
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/omaha/OmahaBaseTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/omaha/OmahaBaseTest.java
index 19b146e..696348d6 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/omaha/OmahaBaseTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/omaha/OmahaBaseTest.java
@@ -342,7 +342,6 @@
     @Test
     @Feature({"Omaha"})
     public void testPipelineFreshInstallUpdatedAvailable_crbug_1095755() {
-        final long now = mDelegate.getScheduler().getCurrentTime();
         final String updateVersion = "10.0.0.0";
 
         // Trigger Omaha.
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/usage_stats/PageViewObserverTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/usage_stats/PageViewObserverTest.java
index 53b25022..1fdae82 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/usage_stats/PageViewObserverTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/usage_stats/PageViewObserverTest.java
@@ -262,7 +262,7 @@
 
     @Test
     public void tabAdded_startReported() {
-        PageViewObserver observer = createPageViewObserver();
+        createPageViewObserver();
         doReturn(STARTING_URL).when(mTab2).getUrl();
         doReturn(mTab2).when(mTabSupplier).get();
         changeTab(mTab2);
@@ -272,7 +272,7 @@
 
     @Test
     public void tabAdded_notSelected_startNotReported() {
-        PageViewObserver observer = createPageViewObserver();
+        createPageViewObserver();
         doReturn(STARTING_URL).when(mTab).getUrl();
         doReturn(null).when(mTabSupplier).get();
         changeTab(mTab);
@@ -282,7 +282,7 @@
 
     @Test
     public void tabAdded_suspendedDomain() {
-        PageViewObserver observer = createPageViewObserver();
+        createPageViewObserver();
         doReturn(STARTING_URL).when(mTab2).getUrl();
         doReturn(mTab2).when(mTabSupplier).get();
         doReturn(true).when(mSuspensionTracker).isWebsiteSuspended(STARTING_FQDN);
@@ -445,7 +445,7 @@
     public void customTab_startReportedUponConstruction() {
         doReturn(STARTING_URL).when(mTab).getUrl();
         doReturn(false).when(mTab).isHidden();
-        PageViewObserver observer = createPageViewObserver();
+        createPageViewObserver();
         verify(mEventTracker, times(1)).addWebsiteEvent(argThat(isStartEvent(STARTING_FQDN)));
 
         doReturn(DIFFERENT_URL).when(mTab2).getUrl();
@@ -457,7 +457,7 @@
     @Test
     public void construction_nullInitialTab() {
         doReturn(null).when(mTabSupplier).get();
-        PageViewObserver observer = createPageViewObserver();
+        createPageViewObserver();
 
         doReturn(mTab).when(mTabSupplier).get();
         doReturn(STARTING_URL).when(mTab).getUrl();
diff --git a/chrome/app/password_manager_ui_strings.grdp b/chrome/app/password_manager_ui_strings.grdp
index 12101e1..e4ec9cb 100644
--- a/chrome/app/password_manager_ui_strings.grdp
+++ b/chrome/app/password_manager_ui_strings.grdp
@@ -977,6 +977,9 @@
   <message name="IDS_PASSWORD_MANAGER_UI_PASSWORD_CHANGE_FAILED_BODY" desc="This is the description of the dialog displayed after failed password change attempt.">
     Your password wasn't updated, but you should still be able to access the site with your old password
   </message>
+  <message name="IDS_PASSWORD_MANAGER_UI_PASSWORD_CHANGE_FAILED_ACCEPT_BUTTON" desc="A button label. When clicked, Chrome will navigate to the site’s change password page. From there, the user should be able to change their password themselves, as they normally would without any help from Google Password Manager. When writing this label, keep the button label “Change it for me” in mind. This feature flow attempts to “Change it for me” for the user. If that doesn’t work, the feature flow offers a path to the user to do it themselves: “[you] Change it on the site”.">
+    Change it on the site
+  </message>
   <message name="IDS_PASSWORD_MANAGER_UI_PASSWORD_CHANGE_FAILED_ACTION" desc="Text on the button displayed on a dialog after failed password change attempt.">
     Check your password
   </message>
diff --git a/chrome/app/password_manager_ui_strings_grdp/IDS_PASSWORD_MANAGER_UI_PASSWORD_CHANGE_FAILED_ACCEPT_BUTTON.png.sha1 b/chrome/app/password_manager_ui_strings_grdp/IDS_PASSWORD_MANAGER_UI_PASSWORD_CHANGE_FAILED_ACCEPT_BUTTON.png.sha1
new file mode 100644
index 0000000..f5a3137
--- /dev/null
+++ b/chrome/app/password_manager_ui_strings_grdp/IDS_PASSWORD_MANAGER_UI_PASSWORD_CHANGE_FAILED_ACCEPT_BUTTON.png.sha1
@@ -0,0 +1 @@
+cc416625733e4d067e9f83462ffce3d003f3d6c8
\ No newline at end of file
diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn
index c508a7d..11230ff 100644
--- a/chrome/browser/BUILD.gn
+++ b/chrome/browser/BUILD.gn
@@ -4402,6 +4402,7 @@
       "//chrome/browser/ui/views/zoom:impl",
       "//chrome/browser/ui/webui/access_code_cast",
       "//chrome/browser/ui/webui/access_code_cast:impl",
+      "//chrome/browser/ui/webui/actor_internals",
       "//chrome/browser/ui/webui/app_service_internals",
       "//chrome/browser/ui/webui/infobar_internals",
       "//chrome/browser/ui/webui/infobar_internals:impl",
diff --git a/chrome/browser/actor/BUILD.gn b/chrome/browser/actor/BUILD.gn
index d65c8680..72b430d 100644
--- a/chrome/browser/actor/BUILD.gn
+++ b/chrome/browser/actor/BUILD.gn
@@ -16,6 +16,7 @@
     "actor_keyed_service.h",
     "actor_keyed_service_factory.h",
     "actor_task.h",
+    "aggregated_journal.h",
   ]
 
   sources = [
@@ -25,6 +26,7 @@
     "actor_keyed_service.cc",
     "actor_keyed_service_factory.cc",
     "actor_task.cc",
+    "aggregated_journal.cc",
     "browser_action_util.cc",
     "browser_action_util.h",
     "site_policy.cc",
diff --git a/chrome/browser/actor/actor_coordinator.cc b/chrome/browser/actor/actor_coordinator.cc
index 669459a..99fef91 100644
--- a/chrome/browser/actor/actor_coordinator.cc
+++ b/chrome/browser/actor/actor_coordinator.cc
@@ -16,9 +16,11 @@
 #include "base/metrics/histogram_macros.h"
 #include "base/notimplemented.h"
 #include "base/types/id_type.h"
+#include "chrome/browser/actor/actor_keyed_service.h"
 #include "chrome/browser/actor/browser_action_util.h"
 #include "chrome/browser/actor/site_policy.h"
 #include "chrome/browser/actor/tools/tool_controller.h"
+#include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/ui/browser_navigator.h"
 #include "chrome/browser/ui/browser_navigator_params.h"
 #include "chrome/common/actor.mojom.h"
@@ -32,6 +34,7 @@
 #include "content/public/browser/render_frame_host.h"
 #include "content/public/browser/web_contents.h"
 #include "content/public/browser/web_contents_observer.h"
+#include "mojo/public/cpp/base/proto_wrapper.h"
 #include "url/origin.h"
 
 using content::RenderFrameHost;
@@ -39,6 +42,7 @@
 using optimization_guide::DocumentIdentifierUserData;
 using optimization_guide::proto::ActionInformation;
 using optimization_guide::proto::ActionTarget;
+using optimization_guide::proto::AnnotatedPageContent;
 using optimization_guide::proto::BrowserAction;
 using tabs::TabInterface;
 
@@ -75,12 +79,15 @@
 
 ActorCoordinator::Actions::~Actions() = default;
 
-ActorCoordinator::ActorCoordinator(Profile* profile) : profile_(profile) {
+ActorCoordinator::ActorCoordinator(Profile* profile)
+    : profile_(profile),
+      journal_(ActorKeyedService::Get(profile)->GetJournal().GetSafeRef()) {
   CHECK(profile_);
 }
 
 ActorCoordinator::ActorCoordinator(Profile* profile, tabs::TabInterface* tab)
     : profile_(profile),
+      journal_(ActorKeyedService::Get(profile)->GetJournal().GetSafeRef()),
       tab_scoped_actions_deprecated_(true),
       tab_(tab->GetWeakPtr()) {
   CHECK(profile_);
@@ -232,7 +239,7 @@
                    "The target frame is no longer present in the tab."));
     return;
   }
-  tool_controller_.Invoke(action, *target_frame,
+  tool_controller_.Invoke(action, *journal_, task_id, *target_frame,
                           base::BindOnce(&ActorCoordinator::FinishOneAction,
                                          GetWeakPtr(), task_id));
 }
@@ -267,6 +274,16 @@
   action_index_ = 0;
 }
 
+void ActorCoordinator::DidObserveContext(
+    const mojo_base::ProtoWrapper& apc_proto) {
+  last_observed_page_content_ = std::make_unique<AnnotatedPageContent>(
+      apc_proto.As<AnnotatedPageContent>().value());
+}
+
+const AnnotatedPageContent* ActorCoordinator::GetLastObservedPageContent() {
+  return last_observed_page_content_.get();
+}
+
 base::WeakPtr<ActorCoordinator> ActorCoordinator::GetWeakPtr() {
   return weak_ptr_factory_.GetWeakPtr();
 }
diff --git a/chrome/browser/actor/actor_coordinator.h b/chrome/browser/actor/actor_coordinator.h
index 1873819..3b95afd 100644
--- a/chrome/browser/actor/actor_coordinator.h
+++ b/chrome/browser/actor/actor_coordinator.h
@@ -10,9 +10,11 @@
 
 #include "base/functional/callback.h"
 #include "base/memory/raw_ptr.h"
+#include "base/memory/safe_ref.h"
 #include "base/memory/weak_ptr.h"
 #include "base/sequence_checker.h"
 #include "base/types/id_type.h"
+#include "chrome/browser/actor/aggregated_journal.h"
 #include "chrome/browser/actor/task_id.h"
 #include "chrome/browser/actor/tools/tool_controller.h"
 #include "chrome/common/actor.mojom-forward.h"
@@ -22,6 +24,10 @@
 
 class Profile;
 
+namespace mojo_base {
+class ProtoWrapper;
+}
+
 namespace content {
 class WebContents;
 }  // namespace content
@@ -81,6 +87,16 @@
   void Act(const optimization_guide::proto::BrowserAction& action,
            ActionResultCallback callback);
 
+  // Gets called when a new observation is made for the actor task.
+  void DidObserveContext(const mojo_base::ProtoWrapper&);
+
+  // Returns last observed page content, nullptr if no observation has been
+  // made.
+  const optimization_guide::proto::AnnotatedPageContent*
+  GetLastObservedPageContent();
+
+  base::WeakPtr<ActorCoordinator> GetWeakPtr();
+
  private:
   class NewTabWebContentsObserver;
 
@@ -96,11 +112,14 @@
   // Fires the callback and clears `actions`.
   void CompleteActions(mojom::ActionResultPtr result);
 
-  base::WeakPtr<ActorCoordinator> GetWeakPtr();
-
   static std::optional<base::TimeDelta> action_observation_delay_for_testing_;
 
   raw_ptr<Profile> profile_;
+  base::SafeRef<AggregatedJournal> journal_;
+
+  // Stores the last observed page content for TOCTOU check.
+  std::unique_ptr<optimization_guide::proto::AnnotatedPageContent>
+      last_observed_page_content_;
 
   struct Actions {
     Actions(const optimization_guide::proto::BrowserAction& actions,
diff --git a/chrome/browser/actor/actor_coordinator_unittest.cc b/chrome/browser/actor/actor_coordinator_unittest.cc
index 49d7648..45ee01f 100644
--- a/chrome/browser/actor/actor_coordinator_unittest.cc
+++ b/chrome/browser/actor/actor_coordinator_unittest.cc
@@ -19,7 +19,7 @@
 #include "chrome/test/base/chrome_render_view_host_test_harness.h"
 #include "components/tabs/public/mock_tab_interface.h"
 #include "content/public/test/navigation_simulator.h"
-#include "mojo/public/cpp/bindings/associated_receiver.h"
+#include "mojo/public/cpp/bindings/associated_receiver_set.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/blink/public/common/associated_interfaces/associated_interface_provider.h"
 #include "third_party/blink/public/mojom/window_features/window_features.mojom.h"
@@ -74,15 +74,18 @@
                   InvokeToolCallback callback) override {
     std::move(callback).Run(MakeOkResult());
   }
+  void StartActorJournal(
+      mojo::PendingAssociatedRemote<actor::mojom::JournalClient> client)
+      override {}
 
  private:
   void Bind(mojo::ScopedInterfaceEndpointHandle handle) {
-    receiver_.Bind(
-        mojo::PendingAssociatedReceiver<chrome::mojom::ChromeRenderFrame>(
-            std::move(handle)));
+    receivers_.Add(
+        this, mojo::PendingAssociatedReceiver<chrome::mojom::ChromeRenderFrame>(
+                  std::move(handle)));
   }
 
-  mojo::AssociatedReceiver<chrome::mojom::ChromeRenderFrame> receiver_{this};
+  mojo::AssociatedReceiverSet<chrome::mojom::ChromeRenderFrame> receivers_;
 };
 
 class ActorCoordinatorTest : public ChromeRenderViewHostTestHarness {
diff --git a/chrome/browser/actor/actor_keyed_service.h b/chrome/browser/actor/actor_keyed_service.h
index 3f91da0..6ab615b 100644
--- a/chrome/browser/actor/actor_keyed_service.h
+++ b/chrome/browser/actor/actor_keyed_service.h
@@ -8,8 +8,10 @@
 #include <memory>
 #include <vector>
 
+#include "base/compiler_specific.h"
 #include "base/memory/raw_ptr.h"
 #include "chrome/browser/actor/actor_task.h"
+#include "chrome/browser/actor/aggregated_journal.h"
 #include "components/keyed_service/core/keyed_service.h"
 
 class Profile;
@@ -40,10 +42,15 @@
   // not to modify them.
   const std::vector<std::unique_ptr<ActorTask>>& GetTasks();
 
+  // The associated journal for the associated profile.
+  AggregatedJournal& GetJournal() LIFETIME_BOUND { return journal_; }
+
  private:
   // In the future we may want to divide this between active and inactive tasks.
   std::vector<std::unique_ptr<ActorTask>> tasks_;
 
+  AggregatedJournal journal_;
+
   // Owns this.
   raw_ptr<Profile> profile_;
 };
diff --git a/chrome/browser/actor/aggregated_journal.cc b/chrome/browser/actor/aggregated_journal.cc
new file mode 100644
index 0000000..d18f98a
--- /dev/null
+++ b/chrome/browser/actor/aggregated_journal.cc
@@ -0,0 +1,171 @@
+// 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/actor/aggregated_journal.h"
+
+#include "base/memory/safe_ref.h"
+#include "base/rand_util.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/common/chrome_render_frame.mojom.h"
+#include "content/public/browser/render_frame_host_receiver_set.h"
+#include "content/public/browser/web_contents_observer.h"
+#include "content/public/browser/web_contents_user_data.h"
+#include "mojo/public/cpp/bindings/associated_remote.h"
+#include "mojo/public/cpp/bindings/pending_associated_remote.h"
+#include "third_party/blink/public/common/associated_interfaces/associated_interface_provider.h"
+
+namespace actor {
+
+namespace {
+
+class JournalObserver : public mojom::JournalClient,
+                        public content::WebContentsUserData<JournalObserver> {
+ public:
+  JournalObserver(const JournalObserver&) = delete;
+  JournalObserver& operator=(const JournalObserver&) = delete;
+
+  ~JournalObserver() override {}
+
+  void EnsureJournalBound(content::RenderFrameHost& render_frame_host) {
+    if (journal_host_receivers_.IsBound(&render_frame_host)) {
+      return;
+    }
+    mojo::PendingAssociatedRemote<actor::mojom::JournalClient> client;
+    journal_host_receivers_.Bind(&render_frame_host,
+                                 client.InitWithNewEndpointAndPassReceiver());
+    mojo::AssociatedRemote<chrome::mojom::ChromeRenderFrame> renderer;
+    render_frame_host.GetRemoteAssociatedInterfaces()->GetInterface(&renderer);
+    renderer->StartActorJournal(std::move(client));
+  }
+
+ private:
+  friend class content::WebContentsUserData<JournalObserver>;
+
+  explicit JournalObserver(content::WebContents* web_contents,
+                           base::SafeRef<AggregatedJournal> journal)
+      : content::WebContentsUserData<JournalObserver>(*web_contents),
+        journal_host_receivers_(web_contents, this),
+        journal_(journal) {}
+
+  // actor::mojom::JournalClient methods.
+  void AddEntriesToJournal(
+      std::vector<mojom::JournalEntryPtr> entries) override {
+    journal_->AppendJournalEntries(
+        journal_host_receivers_.GetCurrentTargetFrame(), std::move(entries));
+  }
+
+  content::RenderFrameHostReceiverSet<mojom::JournalClient>
+      journal_host_receivers_;
+
+  base::SafeRef<AggregatedJournal> journal_;
+
+  WEB_CONTENTS_USER_DATA_KEY_DECL();
+};
+
+WEB_CONTENTS_USER_DATA_KEY_IMPL(JournalObserver);
+
+}  // namespace
+
+AggregatedJournal::Entry::Entry(const std::string& location,
+                                mojom::JournalEntryPtr data_arg)
+    : url(location), data(std::move(data_arg)) {}
+AggregatedJournal::Entry::~Entry() = default;
+
+AggregatedJournal::AggregatedJournal() : next_trace_id_(base::RandUint64()) {}
+AggregatedJournal::~AggregatedJournal() = default;
+
+AggregatedJournal::PendingAsyncEntry::PendingAsyncEntry(
+    base::PassKey<AggregatedJournal> pass_key,
+    base::SafeRef<AggregatedJournal> journal,
+    TaskId task_id,
+    uint64_t trace_id,
+    std::string_view event_name)
+    : pass_key_(pass_key),
+      journal_(journal),
+      task_id_(task_id),
+      trace_id_(trace_id),
+      event_name_(event_name) {}
+
+AggregatedJournal::PendingAsyncEntry::~PendingAsyncEntry() {
+  if (!terminated_) {
+    EndEntry("");
+  }
+}
+
+void AggregatedJournal::PendingAsyncEntry::EndEntry(std::string_view details) {
+  CHECK(!terminated_);
+  terminated_ = true;
+  journal_->AddEndEvent(pass_key_, task_id_, trace_id_, event_name_, details);
+}
+
+base::SafeRef<AggregatedJournal> AggregatedJournal::GetSafeRef() {
+  return weak_ptr_factory_.GetSafeRef();
+}
+
+std::unique_ptr<AggregatedJournal::PendingAsyncEntry>
+AggregatedJournal::CreatePendingAsyncEntry(const std::string& url,
+                                           TaskId task_id,
+                                           std::string_view event_name,
+                                           std::string_view details) {
+  uint64_t trace_id = next_trace_id_++;
+  AddEntry(std::make_unique<Entry>(
+      url, mojom::JournalEntry::New(mojom::JournalEntryType::kBegin,
+                                    task_id.GetUnsafeValue(), trace_id,
+                                    base::Time::Now(), std::string(event_name),
+                                    std::string(details))));
+  return base::WrapUnique(new PendingAsyncEntry(
+      base::PassKey<AggregatedJournal>(), weak_ptr_factory_.GetSafeRef(),
+      task_id, trace_id, event_name));
+}
+
+void AggregatedJournal::EnsureJournalBound(content::RenderFrameHost& rfh) {
+  auto* web_contents = content::WebContents::FromRenderFrameHost(&rfh);
+  CHECK(web_contents);
+  auto* journal_observer = JournalObserver::FromWebContents(web_contents);
+  if (!journal_observer) {
+    JournalObserver::CreateForWebContents(web_contents,
+                                          weak_ptr_factory_.GetSafeRef());
+    journal_observer = JournalObserver::FromWebContents(web_contents);
+  }
+
+  journal_observer->EnsureJournalBound(rfh);
+}
+
+void AggregatedJournal::AddObserver(Observer* observer) {
+  observers_.AddObserver(observer);
+}
+
+void AggregatedJournal::RemoveObserver(Observer* observer) {
+  observers_.RemoveObserver(observer);
+}
+
+void AggregatedJournal::AppendJournalEntries(
+    content::RenderFrameHost* rfh,
+    std::vector<mojom::JournalEntryPtr> entries) {
+  std::string location = rfh->GetLastCommittedURL().possibly_invalid_spec();
+  for (auto& renderer_entry : entries) {
+    AddEntry(std::make_unique<Entry>(location, std::move(renderer_entry)));
+  }
+}
+
+void AggregatedJournal::AddEndEvent(base::PassKey<AggregatedJournal> pass_key,
+                                    TaskId task_id,
+                                    uint64_t trace_id,
+                                    const std::string& event_name,
+                                    std::string_view details) {
+  AddEntry(std::make_unique<Entry>(
+      std::string(),
+      mojom::JournalEntry::New(
+          mojom::JournalEntryType::kEnd, task_id.GetUnsafeValue(), trace_id,
+          base::Time::Now(), event_name, std::string(details))));
+}
+
+void AggregatedJournal::AddEntry(std::unique_ptr<Entry> new_entry) {
+  for (auto& observer : observers_) {
+    observer.WillAddJournalEntry(*new_entry);
+  }
+  entries_.SaveToBuffer(std::move(new_entry));
+}
+
+}  // namespace actor
diff --git a/chrome/browser/actor/aggregated_journal.h b/chrome/browser/actor/aggregated_journal.h
new file mode 100644
index 0000000..0419635
--- /dev/null
+++ b/chrome/browser/actor/aggregated_journal.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 CHROME_BROWSER_ACTOR_AGGREGATED_JOURNAL_H_
+#define CHROME_BROWSER_ACTOR_AGGREGATED_JOURNAL_H_
+
+#include <string>
+#include <string_view>
+#include <vector>
+
+#include "base/containers/ring_buffer.h"
+#include "base/memory/safe_ref.h"
+#include "base/memory/weak_ptr.h"
+#include "base/observer_list.h"
+#include "base/observer_list_types.h"
+#include "base/supports_user_data.h"
+#include "base/types/pass_key.h"
+#include "chrome/browser/actor/task_id.h"
+#include "chrome/common/actor.mojom.h"
+#include "content/public/browser/render_frame_host.h"
+
+namespace actor {
+
+// A class that amalgamates all the journal entries from various RenderFrames.
+class AggregatedJournal {
+ public:
+  AggregatedJournal();
+  ~AggregatedJournal();
+
+  // Journal entry
+  struct Entry {
+    std::string url;
+    mojom::JournalEntryPtr data;
+
+    Entry(const std::string& location, mojom::JournalEntryPtr data);
+    ~Entry();
+  };
+
+  // A pending async journal entry.
+  class PendingAsyncEntry {
+   public:
+    // Creation of the event is only from the AggregatedJournal itself. Use
+    // `AggregatedJournal::CreatePendingAsyncEntry` to create this object.
+    PendingAsyncEntry(base::PassKey<AggregatedJournal>,
+                      base::SafeRef<AggregatedJournal> journal,
+                      TaskId task_id,
+                      uint64_t trace_id,
+                      std::string_view event_name);
+    ~PendingAsyncEntry();
+
+    // End an pending entry with additional details. This can only be called
+    // once and will be automatically called from the destructor if it hasn't
+    // been called.
+    void EndEntry(std::string_view details);
+
+   private:
+    base::PassKey<AggregatedJournal> pass_key_;
+    bool terminated_ = false;
+    base::SafeRef<AggregatedJournal> journal_;
+    TaskId task_id_;
+    uint64_t trace_id_;
+    std::string event_name_;
+  };
+
+  // Observing class for new entries.
+  class Observer : public base::CheckedObserver {
+   public:
+    virtual void WillAddJournalEntry(const Entry& entry) = 0;
+  };
+
+  using EntryBuffer = base::RingBuffer<std::unique_ptr<Entry>, 20>;
+
+  void AddObserver(Observer* observer);
+  void RemoveObserver(Observer* observer);
+
+  // Create an async entry. This will log a Begin Entry event and when the
+  // PendingAsyncEntry object is destroyed the End Entry will be logged.
+  std::unique_ptr<PendingAsyncEntry> CreatePendingAsyncEntry(
+      const std::string& url,
+      TaskId task_id,
+      std::string_view event_name,
+      std::string_view details);
+
+  void EnsureJournalBound(content::RenderFrameHost& rfh);
+  void AppendJournalEntries(content::RenderFrameHost* rfh,
+                            std::vector<mojom::JournalEntryPtr> entries);
+  EntryBuffer::Iterator Items() { return entries_.Begin(); }
+  base::SafeRef<AggregatedJournal> GetSafeRef();
+  void AddEndEvent(base::PassKey<AggregatedJournal>,
+                   TaskId task_id,
+                   uint64_t trace_id,
+                   const std::string& event_name,
+                   std::string_view details);
+
+ private:
+  void AddEntry(std::unique_ptr<Entry>);
+
+  uint64_t next_trace_id_;
+  base::ObserverList<Observer> observers_;
+  EntryBuffer entries_;
+  base::WeakPtrFactory<AggregatedJournal> weak_ptr_factory_{this};
+};
+
+}  // namespace actor
+
+#endif  // CHROME_BROWSER_ACTOR_AGGREGATED_JOURNAL_H_
diff --git a/chrome/browser/actor/tools/history_tool.cc b/chrome/browser/actor/tools/history_tool.cc
index 3e78f7e..91cd28b 100644
--- a/chrome/browser/actor/tools/history_tool.cc
+++ b/chrome/browser/actor/tools/history_tool.cc
@@ -89,8 +89,11 @@
 }
 
 std::string HistoryTool::DebugString() const {
-  return absl::StrFormat("HistoryTool[%s]",
-                         direction_ == kBack ? "Back" : "Forward");
+  return absl::StrFormat("HistoryTool[%s]", JournalEvent());
+}
+
+std::string HistoryTool::JournalEvent() const {
+  return direction_ == kBack ? "Back" : "Forward";
 }
 
 ObservationDelayType HistoryTool::GetObservationDelayType() const {
diff --git a/chrome/browser/actor/tools/history_tool.h b/chrome/browser/actor/tools/history_tool.h
index 63835dd..54468ef4 100644
--- a/chrome/browser/actor/tools/history_tool.h
+++ b/chrome/browser/actor/tools/history_tool.h
@@ -37,6 +37,7 @@
   void Validate(ValidateCallback callback) override;
   void Invoke(InvokeCallback callback) override;
   std::string DebugString() const override;
+  std::string JournalEvent() const override;
   ObservationDelayType GetObservationDelayType() const override;
 
   // content::WebContentsObserver
diff --git a/chrome/browser/actor/tools/navigate_tool.cc b/chrome/browser/actor/tools/navigate_tool.cc
index 0799b7b..8f106f8 100644
--- a/chrome/browser/actor/tools/navigate_tool.cc
+++ b/chrome/browser/actor/tools/navigate_tool.cc
@@ -44,7 +44,6 @@
       false /* is_renderer_initiated */);
 
   CHECK(web_contents());
-
   invoke_callback_ = std::move(callback);
 
   // TODO(crbug.com/406545255): If the page has a BeforeUnload handler the user
@@ -59,6 +58,10 @@
   return absl::StrFormat("NavigateTool[%s]", url_.spec());
 }
 
+std::string NavigateTool::JournalEvent() const {
+  return "Navigate";
+}
+
 ObservationDelayType NavigateTool::GetObservationDelayType() const {
   return ObservationDelayType::kWatchForLoad;
 }
diff --git a/chrome/browser/actor/tools/navigate_tool.h b/chrome/browser/actor/tools/navigate_tool.h
index 1e40768f..e392bfa 100644
--- a/chrome/browser/actor/tools/navigate_tool.h
+++ b/chrome/browser/actor/tools/navigate_tool.h
@@ -30,6 +30,7 @@
   void Validate(ValidateCallback callback) override;
   void Invoke(InvokeCallback callback) override;
   std::string DebugString() const override;
+  std::string JournalEvent() const override;
   ObservationDelayType GetObservationDelayType() const override;
 
   // content::WebContentsObserver
diff --git a/chrome/browser/actor/tools/page_tool.cc b/chrome/browser/actor/tools/page_tool.cc
index f7bae2f..6520934a 100644
--- a/chrome/browser/actor/tools/page_tool.cc
+++ b/chrome/browser/actor/tools/page_tool.cc
@@ -5,6 +5,7 @@
 #include "chrome/browser/actor/tools/page_tool.h"
 
 #include "chrome/browser/actor/actor_coordinator.h"
+#include "chrome/browser/actor/aggregated_journal.h"
 #include "chrome/common/actor/action_result.h"
 #include "chrome/common/chrome_render_frame.mojom.h"
 #include "components/optimization_guide/proto/features/actions_data.pb.h"
@@ -178,10 +179,12 @@
 
 namespace actor {
 
-PageTool::PageTool(RenderFrameHost& frame,
+PageTool::PageTool(AggregatedJournal& journal,
+                   RenderFrameHost& frame,
                    const ActionInformation& action_information)
     : action_information_(action_information) {
   frame.GetRemoteAssociatedInterfaces()->GetInterface(&chrome_render_frame_);
+  journal.EnsureJournalBound(frame);
 }
 
 PageTool::~PageTool() = default;
@@ -257,32 +260,29 @@
 }
 
 std::string PageTool::DebugString() const {
-  std::string tool_type;
   // TODO(crbug.com/402210051): Add more details here about tool params.
+  return absl::StrFormat("PageTool:%s", JournalEvent().c_str());
+}
+
+std::string PageTool::JournalEvent() const {
   switch (action_information_.action_info_case()) {
     case ActionInformation::ActionInfoCase::kClick: {
-      tool_type = "Click";
-      break;
+      return "Click";
     }
     case ActionInformation::ActionInfoCase::kType: {
-      tool_type = "Type";
-      break;
+      return "Type";
     }
     case ActionInformation::ActionInfoCase::kScroll: {
-      tool_type = "Scroll";
-      break;
+      return "Scroll";
     }
     case ActionInformation::ActionInfoCase::kMoveMouse: {
-      tool_type = "MoveMouse";
-      break;
+      return "MoveMouse";
     }
     case ActionInformation::ActionInfoCase::kDragAndRelease: {
-      tool_type = "DragAndRelease";
-      break;
+      return "DragAndRelease";
     }
     case ActionInformation::ActionInfoCase::kSelect: {
-      tool_type = "Select";
-      break;
+      return "Select";
     }
     case ActionInformation::ActionInfoCase::kNavigate:
     case ActionInformation::ActionInfoCase::kBack:
@@ -291,8 +291,6 @@
     case ActionInformation::ActionInfoCase::ACTION_INFO_NOT_SET:
       NOTREACHED();
   }
-
-  return absl::StrFormat("PageTool:%s", tool_type.c_str());
 }
 
 }  // namespace actor
diff --git a/chrome/browser/actor/tools/page_tool.h b/chrome/browser/actor/tools/page_tool.h
index 852998a..cbf86a6 100644
--- a/chrome/browser/actor/tools/page_tool.h
+++ b/chrome/browser/actor/tools/page_tool.h
@@ -16,12 +16,15 @@
 
 namespace actor {
 
+class AggregatedJournal;
+
 // A page tool is any tool implemented in the renderer by ToolExecutor. This
 // class is shared by multiple tools and serves to implement the mojo shuttling
 // of the request to the renderer.
 class PageTool : public Tool {
  public:
   PageTool(
+      AggregatedJournal& journal,
       content::RenderFrameHost& frame,
       const optimization_guide::proto::ActionInformation& action_information);
   ~PageTool() override;
@@ -30,6 +33,7 @@
   void Validate(ValidateCallback callback) override;
   void Invoke(InvokeCallback callback) override;
   std::string DebugString() const override;
+  std::string JournalEvent() const override;
 
  private:
   optimization_guide::proto::ActionInformation action_information_;
diff --git a/chrome/browser/actor/tools/tool.h b/chrome/browser/actor/tools/tool.h
index 595bd27..b319707 100644
--- a/chrome/browser/actor/tools/tool.h
+++ b/chrome/browser/actor/tools/tool.h
@@ -39,6 +39,9 @@
   // debugging purposes.
   virtual std::string DebugString() const = 0;
 
+  // Provides a journal event name.
+  virtual std::string JournalEvent() const = 0;
+
   // Returns the method to use to determine when the page is ready for a new
   // observation after this tool is executed.
   virtual ObservationDelayType GetObservationDelayType() const;
diff --git a/chrome/browser/actor/tools/tool_controller.cc b/chrome/browser/actor/tools/tool_controller.cc
index f0cbe44..f0371f1 100644
--- a/chrome/browser/actor/tools/tool_controller.cc
+++ b/chrome/browser/actor/tools/tool_controller.cc
@@ -11,6 +11,7 @@
 #include "base/memory/safe_ref.h"
 #include "base/notimplemented.h"
 #include "chrome/browser/actor/actor_coordinator.h"
+#include "chrome/browser/actor/aggregated_journal.h"
 #include "chrome/browser/actor/tools/history_tool.h"
 #include "chrome/browser/actor/tools/navigate_tool.h"
 #include "chrome/browser/actor/tools/page_tool.h"
@@ -36,10 +37,12 @@
 ToolController::ActiveState::ActiveState(
     std::unique_ptr<Tool> tool,
     ResultCallback completion_callback,
-    content::WeakDocumentPtr weak_document_ptr)
+    content::WeakDocumentPtr weak_document_ptr,
+    std::unique_ptr<AggregatedJournal::PendingAsyncEntry> journal_entry)
     : tool(std::move(tool)),
       completion_callback(std::move(completion_callback)),
-      weak_document_ptr(weak_document_ptr) {
+      weak_document_ptr(weak_document_ptr),
+      journal_entry(std::move(journal_entry)) {
   CHECK(this->tool);
   CHECK(!this->completion_callback.is_null());
   CHECK(this->weak_document_ptr.AsRenderFrameHostIfValid());
@@ -53,6 +56,8 @@
 ToolController::~ToolController() = default;
 
 std::unique_ptr<Tool> ToolController::CreateTool(
+    AggregatedJournal& journal,
+    TaskId task_id,
     RenderFrameHost& frame,
     const ActionInformation& action_information) {
   WebContents* web_contents = WebContents::FromRenderFrameHost(&frame);
@@ -67,7 +72,7 @@
     case ActionInformation::kSelect: {
       // PageTools are all implemented in the renderer so share the PageTool
       // implementation to shuttle them there.
-      return std::make_unique<PageTool>(frame, action_information);
+      return std::make_unique<PageTool>(journal, frame, action_information);
     }
     case ActionInformation::kNavigate: {
       GURL url(action_information.navigate().url());
@@ -89,10 +94,12 @@
 }
 
 void ToolController::Invoke(const ActionInformation& action_information,
+                            AggregatedJournal& journal,
+                            TaskId task_id,
                             RenderFrameHost& target_frame,
                             ResultCallback result_callback) {
   std::unique_ptr<Tool> created_tool =
-      CreateTool(target_frame, action_information);
+      CreateTool(journal, task_id, target_frame, action_information);
 
   if (!created_tool) {
     // Tool not found.
@@ -101,9 +108,14 @@
     return;
   }
 
+  auto journal_event = journal.CreatePendingAsyncEntry(
+      target_frame.GetLastCommittedURL().possibly_invalid_spec(), task_id,
+      created_tool->JournalEvent(), created_tool->DebugString());
+
   ACTOR_LOG() << "Starting Tool Use: " << created_tool->DebugString();
   active_state_.emplace(std::move(created_tool), std::move(result_callback),
-                        target_frame.GetWeakDocumentPtr());
+                        target_frame.GetWeakDocumentPtr(),
+                        std::move(journal_event));
 
   active_state_->tool->Validate(base::BindOnce(
       &ToolController::ValidationComplete, weak_ptr_factory_.GetWeakPtr()));
@@ -143,6 +155,7 @@
 
 void ToolController::CompleteToolRequest(mojom::ActionResultPtr result) {
   CHECK(active_state_);
+  active_state_->journal_entry->EndEntry(ToDebugString(*result));
   ACTOR_LOG() << "Completed Tool Invoke[" << ToDebugString(*result)
               << "]: " << active_state_->tool->DebugString();
   PostResponseTask(std::move(active_state_->completion_callback),
diff --git a/chrome/browser/actor/tools/tool_controller.h b/chrome/browser/actor/tools/tool_controller.h
index 110ccd9..beac2d6 100644
--- a/chrome/browser/actor/tools/tool_controller.h
+++ b/chrome/browser/actor/tools/tool_controller.h
@@ -9,6 +9,8 @@
 
 #include "base/functional/callback.h"
 #include "base/memory/weak_ptr.h"
+#include "chrome/browser/actor/aggregated_journal.h"
+#include "chrome/browser/actor/task_id.h"
 #include "chrome/browser/actor/tools/observation_delay_controller.h"
 #include "chrome/common/actor.mojom-forward.h"
 #include "content/public/browser/weak_document_ptr.h"
@@ -23,6 +25,7 @@
 
 namespace actor {
 
+class AggregatedJournal;
 class Tool;
 
 // Entry point into actor tool usage. ToolController is a profile-scoped,
@@ -39,6 +42,8 @@
 
   // Invokes a tool action.
   void Invoke(const optimization_guide::proto::ActionInformation& action,
+              AggregatedJournal& journal,
+              TaskId task_id,
               content::RenderFrameHost& target_frame,
               ResultCallback result_callback);
 
@@ -51,6 +56,8 @@
   void CompleteToolRequest(mojom::ActionResultPtr result);
 
   std::unique_ptr<Tool> CreateTool(
+      AggregatedJournal& journal,
+      TaskId task_id,
       content::RenderFrameHost& frame,
       const optimization_guide::proto::ActionInformation& action_information);
 
@@ -58,9 +65,11 @@
 
   // This state is non-null whenever a tool invocation is in progress.
   struct ActiveState {
-    ActiveState(std::unique_ptr<Tool> tool,
-                ResultCallback completion_callback,
-                content::WeakDocumentPtr weak_document_ptr);
+    ActiveState(
+        std::unique_ptr<Tool> tool,
+        ResultCallback completion_callback,
+        content::WeakDocumentPtr weak_document_ptr,
+        std::unique_ptr<AggregatedJournal::PendingAsyncEntry> journal_entry);
     ~ActiveState();
     ActiveState(const ActiveState&) = delete;
     ActiveState& operator=(const ActiveState&) = delete;
@@ -70,6 +79,7 @@
     std::unique_ptr<Tool> tool;
     ResultCallback completion_callback;
     content::WeakDocumentPtr weak_document_ptr;
+    std::unique_ptr<AggregatedJournal::PendingAsyncEntry> journal_entry;
   };
   std::optional<ActiveState> active_state_;
 
diff --git a/chrome/browser/actor/tools/wait_tool.cc b/chrome/browser/actor/tools/wait_tool.cc
index 0a85b96..3e713b28 100644
--- a/chrome/browser/actor/tools/wait_tool.cc
+++ b/chrome/browser/actor/tools/wait_tool.cc
@@ -42,6 +42,10 @@
   return "WaitTool";
 }
 
+std::string WaitTool::JournalEvent() const {
+  return "Wait";
+}
+
 ObservationDelayType WaitTool::GetObservationDelayType() const {
   return ObservationDelayType::kNone;
 }
diff --git a/chrome/browser/actor/tools/wait_tool.h b/chrome/browser/actor/tools/wait_tool.h
index c991e48..cc42489 100644
--- a/chrome/browser/actor/tools/wait_tool.h
+++ b/chrome/browser/actor/tools/wait_tool.h
@@ -21,6 +21,7 @@
   void Validate(ValidateCallback callback) override;
   void Invoke(InvokeCallback callback) override;
   std::string DebugString() const override;
+  std::string JournalEvent() const override;
   ObservationDelayType GetObservationDelayType() const override;
 
   static void SetNoDelayForTesting();
diff --git a/chrome/browser/ash/extensions/DEPS b/chrome/browser/ash/extensions/DEPS
index 4151489..cb3edd8 100644
--- a/chrome/browser/ash/extensions/DEPS
+++ b/chrome/browser/ash/extensions/DEPS
@@ -79,6 +79,7 @@
   "+chrome/browser/ui/webui/ash/cloud_upload",
   "+chrome/browser/ui/webui/ash/crostini_installer",
   "+chrome/browser/ui/webui/ash/manage_mirrorsync",
+  "+chrome/browser/web_applications/policy/app_service_web_app_policy.h",
   "+chrome/browser/web_applications/web_app_provider.h",
   "+chrome/browser/web_applications/web_app_registrar.h",
   "+chrome/common/chrome_constants.h",
@@ -87,7 +88,6 @@
   "+chrome/common/chrome_switches.h",
   "+chrome/common/extensions",
   "+chrome/common/pref_names.h",
-  "+chrome/browser/web_applications/web_app_utils.h",
   "+chrome/grit",
   "+chrome/services/media_gallery_util/public/cpp",
   "+chrome/services/media_gallery_util/public/mojom",
diff --git a/chrome/browser/ash/extensions/default_app_order.cc b/chrome/browser/ash/extensions/default_app_order.cc
index f80790cc..d69eb2b 100644
--- a/chrome/browser/ash/extensions/default_app_order.cc
+++ b/chrome/browser/ash/extensions/default_app_order.cc
@@ -25,7 +25,7 @@
 #include "chrome/browser/apps/app_service/policy_util.h"
 #include "chrome/browser/ash/guest_os/guest_os_terminal.h"
 #include "chrome/browser/browser_process.h"
-#include "chrome/browser/web_applications/web_app_utils.h"
+#include "chrome/browser/web_applications/policy/app_service_web_app_policy.h"
 #include "chrome/common/extensions/extension_constants.h"
 #include "chromeos/ash/components/file_manager/app_id.h"
 #include "chromeos/ash/experiences/arc/app/arc_app_constants.h"
diff --git a/chrome/browser/ash/policy/handlers/DEPS b/chrome/browser/ash/policy/handlers/DEPS
index 589a72a..ad5c2f2 100644
--- a/chrome/browser/ash/policy/handlers/DEPS
+++ b/chrome/browser/ash/policy/handlers/DEPS
@@ -36,6 +36,7 @@
   "+chrome/browser/browser_process_platform_part.h",
   "+chrome/browser/extensions/policy_handlers.h",
   "+chrome/browser/extensions/updater",
+  "+chrome/browser/web_applications/policy/web_app_policy_manager.h",
   "+chrome/browser/web_applications/web_app_utils.h",
   "+chrome/browser/lifetime",
   "+chrome/browser/media/webrtc",
@@ -49,6 +50,7 @@
   "+chrome/browser/ui/webui/ash/login",
   "+chrome/browser/upgrade_detector",
   "+chrome/browser/web_applications/isolated_web_apps",
+  "+chrome/browser/web_applications/policy/app_service_web_app_policy.h",
   "+chrome/common/chrome_features.h",
   "+chrome/common/pref_names.h",
 ]
diff --git a/chrome/browser/ash/policy/handlers/configuration_policy_handler_ash.cc b/chrome/browser/ash/policy/handlers/configuration_policy_handler_ash.cc
index a6b3aef..9016f23 100644
--- a/chrome/browser/ash/policy/handlers/configuration_policy_handler_ash.cc
+++ b/chrome/browser/ash/policy/handlers/configuration_policy_handler_ash.cc
@@ -29,6 +29,8 @@
 #include "chrome/browser/apps/app_service/policy_util.h"
 #include "chrome/browser/ash/accessibility/magnifier_type.h"
 #include "chrome/browser/ui/ash/shelf/chrome_shelf_prefs.h"
+#include "chrome/browser/web_applications/policy/app_service_web_app_policy.h"
+#include "chrome/browser/web_applications/policy/web_app_policy_manager.h"
 #include "chrome/browser/web_applications/web_app_utils.h"
 #include "chrome/common/pref_names.h"
 #include "chromeos/ash/experiences/arc/arc_prefs.h"
@@ -154,12 +156,13 @@
 }
 
 bool IsSupportedAppTypePolicyId(std::string_view policy_id) {
-  return web_app::IsChromeAppPolicyId(policy_id) ||
+  return web_app::WebAppPolicyManager::IsChromeAppPolicyId(policy_id) ||
          web_app::IsArcAppPolicyId(policy_id) ||
          web_app::IsSystemWebAppPolicyId(policy_id) ||
-         web_app::IsWebAppPolicyId(policy_id) ||
-         web_app::IsPreinstalledWebAppPolicyId(policy_id) ||
-         web_app::IsIsolatedWebAppPolicyId(policy_id);
+         web_app::WebAppPolicyManager::IsWebAppPolicyId(policy_id) ||
+         web_app::WebAppPolicyManager::IsPreinstalledWebAppPolicyId(
+             policy_id) ||
+         web_app::WebAppPolicyManager::IsIsolatedWebAppPolicyId(policy_id);
 }
 
 }  // namespace
diff --git a/chrome/browser/autofill/android/java/src/org/chromium/chrome/browser/autofill/PersonalDataManager.java b/chrome/browser/autofill/android/java/src/org/chromium/chrome/browser/autofill/PersonalDataManager.java
index dd1a783..0ae39fa 100644
--- a/chrome/browser/autofill/android/java/src/org/chromium/chrome/browser/autofill/PersonalDataManager.java
+++ b/chrome/browser/autofill/android/java/src/org/chromium/chrome/browser/autofill/PersonalDataManager.java
@@ -735,29 +735,6 @@
                 PersonalDataManagerJni.get().getProfileGUIDsToSuggest(mPersonalDataManagerAndroid));
     }
 
-    /**
-     * TODO(crbug.com/41256488): Reduce the number of Java to Native calls when getting profiles.
-     *
-     * <p>Gets the profiles to suggest when associating a billing address to a credit card. The
-     * profiles will have been processed to be more relevant to the user.
-     *
-     * @param includeOrganizationInLabel Whether the organization name should be included in the
-     *     label.
-     * @return The list of billing addresses to suggest to the user.
-     */
-    public ArrayList<AutofillProfile> getBillingAddressesToSuggest(
-            boolean includeOrganizationInLabel) {
-        ThreadUtils.assertOnUiThread();
-        return getProfilesWithLabels(
-                PersonalDataManagerJni.get()
-                        .getProfileLabelsToSuggest(
-                                mPersonalDataManagerAndroid,
-                                /* includeNameInLabel= */ true,
-                                includeOrganizationInLabel,
-                                /* includeCountryInLabel= */ false),
-                PersonalDataManagerJni.get().getProfileGUIDsToSuggest(mPersonalDataManagerAndroid));
-    }
-
     private ArrayList<AutofillProfile> getProfilesWithLabels(
             String[] profileLabels, String[] profileGUIDs) {
         ArrayList<AutofillProfile> profiles = new ArrayList<AutofillProfile>(profileGUIDs.length);
@@ -1045,24 +1022,6 @@
     }
 
     /**
-     * @return Whether the Autofill feature for FIDO authentication is enabled.
-     */
-    public boolean isAutofillCreditCardFidoAuthEnabled() {
-        return mPrefService.getBoolean(Pref.AUTOFILL_CREDIT_CARD_FIDO_AUTH_ENABLED);
-    }
-
-    /**
-     * Enables or disables the Autofill feature for FIDO authentication. We are trying to align this
-     * pref with the server's source of truth, but any mismatches between this pref and the server
-     * should imply the user's intention to opt in/out.
-     *
-     * @param enable True to enable credit card FIDO authentication, false otherwise.
-     */
-    public void setAutofillCreditCardFidoAuthEnabled(boolean enable) {
-        mPrefService.setBoolean(Pref.AUTOFILL_CREDIT_CARD_FIDO_AUTH_ENABLED, enable);
-    }
-
-    /**
      * @return Whether the Autofill feature for payment methods mandatory reauth is enabled.
      */
     public boolean isPaymentMethodsMandatoryReauthEnabled() {
diff --git a/chrome/browser/chrome_browser_interface_binders.cc b/chrome/browser/chrome_browser_interface_binders.cc
index ce9a555..20511f0 100644
--- a/chrome/browser/chrome_browser_interface_binders.cc
+++ b/chrome/browser/chrome_browser_interface_binders.cc
@@ -32,6 +32,8 @@
 #include "chrome/common/buildflags.h"
 #include "chrome/common/pref_names.h"
 #include "chrome/services/speech/buildflags/buildflags.h"
+#include "components/autofill/content/browser/content_autofill_client.h"
+#include "components/credential_management/content_credential_manager.h"
 #include "components/dom_distiller/content/browser/distillability_driver.h"
 #include "components/dom_distiller/content/browser/distiller_javascript_service_impl.h"
 #include "components/dom_distiller/content/common/mojom/distillability_service.mojom.h"
@@ -396,6 +398,32 @@
   }
 }
 
+void BindCredentialManager(
+    content::RenderFrameHost* frame_host,
+    mojo::PendingReceiver<blink::mojom::CredentialManager> receiver) {
+  content::WebContents* web_contents =
+      content::WebContents::FromRenderFrameHost(frame_host);
+  autofill::ContentAutofillClient* autofill_client =
+      autofill::ContentAutofillClient::FromWebContents(web_contents);
+  // Not every `WebContents` has a `ContentAutofillClient`.
+  if (!autofill_client) {
+    return;
+  }
+  credential_management::ContentCredentialManager* content_credential_manager =
+      autofill_client->GetContentCredentialManager();
+
+  // Try to bind to the credential manager, but if it's not available for this
+  // render frame host, the request will be just dropped. This will cause the
+  // message pipe to be closed, which will raise a connection error on the peer
+  // side.
+  if (!content_credential_manager) {
+    // TODO(crbug.com/406224744): Retry to bind the credential manager.
+    return;
+  }
+
+  content_credential_manager->BindRequest(frame_host, std::move(receiver));
+}
+
 }  // namespace
 
 void PopulateChromeFrameBinders(
@@ -446,7 +474,7 @@
   }
 
   map->Add<blink::mojom::CredentialManager>(
-      base::BindRepeating(&ChromePasswordManagerClient::BindCredentialManager));
+      base::BindRepeating(&BindCredentialManager));
 
   map->Add<chrome::mojom::OpenSearchDescriptionDocumentHandler>(
       base::BindRepeating(
diff --git a/chrome/browser/chrome_browser_interface_binders_webui.cc b/chrome/browser/chrome_browser_interface_binders_webui.cc
index 68bd3883..e9ba0a10 100644
--- a/chrome/browser/chrome_browser_interface_binders_webui.cc
+++ b/chrome/browser/chrome_browser_interface_binders_webui.cc
@@ -86,6 +86,8 @@
 #include "chrome/browser/ui/lens/lens_side_panel_untrusted_ui.h"
 #include "chrome/browser/ui/webui/access_code_cast/access_code_cast.mojom.h"
 #include "chrome/browser/ui/webui/access_code_cast/access_code_cast_ui.h"
+#include "chrome/browser/ui/webui/actor_internals/actor_internals.mojom.h"
+#include "chrome/browser/ui/webui/actor_internals/actor_internals_ui.h"
 #include "chrome/browser/ui/webui/app_service_internals/app_service_internals.mojom.h"
 #include "chrome/browser/ui/webui/app_service_internals/app_service_internals_ui.h"
 #include "chrome/browser/ui/webui/commerce/product_specifications_ui.h"
@@ -487,6 +489,9 @@
 
 #if !BUILDFLAG(IS_ANDROID)
   RegisterWebUIControllerInterfaceBinder<
+      actor_internals::mojom::PageHandlerFactory, ActorInternalsUI>(map);
+
+  RegisterWebUIControllerInterfaceBinder<
       search_engine_choice::mojom::PageHandlerFactory, SearchEngineChoiceUI>(
       map);
 
diff --git a/chrome/browser/digital_credentials/digital_identity_provider_desktop.cc b/chrome/browser/digital_credentials/digital_identity_provider_desktop.cc
index c0f15f1..c6aca0d1 100644
--- a/chrome/browser/digital_credentials/digital_identity_provider_desktop.cc
+++ b/chrome/browser/digital_credentials/digital_identity_provider_desktop.cc
@@ -272,7 +272,8 @@
       ui::DialogModel::Button::Params(),
       base::BindOnce(&DigitalIdentityProviderDesktop::OnCanceled,
                      weak_ptr_factory_.GetWeakPtr()),
-      dialog_title, dialog_body, MakeQrCodeImageView(qr_url));
+      dialog_title, dialog_body, MakeQrCodeImageView(qr_url),
+      /*show_progress_bar=*/false);
 }
 
 void DigitalIdentityProviderDesktop::ShowBluetoothManualTurnOnDialog() {
@@ -308,7 +309,8 @@
                      weak_ptr_factory_.GetWeakPtr()),
       /*dialog_title=*/u"", /*dialog_body=*/u"",
       DigitalIdentityMultiStepDialog::CreateHeaderView(
-          std::move(title_text), /*body_text=*/u"", std::move(illustration)));
+          std::move(title_text), /*body_text=*/u"", std::move(illustration)),
+      /*show_progress_bar=*/true);
 }
 
 void DigitalIdentityProviderDesktop::ShowContinueStepsOnThePhoneDialog() {
@@ -329,7 +331,8 @@
                      weak_ptr_factory_.GetWeakPtr()),
       /*dialog_title=*/u"", /*dialog_body=*/u"",
       DigitalIdentityMultiStepDialog::CreateHeaderView(
-          std::move(title_text), /*body_text=*/u"", std::move(illustration)));
+          std::move(title_text), /*body_text=*/u"", std::move(illustration)),
+      /*show_progress_bar=*/true);
 }
 
 void DigitalIdentityProviderDesktop::OnCableConnectingTimerComplete() {
diff --git a/chrome/browser/enterprise/connectors/analysis/content_analysis_dialog_controller.cc b/chrome/browser/enterprise/connectors/analysis/content_analysis_dialog_controller.cc
index 09a7f28..3d010cd 100644
--- a/chrome/browser/enterprise/connectors/analysis/content_analysis_dialog_controller.cc
+++ b/chrome/browser/enterprise/connectors/analysis/content_analysis_dialog_controller.cc
@@ -15,7 +15,6 @@
 #include "chrome/browser/enterprise/connectors/analysis/content_analysis_views.h"
 #include "chrome/browser/safe_browsing/cloud_content_scanning/deep_scanning_utils.h"
 #include "chrome/grit/generated_resources.h"
-#include "chrome/grit/theme_resources.h"
 #include "components/constrained_window/constrained_window_views.h"
 #include "components/strings/grit/components_strings.h"
 #include "components/web_modal/web_contents_modal_dialog_manager.h"
@@ -95,18 +94,28 @@
     int files_count,
     FinalContentAnalysisResult final_result,
     download::DownloadItem* download_item)
-    : content::WebContentsObserver(contents),
+    : ContentAnalysisDialogDelegate(delegate.get()),
+      content::WebContentsObserver(contents),
       delegate_base_(std::move(delegate)),
-      final_result_(final_result),
       access_point_(std::move(access_point)),
       files_count_(files_count),
       download_item_(download_item),
       is_cloud_(is_cloud) {
   DVLOG(1) << __func__;
   DCHECK(delegate_base_);
+
+  // TODO(crbug.com/422111748): Move this to the code that initializes the
+  // DialogDelegate once this class no longer inherits from it.
+  final_result_ = final_result;
   SetOwnedByWidget(OwnedByWidgetPassKey());
   set_fixed_width(views::LayoutProvider::Get()->GetDistanceMetric(
       views::DISTANCE_MODAL_DIALOG_PREFERRED_WIDTH));
+  DialogDelegate::SetAcceptCallback(
+      base::BindOnce(&ContentAnalysisDialogController::AcceptButtonCallback,
+                     weak_ptr_factory_.GetWeakPtr()));
+  DialogDelegate::SetCancelCallback(
+      base::BindOnce(&ContentAnalysisDialogController::CancelButtonCallback,
+                     weak_ptr_factory_.GetWeakPtr()));
 
   if (observer_for_testing) {
     observer_for_testing->ConstructorCalled(this, base::TimeTicks::Now());
@@ -354,6 +363,10 @@
 ContentAnalysisDialogController::~ContentAnalysisDialogController() {
   DVLOG(1) << __func__;
 
+  // TODO(crbug.com/422111748): Update this cleanup code when this class stops
+  // inheriting from ContentAnalysisDialogDelegate.
+  ContentAnalysisDialogDelegate::delegate_base_ = nullptr;
+
   if (bypass_justification_) {
     bypass_justification_->SetController(nullptr);
   }
@@ -370,25 +383,6 @@
   }
 }
 
-void ContentAnalysisDialogController::UpdateStateFromFinalResult(
-    FinalContentAnalysisResult final_result) {
-  final_result_ = final_result;
-  switch (final_result_) {
-    case FinalContentAnalysisResult::ENCRYPTED_FILES:
-    case FinalContentAnalysisResult::LARGE_FILES:
-    case FinalContentAnalysisResult::FAIL_CLOSED:
-    case FinalContentAnalysisResult::FAILURE:
-      dialog_state_ = State::FAILURE;
-      break;
-    case FinalContentAnalysisResult::SUCCESS:
-      dialog_state_ = State::SUCCESS;
-      break;
-    case FinalContentAnalysisResult::WARNING:
-      dialog_state_ = State::WARNING;
-      break;
-  }
-}
-
 void ContentAnalysisDialogController::UpdateViews() {
   DCHECK(contents_view_);
 
@@ -525,52 +519,6 @@
   widget->SetSize(new_size);
 }
 
-void ContentAnalysisDialogController::SetupButtons() {
-  if (is_warning()) {
-    // Include the Ok and Cancel buttons if there is a bypassable warning.
-    DialogDelegate::SetButtons(
-        static_cast<int>(ui::mojom::DialogButton::kCancel) |
-        static_cast<int>(ui::mojom::DialogButton::kOk));
-    DialogDelegate::SetDefaultButton(
-        static_cast<int>(ui::mojom::DialogButton::kCancel));
-
-    DialogDelegate::SetButtonLabel(ui::mojom::DialogButton::kCancel,
-                                   GetCancelButtonText());
-    DialogDelegate::SetCancelCallback(
-        base::BindOnce(&ContentAnalysisDialogController::CancelButtonCallback,
-                       weak_ptr_factory_.GetWeakPtr()));
-
-    DialogDelegate::SetButtonLabel(ui::mojom::DialogButton::kOk,
-                                   GetBypassWarningButtonText());
-    DialogDelegate::SetAcceptCallback(
-        base::BindOnce(&ContentAnalysisDialogController::AcceptButtonCallback,
-                       weak_ptr_factory_.GetWeakPtr()));
-
-    if (delegate_base_->BypassRequiresJustification()) {
-      DialogDelegate::SetButtonEnabled(ui::mojom::DialogButton::kOk, false);
-    }
-  } else if (is_failure() || is_pending()) {
-    // Include the Cancel button when the scan is pending or failing.
-    DialogDelegate::SetButtons(
-        static_cast<int>(ui::mojom::DialogButton::kCancel));
-    DialogDelegate::SetDefaultButton(
-        static_cast<int>(ui::mojom::DialogButton::kNone));
-
-    DialogDelegate::SetButtonLabel(ui::mojom::DialogButton::kCancel,
-                                   GetCancelButtonText());
-    DialogDelegate::SetCancelCallback(
-        base::BindOnce(&ContentAnalysisDialogController::CancelButtonCallback,
-                       weak_ptr_factory_.GetWeakPtr()));
-  } else {
-    // Include no buttons otherwise.
-    DialogDelegate::SetButtons(
-        static_cast<int>(ui::mojom::DialogButton::kNone));
-    DialogDelegate::SetCancelCallback(
-        base::BindOnce(&ContentAnalysisDialogController::SuccessCallback,
-                       weak_ptr_factory_.GetWeakPtr()));
-  }
-}
-
 std::u16string ContentAnalysisDialogController::GetDialogMessage() const {
   switch (dialog_state_) {
     case State::PENDING:
@@ -584,35 +532,6 @@
   }
 }
 
-std::u16string ContentAnalysisDialogController::GetCancelButtonText() const {
-  int text_id;
-  auto overriden_text = delegate_base_->OverrideCancelButtonText();
-  if (overriden_text) {
-    return overriden_text.value();
-  }
-
-  switch (dialog_state_) {
-    case State::SUCCESS:
-      NOTREACHED();
-    case State::PENDING:
-      text_id = IDS_DEEP_SCANNING_DIALOG_CANCEL_UPLOAD_BUTTON;
-      break;
-    case State::FAILURE:
-      text_id = IDS_CLOSE;
-      break;
-    case State::WARNING:
-      text_id = IDS_DEEP_SCANNING_DIALOG_CANCEL_WARNING_BUTTON;
-      break;
-  }
-  return l10n_util::GetStringUTF16(text_id);
-}
-
-std::u16string ContentAnalysisDialogController::GetBypassWarningButtonText()
-    const {
-  DCHECK(is_warning());
-  return l10n_util::GetStringUTF16(IDS_DEEP_SCANNING_DIALOG_PROCEED_BUTTON);
-}
-
 std::unique_ptr<views::View> ContentAnalysisDialogController::CreateSideIcon() {
   // The icon left of the text has the appearance of a blue "Enterprise" logo
   // with a spinner when the scan is pending.
@@ -631,49 +550,6 @@
   return icon;
 }
 
-ui::ColorId ContentAnalysisDialogController::GetSideImageBackgroundColor()
-    const {
-  DCHECK(is_result());
-  DCHECK(contents_view_);
-
-  switch (dialog_state_) {
-    case State::PENDING:
-      NOTREACHED();
-    case State::SUCCESS:
-      return ui::kColorAccent;
-    case State::FAILURE:
-      return ui::kColorAlertHighSeverity;
-    case State::WARNING:
-      return ui::kColorAlertMediumSeverityIcon;
-  }
-}
-
-int ContentAnalysisDialogController::GetTopImageId() const {
-  if (ShouldUseDarkTopImage()) {
-    switch (dialog_state_) {
-      case State::PENDING:
-        return IDR_UPLOAD_SCANNING_DARK;
-      case State::SUCCESS:
-        return IDR_UPLOAD_SUCCESS_DARK;
-      case State::FAILURE:
-        return IDR_UPLOAD_VIOLATION_DARK;
-      case State::WARNING:
-        return IDR_UPLOAD_WARNING_DARK;
-    }
-  } else {
-    switch (dialog_state_) {
-      case State::PENDING:
-        return IDR_UPLOAD_SCANNING;
-      case State::SUCCESS:
-        return IDR_UPLOAD_SUCCESS;
-      case State::FAILURE:
-        return IDR_UPLOAD_VIOLATION;
-      case State::WARNING:
-        return IDR_UPLOAD_WARNING;
-    }
-  }
-}
-
 std::u16string ContentAnalysisDialogController::GetPendingMessage() const {
   DCHECK(is_pending());
   if (is_print_scan()) {
@@ -950,24 +826,6 @@
   }
 }
 
-ui::ColorId ContentAnalysisDialogController::GetSideImageLogoColor() const {
-  DCHECK(contents_view_);
-
-  switch (dialog_state_) {
-    case State::PENDING:
-      // In the dialog's pending state, the side image is just an enterprise
-      // logo surrounded by a throbber, so we use the throbber color for it.
-      return ui::kColorThrobber;
-    case State::SUCCESS:
-    case State::FAILURE:
-    case State::WARNING:
-      // In a result state, the side image is a circle colored with the result's
-      // color and an enterprise logo in front of it, so the logo should have
-      // the same color as the dialog's overall background.
-      return ui::kColorDialogBackground;
-  }
-}
-
 // static
 void ContentAnalysisDialogController::SetMinimumPendingDialogTimeForTesting(
     base::TimeDelta delta) {
@@ -1056,6 +914,10 @@
 }
 
 void ContentAnalysisDialogController::CancelDialogWithoutCallback() {
+  // TODO(crbug.com/422111748): Update this cleanup code when this class stops
+  // inheriting from ContentAnalysisDialogDelegate.
+  ContentAnalysisDialogDelegate::delegate_base_ = nullptr;
+
   // Reset `delegate` so no logic runs when the dialog is cancelled.
   delegate_base_.reset(nullptr);
 
@@ -1066,8 +928,4 @@
   }
 }
 
-bool ContentAnalysisDialogController::is_result() const {
-  return !is_pending();
-}
-
 }  // namespace enterprise_connectors
diff --git a/chrome/browser/enterprise/connectors/analysis/content_analysis_dialog_controller.h b/chrome/browser/enterprise/connectors/analysis/content_analysis_dialog_controller.h
index 7e9f7ce..55d273a 100644
--- a/chrome/browser/enterprise/connectors/analysis/content_analysis_dialog_controller.h
+++ b/chrome/browser/enterprise/connectors/analysis/content_analysis_dialog_controller.h
@@ -13,7 +13,6 @@
 #include "base/time/time.h"
 #include "chrome/browser/enterprise/connectors/analysis/content_analysis_delegate_base.h"
 #include "chrome/browser/enterprise/connectors/analysis/content_analysis_dialog_delegate.h"
-#include "chrome/browser/enterprise/connectors/analysis/content_analysis_views.h"
 #include "chrome/browser/enterprise/connectors/common.h"
 #include "chrome/browser/safe_browsing/cloud_content_scanning/deep_scanning_utils.h"
 #include "components/download/public/common/download_item.h"
@@ -46,8 +45,7 @@
     : public ContentAnalysisDialogDelegate,
       public content::WebContentsObserver,
       public views::TextfieldController,
-      public download::DownloadItem::Observer,
-      public ContentAnalysisBaseView::Delegate {
+      public download::DownloadItem::Observer {
  public:
   // TestObserver should be implemented by tests that need to track when certain
   // ContentAnalysisDialogController functions are called. The test can add
@@ -117,25 +115,10 @@
   void WebContentsDestroyed() override;
   void PrimaryPageChanged(content::Page& page) override;
 
-  // ContentAnalysisBaseView::Delegate:
-  int GetTopImageId() const override;
-  ui::ColorId GetSideImageLogoColor() const override;
-  ui::ColorId GetSideImageBackgroundColor() const override;
-  bool is_result() const override;
-
   // Updates the dialog with the result, and simply delete it from memory if
   // nothing should be shown.
   void ShowResult(FinalContentAnalysisResult result);
 
-  // Accessors to simplify `dialog_state_` checking.
-  inline bool is_success() const { return dialog_state_ == State::SUCCESS; }
-
-  inline bool is_failure() const { return dialog_state_ == State::FAILURE; }
-
-  inline bool is_warning() const { return dialog_state_ == State::WARNING; }
-
-  inline bool is_pending() const { return dialog_state_ == State::PENDING; }
-
   inline bool is_cloud() const { return is_cloud_; }
 
   bool has_custom_message() const {
@@ -177,35 +160,11 @@
   // Friend to allow use of TaskRunner::DeleteSoon().
   friend class base::DeleteHelper<ContentAnalysisDialogController>;
 
-  // Enum used to represent what the dialog is currently showing.
-  enum class State {
-    // The dialog is shown with an explanation that the scan is being performed
-    // and that the result is pending.
-    PENDING,
-
-    // The dialog is shown with a short message indicating that the scan was a
-    // success and that the user may proceed with their upload, drag-and-drop or
-    // paste.
-    SUCCESS,
-
-    // The dialog is shown with a message indicating that the scan was a failure
-    // and that the user may not proceed with their upload, drag-and-drop or
-    // paste.
-    FAILURE,
-
-    // The dialog is shown with a message indicating that the scan was a
-    // failure, but that the user may proceed with their upload, drag-and-drop
-    // or paste if they want to.
-    WARNING,
-  };
-
   ~ContentAnalysisDialogController() override;
 
   // Callback function of delayed timer to make the dialog visible.
   void ShowDialogNow();
 
-  void UpdateStateFromFinalResult(FinalContentAnalysisResult final_result);
-
   // Updates the views in the dialog to put them in the correct state for
   // `dialog_state_`. This doesn't trigger the same events/resizes as
   // UpdateDialog(), and doesn't require the presence of a widget. This is safe
@@ -224,21 +183,12 @@
   // Resizes the already shown dialog to accommodate changes in its content.
   void Resize(int height_to_add);
 
-  // Setup the appropriate buttons depending on `dialog_state_`.
-  void SetupButtons();
-
   // Returns a newly created side icon.
   std::unique_ptr<views::View> CreateSideIcon();
 
   // Returns the appropriate dialog message depending on `dialog_state_`.
   std::u16string GetDialogMessage() const;
 
-  // Returns the text for the Cancel button depending on `dialog_state_`.
-  std::u16string GetCancelButtonText() const;
-
-  // Returns the text for the Ok button for the warning case.
-  std::u16string GetBypassWarningButtonText() const;
-
   // Returns the appropriate pending message depending on `files_count_`.
   std::u16string GetPendingMessage() const;
 
@@ -309,12 +259,6 @@
 
   base::TimeTicks first_shown_timestamp_;
 
-  // Used to show the appropriate dialog depending on the scan's status.
-  State dialog_state_ = State::PENDING;
-
-  // Used to show the appropriate message.
-  FinalContentAnalysisResult final_result_;
-
   // Used to animate dialog height changes.
   std::unique_ptr<views::BoundsAnimator> bounds_animator_;
 
diff --git a/chrome/browser/enterprise/connectors/analysis/content_analysis_dialog_delegate.cc b/chrome/browser/enterprise/connectors/analysis/content_analysis_dialog_delegate.cc
index b77f463..5b1a1ca 100644
--- a/chrome/browser/enterprise/connectors/analysis/content_analysis_dialog_delegate.cc
+++ b/chrome/browser/enterprise/connectors/analysis/content_analysis_dialog_delegate.cc
@@ -1,12 +1,23 @@
 // 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/connectors/analysis/content_analysis_dialog_delegate.h"
 
+#include "chrome/grit/generated_resources.h"
+#include "chrome/grit/theme_resources.h"
+#include "components/strings/grit/components_strings.h"
+#include "ui/base/l10n/l10n_util.h"
 #include "ui/views/layout/box_layout_view.h"
 
 namespace enterprise_connectors {
 
+ContentAnalysisDialogDelegate::ContentAnalysisDialogDelegate(
+    ContentAnalysisDelegateBase* delegate)
+    : delegate_base_(delegate) {}
+
+ContentAnalysisDialogDelegate::~ContentAnalysisDialogDelegate() = default;
+
 std::u16string ContentAnalysisDialogDelegate::GetWindowTitle() const {
   return std::u16string();
 }
@@ -27,4 +38,146 @@
   return ui::mojom::ModalType::kChild;
 }
 
+int ContentAnalysisDialogDelegate::GetTopImageId() const {
+  if (color_utils::IsDark(contents_view_->GetColorProvider()->GetColor(
+          ui::kColorDialogBackground))) {
+    switch (dialog_state_) {
+      case State::PENDING:
+        return IDR_UPLOAD_SCANNING_DARK;
+      case State::SUCCESS:
+        return IDR_UPLOAD_SUCCESS_DARK;
+      case State::FAILURE:
+        return IDR_UPLOAD_VIOLATION_DARK;
+      case State::WARNING:
+        return IDR_UPLOAD_WARNING_DARK;
+    }
+  } else {
+    switch (dialog_state_) {
+      case State::PENDING:
+        return IDR_UPLOAD_SCANNING;
+      case State::SUCCESS:
+        return IDR_UPLOAD_SUCCESS;
+      case State::FAILURE:
+        return IDR_UPLOAD_VIOLATION;
+      case State::WARNING:
+        return IDR_UPLOAD_WARNING;
+    }
+  }
+}
+
+ui::ColorId ContentAnalysisDialogDelegate::GetSideImageLogoColor() const {
+  DCHECK(contents_view_);
+
+  switch (dialog_state_) {
+    case State::PENDING:
+      // In the dialog's pending state, the side image is just an enterprise
+      // logo surrounded by a throbber, so we use the throbber color for it.
+      return ui::kColorThrobber;
+    case State::SUCCESS:
+    case State::FAILURE:
+    case State::WARNING:
+      // In a result state, the side image is a circle colored with the result's
+      // color and an enterprise logo in front of it, so the logo should have
+      // the same color as the dialog's overall background.
+      return ui::kColorDialogBackground;
+  }
+}
+
+ui::ColorId ContentAnalysisDialogDelegate::GetSideImageBackgroundColor() const {
+  DCHECK(is_result());
+  DCHECK(contents_view_);
+
+  switch (dialog_state_) {
+    case State::PENDING:
+      NOTREACHED();
+    case State::SUCCESS:
+      return ui::kColorAccent;
+    case State::FAILURE:
+      return ui::kColorAlertHighSeverity;
+    case State::WARNING:
+      return ui::kColorAlertMediumSeverityIcon;
+  }
+}
+
+bool ContentAnalysisDialogDelegate::is_result() const {
+  return !is_pending();
+}
+
+void ContentAnalysisDialogDelegate::UpdateStateFromFinalResult(
+    FinalContentAnalysisResult final_result) {
+  final_result_ = final_result;
+  switch (final_result_) {
+    case FinalContentAnalysisResult::ENCRYPTED_FILES:
+    case FinalContentAnalysisResult::LARGE_FILES:
+    case FinalContentAnalysisResult::FAIL_CLOSED:
+    case FinalContentAnalysisResult::FAILURE:
+      dialog_state_ = State::FAILURE;
+      break;
+    case FinalContentAnalysisResult::SUCCESS:
+      dialog_state_ = State::SUCCESS;
+      break;
+    case FinalContentAnalysisResult::WARNING:
+      dialog_state_ = State::WARNING;
+      break;
+  }
+}
+
+void ContentAnalysisDialogDelegate::SetupButtons() {
+  if (is_warning()) {
+    // Include the Ok and Cancel buttons if there is a bypassable warning.
+    DialogDelegate::SetButtons(
+        static_cast<int>(ui::mojom::DialogButton::kCancel) |
+        static_cast<int>(ui::mojom::DialogButton::kOk));
+    DialogDelegate::SetDefaultButton(
+        static_cast<int>(ui::mojom::DialogButton::kCancel));
+
+    DialogDelegate::SetButtonLabel(ui::mojom::DialogButton::kCancel,
+                                   GetCancelButtonText());
+
+    DialogDelegate::SetButtonLabel(
+        ui::mojom::DialogButton::kOk,
+        l10n_util::GetStringUTF16(IDS_DEEP_SCANNING_DIALOG_PROCEED_BUTTON));
+
+    if (delegate_base_->BypassRequiresJustification()) {
+      DialogDelegate::SetButtonEnabled(ui::mojom::DialogButton::kOk, false);
+    }
+  } else if (is_failure() || is_pending()) {
+    // Include the Cancel button when the scan is pending or failing.
+    DialogDelegate::SetButtons(
+        static_cast<int>(ui::mojom::DialogButton::kCancel));
+    DialogDelegate::SetDefaultButton(
+        static_cast<int>(ui::mojom::DialogButton::kNone));
+
+    DialogDelegate::SetButtonLabel(ui::mojom::DialogButton::kCancel,
+                                   GetCancelButtonText());
+  } else {
+    // Include no buttons otherwise.
+    DialogDelegate::SetButtons(
+        static_cast<int>(ui::mojom::DialogButton::kNone));
+  }
+}
+
+std::u16string ContentAnalysisDialogDelegate::GetCancelButtonText() const {
+  int text_id;
+  auto overriden_text = delegate_base_->OverrideCancelButtonText();
+  if (overriden_text) {
+    return overriden_text.value();
+  }
+
+  switch (dialog_state_) {
+    case State::SUCCESS:
+      NOTREACHED();
+    case State::PENDING:
+      text_id = IDS_DEEP_SCANNING_DIALOG_CANCEL_UPLOAD_BUTTON;
+      break;
+    case State::FAILURE:
+      text_id = IDS_CLOSE;
+      break;
+    case State::WARNING:
+      text_id = IDS_DEEP_SCANNING_DIALOG_CANCEL_WARNING_BUTTON;
+      break;
+  }
+  return l10n_util::GetStringUTF16(text_id);
+}
+
 }  // namespace enterprise_connectors
diff --git a/chrome/browser/enterprise/connectors/analysis/content_analysis_dialog_delegate.h b/chrome/browser/enterprise/connectors/analysis/content_analysis_dialog_delegate.h
index 43f2183..dcc5020 100644
--- a/chrome/browser/enterprise/connectors/analysis/content_analysis_dialog_delegate.h
+++ b/chrome/browser/enterprise/connectors/analysis/content_analysis_dialog_delegate.h
@@ -5,6 +5,10 @@
 #ifndef CHROME_BROWSER_ENTERPRISE_CONNECTORS_ANALYSIS_CONTENT_ANALYSIS_DIALOG_DELEGATE_H_
 #define CHROME_BROWSER_ENTERPRISE_CONNECTORS_ANALYSIS_CONTENT_ANALYSIS_DIALOG_DELEGATE_H_
 
+#include "base/functional/callback_forward.h"
+#include "chrome/browser/enterprise/connectors/analysis/content_analysis_delegate_base.h"
+#include "chrome/browser/enterprise/connectors/analysis/content_analysis_views.h"
+#include "components/enterprise/connectors/core/common.h"
 #include "ui/views/window/dialog_delegate.h"
 
 namespace views {
@@ -15,8 +19,34 @@
 
 // Implementation of `views::DialogDelegate` used to show a user the state of
 // content analysis triggered by one of their action.
-class ContentAnalysisDialogDelegate : public views::DialogDelegate {
+class ContentAnalysisDialogDelegate : public views::DialogDelegate,
+                                      public ContentAnalysisBaseView::Delegate {
  public:
+  // Enum used to represent what the dialog is currently showing.
+  enum class State {
+    // The dialog is shown with an explanation that the scan is being performed
+    // and that the result is pending.
+    PENDING,
+
+    // The dialog is shown with a short message indicating that the scan was a
+    // success and that the user may proceed with their upload, drag-and-drop or
+    // paste.
+    SUCCESS,
+
+    // The dialog is shown with a message indicating that the scan was a failure
+    // and that the user may not proceed with their upload, drag-and-drop or
+    // paste.
+    FAILURE,
+
+    // The dialog is shown with a message indicating that the scan was a
+    // failure, but that the user may proceed with their upload, drag-and-drop
+    // or paste if they want to.
+    WARNING,
+  };
+
+  explicit ContentAnalysisDialogDelegate(ContentAnalysisDelegateBase* delegate);
+  ~ContentAnalysisDialogDelegate() override;
+
   // views::DialogDelegate:
   std::u16string GetWindowTitle() const override;
   bool ShouldShowCloseButton() const override;
@@ -24,8 +54,38 @@
   const views::Widget* GetWidget() const override;
   ui::mojom::ModalType GetModalType() const override;
 
+  // ContentAnalysisBaseView::Delegate:
+  int GetTopImageId() const override;
+  ui::ColorId GetSideImageLogoColor() const override;
+  ui::ColorId GetSideImageBackgroundColor() const override;
+  bool is_result() const override;
+
+  // Accessors to simplify `dialog_state_` checking.
+  inline bool is_success() const { return dialog_state_ == State::SUCCESS; }
+  inline bool is_failure() const { return dialog_state_ == State::FAILURE; }
+  inline bool is_warning() const { return dialog_state_ == State::WARNING; }
+  inline bool is_pending() const { return dialog_state_ == State::PENDING; }
+
+  void UpdateStateFromFinalResult(FinalContentAnalysisResult final_result);
+
+  // TODO(crbug.com/422111748): Change this to "private" after
+  // `ContentAnalysisDialogController` no longer inherits from this class.
  protected:
+  // Helper functions to set/get various parts of the dialog depending on the
+  // values of `dialog_state_` and `delegate_base_`.
+  void SetupButtons();
+  std::u16string GetCancelButtonText() const;
+
   raw_ptr<views::BoxLayoutView> contents_view_ = nullptr;
+
+  // Used to show the appropriate message.
+  FinalContentAnalysisResult final_result_;
+
+  // Used to show the appropriate dialog depending on the scan's status.
+  State dialog_state_ = State::PENDING;
+
+  // Should be owned by the parent of this class.
+  raw_ptr<ContentAnalysisDelegateBase> delegate_base_;
 };
 
 }  // namespace enterprise_connectors
diff --git a/chrome/browser/enterprise/connectors/device_trust/key_management/browser/commands/BUILD.gn b/chrome/browser/enterprise/connectors/device_trust/key_management/browser/commands/BUILD.gn
index 031924b..3fa5ccb 100644
--- a/chrome/browser/enterprise/connectors/device_trust/key_management/browser/commands/BUILD.gn
+++ b/chrome/browser/enterprise/connectors/device_trust/key_management/browser/commands/BUILD.gn
@@ -40,6 +40,7 @@
       "win_key_rotation_command.h",
     ]
     deps += [
+      "//chrome/browser/google",
       "//chrome/install_static:install_static_util",
       "//chrome/installer/util:with_no_strings",
       "//chrome/updater/app/server/win:updater_legacy_idl",
diff --git a/chrome/browser/enterprise/connectors/device_trust/key_management/browser/commands/win_key_rotation_command.cc b/chrome/browser/enterprise/connectors/device_trust/key_management/browser/commands/win_key_rotation_command.cc
index 0046139..0f83dd3 100644
--- a/chrome/browser/enterprise/connectors/device_trust/key_management/browser/commands/win_key_rotation_command.cc
+++ b/chrome/browser/enterprise/connectors/device_trust/key_management/browser/commands/win_key_rotation_command.cc
@@ -31,26 +31,14 @@
 #include "base/win/windows_types.h"
 #include "chrome/browser/enterprise/connectors/device_trust/common/device_trust_constants.h"
 #include "chrome/browser/enterprise/connectors/device_trust/key_management/browser/commands/metrics_utils.h"
+#include "chrome/browser/google/google_update_app_command.h"
 #include "chrome/install_static/install_util.h"
 #include "chrome/installer/util/util_constants.h"
-#include "chrome/updater/app/server/win/updater_legacy_idl.h"
 
 namespace enterprise_connectors {
 
 namespace {
 
-// Explicitly allow impersonating the client since some COM code
-// elsewhere in the browser process may have previously used
-// CoInitializeSecurity to set the impersonation level to something other than
-// the default. Ignore errors since an attempt to use Google Update may succeed
-// regardless.
-void ConfigureProxyBlanket(IUnknown* interface_pointer) {
-  ::CoSetProxyBlanket(
-      interface_pointer, RPC_C_AUTHN_DEFAULT, RPC_C_AUTHZ_DEFAULT,
-      COLE_DEFAULT_PRINCIPAL, RPC_C_AUTHN_LEVEL_PKT_PRIVACY,
-      RPC_C_IMP_LEVEL_IMPERSONATE, nullptr, EOAC_DYNAMIC_CLOAKING);
-}
-
 // The maximum number of string that can appear in `args` when calling
 // RunGoogleUpdateElevatedCommand().
 constexpr int kMaxCommandArgs = 9;
@@ -65,93 +53,21 @@
   if (args.size() > kMaxCommandArgs)
     return E_INVALIDARG;
 
-  Microsoft::WRL::ComPtr<IUnknown> server;
-  HRESULT hr = ::CoCreateInstance(CLSID_GoogleUpdate3WebSystemClass, nullptr,
-                                  CLSCTX_ALL, IID_PPV_ARGS(&server));
-  if (FAILED(hr))
-    return hr;
-
-  ConfigureProxyBlanket(server.Get());
-
-  // Chrome queries for the SxS IIDs first, with a fallback to the legacy IID.
-  // Without this change, marshaling can load the typelib from the wrong hive
-  // (HKCU instead of HKLM, or vice-versa).
-  Microsoft::WRL::ComPtr<IGoogleUpdate3Web> google_update;
-  hr = server.CopyTo(__uuidof(IGoogleUpdate3WebSystem),
-                     IID_PPV_ARGS_Helper(&google_update));
-  if (FAILED(hr)) {
-    hr = server.As(&google_update);
-    if (FAILED(hr)) {
-      return hr;
-    }
+  auto get_command_result = GetUpdaterAppCommand(command);
+  if (!get_command_result.has_value()) {
+    return get_command_result.error();
   }
 
-  Microsoft::WRL::ComPtr<IDispatch> dispatch;
-  hr = google_update->createAppBundleWeb(&dispatch);
-  if (FAILED(hr))
-    return hr;
-
-  // Chrome queries for the SxS IIDs first, with a fallback to the legacy IID.
-  // Without this change, marshaling can load the typelib from the wrong hive
-  // (HKCU instead of HKLM, or vice-versa).
-  Microsoft::WRL::ComPtr<IAppBundleWeb> app_bundle;
-  hr = dispatch.CopyTo(__uuidof(IAppBundleWebSystem),
-                       IID_PPV_ARGS_Helper(&app_bundle));
-  if (FAILED(hr)) {
-    hr = dispatch.As(&app_bundle);
-    if (FAILED(hr)) {
-      return hr;
-    }
-  }
-
-  dispatch.Reset();
-  ConfigureProxyBlanket(app_bundle.Get());
-  app_bundle->initialize();
-  const wchar_t* app_guid = install_static::GetAppGuid();
-  hr = app_bundle->createInstalledApp(base::win::ScopedBstr(app_guid).Get());
-  if (FAILED(hr))
-    return hr;
-
-  hr = app_bundle->get_appWeb(0, &dispatch);
-  if (FAILED(hr))
-    return hr;
-
-  Microsoft::WRL::ComPtr<IAppWeb> app;
-  hr = dispatch.CopyTo(__uuidof(IAppWebSystem), IID_PPV_ARGS_Helper(&app));
-  if (FAILED(hr)) {
-    hr = dispatch.As(&app);
-    if (FAILED(hr)) {
-      return hr;
-    }
-  }
-
-  dispatch.Reset();
-  ConfigureProxyBlanket(app.Get());
-
-  hr = app->get_command(base::win::ScopedBstr(command).Get(), &dispatch);
-  if (FAILED(hr)) {
-    return hr;
-  }
-
-  Microsoft::WRL::ComPtr<IAppCommandWeb> app_command;
-  hr = dispatch.CopyTo(__uuidof(IAppCommandWebSystem),
-                       IID_PPV_ARGS_Helper(&app_command));
-  if (FAILED(hr)) {
-    hr = dispatch.As(&app_command);
-    if (FAILED(hr)) {
-      return hr;
-    }
-  }
-
-  ConfigureProxyBlanket(app_command.Get());
-
+  Microsoft::WRL::ComPtr<IAppCommandWeb> app_command =
+      get_command_result.value();
   _variant_t vargs[kMaxCommandArgs];
   for (size_t i = 0; i < args.size(); ++i) {
     vargs[i] = args[i].c_str();
   }
 
-  hr = app_command->execute(vargs[0], vargs[1], vargs[2], vargs[3], vargs[4],
-                            vargs[5], vargs[6], vargs[7], vargs[8]);
+  HRESULT hr =
+      app_command->execute(vargs[0], vargs[1], vargs[2], vargs[3], vargs[4],
+                           vargs[5], vargs[6], vargs[7], vargs[8]);
   if (FAILED(hr))
     return hr;
 
diff --git a/chrome/browser/glic/BUILD.gn b/chrome/browser/glic/BUILD.gn
index 024cd86..27d76464 100644
--- a/chrome/browser/glic/BUILD.gn
+++ b/chrome/browser/glic/BUILD.gn
@@ -303,6 +303,7 @@
     ":impl",
     "//chrome/browser",
     "//chrome/browser:global_features",
+    "//chrome/browser/actor:actor",
     "//chrome/browser/actor:test_support",
     "//chrome/browser/background/glic",
     "//chrome/browser/contextual_cueing",
diff --git a/chrome/browser/glic/host/glic_actor_controller.cc b/chrome/browser/glic/host/glic_actor_controller.cc
index d2db3fde..668a8da 100644
--- a/chrome/browser/glic/host/glic_actor_controller.cc
+++ b/chrome/browser/glic/host/glic_actor_controller.cc
@@ -56,6 +56,7 @@
 
 void OnGetContextFromFocusedTab(
     mojom::WebClientHandler::ActInFocusedTabCallback callback,
+    base::WeakPtr<actor::ActorCoordinator> actor_coordinator,
     mojom::GetContextResultPtr tab_context_result) {
   if (tab_context_result->is_error_reason()) {
     mojom::ActInFocusedTabResultPtr result = MakeActErrorResult(
@@ -64,6 +65,14 @@
     return;
   }
 
+  if (actor_coordinator &&
+      tab_context_result->get_tab_context()
+          ->annotated_page_data->annotated_page_content.has_value()) {
+    actor_coordinator->DidObserveContext(
+        tab_context_result->get_tab_context()
+            ->annotated_page_data->annotated_page_content.value());
+  }
+
   mojom::ActInFocusedTabResultPtr result =
       mojom::ActInFocusedTabResult::NewActInFocusedTabResponse(
           mojom::ActInFocusedTabResponse::New(
@@ -244,7 +253,8 @@
   if (tab) {
     FetchPageContext(
         tab.get(), options, /*include_actionable_data=*/true,
-        base::BindOnce(OnGetContextFromFocusedTab, std::move(callback)));
+        base::BindOnce(OnGetContextFromFocusedTab, std::move(callback),
+                       this->GetActorCoordinator()->GetWeakPtr()));
   } else {
     PostTaskForActCallback(std::move(callback),
                            mojom::ActInFocusedTabErrorReason::kTargetNotFound);
diff --git a/chrome/browser/glic/host/glic_actor_controller_interactive_uitest.cc b/chrome/browser/glic/host/glic_actor_controller_interactive_uitest.cc
index d0ffae7..829bcf0 100644
--- a/chrome/browser/glic/host/glic_actor_controller_interactive_uitest.cc
+++ b/chrome/browser/glic/host/glic_actor_controller_interactive_uitest.cc
@@ -10,7 +10,9 @@
 #include "base/functional/callback.h"
 #include "base/strings/to_string.h"
 #include "base/test/bind.h"
+#include "base/test/protobuf_matchers.h"
 #include "base/test/scoped_feature_list.h"
+#include "chrome/browser/actor/actor_coordinator.h"
 #include "chrome/browser/actor/actor_test_util.h"
 #include "chrome/browser/glic/host/context/glic_page_context_fetcher.h"
 #include "chrome/browser/glic/host/glic.mojom-shared.h"
@@ -30,6 +32,7 @@
 
 namespace {
 
+using ::base::test::EqualsProto;
 using ::optimization_guide::proto::AnnotatedPageContent;
 using ::optimization_guide::proto::BrowserAction;
 using ::optimization_guide::proto::ClickAction;
@@ -363,6 +366,20 @@
         expected)));
   }
 
+  // Check ActorCoordinator caches the last apc observation.
+  auto CheckActorCoordinatorHasAnnotatedPageContentCache() {
+    return Steps(Do([&]() {
+      GlicKeyedService* glic_service =
+          GlicKeyedServiceFactory::GetGlicKeyedService(browser()->GetProfile());
+      ASSERT_TRUE(glic_service);
+
+      const AnnotatedPageContent& cached_apc =
+          *glic_service->GetActorCoordinatorForTesting(nullptr)
+               .GetLastObservedPageContent();
+      EXPECT_THAT(*annotated_page_content_, EqualsProto(cached_apc));
+    }));
+  }
+
  private:
   int32_t SearchAnnotatedPageContent(std::string_view label) {
     CHECK(annotated_page_content_)
@@ -409,6 +426,20 @@
 }
 
 IN_PROC_BROWSER_TEST_F(GlicActorControllerUiTest,
+                       CachesLastObservedPageContentAfterActionFinish) {
+  DEFINE_LOCAL_ELEMENT_IDENTIFIER_VALUE(kNewActorTabId);
+
+  const GURL task_url =
+      embedded_test_server()->GetURL("/actor/page_with_clickable_element.html");
+  BrowserAction navigate = actor::MakeNavigate(task_url.spec());
+
+  RunTestSequence(InitializeWithOpenGlicWindow(),
+                  StartActorTaskInNewTab(task_url, kNewActorTabId),
+                  GetPageContextFromFocusedTab(),
+                  CheckActorCoordinatorHasAnnotatedPageContentCache());
+}
+
+IN_PROC_BROWSER_TEST_F(GlicActorControllerUiTest,
                        UsesExistingActorTabOnSubsequentNavigate) {
   DEFINE_LOCAL_ELEMENT_IDENTIFIER_VALUE(kNewActorTabId);
   const GURL task_url =
diff --git a/chrome/browser/google/BUILD.gn b/chrome/browser/google/BUILD.gn
index f2b82f6..8e7a23d 100644
--- a/chrome/browser/google/BUILD.gn
+++ b/chrome/browser/google/BUILD.gn
@@ -41,6 +41,8 @@
     sources += [
       "did_run_updater_win.cc",
       "did_run_updater_win.h",
+      "google_update_app_command.cc",
+      "google_update_app_command.h",
     ]
     if (is_chrome_branded) {
       sources += [
@@ -53,8 +55,6 @@
         "switches.cc",
         "switches.h",
       ]
-
-      deps += [ "//chrome/updater/app/server/win:updater_legacy_idl" ]
     }
 
     deps += [
@@ -62,6 +62,7 @@
       "//chrome/common",
       "//chrome/install_static:install_static_util",
       "//chrome/installer/util:with_no_strings",
+      "//chrome/updater/app/server/win:updater_legacy_idl",
       "//components/policy/core/browser",
       "//components/policy/core/common",
     ]
diff --git a/chrome/browser/google/google_update_app_command.cc b/chrome/browser/google/google_update_app_command.cc
new file mode 100644
index 0000000..069a43e
--- /dev/null
+++ b/chrome/browser/google/google_update_app_command.cc
@@ -0,0 +1,103 @@
+// 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/google/google_update_app_command.h"
+
+#include "base/win/scoped_bstr.h"
+#include "chrome/install_static/install_util.h"
+
+void ConfigureProxyBlanket(IUnknown* interface_pointer) {
+  ::CoSetProxyBlanket(
+      interface_pointer, RPC_C_AUTHN_DEFAULT, RPC_C_AUTHZ_DEFAULT,
+      COLE_DEFAULT_PRINCIPAL, RPC_C_AUTHN_LEVEL_PKT_PRIVACY,
+      RPC_C_IMP_LEVEL_IMPERSONATE, nullptr, EOAC_DYNAMIC_CLOAKING);
+}
+
+base::expected<Microsoft::WRL::ComPtr<IAppCommandWeb>, HRESULT>
+GetUpdaterAppCommand(const std::wstring& command_name) {
+  Microsoft::WRL::ComPtr<IUnknown> server;
+  HRESULT hr = ::CoCreateInstance(CLSID_GoogleUpdate3WebSystemClass, nullptr,
+                                  CLSCTX_ALL, IID_PPV_ARGS(&server));
+  if (FAILED(hr)) {
+    return base::unexpected(hr);
+  }
+
+  ConfigureProxyBlanket(server.Get());
+
+  // Chrome queries for the SxS IIDs first, with a fallback to the legacy IID.
+  // Without this change, marshaling can load the typelib from the wrong hive
+  // (HKCU instead of HKLM, or vice-versa).
+  Microsoft::WRL::ComPtr<IGoogleUpdate3Web> google_update;
+  hr = server.CopyTo(__uuidof(IGoogleUpdate3WebSystem),
+                     IID_PPV_ARGS_Helper(&google_update));
+  if (FAILED(hr)) {
+    hr = server.As(&google_update);
+    if (FAILED(hr)) {
+      return base::unexpected(hr);
+    }
+  }
+
+  Microsoft::WRL::ComPtr<IDispatch> dispatch;
+  hr = google_update->createAppBundleWeb(&dispatch);
+  if (FAILED(hr)) {
+    return base::unexpected(hr);
+  }
+
+  // Chrome queries for the SxS IIDs first, with a fallback to the legacy IID.
+  // Without this change, marshaling can load the typelib from the wrong hive
+  // (HKCU instead of HKLM, or vice-versa).
+  Microsoft::WRL::ComPtr<IAppBundleWeb> app_bundle;
+  hr = dispatch.CopyTo(__uuidof(IAppBundleWebSystem),
+                       IID_PPV_ARGS_Helper(&app_bundle));
+  if (FAILED(hr)) {
+    hr = dispatch.As(&app_bundle);
+    if (FAILED(hr)) {
+      return base::unexpected(hr);
+    }
+  }
+
+  dispatch.Reset();
+  ConfigureProxyBlanket(app_bundle.Get());
+  app_bundle->initialize();
+  const wchar_t* app_guid = install_static::GetAppGuid();
+  hr = app_bundle->createInstalledApp(base::win::ScopedBstr(app_guid).Get());
+  if (FAILED(hr)) {
+    return base::unexpected(hr);
+  }
+
+  hr = app_bundle->get_appWeb(0, &dispatch);
+  if (FAILED(hr)) {
+    return base::unexpected(hr);
+  }
+
+  Microsoft::WRL::ComPtr<IAppWeb> app;
+  hr = dispatch.CopyTo(__uuidof(IAppWebSystem), IID_PPV_ARGS_Helper(&app));
+  if (FAILED(hr)) {
+    hr = dispatch.As(&app);
+    if (FAILED(hr)) {
+      return base::unexpected(hr);
+    }
+  }
+
+  dispatch.Reset();
+  ConfigureProxyBlanket(app.Get());
+
+  hr = app->get_command(base::win::ScopedBstr(command_name).Get(), &dispatch);
+  if (FAILED(hr) || !dispatch) {
+    return base::unexpected(hr);
+  }
+
+  Microsoft::WRL::ComPtr<IAppCommandWeb> app_command;
+  hr = dispatch.CopyTo(__uuidof(IAppCommandWebSystem),
+                       IID_PPV_ARGS_Helper(&app_command));
+  if (FAILED(hr)) {
+    hr = dispatch.As(&app_command);
+    if (FAILED(hr)) {
+      return base::unexpected(hr);
+    }
+  }
+
+  ConfigureProxyBlanket(app_command.Get());
+  return base::ok(app_command);
+}
diff --git a/chrome/browser/google/google_update_app_command.h b/chrome/browser/google/google_update_app_command.h
new file mode 100644
index 0000000..293a263
--- /dev/null
+++ b/chrome/browser/google/google_update_app_command.h
@@ -0,0 +1,31 @@
+// 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_GOOGLE_GOOGLE_UPDATE_APP_COMMAND_H_
+#define CHROME_BROWSER_GOOGLE_GOOGLE_UPDATE_APP_COMMAND_H_
+
+#include <windows.h>
+
+#include <wrl/client.h>
+
+#include <string>
+#include <utility>
+
+#include "base/types/expected.h"
+#include "chrome/updater/app/server/win/updater_legacy_idl.h"
+
+// Explicitly allows the Google Update service to impersonate the client since
+// some COM code elsewhere in the browser process may have previously used
+// CoInitializeSecurity to set the impersonation level to something other than
+// the default.
+// Ignores errors since an attempt to use Google Update may succeed
+// regardless.
+void ConfigureProxyBlanket(IUnknown* interface_pointer);
+
+// Gets the AppCommand with the given name, ready to be invoked by the Google
+// Updater.
+base::expected<Microsoft::WRL::ComPtr<IAppCommandWeb>, HRESULT>
+GetUpdaterAppCommand(const std::wstring& command_name);
+
+#endif  // CHROME_BROWSER_GOOGLE_GOOGLE_UPDATE_APP_COMMAND_H_
diff --git a/chrome/browser/google/google_update_win.cc b/chrome/browser/google/google_update_win.cc
index 2c90373c..7c92f4c 100644
--- a/chrome/browser/google/google_update_win.cc
+++ b/chrome/browser/google/google_update_win.cc
@@ -37,6 +37,7 @@
 #include "base/win/atl.h"
 #include "base/win/scoped_bstr.h"
 #include "base/win/win_util.h"
+#include "chrome/browser/google/google_update_app_command.h"
 #include "chrome/browser/google/switches.h"
 #include "chrome/common/url_constants.h"
 #include "chrome/grit/generated_resources.h"
@@ -118,18 +119,6 @@
              : CANNOT_UPGRADE_CHROME_IN_THIS_DIRECTORY;
 }
 
-// Explicitly allow the Google Update service to impersonate the client since
-// some COM code elsewhere in the browser process may have previously used
-// CoInitializeSecurity to set the impersonation level to something other than
-// the default. Ignore errors since an attempt to use Google Update may succeed
-// regardless.
-void ConfigureProxyBlanket(IUnknown* interface_pointer) {
-  ::CoSetProxyBlanket(
-      interface_pointer, RPC_C_AUTHN_DEFAULT, RPC_C_AUTHZ_DEFAULT,
-      COLE_DEFAULT_PRINCIPAL, RPC_C_AUTHN_LEVEL_PKT_PRIVACY,
-      RPC_C_IMP_LEVEL_IMPERSONATE, nullptr, EOAC_DYNAMIC_CLOAKING);
-}
-
 // Creates a class factory for a COM Local Server class using the Elevation
 // moniker. |hwnd| must refer to a foregound window in order to get the UAC
 // prompt to appear in the foreground if running on Vista+. It can also be NULL
diff --git a/chrome/browser/password_manager/android/cred_man_controller.cc b/chrome/browser/password_manager/android/cred_man_controller.cc
index fdbb4e1..f1fb9df 100644
--- a/chrome/browser/password_manager/android/cred_man_controller.cc
+++ b/chrome/browser/password_manager/android/cred_man_controller.cc
@@ -37,15 +37,32 @@
     raw_ptr<WebAuthnCredManDelegate> cred_man_delegate,
     std::unique_ptr<PasswordCredentialFiller> filler,
     base::WeakPtr<password_manager::ContentPasswordManagerDriver> frame_driver,
-    bool is_webauthn_form) {
+    bool is_webauthn_form,
+    PasskeyDelayCallback delay_callback) {
   // webauthn forms without passkeys should show TouchToFill bottom sheet.
   if (!cred_man_delegate || !is_webauthn_form ||
       WebAuthnCredManDelegate::CredManMode() !=
-          WebAuthnCredManDelegate::CredManEnabledMode::kAllCredMan ||
-      cred_man_delegate->HasPasskeys() !=
-          WebAuthnCredManDelegate::State::kHasPasskeys) {
+          WebAuthnCredManDelegate::CredManEnabledMode::kAllCredMan) {
     return false;
   }
+
+  switch (cred_man_delegate->HasPasskeys()) {
+    case WebAuthnCredManDelegate::State::kHasPasskeys:
+      break;
+    case WebAuthnCredManDelegate::State::kNotReady:
+      if (!delay_callback.is_null()) {
+        base::OnceCallback<void(base::OnceClosure)> notification_callback =
+            base::BindOnce(&WebAuthnCredManDelegate::
+                               RequestNotificationWhenCredentialsReady,
+                           cred_man_delegate->AsWeakPtr());
+        std::move(delay_callback).Run(std::move(notification_callback));
+        return true;
+      }
+      return false;
+    case WebAuthnCredManDelegate::State::kNoPasskeys:
+      return false;
+  }
+
   visibility_controller_->SetVisible(std::move(frame_driver));
   filler_ = std::move(filler);
   cred_man_delegate->SetRequestCompletionCallback(base::BindRepeating(
diff --git a/chrome/browser/password_manager/android/cred_man_controller.h b/chrome/browser/password_manager/android/cred_man_controller.h
index 2bf6cdb..bf963ac 100644
--- a/chrome/browser/password_manager/android/cred_man_controller.h
+++ b/chrome/browser/password_manager/android/cred_man_controller.h
@@ -7,6 +7,7 @@
 
 #include <string>
 
+#include "base/functional/callback.h"
 #include "base/memory/raw_ptr.h"
 #include "base/memory/weak_ptr.h"
 #include "components/password_manager/core/browser/password_manager_client.h"
@@ -27,6 +28,9 @@
 // used in Android U+ only.
 class CredManController {
  public:
+  using PasskeyDelayCallback =
+      base::OnceCallback<void(base::OnceCallback<void(base::OnceClosure)>)>;
+
   CredManController(base::WeakPtr<KeyboardReplacingSurfaceVisibilityController>
                         visibility_controller,
                     password_manager::PasswordManagerClient* password_client);
@@ -39,11 +43,17 @@
   // Determines if the Android Credential Manager UI should be shown and shows
   // if required. Returns true if the Android Credential Manager UI is shown,
   // false otherwise.
+  // If `delay_callback` is not null and passkeys are not yet enumerated,
+  // invokes `delay_callback` and returns true. `delay_callback` takes as an
+  // argument a callback that registers for notifications when passkeys become
+  // available. If `delay_callback` is not null and passkeys enumeration has
+  // completed, the callback will be destroyed without invocation.
   bool Show(raw_ptr<webauthn::WebAuthnCredManDelegate> cred_man_delegate,
             std::unique_ptr<PasswordCredentialFiller> filler,
             base::WeakPtr<password_manager::ContentPasswordManagerDriver>
                 frame_driver,
-            bool is_webauthn_form);
+            bool is_webauthn_form,
+            PasskeyDelayCallback delay_callback);
 
  private:
   void Dismiss(bool success);
diff --git a/chrome/browser/password_manager/android/cred_man_controller_unittest.cc b/chrome/browser/password_manager/android/cred_man_controller_unittest.cc
index 2f36c24..b079709 100644
--- a/chrome/browser/password_manager/android/cred_man_controller_unittest.cc
+++ b/chrome/browser/password_manager/android/cred_man_controller_unittest.cc
@@ -7,6 +7,7 @@
 #include <memory>
 
 #include "base/memory/weak_ptr.h"
+#include "base/test/bind.h"
 #include "base/test/gmock_callback_support.h"
 #include "base/test/metrics/histogram_tester.h"
 #include "base/test/mock_callback.h"
@@ -96,10 +97,10 @@
 TEST_F(CredManControllerTest, DoesNotShowIfNonWebAuthnForm) {
   std::unique_ptr<MockPasswordCredentialFiller> filler = PrepareFiller();
   EXPECT_CALL(visibility_controller(), SetVisible(_)).Times(0);
-  EXPECT_FALSE(controller().Show(web_authn_cred_man_delegate(),
-                                 std::move(filler),
-                                 /*frame_driver=*/nullptr,
-                                 /*is_webauthn_form=*/false));
+  EXPECT_FALSE(controller().Show(
+      web_authn_cred_man_delegate(), std::move(filler),
+      /*frame_driver=*/nullptr,
+      /*is_webauthn_form=*/false, CredManController::PasskeyDelayCallback()));
 }
 
 TEST_F(CredManControllerTest, DoesNotShowIfFeatureDisabled) {
@@ -107,10 +108,10 @@
       CredManSupport::DISABLED);
   std::unique_ptr<MockPasswordCredentialFiller> filler = PrepareFiller();
   EXPECT_CALL(visibility_controller(), SetVisible(_)).Times(0);
-  EXPECT_FALSE(controller().Show(web_authn_cred_man_delegate(),
-                                 std::move(filler),
-                                 /*frame_driver=*/nullptr,
-                                 /*is_webauthn_form=*/true));
+  EXPECT_FALSE(controller().Show(
+      web_authn_cred_man_delegate(), std::move(filler),
+      /*frame_driver=*/nullptr,
+      /*is_webauthn_form=*/true, CredManController::PasskeyDelayCallback()));
 }
 
 TEST_F(CredManControllerTest, DoesNotShowIfGpmNotInCredMan) {
@@ -118,10 +119,10 @@
       CredManSupport::PARALLEL_WITH_FIDO_2);
   std::unique_ptr<MockPasswordCredentialFiller> filler = PrepareFiller();
   EXPECT_CALL(visibility_controller(), SetVisible(_)).Times(0);
-  EXPECT_FALSE(controller().Show(web_authn_cred_man_delegate(),
-                                 std::move(filler),
-                                 /*frame_driver=*/nullptr,
-                                 /*is_webauthn_form=*/true));
+  EXPECT_FALSE(controller().Show(
+      web_authn_cred_man_delegate(), std::move(filler),
+      /*frame_driver=*/nullptr,
+      /*is_webauthn_form=*/true, CredManController::PasskeyDelayCallback()));
 }
 
 TEST_F(CredManControllerTest, DoesNotShowIfNoResults) {
@@ -133,10 +134,10 @@
       /*has_results=*/false, mock_full_assertion_request.Get());
 
   EXPECT_CALL(visibility_controller(), SetVisible(_)).Times(0);
-  EXPECT_FALSE(controller().Show(web_authn_cred_man_delegate(),
-                                 std::move(filler),
-                                 /*frame_driver=*/nullptr,
-                                 /*is_webauthn_form=*/true));
+  EXPECT_FALSE(controller().Show(
+      web_authn_cred_man_delegate(), std::move(filler),
+      /*frame_driver=*/nullptr,
+      /*is_webauthn_form=*/true, CredManController::PasskeyDelayCallback()));
 }
 
 TEST_F(CredManControllerTest, ShowIfResultsExist) {
@@ -150,10 +151,10 @@
       /*has_results=*/true, mock_full_assertion_request.Get());
 
   EXPECT_CALL(visibility_controller(), SetVisible(_));
-  EXPECT_TRUE(controller().Show(web_authn_cred_man_delegate(),
-                                std::move(filler),
-                                /*frame_driver=*/nullptr,
-                                /*is_webauthn_form=*/true));
+  EXPECT_TRUE(controller().Show(
+      web_authn_cred_man_delegate(), std::move(filler),
+      /*frame_driver=*/nullptr,
+      /*is_webauthn_form=*/true, CredManController::PasskeyDelayCallback()));
 }
 
 TEST_F(CredManControllerTest, Fill) {
@@ -170,10 +171,10 @@
   web_authn_cred_man_delegate()->OnCredManConditionalRequestPending(
       /*has_results=*/true, mock_full_assertion_request.Get());
 
-  EXPECT_TRUE(controller().Show(web_authn_cred_man_delegate(),
-                                std::move(filler),
-                                /*frame_driver=*/nullptr,
-                                /*is_webauthn_form=*/true));
+  EXPECT_TRUE(controller().Show(
+      web_authn_cred_man_delegate(), std::move(filler),
+      /*frame_driver=*/nullptr,
+      /*is_webauthn_form=*/true, CredManController::PasskeyDelayCallback()));
 
   ON_CALL(last_filler(), ShouldTriggerSubmission()).WillByDefault(Return(true));
   EXPECT_CALL(last_filler(), FillUsernameAndPassword(kUsername, kPassword, _))
@@ -194,10 +195,10 @@
   web_authn_cred_man_delegate()->OnCredManConditionalRequestPending(
       /*has_results=*/true, mock_full_assertion_request.Get());
 
-  ASSERT_TRUE(controller().Show(web_authn_cred_man_delegate(),
-                                std::move(filler),
-                                /*frame_driver=*/nullptr,
-                                /*is_webauthn_form=*/true));
+  ASSERT_TRUE(controller().Show(
+      web_authn_cred_man_delegate(), std::move(filler),
+      /*frame_driver=*/nullptr,
+      /*is_webauthn_form=*/true, CredManController::PasskeyDelayCallback()));
 
   ON_CALL(*password_client(), IsReauthBeforeFillingRequired)
       .WillByDefault(Return(true));
@@ -219,10 +220,10 @@
   web_authn_cred_man_delegate()->OnCredManConditionalRequestPending(
       /*has_results=*/true, mock_full_assertion_request.Get());
 
-  ASSERT_TRUE(controller().Show(web_authn_cred_man_delegate(),
-                                std::move(filler),
-                                /*frame_driver=*/nullptr,
-                                /*is_webauthn_form=*/true));
+  ASSERT_TRUE(controller().Show(
+      web_authn_cred_man_delegate(), std::move(filler),
+      /*frame_driver=*/nullptr,
+      /*is_webauthn_form=*/true, CredManController::PasskeyDelayCallback()));
 
   ON_CALL(*password_client(), IsReauthBeforeFillingRequired)
       .WillByDefault(Return(true));
@@ -232,4 +233,58 @@
   web_authn_cred_man_delegate()->FillUsernameAndPassword(kUsername, kPassword);
 }
 
+TEST_F(CredManControllerTest, InvokeDelayCallbackWhenNotReady) {
+  bool delay_callback_invoked = false;
+  bool credential_availability_notified = false;
+  CredManController::PasskeyDelayCallback delay_callback =
+      base::BindLambdaForTesting(
+          [&delay_callback_invoked, &credential_availability_notified](
+              base::OnceCallback<void(base::OnceClosure)> callback) {
+            delay_callback_invoked = true;
+            base::OnceClosure notification_closure = base::BindLambdaForTesting(
+                [&credential_availability_notified]() {
+                  credential_availability_notified = true;
+                });
+            std::move(callback).Run(std::move(notification_closure));
+          });
+
+  EXPECT_CALL(visibility_controller(), SetVisible).Times(0);
+  EXPECT_TRUE(controller().Show(web_authn_cred_man_delegate(), PrepareFiller(),
+                                /*frame_driver=*/nullptr,
+                                /*is_webauthn_form=*/true,
+                                std::move(delay_callback)));
+  EXPECT_TRUE(delay_callback_invoked);
+
+  base::MockCallback<base::RepeatingCallback<void(bool)>>
+      mock_full_assertion_request;
+  web_authn_cred_man_delegate()->OnCredManConditionalRequestPending(
+      /*has_results=*/true, mock_full_assertion_request.Get());
+
+  EXPECT_TRUE(credential_availability_notified);
+}
+
+// Verify that if a passkey list has been received, but it is empty, the
+// `delay_callback` is not invoked, even when it is present.
+TEST_F(CredManControllerTest, DelayCallbackNotInvokedForEmptyPasskeyList) {
+  bool delay_callback_invoked = false;
+  CredManController::PasskeyDelayCallback delay_callback =
+      base::BindLambdaForTesting(
+          [&delay_callback_invoked](
+              base::OnceCallback<void(base::OnceClosure)> callback) {
+            delay_callback_invoked = true;
+          });
+
+  base::MockCallback<base::RepeatingCallback<void(bool)>>
+      mock_full_assertion_request;
+  web_authn_cred_man_delegate()->OnCredManConditionalRequestPending(
+      /*has_results=*/false, mock_full_assertion_request.Get());
+
+  EXPECT_CALL(visibility_controller(), SetVisible).Times(0);
+  EXPECT_FALSE(controller().Show(web_authn_cred_man_delegate(), PrepareFiller(),
+                                 /*frame_driver=*/nullptr,
+                                 /*is_webauthn_form=*/true,
+                                 std::move(delay_callback)));
+  EXPECT_FALSE(delay_callback_invoked);
+}
+
 }  // namespace password_manager
diff --git a/chrome/browser/password_manager/chrome_password_manager_client.cc b/chrome/browser/password_manager/chrome_password_manager_client.cc
index 1f0b72b..1d7f622b 100644
--- a/chrome/browser/password_manager/chrome_password_manager_client.cc
+++ b/chrome/browser/password_manager/chrome_password_manager_client.cc
@@ -62,7 +62,6 @@
 #include "components/autofill/core/common/autofill_util.h"
 #include "components/autofill/core/common/mojom/autofill_types.mojom-shared.h"
 #include "components/autofill/core/common/password_generation_util.h"
-#include "components/back_forward_cache/back_forward_cache_disable.h"
 #include "components/browsing_data/content/browsing_data_helper.h"
 #include "components/device_reauth/device_authenticator.h"
 #include "components/feature_engagement/public/feature_constants.h"
@@ -73,8 +72,8 @@
 #include "components/password_manager/content/browser/form_meta_data.h"
 #include "components/password_manager/content/browser/password_manager_log_router_factory.h"
 #include "components/password_manager/content/browser/password_requirements_service_factory.h"
-#include "components/password_manager/core/browser/browser_credential_manager_factory.h"
 #include "components/password_manager/core/browser/browser_save_password_progress_logger.h"
+#include "components/password_manager/core/browser/credential_manager_impl.h"
 #include "components/password_manager/core/browser/features/password_features.h"
 #include "components/password_manager/core/browser/hsts_query.h"
 #include "components/password_manager/core/browser/http_auth_manager.h"
@@ -161,7 +160,6 @@
 #include "chrome/browser/password_manager/android/password_manager_ui_util_android.h"
 #include "chrome/browser/password_manager/android/password_manager_util_bridge.h"
 #include "chrome/browser/touch_to_fill/password_manager/password_generation/android/touch_to_fill_password_generation_controller.h"
-#include "chrome/browser/touch_to_fill/password_manager/touch_to_fill_controller.h"
 #include "chrome/browser/touch_to_fill/password_manager/touch_to_fill_controller_autofill_delegate.h"
 #include "components/password_manager/content/browser/keyboard_replacing_surface_visibility_controller_impl.h"
 #include "components/password_manager/core/browser/credential_cache.h"
@@ -475,6 +473,10 @@
     autofill::FieldRendererId focused_field_id,
     autofill::mojom::FocusedFieldType focused_field_type) {
 #if BUILDFLAG(IS_ANDROID)
+  // If there was a timer waiting for passkeys before showing a bottom sheet,
+  // cancel it because the original element is no longer focused.
+  wait_for_passkeys_timer_.Stop();
+
   // Suppress keyboard accessory if password store is not available.
   if (GetProfilePasswordStore() == nullptr) {
     return;
@@ -574,6 +576,55 @@
   }
 }
 
+password_manager::CredManController::PasskeyDelayCallback
+ChromePasswordManagerClient::GetPasskeyDelayCallback(
+    base::OnceClosure continue_closure) {
+  return base::BindOnce(
+      [](base::WeakPtr<ChromePasswordManagerClient> client,
+         base::OnceClosure continue_closure,
+         base::OnceCallback<void(base::OnceClosure)>
+             request_notification_callback) {
+        if (client && !client->wait_for_passkeys_timer_.IsRunning()) {
+          // The callback has to be split because there are two ways for this
+          // to resolve:
+          // 1. It times out, in which case the closure passed to the
+          //    `timer_` fires, resuming the attempt to show the sheet.
+          // 2. Passkeys become available before the timer expires.
+          //    In this case the second callback gets invoked, and cancels
+          //    the timer.
+          auto split_closures =
+              base::SplitOnceCallback(std::move(continue_closure));
+
+          client->wait_for_passkeys_timer_.Start(
+              FROM_HERE,
+              base::Milliseconds(password_manager::features::
+                                     kDelaySuggestionsOnAutofocusTimeout.Get()),
+              std::move(split_closures.first));
+
+          // If passkeys become available before the timer expires, this
+          // closure checks if the timer is still running. If so, it triggers
+          // the bottom sheet to show, and cancels the timer so there won't
+          // be a second attempt to show it.
+          base::OnceClosure passkeys_available_callback = base::BindOnce(
+              [](base::WeakPtr<ChromePasswordManagerClient> client,
+                 base::OnceClosure continue_closure) {
+                if (!client) {
+                  return;
+                }
+                if (client->wait_for_passkeys_timer_.IsRunning()) {
+                  client->wait_for_passkeys_timer_.Stop();
+                  std::move(continue_closure).Run();
+                }
+              },
+              client, std::move(split_closures.second));
+
+          std::move(request_notification_callback)
+              .Run(std::move(passkeys_available_callback));
+        }
+      },
+      weak_ptr_factory_.GetWeakPtr(), std::move(continue_closure));
+}
+
 void ChromePasswordManagerClient::ShowKeyboardReplacingSurface(
     password_manager::PasswordManagerDriver* driver,
     const autofill::PasswordSuggestionRequest& request) {
@@ -589,24 +640,61 @@
     return;
   }
 
+  password_manager::CredManController::PasskeyDelayCallback delay_callback;
+  if (base::FeatureList::IsEnabled(
+          password_manager::features::
+              kDelaySuggestionsOnAutofocusWaitingForPasskeys) &&
+      request.field.trigger_source ==
+          autofill::AutofillSuggestionTriggerSource::
+              kPasswordManagerProcessedFocusedField) {
+    // A null PasskeyDelayCallback is bound to `continue_closure` to prevent
+    // it from delaying again, even if the passkey list has not yet arrived.
+    auto continue_closure = base::BindOnce(
+        &ChromePasswordManagerClient::ContinueShowKeyboardReplacingSurface,
+        weak_ptr_factory_.GetWeakPtr(), driver->AsWeakPtr(), request,
+        password_manager::CredManController::PasskeyDelayCallback());
+    delay_callback = GetPasskeyDelayCallback(std::move(continue_closure));
+  } else if (wait_for_passkeys_timer_.IsRunning()) {
+    // If there was an attempt to show the new surface with a different trigger
+    // source while waiting for passkey enumeration, stop the timer and
+    // proceed without waiting.
+    wait_for_passkeys_timer_.Stop();
+  }
+
+  ContinueShowKeyboardReplacingSurface(driver->AsWeakPtr(), request,
+                                       std::move(delay_callback));
+}
+
+void ChromePasswordManagerClient::ContinueShowKeyboardReplacingSurface(
+    base::WeakPtr<password_manager::PasswordManagerDriver> weak_driver,
+    const autofill::PasswordSuggestionRequest& request,
+    password_manager::CredManController::PasskeyDelayCallback delay_callback) {
+  // The delay callback gets split because one instance has to be passed to the
+  // CredMan controller. If CredMan will not be used, that instance is destroyed
+  // without being called.
+  auto split_delay_callback =
+      base::SplitOnceCallback(std::move(delay_callback));
+  password_manager::ContentPasswordManagerDriver* content_driver =
+      static_cast<password_manager::ContentPasswordManagerDriver*>(
+          weak_driver.get());
   if (GetOrCreateCredManController()->Show(
-          GetWebAuthnCredManDelegateForDriver(driver),
-          std::make_unique<PasswordCredentialFillerImpl>(driver->AsWeakPtr(),
-                                                         request),
+          GetWebAuthnCredManDelegateForDriver(weak_driver.get()),
+          std::make_unique<PasswordCredentialFillerImpl>(weak_driver, request),
           content_driver->AsWeakPtrImpl(),
-          request.field.show_webauthn_credentials)) {
+          request.field.show_webauthn_credentials,
+          std::move(split_delay_callback.first))) {
     return;
   }
 
   // base::Unretained() is safe: if the callback is called, AccountStorageNotice
   // is alive, then so is its parent ChromePasswordManagerClient.
-  MaybeShowAccountStorageNotice(
-      base::BindOnce(&ChromePasswordManagerClient::
-                         ShowKeyboardReplacingSurfaceOnAccountStorageNoticeDone,
-                     base::Unretained(this), content_driver->AsWeakPtrImpl(),
-                     request.field,  // Intentional & cheap copy.
-                     std::make_unique<PasswordCredentialFillerImpl>(
-                         driver->AsWeakPtr(), request)));
+  MaybeShowAccountStorageNotice(base::BindOnce(
+      &ChromePasswordManagerClient::
+          ShowKeyboardReplacingSurfaceOnAccountStorageNoticeDone,
+      base::Unretained(this), content_driver->AsWeakPtrImpl(),
+      request.field,  // Intentional & cheap copy.
+      std::make_unique<PasswordCredentialFillerImpl>(weak_driver, request),
+      std::move(split_delay_callback.second)));
 }
 
 void ChromePasswordManagerClient::
@@ -614,7 +702,9 @@
         base::WeakPtr<password_manager::ContentPasswordManagerDriver>
             weak_driver,
         autofill::TriggeringField triggering_field,
-        std::unique_ptr<PasswordCredentialFillerImpl> filler) {
+        std::unique_ptr<PasswordCredentialFillerImpl> filler,
+        password_manager::CredManController::PasskeyDelayCallback
+            delay_callback) {
   // TODO(crbug.com/346748438): Maybe don't show TTF if there was a navigation.
   if (!weak_driver) {
     // No further suggestions are possible: without the driver, there is no
@@ -628,12 +718,25 @@
   bool should_show_hybrid_option = false;
   if (webauthn_delegate) {
     webauthn_delegate->NotifyForPasskeysDisplay();
-    if (webauthn_delegate->GetPasskeys().has_value()) {
-      passkeys = *webauthn_delegate->GetPasskeys().value();
+    auto maybe_passkeys = webauthn_delegate->GetPasskeys();
+    if (maybe_passkeys.has_value()) {
+      passkeys = *maybe_passkeys.value();
       should_show_hybrid_option =
           webauthn_delegate->IsSecurityKeyOrHybridFlowAvailable();
+    } else if (!delay_callback.is_null() &&
+               maybe_passkeys.error() ==
+                   password_manager::WebAuthnCredentialsDelegate::
+                       PasskeysUnavailableReason::kNotReceived) {
+      base::OnceCallback<void(base::OnceClosure)> notification_callback =
+          base::BindOnce(&password_manager::WebAuthnCredentialsDelegate::
+                             RequestNotificationWhenPasskeysReady,
+                         webauthn_delegate->AsWeakPtr());
+
+      std::move(delay_callback).Run(std::move(notification_callback));
+      return;
     }
   }
+
   const PasswordForm* form_to_fill = password_manager_.GetParsedObservedForm(
       driver, triggering_field.element_id);
   auto ttf_controller_autofill_delegate =
@@ -1707,59 +1810,6 @@
 }
 
 // static
-void ChromePasswordManagerClient::BindCredentialManager(
-    content::RenderFrameHost* render_frame_host,
-    mojo::PendingReceiver<blink::mojom::CredentialManager> receiver) {
-  // Only valid for the main frame.
-  if (render_frame_host->GetParent()) {
-    return;
-  }
-
-  content::WebContents* web_contents =
-      content::WebContents::FromRenderFrameHost(render_frame_host);
-  DCHECK(web_contents);
-
-  // Only valid for the currently committed RenderFrameHost, and not, e.g. old
-  // zombie RFH's being swapped out following cross-origin navigations.
-  if (web_contents->GetPrimaryMainFrame() != render_frame_host) {
-    return;
-  }
-
-  // The ChromePasswordManagerClient will not bind the mojo interface for
-  // non-primary frames, e.g. BackForwardCache, Prerenderer, since the
-  // MojoBinderPolicy prevents this interface from being granted.
-  DCHECK_EQ(render_frame_host->GetLifecycleState(),
-            content::RenderFrameHost::LifecycleState::kActive);
-
-  ChromePasswordManagerClient* instance =
-      ChromePasswordManagerClient::FromWebContents(web_contents);
-
-  // Try to bind to the driver, but if driver is not available for this render
-  // frame host, the request will be just dropped. This will cause the message
-  // pipe to be closed, which will raise a connection error on the peer side.
-  if (!instance) {
-    return;
-  }
-
-  // Disable BackForwardCache for this page.
-  // This is necessary because ContentCredentialManager::DisconnectBinding()
-  // will be called when the page is navigated away from, leaving it
-  // in an unusable state if the page is restored from the BackForwardCache.
-  //
-  // It looks like in order to remove this workaround, we probably just need to
-  // make the CredentialManager mojo API rebind on the renderer side when the
-  // next call is made, if it has become disconnected.
-  // TODO(crbug.com/40653684): Remove this workaround.
-  content::BackForwardCache::DisableForRenderFrameHost(
-      render_frame_host,
-      back_forward_cache::DisabledReason(
-          back_forward_cache::DisabledReasonId::
-              kChromePasswordManagerClient_BindCredentialManager));
-
-  instance->content_credential_manager_.BindRequest(std::move(receiver));
-}
-
-// static
 bool ChromePasswordManagerClient::CanShowBubbleOnURL(const GURL& url) {
   std::string scheme = url.scheme();
   return (content::ChildProcessSecurityPolicy::GetInstance()->IsWebSafeScheme(
@@ -1835,6 +1885,11 @@
 }
 #endif  // BUILDFLAG(IS_ANDROID)
 
+credential_management::ContentCredentialManager*
+ChromePasswordManagerClient::GetContentCredentialManager() {
+  return &content_credential_manager_;
+}
+
 ChromePasswordManagerClient::ChromePasswordManagerClient(
     content::WebContents* web_contents)
     : content::WebContentsObserver(web_contents),
@@ -1847,8 +1902,7 @@
       httpauth_manager_(this),
       otp_manager_(this),
       content_credential_manager_(
-          password_manager::BrowserCredentialManagerFactory(this)
-              .CreateCredentialManager()),
+          std::make_unique<password_manager::CredentialManagerImpl>(this)),
       password_generation_driver_receivers_(web_contents, this),
       observer_(nullptr),
 #if BUILDFLAG(ENABLE_DICE_SUPPORT)
diff --git a/chrome/browser/password_manager/chrome_password_manager_client.h b/chrome/browser/password_manager/chrome_password_manager_client.h
index 5e2b0af3..0172b3d8 100644
--- a/chrome/browser/password_manager/chrome_password_manager_client.h
+++ b/chrome/browser/password_manager/chrome_password_manager_client.h
@@ -47,12 +47,15 @@
 #include "url/origin.h"
 
 #if BUILDFLAG(IS_ANDROID)
+#include "base/timer/timer.h"
 #include "chrome/browser/password_manager/android/account_storage_notice/account_storage_notice.h"
 #include "chrome/browser/password_manager/android/cct_password_saving_metrics_recorder_bridge.h"
+#include "chrome/browser/password_manager/android/cred_man_controller.h"
 #include "chrome/browser/password_manager/android/generated_password_saved_message_delegate.h"
 #include "chrome/browser/password_manager/android/password_access_loss_warning_startup_launcher.h"
 #include "chrome/browser/password_manager/android/password_manager_error_message_delegate.h"
 #include "chrome/browser/password_manager/android/save_update_password_message_delegate.h"
+#include "chrome/browser/touch_to_fill/password_manager/touch_to_fill_controller.h"
 #include "components/enterprise/connectors/core/features.h"
 #include "components/password_manager/core/browser/credential_cache.h"
 #include "components/password_manager/core/browser/first_cct_page_load_passwords_ukm_recorder.h"
@@ -71,7 +74,6 @@
 #if BUILDFLAG(IS_ANDROID)
 class AcknowledgeGroupedCredentialSheetController;
 class PasswordAccessoryController;
-class TouchToFillController;
 #else
 class PasswordCrossDomainConfirmationPopupControllerImpl;
 #endif
@@ -362,10 +364,6 @@
   // Observer for PasswordGenerationPopup events. Used for testing.
   void SetTestObserver(PasswordGenerationPopupObserver* observer);
 
-  static void BindCredentialManager(
-      content::RenderFrameHost* render_frame_host,
-      mojo::PendingReceiver<blink::mojom::CredentialManager> receiver);
-
   // A helper method to determine whether a save/update bubble can be shown
   // on this |url|.
   static bool CanShowBubbleOnURL(const GURL& url);
@@ -384,7 +382,14 @@
     password_generation_driver_receivers_.SetCurrentTargetFrameForTesting(
         render_frame_host);
   }
-#endif
+#if BUILDFLAG(IS_ANDROID)
+  void SetTouchToFillControllerForTesting(
+      std::unique_ptr<TouchToFillController> controller) {
+    touch_to_fill_controller_ = std::move(controller);
+  }
+
+#endif  // BUILDFLAG(IS_ANDROID)
+#endif  // defined(UNIT_TEST)
 
   void set_cross_domain_confirmation_popup_factory_for_testing(
       CrossDomainConfirmationPopupFactory factory) {
@@ -399,6 +404,9 @@
   }
 #endif
 
+  credential_management::ContentCredentialManager*
+  GetContentCredentialManager();
+
  protected:
   // Callable for tests.
   explicit ChromePasswordManagerClient(content::WebContents* web_contents);
@@ -411,10 +419,16 @@
 
   void MaybeShowAccountStorageNotice(base::OnceClosure callback);
 
+  void ContinueShowKeyboardReplacingSurface(
+      base::WeakPtr<password_manager::PasswordManagerDriver> weak_driver,
+      const autofill::PasswordSuggestionRequest& request,
+      password_manager::CredManController::PasskeyDelayCallback delay_callback);
+
   void ShowKeyboardReplacingSurfaceOnAccountStorageNoticeDone(
       base::WeakPtr<password_manager::ContentPasswordManagerDriver> weak_driver,
       autofill::TriggeringField triggering_field,
-      std::unique_ptr<password_manager::PasswordCredentialFillerImpl> filler);
+      std::unique_ptr<password_manager::PasswordCredentialFillerImpl> filler,
+      password_manager::CredManController::PasskeyDelayCallback delay_callback);
 #endif
 
   // content::WebContentsObserver overrides.
@@ -482,6 +496,15 @@
 
   base::WeakPtr<password_manager::KeyboardReplacingSurfaceVisibilityController>
   GetOrCreateKeyboardReplacingSurfaceVisibilityController();
+
+  // Returns a callback that should be invoked if passkeys are not available
+  // and we need to delay showing a bottom sheet. `continue_closure` will be
+  // invoked when passkeys arrive, or the wait times out.
+  // The returned callback must be called with the method that registers a
+  // listener for the arrival of a passkey list. The listening registration
+  // method is different depending on whether CredMan is being used.
+  password_manager::CredManController::PasskeyDelayCallback
+  GetPasskeyDelayCallback(base::OnceClosure continue_closure);
 #endif
 
   autofill::LogManager* GetOrCreateLogManager() const;
@@ -581,6 +604,10 @@
   std::unique_ptr<CctPasswordSavingMetricsRecorderBridge>
       cct_saving_metrics_recorder_bridge_;
 
+  // This timer is used to delay showing the Touch To Fill or CredMan sheets if
+  // passkey suggestions are allowed but the passkey list has not yet arrived.
+  base::OneShotTimer wait_for_passkeys_timer_;
+
 #endif  // BUILDFLAG(IS_ANDROID)
 
   // Observes `AutofillManager`s of the `WebContents` that `this` belongs to.
@@ -592,6 +619,8 @@
   CrossDomainConfirmationPopupFactory
       cross_domain_confirmation_popup_factory_for_testing_;
 
+  base::WeakPtrFactory<ChromePasswordManagerClient> weak_ptr_factory_{this};
+
   WEB_CONTENTS_USER_DATA_KEY_DECL();
 };
 
diff --git a/chrome/browser/password_manager/chrome_password_manager_client_unittest.cc b/chrome/browser/password_manager/chrome_password_manager_client_unittest.cc
index 26a48557..a74ba79 100644
--- a/chrome/browser/password_manager/chrome_password_manager_client_unittest.cc
+++ b/chrome/browser/password_manager/chrome_password_manager_client_unittest.cc
@@ -103,6 +103,7 @@
 
 #if BUILDFLAG(IS_ANDROID)
 #include "base/android/build_info.h"
+#include "base/i18n/rtl.h"
 #include "chrome/browser/autofill/mock_manual_filling_view.h"
 #include "chrome/browser/keyboard_accessory/android/manual_filling_controller_impl.h"
 #include "chrome/browser/keyboard_accessory/android/password_accessory_controller_impl.h"
@@ -113,6 +114,14 @@
 #include "chrome/browser/password_manager/account_password_store_factory.h"
 #include "chrome/browser/password_manager/android/grouped_affiliations/acknowledge_grouped_credential_sheet_controller_test_helper.h"
 #include "chrome/browser/password_manager/android/password_generation_controller.h"
+#include "chrome/browser/password_manager/chrome_webauthn_credentials_delegate.h"
+#include "chrome/browser/password_manager/chrome_webauthn_credentials_delegate_factory.h"
+#include "chrome/browser/touch_to_fill/password_manager/touch_to_fill_controller.h"
+#include "chrome/browser/touch_to_fill/password_manager/touch_to_fill_controller_delegate.h"
+#include "components/password_manager/content/browser/mock_keyboard_replacing_surface_visibility_controller.h"
+#include "components/password_manager/core/browser/passkey_credential.h"
+#include "components/webauthn/android/cred_man_support.h"
+#include "components/webauthn/android/webauthn_cred_man_delegate.h"
 #else
 #include "chrome/browser/ui/hats/hats_service_factory.h"
 #include "chrome/browser/ui/hats/mock_hats_service.h"
@@ -140,6 +149,8 @@
 using sessions::GetPasswordStateFromNavigation;
 using sessions::SerializedNavigationEntry;
 using testing::_;
+using testing::Eq;
+using testing::Invoke;
 using testing::Key;
 using testing::NiceMock;
 using testing::Return;
@@ -385,6 +396,31 @@
   return std::make_unique<MockPasswordChangeService>();
 }
 
+#if BUILDFLAG(IS_ANDROID)
+class MockTouchToFillController : public TouchToFillController {
+ public:
+  MockTouchToFillController(
+      Profile* profile,
+      base::WeakPtr<
+          password_manager::KeyboardReplacingSurfaceVisibilityController>
+          visibility_controller)
+      : TouchToFillController(profile, visibility_controller, nullptr) {}
+
+  MOCK_METHOD(void,
+              InitData,
+              (base::span<const password_manager::UiCredential>,
+               std::vector<password_manager::PasskeyCredential>,
+               base::WeakPtr<password_manager::ContentPasswordManagerDriver>),
+              (override));
+
+  MOCK_METHOD(bool,
+              Show,
+              (std::unique_ptr<TouchToFillControllerDelegate>,
+               webauthn::WebAuthnCredManDelegate*),
+              (override));
+};
+#endif  // BUILDFLAG_IS_ANDROID)
+
 }  // namespace
 
 class ChromePasswordManagerClientTest : public ChromeRenderViewHostTestHarness {
@@ -1318,23 +1354,6 @@
       GetPasswordStateFromNavigation(controller().GetLastCommittedEntry()));
 }
 
-// Handle missing ChromePasswordManagerClient instance in BindCredentialManager
-// gracefully.
-TEST_F(ChromePasswordManagerClientTest, BindCredentialManager_MissingInstance) {
-  // Create a WebContent without tab helpers.
-  std::unique_ptr<content::WebContents> web_contents =
-      content::WebContents::Create(
-          content::WebContents::CreateParams(profile()));
-  // In particular, this WebContent should not have the
-  // ChromePasswordManagerClient.
-  ASSERT_FALSE(
-      ChromePasswordManagerClient::FromWebContents(web_contents.get()));
-
-  // This call should not crash.
-  ChromePasswordManagerClient::BindCredentialManager(
-      web_contents->GetPrimaryMainFrame(), mojo::NullReceiver());
-}
-
 TEST_F(ChromePasswordManagerClientTest, CanShowBubbleOnURL) {
   struct TestCase {
     const char* scheme;
@@ -1483,11 +1502,38 @@
     task_environment()->AdvanceClock(delta);
   }
 
+  MockTouchToFillController* MakeMockTouchToFillController() {
+    visibility_controller_ = std::make_unique<
+        password_manager::MockKeyboardReplacingSurfaceVisibilityController>();
+    auto owned_ttf_controller = std::make_unique<MockTouchToFillController>(
+        profile(), visibility_controller_->AsWeakPtr());
+    auto* ttf_controller = owned_ttf_controller.get();
+    GetClient()->SetTouchToFillControllerForTesting(
+        std::move(owned_ttf_controller));
+    return ttf_controller;
+  }
+
+  autofill::PasswordSuggestionRequest GetFocusedFieldSuggestionRequest(
+      const FormData& form) {
+    return autofill::PasswordSuggestionRequest(
+        autofill::TriggeringField(form.fields()[0].renderer_id(),
+                                  autofill::AutofillSuggestionTriggerSource::
+                                      kPasswordManagerProcessedFocusedField,
+                                  base::i18n::LEFT_TO_RIGHT, u"",
+                                  /*show_webauthn_credentials=*/true,
+                                  /*show_identity_credentials=*/false,
+                                  gfx::RectF()),
+        form, /*username_field_index=*/0, /*password_field_index=*/1);
+  }
+
  private:
   NiceMock<MockPasswordAccessoryController> mock_pwd_controller_;
   NiceMock<MockAddressAccessoryController> mock_address_controller_;
   NiceMock<MockPaymentMethodAccessoryController>
       mock_payment_method_controller_;
+  std::unique_ptr<
+      password_manager::MockKeyboardReplacingSurfaceVisibilityController>
+      visibility_controller_;
 };
 
 void ChromePasswordManagerClientAndroidTest::SetUp() {
@@ -2119,3 +2165,169 @@
       &Observer::OnFieldTypesDetermined, form.global_id(),
       Observer::FieldTypeSource::kHeuristicsOrAutocomplete);
 }
+
+#if BUILDFLAG(IS_ANDROID)
+TEST_F(ChromePasswordManagerClientAndroidTest,
+       DelaySuggestionsSheetWhenPasskeysPending) {
+  webauthn::WebAuthnCredManDelegate::override_cred_man_support_for_testing(
+      webauthn::CredManSupport::DISABLED);
+  base::test::ScopedFeatureList features(
+      password_manager::features::
+          kDelaySuggestionsOnAutofocusWaitingForPasskeys);
+  CreateManualFillingController(web_contents());
+
+  auto* ttf_controller = MakeMockTouchToFillController();
+
+  EXPECT_CALL(*ttf_controller, InitData).Times(0);
+  EXPECT_CALL(*ttf_controller, Show).Times(0);
+
+  constexpr char kUrl[] = "https://www.foo.com/login.html";
+  NavigateAndCommit(GURL(kUrl));
+  ContentAutofillDriver* autofill_driver =
+      ContentAutofillDriver::GetForRenderFrameHost(main_rfh());
+  ASSERT_TRUE(autofill_driver);
+
+  std::vector<FormFieldData> fields = {CreateTestFormField(
+      "Username:", "username", "", FormControlType::kInputText, "webauthn")};
+  FormData form =
+      CreateFormDataForRenderFrameHost(*main_rfh(), std::move(fields));
+  {
+    autofill::TestAutofillManagerWaiter waiter(
+        autofill_driver->GetAutofillManager(),
+        {autofill::AutofillManagerEvent::kFormsSeen});
+    autofill_driver->renderer_events().FormsSeen(/*updated_forms=*/{form},
+                                                 /*removed_forms=*/{});
+    ASSERT_TRUE(waiter.Wait(/*num_awaiting_calls=*/1));
+  }
+
+  GetClient()->ShowKeyboardReplacingSurface(
+      ContentPasswordManagerDriver::GetForRenderFrameHost(main_rfh()),
+      GetFocusedFieldSuggestionRequest(form));
+
+  base::RunLoop().RunUntilIdle();
+
+  std::vector<password_manager::PasskeyCredential> credentials{};
+  EXPECT_CALL(*ttf_controller, InitData(_, Eq(credentials), _));
+  EXPECT_CALL(*ttf_controller, Show).WillOnce(Return(true));
+
+  // Simulate an empty passkey list being provided.
+  ChromeWebAuthnCredentialsDelegateFactory::GetFactory(web_contents())
+      ->GetDelegateForFrame(main_rfh())
+      ->OnCredentialsReceived(
+          credentials,
+          ChromeWebAuthnCredentialsDelegate::SecurityKeyOrHybridFlowAvailable(
+              true));
+}
+
+TEST_F(ChromePasswordManagerClientAndroidTest,
+       DelaySuggestionsSheetWhenPasskeysPendingTimeOut) {
+  webauthn::WebAuthnCredManDelegate::override_cred_man_support_for_testing(
+      webauthn::CredManSupport::DISABLED);
+  base::test::ScopedFeatureList features(
+      password_manager::features::
+          kDelaySuggestionsOnAutofocusWaitingForPasskeys);
+  CreateManualFillingController(web_contents());
+
+  auto* ttf_controller = MakeMockTouchToFillController();
+  EXPECT_CALL(*ttf_controller, InitData).Times(0);
+  EXPECT_CALL(*ttf_controller, Show).Times(0);
+
+  constexpr char kUrl[] = "https://www.foo.com/login.html";
+  NavigateAndCommit(GURL(kUrl));
+  ContentAutofillDriver* autofill_driver =
+      ContentAutofillDriver::GetForRenderFrameHost(main_rfh());
+  ASSERT_TRUE(autofill_driver);
+
+  std::vector<FormFieldData> fields = {CreateTestFormField(
+      "Username:", "username", "", FormControlType::kInputText, "webauthn")};
+  FormData form =
+      CreateFormDataForRenderFrameHost(*main_rfh(), std::move(fields));
+  {
+    autofill::TestAutofillManagerWaiter waiter(
+        autofill_driver->GetAutofillManager(),
+        {autofill::AutofillManagerEvent::kFormsSeen});
+    autofill_driver->renderer_events().FormsSeen(/*updated_forms=*/{form},
+                                                 /*removed_forms=*/{});
+    ASSERT_TRUE(waiter.Wait(/*num_awaiting_calls=*/1));
+  }
+
+  GetClient()->ShowKeyboardReplacingSurface(
+      ContentPasswordManagerDriver::GetForRenderFrameHost(main_rfh()),
+      GetFocusedFieldSuggestionRequest(form));
+
+  base::RunLoop().RunUntilIdle();
+
+  base::RunLoop waiter;
+  std::vector<password_manager::PasskeyCredential> credentials{};
+  EXPECT_CALL(*ttf_controller, InitData(_, Eq(credentials), _));
+  EXPECT_CALL(*ttf_controller, Show)
+      .WillOnce(Invoke([&waiter](std::unique_ptr<TouchToFillControllerDelegate>,
+                                 webauthn::WebAuthnCredManDelegate*) {
+        waiter.Quit();
+        return true;
+      }));
+
+  // Simulate a timeout.
+  AdvanceClock(base::Seconds(5));
+  waiter.Run();
+
+  EXPECT_CALL(*ttf_controller, InitData).Times(0);
+  EXPECT_CALL(*ttf_controller, Show).Times(0);
+
+  // Simulate an empty passkey list being provided. Nothing should happen
+  // because the timer has already expired.
+  ChromeWebAuthnCredentialsDelegateFactory::GetFactory(web_contents())
+      ->GetDelegateForFrame(main_rfh())
+      ->OnCredentialsReceived(
+          credentials,
+          ChromeWebAuthnCredentialsDelegate::SecurityKeyOrHybridFlowAvailable(
+              true));
+}
+
+TEST_F(ChromePasswordManagerClientAndroidTest,
+       DelaySuggestionsSheetWhenPasskeysTimerCancelledWhenFocusLost) {
+  webauthn::WebAuthnCredManDelegate::override_cred_man_support_for_testing(
+      webauthn::CredManSupport::DISABLED);
+  base::test::ScopedFeatureList features(
+      password_manager::features::
+          kDelaySuggestionsOnAutofocusWaitingForPasskeys);
+  CreateManualFillingController(web_contents());
+
+  auto* ttf_controller = MakeMockTouchToFillController();
+  EXPECT_CALL(*ttf_controller, InitData).Times(0);
+  EXPECT_CALL(*ttf_controller, Show).Times(0);
+
+  constexpr char kUrl[] = "https://www.foo.com/login.html";
+  NavigateAndCommit(GURL(kUrl));
+  ContentAutofillDriver* autofill_driver =
+      ContentAutofillDriver::GetForRenderFrameHost(main_rfh());
+  ASSERT_TRUE(autofill_driver);
+
+  std::vector<FormFieldData> fields = {CreateTestFormField(
+      "Username:", "username", "", FormControlType::kInputText, "webauthn")};
+  FormData form =
+      CreateFormDataForRenderFrameHost(*main_rfh(), std::move(fields));
+  {
+    autofill::TestAutofillManagerWaiter waiter(
+        autofill_driver->GetAutofillManager(),
+        {autofill::AutofillManagerEvent::kFormsSeen});
+    autofill_driver->renderer_events().FormsSeen(/*updated_forms=*/{form},
+                                                 /*removed_forms=*/{});
+    ASSERT_TRUE(waiter.Wait(/*num_awaiting_calls=*/1));
+  }
+
+  auto* driver =
+      ContentPasswordManagerDriver::GetForRenderFrameHost(main_rfh());
+  GetClient()->ShowKeyboardReplacingSurface(
+      driver, GetFocusedFieldSuggestionRequest(form));
+
+  GetClient()->FocusedInputChanged(driver, FieldRendererId(123),
+                                   FocusedFieldType::kFillablePasswordField);
+
+  // Simulate a timeout. The timer should be cancelled so TTF methods should not
+  // be called.
+  AdvanceClock(base::Seconds(5));
+  base::RunLoop().RunUntilIdle();
+}
+
+#endif  // BUILDFLAG(IS_ANDROID)
diff --git a/chrome/browser/password_manager/password_change/change_password_form_filling_submission_helper.cc b/chrome/browser/password_manager/password_change/change_password_form_filling_submission_helper.cc
index 479555e..1cc5165c 100644
--- a/chrome/browser/password_manager/password_change/change_password_form_filling_submission_helper.cc
+++ b/chrome/browser/password_manager/password_change/change_password_form_filling_submission_helper.cc
@@ -6,12 +6,28 @@
 
 #include "base/metrics/histogram_functions.h"
 #include "base/task/single_thread_task_runner.h"
+#include "chrome/browser/optimization_guide/optimization_guide_keyed_service.h"
+#include "chrome/browser/optimization_guide/optimization_guide_keyed_service_factory.h"
 #include "chrome/browser/password_manager/password_change/password_change_submission_verifier.h"
 #include "chrome/browser/profiles/profile.h"
+#include "components/optimization_guide/core/model_quality/model_execution_logging_wrappers.h"
 #include "components/password_manager/content/browser/content_password_manager_driver.h"
 #include "content/public/browser/web_contents.h"
 #include "third_party/abseil-cpp/absl/cleanup/cleanup.h"
 
+namespace {
+
+blink::mojom::AIPageContentOptionsPtr GetAIPageContentOptions() {
+  auto options = blink::mojom::AIPageContentOptions::New();
+  // WebContents where password change is happening is hidden, and renderer
+  // won't capture a snapshot unless it becomes visible again or
+  // on_critical_path is set to true.
+  options->on_critical_path = true;
+  return options;
+}
+
+}  // namespace
+
 ChangePasswordFormFillingSubmissionHelper::
     ChangePasswordFormFillingSubmissionHelper(
         content::WebContents* web_contents,
@@ -19,7 +35,25 @@
         base::OnceCallback<void(bool)> callback)
     : web_contents_(web_contents->GetWeakPtr()),
       callback_(std::move(callback)),
-      logs_uploader_(logs_uploader) {}
+      logs_uploader_(logs_uploader) {
+  capture_annotated_page_content_ =
+      base::BindOnce(&optimization_guide::GetAIPageContent, web_contents,
+                     GetAIPageContentOptions());
+}
+
+ChangePasswordFormFillingSubmissionHelper::
+    ChangePasswordFormFillingSubmissionHelper(
+        base::PassKey<class ChangePasswordFormFillingSubmissionHelperTest>,
+        content::WebContents* web_contents,
+        ModelQualityLogsUploader* logs_uploader,
+        base::OnceCallback<void(optimization_guide::OnAIPageContentDone)>
+            capture_annotated_page_content,
+        base::OnceCallback<void(bool)> result_callback)
+    : web_contents_(web_contents->GetWeakPtr()),
+      callback_(std::move(result_callback)),
+      logs_uploader_(logs_uploader),
+      capture_annotated_page_content_(
+          std::move(capture_annotated_page_content)) {}
 
 ChangePasswordFormFillingSubmissionHelper::
     ~ChangePasswordFormFillingSubmissionHelper() = default;
@@ -126,19 +160,71 @@
                      password_manager::PossibleUsernameData>(
           password_manager::kMaxSingleUsernameFieldsToStore));
   driver->SubmitFormWithEnter(
-      field_id, base::BindOnce(
-                    &ChangePasswordFormFillingSubmissionHelper::OnFormSubmitted,
-                    weak_ptr_factory_.GetWeakPtr(), driver));
+      field_id,
+      base::BindOnce(
+          &ChangePasswordFormFillingSubmissionHelper::OnSubmitWithEnterResult,
+          weak_ptr_factory_.GetWeakPtr(), driver));
 }
 
-void ChangePasswordFormFillingSubmissionHelper::OnFormSubmitted(
+void ChangePasswordFormFillingSubmissionHelper::OnSubmitWithEnterResult(
     base::WeakPtr<password_manager::PasswordManagerDriver> driver,
     bool success) {
   if (!success) {
-    // TODO(crbug.com/407487665): Attempt to submit change password form by
-    // looking for a submit button.
+    std::move(capture_annotated_page_content_)
+        .Run(base::BindOnce(
+            &ChangePasswordFormFillingSubmissionHelper::OnPageContentReceived,
+            weak_ptr_factory_.GetWeakPtr()));
     return;
   }
+  OnFormSubmitted();
+}
+
+void ChangePasswordFormFillingSubmissionHelper::OnPageContentReceived(
+    std::optional<optimization_guide::AIPageContentResult> content) {
+  if (!content || !web_contents_) {
+    return;
+  }
+
+  optimization_guide::proto::PasswordChangeRequest request;
+  request.set_step(optimization_guide::proto::PasswordChangeRequest::FlowStep::
+                       PasswordChangeRequest_FlowStep_SUBMIT_FORM_STEP);
+  *request.mutable_page_context()->mutable_annotated_page_content() =
+      std::move(content->proto);
+  optimization_guide::ExecuteModelWithLogging(
+      GetOptimizationService(),
+      optimization_guide::ModelBasedCapabilityKey::kPasswordChangeSubmission,
+      request, /*execution_timeout=*/std::nullopt,
+      base::BindOnce(&ChangePasswordFormFillingSubmissionHelper::
+                         OnExecutionResponseCallback,
+                     weak_ptr_factory_.GetWeakPtr()));
+}
+
+OptimizationGuideKeyedService*
+ChangePasswordFormFillingSubmissionHelper::GetOptimizationService() {
+  return OptimizationGuideKeyedServiceFactory::GetForProfile(
+      Profile::FromBrowserContext(web_contents_->GetBrowserContext()));
+}
+
+void ChangePasswordFormFillingSubmissionHelper::OnExecutionResponseCallback(
+    optimization_guide::OptimizationGuideModelExecutionResult execution_result,
+    std::unique_ptr<
+        optimization_guide::proto::PasswordChangeSubmissionLoggingData>
+        logging_data) {
+  if (!web_contents_ || !execution_result.response.has_value()) {
+    return;
+  }
+  std::optional<optimization_guide::proto::PasswordChangeResponse> response =
+      optimization_guide::ParsedAnyMetadata<
+          optimization_guide::proto::PasswordChangeResponse>(
+          execution_result.response.value());
+  if (!response) {
+    return;
+  }
+
+  // TODO(crbug.com/407487665): Click the button specified in execution_result.
+}
+
+void ChangePasswordFormFillingSubmissionHelper::OnFormSubmitted() {
   submission_verifier_ = std::make_unique<PasswordChangeSubmissionVerifier>(
       web_contents_.get(), logs_uploader_);
 }
diff --git a/chrome/browser/password_manager/password_change/change_password_form_filling_submission_helper.h b/chrome/browser/password_manager/password_change/change_password_form_filling_submission_helper.h
index 55de23a..cfc8eae 100644
--- a/chrome/browser/password_manager/password_change/change_password_form_filling_submission_helper.h
+++ b/chrome/browser/password_manager/password_change/change_password_form_filling_submission_helper.h
@@ -11,7 +11,9 @@
 #include "base/memory/raw_ptr.h"
 #include "base/memory/weak_ptr.h"
 #include "base/timer/timer.h"
+#include "chrome/browser/optimization_guide/optimization_guide_keyed_service.h"
 #include "components/autofill/core/common/form_data.h"
+#include "components/optimization_guide/content/browser/page_content_proto_provider.h"
 #include "components/password_manager/core/browser/password_form.h"
 
 namespace content {
@@ -36,6 +38,15 @@
       content::WebContents* web_contents,
       ModelQualityLogsUploader* logs_uploader,
       base::OnceCallback<void(bool)> result_callback);
+
+  // Test constructor (allows to mock `capture_annotated_page_content`).
+  ChangePasswordFormFillingSubmissionHelper(
+      base::PassKey<class ChangePasswordFormFillingSubmissionHelperTest>,
+      content::WebContents* web_contents,
+      ModelQualityLogsUploader* logs_uploader,
+      base::OnceCallback<void(optimization_guide::OnAIPageContentDone)>
+          capture_annotated_page_content,
+      base::OnceCallback<void(bool)> result_callback);
   ~ChangePasswordFormFillingSubmissionHelper();
 
   // Starts chain of actions:
@@ -75,10 +86,24 @@
       autofill::FieldRendererId field_id,
       const std::optional<autofill::FormData>& submitted_form);
 
-  void OnFormSubmitted(
+  void OnSubmitWithEnterResult(
       base::WeakPtr<password_manager::PasswordManagerDriver> driver,
       bool success);
 
+  void OnPageContentReceived(
+      std::optional<optimization_guide::AIPageContentResult> content);
+
+  OptimizationGuideKeyedService* GetOptimizationService();
+
+  void OnExecutionResponseCallback(
+      optimization_guide::OptimizationGuideModelExecutionResult
+          execution_result,
+      std::unique_ptr<
+          optimization_guide::proto::PasswordChangeSubmissionLoggingData>
+          logging_data);
+
+  void OnFormSubmitted();
+
   void OnSubmissionDetectedOrTimeout();
 
   base::OneShotTimer timeout_timer_;
@@ -90,6 +115,8 @@
   bool submission_detected_ = false;
 
   std::unique_ptr<PasswordChangeSubmissionVerifier> submission_verifier_;
+  base::OnceCallback<void(optimization_guide::OnAIPageContentDone)>
+      capture_annotated_page_content_;
 
   base::WeakPtrFactory<ChangePasswordFormFillingSubmissionHelper>
       weak_ptr_factory_{this};
diff --git a/chrome/browser/password_manager/password_change/change_password_form_filling_submission_helper_unittest.cc b/chrome/browser/password_manager/password_change/change_password_form_filling_submission_helper_unittest.cc
index 582e91ad..7cc63409 100644
--- a/chrome/browser/password_manager/password_change/change_password_form_filling_submission_helper_unittest.cc
+++ b/chrome/browser/password_manager/password_change/change_password_form_filling_submission_helper_unittest.cc
@@ -4,12 +4,14 @@
 
 #include "chrome/browser/password_manager/password_change/change_password_form_filling_submission_helper.h"
 
+#include "base/functional/callback_helpers.h"
 #include "base/test/gmock_callback_support.h"
 #include "base/test/gmock_move_support.h"
 #include "base/test/metrics/histogram_tester.h"
 #include "base/test/mock_callback.h"
 #include "base/test/run_until.h"
 #include "base/test/test_future.h"
+#include "base/types/pass_key.h"
 #include "chrome/browser/optimization_guide/mock_optimization_guide_keyed_service.h"
 #include "chrome/browser/optimization_guide/optimization_guide_keyed_service.h"
 #include "chrome/browser/optimization_guide/optimization_guide_keyed_service_factory.h"
@@ -118,7 +120,7 @@
 }
 
 template <bool success>
-void PostResponse(
+void PostResponseForSubmissionVerification(
     optimization_guide::OptimizationGuideModelExecutionResultCallback
         callback) {
   optimization_guide::proto::PasswordChangeResponse response;
@@ -136,6 +138,21 @@
                                 /*log_entry=*/nullptr));
 }
 
+template <bool success>
+void PostResponseForSubmissionButtonClick(
+    optimization_guide::OptimizationGuideModelExecutionResultCallback
+        callback) {
+  optimization_guide::proto::PasswordChangeResponse response;
+  response.mutable_submit_form_data()->set_dom_node_id_to_click(success ? 1
+                                                                        : 0);
+  auto result = optimization_guide::OptimizationGuideModelExecutionResult(
+      optimization_guide::AnyWrapProto(response),
+      /*execution_info=*/nullptr);
+  base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
+      FROM_HERE, base::BindOnce(std::move(callback), std::move(result),
+                                /*log_entry=*/nullptr));
+}
+
 }  // namespace
 
 class ChangePasswordFormFillingSubmissionHelperTest
@@ -187,9 +204,13 @@
 
   std::unique_ptr<ChangePasswordFormFillingSubmissionHelper> CreateVerifier(
       password_manager::PasswordFormManager* manager,
-      base::OnceCallback<void(bool)> result_callback) {
+      base::OnceCallback<void(bool)> result_callback,
+      base::OnceCallback<void(optimization_guide::OnAIPageContentDone)>
+          capture_annotated_page_content = base::NullCallback()) {
     auto verifier = std::make_unique<ChangePasswordFormFillingSubmissionHelper>(
-        web_contents(), logs_uploader_.get(), std::move(result_callback));
+        base::PassKey<class ChangePasswordFormFillingSubmissionHelperTest>(),
+        web_contents(), logs_uploader_.get(),
+        std::move(capture_annotated_page_content), std::move(result_callback));
     verifier->FillChangePasswordForm(manager, kOldPassword, kNewPassword);
     return verifier;
   }
@@ -241,7 +262,8 @@
       capture_annotated_page_content.Get());
 
   EXPECT_CALL(*optimization_service(), ExecuteModel)
-      .WillOnce(WithArg<3>(Invoke(&PostResponse<true>)));
+      .WillOnce(
+          WithArg<3>(Invoke(&PostResponseForSubmissionVerification<true>)));
   verifier->OnPasswordFormSubmission(web_contents());
 
   EXPECT_TRUE(completion_future.Get());
@@ -280,7 +302,8 @@
       capture_annotated_page_content.Get());
 
   EXPECT_CALL(*optimization_service(), ExecuteModel)
-      .WillOnce(WithArg<3>(Invoke(&PostResponse<false>)));
+      .WillOnce(
+          WithArg<3>(Invoke(&PostResponseForSubmissionVerification<false>)));
   verifier->OnPasswordFormSubmission(web_contents());
 
   EXPECT_FALSE(completion_future.Get());
@@ -320,7 +343,8 @@
       .WillOnce(base::test::RunOnceCallback<0>(
           optimization_guide::AIPageContentResult()));
   EXPECT_CALL(*optimization_service(), ExecuteModel)
-      .WillOnce(WithArg<3>(Invoke(&PostResponse<true>)));
+      .WillOnce(
+          WithArg<3>(Invoke(&PostResponseForSubmissionVerification<true>)));
 
   EXPECT_TRUE(completion_future.Wait());
   EXPECT_TRUE(completion_future.Take());
@@ -382,7 +406,8 @@
   verifier->submission_verifier()->set_annotated_page_callback(
       capture_annotated_page_content.Get());
   EXPECT_CALL(*optimization_service(), ExecuteModel)
-      .WillOnce(WithArg<3>(Invoke(&PostResponse<true>)));
+      .WillOnce(
+          WithArg<3>(Invoke(&PostResponseForSubmissionVerification<true>)));
   verifier->OnPasswordFormSubmission(web_contents());
 
   EXPECT_TRUE(completion_future.Get());
@@ -416,10 +441,42 @@
       capture_annotated_page_content.Get());
   EXPECT_CALL(*optimization_service(), ExecuteModel)
       .Times(1)
-      .WillOnce(WithArg<3>(Invoke(&PostResponse<true>)));
+      .WillOnce(
+          WithArg<3>(Invoke(&PostResponseForSubmissionVerification<true>)));
   verifier->OnPasswordFormSubmission(web_contents());
   verifier->OnPasswordFormSubmission(web_contents());
   verifier->OnPasswordFormSubmission(web_contents());
 
   EXPECT_TRUE(completion_future.Get());
 }
+
+TEST_F(ChangePasswordFormFillingSubmissionHelperTest,
+       SubmissionWithEnterFailingTriggersButtonSearch) {
+  auto form_manager = CreateFormManager();
+
+  base::test::TestFuture<bool> completion_future;
+  base::MockCallback<
+      base::OnceCallback<void(optimization_guide::OnAIPageContentDone)>>
+      capture_annotated_page_content;
+  EXPECT_CALL(capture_annotated_page_content, Run)
+      .WillOnce(base::test::RunOnceCallback<0>(
+          optimization_guide::AIPageContentResult()));
+  auto verifier =
+      CreateVerifier(form_manager.get(), completion_future.GetCallback(),
+                     capture_annotated_page_content.Get());
+
+  base::RunLoop run_loop;
+  EXPECT_CALL(driver(), FillChangePasswordForm)
+      .WillOnce(RunOnceCallback<5>(CreateTestPasswordFormData()));
+  EXPECT_CALL(driver(), SubmitFormWithEnter)
+      .WillOnce(DoAll(Invoke(&run_loop, &base::RunLoop::Quit),
+                      RunOnceCallback<1>(/*success=*/false)));
+  EXPECT_CALL(*optimization_service(), ExecuteModel)
+      .WillOnce(
+          WithArg<3>(Invoke(&PostResponseForSubmissionButtonClick<false>)));
+  run_loop.Run();
+
+  verifier->OnPasswordFormSubmission(web_contents());
+
+  EXPECT_FALSE(completion_future.Get());
+}
diff --git a/chrome/browser/password_manager/password_change_browsertest.cc b/chrome/browser/password_manager/password_change_browsertest.cc
index 4b5f1e1b..cf7d1a6 100644
--- a/chrome/browser/password_manager/password_change_browsertest.cc
+++ b/chrome/browser/password_manager/password_change_browsertest.cc
@@ -794,7 +794,7 @@
 }
 
 IN_PROC_BROWSER_TEST_F(PasswordChangeBrowserTest,
-                       FailureDialogDisplayedAutomatically) {
+                       FailureBubbleNotDisplayedAutomatically) {
   SetPrivacyNoticeAcceptedPref();
   const GURL main_url = WebContents()->GetLastCommittedURL();
   EXPECT_CALL(*affiliation_service(), GetChangePasswordURL(main_url))
@@ -813,12 +813,13 @@
     return delegate->GetCurrentState() ==
            PasswordChangeDelegate::State::kPasswordChangeFailed;
   }));
-  // Now bubble should automatically appear.
-  EXPECT_TRUE(prompt_observer.IsBubbleDisplayedAutomatically());
+
+  // TODO(crbug.com/417388947): Check that dialog is displayed instead.
+  EXPECT_FALSE(prompt_observer.IsBubbleDisplayedAutomatically());
 }
 
 IN_PROC_BROWSER_TEST_F(PasswordChangeBrowserTest,
-                       LeakCheckBubbleDisplayedAutomatically) {
+                       LeakCheckBubbleNotDisplayedAutomatically) {
   const GURL main_url = WebContents()->GetLastCommittedURL();
   EXPECT_CALL(*affiliation_service(), GetChangePasswordURL(main_url))
       .WillOnce(testing::Return(embedded_test_server()->GetURL(
@@ -832,8 +833,9 @@
       password_change_service()->GetPasswordChangeDelegate(WebContents());
   EXPECT_EQ(delegate->GetCurrentState(),
             PasswordChangeDelegate::State::kOfferingPasswordChange);
-  // Now bubble should automatically appear.
-  EXPECT_TRUE(prompt_observer.IsBubbleDisplayedAutomatically());
+
+  // TODO(crbug.com/417388947): Check that dialog is displayed instead.
+  EXPECT_FALSE(prompt_observer.IsBubbleDisplayedAutomatically());
 }
 
 IN_PROC_BROWSER_TEST_F(PasswordChangeBrowserTest,
diff --git a/chrome/browser/password_manager/password_change_delegate_impl.cc b/chrome/browser/password_manager/password_change_delegate_impl.cc
index 420f9f66..bb5246a7 100644
--- a/chrome/browser/password_manager/password_change_delegate_impl.cc
+++ b/chrome/browser/password_manager/password_change_delegate_impl.cc
@@ -18,7 +18,10 @@
 #include "chrome/browser/tab_contents/tab_util.h"
 #include "chrome/browser/ui/autofill/autofill_client_provider.h"
 #include "chrome/browser/ui/autofill/autofill_client_provider_factory.h"
+#include "chrome/browser/ui/browser_window/public/browser_window_interface.h"
 #include "chrome/browser/ui/passwords/manage_passwords_ui_controller.h"
+#include "chrome/browser/ui/passwords/password_change_ui_controller.h"
+#include "chrome/browser/ui/tabs/tab_strip_model.h"
 #include "components/autofill/core/browser/logging/log_manager.h"
 #include "components/autofill/core/common/form_data.h"
 #include "components/autofill/core/common/save_password_progress_logger.h"
@@ -31,6 +34,7 @@
 #include "components/password_manager/core/browser/password_form.h"
 #include "components/password_manager/core/common/password_manager_pref_names.h"
 #include "components/prefs/pref_service.h"
+#include "components/tabs/public/tab_interface.h"
 #include "components/url_formatter/elide_url.h"
 #include "content/public/browser/browser_context.h"
 #include "content/public/browser/navigation_controller.h"
@@ -38,9 +42,6 @@
 #include "content/public/common/referrer.h"
 #include "ui/base/window_open_disposition.h"
 #include "url/gurl.h"
-#include "chrome/browser/ui/browser_window/public/browser_window_interface.h"
-#include "chrome/browser/ui/tabs/tab_strip_model.h"
-#include "components/tabs/public/tab_interface.h"
 
 namespace {
 
@@ -140,7 +141,10 @@
       username_(std::move(username)),
       original_password_(std::move(password)),
       profile_(Profile::FromBrowserContext(originator->GetBrowserContext())),
-      originator_(originator->GetWeakPtr()) {
+      originator_(originator->GetWeakPtr()),
+      ui_controller_(std::make_unique<PasswordChangeUIController>(
+          this,
+          originator->GetWeakPtr())) {
   if (auto logger = GetLoggerIfAvailable(originator_)) {
     logger->LogMessage(
         BrowserSavePasswordProgressLogger::STRING_PASSWORD_CHANGE_STARTED);
@@ -318,19 +322,20 @@
   current_state_ = new_state;
   observers_.Notify(&PasswordChangeDelegate::Observer::OnStateChanged,
                     new_state);
+  ui_controller_->UpdateState(new_state);
 
   switch (current_state_) {
     case State::kWaitingForChangePasswordForm:
     case State::kChangingPassword:
+    case State::kOfferingPasswordChange:
+    case State::kPasswordChangeFailed:
       return;
     case State::kPasswordSuccessfullyChanged:
       NotifyPasswordChangeFinishedSuccessfully(originator_);
       // Fallthrough to trigger bubble display.
       [[fallthrough]];
     case State::kChangePasswordFormNotFound:
-    case State::kOfferingPasswordChange:
     case State::kWaitingForAgreement:
-    case State::kPasswordChangeFailed:
     case State::kOtpDetected:
       DisplayChangePasswordBubbleAutomatically(originator_);
       break;
diff --git a/chrome/browser/password_manager/password_change_delegate_impl.h b/chrome/browser/password_manager/password_change_delegate_impl.h
index b50ab6cc..1faf838 100644
--- a/chrome/browser/password_manager/password_change_delegate_impl.h
+++ b/chrome/browser/password_manager/password_change_delegate_impl.h
@@ -27,6 +27,7 @@
 class ChangePasswordFormFillingSubmissionHelper;
 class ChangePasswordFormFinder;
 class ModelQualityLogsUploader;
+class PasswordChangeUIController;
 class Profile;
 
 // This class controls password change process including acceptance of privacy
@@ -119,6 +120,9 @@
 
   base::Time flow_start_time_;
 
+  // The controller for password change views.
+  std::unique_ptr<PasswordChangeUIController> ui_controller_;
+
   base::WeakPtrFactory<PasswordChangeDelegateImpl> weak_ptr_factory_{this};
 };
 
diff --git a/chrome/browser/permissions/prediction_based_permission_ui_selector.cc b/chrome/browser/permissions/prediction_based_permission_ui_selector.cc
index be06330..1aa0f9bb 100644
--- a/chrome/browser/permissions/prediction_based_permission_ui_selector.cc
+++ b/chrome/browser/permissions/prediction_based_permission_ui_selector.cc
@@ -122,8 +122,7 @@
 }
 
 void LogAiv3RelevanceHasValue(bool has_value) {
-  base::UmaHistogramBoolean("Permissions.AIv3.RelevanceHasValue",
-    has_value);
+  base::UmaHistogramBoolean("Permissions.AIv3.RelevanceHasValue", has_value);
 }
 
 }  // namespace
@@ -296,7 +295,7 @@
     last_permission_request_relevance_ = relevance.value();
     features.permission_relevance = relevance.value();
     base::UmaHistogramEnumeration("Permissions.AIv3.PermissionRequestRelevance",
-          features.permission_relevance);
+                                  features.permission_relevance);
   } else {
     last_permission_request_relevance_ =
         PermissionRequestRelevance::kUnspecified;
@@ -670,11 +669,7 @@
   if (use_server_side) {
     // Aiv3 takes priority over Aiv1 if both are enabled.
 #if BUILDFLAG(BUILD_WITH_TFLITE_LIB)
-    if (request_type == permissions::RequestType::kNotifications &&
-        base::FeatureList::IsEnabled(permissions::features::kPermissionsAIv3)) {
-      return PredictionSource::kOnDeviceAiv3AndServerSideModel;
-    }
-    if (request_type == permissions::RequestType::kGeolocation &&
+    if (base::FeatureList::IsEnabled(permissions::features::kPermissionsAIv3) ||
         base::FeatureList::IsEnabled(
             permissions::features::kPermissionsAIv3Geolocation)) {
       return PredictionSource::kOnDeviceAiv3AndServerSideModel;
diff --git a/chrome/browser/permissions/prediction_based_permission_ui_selector_unittest.cc b/chrome/browser/permissions/prediction_based_permission_ui_selector_unittest.cc
index 1a78b89..f0fd008 100644
--- a/chrome/browser/permissions/prediction_based_permission_ui_selector_unittest.cc
+++ b/chrome/browser/permissions/prediction_based_permission_ui_selector_unittest.cc
@@ -339,16 +339,14 @@
          PredictionSource::kOnDeviceAiv1AndServerSideModel},
         {/*test_name=*/"UsePermissionsAiv3OnDesktop",
          /*enabled_features=*/
-         {BASIC_CPSS_FEATURES, permissions::features::kPermissionsAIv3,
-          permissions::features::kPermissionsAIv3Geolocation},
+         {BASIC_CPSS_FEATURES, permissions::features::kPermissionsAIv3},
          /*disabled_features=*/{},
          /*expected_prediction_source=*/
          PredictionSource::kOnDeviceAiv3AndServerSideModel},
         {/*test_name=*/"UsePermissionsAiv3OverAiv1OnDesktop",
          /*enabled_features=*/
          {BASIC_CPSS_FEATURES, permissions::features::kPermissionsAIv1,
-          permissions::features::kPermissionsAIv3,
-          permissions::features::kPermissionsAIv3Geolocation},
+          permissions::features::kPermissionsAIv3},
          /*disabled_features=*/{},
          /*expected_prediction_source=*/
          PredictionSource::kOnDeviceAiv3AndServerSideModel},
diff --git a/chrome/browser/permissions/prediction_model_handler_provider.cc b/chrome/browser/permissions/prediction_model_handler_provider.cc
index ea4b07b..0eb0aef 100644
--- a/chrome/browser/permissions/prediction_model_handler_provider.cc
+++ b/chrome/browser/permissions/prediction_model_handler_provider.cc
@@ -37,15 +37,14 @@
           optimization_guide::proto::OptimizationTarget::
               OPTIMIZATION_TARGET_GEOLOCATION_PERMISSION_PREDICTIONS);
 
-  if (base::FeatureList::IsEnabled(permissions::features::kPermissionsAIv3)) {
+  if (base::FeatureList::IsEnabled(permissions::features::kPermissionsAIv3) ||
+      base::FeatureList::IsEnabled(
+          permissions::features::kPermissionsAIv3Geolocation)) {
     notification_aiv3_handler_ = std::make_unique<PermissionsAiv3Handler>(
         optimization_guide,
         optimization_guide::proto::OptimizationTarget::
             OPTIMIZATION_TARGET_NOTIFICATION_IMAGE_PERMISSION_RELEVANCE,
         RequestType::kNotifications);
-  }
-  if (base::FeatureList::IsEnabled(
-          permissions::features::kPermissionsAIv3Geolocation)) {
     geolocation_aiv3_handler_ = std::make_unique<PermissionsAiv3Handler>(
         optimization_guide,
         optimization_guide::proto::OptimizationTarget::
diff --git a/chrome/browser/permissions/prediction_service_browsertest.cc b/chrome/browser/permissions/prediction_service_browsertest.cc
index bb7d6168..51a2917 100644
--- a/chrome/browser/permissions/prediction_service_browsertest.cc
+++ b/chrome/browser/permissions/prediction_service_browsertest.cc
@@ -686,9 +686,6 @@
                                              {permissions::features::
                                                   kPermissionsAIv3,
                                               {}},
-                                             {permissions::features::
-                                                  kPermissionsAIv3Geolocation,
-                                              {}},
                                          },
                                          /*disabled_features=*/{}) {}
 
diff --git a/chrome/browser/policy/browser_dm_token_storage_win.cc b/chrome/browser/policy/browser_dm_token_storage_win.cc
index bb2a8d2..18d5d7d 100644
--- a/chrome/browser/policy/browser_dm_token_storage_win.cc
+++ b/chrome/browser/policy/browser_dm_token_storage_win.cc
@@ -30,135 +30,36 @@
 #include "base/strings/utf_string_conversions.h"
 #include "base/task/thread_pool.h"
 #include "base/win/registry.h"
-#include "base/win/scoped_bstr.h"
 #include "build/branding_buildflags.h"
-#include "chrome/install_static/install_util.h"
 #include "chrome/installer/util/install_util.h"
 #include "chrome/installer/util/util_constants.h"
 #include "content/public/browser/browser_thread.h"
 
 #if BUILDFLAG(GOOGLE_CHROME_BRANDING)
-#include "chrome/updater/app/server/win/updater_legacy_idl.h"
+#include "chrome/browser/google/google_update_app_command.h"
 #endif  // BUILDFLAG(GOOGLE_CHROME_BRANDING)
 
 namespace policy {
 namespace {
-#if BUILDFLAG(GOOGLE_CHROME_BRANDING)
-// Explicitly allow DMTokenStorage impersonate the client since some COM code
-// elsewhere in the browser process may have previously used
-// CoInitializeSecurity to set the impersonation level to something other than
-// the default. Ignore errors since an attempt to use Google Update may succeed
-// regardless.
-void ConfigureProxyBlanket(IUnknown* interface_pointer) {
-  ::CoSetProxyBlanket(
-      interface_pointer, RPC_C_AUTHN_DEFAULT, RPC_C_AUTHZ_DEFAULT,
-      COLE_DEFAULT_PRINCIPAL, RPC_C_AUTHN_LEVEL_PKT_PRIVACY,
-      RPC_C_IMP_LEVEL_IMPERSONATE, nullptr, EOAC_DYNAMIC_CLOAKING);
-}
-#endif  // BUILDFLAG(GOOGLE_CHROME_BRANDING)
-
-#if BUILDFLAG(GOOGLE_CHROME_BRANDING)
-Microsoft::WRL::ComPtr<IAppCommandWeb> GetUpdaterAppCommand(
-    const std::wstring& command_name) {
-  Microsoft::WRL::ComPtr<IUnknown> server;
-  HRESULT hr = ::CoCreateInstance(CLSID_GoogleUpdate3WebSystemClass, nullptr,
-                                  CLSCTX_ALL, IID_PPV_ARGS(&server));
-  if (FAILED(hr))
-    return nullptr;
-
-  ConfigureProxyBlanket(server.Get());
-
-  // Chrome queries for the SxS IIDs first, with a fallback to the legacy IID.
-  // Without this change, marshaling can load the typelib from the wrong hive
-  // (HKCU instead of HKLM, or vice-versa).
-  Microsoft::WRL::ComPtr<IGoogleUpdate3Web> google_update;
-  hr = server.CopyTo(__uuidof(IGoogleUpdate3WebSystem),
-                     IID_PPV_ARGS_Helper(&google_update));
-  if (FAILED(hr)) {
-    hr = server.As(&google_update);
-    if (FAILED(hr)) {
-      return nullptr;
-    }
-  }
-
-  Microsoft::WRL::ComPtr<IDispatch> dispatch;
-  hr = google_update->createAppBundleWeb(&dispatch);
-  if (FAILED(hr))
-    return nullptr;
-
-  // Chrome queries for the SxS IIDs first, with a fallback to the legacy IID.
-  // Without this change, marshaling can load the typelib from the wrong hive
-  // (HKCU instead of HKLM, or vice-versa).
-  Microsoft::WRL::ComPtr<IAppBundleWeb> app_bundle;
-  hr = dispatch.CopyTo(__uuidof(IAppBundleWebSystem),
-                       IID_PPV_ARGS_Helper(&app_bundle));
-  if (FAILED(hr)) {
-    hr = dispatch.As(&app_bundle);
-    if (FAILED(hr)) {
-      return nullptr;
-    }
-  }
-
-  dispatch.Reset();
-  ConfigureProxyBlanket(app_bundle.Get());
-  app_bundle->initialize();
-  const wchar_t* app_guid = install_static::GetAppGuid();
-  hr = app_bundle->createInstalledApp(base::win::ScopedBstr(app_guid).Get());
-  if (FAILED(hr))
-    return nullptr;
-
-  hr = app_bundle->get_appWeb(0, &dispatch);
-  if (FAILED(hr))
-    return nullptr;
-
-  Microsoft::WRL::ComPtr<IAppWeb> app;
-  hr = dispatch.CopyTo(__uuidof(IAppWebSystem), IID_PPV_ARGS_Helper(&app));
-  if (FAILED(hr)) {
-    hr = dispatch.As(&app);
-    if (FAILED(hr)) {
-      return nullptr;
-    }
-  }
-
-  dispatch.Reset();
-  ConfigureProxyBlanket(app.Get());
-
-  hr = app->get_command(base::win::ScopedBstr(command_name).Get(), &dispatch);
-  if (FAILED(hr) || !dispatch)
-    return nullptr;
-
-  Microsoft::WRL::ComPtr<IAppCommandWeb> app_command;
-  hr = dispatch.CopyTo(__uuidof(IAppCommandWebSystem),
-                       IID_PPV_ARGS_Helper(&app_command));
-  if (FAILED(hr)) {
-    hr = dispatch.As(&app_command);
-    if (FAILED(hr)) {
-      return nullptr;
-    }
-  }
-
-  ConfigureProxyBlanket(app_command.Get());
-  return app_command;
-}
-#endif  // BUILDFLAG(GOOGLE_CHROME_BRANDING)
 
 bool StoreDMTokenInRegistry(const std::string& token) {
 #if BUILDFLAG(GOOGLE_CHROME_BRANDING)
   if (token.empty())
     return false;
 
-  Microsoft::WRL::ComPtr<IAppCommandWeb> app_command =
-      GetUpdaterAppCommand(installer::kCmdStoreDMToken);
-  if (!app_command)
+  auto app_command = GetUpdaterAppCommand(installer::kCmdStoreDMToken);
+  if (!app_command.has_value()) {
     return false;
+  }
 
   std::string token_base64 = base::Base64Encode(token);
   VARIANT var;
   VariantInit(&var);
   _variant_t token_var = token_base64.c_str();
-  if (FAILED(app_command->execute(token_var, var, var, var, var, var, var, var,
-                                  var)))
+  if (FAILED(app_command.value()->execute(token_var, var, var, var, var, var,
+                                          var, var, var))) {
     return false;
+  }
 
   // TODO(crbug.com/41377531): Get the status of the app command execution and
   // return a corresponding value for |success|. For now, assume that the call
@@ -171,15 +72,17 @@
 
 bool DeleteDMTokenFromRegistry() {
 #if BUILDFLAG(GOOGLE_CHROME_BRANDING)
-  Microsoft::WRL::ComPtr<IAppCommandWeb> app_command =
-      GetUpdaterAppCommand(installer::kCmdDeleteDMToken);
-  if (!app_command)
+  auto app_command = GetUpdaterAppCommand(installer::kCmdDeleteDMToken);
+  if (!app_command.has_value()) {
     return false;
+  }
 
   VARIANT var;
   VariantInit(&var);
-  if (FAILED(app_command->execute(var, var, var, var, var, var, var, var, var)))
+  if (FAILED(app_command.value()->execute(var, var, var, var, var, var, var,
+                                          var, var))) {
     return false;
+  }
 
   // TODO(crbug.com/41377531): Get the status of the app command execution and
   // return a corresponding value for |success|. For now, assume that the call
diff --git a/chrome/browser/privacy_sandbox/android/javatests/src/org/chromium/chrome/browser/privacy_sandbox/PrivacySandboxDialogTest.java b/chrome/browser/privacy_sandbox/android/javatests/src/org/chromium/chrome/browser/privacy_sandbox/PrivacySandboxDialogTest.java
index 7540555..5cc28a5 100644
--- a/chrome/browser/privacy_sandbox/android/javatests/src/org/chromium/chrome/browser/privacy_sandbox/PrivacySandboxDialogTest.java
+++ b/chrome/browser/privacy_sandbox/android/javatests/src/org/chromium/chrome/browser/privacy_sandbox/PrivacySandboxDialogTest.java
@@ -651,7 +651,6 @@
     @Test
     @SmallTest
     @EnableFeatures({
-        ChromeFeatureList.PRIVACY_SANDBOX_ADS_NOTICE_CCT,
         ChromeFeatureList.PRIVACY_SANDBOX_SETTINGS_4
                 + ":force-show-notice-row-for-testing/true/notice-required/true"
     })
diff --git a/chrome/browser/resources/BUILD.gn b/chrome/browser/resources/BUILD.gn
index d8628e7..db1407881 100644
--- a/chrome/browser/resources/BUILD.gn
+++ b/chrome/browser/resources/BUILD.gn
@@ -320,6 +320,7 @@
     ]
   } else {
     public_deps += [
+      "actor_internals:resources",
       "infobar_internals:resources",
       "media_router/internals:resources",
       "on_device_internals:resources",
@@ -383,6 +384,7 @@
     ]
   } else {
     sources += [
+      "$root_gen_dir/chrome/actor_internals_resources.pak",
       "$root_gen_dir/chrome/infobar_internals_resources.pak",
       "$root_gen_dir/chrome/media_router_internals_resources.pak",
       "$root_gen_dir/chrome/on_device_internals_resources.pak",
diff --git a/chrome/browser/resources/actor_internals/BUILD.gn b/chrome/browser/resources/actor_internals/BUILD.gn
new file mode 100644
index 0000000..ebdf548
--- /dev/null
+++ b/chrome/browser/resources/actor_internals/BUILD.gn
@@ -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.
+
+import("//ui/webui/resources/tools/build_webui.gni")
+
+build_webui("build") {
+  grd_prefix = "actor_internals"
+
+  static_files = [
+    "actor_internals.css",
+    "actor_internals.html",
+  ]
+  ts_files = [
+    "actor_internals.ts",
+    "browser_proxy.ts",
+  ]
+
+  mojo_files_deps = [
+    "//chrome/browser/ui/webui/actor_internals:mojo_bindings_ts__generator",
+  ]
+
+  mojo_files = [ "$root_gen_dir/chrome/browser/ui/webui/actor_internals/actor_internals.mojom-webui.ts" ]
+
+  ts_deps = [
+    "//ui/webui/resources/js:build_ts",
+    "//ui/webui/resources/mojo:build_ts",
+  ]
+  webui_context_type = "trusted"
+}
diff --git a/chrome/browser/resources/actor_internals/actor_internals.css b/chrome/browser/resources/actor_internals/actor_internals.css
new file mode 100644
index 0000000..60b5433
--- /dev/null
+++ b/chrome/browser/resources/actor_internals/actor_internals.css
@@ -0,0 +1,15 @@
+/* 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. */
+
+thead {
+  white-space: nowrap;
+}
+
+th {
+  background-color: #C0C0C0;
+}
+
+td {
+  background-color: #F0F0F0;
+}
\ No newline at end of file
diff --git a/chrome/browser/resources/actor_internals/actor_internals.html b/chrome/browser/resources/actor_internals/actor_internals.html
new file mode 100644
index 0000000..8111e8e
--- /dev/null
+++ b/chrome/browser/resources/actor_internals/actor_internals.html
@@ -0,0 +1,24 @@
+<!doctype html>
+<html lang="en">
+<head>
+  <meta charset="utf-8">
+  <title>Actor Debug Page</title>
+  <link rel="stylesheet" href="chrome://resources/css/text_defaults.css">
+  <link rel="stylesheet" href="actor_internals.css">
+  <script type="module" src="actor_internals.js"></script>
+</head>
+<body>
+  <p>
+    Listening for actor events...
+  </p>
+  <table id="actor-events-table">
+    <thead>
+      <th>URL</th>
+      <th>Event</th>
+      <th>Type</th>
+      <th>Details</th>
+      <th>Timestamp</th>
+    </thead>
+  </table>
+</body>
+</html>
\ No newline at end of file
diff --git a/chrome/browser/resources/actor_internals/actor_internals.ts b/chrome/browser/resources/actor_internals/actor_internals.ts
new file mode 100644
index 0000000..f8a0ede9
--- /dev/null
+++ b/chrome/browser/resources/actor_internals/actor_internals.ts
@@ -0,0 +1,40 @@
+// 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.
+
+/**
+ * Typescript for actor_internals.html, served from chrome://actor-internals/
+ * This is used to debug actor events recording. It displays a live
+ * stream of all actor events that occur in chromium while the
+ * chrome://actor-internals/ page is open.
+ */
+
+import {getRequiredElement} from '//resources/js/util.js';
+
+import type {JournalEntry} from './actor_internals.mojom-webui.js';
+import {BrowserProxy} from './browser_proxy.js';
+
+
+window.onload = function() {
+  const proxy = BrowserProxy.getInstance();
+  proxy.callbackRouter.journalEntryAdded.addListener((entry: JournalEntry) => {
+    const table = getRequiredElement('actor-events-table');
+    const tr = document.createElement('tr');
+    let td = document.createElement('td');
+    td.textContent = entry.url;
+    tr.appendChild(td);
+    td = document.createElement('td');
+    td.textContent = entry.event;
+    tr.appendChild(td);
+    td = document.createElement('td');
+    td.textContent = entry.type;
+    tr.appendChild(td);
+    td = document.createElement('td');
+    td.textContent = entry.details;
+    tr.appendChild(td);
+    td = document.createElement('td');
+    td.textContent = new Date(entry.timestamp).toUTCString();
+    tr.appendChild(td);
+    table.appendChild(tr);
+  });
+};
diff --git a/chrome/browser/resources/actor_internals/browser_proxy.ts b/chrome/browser/resources/actor_internals/browser_proxy.ts
new file mode 100644
index 0000000..43052360
--- /dev/null
+++ b/chrome/browser/resources/actor_internals/browser_proxy.ts
@@ -0,0 +1,31 @@
+// 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 {PageCallbackRouter, PageHandlerFactory, PageHandlerRemote} from './actor_internals.mojom-webui.js';
+import type {PageHandlerInterface} from './actor_internals.mojom-webui.js';
+
+export class BrowserProxy {
+  callbackRouter: PageCallbackRouter;
+  handler: PageHandlerInterface;
+
+  private constructor() {
+    this.callbackRouter = new PageCallbackRouter();
+
+    this.handler = new PageHandlerRemote();
+
+    PageHandlerFactory.getRemote().createPageHandler(
+        this.callbackRouter.$.bindNewPipeAndPassRemote(),
+        (this.handler as PageHandlerRemote).$.bindNewPipeAndPassReceiver());
+  }
+
+  static getInstance(): BrowserProxy {
+    return instance || (instance = new BrowserProxy());
+  }
+
+  static setInstance(proxy: BrowserProxy) {
+    instance = proxy;
+  }
+}
+
+let instance: BrowserProxy|null = null;
diff --git a/chrome/browser/resources/settings/clear_browsing_data_dialog/clear_browsing_data_dialog_v2.html b/chrome/browser/resources/settings/clear_browsing_data_dialog/clear_browsing_data_dialog_v2.html
index 48af324..41440a6 100644
--- a/chrome/browser/resources/settings/clear_browsing_data_dialog/clear_browsing_data_dialog_v2.html
+++ b/chrome/browser/resources/settings/clear_browsing_data_dialog/clear_browsing_data_dialog_v2.html
@@ -1,18 +1,23 @@
 <style include="settings-shared cr-spinner-style">
   :host {
-    --dbd-container-color: var(--cr-fallback-color-surface3);
-    --dbd-container-border-radius: 12px;
-    --dbd-container-stacked-border-radius: 4px;
     --cr-dialog-button-container-padding-bottom: 20px;
     --cr-dialog-button-container-padding-horizontal: 20px;
     --cr-dialog-button-container-padding-top: 20px;
     --cr-dialog-title-font-size: 16px;
+    --cr-dialog-title-slot-padding-bottom: 14px;
+    --cr-section-two-line-min-height: 40px;
+    --dbd-container-border-radius: 12px;
+    --dbd-container-color: var(--cr-fallback-color-surface3);
+    --dbd-container-stacked-border-radius: 4px;
+    --dbd-dialog-body-max-height: 400px;
+    --dbd-dialog-max-height: 600px;
+    --settings-checkbox-margin-top: 8px;
   }
 
   #checkboxContainer {
     background-color: var(--dbd-container-color);
     border-radius: var(--dbd-container-border-radius);
-    padding: 16px var(--cr-section-padding);
+    padding: 8px var(--cr-section-padding) 12px var(--cr-section-padding);
   }
 
   #checkboxContainer:has(+ #showMoreButton:not([hidden])) {
@@ -23,6 +28,20 @@
     margin-bottom: 2px;
   }
 
+  #deleteBrowsingDataDialog [slot=header] {
+    padding-bottom: var(--cr-section-padding);
+    padding-inline-end: var(--cr-section-padding);
+    padding-inline-start: var(--cr-section-padding);
+  }
+
+  #deleteBrowsingDataDialog::part(body-container) {
+    max-height: var(--dbd-dialog-body-max-height);
+  }
+
+  #deleteBrowsingDataDialog::part(dialog) {
+    max-height: var(--dbd-dialog-max-height);
+  }
+
   #manageOtherGoogleDataRow {
     border-radius: var(--dbd-container-border-radius);
     color: var(--cr-primary-text-color);
@@ -75,10 +94,12 @@
     show-on-attach ignore-popstate ignore-enter-key
     hidden="[[showOtherGoogleDataDialog_]]">
   <div slot="title" class="dialog-title">$i18n{clearBrowsingData}</div>
-  <div slot="body">
+  <div slot="header">
     <settings-clear-browsing-data-time-picker id="timePicker" prefs="{{prefs}}"
         on-selected-time-period-change="onTimePeriodChanged_">
     </settings-clear-browsing-data-time-picker>
+  </div>
+  <div slot="body">
     <div id="checkboxContainer">
       <template is="dom-repeat"
           items="[[expandedBrowsingDataTypeOptionsList_]]">
@@ -117,8 +138,8 @@
     </settings-clear-browsing-data-account-indicator>
 </if>
     <div id="spinner" class="spinner" hidden="[[!isDeletionInProgress_]]"></div>
-    <cr-button id="cancelButton" class="cancel-button" autofocus
-        on-click="onCancelClick_" disabled="[[isDeletionInProgress_]]">
+    <cr-button id="cancelButton" class="cancel-button" on-click="onCancelClick_"
+        disabled="[[isDeletionInProgress_]]" autofocus>
       $i18n{cancel}
     </cr-button>
     <cr-button id="deleteButton" class="action-button"
diff --git a/chrome/browser/resources/settings/clear_browsing_data_dialog/clear_browsing_data_dialog_v2.ts b/chrome/browser/resources/settings/clear_browsing_data_dialog/clear_browsing_data_dialog_v2.ts
index 006abf7..a4379fec 100644
--- a/chrome/browser/resources/settings/clear_browsing_data_dialog/clear_browsing_data_dialog_v2.ts
+++ b/chrome/browser/resources/settings/clear_browsing_data_dialog/clear_browsing_data_dialog_v2.ts
@@ -23,6 +23,7 @@
 import type {CrDialogElement} from 'chrome://resources/cr_elements/cr_dialog/cr_dialog.js';
 import {WebUiListenerMixin} from 'chrome://resources/cr_elements/web_ui_listener_mixin.js';
 import {assert} from 'chrome://resources/js/assert.js';
+import {FocusOutlineManager} from 'chrome://resources/js/focus_outline_manager.js';
 import {afterNextRender, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
 import type {SettingsCheckboxElement} from '../controls/settings_checkbox.js';
@@ -204,6 +205,8 @@
     super.connectedCallback();
 
     this.clearBrowsingDataBrowserProxy_.initialize();
+
+    this.setFocusOutlineToVisible_();
   }
 
   private setUpDataTypeOptionLists_() {
@@ -339,9 +342,26 @@
     this.showOtherGoogleDataDialog_ = true;
   }
 
+  private setFocusOutlineToVisible_() {
+    // AutoFocus is not visible in mouse navigation by default. But in this
+    // dialog the default focus is on cancel which is not a default button. To
+    // make this clear to the user we make it visible to the user and remove
+    // the focus after the next mouse event.
+    const focusOutlineManager = FocusOutlineManager.forDocument(document);
+    focusOutlineManager.visible = true;
+
+    document.addEventListener('mousedown', () => {
+      focusOutlineManager.visible = false;
+    }, {once: true});
+  }
+
   private onOtherGoogleDataDialogClose_(e: Event) {
     e.stopPropagation();
     this.showOtherGoogleDataDialog_ = false;
+    afterNextRender(this, () => {
+      this.$.cancelButton.focus();
+      this.setFocusOutlineToVisible_();
+    });
   }
 }
 
diff --git a/chrome/browser/resources/settings/clear_browsing_data_dialog/clear_browsing_data_time_picker.html b/chrome/browser/resources/settings/clear_browsing_data_dialog/clear_browsing_data_time_picker.html
index 7e3d654..348770b 100644
--- a/chrome/browser/resources/settings/clear_browsing_data_dialog/clear_browsing_data_time_picker.html
+++ b/chrome/browser/resources/settings/clear_browsing_data_dialog/clear_browsing_data_time_picker.html
@@ -10,7 +10,6 @@
   .row {
     align-items: center;
     display: flex;
-    padding-bottom: var(--cr-section-padding);
   }
 
   .time-period-chip {
diff --git a/chrome/browser/resources/settings/clear_browsing_data_dialog/other_google_data_dialog.html b/chrome/browser/resources/settings/clear_browsing_data_dialog/other_google_data_dialog.html
index bc5706b..a0876f03 100644
--- a/chrome/browser/resources/settings/clear_browsing_data_dialog/other_google_data_dialog.html
+++ b/chrome/browser/resources/settings/clear_browsing_data_dialog/other_google_data_dialog.html
@@ -1,21 +1,85 @@
-<style include="cr-icons"></style>
+<style include="cr-icons cr-shared-style settings-shared">
+  #subpageDescription {
+    padding-bottom: 16px;
+  }
+
+  .cancel-button {
+    margin: 0;
+  }
+
+  .icon-arrow-back {
+    --cr-icon-button-margin-end: 12px;
+    --cr-icon-button-margin-start: 0;
+    --cr-icon-button-size: var(--cr-icon-size);
+  }
+
+  cr-link-row {
+    background-color: var(--dbd-container-color);
+    padding: 4px 16px;
+  }
+
+  cr-link-row:first-child {
+    border-bottom-left-radius: var(--dbd-container-stacked-border-radius);
+    border-bottom-right-radius: var(--dbd-container-stacked-border-radius);
+    border-top-left-radius: var(--dbd-container-border-radius);
+    border-top-right-radius: var(--dbd-container-border-radius);
+    margin-bottom: 2px;
+  }
+
+  cr-link-row:last-child {
+    border-bottom-left-radius: var(--dbd-container-border-radius);
+    border-bottom-right-radius: var(--dbd-container-border-radius);
+    border-top-left-radius: var(--dbd-container-stacked-border-radius);
+    border-top-right-radius: var(--dbd-container-stacked-border-radius);
+    margin-bottom: 0;
+  }
+
+  cr-link-row:not(:first-child):not(:last-child) {
+    border-radius: var(--dbd-container-stacked-border-radius);
+    margin-bottom: 2px;
+  }
+
+  cr-link-row:only-child {
+    border-radius: var(--dbd-container-border-radius);
+    margin-bottom: 0;
+  }
+
+  div[slot="title"] {
+    --cr-dialog-title-slot-padding-bottom: 12px;
+    --cr-dialog-title-slot-padding-top: 18px;
+    align-items: center;
+    display: flex;
+    font-size: 16px;
+    font-weight: 500;
+  }
+
+  div[slot="label"] {
+    color: var(--cr-primary-text-color);
+    font-weight: 500;
+  }
+</style>
+
 <cr-dialog id="dialog" show-on-attach>
   <div slot="title">
-<!-- TODO(crbug.com/417692423): Add autofocus for DBD and Other Google Data. -->
     <cr-icon-button class="icon-arrow-back" aria-label="$i18n{back}"
         on-click="onBackOrCancelClick_"></cr-icon-button>
-    <span class="title-text">$i18n{otherDataTitle}</span>
+    $i18n{otherDataTitle}
   </div>
   <div slot="body">
-    <div class="subpage-description">$i18n{otherDataDescription}</div>
-    <div>
-      <cr-link-row label="$i18n{passwordsAndPasskeys}"
-          sub-label="$i18n{manageInGooglePasswordManager}" external>
+    <div id="subpageDescription">$i18n{otherDataDescription}
+    </div>
+    <div class="link-rows-block">
+      <cr-link-row sub-label="$i18n{manageInGooglePasswordManager}" external
+          using-slotted-label>
+        <div slot="label">$i18n{passwordsAndPasskeys}</div>
       </cr-link-row>
-      <cr-link-row label="$i18n{myActivity}"
-          sub-label="$i18n{manageInYourGoogleAccount}" external>
+      <cr-link-row sub-label="$i18n{manageInYourGoogleAccount}" external
+          using-slotted-label>
+        <div slot="label">$i18n{myActivity}</div>
       </cr-link-row>
-      <cr-link-row label="$i18n{searchHistory}" external></cr-link-row>
+      <cr-link-row external using-slotted-label>
+        <div slot="label">$i18n{searchHistory}</div>
+      </cr-link-row>
     </div>
   </div>
   <div slot="button-container">
diff --git a/chrome/browser/touch_to_fill/autofill/android/touch_to_fill_delegate_android_impl.cc b/chrome/browser/touch_to_fill/autofill/android/touch_to_fill_delegate_android_impl.cc
index 0250244..3866616 100644
--- a/chrome/browser/touch_to_fill/autofill/android/touch_to_fill_delegate_android_impl.cc
+++ b/chrome/browser/touch_to_fill/autofill/android/touch_to_fill_delegate_android_impl.cc
@@ -186,15 +186,13 @@
   if (!vdm) {
     return DryRunResult(TriggerOutcome::kNoValidPaymentMethods, {});
   }
-  const std::vector<LoyaltyCard> loyalty_cards = vdm->GetLoyaltyCards();
-  if (loyalty_cards.empty()) {
-    return DryRunResult(TriggerOutcome::kNoValidPaymentMethods, {});
-  }
-  const GURL& current_domain =
-      manager_->client().GetLastCommittedPrimaryMainFrameURL();
+  const std::vector<LoyaltyCard> loyalty_cards =
+      vdm->GetLoyaltyCardsToSuggest();
 
   // Only show the TTF surface if any loyalty card have a matching merchant
   // domain.
+  const GURL& current_domain =
+      manager_->client().GetLastCommittedPrimaryMainFrameURL();
   if (std::ranges::any_of(
           loyalty_cards, [&current_domain](const LoyaltyCard& loyalty_card) {
             return loyalty_card.HasMatchingMerchantDomain(current_domain);
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 0654bf3d..ceebbe25f 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
@@ -1151,16 +1151,18 @@
 
 TEST_F(TouchToFillDelegateAndroidImplLoyaltyCardUnitTest,
        PassTheLoyaltyCardsToTheClient) {
-  // TODO: crbug.com/404437211 - Test that the loyalty cards are sorted.
-  LoyaltyCard card = test::CreateLoyaltyCard();
-  std::vector<LoyaltyCard> loyalty_cards{card};
+  LoyaltyCard card1 = test::CreateLoyaltyCard();
+  LoyaltyCard card2 = test::CreateLoyaltyCard2();
+  std::vector<LoyaltyCard> loyalty_cards{card2, card1};
+  // Makes sure there is at least one affiliated card available.
   autofill_client_.set_last_committed_primary_main_frame_url(
-      card.merchant_domains()[0]);
+      card1.merchant_domains()[0]);
   test_api(*autofill_client_.GetValuablesDataManager())
       .SetLoyaltyCards(loyalty_cards);
 
+  // Cards must be sorted by merchant name.
   EXPECT_CALL(payments_autofill_client(),
-              ShowTouchToFillLoyaltyCard(_, ElementsAreArray(loyalty_cards)));
+              ShowTouchToFillLoyaltyCard(_, ElementsAre(card1, card2)));
 
   TryToShowTouchToFill(/*expected_success=*/true);
 }
diff --git a/chrome/browser/touch_to_fill/password_manager/touch_to_fill_controller.h b/chrome/browser/touch_to_fill/password_manager/touch_to_fill_controller.h
index 4865200..43fb704 100644
--- a/chrome/browser/touch_to_fill/password_manager/touch_to_fill_controller.h
+++ b/chrome/browser/touch_to_fill/password_manager/touch_to_fill_controller.h
@@ -30,7 +30,7 @@
 class Profile;
 class TouchToFillControllerDelegate;
 
-class TouchToFillController final {
+class TouchToFillController {
  public:
   // Convenience enum for selecting the correct UI that this controller can
   // display.
@@ -50,11 +50,11 @@
           grouped_credential_sheet_controller);
   TouchToFillController(const TouchToFillController&) = delete;
   TouchToFillController& operator=(const TouchToFillController&) = delete;
-  ~TouchToFillController();
+  virtual ~TouchToFillController();
 
   // Sets the credentials and passkeys that will be shown in the sheet. Also
   // sets the `frame_driver` for which TTF is expected to be shown.
-  void InitData(
+  virtual void InitData(
       base::span<const password_manager::UiCredential> credentials,
       std::vector<password_manager::PasskeyCredential> passkey_credentials,
       base::WeakPtr<password_manager::ContentPasswordManagerDriver>
@@ -63,8 +63,8 @@
   // Instructs the controller to show the provided the bottom sheet.
   // IMPORTANT: call `InitData` prior to this method to set |credentials| and
   // |passkey_credentials|, that will be displayed.
-  bool Show(std::unique_ptr<TouchToFillControllerDelegate> ttf_delegate,
-            webauthn::WebAuthnCredManDelegate* cred_man_delegate);
+  virtual bool Show(std::unique_ptr<TouchToFillControllerDelegate> ttf_delegate,
+                    webauthn::WebAuthnCredManDelegate* cred_man_delegate);
 
   // Informs the controller that the user has made a selection. Invokes both
   // FillSuggestion() and TouchToFillDismissed() on |driver_|. No-op if invoked
diff --git a/chrome/browser/ui/BUILD.gn b/chrome/browser/ui/BUILD.gn
index 9ecfe77..cae0c59 100644
--- a/chrome/browser/ui/BUILD.gn
+++ b/chrome/browser/ui/BUILD.gn
@@ -1072,6 +1072,8 @@
       "passwords/password_base_dialog_controller.h",
       "passwords/password_change_icon_views_controller.cc",
       "passwords/password_change_icon_views_controller.h",
+      "passwords/password_change_ui_controller.cc",
+      "passwords/password_change_ui_controller.h",
       "passwords/password_dialog_prompts.h",
       "passwords/password_generation_popup_controller.h",
       "passwords/password_generation_popup_controller_impl.cc",
@@ -1710,6 +1712,7 @@
       "//chrome/browser/ui/views/toolbar",
       "//chrome/browser/ui/views/zoom",
       "//chrome/browser/ui/webui:webui_util",
+      "//chrome/browser/ui/webui/actor_internals",
       "//chrome/browser/ui/webui/app_management",
       "//chrome/browser/ui/webui/commerce",
       "//chrome/browser/ui/webui/commerce:impl",
diff --git a/chrome/browser/ui/android/autofill/autofill_keyboard_accessory_view_impl.cc b/chrome/browser/ui/android/autofill/autofill_keyboard_accessory_view_impl.cc
index 3cef80c3..c39d2de9 100644
--- a/chrome/browser/ui/android/autofill/autofill_keyboard_accessory_view_impl.cc
+++ b/chrome/browser/ui/android/autofill/autofill_keyboard_accessory_view_impl.cc
@@ -117,6 +117,13 @@
       }
     }
 
+    base::android::ScopedJavaLocalRef<jobject> payload;
+    if (const Suggestion::AutofillProfilePayload* profile_payload =
+            std::get_if<Suggestion::AutofillProfilePayload>(
+                &suggestion.payload)) {
+      payload = profile_payload->CreateJavaObject();
+    }
+
     auto* custom_icon_url =
         std::get_if<Suggestion::CustomIconUrl>(&suggestion.custom_icon);
     java_suggestions.push_back(
@@ -131,7 +138,7 @@
             custom_icon_url
                 ? url::GURLAndroid::FromNativeGURL(env, **custom_icon_url)
                 : url::GURLAndroid::EmptyGURL(env),
-            suggestion.HasDeactivatedStyle()));
+            suggestion.HasDeactivatedStyle(), payload));
   }
   Java_AutofillKeyboardAccessoryViewBridge_show(env, java_object_,
                                                 std::move(java_suggestions));
diff --git a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/SearchEngineUtils.java b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/SearchEngineUtils.java
index 0039875..c5fc6a7 100644
--- a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/SearchEngineUtils.java
+++ b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/SearchEngineUtils.java
@@ -31,7 +31,9 @@
 import org.chromium.chrome.browser.profiles.Profile;
 import org.chromium.chrome.browser.profiles.ProfileKeyedMap;
 import org.chromium.chrome.browser.search_engines.TemplateUrlServiceFactory;
+import org.chromium.chrome.browser.theme.ThemeUtils;
 import org.chromium.chrome.browser.ui.favicon.FaviconHelper;
+import org.chromium.chrome.browser.ui.theme.BrandedColorScheme;
 import org.chromium.components.browser_ui.util.GlobalDiscardableReferencePool;
 import org.chromium.components.image_fetcher.ImageFetcher;
 import org.chromium.components.image_fetcher.ImageFetcherConfig;
@@ -44,7 +46,6 @@
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
-import java.util.Objects;
 
 /** Common Default Search Engine functions. */
 @NullMarked
@@ -64,8 +65,6 @@
     private final int mSearchEngineLogoTargetSizePixels;
     private final ObserverList<SearchBoxHintTextObserver> mSearchBoxHintTextObservers =
             new ObserverList<>();
-    private final ObserverList<SearchEngineIconObserver> mSearchEngineIconObservers =
-            new ObserverList<>();
     private @Nullable SearchEngineMetadata mDefaultSearchEngineMetadata;
     private @Nullable Boolean mNeedToCheckForSearchEnginePromo;
     private boolean mDoesDefaultSearchEngineHaveLogo;
@@ -108,16 +107,6 @@
         void onSearchBoxHintTextChanged(String newHintText);
     }
 
-    @FunctionalInterface
-    public interface SearchEngineIconObserver {
-        /**
-         * Invoked when the Search Engine icon changes.
-         *
-         * @param newIcon the new search engine icon to apply
-         */
-        void onSearchEngineIconChanged(@Nullable StatusIconResource newIcon);
-    }
-
     @VisibleForTesting
     SearchEngineUtils(Profile profile, FaviconHelper faviconHelper) {
         mProfile = profile;
@@ -166,7 +155,6 @@
         mTemplateUrlService.removeObserver(this);
         mFaviconHelper.destroy();
         mImageFetcher.destroy();
-        mSearchEngineIconObservers.clear();
         mSearchBoxHintTextObservers.clear();
     }
 
@@ -228,26 +216,6 @@
         }
     }
 
-    /** Add observer to be notified whenever the Search Enigne Icon changes. */
-    public void addIconObserver(SearchEngineIconObserver observer) {
-        mSearchEngineIconObservers.addObserver(observer);
-        observer.onSearchEngineIconChanged(mFavicon);
-    }
-
-    /** Remove previously registered Search Engine Icon observer. */
-    public void removeIconObserver(SearchEngineIconObserver observer) {
-        mSearchEngineIconObservers.removeObserver(observer);
-    }
-
-    private void setSearchEngineIcon(@Nullable StatusIconResource newIcon) {
-        if (Objects.equals(mFavicon, newIcon)) return;
-        mFavicon = newIcon;
-        for (var observer : mSearchEngineIconObservers) {
-            observer.onSearchEngineIconChanged(newIcon);
-            recordEvent(Events.FETCH_SUCCESS_CACHE_HIT);
-        }
-    }
-
     @VisibleForTesting
     void retrieveFavicon(TemplateUrl templateUrl) {
         if (!mTemplateUrlService.isDefaultSearchEngineGoogle()) {
@@ -257,7 +225,7 @@
             return;
         }
 
-        setSearchEngineIcon(new StatusIconResource(R.drawable.ic_logo_googleg_20dp, 0));
+        mFavicon = new StatusIconResource(R.drawable.ic_logo_googleg_20dp, 0);
     }
 
     private void retrieveFaviconFromFaviconUrl(TemplateUrl templateUrl) {
@@ -305,11 +273,11 @@
     }
 
     private void resetFavicon() {
-        setSearchEngineIcon(null);
+        mFavicon = null;
     }
 
     private void onFaviconRetrieveCompleted(GURL faviconUrl, Bitmap bitmap) {
-        setSearchEngineIcon(new StatusIconResource(faviconUrl.getSpec(), bitmap, 0));
+        mFavicon = new StatusIconResource(faviconUrl.getSpec(), bitmap, 0);
         recordEvent(Events.FETCH_SUCCESS);
     }
 
@@ -319,6 +287,35 @@
     }
 
     /**
+     * Get the search engine logo favicon. This can return a null bitmap under certain
+     * circumstances, such as: no logo url found, network/cache error, etc.
+     *
+     * @param brandedColorScheme The {@link BrandedColorScheme}, used to tint icons.
+     */
+    public StatusIconResource getSearchEngineLogo(@BrandedColorScheme int brandedColorScheme) {
+        if (needToCheckForSearchEnginePromo() || mFavicon == null) {
+            return getFallbackSearchIcon(brandedColorScheme);
+        }
+        recordEvent(Events.FETCH_SUCCESS_CACHE_HIT);
+        return mFavicon;
+    }
+
+    /** Returns an icon to be shown as a fallback Search icon. */
+    public static StatusIconResource getFallbackSearchIcon(
+            @BrandedColorScheme int brandedColorScheme) {
+        return new StatusIconResource(
+                R.drawable.ic_search, ThemeUtils.getThemedToolbarIconTintRes(brandedColorScheme));
+    }
+
+    /** Returns an icon to be shown as a fallback Navigation icon. */
+    public static StatusIconResource getFallbackNavigationIcon(
+            @BrandedColorScheme int brandedColorScheme) {
+        return new StatusIconResource(
+                R.drawable.ic_globe_24dp,
+                ThemeUtils.getThemedToolbarIconTintRes(brandedColorScheme));
+    }
+
+    /**
      * Returns whether the search engine promo is complete. Once fetchCheckForSearchEnginePromo()
      * returns false the first time, this method will cache that result as it's presumed we don't
      * need to re-run the promo during the process lifetime.
diff --git a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/SearchEngineUtilsUnitTest.java b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/SearchEngineUtilsUnitTest.java
index 767744ec..2ca3acb 100644
--- a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/SearchEngineUtilsUnitTest.java
+++ b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/SearchEngineUtilsUnitTest.java
@@ -17,7 +17,6 @@
 import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyNoMoreInteractions;
 import static org.robolectric.Shadows.shadowOf;
 
 import android.content.Context;
@@ -25,6 +24,7 @@
 import android.graphics.Bitmap;
 
 import org.junit.After;
+import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
@@ -51,7 +51,9 @@
 import org.chromium.chrome.browser.omnibox.test.R;
 import org.chromium.chrome.browser.profiles.Profile;
 import org.chromium.chrome.browser.search_engines.TemplateUrlServiceFactory;
+import org.chromium.chrome.browser.theme.ThemeUtils;
 import org.chromium.chrome.browser.ui.favicon.FaviconHelper;
+import org.chromium.chrome.browser.ui.theme.BrandedColorScheme;
 import org.chromium.components.embedder_support.util.UrlConstants;
 import org.chromium.components.metrics.OmniboxEventProtos.OmniboxEventProto.PageClassification;
 import org.chromium.components.omnibox.OmniboxFeatureList;
@@ -76,7 +78,6 @@
     @Mock Resources mResources;
     @Mock Profile mProfile;
     @Mock SearchEngineUtils.SearchBoxHintTextObserver mHintTextObserver;
-    @Mock SearchEngineUtils.SearchEngineIconObserver mEngineIconObserver;
 
     private Context mContext;
     private Bitmap mBitmap;
@@ -152,10 +153,6 @@
     @Test
     public void getSearchEngineLogo() {
         var searchEngineUtils = new SearchEngineUtils(mProfile, mFaviconHelper);
-        searchEngineUtils.addIconObserver(mEngineIconObserver);
-        verify(mEngineIconObserver).onSearchEngineIconChanged(null);
-        reset(mEngineIconObserver);
-
         // SearchEngineUtils retrieves logo when it's first created, and whenever the DSE changes.
         verify(mFaviconHelper).getLocalFaviconImageForURL(any(), any(), anyInt(), any());
         mCallbackCaptor.getValue().onFaviconAvailable(mBitmap, new GURL(LOGO_URL));
@@ -169,36 +166,32 @@
                 RecordHistogram.getHistogramValueCountForTesting(
                         EVENTS_HISTOGRAM, SearchEngineUtils.Events.FETCH_SUCCESS));
 
-        ArgumentCaptor<StatusIconResource> captor =
-                ArgumentCaptor.forClass(StatusIconResource.class);
-        verify(mEngineIconObserver).onSearchEngineIconChanged(captor.capture());
-
-        assertEquals(captor.getValue(), new StatusIconResource(LOGO_URL, mBitmap, 0));
+        var expected = new StatusIconResource(LOGO_URL, mBitmap, 0);
+        var icon = searchEngineUtils.getSearchEngineLogo(BrandedColorScheme.APP_DEFAULT);
+        assertEquals(expected, icon);
     }
 
     @Test
     public void getSearchEngineLogo_nullTemplateUrlService() {
         var searchEngineUtils = new SearchEngineUtils(mProfile, mFaviconHelper);
-        searchEngineUtils.addIconObserver(mEngineIconObserver);
+        StatusIconResource expected =
+                SearchEngineUtils.getFallbackSearchIcon(BrandedColorScheme.APP_DEFAULT);
 
-        verify(mEngineIconObserver).onSearchEngineIconChanged(null);
+        var icon = searchEngineUtils.getSearchEngineLogo(BrandedColorScheme.APP_DEFAULT);
+
+        assertEquals(expected, icon);
     }
 
     @Test
     public void getSearchEngineLogo_searchEngineGoogle() {
         var searchEngineUtils = new SearchEngineUtils(mProfile, mFaviconHelper);
-        searchEngineUtils.addIconObserver(mEngineIconObserver);
-        verify(mEngineIconObserver).onSearchEngineIconChanged(null);
-        reset(mEngineIconObserver);
-
         // Simulate DSE change to Google.
         doReturn(true).when(mTemplateUrlService).isDefaultSearchEngineGoogle();
         searchEngineUtils.onTemplateURLServiceChanged();
 
-        ArgumentCaptor<StatusIconResource> captor =
-                ArgumentCaptor.forClass(StatusIconResource.class);
-        verify(mEngineIconObserver).onSearchEngineIconChanged(captor.capture());
-        assertEquals(captor.getValue(), new StatusIconResource(R.drawable.ic_logo_googleg_20dp, 0));
+        var expected = new StatusIconResource(R.drawable.ic_logo_googleg_20dp, 0);
+        var icon = searchEngineUtils.getSearchEngineLogo(BrandedColorScheme.APP_DEFAULT);
+        assertEquals(expected, icon);
     }
 
     private void configureSearchEngine(String keyword, String shortName) {
@@ -437,17 +430,15 @@
     @Test
     public void getSearchEngineLogo_faviconCached() {
         var searchEngineUtils = new SearchEngineUtils(mProfile, mFaviconHelper);
-        searchEngineUtils.addIconObserver(mEngineIconObserver);
-        verify(mEngineIconObserver).onSearchEngineIconChanged(null);
-        reset(mEngineIconObserver);
-
         verify(mFaviconHelper).getLocalFaviconImageForURL(any(), any(), anyInt(), any());
         mCallbackCaptor.getValue().onFaviconAvailable(mBitmap, new GURL(LOGO_URL));
 
-        ArgumentCaptor<StatusIconResource> captor =
-                ArgumentCaptor.forClass(StatusIconResource.class);
-        verify(mEngineIconObserver).onSearchEngineIconChanged(captor.capture());
-        assertEquals(captor.getValue(), new StatusIconResource(LOGO_URL, mBitmap, 0));
+        var expected = new StatusIconResource(LOGO_URL, mBitmap, 0);
+        var icon = searchEngineUtils.getSearchEngineLogo(BrandedColorScheme.APP_DEFAULT);
+        assertEquals(expected, icon);
+
+        icon = searchEngineUtils.getSearchEngineLogo(BrandedColorScheme.APP_DEFAULT);
+        assertEquals(expected, icon);
 
         // Expect only one actual fetch, that happens independently from get request.
         // All get requests always supply cached value.
@@ -460,7 +451,7 @@
                 RecordHistogram.getHistogramValueCountForTesting(
                         EVENTS_HISTOGRAM, SearchEngineUtils.Events.FETCH_SUCCESS));
         assertEquals(
-                1,
+                2,
                 RecordHistogram.getHistogramValueCountForTesting(
                         EVENTS_HISTOGRAM, SearchEngineUtils.Events.FETCH_SUCCESS_CACHE_HIT));
     }
@@ -469,14 +460,15 @@
     public void getSearchEngineLogo_nullUrl() {
         UmaRecorderHolder.resetForTesting();
         var searchEngineUtils = new SearchEngineUtils(mProfile, mFaviconHelper);
-        searchEngineUtils.addIconObserver(mEngineIconObserver);
 
         // Simulate DSE change - policy blocking searches
         doReturn(null).when(mTemplateUrlService).getDefaultSearchEngineTemplateUrl();
         searchEngineUtils.onTemplateURLServiceChanged();
 
-        verify(mEngineIconObserver).onSearchEngineIconChanged(null);
+        var expected = SearchEngineUtils.getFallbackSearchIcon(BrandedColorScheme.APP_DEFAULT);
+        var icon = searchEngineUtils.getSearchEngineLogo(BrandedColorScheme.APP_DEFAULT);
 
+        assertEquals(expected, icon);
         assertEquals(
                 1,
                 RecordHistogram.getHistogramValueCountForTesting(
@@ -496,10 +488,11 @@
                 .when(mFaviconHelper)
                 .getLocalFaviconImageForURL(any(), any(), anyInt(), mCallbackCaptor.capture());
         var searchEngineUtils = new SearchEngineUtils(mProfile, mFaviconHelper);
-        searchEngineUtils.addIconObserver(mEngineIconObserver);
 
-        verify(mEngineIconObserver).onSearchEngineIconChanged(null);
+        var expected = SearchEngineUtils.getFallbackSearchIcon(BrandedColorScheme.APP_DEFAULT);
+        var icon = searchEngineUtils.getSearchEngineLogo(BrandedColorScheme.APP_DEFAULT);
 
+        assertEquals(expected, icon);
         assertEquals(
                 1,
                 RecordHistogram.getHistogramValueCountForTesting(
@@ -514,16 +507,16 @@
     @Test
     public void getSearchEngineLogo_returnedBitmapNull() {
         var searchEngineUtils = new SearchEngineUtils(mProfile, mFaviconHelper);
-        searchEngineUtils.addIconObserver(mEngineIconObserver);
+        StatusIconResource expected =
+                SearchEngineUtils.getFallbackSearchIcon(BrandedColorScheme.APP_DEFAULT);
 
-        verify(mEngineIconObserver).onSearchEngineIconChanged(null);
-        reset(mEngineIconObserver);
-
+        var icon = searchEngineUtils.getSearchEngineLogo(BrandedColorScheme.APP_DEFAULT);
         verify(mFaviconHelper)
                 .getLocalFaviconImageForURL(any(), any(), anyInt(), mCallbackCaptor.capture());
         FaviconHelper.FaviconImageCallback faviconCallback = mCallbackCaptor.getValue();
         faviconCallback.onFaviconAvailable(null, new GURL(LOGO_URL));
 
+        assertEquals(expected, icon);
         assertEquals(
                 1,
                 RecordHistogram.getHistogramValueCountForTesting(
@@ -533,9 +526,44 @@
                 RecordHistogram.getHistogramValueCountForTesting(
                         EVENTS_HISTOGRAM,
                         SearchEngineUtils.Events.FETCH_FAILED_RETURNED_BITMAP_NULL));
+    }
 
-        // Not emitting second null icon
-        verifyNoMoreInteractions(mEngineIconObserver);
+    @Test
+    public void getFallbackSearchIcon() {
+        var searchEngineUtils = new SearchEngineUtils(mProfile, mFaviconHelper);
+        StatusIconResource expected =
+                new StatusIconResource(
+                        R.drawable.ic_search, R.color.default_icon_color_white_tint_list);
+        Assert.assertEquals(
+                expected,
+                SearchEngineUtils.getFallbackSearchIcon(BrandedColorScheme.DARK_BRANDED_THEME));
+
+        expected =
+                new StatusIconResource(
+                        R.drawable.ic_search,
+                        ThemeUtils.getThemedToolbarIconTintRes(/* useLight= */ true));
+        Assert.assertEquals(
+                expected, SearchEngineUtils.getFallbackSearchIcon(BrandedColorScheme.INCOGNITO));
+    }
+
+    @Test
+    public void getFallbackNavigationIcon() {
+        var searchEngineUtils = new SearchEngineUtils(mProfile, mFaviconHelper);
+        StatusIconResource expected =
+                new StatusIconResource(
+                        R.drawable.ic_globe_24dp, R.color.default_icon_color_white_tint_list);
+
+        Assert.assertEquals(
+                expected,
+                SearchEngineUtils.getFallbackNavigationIcon(BrandedColorScheme.DARK_BRANDED_THEME));
+
+        expected =
+                new StatusIconResource(
+                        R.drawable.ic_globe_24dp,
+                        ThemeUtils.getThemedToolbarIconTintRes(/* useLight= */ true));
+        Assert.assertEquals(
+                expected,
+                SearchEngineUtils.getFallbackNavigationIcon(BrandedColorScheme.INCOGNITO));
     }
 
     @Test
diff --git a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/status/StatusCoordinator.java b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/status/StatusCoordinator.java
index bfa3f0d4b..1e77215 100644
--- a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/status/StatusCoordinator.java
+++ b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/status/StatusCoordinator.java
@@ -19,7 +19,6 @@
 import androidx.annotation.VisibleForTesting;
 
 import org.chromium.base.ContextUtils;
-import org.chromium.base.supplier.ObservableSupplier;
 import org.chromium.base.supplier.OneshotSupplier;
 import org.chromium.base.supplier.Supplier;
 import org.chromium.build.annotations.NullMarked;
@@ -92,7 +91,7 @@
             UrlBarEditingTextStateProvider urlBarEditingTextStateProvider,
             LocationBarDataProvider locationBarDataProvider,
             OneshotSupplier<TemplateUrlService> templateUrlServiceSupplier,
-            ObservableSupplier<Profile> profileSupplier,
+            Supplier<Profile> profileSupplier,
             WindowAndroid windowAndroid,
             PageInfoAction pageInfoAction,
             @Nullable Supplier<MerchantTrustSignalsCoordinator>
diff --git a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/status/StatusMediator.java b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/status/StatusMediator.java
index 4577467..777133c3 100644
--- a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/status/StatusMediator.java
+++ b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/status/StatusMediator.java
@@ -17,7 +17,6 @@
 import androidx.annotation.StringRes;
 import androidx.annotation.VisibleForTesting;
 
-import org.chromium.base.supplier.ObservableSupplier;
 import org.chromium.base.supplier.OneshotSupplier;
 import org.chromium.base.supplier.Supplier;
 import org.chromium.build.annotations.NullMarked;
@@ -63,8 +62,7 @@
         implements PermissionDialogController.Observer,
                 TemplateUrlServiceObserver,
                 MerchantTrustSignalsCoordinator.OmniboxIconController,
-                CookieControlsObserver,
-                SearchEngineUtils.SearchEngineIconObserver {
+                CookieControlsObserver {
     private static final int PERMISSION_ICON_DEFAULT_DISPLAY_TIMEOUT_MS = 8500;
     public static final String PERMISSION_ICON_TIMEOUT_MS_PARAM = "PermissionIconTimeoutMs";
 
@@ -72,7 +70,7 @@
 
     private final PropertyModel mModel;
     private final OneshotSupplier<TemplateUrlService> mTemplateUrlServiceSupplier;
-    private final ObservableSupplier<Profile> mProfileSupplier;
+    private final Supplier<Profile> mProfileSupplier;
     private final @Nullable Supplier<MerchantTrustSignalsCoordinator>
             mMerchantTrustSignalsCoordinatorSupplier;
     // When the parity update is enabled, we want to:
@@ -118,8 +116,6 @@
     private float mUrlFocusPercent;
 
     private @Nullable CookieControlsBridge mCookieControlsBridge;
-    private @Nullable SearchEngineUtils mSearchEngineUtils;
-    private @Nullable StatusIconResource mSearchEngineIcon;
     private int mBlockingStatus3pcd;
     private int mLastTabId;
     private boolean mCurrentTabCrashed;
@@ -152,7 +148,7 @@
             LocationBarDataProvider locationBarDataProvider,
             PermissionDialogController permissionDialogController,
             OneshotSupplier<TemplateUrlService> templateUrlServiceSupplier,
-            ObservableSupplier<Profile> profileSupplier,
+            Supplier<Profile> profileSupplier,
             PageInfoIphController pageInfoIphController,
             WindowAndroid windowAndroid,
             @Nullable Supplier<MerchantTrustSignalsCoordinator>
@@ -184,15 +180,6 @@
         mPermissionDialogController = permissionDialogController;
         mPermissionDialogController.addObserver(this);
 
-        mProfileSupplier.addObserver(
-                p -> {
-                    if (mSearchEngineUtils != null) {
-                        mSearchEngineUtils.removeIconObserver(this);
-                    }
-                    mSearchEngineUtils = SearchEngineUtils.getForProfile(p);
-                    mSearchEngineUtils.addIconObserver(this);
-                });
-
         updateColorTheme();
         setStatusIconShown(
                 /* show= */ mParityUpdateEnabled || !mLocationBarDataProvider.isIncognitoBranded());
@@ -200,11 +187,6 @@
     }
 
     public void destroy() {
-        if (mSearchEngineUtils != null) {
-            mSearchEngineUtils.removeIconObserver(this);
-            mSearchEngineUtils = null;
-        }
-
         mPermissionTaskHandler.removeCallbacksAndMessages(null);
         mPermissionDialogController.removeObserver(this);
         mStoreIconHandler.removeCallbacksAndMessages(null);
@@ -611,18 +593,15 @@
     private StatusIconResource getStatusIconResourceForSearchEngineIcon() {
         // If the current url text is a valid url, then swap the dse icon for a globe.
         if (!mUrlBarTextIsSearch) {
-            return new StatusIconResource(
-                    R.drawable.ic_globe_24dp,
-                    ThemeUtils.getThemedToolbarIconTintRes(mBrandedColorScheme));
+            return SearchEngineUtils.getFallbackNavigationIcon(mBrandedColorScheme);
         }
 
-        if (mSearchEngineIcon == null) {
-            return new StatusIconResource(
-                    R.drawable.ic_search,
-                    ThemeUtils.getThemedToolbarIconTintRes(mBrandedColorScheme));
+        if (!mProfileSupplier.hasValue()) {
+            return SearchEngineUtils.getFallbackSearchIcon(mBrandedColorScheme);
         }
 
-        return mSearchEngineIcon;
+        var profile = mProfileSupplier.get();
+        return SearchEngineUtils.getForProfile(profile).getSearchEngineLogo(mBrandedColorScheme);
     }
 
     /** Return the resource id for the accessibility description or 0 if none apply. */
@@ -866,12 +845,6 @@
         updateLocationBarIcon(IconTransitionType.CROSSFADE);
     }
 
-    @Override
-    public void onSearchEngineIconChanged(@Nullable StatusIconResource newIcon) {
-        mSearchEngineIcon = newIcon;
-        maybeUpdateStatusIconForSearchEngineIcon();
-    }
-
     void setTranslationX(float translationX) {
         mModel.set(StatusProperties.TRANSLATION_X, translationX);
     }
diff --git a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/status/StatusMediatorUnitTest.java b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/status/StatusMediatorUnitTest.java
index 1b3cdd50..c0d7960 100644
--- a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/status/StatusMediatorUnitTest.java
+++ b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/status/StatusMediatorUnitTest.java
@@ -130,6 +130,7 @@
 
         doReturn(false).when(mLocationBarDataProvider).isIncognito();
         doReturn(mNewTabPageDelegate).when(mLocationBarDataProvider).getNewTabPageDelegate();
+        doReturn(logo).when(mSearchEngineUtils).getSearchEngineLogo(BrandedColorScheme.APP_DEFAULT);
 
         UserPrefsJni.setInstanceForTesting(mMockUserPrefsJni);
         doReturn(mPrefs).when(mMockUserPrefsJni).get(mProfile);
@@ -137,8 +138,6 @@
         TrackerFactory.setTrackerForTests(mTracker);
 
         setupStatusMediator(/* isTablet= */ false);
-
-        mMediator.onSearchEngineIconChanged(logo);
     }
 
     @After
@@ -160,7 +159,7 @@
                         mLocationBarDataProvider,
                         mPermissionDialogController,
                         mTemplateUrlServiceSupplier,
-                        new ObservableSupplierImpl(mProfile),
+                        () -> mProfile,
                         mPageInfoIphController,
                         mWindowAndroid,
                         merchantTrustSignalsCoordinatorObservableSupplier);
@@ -415,6 +414,8 @@
         mMediator.setUrlFocusChangePercent(0.9f);
         Assert.assertEquals(true, mModel.get(StatusProperties.SHOW_STATUS_ICON));
 
+        verify(mSearchEngineUtils, times(1)).getSearchEngineLogo(BrandedColorScheme.APP_DEFAULT);
+
         mMediator.setUrlFocusChangePercent(0.0f);
         Assert.assertEquals(false, mModel.get(StatusProperties.SHOW_STATUS_ICON));
     }
@@ -534,6 +535,16 @@
 
     @Test
     @SmallTest
+    public void testTemplateUrlServiceChanged() {
+        mMediator.setShowIconsWhenUrlFocused(true);
+        mMediator.setUrlHasFocus(true);
+
+        mMediator.onTemplateURLServiceChanged();
+        verify(mSearchEngineUtils, times(2)).getSearchEngineLogo(BrandedColorScheme.APP_DEFAULT);
+    }
+
+    @Test
+    @SmallTest
     public void testSetStoreIconController() {
         mMediator.setStoreIconController();
         verify(mMerchantTrustSignalsCoordinator, times(1)).setOmniboxIconController(eq(mMediator));
diff --git a/chrome/browser/ui/ash/shelf/chrome_shelf_controller_unittest.cc b/chrome/browser/ui/ash/shelf/chrome_shelf_controller_unittest.cc
index afefca9e..4fa87f6 100644
--- a/chrome/browser/ui/ash/shelf/chrome_shelf_controller_unittest.cc
+++ b/chrome/browser/ui/ash/shelf/chrome_shelf_controller_unittest.cc
@@ -133,6 +133,8 @@
 #include "chrome/browser/ui/web_applications/test/web_app_browsertest_util.h"
 #include "chrome/browser/web_applications/isolated_web_apps/isolated_web_app_url_info.h"
 #include "chrome/browser/web_applications/isolated_web_apps/test/isolated_web_app_builder.h"
+#include "chrome/browser/web_applications/policy/app_service_web_app_policy.h"
+#include "chrome/browser/web_applications/policy/web_app_policy_manager.h"
 #include "chrome/browser/web_applications/preinstalled_web_app_manager.h"
 #include "chrome/browser/web_applications/test/fake_web_app_provider.h"
 #include "chrome/browser/web_applications/test/web_app_install_test_utils.h"
@@ -3487,7 +3489,7 @@
   base::flat_map<std::string_view, std::string_view>
       preinstalled_web_apps_mapping;
   preinstalled_web_apps_mapping.emplace(kGmailPolicyId, ash::kGmailAppId);
-  web_app::SetPreinstalledWebAppsMappingForTesting(
+  web_app::WebAppPolicyManager::SetPreinstalledWebAppsMappingForTesting(
       preinstalled_web_apps_mapping);
 
   // Force-install Gmail.
diff --git a/chrome/browser/ui/autofill/DEPS b/chrome/browser/ui/autofill/DEPS
index f939f7b6..0a8699b 100644
--- a/chrome/browser/ui/autofill/DEPS
+++ b/chrome/browser/ui/autofill/DEPS
@@ -1,5 +1,6 @@
 include_rules = [
   '+components/android_autofill',
+  '+components/credential_management',
   '+components/plus_addresses',
   '+components/webauthn',
   '+third_party/libaddressinput/chromium/addressinput_util.h',
diff --git a/chrome/browser/ui/autofill/autofill_suggestion_controller_utils.cc b/chrome/browser/ui/autofill/autofill_suggestion_controller_utils.cc
index be7ae86..fd25b90 100644
--- a/chrome/browser/ui/autofill/autofill_suggestion_controller_utils.cc
+++ b/chrome/browser/ui/autofill/autofill_suggestion_controller_utils.cc
@@ -77,7 +77,6 @@
     case SuggestionType::kFillExistingPlusAddress:
     case SuggestionType::kFillPassword:
     case SuggestionType::kGeneratePasswordEntry:
-    case SuggestionType::kHomeAndWorkAddressEntry:
     case SuggestionType::kIbanEntry:
     case SuggestionType::kInsecureContextPaymentDisabledMessage:
     case SuggestionType::kLoyaltyCardEntry:
diff --git a/chrome/browser/ui/autofill/chrome_autofill_client.cc b/chrome/browser/ui/autofill/chrome_autofill_client.cc
index 71e70c17..246031a0 100644
--- a/chrome/browser/ui/autofill/chrome_autofill_client.cc
+++ b/chrome/browser/ui/autofill/chrome_autofill_client.cc
@@ -1145,6 +1145,15 @@
   return std::make_unique<BrowserAutofillManager>(&driver);
 }
 
+credential_management::ContentCredentialManager*
+ChromeAutofillClient::GetContentCredentialManager() {
+  if (auto* chrome_password_manager_client =
+          ChromePasswordManagerClient::FromWebContents(web_contents())) {
+    return chrome_password_manager_client->GetContentCredentialManager();
+  }
+  return nullptr;
+}
+
 void ChromeAutofillClient::set_test_addresses(
     std::vector<AutofillProfile> test_addresses) {
   test_addresses_ = std::move(test_addresses);
diff --git a/chrome/browser/ui/autofill/chrome_autofill_client.h b/chrome/browser/ui/autofill/chrome_autofill_client.h
index 92735986..51cc786 100644
--- a/chrome/browser/ui/autofill/chrome_autofill_client.h
+++ b/chrome/browser/ui/autofill/chrome_autofill_client.h
@@ -246,6 +246,10 @@
       base::PassKey<ContentAutofillDriver> pass_key,
       ContentAutofillDriver& driver) final;
 
+  // ContentAutofillClient:
+  credential_management::ContentCredentialManager* GetContentCredentialManager()
+      override;
+
  protected:
   explicit ChromeAutofillClient(content::WebContents* web_contents);
 
diff --git a/chrome/browser/ui/autofill/payments/chrome_payments_autofill_client.cc b/chrome/browser/ui/autofill/payments/chrome_payments_autofill_client.cc
index b2597ec..1d957a3 100644
--- a/chrome/browser/ui/autofill/payments/chrome_payments_autofill_client.cc
+++ b/chrome/browser/ui/autofill/payments/chrome_payments_autofill_client.cc
@@ -923,14 +923,12 @@
 #if BUILDFLAG(IS_ANDROID)
   const GURL& current_domain = client_->GetLastCommittedPrimaryMainFrameURL();
 
-  auto non_affiliated_loyalty_cards = std::ranges::stable_partition(
-      loyalty_cards_to_suggest,
-      [&current_domain](const autofill::LoyaltyCard& card) {
-        return card.HasMatchingMerchantDomain(current_domain);
-      });
-
-  std::vector<autofill::LoyaltyCard> affiliated_loyalty_cards(
-      loyalty_cards_to_suggest.begin(), non_affiliated_loyalty_cards.begin());
+  std::vector<autofill::LoyaltyCard> affiliated_loyalty_cards;
+  std::ranges::copy_if(loyalty_cards_to_suggest,
+                       std::back_inserter(affiliated_loyalty_cards),
+                       [&current_domain](const autofill::LoyaltyCard& card) {
+                         return card.HasMatchingMerchantDomain(current_domain);
+                       });
 
   return touch_to_fill_payment_method_controller_.ShowLoyaltyCards(
       std::make_unique<TouchToFillPaymentMethodViewImpl>(web_contents()),
diff --git a/chrome/browser/ui/passwords/bubble_controllers/post_save_compromised_bubble_controller.cc b/chrome/browser/ui/passwords/bubble_controllers/post_save_compromised_bubble_controller.cc
index f25d3856..9acd0a5 100644
--- a/chrome/browser/ui/passwords/bubble_controllers/post_save_compromised_bubble_controller.cc
+++ b/chrome/browser/ui/passwords/bubble_controllers/post_save_compromised_bubble_controller.cc
@@ -4,7 +4,6 @@
 
 #include "chrome/browser/ui/passwords/bubble_controllers/post_save_compromised_bubble_controller.h"
 
-#include "base/metrics/histogram_functions.h"
 #include "base/notreached.h"
 #include "chrome/browser/ui/passwords/passwords_model_delegate.h"
 #include "chrome/grit/generated_resources.h"
@@ -28,7 +27,6 @@
     default:
       NOTREACHED();
   }
-  base::UmaHistogramEnumeration("PasswordBubble.CompromisedBubble.Type", type_);
 }
 
 PostSaveCompromisedBubbleController::~PostSaveCompromisedBubbleController() {
diff --git a/chrome/browser/ui/passwords/bubble_controllers/post_save_compromised_bubble_controller_unittest.cc b/chrome/browser/ui/passwords/bubble_controllers/post_save_compromised_bubble_controller_unittest.cc
index 9644ad6..dfa943a 100644
--- a/chrome/browser/ui/passwords/bubble_controllers/post_save_compromised_bubble_controller_unittest.cc
+++ b/chrome/browser/ui/passwords/bubble_controllers/post_save_compromised_bubble_controller_unittest.cc
@@ -4,7 +4,6 @@
 
 #include "chrome/browser/ui/passwords/bubble_controllers/post_save_compromised_bubble_controller.h"
 
-#include "base/test/metrics/histogram_tester.h"
 #include "chrome/browser/ui/passwords/passwords_model_delegate_mock.h"
 #include "chrome/grit/theme_resources.h"
 #include "testing/gmock/include/gmock/gmock.h"
@@ -12,8 +11,6 @@
 
 namespace {
 
-constexpr char kTypeHistogram[] = "PasswordBubble.CompromisedBubble.Type";
-
 using BubbleType = PostSaveCompromisedBubbleController::BubbleType;
 
 class PostSaveCompromisedBubbleControllerTest : public ::testing::Test {
@@ -47,13 +44,10 @@
 }
 
 TEST_F(PostSaveCompromisedBubbleControllerTest, SafeState_Destroy) {
-  base::HistogramTester histogram_tester;
   CreateController(password_manager::ui::PASSWORD_UPDATED_SAFE_STATE);
 
   EXPECT_CALL(*delegate(), OnBubbleHidden());
   controller()->OnBubbleClosing();
-  histogram_tester.ExpectUniqueSample(kTypeHistogram,
-                                      BubbleType::kPasswordUpdatedSafeState, 1);
 }
 
 TEST_F(PostSaveCompromisedBubbleControllerTest, SafeState_DestroyImplicictly) {
@@ -85,13 +79,10 @@
 }
 
 TEST_F(PostSaveCompromisedBubbleControllerTest, MoreToFix_Destroy) {
-  base::HistogramTester histogram_tester;
   CreateController(password_manager::ui::PASSWORD_UPDATED_MORE_TO_FIX);
 
   EXPECT_CALL(*delegate(), OnBubbleHidden());
   controller()->OnBubbleClosing();
-  histogram_tester.ExpectUniqueSample(
-      kTypeHistogram, BubbleType::kPasswordUpdatedWithMoreToFix, 1);
 }
 
 TEST_F(PostSaveCompromisedBubbleControllerTest, MoreToFix_DestroyImplicictly) {
@@ -114,7 +105,6 @@
 }
 
 TEST_F(PostSaveCompromisedBubbleControllerTest, MoreToFix_Click) {
-  base::HistogramTester histogram_tester;
   CreateController(password_manager::ui::PASSWORD_UPDATED_MORE_TO_FIX);
 
   EXPECT_CALL(*delegate(),
diff --git a/chrome/browser/ui/passwords/password_change_ui_controller.cc b/chrome/browser/ui/passwords/password_change_ui_controller.cc
new file mode 100644
index 0000000..092466cc
--- /dev/null
+++ b/chrome/browser/ui/passwords/password_change_ui_controller.cc
@@ -0,0 +1,164 @@
+// 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/ui/passwords/password_change_ui_controller.h"
+
+#include "base/functional/callback.h"
+#include "chrome/browser/ui/passwords/ui_utils.h"
+#include "chrome/browser/ui/tabs/public/tab_dialog_manager.h"
+#include "chrome/browser/ui/tabs/public/tab_features.h"
+#include "chrome/grit/generated_resources.h"
+#include "chrome/grit/theme_resources.h"
+#include "components/constrained_window/constrained_window_views.h"
+#include "components/tabs/public/tab_interface.h"
+#include "ui/base/l10n/l10n_util.h"
+#include "ui/base/models/dialog_model.h"
+#include "ui/views/bubble/bubble_dialog_model_host.h"
+
+namespace {
+
+// Whether a dialog should be displayed for a given `state`.
+bool ShouldDisplayDialog(PasswordChangeDelegate::State state) {
+  switch (state) {
+    case PasswordChangeDelegate::State::kOfferingPasswordChange:
+    case PasswordChangeDelegate::State::kPasswordChangeFailed:
+      return true;
+    case PasswordChangeDelegate::State::kWaitingForAgreement:
+    case PasswordChangeDelegate::State::kWaitingForChangePasswordForm:
+    case PasswordChangeDelegate::State::kChangePasswordFormNotFound:
+    case PasswordChangeDelegate::State::kChangingPassword:
+    case PasswordChangeDelegate::State::kPasswordSuccessfullyChanged:
+    case PasswordChangeDelegate::State::kOtpDetected:
+      return false;
+  }
+}
+
+// Creates dialog for `PasswordChangeDelegate::State::kOfferingPasswordChange`.
+std::unique_ptr<ui::DialogModel> CreateOfferChangePasswordDialog(
+    base::OnceClosure accept_callback) {
+  return ui::DialogModel::Builder()
+      .SetBannerImage(
+          ui::ImageModel::FromResourceId(IDR_PASSWORD_CHANGE_WARNING),
+          ui::ImageModel::FromResourceId(IDR_PASSWORD_CHANGE_WARNING_DARK))
+      .SetIcon(
+          ui::ImageModel::FromVectorIcon(GooglePasswordManagerVectorIcon()))
+      .SetTitle(l10n_util::GetStringUTF16(
+          IDS_PASSWORD_MANAGER_UI_PASSWORD_CHANGE_LEAK_BUBBLE_TITLE))
+      .AddParagraph(ui::DialogModelLabel(l10n_util::GetStringUTF16(
+          IDS_PASSWORD_MANAGER_UI_PASSWORD_CHANGE_LEAK_BUBBLE_DETAILS)))
+      .AddCancelButton(base::DoNothing(),
+                       ui::DialogModel::Button::Params().SetLabel(
+                           l10n_util::GetStringUTF16(IDS_NO_THANKS)))
+      .AddOkButton(
+          std::move(accept_callback),
+          ui::DialogModel::Button::Params().SetLabel(l10n_util::GetStringUTF16(
+              IDS_PASSWORD_MANAGER_UI_PASSWORD_CHANGE_CHANGE_PASSWORD)))
+      .Build();
+}
+
+// Creates dialog for `PasswordChangeDelegate::State::kPasswordChangeFailed`.
+std::unique_ptr<ui::DialogModel> CreatePasswordChangeFailedDialog(
+    base::OnceClosure accept_callback) {
+  return ui::DialogModel::Builder()
+      .SetBannerImage(
+          ui::ImageModel::FromResourceId(IDR_PASSWORD_CHANGE_WARNING),
+          ui::ImageModel::FromResourceId(IDR_PASSWORD_CHANGE_WARNING_DARK))
+      .SetTitle(l10n_util::GetStringUTF16(
+          IDS_PASSWORD_MANAGER_UI_PASSWORD_CHANGE_FAILED_TITLE))
+      .AddParagraph(ui::DialogModelLabel(l10n_util::GetStringUTF16(
+          IDS_PASSWORD_MANAGER_UI_PASSWORD_CHANGE_FAILED_BODY)))
+      .AddCancelButton(base::DoNothing(),
+                       ui::DialogModel::Button::Params().SetLabel(
+                           l10n_util::GetStringUTF16(IDS_CLOSE)))
+      .AddOkButton(
+          std::move(accept_callback),
+          ui::DialogModel::Button::Params().SetLabel(l10n_util::GetStringUTF16(
+              IDS_PASSWORD_MANAGER_UI_PASSWORD_CHANGE_FAILED_ACCEPT_BUTTON)))
+      .Build();
+}
+
+// Creates dialog for `state`.
+std::unique_ptr<ui::DialogModel> CreateDialog(
+    PasswordChangeDelegate::State state,
+    base::OnceClosure accept_callback) {
+  switch (state) {
+    case PasswordChangeDelegate::State::kOfferingPasswordChange:
+      return CreateOfferChangePasswordDialog(std::move(accept_callback));
+    case PasswordChangeDelegate::State::kPasswordChangeFailed:
+      return CreatePasswordChangeFailedDialog(std::move(accept_callback));
+    case PasswordChangeDelegate::State::kWaitingForAgreement:
+    case PasswordChangeDelegate::State::kWaitingForChangePasswordForm:
+    case PasswordChangeDelegate::State::kChangePasswordFormNotFound:
+    case PasswordChangeDelegate::State::kChangingPassword:
+    case PasswordChangeDelegate::State::kPasswordSuccessfullyChanged:
+    case PasswordChangeDelegate::State::kOtpDetected:
+      NOTREACHED();
+  }
+}
+
+}  // namespace
+
+PasswordChangeUIController::PasswordChangeUIController(
+    PasswordChangeDelegate* password_change_delegate,
+    base::WeakPtr<content::WebContents> web_contents)
+    : password_change_delegate_(password_change_delegate),
+      web_contents_(web_contents) {}
+
+PasswordChangeUIController::~PasswordChangeUIController() = default;
+
+void PasswordChangeUIController::UpdateState(
+    PasswordChangeDelegate::State state) {
+  if (state_ == state) {
+    return;
+  }
+
+  state_ = state;
+
+  // TODO(crbug.com/417389698): Handle other states.
+  if (ShouldDisplayDialog(state_)) {
+    tabs::TabInterface* tab_interface =
+        tabs::TabInterface::MaybeGetFromContents(web_contents_.get());
+    if (!tab_interface || !tab_interface->CanShowModalUI()) {
+      return;
+    }
+
+    base::OnceClosure accept_callback = base::BindOnce(
+        &PasswordChangeUIController::OnDialogAccepted, base::Unretained(this));
+    std::unique_ptr<views::BubbleDialogModelHost> model_host =
+        views::BubbleDialogModelHost::CreateModal(
+            CreateDialog(state_, std::move(accept_callback)),
+            ui::mojom::ModalType::kChild);
+    // TODO(crbug.com/338254375): Remove once it is a default state.
+    model_host->SetOwnershipOfNewWidget(
+        views::Widget::InitParams::CLIENT_OWNS_WIDGET);
+    tab_interface->GetTabFeatures()
+        ->tab_dialog_manager()
+        ->CreateAndShowDialog(
+            model_host.release(),
+            std::make_unique<tabs::TabDialogManager::Params>())
+        .release();
+    return;
+  }
+}
+
+void PasswordChangeUIController::OnDialogAccepted() {
+  CHECK(password_change_delegate_);
+
+  switch (state_) {
+    case PasswordChangeDelegate::State::kOfferingPasswordChange:
+      password_change_delegate_->StartPasswordChangeFlow();
+      return;
+    case PasswordChangeDelegate::State::kPasswordChangeFailed:
+      password_change_delegate_->OpenPasswordChangeTab();
+      password_change_delegate_->Stop();
+      return;
+    case PasswordChangeDelegate::State::kWaitingForAgreement:
+    case PasswordChangeDelegate::State::kWaitingForChangePasswordForm:
+    case PasswordChangeDelegate::State::kChangePasswordFormNotFound:
+    case PasswordChangeDelegate::State::kChangingPassword:
+    case PasswordChangeDelegate::State::kPasswordSuccessfullyChanged:
+    case PasswordChangeDelegate::State::kOtpDetected:
+      NOTREACHED();
+  }
+}
diff --git a/chrome/browser/ui/passwords/password_change_ui_controller.h b/chrome/browser/ui/passwords/password_change_ui_controller.h
new file mode 100644
index 0000000..bd5de1d
--- /dev/null
+++ b/chrome/browser/ui/passwords/password_change_ui_controller.h
@@ -0,0 +1,40 @@
+// 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_UI_PASSWORDS_PASSWORD_CHANGE_UI_CONTROLLER_H_
+#define CHROME_BROWSER_UI_PASSWORDS_PASSWORD_CHANGE_UI_CONTROLLER_H_
+
+#include "base/memory/weak_ptr.h"
+#include "chrome/browser/password_manager/password_change_delegate.h"
+
+namespace content {
+class WebContents;
+}  // namespace content
+
+// Responsible for creating and displaying appropriate views based on the
+// current state of the password change flow.
+class PasswordChangeUIController {
+ public:
+  explicit PasswordChangeUIController(
+      PasswordChangeDelegate* password_change_delegate,
+      base::WeakPtr<content::WebContents> web_contents);
+  ~PasswordChangeUIController();
+
+  // Updates the `state_` and the UI.
+  void UpdateState(PasswordChangeDelegate::State state);
+
+ private:
+  // Handles clicking accept button on the currently displayed dialog.
+  void OnDialogAccepted();
+
+  // Controls password change process. Owns this class.
+  const raw_ptr<PasswordChangeDelegate> password_change_delegate_;
+
+  base::WeakPtr<content::WebContents> web_contents_;
+
+  // Current state of the password change flow.
+  PasswordChangeDelegate::State state_;
+};
+
+#endif  // CHROME_BROWSER_UI_PASSWORDS_PASSWORD_CHANGE_UI_CONTROLLER_H_
diff --git a/chrome/browser/ui/views/autofill/popup/popup_cell_utils.cc b/chrome/browser/ui/views/autofill/popup/popup_cell_utils.cc
index abc10ab..8ceba7c 100644
--- a/chrome/browser/ui/views/autofill/popup/popup_cell_utils.cc
+++ b/chrome/browser/ui/views/autofill/popup/popup_cell_utils.cc
@@ -215,7 +215,7 @@
     std::vector<std::unique_ptr<views::View>> minor_text_labels,
     std::unique_ptr<views::Label> description_label,
     std::vector<std::unique_ptr<views::View>> subtext_views) {
-  const bool kHasTwoColumns = !!description_label;
+  const bool has_two_columns = !!description_label;
   auto table =
       views::Builder<views::TableLayoutView>()
           .AddColumn(views::LayoutAlignment::kStart,
@@ -223,7 +223,7 @@
                      views::TableLayout::kFixedSize,
                      views::TableLayout::ColumnSize::kUsePreferred, 0, 0)
           .Build();
-  if (kHasTwoColumns) {
+  if (has_two_columns) {
     const int kDividerSpacing = ChromeLayoutProvider::Get()->GetDistanceMetric(
         DISTANCE_RELATED_LABEL_HORIZONTAL_LIST);
     table->AddPaddingColumn(views::TableLayout::kFixedSize, kDividerSpacing);
@@ -259,16 +259,15 @@
   }
 
   // The description goes into the first row, second column.
-  if (kHasTwoColumns) {
-    table->AddChildView(description_label ? std::move(description_label)
-                                          : std::make_unique<views::View>());
+  if (has_two_columns) {
+    table->AddChildView(std::move(description_label));
   }
 
   // Every subtext label goes into an additional row.
   for (std::unique_ptr<views::View>& subtext_view : subtext_views) {
     table->AddPaddingRow(0, kAdjacentLabelsVerticalSpacing).AddRows(1, 0);
     table->AddChildView(std::move(subtext_view));
-    if (kHasTwoColumns) {
+    if (has_two_columns) {
       table->AddChildView(std::make_unique<views::View>());
     }
   }
diff --git a/chrome/browser/ui/views/autofill/popup/popup_view_utils.cc b/chrome/browser/ui/views/autofill/popup/popup_view_utils.cc
index 7cc118b..5e907e3 100644
--- a/chrome/browser/ui/views/autofill/popup/popup_view_utils.cc
+++ b/chrome/browser/ui/views/autofill/popup/popup_view_utils.cc
@@ -460,7 +460,6 @@
     case SuggestionType::kDevtoolsTestAddresses:
     case SuggestionType::kFillAutofillAi:
     case SuggestionType::kLoyaltyCardEntry:
-    case SuggestionType::kHomeAndWorkAddressEntry:
     case SuggestionType::kPasswordEntry:
       return true;
     case SuggestionType::kAccountStoragePasswordEntry:
diff --git a/chrome/browser/ui/views/digital_credentials/digital_identity_bluetooth_manual_dialog_controller.cc b/chrome/browser/ui/views/digital_credentials/digital_identity_bluetooth_manual_dialog_controller.cc
index fca9189..24c7022 100644
--- a/chrome/browser/ui/views/digital_credentials/digital_identity_bluetooth_manual_dialog_controller.cc
+++ b/chrome/browser/ui/views/digital_credentials/digital_identity_bluetooth_manual_dialog_controller.cc
@@ -60,9 +60,10 @@
       base::BindOnce(&DigitalIdentityBluetoothManualDialogController::OnCancel,
                      weak_factory_.GetWeakPtr()),
       /*dialog_title=*/u"", /*dialog_body=*/u"",
-      DigitalIdentityMultiStepDialog::CreateHeaderView(
-          std::move(dialog_title), std::move(dialog_body),
-          std::move(illustration)));
+      DigitalIdentityMultiStepDialog::CreateHeaderView(std::move(dialog_title),
+                                                       std::move(dialog_body),
+                                                       std::move(illustration)),
+      /*show_progress_bar=*/false);
 }
 
 void DigitalIdentityBluetoothManualDialogController::OnAccept() {
diff --git a/chrome/browser/ui/views/digital_credentials/digital_identity_multi_step_dialog.cc b/chrome/browser/ui/views/digital_credentials/digital_identity_multi_step_dialog.cc
index e5fa262..423ff63a 100644
--- a/chrome/browser/ui/views/digital_credentials/digital_identity_multi_step_dialog.cc
+++ b/chrome/browser/ui/views/digital_credentials/digital_identity_multi_step_dialog.cc
@@ -232,7 +232,8 @@
     base::OnceClosure cancel_callback,
     const std::u16string& dialog_title,
     const std::u16string& body_text,
-    std::unique_ptr<views::View> custom_body_field) {
+    std::unique_ptr<views::View> custom_body_field,
+    bool show_progress_bar) {
   if (!web_contents_) {
     // Post task so that the callback is guaranteed to be called asynchronously
     // in all cases.
@@ -260,6 +261,9 @@
                   new_dialog_delegate.release(), web_contents_.get())
                   ->GetWeakPtr();
   }
+
+  delegate->GetBubbleFrameView()->SetProgress(
+      show_progress_bar ? std::optional<double>(-1) : std::nullopt);
 }
 
 ui::ColorVariant DigitalIdentityMultiStepDialog::GetBackgroundColor() {
diff --git a/chrome/browser/ui/views/digital_credentials/digital_identity_multi_step_dialog.h b/chrome/browser/ui/views/digital_credentials/digital_identity_multi_step_dialog.h
index 702ea68d..6c5c73a 100644
--- a/chrome/browser/ui/views/digital_credentials/digital_identity_multi_step_dialog.h
+++ b/chrome/browser/ui/views/digital_credentials/digital_identity_multi_step_dialog.h
@@ -59,14 +59,24 @@
             views::DISTANCE_MODAL_DIALOG_PREFERRED_WIDTH) -
         insets.right() - insets.left();
     const gfx::Size header_size(available_width, kHeaderHeight);
-
-    illustration->SetPreferredSize(header_size);
+    // `illustration` will horizontally center if the width is
+    // larger than the size from the Lottie file, but the height is just used to
+    // truncate the image, so that is disabled with a very large value.
+    illustration->SetPreferredSize(gfx::Size(available_width, 9999));
     illustration->SetBorder(views::CreateEmptyBorder(
         gfx::Insets::TLBR(kImageMarginTop, 0, kImageMarginBottom, 0)));
     illustration->SetSize(header_size);
     illustration->SetVerticalAlignment(views::ImageView::Alignment::kLeading);
 
-    auto container_view =
+    auto illustration_container_view =
+        views::Builder<views::BoxLayoutView>()
+            .SetOrientation(views::BoxLayout::Orientation::kVertical)
+            .SetInsideBorderInsets(gfx::Insets())
+            .SetPreferredSize(header_size)
+            .Build();
+    illustration_container_view->AddChildView(std::move(illustration));
+
+    auto header_view =
         views::Builder<views::BoxLayoutView>()
             .SetOrientation(views::BoxLayout::Orientation::kVertical)
             .SetInsideBorderInsets(gfx::Insets())
@@ -74,7 +84,7 @@
                 views::LayoutProvider::Get()->GetDistanceMetric(
                     views::DISTANCE_RELATED_CONTROL_VERTICAL))
             .Build();
-    container_view->AddChildView(std::move(illustration));
+    header_view->AddChildView(std::move(illustration_container_view));
 
     // Add title if not empty
     if (!title.empty()) {
@@ -83,7 +93,7 @@
                              .SetTextContext(views::style::CONTEXT_DIALOG_TITLE)
                              .SetHorizontalAlignment(gfx::ALIGN_LEFT)
                              .Build();
-      container_view->AddChildView(std::move(title_label));
+      header_view->AddChildView(std::move(title_label));
     }
 
     // Add body text if not empty
@@ -94,9 +104,9 @@
                             .SetMultiLine(true)
                             .SetHorizontalAlignment(gfx::ALIGN_LEFT)
                             .Build();
-      container_view->AddChildView(std::move(body_label));
+      header_view->AddChildView(std::move(body_label));
     }
-    return container_view;
+    return header_view;
   }
 
   class TestApi {
@@ -125,7 +135,8 @@
       base::OnceClosure cancel_callback,
       const std::u16string& dialog_title,
       const std::u16string& body_text,
-      std::unique_ptr<views::View> custom_body_field);
+      std::unique_ptr<views::View> custom_body_field,
+      bool show_progress_bar);
 
   ui::ColorVariant GetBackgroundColor();
 
diff --git a/chrome/browser/ui/views/digital_credentials/digital_identity_multi_step_dialog_browsertest.cc b/chrome/browser/ui/views/digital_credentials/digital_identity_multi_step_dialog_browsertest.cc
index 7b5781c..8b134ca 100644
--- a/chrome/browser/ui/views/digital_credentials/digital_identity_multi_step_dialog_browsertest.cc
+++ b/chrome/browser/ui/views/digital_credentials/digital_identity_multi_step_dialog_browsertest.cc
@@ -112,7 +112,8 @@
 
     dialog->TryShow(accept_button_params, base::DoNothing(),
                     cancel_button_params, base::DoNothing(), kStep1Title,
-                    kStep1Body, nullptr);
+                    kStep1Body, /*custom_body_field=*/nullptr,
+                    /*show_progress_bar=*/false);
   }
 
   views::Widget* widget = dialog_test_api.GetWidget();
@@ -144,7 +145,8 @@
 
     dialog->TryShow(accept_button_params, base::DoNothing(),
                     cancel_button_params, base::DoNothing(), kStep2Title,
-                    kStep2Body, nullptr);
+                    kStep2Body, /*custom_body_field=*/nullptr,
+                    /*show_progress_bar=*/false);
   }
 
   // The same widget should be showing.
@@ -181,11 +183,11 @@
       GetActiveWebContents()->GetWeakPtr());
   auto dialog_test_api =
       std::make_unique<DigitalIdentityMultiStepDialog::TestApi>(dialog.get());
-  dialog->TryShow(std::make_optional<ButtonParams>(),
-                  base::BindRepeating(ok_callback, &was_ok_callback_called),
-                  ButtonParams(),
-                  base::BindOnce(cancel_callback, &was_cancel_callback_called),
-                  u"Title", u"Body", nullptr);
+  dialog->TryShow(
+      std::make_optional<ButtonParams>(),
+      base::BindRepeating(ok_callback, &was_ok_callback_called), ButtonParams(),
+      base::BindOnce(cancel_callback, &was_cancel_callback_called), u"Title",
+      u"Body", /*custom_body_field=*/nullptr, /*show_progress_bar=*/false);
   EXPECT_TRUE(dialog_test_api->GetWidget()->IsVisible());
 
   // Accept dialog and run any pending tasks.
@@ -215,7 +217,8 @@
         std::make_optional<ButtonParams>();
     accept_button_params->SetEnabled(false);
     dialog->TryShow(accept_button_params, base::DoNothing(), ButtonParams(),
-                    base::DoNothing(), u"Title", u"Body", nullptr);
+                    base::DoNothing(), u"Title", u"Body",
+                    /*custom_body_field=*/nullptr, /*show_progress_bar=*/false);
     EXPECT_FALSE(dialog_test_api.GetWidgetDelegate()->IsDialogButtonEnabled(
         ui::mojom::DialogButton::kOk));
   }
@@ -225,7 +228,8 @@
         std::make_optional<ButtonParams>();
     accept_button_params->SetEnabled(true);
     dialog->TryShow(accept_button_params, base::DoNothing(), ButtonParams(),
-                    base::DoNothing(), u"Title", u"Body", nullptr);
+                    base::DoNothing(), u"Title", u"Body",
+                    /*custom_body_field=*/nullptr, /*show_progress_bar=*/false);
     EXPECT_TRUE(dialog_test_api.GetWidgetDelegate()->IsDialogButtonEnabled(
         ui::mojom::DialogButton::kOk));
   }
diff --git a/chrome/browser/ui/views/passwords/password_change/password_change_icon_views_interactive_ui_test.cc b/chrome/browser/ui/views/passwords/password_change/password_change_icon_views_interactive_ui_test.cc
index e9606cb..6fe24dda 100644
--- a/chrome/browser/ui/views/passwords/password_change/password_change_icon_views_interactive_ui_test.cc
+++ b/chrome/browser/ui/views/passwords/password_change/password_change_icon_views_interactive_ui_test.cc
@@ -109,41 +109,6 @@
             GetView()->GetVectorIcon().name);
 }
 
-IN_PROC_BROWSER_TEST_F(
-    PasswordChangeIconViewsTest,
-    ViewIsVisibleWhenChangingPasswordWaitingForPasswordForm) {
-  EnableSignIn();
-  SetPrivacyNoticeAcceptedPref();
-  SetupPasswordChange();
-  EXPECT_TRUE(GetView()->GetVisible());
-  EXPECT_EQ(views::kPasswordChangeIcon.name, GetView()->GetVectorIcon().name);
-  EXPECT_EQ(l10n_util::GetStringUTF16(
-                IDS_PASSWORD_MANAGER_UI_PASSWORD_CHANGE_OMNIBOX_SIGN_IN_CHECK),
-            GetView()->GetText());
-}
-
-IN_PROC_BROWSER_TEST_F(PasswordChangeIconViewsTest,
-                       ViewIsVisibleWhenChangingPasswordFinished) {
-  EnableSignIn();
-  SetPrivacyNoticeAcceptedPref();
-  SetupPasswordChange();
-  // Wait until the password change flow runs. The flow will fail
-  // because OptimizationGuideKeyedService is not set up for this test. But it
-  // doesn't matter here, it only checks that the icon should change
-  // back to standard password manger icon and label should be removed.
-  ASSERT_TRUE(base::test::RunUntil([&]() {
-    PasswordChangeDelegate* delegate =
-        static_cast<PasswordsModelDelegate*>(GetController())
-            ->GetPasswordChangeDelegate();
-    return delegate->GetCurrentState() ==
-           PasswordChangeDelegate::State::kPasswordChangeFailed;
-  }));
-  EXPECT_TRUE(GetView()->GetVisible());
-  EXPECT_EQ(vector_icons::kPasswordManagerIcon.name,
-            GetView()->GetVectorIcon().name);
-  EXPECT_EQ(u"", GetView()->GetText());
-}
-
 IN_PROC_BROWSER_TEST_F(PasswordChangeIconViewsTest,
                        ViewIsNotVisibleWhenChangingPasswordCanceled) {
   SetupPasswordChange();
diff --git a/chrome/browser/ui/views/passwords/password_change/password_change_ui_browsertest.cc b/chrome/browser/ui/views/passwords/password_change/password_change_ui_browsertest.cc
index ca8d5390..a566faee 100644
--- a/chrome/browser/ui/views/passwords/password_change/password_change_ui_browsertest.cc
+++ b/chrome/browser/ui/views/passwords/password_change/password_change_ui_browsertest.cc
@@ -8,7 +8,6 @@
 #include "chrome/browser/ui/browser.h"
 #include "chrome/browser/ui/passwords/manage_passwords_ui_controller.h"
 #include "chrome/browser/ui/test/test_browser_dialog.h"
-#include "chrome/browser/ui/views/passwords/password_bubble_view_base.h"
 #include "chrome/test/base/ui_test_utils.h"
 #include "components/affiliations/core/browser/mock_affiliation_service.h"
 #include "components/keyed_service/content/browser_context_dependency_manager.h"
@@ -72,8 +71,6 @@
           GURL("https://example.com/"), u"username", u"password",
           /*in_account_store=*/false));
     }
-    PasswordBubbleViewBase::ShowBubble(
-        web_contents, LocationBarBubbleDelegateView::USER_GESTURE);
   }
 
   ChromePasswordChangeService* password_change_service() {
diff --git a/chrome/browser/ui/views/profiles/first_run_flow_controller_dice.cc b/chrome/browser/ui/views/profiles/first_run_flow_controller_dice.cc
index fece831..e185b5f 100644
--- a/chrome/browser/ui/views/profiles/first_run_flow_controller_dice.cc
+++ b/chrome/browser/ui/views/profiles/first_run_flow_controller_dice.cc
@@ -251,6 +251,9 @@
                          ->GetController()
                          ->GetAs<IntroUI>();
     CHECK(intro_ui);
+    if (can_pin_) {
+      intro_ui->SetCanPinToTaskbar(can_pin_);
+    }
     intro_ui->SetDefaultBrowserCallback(DefaultBrowserCallback(
         base::BindOnce(&DefaultBrowserStepController::OnStepCompleted,
                        // WeakPtr: The callback is given to the WebUIController,
@@ -261,16 +264,6 @@
 
   void OnCanPinToTaskbarResult(bool can_pin) {
     can_pin_ = can_pin;
-    if (can_pin) {
-      // Change subtitle if Chrome can be pinned to the taskbar.
-      auto* intro_ui = host()
-                           ->GetPickerContents()
-                           ->GetWebUI()
-                           ->GetController()
-                           ->GetAs<IntroUI>();
-      CHECK(intro_ui);
-      intro_ui->SetCanPinToTaskbar(can_pin);
-    }
     std::move(show_default_browser_screen_callback_).Run();
   }
 
@@ -320,7 +313,7 @@
             ShellUtil::GetBrowserModelId(InstallUtil::IsPerUserInstall()),
             base::BindOnce(
                 &DefaultBrowserStepController::OnCanPinToTaskbarResult,
-                base::Unretained(this)));
+                weak_ptr_factory_.GetWeakPtr()));
         return;
       }
 #endif  // BUILDFLAG(IS_WIN)
diff --git a/chrome/browser/ui/webui/BUILD.gn b/chrome/browser/ui/webui/BUILD.gn
index b0cea16b..fb70459 100644
--- a/chrome/browser/ui/webui/BUILD.gn
+++ b/chrome/browser/ui/webui/BUILD.gn
@@ -55,6 +55,7 @@
   if (!is_android) {
     deps += [
       "//chrome/browser/ui/webui/access_code_cast",
+      "//chrome/browser/ui/webui/actor_internals",
       "//chrome/browser/ui/webui/app_service_internals",
       "//chrome/browser/ui/webui/infobar_internals",
       "//chrome/browser/ui/webui/new_tab_footer",
diff --git a/chrome/browser/ui/webui/actor_internals/BUILD.gn b/chrome/browser/ui/webui/actor_internals/BUILD.gn
new file mode 100644
index 0000000..d000cf1
--- /dev/null
+++ b/chrome/browser/ui/webui/actor_internals/BUILD.gn
@@ -0,0 +1,39 @@
+# 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("//mojo/public/tools/bindings/mojom.gni")
+
+mojom("mojo_bindings") {
+  sources = [ "actor_internals.mojom" ]
+  webui_module_path = "/"
+
+  public_deps = [ "//mojo/public/mojom/base" ]
+}
+
+source_set("actor_internals") {
+  sources = [
+    "actor_internals_ui.cc",
+    "actor_internals_ui.h",
+    "actor_internals_ui_handler.cc",
+    "actor_internals_ui_handler.h",
+  ]
+
+  public_deps = [
+    ":mojo_bindings",
+    "//base",
+    "//content/public/browser",
+    "//ui/webui",
+  ]
+
+  deps = [
+    "//chrome/browser:browser_process",
+    "//chrome/browser:global_features",
+    "//chrome/browser/actor",
+    "//chrome/browser/profiles",
+    "//chrome/browser/resources/actor_internals:resources",
+    "//chrome/browser/ui/webui/actor_internals:mojo_bindings",
+    "//chrome/common",
+    "//content/public/browser",
+  ]
+}
diff --git a/chrome/browser/ui/webui/actor_internals/OWNERS b/chrome/browser/ui/webui/actor_internals/OWNERS
new file mode 100644
index 0000000..cad4b1f
--- /dev/null
+++ b/chrome/browser/ui/webui/actor_internals/OWNERS
@@ -0,0 +1,4 @@
+file://chrome/browser/actor/OWNERS
+
+per-file *.mojom=set noparent
+per-file *.mojom=file://ipc/SECURITY_OWNERS
diff --git a/chrome/browser/ui/webui/actor_internals/actor_internals.mojom b/chrome/browser/ui/webui/actor_internals/actor_internals.mojom
new file mode 100644
index 0000000..4b78c3f
--- /dev/null
+++ b/chrome/browser/ui/webui/actor_internals/actor_internals.mojom
@@ -0,0 +1,44 @@
+// 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.
+
+module actor_internals.mojom;
+
+import "mojo/public/mojom/base/time.mojom";
+
+// Describes a single journal entry.
+struct JournalEntry {
+  // This url on which the event occurred. This may be invalid.
+  // It should only used for display purposes.
+  string url;
+
+  // The event name of the event.
+  string event;
+
+  // What type of event, 'B' == Begin, 'E' == End, 'I' == Instant.
+  string type;
+
+  // Any additional details.
+  string details;
+
+  // Timestamp the event occurred at.
+  mojo_base.mojom.JSTime timestamp;
+};
+
+// Interface for browser-to-page events
+interface Page {
+  // Event indicatiing a journal entry was added.
+  JournalEntryAdded(JournalEntry entry);
+};
+
+// Interface that implements page-to-browser events.
+interface PageHandler {
+  // Empty for now.
+};
+
+// Used by the WebUI page to bootstrap bidirectional communication.
+interface PageHandlerFactory {
+  // Creates and binds a new PageHandler instance for the requesting page.
+  CreatePageHandler(pending_remote<Page> page,
+                    pending_receiver<PageHandler> handler);
+};
\ No newline at end of file
diff --git a/chrome/browser/ui/webui/actor_internals/actor_internals_ui.cc b/chrome/browser/ui/webui/actor_internals/actor_internals_ui.cc
new file mode 100644
index 0000000..0bd470c
--- /dev/null
+++ b/chrome/browser/ui/webui/actor_internals/actor_internals_ui.cc
@@ -0,0 +1,48 @@
+// 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/ui/webui/actor_internals/actor_internals_ui.h"
+
+#include <memory>
+
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/ui/webui/actor_internals/actor_internals_ui_handler.h"
+#include "chrome/common/url_constants.h"
+#include "chrome/grit/actor_internals_resources.h"
+#include "chrome/grit/actor_internals_resources_map.h"
+#include "content/public/browser/web_contents.h"
+#include "content/public/browser/web_ui.h"
+#include "content/public/browser/web_ui_controller.h"
+#include "content/public/browser/web_ui_data_source.h"
+#include "ui/webui/webui_util.h"
+
+ActorInternalsUI::ActorInternalsUI(content::WebUI* web_ui)
+    : ui::MojoWebUIController(web_ui, true) {
+  // Set up the chrome://actor-internals/ source.
+  content::WebUIDataSource* source = content::WebUIDataSource::CreateAndAdd(
+      web_ui->GetWebContents()->GetBrowserContext(),
+      chrome::kChromeUIActorInternalsHost);
+  webui::SetupWebUIDataSource(source, kActorInternalsResources,
+                              IDR_ACTOR_INTERNALS_ACTOR_INTERNALS_HTML);
+}
+
+WEB_UI_CONTROLLER_TYPE_IMPL(ActorInternalsUI)
+
+ActorInternalsUI::~ActorInternalsUI() = default;
+
+void ActorInternalsUI::BindInterface(
+    mojo::PendingReceiver<actor_internals::mojom::PageHandlerFactory>
+        receiver) {
+  page_factory_receiver_.reset();
+  page_factory_receiver_.Bind(std::move(receiver));
+}
+
+void ActorInternalsUI::CreatePageHandler(
+    mojo::PendingRemote<actor_internals::mojom::Page> page,
+    mojo::PendingReceiver<actor_internals::mojom::PageHandler> receiver) {
+  page_handler_ = std::make_unique<ActorInternalsUIHandler>(
+      Profile::FromBrowserContext(
+          web_ui()->GetWebContents()->GetBrowserContext()),
+      std::move(page), std::move(receiver));
+}
diff --git a/chrome/browser/ui/webui/actor_internals/actor_internals_ui.h b/chrome/browser/ui/webui/actor_internals/actor_internals_ui.h
new file mode 100644
index 0000000..18b3355
--- /dev/null
+++ b/chrome/browser/ui/webui/actor_internals/actor_internals_ui.h
@@ -0,0 +1,55 @@
+// 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_UI_WEBUI_ACTOR_INTERNALS_ACTOR_INTERNALS_UI_H_
+#define CHROME_BROWSER_UI_WEBUI_ACTOR_INTERNALS_ACTOR_INTERNALS_UI_H_
+
+#include "chrome/browser/ui/webui/actor_internals/actor_internals.mojom.h"
+#include "chrome/common/webui_url_constants.h"
+#include "content/public/browser/internal_webui_config.h"
+#include "content/public/browser/web_ui_controller.h"
+#include "ui/webui/mojo_web_ui_controller.h"
+
+class ActorInternalsUI;
+class ActorInternalsUIHandler;
+
+class ActorInternalsUIConfig
+    : public content::DefaultInternalWebUIConfig<ActorInternalsUI> {
+ public:
+  ActorInternalsUIConfig()
+      : DefaultInternalWebUIConfig(chrome::kChromeUIActorInternalsHost) {}
+};
+
+// The UI for chrome://actor-internals/
+class ActorInternalsUI : public ui::MojoWebUIController,
+                         public actor_internals::mojom::PageHandlerFactory {
+ public:
+  explicit ActorInternalsUI(content::WebUI* contents);
+
+  ActorInternalsUI(const ActorInternalsUI&) = delete;
+  ActorInternalsUI& operator=(const ActorInternalsUI&) = delete;
+
+  ~ActorInternalsUI() override;
+
+  // Instantiates the implementor of the mojom::PageHandlerFactory mojo
+  // interface passing the pending receiver that will be internally bound.
+  void BindInterface(
+      mojo::PendingReceiver<actor_internals::mojom::PageHandlerFactory>
+          receiver);
+
+ private:
+  // actor_internals::mojom::PageHandlerFactory:
+  void CreatePageHandler(
+      mojo::PendingRemote<actor_internals::mojom::Page> page,
+      mojo::PendingReceiver<actor_internals::mojom::PageHandler> receiver)
+      override;
+
+  std::unique_ptr<ActorInternalsUIHandler> page_handler_;
+  mojo::Receiver<actor_internals::mojom::PageHandlerFactory>
+      page_factory_receiver_{this};
+
+  WEB_UI_CONTROLLER_TYPE_DECL();
+};
+
+#endif  // CHROME_BROWSER_UI_WEBUI_ACTOR_INTERNALS_ACTOR_INTERNALS_UI_H_
diff --git a/chrome/browser/ui/webui/actor_internals/actor_internals_ui_handler.cc b/chrome/browser/ui/webui/actor_internals/actor_internals_ui_handler.cc
new file mode 100644
index 0000000..b07e6b1
--- /dev/null
+++ b/chrome/browser/ui/webui/actor_internals/actor_internals_ui_handler.cc
@@ -0,0 +1,56 @@
+// 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/ui/webui/actor_internals/actor_internals_ui_handler.h"
+
+#include "base/functional/bind.h"
+#include "base/notreached.h"
+#include "base/time/time.h"
+#include "base/values.h"
+#include "chrome/browser/actor/actor_keyed_service.h"
+#include "chrome/browser/actor/aggregated_journal.h"
+#include "chrome/browser/profiles/profile.h"
+#include "content/public/browser/web_contents.h"
+#include "content/public/browser/web_ui.h"
+
+namespace {
+std::string ToString(actor::mojom::JournalEntryType type) {
+  switch (type) {
+    case actor::mojom::JournalEntryType::kBegin:
+      return "B";
+    case actor::mojom::JournalEntryType::kEnd:
+      return "E";
+    case actor::mojom::JournalEntryType::kInstant:
+      return "I";
+  }
+  NOTREACHED();
+}
+}  // namespace
+
+ActorInternalsUIHandler::ActorInternalsUIHandler(
+    Profile* profile,
+    mojo::PendingRemote<actor_internals::mojom::Page> page,
+    mojo::PendingReceiver<actor_internals::mojom::PageHandler> receiver)
+    : profile_(profile),
+      remote_(std::move(page)),
+      receiver_(this, std::move(receiver)) {
+  auto& journal = actor::ActorKeyedService::Get(profile_)->GetJournal();
+  journal.AddObserver(this);
+
+  for (auto it = journal.Items(); it; ++it) {
+    WillAddJournalEntry(***it);
+  }
+}
+
+ActorInternalsUIHandler::~ActorInternalsUIHandler() {
+  auto& journal = actor::ActorKeyedService::Get(profile_)->GetJournal();
+  journal.RemoveObserver(this);
+}
+
+void ActorInternalsUIHandler::WillAddJournalEntry(
+    const actor::AggregatedJournal::Entry& entry) {
+  remote_->JournalEntryAdded(actor_internals::mojom::JournalEntry::New(
+      entry.url, entry.data->event, ToString(entry.data->type),
+      entry.data->details, entry.data->timestamp));
+}
diff --git a/chrome/browser/ui/webui/actor_internals/actor_internals_ui_handler.h b/chrome/browser/ui/webui/actor_internals/actor_internals_ui_handler.h
new file mode 100644
index 0000000..cf41a21
--- /dev/null
+++ b/chrome/browser/ui/webui/actor_internals/actor_internals_ui_handler.h
@@ -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.
+
+#ifndef CHROME_BROWSER_UI_WEBUI_ACTOR_INTERNALS_ACTOR_INTERNALS_UI_HANDLER_H_
+#define CHROME_BROWSER_UI_WEBUI_ACTOR_INTERNALS_ACTOR_INTERNALS_UI_HANDLER_H_
+
+#include "chrome/browser/actor/aggregated_journal.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/ui/webui/actor_internals/actor_internals.mojom.h"
+#include "mojo/public/cpp/bindings/pending_receiver.h"
+#include "mojo/public/cpp/bindings/pending_remote.h"
+#include "mojo/public/cpp/bindings/receiver.h"
+#include "mojo/public/cpp/bindings/remote.h"
+#include "ui/webui/mojo_web_ui_controller.h"
+
+// UI Handler for chrome://actor-internals/
+// It listens to actor journal events and passes those notifications
+// into the Javascript to update the page.
+class ActorInternalsUIHandler : public actor_internals::mojom::PageHandler,
+                                public actor::AggregatedJournal::Observer {
+ public:
+  ActorInternalsUIHandler(
+      Profile* profile,
+      mojo::PendingRemote<actor_internals::mojom::Page> page,
+      mojo::PendingReceiver<actor_internals::mojom::PageHandler> receiver);
+
+  ActorInternalsUIHandler(const ActorInternalsUIHandler&) = delete;
+  ActorInternalsUIHandler& operator=(const ActorInternalsUIHandler&) = delete;
+
+  ~ActorInternalsUIHandler() override;
+
+  // AggregatedJournalObserver implementation:
+  void WillAddJournalEntry(
+      const actor::AggregatedJournal::Entry& entry) override;
+
+ private:
+  void Cleanup();
+
+  raw_ptr<Profile> profile_;
+  mojo::Remote<actor_internals::mojom::Page> remote_;
+  mojo::Receiver<actor_internals::mojom::PageHandler> receiver_;
+};
+
+#endif  // CHROME_BROWSER_UI_WEBUI_ACTOR_INTERNALS_ACTOR_INTERNALS_UI_HANDLER_H_
diff --git a/chrome/browser/ui/webui/chrome_web_ui_configs.cc b/chrome/browser/ui/webui/chrome_web_ui_configs.cc
index f740a18..61bd1d0 100644
--- a/chrome/browser/ui/webui/chrome_web_ui_configs.cc
+++ b/chrome/browser/ui/webui/chrome_web_ui_configs.cc
@@ -78,6 +78,7 @@
 #include "chrome/browser/ui/webui/media_router/cast_feedback_ui.h"
 #endif
 #include "chrome/browser/ui/webui/access_code_cast/access_code_cast_ui.h"
+#include "chrome/browser/ui/webui/actor_internals/actor_internals_ui.h"
 #include "chrome/browser/ui/webui/app_service_internals/app_service_internals_ui.h"
 #include "chrome/browser/ui/webui/bookmarks/bookmarks_ui.h"
 #include "chrome/browser/ui/webui/commerce/product_specifications_ui.h"
@@ -292,6 +293,7 @@
 #if BUILDFLAG(GOOGLE_CHROME_BRANDING)
   map.AddWebUIConfig(std::make_unique<media_router::CastFeedbackUIConfig>());
 #endif  // BUILDFLAG(GOOGLE_CHROME_BRANDING)
+  map.AddWebUIConfig(std::make_unique<ActorInternalsUIConfig>());
   map.AddWebUIConfig(std::make_unique<AppServiceInternalsUIConfig>());
   map.AddWebUIConfig(std::make_unique<media_router::AccessCodeCastUIConfig>());
   map.AddWebUIConfig(std::make_unique<BookmarksSidePanelUIConfig>());
diff --git a/chrome/browser/web_applications/BUILD.gn b/chrome/browser/web_applications/BUILD.gn
index 791d8a6..b0a6da6a 100644
--- a/chrome/browser/web_applications/BUILD.gn
+++ b/chrome/browser/web_applications/BUILD.gn
@@ -404,6 +404,8 @@
       "isolated_web_apps/policy/isolated_web_app_cache_manager.h",
       "os_integration/web_app_run_on_os_login_chromeos.cc",
       "os_integration/web_app_shortcut_chromeos.cc",
+      "policy/app_service_web_app_policy.cc",
+      "policy/app_service_web_app_policy.h",
       "web_app_run_on_os_login_manager.cc",
       "web_app_run_on_os_login_manager.h",
       "web_app_system_web_app_delegate_map_utils.cc",
diff --git a/chrome/browser/web_applications/app_service/web_app_publisher_helper.cc b/chrome/browser/web_applications/app_service/web_app_publisher_helper.cc
index 8180c0c..75874091 100644
--- a/chrome/browser/web_applications/app_service/web_app_publisher_helper.cc
+++ b/chrome/browser/web_applications/app_service/web_app_publisher_helper.cc
@@ -121,6 +121,7 @@
 #include "chrome/browser/media/webrtc/media_capture_devices_dispatcher.h"
 #include "chrome/browser/notifications/notification_display_service_factory.h"
 #include "chrome/browser/web_applications/chromeos_web_app_experiments.h"
+#include "chrome/browser/web_applications/policy/app_service_web_app_policy.h"
 #include "chromeos/ash/components/file_manager/app_id.h"
 #include "chromeos/ash/experiences/system_web_apps/types/system_web_app_data.h"
 #include "chromeos/ash/experiences/system_web_apps/types/system_web_app_delegate.h"
@@ -718,7 +719,7 @@
             app->install_reason == apps::InstallReason::kSystem)
       << base::ToString(app->install_reason);
 
-  app->policy_ids = GetPolicyIds(*web_app);
+  app->policy_ids = WebAppPolicyManager::GetPolicyIds(profile(), *web_app);
 
   app->permissions = CreatePermissions(web_app);
 
@@ -1686,65 +1687,6 @@
           std::move(callback)));
 }
 
-std::vector<std::string> WebAppPublisherHelper::GetPolicyIds(
-    const WebApp& web_app) const {
-  const auto& app_id = web_app.app_id();
-
-  if (web_app.isolation_data() && registrar().IsInstalledByPolicy(app_id)) {
-    // This is an IWA - and thus, web_bundle_id == policy_id == URL hostname
-    return {web_app.start_url().host()};
-  }
-
-  std::vector<std::string> policy_ids;
-
-  if (std::optional<std::string_view> preinstalled_web_app_policy_id =
-          GetPolicyIdForPreinstalledWebApp(app_id)) {
-    policy_ids.emplace_back(*preinstalled_web_app_policy_id);
-  }
-
-#if BUILDFLAG(IS_CHROMEOS)
-  auto* swa_manager = ash::SystemWebAppManager::Get(profile());
-  if (swa_manager && swa_manager->IsSystemWebApp(app_id)) {
-    const auto& swa_data = web_app.client_data().system_web_app_data;
-    DCHECK(swa_data);
-    const ash::SystemWebAppType swa_type = swa_data->system_app_type;
-    const std::optional<std::string_view> swa_policy_id =
-        GetPolicyIdForSystemWebAppType(swa_type);
-    if (swa_policy_id) {
-      policy_ids.emplace_back(*swa_policy_id);
-    }
-
-    // File Manager SWA uses File Manager Extension's ID for policy.
-    if (swa_type == ash::SystemWebAppType::FILE_MANAGER) {
-      policy_ids.push_back(file_manager::kFileManagerAppId);
-    }
-  }
-#endif  // BUIDLFLAG(IS_CHROMEOS)
-
-  for (const auto& [source, external_config] :
-       web_app.management_to_external_config_map()) {
-    if (!external_config.additional_policy_ids.empty()) {
-      std::ranges::copy(external_config.additional_policy_ids,
-                        std::back_inserter(policy_ids));
-    }
-  }
-
-  if (!registrar().HasExternalAppWithInstallSource(
-          app_id, ExternalInstallSource::kExternalPolicy)) {
-    return policy_ids;
-  }
-
-  base::flat_map<webapps::AppId, base::flat_set<GURL>> installed_apps =
-      registrar().GetExternallyInstalledApps(
-          ExternalInstallSource::kExternalPolicy);
-  if (auto* install_urls = base::FindOrNull(installed_apps, app_id)) {
-    DCHECK(!install_urls->empty());
-    base::Extend(policy_ids, base::ToVector(*install_urls, &GURL::spec));
-  }
-
-  return policy_ids;
-}
-
 apps::PackageId WebAppPublisherHelper::GetPackageId(
     const WebApp& web_app) const {
 #if BUILDFLAG(IS_CHROMEOS)
diff --git a/chrome/browser/web_applications/app_service/web_app_publisher_helper.h b/chrome/browser/web_applications/app_service/web_app_publisher_helper.h
index 834ed012..9ed5029 100644
--- a/chrome/browser/web_applications/app_service/web_app_publisher_helper.h
+++ b/chrome/browser/web_applications/app_service/web_app_publisher_helper.h
@@ -371,10 +371,6 @@
       int64_t display_id,
       base::OnceCallback<void(std::vector<content::WebContents*>)> callback);
 
-  // Get the list of identifiers for the app that will be used in policy
-  // controls, such as force-installation and pinning. May be empty.
-  std::vector<std::string> GetPolicyIds(const WebApp& web_app) const;
-
   apps::PackageId GetPackageId(const WebApp& web_app) const;
 
 #if BUILDFLAG(IS_CHROMEOS)
diff --git a/chrome/browser/web_applications/policy/app_service_web_app_policy.cc b/chrome/browser/web_applications/policy/app_service_web_app_policy.cc
new file mode 100644
index 0000000..885d576
--- /dev/null
+++ b/chrome/browser/web_applications/policy/app_service_web_app_policy.cc
@@ -0,0 +1,90 @@
+// 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/web_applications/policy/app_service_web_app_policy.h"
+
+#include <algorithm>
+#include <string_view>
+
+#include "ash/constants/web_app_id_constants.h"
+#include "ash/webui/system_apps/public/system_web_app_type.h"
+#include "base/containers/contains.h"
+#include "base/containers/fixed_flat_map.h"
+#include "chrome/browser/web_applications/policy/web_app_policy_manager.h"
+
+namespace web_app {
+
+namespace {
+
+enum class SystemWebAppType;
+
+// This mapping excludes SWAs not included in official builds (like SAMPLE).
+// These app Id constants need to be kept in sync with java/com/
+// google/chrome/cros/policyconverter/ChromePolicySettingsProcessor.java
+// LINT.IfChange
+constexpr auto kSystemWebAppsMapping =
+    base::MakeFixedFlatMap<std::string_view, ash::SystemWebAppType>(
+        {{"file_manager", ash::SystemWebAppType::FILE_MANAGER},
+         {"settings", ash::SystemWebAppType::SETTINGS},
+         {"camera", ash::SystemWebAppType::CAMERA},
+         {"terminal", ash::SystemWebAppType::TERMINAL},
+         {"media", ash::SystemWebAppType::MEDIA},
+         {"help", ash::SystemWebAppType::HELP},
+         {"print_management", ash::SystemWebAppType::PRINT_MANAGEMENT},
+         {"scanning", ash::SystemWebAppType::SCANNING},
+         {"diagnostics", ash::SystemWebAppType::DIAGNOSTICS},
+         {"connectivity_diagnostics",
+          ash::SystemWebAppType::CONNECTIVITY_DIAGNOSTICS},
+         {"eche", ash::SystemWebAppType::ECHE},
+         {"crosh", ash::SystemWebAppType::CROSH},
+         {"personalization", ash::SystemWebAppType::PERSONALIZATION},
+         {"shortcut_customization",
+          ash::SystemWebAppType::SHORTCUT_CUSTOMIZATION},
+         {"shimless_rma", ash::SystemWebAppType::SHIMLESS_RMA},
+         {"demo_mode", ash::SystemWebAppType::DEMO_MODE},
+         {"os_feedback", ash::SystemWebAppType::OS_FEEDBACK},
+         {"os_sanitize", ash::SystemWebAppType::OS_SANITIZE},
+         {"projector", ash::SystemWebAppType::PROJECTOR},
+         {"firmware_update", ash::SystemWebAppType::FIRMWARE_UPDATE},
+         {"os_flags", ash::SystemWebAppType::OS_FLAGS},
+         {"vc_background", ash::SystemWebAppType::VC_BACKGROUND},
+         {"print_preview_cros", ash::SystemWebAppType::PRINT_PREVIEW_CROS},
+         {"boca", ash::SystemWebAppType::BOCA},
+         {"app_mall", ash::SystemWebAppType::MALL},
+         {"recorder", ash::SystemWebAppType::RECORDER},
+         {"graduation", ash::SystemWebAppType::GRADUATION}});
+// LINT.ThenChange(//depot/google3/java/com/google/chrome/cros/policyconverter/ChromePolicySettingsProcessor.java)
+
+constexpr ash::SystemWebAppType GetMaxSystemWebAppType() {
+  return std::ranges::max_element(
+             kSystemWebAppsMapping, std::ranges::less{},
+             &decltype(kSystemWebAppsMapping)::value_type::second)
+      ->second;
+}
+
+static_assert(GetMaxSystemWebAppType() == ash::SystemWebAppType::kMaxValue,
+              "Not all SWA types are listed in |system_web_apps_mapping|.");
+
+}  // namespace
+
+std::optional<std::string_view> GetPolicyIdForSystemWebAppType(
+    ash::SystemWebAppType swa_type) {
+  for (const auto& [policy_id, mapped_swa_type] : kSystemWebAppsMapping) {
+    if (mapped_swa_type == swa_type) {
+      return policy_id;
+    }
+  }
+  return {};
+}
+
+bool IsArcAppPolicyId(std::string_view policy_id) {
+  return base::Contains(policy_id, '.') &&
+         !WebAppPolicyManager::IsWebAppPolicyId(policy_id);
+}
+
+bool IsSystemWebAppPolicyId(std::string_view policy_id) {
+  return base::Contains(kSystemWebAppsMapping, policy_id);
+}
+
+}  // namespace web_app
diff --git a/chrome/browser/web_applications/policy/app_service_web_app_policy.h b/chrome/browser/web_applications/policy/app_service_web_app_policy.h
new file mode 100644
index 0000000..2d847d7
--- /dev/null
+++ b/chrome/browser/web_applications/policy/app_service_web_app_policy.h
@@ -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.
+
+#ifndef CHROME_BROWSER_WEB_APPLICATIONS_POLICY_APP_SERVICE_WEB_APP_POLICY_H_
+#define CHROME_BROWSER_WEB_APPLICATIONS_POLICY_APP_SERVICE_WEB_APP_POLICY_H_
+
+#include <optional>
+#include <string_view>
+
+#include "ash/webui/system_apps/public/system_web_app_type.h"
+
+namespace web_app {
+
+// Returns the policy ID (string_view) for a given System Web App Type.
+// Returns std::nullopt if the type is not found in the mapping.
+std::optional<std::string_view> GetPolicyIdForSystemWebAppType(
+    ash::SystemWebAppType swa_type);
+
+// Checks if the given policy ID corresponds to a System Web App.
+bool IsSystemWebAppPolicyId(std::string_view policy_id);
+
+// Checks whether |policy_id| specifies an Arc App.
+bool IsArcAppPolicyId(std::string_view policy_id);
+
+}  // namespace web_app
+
+#endif  // CHROME_BROWSER_WEB_APPLICATIONS_POLICY_APP_SERVICE_WEB_APP_POLICY_H_
diff --git a/chrome/browser/web_applications/policy/web_app_policy_manager.cc b/chrome/browser/web_applications/policy/web_app_policy_manager.cc
index 24489cb21..89de9efd 100644
--- a/chrome/browser/web_applications/policy/web_app_policy_manager.cc
+++ b/chrome/browser/web_applications/policy/web_app_policy_manager.cc
@@ -13,8 +13,12 @@
 
 #include "base/check_deref.h"
 #include "base/containers/contains.h"
+#include "base/containers/extend.h"
+#include "base/containers/fixed_flat_map.h"
 #include "base/containers/flat_map.h"
 #include "base/containers/flat_set.h"
+#include "base/containers/map_util.h"
+#include "base/containers/to_vector.h"
 #include "base/feature_list.h"
 #include "base/functional/bind.h"
 #include "base/functional/callback_forward.h"
@@ -45,6 +49,7 @@
 #include "chrome/browser/web_applications/web_app_utils.h"
 #include "chrome/common/chrome_features.h"
 #include "chrome/common/pref_names.h"
+#include "components/crx_file/id_util.h"
 #include "components/pref_registry/pref_registry_syncable.h"
 #include "components/prefs/pref_service.h"
 #include "components/webapps/browser/install_result_code.h"
@@ -65,8 +70,10 @@
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/policy/profile_policy_connector.h"
 #include "chrome/browser/policy/system_features_disable_list_policy_handler.h"
+#include "chrome/browser/web_applications/policy/app_service_web_app_policy.h"
 #include "chrome/browser/web_applications/web_app_system_web_app_delegate_map_utils.h"
 #include "chrome/browser/web_applications/web_app_utils.h"
+#include "chromeos/ash/components/file_manager/app_id.h"
 #include "chromeos/ash/components/policy/system_features_disable_list/system_features_disable_list_policy_utils.h"
 #include "components/policy/core/common/policy_pref_names.h"
 #include "components/policy/core/common/system_features_disable_list_constants.h"
@@ -84,7 +91,6 @@
   return false;
 }
 
-
 // Policy installed apps are only allowed on:
 // 1. ChromeOS guest sessions (current only on Ash).
 // 2. All Chrome profiles apart from incognito/guest profiles.
@@ -104,7 +110,26 @@
 
 #if BUILDFLAG(IS_CHROMEOS)
 inline constexpr std::string_view kDisabled = "disabled";
-#endif
+
+// Note that this mapping lists only selected Preinstalled Web Apps
+// actively used in policies and is not meant to be exhaustive.
+// These app Id constants need to be kept in sync with java/com/
+// google/chrome/cros/policyconverter/ChromePolicySettingsProcessor.java
+// LINT.IfChange
+constexpr auto kPreinstalledWebAppsMapping =
+    base::MakeFixedFlatMap<std::string_view, std::string_view>(
+        {{"cursive", ash::kCursiveAppId}, {"canvas", ash::kCanvasAppId}});
+// LINT.ThenChange(//depot/google3/java/com/google/chrome/cros/policyconverter/ChromePolicySettingsProcessor.java)
+#endif  // BUILDFLAG(IS_CHROMEOS)
+
+std::optional<base::flat_map<std::string_view, std::string_view>>&
+GetPreinstalledWebAppsMappingForTesting() {
+  static base::NoDestructor<
+      std::optional<base::flat_map<std::string_view, std::string_view>>>
+      preinstalled_web_apps_mapping_for_testing;
+  return *preinstalled_web_apps_mapping_for_testing;
+}
+
 }  // namespace
 
 namespace web_app {
@@ -207,6 +232,125 @@
   registry->RegisterListPref(prefs::kWebAppSettings);
 }
 
+// static
+bool WebAppPolicyManager::IsChromeAppPolicyId(std::string_view policy_id) {
+  return crx_file::id_util::IdIsValid(policy_id);
+}
+
+// static
+bool WebAppPolicyManager::IsWebAppPolicyId(std::string_view policy_id) {
+  return GURL{policy_id}.is_valid();
+}
+
+// static
+std::optional<std::string_view>
+WebAppPolicyManager::GetPolicyIdForPreinstalledWebApp(std::string_view app_id) {
+  if (const auto& test_mapping = GetPreinstalledWebAppsMappingForTesting()) {
+    for (const auto& [policy_id, mapped_app_id] : *test_mapping) {
+      if (mapped_app_id == app_id) {
+        return policy_id;
+      }
+    }
+    return {};
+  }
+
+#if BUILDFLAG(IS_CHROMEOS)
+  for (const auto& [policy_id, mapped_app_id] : kPreinstalledWebAppsMapping) {
+    if (mapped_app_id == app_id) {
+      return policy_id;
+    }
+  }
+#endif  // BUILDFLAG(IS_CHROMEOS)
+  return {};
+}
+
+// static
+void WebAppPolicyManager::SetPreinstalledWebAppsMappingForTesting(  // IN-TEST
+    std::optional<base::flat_map<std::string_view, std::string_view>>
+        preinstalled_web_apps_mapping_for_testing) {
+  GetPreinstalledWebAppsMappingForTesting() =                // IN-TEST
+      std::move(preinstalled_web_apps_mapping_for_testing);  // IN-TEST
+}
+
+// static
+bool WebAppPolicyManager::IsPreinstalledWebAppPolicyId(
+    std::string_view policy_id) {
+  if (auto& mapping = GetPreinstalledWebAppsMappingForTesting()) {  // IN-TEST
+    return base::Contains(*mapping, policy_id);
+  }
+#if BUILDFLAG(IS_CHROMEOS)
+  return base::Contains(kPreinstalledWebAppsMapping, policy_id);
+#else
+  return false;
+#endif  // BUILDFLAG(IS_CHROMEOS)
+}
+
+// static
+bool WebAppPolicyManager::IsIsolatedWebAppPolicyId(std::string_view policy_id) {
+  return web_package::SignedWebBundleId::Create(policy_id).has_value();
+}
+
+// static
+std::vector<std::string> WebAppPolicyManager::GetPolicyIds(
+    Profile* profile,
+    const WebApp& web_app) {
+  const auto& app_id = web_app.app_id();
+  WebAppRegistrar& web_app_registrar =
+      WebAppProvider::GetForWebApps(profile)->registrar_unsafe();
+
+  if (web_app_registrar.IsIsolated(app_id) &&
+      web_app_registrar.IsInstalledByPolicy(app_id)) {
+    // This is an IWA - and thus, web_bundle_id == policy_id == URL hostname
+    return {web_app.start_url().host()};
+  }
+
+  std::vector<std::string> policy_ids;
+
+  if (std::optional<std::string_view> preinstalled_web_app_policy_id =
+          GetPolicyIdForPreinstalledWebApp(app_id)) {
+    policy_ids.emplace_back(*preinstalled_web_app_policy_id);
+  }
+
+#if BUILDFLAG(IS_CHROMEOS)
+  const auto& swa_data = web_app.client_data().system_web_app_data;
+  if (swa_data) {
+    const ash::SystemWebAppType swa_type = swa_data->system_app_type;
+    const std::optional<std::string_view> swa_policy_id =
+        GetPolicyIdForSystemWebAppType(swa_type);
+    if (swa_policy_id) {
+      policy_ids.emplace_back(*swa_policy_id);
+    }
+
+    // File Manager SWA uses File Manager Extension's ID for policy.
+    if (swa_type == ash::SystemWebAppType::FILE_MANAGER) {
+      policy_ids.push_back(file_manager::kFileManagerAppId);
+    }
+  }
+#endif  // BUIDLFLAG(IS_CHROMEOS)
+
+  for (const auto& [source, external_config] :
+       web_app.management_to_external_config_map()) {
+    if (!external_config.additional_policy_ids.empty()) {
+      base::Extend(policy_ids, external_config.additional_policy_ids);
+    }
+  }
+
+  if (!web_app_registrar.HasExternalAppWithInstallSource(
+          app_id, ExternalInstallSource::kExternalPolicy)) {
+    return policy_ids;
+  }
+
+  base::flat_map<webapps::AppId, base::flat_set<GURL>> installed_apps =
+      web_app_registrar.GetExternallyInstalledApps(
+          ExternalInstallSource::kExternalPolicy);
+  if (auto* install_urls = base::FindOrNull(installed_apps, app_id)) {
+    DCHECK(!install_urls->empty());
+    base::Extend(policy_ids, base::ToVector(*install_urls, &GURL::spec));
+  }
+
+  return policy_ids;
+}
+
 void WebAppPolicyManager::InitChangeRegistrarAndRefreshPolicy() {
   pref_change_registrar_.Init(pref_service_);
   pref_change_registrar_.Add(
diff --git a/chrome/browser/web_applications/policy/web_app_policy_manager.h b/chrome/browser/web_applications/policy/web_app_policy_manager.h
index 874db128c..e59e5d5d 100644
--- a/chrome/browser/web_applications/policy/web_app_policy_manager.h
+++ b/chrome/browser/web_applications/policy/web_app_policy_manager.h
@@ -8,8 +8,10 @@
 #include <memory>
 #include <optional>
 #include <string>
+#include <string_view>
 #include <vector>
 
+#include "base/containers/flat_map.h"
 #include "base/functional/callback_forward.h"
 #include "base/memory/raw_ptr.h"
 #include "base/memory/weak_ptr.h"
@@ -82,8 +84,36 @@
 
   static void RegisterProfilePrefs(user_prefs::PrefRegistrySyncable* registry);
 
-  // Used for handling SystemFeaturesDisableList policy. Checks if the app is
-  // disabled and notifies sync_bridge_ about the current app state.
+  // Checks whether |policy_id| specifies a Chrome App.
+  static bool IsChromeAppPolicyId(std::string_view policy_id);
+
+  // Checks whether |policy_id| specifies a Web App.
+  static bool IsWebAppPolicyId(std::string_view policy_id);
+
+  // Returns the policy ID for a given preinstalled web app ID. Note that not
+  // all
+  // preinstalled web apps are supposed to have a policy ID (currently we only
+  // support EDU apps) - in all other cases this will return std::nullopt.
+  static std::optional<std::string_view> GetPolicyIdForPreinstalledWebApp(
+      std::string_view preinstalled_web_app_id);
+
+  static void SetPreinstalledWebAppsMappingForTesting(
+      std::optional<base::flat_map<std::string_view, std::string_view>>
+          preinstalled_web_apps_mapping_for_testing);
+
+  // Checks whether |policy_id| specifies a Preinstalled Web App.
+  static bool IsPreinstalledWebAppPolicyId(std::string_view policy_id);
+
+  // Checks whether |policy_id| specifies an Isolated Web App.
+  static bool IsIsolatedWebAppPolicyId(std::string_view policy_id);
+
+  // Get the list of identifiers for the app that will be used in policy
+  // controls, such as force-installation and pinning. May be empty.
+  static std::vector<std::string> GetPolicyIds(Profile* profile,
+                                               const WebApp& web_app);
+
+  // Used for handling SystemFeaturesDisableList policy. Checks if the app
+  // is disabled and notifies sync_bridge_ about the current app state.
   void OnDisableListPolicyChanged();
 
 #if BUILDFLAG(IS_CHROMEOS)
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 430709e..5a0efc3 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
@@ -28,6 +28,7 @@
 #include "build/build_config.h"
 #include "chrome/browser/web_applications/external_install_options.h"
 #include "chrome/browser/web_applications/externally_managed_app_manager.h"
+#include "chrome/browser/web_applications/isolated_web_apps/test/isolated_web_app_builder.h"
 #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"
@@ -57,6 +58,7 @@
 #include "components/sync_preferences/testing_pref_service_syncable.h"
 #include "components/webapps/browser/install_result_code.h"
 #include "components/webapps/common/web_app_id.h"
+#include "services/data_decoder/public/cpp/test_support/in_process_data_decoder.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/skia/include/core/SkColor.h"
@@ -271,15 +273,16 @@
     auto web_app = test::CreateWebApp(
         url, ConvertExternalInstallSourceToSource(install_source));
     RegisterApp(std::move(web_app));
-    test::AddInstallUrlData(profile()->GetPrefs(), &sync_bridge(),
-                            GenerateAppId(/*manifest_id=*/std::nullopt, url),
-                            url, install_source);
+    test::AddInstallUrlData(
+        profile()->GetPrefs(), &sync_bridge(),
+        GenerateAppId(/*manifest_id_path=*/std::nullopt, url), url,
+        install_source);
   }
 
   void MakeInstalledAppPlaceholder(const GURL& url) {
     test::AddInstallUrlAndPlaceholderData(
         profile()->GetPrefs(), &sync_bridge(),
-        GenerateAppId(/*manifest_id=*/std::nullopt, url), url,
+        GenerateAppId(/*manifest_id_path=*/std::nullopt, url), url,
         ExternalInstallSource::kExternalPolicy, /*is_placeholder=*/true);
   }
 
@@ -302,8 +305,6 @@
 
   WebAppProvider* provider() { return WebAppProvider::GetForTest(profile()); }
 
-  ScopedTestingLocalState testing_local_state_;
-
   void ValidateEmptyWebAppSettingsPolicy() {
     EXPECT_TRUE(policy_manager().settings_by_url_.empty());
 
@@ -358,6 +359,9 @@
         provider_->web_contents_manager());
   }
 
+  ScopedTestingLocalState testing_local_state_;
+  data_decoder::test::InProcessDataDecoder data_decoder_;
+
  private:
   raw_ptr<FakeWebAppProvider, DanglingUntriaged> provider_ = nullptr;
   raw_ptr<WebAppPolicyManager, DanglingUntriaged> web_app_policy_manager_ =
@@ -377,12 +381,63 @@
 class WebAppPolicyManagerTest : public WebAppPolicyManagerTestBase,
                                 public testing::WithParamInterface<bool> {
  public:
+  using InstallResults = std::map<GURL /*install_url*/,
+                                  ExternallyManagedAppManager::InstallResult>;
+  using UninstallResults =
+      std::map<GURL /*install_url*/, webapps::UninstallResultCode>;
+  using SynchronizeFuture =
+      base::test::TestFuture<InstallResults, UninstallResults>;
+
   WebAppPolicyManagerTest() = default;
   WebAppPolicyManagerTest(const WebAppPolicyManagerTest&) = delete;
   WebAppPolicyManagerTest& operator=(const WebAppPolicyManagerTest&) = delete;
   ~WebAppPolicyManagerTest() override = default;
 };
 
+TEST_F(WebAppPolicyManagerTest, GetPolicyIdsForWebApp) {
+  const GURL kWebAppUrl = GURL("https://example.com/path/index.html");
+  const GURL kInstallUrl = GURL("https://www.example.com/install_url.html");
+  const GURL kManifestUrl = GURL("https://www.example.com/manifest.json");
+
+  webapps::AppId app_id =
+      static_cast<FakeWebContentsManager&>(provider()->web_contents_manager())
+          .CreateBasicInstallPageState(kInstallUrl, kManifestUrl, kWebAppUrl);
+
+  ExternalInstallOptions template_options(
+      kInstallUrl, mojom::UserDisplayMode::kStandalone,
+      ExternalInstallSource::kExternalPolicy);
+
+  SynchronizeFuture result;
+  std::vector<ExternalInstallOptions> install_options_list;
+  install_options_list.emplace_back(kInstallUrl,
+                                    /*user_display_mode=*/std::nullopt,
+                                    ExternalInstallSource::kExternalPolicy);
+
+  provider()->externally_managed_app_manager().SynchronizeInstalledApps(
+      std::move(install_options_list), ExternalInstallSource::kExternalPolicy,
+      result.GetCallback());
+  ASSERT_TRUE(result.Wait());
+  const WebApp* app = provider()->registrar_unsafe().GetAppById(app_id);
+
+  EXPECT_EQ(
+      WebAppPolicyManager::GetPolicyIds(profile(), *app),
+      std::vector<std::string>({"https://www.example.com/install_url.html"}));
+}
+
+TEST_F(WebAppPolicyManagerTest, GetPolicyIdsForIsolatedWebApp) {
+  auto bundle = IsolatedWebAppBuilder(ManifestBuilder().SetVersion("1.0.0"))
+                    .BuildBundle();
+  IsolatedWebAppUrlInfo info =
+      bundle
+          ->InstallWithSource(profile(),
+                              &IsolatedWebAppInstallSource::FromExternalPolicy)
+          .value();
+  const WebApp* app = provider()->registrar_unsafe().GetAppById(info.app_id());
+
+  EXPECT_EQ(WebAppPolicyManager::GetPolicyIds(profile(), *app),
+            std::vector<std::string>({info.web_bundle_id().id()}));
+}
+
 TEST_F(WebAppPolicyManagerTest, NoPrefValues) {
   ValidateEmptyWebAppSettingsPolicy();
 }
diff --git a/chrome/browser/web_applications/web_app_registrar.cc b/chrome/browser/web_applications/web_app_registrar.cc
index a4f8f1f..1dc83c4 100644
--- a/chrome/browser/web_applications/web_app_registrar.cc
+++ b/chrome/browser/web_applications/web_app_registrar.cc
@@ -1659,7 +1659,7 @@
   }
 
   std::optional<std::vector<std::string>> app_policy_ids =
-      web_app::GetPolicyIds(profile(), *web_app);
+      WebAppPolicyManager::GetPolicyIds(profile(), *web_app);
 
   if (!app_policy_ids->empty()) {
     return base::Contains(app_policy_ids.value(), *file_extension_policy_id);
@@ -1680,7 +1680,7 @@
   }
 
   std::optional<std::vector<std::string>> app_policy_ids =
-      web_app::GetPolicyIds(profile(), *web_app);
+      WebAppPolicyManager::GetPolicyIds(profile(), *web_app);
 
   if (!app_policy_ids->empty()) {
     return std::ranges::any_of(default_handlers, [&](const auto& handler) {
diff --git a/chrome/browser/web_applications/web_app_utils.cc b/chrome/browser/web_applications/web_app_utils.cc
index 3ebfe1d..b57b5d7 100644
--- a/chrome/browser/web_applications/web_app_utils.cc
+++ b/chrome/browser/web_applications/web_app_utils.cc
@@ -18,10 +18,7 @@
 #include "base/containers/contains.h"
 #include "base/containers/enum_set.h"
 #include "base/containers/extend.h"
-#include "base/containers/fixed_flat_map.h"
-#include "base/containers/flat_set.h"
 #include "base/containers/map_util.h"
-#include "base/containers/to_vector.h"
 #include "base/files/file_path.h"
 #include "base/functional/bind.h"
 #include "base/functional/callback_helpers.h"
@@ -35,6 +32,7 @@
 #include "build/buildflag.h"
 #include "chrome/browser/content_settings/host_content_settings_map_factory.h"
 #include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/web_applications/policy/web_app_policy_manager.h"
 #include "chrome/browser/web_applications/proto/web_app_install_state.pb.h"
 #include "chrome/browser/web_applications/web_app_constants.h"
 #include "chrome/browser/web_applications/web_app_icon_manager.h"
@@ -84,73 +82,6 @@
 
 namespace {
 
-#if BUILDFLAG(IS_CHROMEOS)
-
-// This mapping excludes SWAs not included in official builds (like SAMPLE).
-// These app Id constants need to be kept in sync with java/com/
-// google/chrome/cros/policyconverter/ChromePolicySettingsProcessor.java
-constexpr auto kSystemWebAppsMapping =
-    base::MakeFixedFlatMap<std::string_view, ash::SystemWebAppType>(
-        {{"file_manager", ash::SystemWebAppType::FILE_MANAGER},
-         {"settings", ash::SystemWebAppType::SETTINGS},
-         {"camera", ash::SystemWebAppType::CAMERA},
-         {"terminal", ash::SystemWebAppType::TERMINAL},
-         {"media", ash::SystemWebAppType::MEDIA},
-         {"help", ash::SystemWebAppType::HELP},
-         {"print_management", ash::SystemWebAppType::PRINT_MANAGEMENT},
-         {"scanning", ash::SystemWebAppType::SCANNING},
-         {"diagnostics", ash::SystemWebAppType::DIAGNOSTICS},
-         {"connectivity_diagnostics",
-          ash::SystemWebAppType::CONNECTIVITY_DIAGNOSTICS},
-         {"eche", ash::SystemWebAppType::ECHE},
-         {"crosh", ash::SystemWebAppType::CROSH},
-         {"personalization", ash::SystemWebAppType::PERSONALIZATION},
-         {"shortcut_customization",
-          ash::SystemWebAppType::SHORTCUT_CUSTOMIZATION},
-         {"shimless_rma", ash::SystemWebAppType::SHIMLESS_RMA},
-         {"demo_mode", ash::SystemWebAppType::DEMO_MODE},
-         {"os_feedback", ash::SystemWebAppType::OS_FEEDBACK},
-         {"os_sanitize", ash::SystemWebAppType::OS_SANITIZE},
-         {"projector", ash::SystemWebAppType::PROJECTOR},
-         {"firmware_update", ash::SystemWebAppType::FIRMWARE_UPDATE},
-         {"os_flags", ash::SystemWebAppType::OS_FLAGS},
-         {"vc_background", ash::SystemWebAppType::VC_BACKGROUND},
-         {"print_preview_cros", ash::SystemWebAppType::PRINT_PREVIEW_CROS},
-         {"boca", ash::SystemWebAppType::BOCA},
-         {"app_mall", ash::SystemWebAppType::MALL},
-         {"recorder", ash::SystemWebAppType::RECORDER},
-         {"graduation", ash::SystemWebAppType::GRADUATION}});
-
-constexpr ash::SystemWebAppType GetMaxSystemWebAppType() {
-  return std::ranges::max_element(
-             kSystemWebAppsMapping, std::ranges::less{},
-             &decltype(kSystemWebAppsMapping)::value_type::second)
-      ->second;
-}
-
-static_assert(GetMaxSystemWebAppType() == ash::SystemWebAppType::kMaxValue,
-              "Not all SWA types are listed in |system_web_apps_mapping|.");
-
-#endif  // BUILDFLAG(IS_CHROMEOS)
-
-// Note that this mapping lists only selected Preinstalled Web Apps
-// actively used in policies and is not meant to be exhaustive.
-// These app Id constants need to be kept in sync with java/com/
-// google/chrome/cros/policyconverter/ChromePolicySettingsProcessor.java
-// LINT.IfChange
-constexpr auto kPreinstalledWebAppsMapping =
-    base::MakeFixedFlatMap<std::string_view, std::string_view>(
-        {{"cursive", ash::kCursiveAppId}, {"canvas", ash::kCanvasAppId}});
-
-std::optional<base::flat_map<std::string_view, std::string_view>>&
-GetPreinstalledWebAppsMappingForTesting() {
-  static base::NoDestructor<
-      std::optional<base::flat_map<std::string_view, std::string_view>>>
-      preinstalled_web_apps_mapping_for_testing;
-  return *preinstalled_web_apps_mapping_for_testing;
-}
-// LINT.ThenChange(//depot/google3/java/com/google/chrome/cros/policyconverter/ChromePolicySettingsProcessor.java)
-
 GURL EncodeIconAsUrl(const SkBitmap& bitmap) {
   std::optional<std::vector<uint8_t>> output =
       gfx::PNGCodec::EncodeBGRASkBitmap(bitmap, /*discard_transparency=*/false);
@@ -267,32 +198,6 @@
 
 }  // namespace
 
-std::optional<std::string_view> GetPolicyIdForPreinstalledWebApp(
-    std::string_view app_id) {
-  if (const auto& test_mapping = GetPreinstalledWebAppsMappingForTesting()) {
-    for (const auto& [policy_id, mapped_app_id] : *test_mapping) {
-      if (mapped_app_id == app_id) {
-        return policy_id;
-      }
-    }
-    return {};
-  }
-
-  for (const auto& [policy_id, mapped_app_id] : kPreinstalledWebAppsMapping) {
-    if (mapped_app_id == app_id) {
-      return policy_id;
-    }
-  }
-  return {};
-}
-
-void SetPreinstalledWebAppsMappingForTesting(  // IN-TEST
-    std::optional<base::flat_map<std::string_view, std::string_view>>
-        preinstalled_web_apps_mapping_for_testing) {
-  GetPreinstalledWebAppsMappingForTesting() =                // IN-TEST
-      std::move(preinstalled_web_apps_mapping_for_testing);  // IN-TEST
-}
-
 constexpr base::FilePath::CharType kManifestResourcesDirectoryName[] =
     FILE_PATH_LITERAL("Manifest Resources");
 
@@ -535,107 +440,6 @@
   return path.substr(1);
 }
 
-#if BUILDFLAG(IS_CHROMEOS)
-std::optional<std::string_view> GetPolicyIdForSystemWebAppType(
-    ash::SystemWebAppType swa_type) {
-  for (const auto& [policy_id, mapped_swa_type] : kSystemWebAppsMapping) {
-    if (mapped_swa_type == swa_type) {
-      return policy_id;
-    }
-  }
-  return {};
-}
-#endif  // BUILDFLAG(IS_CHROMEOS)
-
-bool IsChromeAppPolicyId(std::string_view policy_id) {
-  return crx_file::id_util::IdIsValid(policy_id);
-}
-
-#if BUILDFLAG(IS_CHROMEOS)
-bool IsArcAppPolicyId(std::string_view policy_id) {
-  return base::Contains(policy_id, '.') && !IsWebAppPolicyId(policy_id);
-}
-#endif  // BUILDFLAG(IS_CHROMEOS)
-
-bool IsWebAppPolicyId(std::string_view policy_id) {
-  return GURL{policy_id}.is_valid();
-}
-
-#if BUILDFLAG(IS_CHROMEOS)
-bool IsSystemWebAppPolicyId(std::string_view policy_id) {
-  return base::Contains(kSystemWebAppsMapping, policy_id);
-}
-#endif  // BUILDFLAG(IS_CHROMEOS)
-
-bool IsPreinstalledWebAppPolicyId(std::string_view policy_id) {
-  if (auto& mapping = GetPreinstalledWebAppsMappingForTesting()) {  // IN-TEST
-    return base::Contains(*mapping, policy_id);
-  }
-  return base::Contains(kPreinstalledWebAppsMapping, policy_id);
-}
-
-bool IsIsolatedWebAppPolicyId(std::string_view policy_id) {
-  return web_package::SignedWebBundleId::Create(policy_id).has_value();
-}
-
-std::vector<std::string> GetPolicyIds(Profile* profile, const WebApp& web_app) {
-  const auto& app_id = web_app.app_id();
-  WebAppRegistrar& web_app_registrar =
-      WebAppProvider::GetForWebApps(profile)->registrar_unsafe();
-
-  if (web_app_registrar.IsIsolated(app_id) &&
-      web_app_registrar.IsInstalledByPolicy(app_id)) {
-    // This is an IWA - and thus, web_bundle_id == policy_id == URL hostname
-    return {web_app.start_url().host()};
-  }
-
-  std::vector<std::string> policy_ids;
-
-  if (std::optional<std::string_view> preinstalled_web_app_policy_id =
-          GetPolicyIdForPreinstalledWebApp(app_id)) {
-    policy_ids.emplace_back(*preinstalled_web_app_policy_id);
-  }
-
-#if BUILDFLAG(IS_CHROMEOS)
-  const auto& swa_data = web_app.client_data().system_web_app_data;
-  if (swa_data) {
-    const ash::SystemWebAppType swa_type = swa_data->system_app_type;
-    const std::optional<std::string_view> swa_policy_id =
-        GetPolicyIdForSystemWebAppType(swa_type);
-    if (swa_policy_id) {
-      policy_ids.emplace_back(*swa_policy_id);
-    }
-
-    // File Manager SWA uses File Manager Extension's ID for policy.
-    if (swa_type == ash::SystemWebAppType::FILE_MANAGER) {
-      policy_ids.push_back(file_manager::kFileManagerAppId);
-    }
-  }
-#endif  // BUIDLFLAG(IS_CHROMEOS)
-
-  for (const auto& [source, external_config] :
-       web_app.management_to_external_config_map()) {
-    if (!external_config.additional_policy_ids.empty()) {
-      base::Extend(policy_ids, external_config.additional_policy_ids);
-    }
-  }
-
-  if (!web_app_registrar.HasExternalAppWithInstallSource(
-          app_id, ExternalInstallSource::kExternalPolicy)) {
-    return policy_ids;
-  }
-
-  base::flat_map<webapps::AppId, base::flat_set<GURL>> installed_apps =
-      web_app_registrar.GetExternallyInstalledApps(
-          ExternalInstallSource::kExternalPolicy);
-  if (auto* install_urls = base::FindOrNull(installed_apps, app_id)) {
-    DCHECK(!install_urls->empty());
-    base::Extend(policy_ids, base::ToVector(*install_urls, &GURL::spec));
-  }
-
-  return policy_ids;
-}
-
 bool IsInScope(const GURL& url, const GURL& scope) {
   if (!scope.is_valid()) {
     return false;
diff --git a/chrome/browser/web_applications/web_app_utils.h b/chrome/browser/web_applications/web_app_utils.h
index 672a575..1f820e5 100644
--- a/chrome/browser/web_applications/web_app_utils.h
+++ b/chrome/browser/web_applications/web_app_utils.h
@@ -7,15 +7,10 @@
 
 #include <stddef.h>
 
-#include <optional>
 #include <set>
 #include <string>
-#include <string_view>
 #include <tuple>
-#include <vector>
 
-#include "base/containers/flat_map.h"
-#include "build/build_config.h"
 #include "chrome/browser/web_applications/web_app.h"
 #include "chrome/browser/web_applications/web_app_constants.h"
 #include "chrome/browser/web_applications/web_app_management_type.h"
@@ -25,9 +20,6 @@
 
 class GURL;
 class Profile;
-#if BUILDFLAG(IS_CHROMEOS)
-enum class SystemWebAppType;
-#endif  // BUILDFLAG(IS_CHROMEOS)
 
 namespace apps {
 enum class LaunchContainer;
@@ -139,48 +131,6 @@
 // Extracts app_id from chrome://app-settings/<app-id> URL path.
 webapps::AppId GetAppIdFromAppSettingsUrl(const GURL& url);
 
-// Checks whether |policy_id| specifies a Chrome App.
-bool IsChromeAppPolicyId(std::string_view policy_id);
-
-#if BUILDFLAG(IS_CHROMEOS)
-// Checks whether |policy_id| specifies an Arc App.
-bool IsArcAppPolicyId(std::string_view policy_id);
-#endif  // BUILDFLAG(IS_CHROMEOS)
-
-// Checks whether |policy_id| specifies a Web App.
-bool IsWebAppPolicyId(std::string_view policy_id);
-
-// TODO(https://crbug.com/411013748) Move WebApp utils to WebAppPolicyManager
-#if BUILDFLAG(IS_CHROMEOS)
-// Checks whether |policy_id| specifies a System Web App.
-bool IsSystemWebAppPolicyId(std::string_view policy_id);
-#endif  // BUILDFLAG(IS_CHROMEOS)
-
-// Checks whether |policy_id| specifies a Preinstalled Web App.
-bool IsPreinstalledWebAppPolicyId(std::string_view policy_id);
-
-// Checks whether |policy_id| specifies an Isolated Web App.
-bool IsIsolatedWebAppPolicyId(std::string_view policy_id);
-
-std::vector<std::string> GetPolicyIds(Profile* profile, const WebApp& web_app);
-
-#if BUILDFLAG(IS_CHROMEOS)
-// Maps `SystemWebAppType` to a policy id. Returns the associated policy id.
-// Returns std::nullopt for apps not included in official builds.
-std::optional<std::string_view> GetPolicyIdForSystemWebAppType(
-    ash::SystemWebAppType swa_type);
-#endif  // BUILDFLAG(IS_CHROMEOS)
-
-// Returns the policy ID for a given preinstalled web app ID. Note that not all
-// preinstalled web apps are supposed to have a policy ID (currently we only
-// support EDU apps) - in all other cases this will return std::nullopt.
-std::optional<std::string_view> GetPolicyIdForPreinstalledWebApp(
-    std::string_view preinstalled_web_app_id);
-
-void SetPreinstalledWebAppsMappingForTesting(
-    std::optional<base::flat_map<std::string_view, std::string_view>>
-        preinstalled_web_apps_mapping_for_testing);
-
 // Returns whether `url` is in scope `scope`. False if scope is invalid.
 bool IsInScope(const GURL& url, const GURL& scope);
 
diff --git a/chrome/browser/web_applications/web_app_utils_unittest.cc b/chrome/browser/web_applications/web_app_utils_unittest.cc
index a9f8ab5..120c430 100644
--- a/chrome/browser/web_applications/web_app_utils_unittest.cc
+++ b/chrome/browser/web_applications/web_app_utils_unittest.cc
@@ -4,32 +4,20 @@
 
 #include "chrome/browser/web_applications/web_app_utils.h"
 
-#include <map>
 #include <memory>
-#include <string>
-#include <vector>
 
 #include "ash/constants/web_app_id_constants.h"
 #include "base/containers/adapters.h"
 #include "base/files/file_path.h"
-#include "base/memory/raw_ptr.h"
-#include "base/test/test_future.h"
 #include "build/build_config.h"
 #include "chrome/browser/profiles/profile.h"
-#include "chrome/browser/web_applications/externally_managed_app_manager.h"
-#include "chrome/browser/web_applications/isolated_web_apps/isolated_web_app_url_info.h"
-#include "chrome/browser/web_applications/isolated_web_apps/test/isolated_web_app_builder.h"
-#include "chrome/browser/web_applications/test/fake_web_app_provider.h"
-#include "chrome/browser/web_applications/test/web_app_install_test_utils.h"
+#include "chrome/browser/web_applications/mojom/user_display_mode.mojom.h"
 #include "chrome/browser/web_applications/test/web_app_test.h"
 #include "chrome/browser/web_applications/test/web_app_test_utils.h"
-#include "chrome/browser/web_applications/web_app_command_scheduler.h"
 #include "chrome/browser/web_applications/web_app_install_info.h"
 #include "chrome/browser/web_applications/web_app_management_type.h"
-#include "chrome/browser/web_applications/web_app_registrar.h"
 #include "chrome/common/chrome_constants.h"
 #include "chrome/common/chrome_features.h"
-#include "services/data_decoder/public/cpp/test_support/in_process_data_decoder.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
@@ -44,7 +32,6 @@
 
 using WebAppUtilsTest = WebAppTest;
 using ::testing::ElementsAre;
-class FakeWebAppProvider;
 
 // Sanity check that iteration order of SortedSizesPx is ascending. The
 // correctness of most usage of SortedSizesPx depends on this.
@@ -250,71 +237,4 @@
 }
 #endif  // BUILDFLAG(GOOGLE_CHROME_BRANDING) && BUILDFLAG(IS_CHROMEOS)
 
-class WebAppUtilsPolicyIdsTest : public WebAppTest {
- public:
-  using InstallResults = std::map<GURL /*install_url*/,
-                                  ExternallyManagedAppManager::InstallResult>;
-  using UninstallResults =
-      std::map<GURL /*install_url*/, webapps::UninstallResultCode>;
-  using SynchronizeFuture =
-      base::test::TestFuture<InstallResults, UninstallResults>;
-
-  void SetUp() override {
-    WebAppTest::SetUp();
-    provider_ = FakeWebAppProvider::Get(profile());
-    provider_->UseRealOsIntegrationManager();
-    test::AwaitStartWebAppProviderAndSubsystems(profile());
-  }
-
- private:
-  raw_ptr<FakeWebAppProvider, DanglingUntriaged> provider_ = nullptr;
-  data_decoder::test::InProcessDataDecoder in_process_data_decoder_;
-};
-
-TEST_F(WebAppUtilsPolicyIdsTest, GetWebAppPolicyIdsForWebApp) {
-  const GURL kWebAppUrl = GURL("https://example.com/path/index.html");
-  const GURL kInstallUrl = GURL("https://www.example.com/install_url.html");
-  const GURL kManifestUrl = GURL("https://www.example.com/manifest.json");
-
-  webapps::AppId app_id =
-      static_cast<FakeWebContentsManager&>(
-          fake_provider().web_contents_manager())
-          .CreateBasicInstallPageState(kInstallUrl, kManifestUrl, kWebAppUrl);
-
-  ExternalInstallOptions template_options(
-      kInstallUrl, mojom::UserDisplayMode::kStandalone,
-      ExternalInstallSource::kExternalPolicy);
-
-  SynchronizeFuture result;
-  std::vector<ExternalInstallOptions> install_options_list;
-  install_options_list.emplace_back(kInstallUrl,
-                                    /*user_display_mode=*/std::nullopt,
-                                    ExternalInstallSource::kExternalPolicy);
-
-  fake_provider().externally_managed_app_manager().SynchronizeInstalledApps(
-      std::move(install_options_list), ExternalInstallSource::kExternalPolicy,
-      result.GetCallback());
-  ASSERT_TRUE(result.Wait());
-  const WebApp* app = fake_provider().registrar_unsafe().GetAppById(app_id);
-
-  EXPECT_EQ(
-      GetPolicyIds(profile(), *app),
-      std::vector<std::string>({"https://www.example.com/install_url.html"}));
-}
-
-TEST_F(WebAppUtilsPolicyIdsTest, GetWebAppPolicyIdsForIsolatedWebApp) {
-  auto bundle = IsolatedWebAppBuilder(ManifestBuilder().SetVersion("1.0.0"))
-                    .BuildBundle();
-  IsolatedWebAppUrlInfo info =
-      bundle
-          ->InstallWithSource(profile(),
-                              &IsolatedWebAppInstallSource::FromExternalPolicy)
-          .value();
-  const WebApp* app =
-      fake_provider().registrar_unsafe().GetAppById(info.app_id());
-
-  EXPECT_EQ(GetPolicyIds(profile(), *app),
-            std::vector<std::string>({info.web_bundle_id().id()}));
-}
-
 }  // namespace web_app
diff --git a/chrome/browser_exposed_mojom_targets.gni b/chrome/browser_exposed_mojom_targets.gni
index e687936..33ebc07 100644
--- a/chrome/browser_exposed_mojom_targets.gni
+++ b/chrome/browser_exposed_mojom_targets.gni
@@ -25,6 +25,7 @@
   "//chrome/browser/resources/certificate_manager:mojom",
   "//chrome/browser/ui/tabs/tab_strip_api:mojom",
   "//chrome/browser/ui/webui/access_code_cast:mojo_bindings",
+  "//chrome/browser/ui/webui/actor_internals:mojo_bindings",
   "//chrome/browser/ui/webui/app_home:mojo_bindings",
   "//chrome/browser/ui/webui/app_service_internals:mojo_bindings",
   "//chrome/browser/ui/webui/bluetooth_internals:mojo_bindings",
@@ -172,7 +173,7 @@
   "//content/browser/indexed_db:internals_mojo_bindings",
   "//content/browser/private_aggregation:mojo_bindings",
   "//content/browser/process_internals:mojo_bindings",
-  "//content/browser/tracing/trace_report:mojo_bindings",
+  "//content/browser/tracing/traces_internals:mojo_bindings",
   "//content/browser/xr/webxr_internals/mojom:mojo_bindings",
   "//content/common:mojo_bindings",
   "//content/public/common:interfaces",
diff --git a/chrome/build/android-arm64.pgo.txt b/chrome/build/android-arm64.pgo.txt
index f1dda51..c4b049f 100644
--- a/chrome/build/android-arm64.pgo.txt
+++ b/chrome/build/android-arm64.pgo.txt
@@ -1 +1 @@
-chrome-android64-main-1749095350-a8132cbab3d70e7b6dc7c7187b338e58bb268f1a-50b409e993d5468ee2a8e6e1b9726e70e00280d9.profdata
+chrome-android64-main-1749127202-62a974c33d6d99b18b3da59f59b4057354da1602-8af105aa2c06befede8aa22809bfec6f75d7efa2.profdata
diff --git a/chrome/build/linux.pgo.txt b/chrome/build/linux.pgo.txt
index 9223b43..d27cf5aa 100644
--- a/chrome/build/linux.pgo.txt
+++ b/chrome/build/linux.pgo.txt
@@ -1 +1 @@
-chrome-linux-main-1749081115-1e9ddf5478e8b15236b724200e426160a69cb1bf-c4c80ea2a5c252476eeba2726a1862b2908d9b42.profdata
+chrome-linux-main-1749124318-7b2ef47ceceda339f55d3e39affe05f224770a9d-26cbea720b9742a07237e7129eaadad0e8ba70e4.profdata
diff --git a/chrome/build/mac-arm.pgo.txt b/chrome/build/mac-arm.pgo.txt
index b7f3bbd..04af227c 100644
--- a/chrome/build/mac-arm.pgo.txt
+++ b/chrome/build/mac-arm.pgo.txt
@@ -1 +1 @@
-chrome-mac-arm-main-1749103132-e69b4a9ba5dee47af19e6d851922284ffd8e39c3-0b02c2102a30672284a328d6a342b4fcbe77a5c6.profdata
+chrome-mac-arm-main-1749131410-ddbb787b277fbd92608e4309af1f138437291a38-463eacc03950670d682596059adc9ffb9f5f6591.profdata
diff --git a/chrome/build/win-arm64.pgo.txt b/chrome/build/win-arm64.pgo.txt
index 5841048..34abd918 100644
--- a/chrome/build/win-arm64.pgo.txt
+++ b/chrome/build/win-arm64.pgo.txt
@@ -1 +1 @@
-chrome-win-arm64-main-1749081115-1c6a1381af5d1e9a683889d42edb52b972257c2a-c4c80ea2a5c252476eeba2726a1862b2908d9b42.profdata
+chrome-win-arm64-main-1749124318-34599a178dbe65d69dfd545fd1c9f28c5c402ef9-26cbea720b9742a07237e7129eaadad0e8ba70e4.profdata
diff --git a/chrome/build/win32.pgo.txt b/chrome/build/win32.pgo.txt
index c4b5448..c185f43 100644
--- a/chrome/build/win32.pgo.txt
+++ b/chrome/build/win32.pgo.txt
@@ -1 +1 @@
-chrome-win32-main-1749070729-106ed1eba7926510fb133ed1c1dd42567e9217c0-111f947c38250bd7fdf0af4ce5f64a9ae02867f5.profdata
+chrome-win32-main-1749103132-399b5f4ed9e1997e9e8d26d32c909a6d5cdfb66a-0b02c2102a30672284a328d6a342b4fcbe77a5c6.profdata
diff --git a/chrome/build/win64.pgo.txt b/chrome/build/win64.pgo.txt
index 98fdec4..ac2f0b5 100644
--- a/chrome/build/win64.pgo.txt
+++ b/chrome/build/win64.pgo.txt
@@ -1 +1 @@
-chrome-win64-main-1749027507-3066109f82f6da8f3b0143b41cc9c7414868b91f-18340da82c2b57070968681ff13136fa3f054f3e.profdata
+chrome-win64-main-1749103132-31202ad215cdd281634da33eb62e34d5520e3a2d-0b02c2102a30672284a328d6a342b4fcbe77a5c6.profdata
diff --git a/chrome/common/actor.mojom b/chrome/common/actor.mojom
index 93ce1b5..7088375 100644
--- a/chrome/common/actor.mojom
+++ b/chrome/common/actor.mojom
@@ -4,6 +4,7 @@
 
 module actor.mojom;
 
+import "mojo/public/mojom/base/time.mojom";
 import "ui/gfx/geometry/mojom/geometry.mojom";
 
 // This interface is meant to largely mirror the
@@ -293,6 +294,9 @@
 
 // All information required to invoke a tool in the renderer.
 struct ToolInvocation {
+  // The task ID owning this invocation.
+  int32 task_id;
+
   ToolAction action;
 };
 
@@ -303,3 +307,42 @@
   // If the code is not kOk, an English language message describing the error.
   string message;
 };
+
+// The type of the journal entry.
+enum JournalEntryType {
+  kBegin,
+  kEnd,
+  kInstant,
+};
+
+// Represents a journal entry. This should only be used for logging and
+// debugging. It should not be used to make logic decisions since a
+// compromised renderer could lie about events (such as mismatched
+// or missing begin and end events).
+struct JournalEntry {
+  // What type of entry this is.
+  JournalEntryType type;
+
+  // The task ID associated with this entry.
+  int32 task_id;
+
+  // Unique identifier for entry. Begin/End types
+  // should use the same id.
+  uint64 id;
+
+  // The time the event occurred at.
+  mojo_base.mojom.Time timestamp;
+
+  // The event name.
+  string event;
+
+  // Specific details of the event logged.
+  string details;
+};
+
+// Use to listen for new journal entries. Lives in the browser process and
+// is used to receive entries from renderer processes.
+interface JournalClient {
+  // New log messages are sent in batches to limit the frequency of calls.
+  AddEntriesToJournal(array<JournalEntry> entries);
+};
diff --git a/chrome/common/chrome_render_frame.mojom b/chrome/common/chrome_render_frame.mojom
index f51f72c..fe6b558 100644
--- a/chrome/common/chrome_render_frame.mojom
+++ b/chrome/common/chrome_render_frame.mojom
@@ -106,4 +106,11 @@
   [EnableIfNot=is_android]
   InvokeTool(actor.mojom.ToolInvocation request) =>
       (actor.mojom.ActionResult result);
+
+  // Requests actor automation framework journaling. Journaling can be
+  // stopped by disconnecting the passed in remote. We use an associated
+  // remote here to keep this consistent with navigation events. The
+  // journal will record the last committed URL in the logs.
+  [EnableIfNot=is_android] StartActorJournal(
+    pending_associated_remote<actor.mojom.JournalClient> client);
 };
diff --git a/chrome/common/webui_url_constants.cc b/chrome/common/webui_url_constants.cc
index e5e724a2..4df20078 100644
--- a/chrome/common/webui_url_constants.cc
+++ b/chrome/common/webui_url_constants.cc
@@ -74,6 +74,7 @@
   static constexpr auto kChromeURLHosts = std::to_array<base::cstring_view>({
       kChromeUIAboutHost,
       kChromeUIAccessibilityHost,
+      kChromeUIActorInternalsHost,
 #if !BUILDFLAG(IS_ANDROID)
       kChromeUIAppServiceInternalsHost,
 #endif
diff --git a/chrome/common/webui_url_constants.h b/chrome/common/webui_url_constants.h
index c35d525..e729fff 100644
--- a/chrome/common/webui_url_constants.h
+++ b/chrome/common/webui_url_constants.h
@@ -39,6 +39,7 @@
 inline constexpr char kChromeUIAccessibilityHost[] = "accessibility";
 inline constexpr char kChromeUIActivateSafetyCheckSettingsURL[] =
     "chrome://settings/safetyCheck?activateSafetyCheck";
+inline constexpr char kChromeUIActorInternalsHost[] = "actor-internals";
 inline constexpr char kChromeUIAllSitesPath[] = "/content/all";
 inline constexpr char kChromeUIAppIconHost[] = "app-icon";
 inline constexpr char kChromeUIAppIconURL[] = "chrome://app-icon/";
diff --git a/chrome/renderer/actor/BUILD.gn b/chrome/renderer/actor/BUILD.gn
index a7fe10c..3e894c6f 100644
--- a/chrome/renderer/actor/BUILD.gn
+++ b/chrome/renderer/actor/BUILD.gn
@@ -12,6 +12,8 @@
     "click_tool.h",
     "drag_and_release_tool.cc",
     "drag_and_release_tool.h",
+    "journal.cc",
+    "journal.h",
     "mouse_move_tool.cc",
     "mouse_move_tool.h",
     "scroll_tool.cc",
diff --git a/chrome/renderer/actor/click_tool.cc b/chrome/renderer/actor/click_tool.cc
index 75ccb68..b6cacd2 100644
--- a/chrome/renderer/actor/click_tool.cc
+++ b/chrome/renderer/actor/click_tool.cc
@@ -37,8 +37,11 @@
 using ::blink::WebMouseEvent;
 using ::blink::WebNode;
 
-ClickTool::ClickTool(mojom::ClickActionPtr action, content::RenderFrame& frame)
-    : frame_(frame), action_(std::move(action)) {}
+ClickTool::ClickTool(content::RenderFrame& frame,
+                     Journal::TaskId task_id,
+                     Journal& journal,
+                     mojom::ClickActionPtr action)
+    : ToolBase(frame, task_id, journal), action_(std::move(action)) {}
 
 ClickTool::~ClickTool() = default;
 
diff --git a/chrome/renderer/actor/click_tool.h b/chrome/renderer/actor/click_tool.h
index bf357692..a098dd93 100644
--- a/chrome/renderer/actor/click_tool.h
+++ b/chrome/renderer/actor/click_tool.h
@@ -29,7 +29,10 @@
 // A tool that can be invoked to perform a click on a target.
 class ClickTool : public ToolBase {
  public:
-  ClickTool(mojom::ClickActionPtr action, content::RenderFrame& frame);
+  ClickTool(content::RenderFrame& frame,
+            Journal::TaskId task_id,
+            Journal& journal,
+            mojom::ClickActionPtr action);
   ~ClickTool() override;
 
   // actor::ToolBase
@@ -43,9 +46,6 @@
   void SendMouseUp(blink::WebMouseEvent mouse_event,
                    ToolFinishedCallback callback);
 
-  // Raw ref since this is owned by ToolExecutor whose lifetime is tied to
-  // RenderFrame.
-  base::raw_ref<content::RenderFrame> frame_;
   mojom::ClickActionPtr action_;
 };
 
diff --git a/chrome/renderer/actor/drag_and_release_tool.cc b/chrome/renderer/actor/drag_and_release_tool.cc
index eb85098b..1b452f12 100644
--- a/chrome/renderer/actor/drag_and_release_tool.cc
+++ b/chrome/renderer/actor/drag_and_release_tool.cc
@@ -29,9 +29,11 @@
 using ::blink::WebMouseEvent;
 using ::blink::mojom::EventType;
 
-DragAndReleaseTool::DragAndReleaseTool(mojom::DragAndReleaseActionPtr action,
-                                       content::RenderFrame& frame)
-    : frame_(frame), action_(std::move(action)) {}
+DragAndReleaseTool::DragAndReleaseTool(content::RenderFrame& frame,
+                                       Journal::TaskId task_id,
+                                       Journal& journal,
+                                       mojom::DragAndReleaseActionPtr action)
+    : ToolBase(frame, task_id, journal), action_(std::move(action)) {}
 
 DragAndReleaseTool::~DragAndReleaseTool() = default;
 
diff --git a/chrome/renderer/actor/drag_and_release_tool.h b/chrome/renderer/actor/drag_and_release_tool.h
index 1abae758..c65c660 100644
--- a/chrome/renderer/actor/drag_and_release_tool.h
+++ b/chrome/renderer/actor/drag_and_release_tool.h
@@ -27,8 +27,10 @@
 // A tool that can be invoked to perform a drag and release over a target.
 class DragAndReleaseTool : public ToolBase {
  public:
-  DragAndReleaseTool(mojom::DragAndReleaseActionPtr action,
-                     content::RenderFrame& frame);
+  DragAndReleaseTool(content::RenderFrame& frame,
+                     Journal::TaskId task_id,
+                     Journal& journal,
+                     mojom::DragAndReleaseActionPtr action);
 
   ~DragAndReleaseTool() override;
 
@@ -48,9 +50,6 @@
                         const gfx::PointF& position_in_widget,
                         blink::WebMouseEvent::Button button);
 
-  // Raw ref since this is owned by ToolExecutor whose lifetime is tied to
-  // RenderFrame.
-  base::raw_ref<content::RenderFrame> frame_;
   mojom::DragAndReleaseActionPtr action_;
 };
 
diff --git a/chrome/renderer/actor/journal.cc b/chrome/renderer/actor/journal.cc
new file mode 100644
index 0000000..fa2ae091
--- /dev/null
+++ b/chrome/renderer/actor/journal.cc
@@ -0,0 +1,69 @@
+// 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/renderer/actor/journal.h"
+
+#include "base/rand_util.h"
+#include "chrome/common/actor/actor_logging.h"
+
+namespace actor {
+
+namespace {
+constexpr base::TimeDelta kMinTimeSinceLastLogBufferSend =
+    base::Milliseconds(100);
+constexpr base::TimeDelta kSendLogBufferDelay = base::Milliseconds(200);
+}  // namespace
+
+Journal::Journal() : current_id_(base::RandUint64()) {}
+Journal::~Journal() {
+  if (log_buffer_.size() > 0) {
+    SendLogBuffer();
+  }
+}
+
+void Journal::Bind(mojo::PendingAssociatedRemote<mojom::JournalClient> client) {
+  client_.Bind(std::move(client));
+  client_.reset_on_disconnect();
+}
+
+void Journal::Log(int32_t task_id,
+                  std::string_view event,
+                  std::string_view details) {
+  ACTOR_LOG() << event << ": " << details;
+
+  if (!client_) {
+    return;
+  }
+
+  auto journal_entry = mojom::JournalEntry::New(
+      mojom::JournalEntryType::kInstant, task_id, current_id_++,
+      base::Time::Now(), std::string(event), std::string(details));
+  log_buffer_.push_back(std::move(journal_entry));
+  if (log_buffer_.size() > 1) {
+    // A delayed task has already been posted for sending the buffer contents.
+    return;
+  }
+
+  if ((base::TimeTicks::Now() - last_log_buffer_send_) >
+      kMinTimeSinceLastLogBufferSend) {
+    SendLogBuffer();
+  } else {
+    base::SequencedTaskRunner::GetCurrentDefault()->PostDelayedTask(
+        FROM_HERE,
+        base::BindOnce(&Journal::SendLogBuffer, weak_factory_.GetWeakPtr()),
+        kSendLogBufferDelay);
+  }
+}
+
+void Journal::SendLogBuffer() {
+  last_log_buffer_send_ = base::TimeTicks::Now();
+  if (client_) {
+    client_->AddEntriesToJournal(std::move(log_buffer_));
+  } else {
+    ACTOR_LOG() << "Clearing journal entries";
+    log_buffer_.clear();
+  }
+}
+
+}  // namespace actor
diff --git a/chrome/renderer/actor/journal.h b/chrome/renderer/actor/journal.h
new file mode 100644
index 0000000..3e769994
--- /dev/null
+++ b/chrome/renderer/actor/journal.h
@@ -0,0 +1,47 @@
+// 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_RENDERER_ACTOR_JOURNAL_H_
+#define CHROME_RENDERER_ACTOR_JOURNAL_H_
+
+#include "base/memory/weak_ptr.h"
+#include "base/time/time.h"
+#include "chrome/common/actor.mojom.h"
+#include "mojo/public/cpp/bindings/associated_remote.h"
+#include "mojo/public/cpp/bindings/pending_associated_remote.h"
+
+namespace actor {
+
+// A logging class that records actions taken be the actor.
+// This class employs a buffering strategy to minimize the number of
+// IPCs sent to the browser. The following strategy is employed:
+// 1) If there is a pending event in the buffer, don't do anything just
+//    wait for the previous event logged to trigger (the 200ms timeout).
+// 2) If it has been more than 100ms since we last sent data to the browser
+//    send it immediately.
+// 3) Otherwise schedule a timer to send the contents of the buffer in 200ms.
+class Journal {
+ public:
+  using TaskId = int32_t;
+
+  Journal();
+  ~Journal();
+
+  void Bind(mojo::PendingAssociatedRemote<mojom::JournalClient> client);
+  void Log(TaskId task_id, std::string_view event, std::string_view details);
+
+ private:
+  void SendLogBuffer();
+
+  mojo::AssociatedRemote<mojom::JournalClient> client_;
+  std::vector<mojom::JournalEntryPtr> log_buffer_;
+  base::TimeTicks last_log_buffer_send_;
+  uint64_t current_id_;
+
+  base::WeakPtrFactory<Journal> weak_factory_{this};
+};
+
+}  // namespace actor
+
+#endif  // CHROME_RENDERER_ACTOR_JOURNAL_H_
diff --git a/chrome/renderer/actor/mouse_move_tool.cc b/chrome/renderer/actor/mouse_move_tool.cc
index 7cb16fed..815b384b 100644
--- a/chrome/renderer/actor/mouse_move_tool.cc
+++ b/chrome/renderer/actor/mouse_move_tool.cc
@@ -38,9 +38,11 @@
 
 namespace actor {
 
-MouseMoveTool::MouseMoveTool(mojom::MouseMoveActionPtr action,
-                             content::RenderFrame& frame)
-    : frame_(frame), action_(std::move(action)) {}
+MouseMoveTool::MouseMoveTool(content::RenderFrame& frame,
+                             Journal::TaskId task_id,
+                             Journal& journal,
+                             mojom::MouseMoveActionPtr action)
+    : ToolBase(frame, task_id, journal), action_(std::move(action)) {}
 
 MouseMoveTool::~MouseMoveTool() = default;
 
diff --git a/chrome/renderer/actor/mouse_move_tool.h b/chrome/renderer/actor/mouse_move_tool.h
index aed1bf71..fa8fe139 100644
--- a/chrome/renderer/actor/mouse_move_tool.h
+++ b/chrome/renderer/actor/mouse_move_tool.h
@@ -22,7 +22,10 @@
 // A tool that can be invoked to perform a mouse move over a target.
 class MouseMoveTool : public ToolBase {
  public:
-  MouseMoveTool(mojom::MouseMoveActionPtr action, content::RenderFrame& frame);
+  MouseMoveTool(content::RenderFrame& frame,
+                Journal::TaskId task_id,
+                Journal& journal,
+                mojom::MouseMoveActionPtr action);
 
   ~MouseMoveTool() override;
 
@@ -34,9 +37,6 @@
   using ValidatedResult = base::expected<gfx::PointF, mojom::ActionResultPtr>;
   ValidatedResult Validate() const;
 
-  // Raw ref since this is owned by ToolExecutor whose lifetime is tied to
-  // RenderFrame.
-  base::raw_ref<content::RenderFrame> frame_;
   mojom::MouseMoveActionPtr action_;
 };
 
diff --git a/chrome/renderer/actor/scroll_tool.cc b/chrome/renderer/actor/scroll_tool.cc
index dc09634..a351bd72 100644
--- a/chrome/renderer/actor/scroll_tool.cc
+++ b/chrome/renderer/actor/scroll_tool.cc
@@ -28,9 +28,11 @@
 using ::blink::WebLocalFrame;
 using ::blink::WebNode;
 
-ScrollTool::ScrollTool(mojom::ScrollActionPtr action,
-                       content::RenderFrame& frame)
-    : frame_(frame), action_(std::move(action)) {}
+ScrollTool::ScrollTool(content::RenderFrame& frame,
+                       Journal::TaskId task_id,
+                       Journal& journal,
+                       mojom::ScrollActionPtr action)
+    : ToolBase(frame, task_id, journal), action_(std::move(action)) {}
 
 ScrollTool::~ScrollTool() = default;
 
diff --git a/chrome/renderer/actor/scroll_tool.h b/chrome/renderer/actor/scroll_tool.h
index b08bd5b..73dd59d3 100644
--- a/chrome/renderer/actor/scroll_tool.h
+++ b/chrome/renderer/actor/scroll_tool.h
@@ -23,7 +23,10 @@
 // A tool that can be invoked to perform a scroll over a target.
 class ScrollTool : public ToolBase {
  public:
-  ScrollTool(mojom::ScrollActionPtr action, content::RenderFrame& frame);
+  ScrollTool(content::RenderFrame& frame,
+             Journal::TaskId task_id,
+             Journal& journal,
+             mojom::ScrollActionPtr action);
 
   ~ScrollTool() override;
 
@@ -41,9 +44,6 @@
 
   ValidatedResult Validate() const;
 
-  // Raw ref since this is owned by ToolExecutor whose lifetime is tied to
-  // RenderFrame.
-  base::raw_ref<content::RenderFrame> frame_;
   mojom::ScrollActionPtr action_;
 };
 
diff --git a/chrome/renderer/actor/select_tool.cc b/chrome/renderer/actor/select_tool.cc
index bda2638..09a8bb73 100644
--- a/chrome/renderer/actor/select_tool.cc
+++ b/chrome/renderer/actor/select_tool.cc
@@ -28,9 +28,11 @@
 using blink::WebSelectElement;
 using blink::WebString;
 
-SelectTool::SelectTool(mojom::SelectActionPtr action,
-                       content::RenderFrame& frame)
-    : frame_(frame), action_(std::move(action)) {}
+SelectTool::SelectTool(content::RenderFrame& frame,
+                       Journal::TaskId task_id,
+                       Journal& journal,
+                       mojom::SelectActionPtr action)
+    : ToolBase(frame, task_id, journal), action_(std::move(action)) {}
 
 SelectTool::~SelectTool() = default;
 
diff --git a/chrome/renderer/actor/select_tool.h b/chrome/renderer/actor/select_tool.h
index b1b413f8f..b030ec3a 100644
--- a/chrome/renderer/actor/select_tool.h
+++ b/chrome/renderer/actor/select_tool.h
@@ -21,7 +21,10 @@
 // A tool that can be invoked to choose an option from a <select> element.
 class SelectTool : public ToolBase {
  public:
-  SelectTool(mojom::SelectActionPtr action, content::RenderFrame& frame);
+  SelectTool(content::RenderFrame& frame,
+             Journal::TaskId task_id,
+             Journal& journal,
+             mojom::SelectActionPtr action);
   ~SelectTool() override;
 
   // actor::ToolBase
@@ -37,9 +40,6 @@
       base::expected<TargetAndValue, mojom::ActionResultPtr>;
   ValidatedResult Validate() const;
 
-  // Raw ref since this is owned by ToolExecutor whose lifetime is tied to
-  // RenderFrame.
-  base::raw_ref<content::RenderFrame> frame_;
   mojom::SelectActionPtr action_;
 };
 
diff --git a/chrome/renderer/actor/tool_base.h b/chrome/renderer/actor/tool_base.h
index 2e6fb4d..e6dd2ce 100644
--- a/chrome/renderer/actor/tool_base.h
+++ b/chrome/renderer/actor/tool_base.h
@@ -9,12 +9,23 @@
 #include <string>
 
 #include "base/functional/callback_forward.h"
+#include "base/memory/raw_ref.h"
 #include "chrome/common/actor.mojom-forward.h"
+#include "chrome/renderer/actor/journal.h"
+
+namespace content {
+class RenderFrame;
+}
 
 namespace actor {
+
 class ToolBase {
  public:
   using ToolFinishedCallback = base::OnceCallback<void(mojom::ActionResultPtr)>;
+  ToolBase(content::RenderFrame& frame,
+           Journal::TaskId task_id,
+           Journal& journal)
+      : frame_(frame), task_id_(task_id), journal_(journal) {}
   virtual ~ToolBase() = default;
 
   // Executes the tool. `callback` is invoked with the tool result.
@@ -23,6 +34,13 @@
   // Returns a human readable string representing this tool and its parameters.
   // Used primarily for logging and debugging.
   virtual std::string DebugString() const = 0;
+
+ protected:
+  // Raw ref since this is owned by ToolExecutor whose lifetime is tied to
+  // RenderFrame.
+  base::raw_ref<content::RenderFrame> frame_;
+  Journal::TaskId task_id_;
+  base::raw_ref<Journal> journal_;
 };
 }  // namespace actor
 
diff --git a/chrome/renderer/actor/tool_executor.cc b/chrome/renderer/actor/tool_executor.cc
index 39a5fb13..71ba047 100644
--- a/chrome/renderer/actor/tool_executor.cc
+++ b/chrome/renderer/actor/tool_executor.cc
@@ -9,10 +9,11 @@
 
 #include "base/functional/callback.h"
 #include "base/memory/ptr_util.h"
+#include "base/notreached.h"
 #include "chrome/common/actor.mojom.h"
-#include "chrome/common/actor/actor_logging.h"
 #include "chrome/renderer/actor/click_tool.h"
 #include "chrome/renderer/actor/drag_and_release_tool.h"
+#include "chrome/renderer/actor/journal.h"
 #include "chrome/renderer/actor/mouse_move_tool.h"
 #include "chrome/renderer/actor/scroll_tool.h"
 #include "chrome/renderer/actor/select_tool.h"
@@ -25,7 +26,8 @@
 
 namespace actor {
 
-ToolExecutor::ToolExecutor(RenderFrame* frame) : frame_(*frame) {}
+ToolExecutor::ToolExecutor(RenderFrame* frame, Journal& journal)
+    : frame_(*frame), journal_(journal) {}
 
 ToolExecutor::~ToolExecutor() = default;
 
@@ -37,42 +39,50 @@
       // Check the mojom we received is in good shape.
       CHECK(request->action->get_click());
       tool_ = std::make_unique<ClickTool>(
-          std::move(request->action->get_click()), frame_.get());
+          frame_.get(), request->task_id, journal_.get(),
+          std::move(request->action->get_click()));
       break;
     }
     case actor::mojom::ToolAction::Tag::kMouseMove: {
       CHECK(request->action->get_mouse_move());
       tool_ = std::make_unique<MouseMoveTool>(
-          std::move(request->action->get_mouse_move()), frame_.get());
+          frame_.get(), request->task_id, journal_.get(),
+          std::move(request->action->get_mouse_move()));
       break;
     }
     case actor::mojom::ToolAction::Tag::kType: {
       CHECK(request->action->get_type());
-      tool_ = std::make_unique<TypeTool>(std::move(request->action->get_type()),
-                                         frame_.get());
+      tool_ = std::make_unique<TypeTool>(
+          frame_.get(), request->task_id, journal_.get(),
+          std::move(request->action->get_type()));
       break;
     }
     case actor::mojom::ToolAction::Tag::kScroll: {
       CHECK(request->action->get_scroll());
       tool_ = std::make_unique<ScrollTool>(
-          std::move(request->action->get_scroll()), frame_.get());
+          frame_.get(), request->task_id, journal_.get(),
+          std::move(request->action->get_scroll()));
       break;
     }
     case actor::mojom::ToolAction::Tag::kSelect: {
       CHECK(request->action->get_select());
       tool_ = std::make_unique<SelectTool>(
-          std::move(request->action->get_select()), frame_.get());
+          frame_.get(), request->task_id, journal_.get(),
+          std::move(request->action->get_select()));
       break;
     }
     case actor::mojom::ToolAction::Tag::kDragAndRelease: {
       CHECK(request->action->get_drag_and_release());
       tool_ = std::make_unique<DragAndReleaseTool>(
-          std::move(request->action->get_drag_and_release()), frame_.get());
+          frame_.get(), request->task_id, journal_.get(),
+          std::move(request->action->get_drag_and_release()));
       break;
     }
+    default:
+      NOTREACHED();
   }
 
-  ACTOR_LOG() << "Renderer InvokeTool: " << tool_->DebugString();
+  journal_->Log(request->task_id, "Renderer InvokeTool", tool_->DebugString());
 
   // It's safe to use base::Unretained as tool_ is owned by this object and
   // tool_ has its own weak factory to manage the callback.
diff --git a/chrome/renderer/actor/tool_executor.h b/chrome/renderer/actor/tool_executor.h
index 4100a70d..702ad197 100644
--- a/chrome/renderer/actor/tool_executor.h
+++ b/chrome/renderer/actor/tool_executor.h
@@ -19,6 +19,8 @@
 
 namespace actor {
 
+class Journal;
+
 // Renderer-side tool executor.
 //
 // This class is responsible for receiving tool request messages and invoking
@@ -32,7 +34,7 @@
 
  public:
   using ToolExecutorCallback = base::OnceCallback<void(mojom::ActionResultPtr)>;
-  explicit ToolExecutor(content::RenderFrame* frame);
+  explicit ToolExecutor(content::RenderFrame* frame, Journal& journal);
   ~ToolExecutor();
 
   ToolExecutor(const ToolExecutor&) = delete;
@@ -49,6 +51,7 @@
   // render frame so it must be outlived.
   base::raw_ref<content::RenderFrame> frame_;
   std::unique_ptr<ToolBase> tool_;
+  base::raw_ref<Journal> journal_;
 };
 
 }  // namespace actor
diff --git a/chrome/renderer/actor/type_tool.cc b/chrome/renderer/actor/type_tool.cc
index 257cb873..7e78b63 100644
--- a/chrome/renderer/actor/type_tool.cc
+++ b/chrome/renderer/actor/type_tool.cc
@@ -135,8 +135,11 @@
 TypeTool::KeyParams::~KeyParams() = default;
 TypeTool::KeyParams::KeyParams(const KeyParams& other) = default;
 
-TypeTool::TypeTool(mojom::TypeActionPtr action, content::RenderFrame& frame)
-    : frame_(frame), action_(std::move(action)) {}
+TypeTool::TypeTool(content::RenderFrame& frame,
+                   Journal::TaskId task_id,
+                   Journal& journal,
+                   mojom::TypeActionPtr action)
+    : ToolBase(frame, task_id, journal), action_(std::move(action)) {}
 
 TypeTool::~TypeTool() = default;
 
diff --git a/chrome/renderer/actor/type_tool.h b/chrome/renderer/actor/type_tool.h
index 55e7a052..4c98751 100644
--- a/chrome/renderer/actor/type_tool.h
+++ b/chrome/renderer/actor/type_tool.h
@@ -23,10 +23,15 @@
 
 namespace actor {
 
+class Journal;
+
 // A tool that simulates typing text into a target DOM node.
 class TypeTool : public ToolBase {
  public:
-  TypeTool(mojom::TypeActionPtr action, content::RenderFrame& frame);
+  TypeTool(content::RenderFrame& frame,
+           Journal::TaskId task_id,
+           Journal& journal,
+           mojom::TypeActionPtr action);
   ~TypeTool() override;
 
   // actor::ToolBase
@@ -77,9 +82,6 @@
       KeyParams key_params);
   mojom::ActionResultPtr SimulateKeyPress(TypeTool::KeyParams params);
 
-  // Raw ref since this is owned by ToolExecutor whose lifetime is tied to
-  // RenderFrame.
-  base::raw_ref<content::RenderFrame> frame_;
   mojom::TypeActionPtr action_;
 };
 
diff --git a/chrome/renderer/autofill/password_autofill_agent_browsertest.cc b/chrome/renderer/autofill/password_autofill_agent_browsertest.cc
index 4d3a17b..71df494 100644
--- a/chrome/renderer/autofill/password_autofill_agent_browsertest.cc
+++ b/chrome/renderer/autofill/password_autofill_agent_browsertest.cc
@@ -2,6 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#include <string>
 #ifdef UNSAFE_BUFFERS_BUILD
 // TODO(crbug.com/390223051): Remove C-library calls to fix the errors.
 #pragma allow_unsafe_libc_calls
@@ -5154,34 +5155,40 @@
 
 // Tests that `FillChangePasswordForm` fills change password form.
 TEST_F(PasswordAutofillAgentTest, FillChangePasswordForm) {
-  LoadHTML(kPasswordChangeFormHTML);
-  UpdateUrlForHTML(kPasswordChangeFormHTML);
+  std::vector<std::string> htms_to_test = {kPasswordChangeFormHTML,
+                                           kPasswordChangeWithoutFormHTML};
+  for (const std::string& html : htms_to_test) {
+    SCOPED_TRACE(testing::Message() << "Running for the page: " << html);
+    LoadHTML(html);
+    UpdateUrlForHTML(html);
 
-  WebInputElement password = GetInputElementByID("password"),
-                  new_password = GetInputElementByID("newpassword"),
-                  confirmation_password =
-                      GetInputElementByID("confirmpassword");
-  auto password_id = autofill::form_util::GetFieldRendererId(password),
-       new_password_id = autofill::form_util::GetFieldRendererId(new_password),
-       password_confirmation =
-           autofill::form_util::GetFieldRendererId(confirmation_password);
+    WebInputElement password = GetInputElementByID("password"),
+                    new_password = GetInputElementByID("newpassword"),
+                    confirmation_password =
+                        GetInputElementByID("confirmpassword");
+    auto password_id = autofill::form_util::GetFieldRendererId(password),
+         new_password_id =
+             autofill::form_util::GetFieldRendererId(new_password),
+         password_confirmation =
+             autofill::form_util::GetFieldRendererId(confirmation_password);
 
-  const std::vector<autofill::FormData>& parsed_form_data =
-      fake_driver_.form_data_parsed().value();
-  EXPECT_EQ(1u, parsed_form_data.size());
+    const std::vector<autofill::FormData>& parsed_form_data =
+        fake_driver_.form_data_parsed().value();
+    EXPECT_EQ(1u, parsed_form_data.size());
 
-  base::MockCallback<
-      base::OnceCallback<void(const std::optional<autofill::FormData>&)>>
-      mock_reply;
-  EXPECT_CALL(mock_reply, Run(testing::Optional(parsed_form_data[0])));
+    base::MockCallback<
+        base::OnceCallback<void(const std::optional<autofill::FormData>&)>>
+        mock_reply;
+    EXPECT_CALL(mock_reply, Run(testing::Optional(parsed_form_data[0])));
 
-  password_autofill_agent_->FillChangePasswordForm(
-      password_id, new_password_id, password_confirmation, u"qwerty",
-      u"Pa$sw0rD", mock_reply.Get());
+    password_autofill_agent_->FillChangePasswordForm(
+        password_id, new_password_id, password_confirmation, u"qwerty",
+        u"Pa$sw0rD", mock_reply.Get());
 
-  EXPECT_EQ(u"qwerty", password.Value().Utf16());
-  EXPECT_EQ(u"Pa$sw0rD", new_password.Value().Utf16());
-  EXPECT_EQ(u"Pa$sw0rD", confirmation_password.Value().Utf16());
+    EXPECT_EQ(u"qwerty", password.Value().Utf16());
+    EXPECT_EQ(u"Pa$sw0rD", new_password.Value().Utf16());
+    EXPECT_EQ(u"Pa$sw0rD", confirmation_password.Value().Utf16());
+  }
 }
 
 // Tests that `FillChangePasswordForm` invokes callback with std::nullopt when
diff --git a/chrome/renderer/chrome_render_frame_observer.cc b/chrome/renderer/chrome_render_frame_observer.cc
index 568dd8d54..b6f3621 100644
--- a/chrome/renderer/chrome_render_frame_observer.cc
+++ b/chrome/renderer/chrome_render_frame_observer.cc
@@ -71,6 +71,7 @@
 
 #if !BUILDFLAG(IS_ANDROID)
 #include "chrome/renderer/accessibility/read_anything/read_anything_app_controller.h"
+#include "chrome/renderer/actor/journal.h"
 #include "chrome/renderer/actor/tool_executor.h"
 #include "chrome/renderer/searchbox/searchbox_extension.h"
 #endif  // !BUILDFLAG(IS_ANDROID)
@@ -185,6 +186,9 @@
     : content::RenderFrameObserver(render_frame),
       translate_agent_(nullptr),
       page_text_agent_(new optimization_guide::PageTextAgent(render_frame)),
+#if !BUILDFLAG(IS_ANDROID)
+      actor_journal_(std::make_unique<actor::Journal>()),
+#endif
       web_cache_impl_(web_cache_impl) {
   render_frame->GetAssociatedInterfaceRegistry()
       ->AddInterface<chrome::mojom::ChromeRenderFrame>(base::BindRepeating(
@@ -618,9 +622,14 @@
 void ChromeRenderFrameObserver::InvokeTool(
     actor::mojom::ToolInvocationPtr request,
     InvokeToolCallback callback) {
-  actor::ToolExecutor executor(render_frame());
+  actor::ToolExecutor executor(render_frame(), *actor_journal_);
   executor.InvokeTool(std::move(request), std::move(callback));
 }
+
+void ChromeRenderFrameObserver::StartActorJournal(
+    mojo::PendingAssociatedRemote<actor::mojom::JournalClient> client) {
+  actor_journal_->Bind(std::move(client));
+}
 #endif
 
 void ChromeRenderFrameObserver::SetClientSidePhishingDetection() {
diff --git a/chrome/renderer/chrome_render_frame_observer.h b/chrome/renderer/chrome_render_frame_observer.h
index eb5b8d4..d785ab23 100644
--- a/chrome/renderer/chrome_render_frame_observer.h
+++ b/chrome/renderer/chrome_render_frame_observer.h
@@ -25,6 +25,10 @@
 
 class SkBitmap;
 
+namespace actor {
+class Journal;
+}
+
 namespace gfx {
 class Size;
 }
@@ -123,6 +127,9 @@
 #if !BUILDFLAG(IS_ANDROID)
   void InvokeTool(actor::mojom::ToolInvocationPtr request,
                   InvokeToolCallback callback) override;
+  void StartActorJournal(
+      mojo::PendingAssociatedRemote<actor::mojom::JournalClient> client)
+      override;
 #endif
 
   // Initialize a |phishing_classifier_delegate_|.
@@ -173,6 +180,10 @@
       phishing_image_embedder_ = nullptr;
 #endif
 
+#if !BUILDFLAG(IS_ANDROID)
+  std::unique_ptr<actor::Journal> actor_journal_;
+#endif
+
   // Owned by ChromeContentRendererClient and outlive us.
   raw_ptr<web_cache::WebCacheImpl> web_cache_impl_;
 
diff --git a/chrome/test/android/test_support/src/org/chromium/chrome/test_support/OWNERS b/chrome/test/android/test_support/src/org/chromium/chrome/test_support/OWNERS
index 223ce03..5ca17ed 100644
--- a/chrome/test/android/test_support/src/org/chromium/chrome/test_support/OWNERS
+++ b/chrome/test/android/test_support/src/org/chromium/chrome/test_support/OWNERS
@@ -1,2 +1 @@
 per-file PaymentRequestTestBridge.java=rouslan@chromium.org
-per-file PaymentRequestTestBridge.java=npnavarro@chromium.org
diff --git a/chrome/updater/BUILD.gn b/chrome/updater/BUILD.gn
index 4582e54..3f8642ea 100644
--- a/chrome/updater/BUILD.gn
+++ b/chrome/updater/BUILD.gn
@@ -184,10 +184,9 @@
       "update_service_internal_impl_inactive.h",
       "update_service_internal_impl_qualifying.cc",
       "update_service_internal_impl_qualifying.h",
-      "update_usage_stats_task.cc",
-      "update_usage_stats_task.h",
       "updater.cc",
       "updater.h",
+      "usage_stats_permissions.h",
     ]
 
     deps = [
@@ -263,7 +262,7 @@
         "policy/mac/managed_preference_policy_manager_impl.mm",
         "setup_mac.mm",
         "update_service_internal_impl_qualifying_mac.cc",
-        "update_usage_stats_task_mac.mm",
+        "usage_stats_permissions_mac.mm",
       ]
 
       deps += [
@@ -314,7 +313,7 @@
         "setup_win.cc",
         "update_block_check_win.cc",
         "update_service_internal_impl_qualifying_win.cc",
-        "update_usage_stats_task_win.cc",
+        "usage_stats_permissions_win.cc",
         "util/progress_sampler.cc",
         "util/progress_sampler.h",
         "win/action_handler.cc",
@@ -419,7 +418,7 @@
         "net/network_fetcher_linux.cc",
         "setup_linux.cc",
         "update_service_internal_impl_qualifying_linux.cc",
-        "update_usage_stats_task_linux.cc",
+        "usage_stats_permissions_linux.cc",
       ]
 
       libs = [
@@ -886,9 +885,9 @@
       "update_service_impl_impl_unittest.cc",
       "update_service_impl_inactive_unittest.cc",
       "update_service_unittest.cc",
-      "update_usage_stats_task_unittest.cc",
       "updater_scope_unittest.cc",
       "updater_unittest.cc",
+      "usage_stats_permissions_unittest.cc",
       "util/util_unittest.cc",
     ]
     if (!is_component_build) {
diff --git a/chrome/updater/app/app_install.cc b/chrome/updater/app/app_install.cc
index 54dfdf8..4a1cf41 100644
--- a/chrome/updater/app/app_install.cc
+++ b/chrome/updater/app/app_install.cc
@@ -34,8 +34,8 @@
 #include "chrome/updater/tag.h"
 #include "chrome/updater/update_service.h"
 #include "chrome/updater/update_service_internal.h"
-#include "chrome/updater/update_usage_stats_task.h"
 #include "chrome/updater/updater_version.h"
+#include "chrome/updater/usage_stats_permissions.h"
 #include "chrome/updater/util/util.h"
 #include "components/prefs/pref_service.h"
 #include "components/update_client/protocol_definition.h"
diff --git a/chrome/updater/app/app_install_win.cc b/chrome/updater/app/app_install_win.cc
index d10b5fb..63b3ed3 100644
--- a/chrome/updater/app/app_install_win.cc
+++ b/chrome/updater/app/app_install_win.cc
@@ -63,6 +63,7 @@
 #include "chrome/updater/util/util.h"
 #include "chrome/updater/util/win_util.h"
 #include "chrome/updater/win/installer/exit_code.h"
+#include "chrome/updater/win/installer_api.h"
 #include "chrome/updater/win/manifest_util.h"
 #include "chrome/updater/win/protocol_parser_xml.h"
 #include "chrome/updater/win/ui/l10n_util.h"
@@ -641,7 +642,10 @@
 
   RegistrationRequest request;
   request.app_id = app_id_;
-  request.version = base::Version(kNullVersion);
+  const base::Version installed_version =
+      LookupVersion(GetUpdaterScope(), app_id_, {}, {}, {});
+  request.version = installed_version.IsValid() ? installed_version
+                                                : base::Version(kNullVersion);
 
   std::optional<tagging::AppArgs> app_args = GetAppArgs(app_id_);
   if (app_args) {
diff --git a/chrome/updater/app/server/win/com_classes_legacy.cc b/chrome/updater/app/server/win/com_classes_legacy.cc
index 18336c5..fb9e3e7 100644
--- a/chrome/updater/app/server/win/com_classes_legacy.cc
+++ b/chrome/updater/app/server/win/com_classes_legacy.cc
@@ -18,6 +18,7 @@
 #include "base/check_op.h"
 #include "base/functional/bind.h"
 #include "base/functional/callback.h"
+#include "base/functional/callback_forward.h"
 #include "base/functional/callback_helpers.h"
 #include "base/logging.h"
 #include "base/memory/ref_counted.h"
@@ -48,9 +49,9 @@
 #include "chrome/updater/prefs.h"
 #include "chrome/updater/registration_data.h"
 #include "chrome/updater/update_service.h"
-#include "chrome/updater/update_usage_stats_task.h"
 #include "chrome/updater/updater_scope.h"
 #include "chrome/updater/updater_version.h"
+#include "chrome/updater/usage_stats_permissions.h"
 #include "chrome/updater/util/progress_sampler.h"
 #include "chrome/updater/util/util.h"
 #include "chrome/updater/util/win_util.h"
@@ -1278,18 +1279,17 @@
                                        const std::string& app_id,
                                        const std::string& command_id,
                                        ErrorParams error_params) {
-  AppServerWin::PostRpcTask(base::BindOnce(
+  base::OnceCallback<void(bool enable_usage_stats)> rpc_task = base::BindOnce(
       [](UpdaterScope scope, const std::string& app_id,
-         const std::string& command_id, ErrorParams error_params) {
+         const std::string& command_id, ErrorParams error_params,
+         bool enable_usage_stats) {
+        if (!enable_usage_stats) {
+          return;
+        }
         scoped_refptr<Configurator> config =
             GetAppServerWinInstance()->config();
         scoped_refptr<PersistedData> persisted_data =
             config->GetUpdaterPersistedData();
-        if (!persisted_data->GetUsageStatsEnabled() &&
-            !AnyAppEnablesUsageStats(scope)) {
-          return;
-        }
-
         update_client::CrxComponent app_command_data;
         app_command_data.ap = persisted_data->GetAP(app_id);
         app_command_data.app_id = app_id;
@@ -1314,7 +1314,18 @@
               VLOG(1) << "App command ping completed: " << error;
             }));
       },
-      scope, app_id, command_id, error_params));
+      scope, app_id, command_id, error_params);
+  base::ThreadPool::PostTaskAndReplyWithResult(
+      FROM_HERE, {base::MayBlock()},
+      base::BindOnce(
+          [](UpdaterScope scope) { return AnyAppEnablesUsageStats(scope); },
+          scope),
+      base::BindOnce(
+          [](base::OnceCallback<void(bool)> rpc_task, bool enable_usage_stats) {
+            AppServerWin::PostRpcTask(
+                base::BindOnce(std::move(rpc_task), enable_usage_stats));
+          },
+          std::move(rpc_task)));
 }
 
 PolicyStatusImpl::PolicyStatusImpl()
diff --git a/chrome/updater/crash_client.cc b/chrome/updater/crash_client.cc
index 37d4f553..4f08562 100644
--- a/chrome/updater/crash_client.cc
+++ b/chrome/updater/crash_client.cc
@@ -18,9 +18,9 @@
 #include "build/build_config.h"
 #include "chrome/updater/branded_constants.h"
 #include "chrome/updater/tag.h"
-#include "chrome/updater/update_usage_stats_task.h"
 #include "chrome/updater/updater_branding.h"
 #include "chrome/updater/updater_scope.h"
+#include "chrome/updater/usage_stats_permissions.h"
 #include "chrome/updater/util/util.h"
 #include "third_party/crashpad/crashpad/client/crash_report_database.h"
 #include "third_party/crashpad/crashpad/client/crashpad_client.h"
diff --git a/chrome/updater/installer.cc b/chrome/updater/installer.cc
index 6cf32d63..71e9485b 100644
--- a/chrome/updater/installer.cc
+++ b/chrome/updater/installer.cc
@@ -26,8 +26,8 @@
 #include "chrome/updater/app/app_utils.h"
 #include "chrome/updater/constants.h"
 #include "chrome/updater/update_service.h"
-#include "chrome/updater/update_usage_stats_task.h"
 #include "chrome/updater/updater_scope.h"
+#include "chrome/updater/usage_stats_permissions.h"
 #include "chrome/updater/util/util.h"
 #include "components/crx_file/crx_verifier.h"
 #include "components/update_client/update_client_errors.h"
@@ -107,8 +107,6 @@
       policy_same_version_update_(policy_same_version_update),
       persisted_data_(persisted_data),
       crx_verifier_format_(crx_verifier_format),
-      usage_stats_enabled_(IsUpdaterOrCompanionApp(app_id) &&
-                           persisted_data->GetUsageStatsEnabled()),
       app_info_(AppInfo(GetUpdaterScope(), app_id, {}, {}, {}, {}, {})) {}
 
 Installer::~Installer() = default;
@@ -208,7 +206,9 @@
                                    client_install_data_.empty()
                                        ? install_params->server_install_data
                                        : client_install_data_),
-      usage_stats_enabled_, kWaitForAppInstaller, std::move(progress_callback));
+      /*usage_stats_enabled=*/IsUpdaterOrCompanionApp(app_id_) &&
+          AnyAppEnablesUsageStats(GetUpdaterScope()),
+      kWaitForAppInstaller, std::move(progress_callback));
 }
 
 void Installer::InstallWithSyncPrimitives(
diff --git a/chrome/updater/installer.h b/chrome/updater/installer.h
index bcd4873..c404026 100644
--- a/chrome/updater/installer.h
+++ b/chrome/updater/installer.h
@@ -73,8 +73,9 @@
                          const std::string& keyname,
                          const std::string& default_value);
 
-// Retrieves the version of the installed application. If the version cannot
-// be determined the `default_value` is returned.
+// Retrieves the installed version of the provided `app_id`. If `app_id` is not
+// installed, the `default_value` is returned. `version_path` and `version_key`
+// are not used on Windows.
 base::Version LookupVersion(UpdaterScope scope,
                             const std::string& app_id,
                             const base::FilePath& version_path,
@@ -172,7 +173,6 @@
   const UpdateService::PolicySameVersionUpdate policy_same_version_update_;
   scoped_refptr<PersistedData> persisted_data_;
   const crx_file::VerifierFormat crx_verifier_format_;
-  const bool usage_stats_enabled_;
 
   // AppInfo is set only after MakeCrxComponent is called, and is not updated
   // when the installer succeeds.
diff --git a/chrome/updater/installer_mac.cc b/chrome/updater/installer_mac.cc
index 17921792..95000ed 100644
--- a/chrome/updater/installer_mac.cc
+++ b/chrome/updater/installer_mac.cc
@@ -59,7 +59,7 @@
   std::optional<std::string> value =
       ReadValueFromPlist(version_path, version_key);
   if (value) {
-    base::Version value_version(*value);
+    const base::Version value_version(*value);
     return value_version.IsValid() ? value_version : default_value;
   }
   return default_value;
diff --git a/chrome/updater/persisted_data.cc b/chrome/updater/persisted_data.cc
index eb577a6..d1d036d3 100644
--- a/chrome/updater/persisted_data.cc
+++ b/chrome/updater/persisted_data.cc
@@ -664,18 +664,6 @@
   }
 }
 
-bool PersistedData::GetUsageStatsEnabled() const {
-  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  return pref_service_ && pref_service_->GetBoolean(kUsageStatsEnabledKey);
-}
-
-void PersistedData::SetUsageStatsEnabled(bool usage_stats_enabled) {
-  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  if (pref_service_) {
-    pref_service_->SetBoolean(kUsageStatsEnabledKey, usage_stats_enabled);
-  }
-}
-
 std::optional<PersistedData::Cookie> PersistedData::GetRemoteLoggingCookie()
     const {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
@@ -841,13 +829,20 @@
 // kPersistedDataPreference is registered by update_client::RegisterPrefs.
 void RegisterPersistedDataPrefs(scoped_refptr<PrefRegistrySimple> registry) {
   registry->RegisterBooleanPref(kHadApps, false);
-  registry->RegisterBooleanPref(kUsageStatsEnabledKey, false);
   registry->RegisterBooleanPref(kEulaRequired, false);
   registry->RegisterTimePref(kNextAllowedLoggingAttemptTime, {});
   registry->RegisterTimePref(kLastChecked, {});
   registry->RegisterTimePref(kLastStarted, {});
   registry->RegisterStringPref(kLastOSVersion, {});
   registry->RegisterDictionaryPref(kRemoteLoggingCookie, {});
+
+  // TODO(crbug.com/422187975): Remove obsolete pref no earlier than 6/3/2026.
+  registry->RegisterBooleanPref(kUsageStatsEnabledKey, false);
+}
+
+void MigrateObsoletePersistedDataPrefs(PrefService* pref_service) {
+  // TODO(crbug.com/422187975): Remove obsolete pref no earlier than 6/3/2026.
+  pref_service->ClearPref(kUsageStatsEnabledKey);
 }
 
 }  // namespace updater
diff --git a/chrome/updater/persisted_data.h b/chrome/updater/persisted_data.h
index 676c47d..373fb92 100644
--- a/chrome/updater/persisted_data.h
+++ b/chrome/updater/persisted_data.h
@@ -107,12 +107,6 @@
   bool GetHadApps() const;
   void SetHadApps();
 
-  // UsageStatsEnabled reflects whether the updater as a whole is allowed to
-  // send usage stats, and is set or reset periodically based on the usage
-  // stats opt-in state of each product.
-  bool GetUsageStatsEnabled() const;
-  void SetUsageStatsEnabled(bool usage_stats_enabled);
-
   struct Cookie {
     std::string value;
     base::Time expiration;
@@ -226,6 +220,7 @@
 };
 
 void RegisterPersistedDataPrefs(scoped_refptr<PrefRegistrySimple> registry);
+void MigrateObsoletePersistedDataPrefs(PrefService* pref_service);
 
 }  // namespace updater
 
diff --git a/chrome/updater/policy/policy_fetcher.cc b/chrome/updater/policy/policy_fetcher.cc
index e7494bae..b9b27db 100644
--- a/chrome/updater/policy/policy_fetcher.cc
+++ b/chrome/updater/policy/policy_fetcher.cc
@@ -14,9 +14,10 @@
 #include "base/logging.h"
 #include "base/memory/scoped_refptr.h"
 #include "base/sequence_checker.h"
-#include "base/task/sequenced_task_runner.h"
+#include "base/task/task_traits.h"
 #include "base/task/thread_pool.h"
 #include "base/time/default_clock.h"
+#include "base/time/time.h"
 #include "chrome/enterprise_companion/constants.h"
 #include "chrome/enterprise_companion/device_management_storage/dm_storage.h"
 #include "chrome/enterprise_companion/enterprise_companion_client.h"
@@ -26,6 +27,8 @@
 #include "chrome/updater/persisted_data.h"
 #include "chrome/updater/policy/dm_policy_manager.h"
 #include "chrome/updater/policy/manager.h"
+#include "chrome/updater/updater_scope.h"
+#include "chrome/updater/usage_stats_permissions.h"
 #include "components/policy/core/common/policy_types.h"
 #include "components/update_client/timed_callback.h"
 #include "mojo/public/cpp/bindings/callback_helpers.h"
@@ -89,12 +92,25 @@
                                        scoped_refptr<PolicyManagerInterface>()),
       kErrorIpcDisconnect, nullptr);
 
-  enterprise_companion::ConnectAndLaunchServer(
-      base::DefaultClock::GetInstance(), connection_timeout_,
-      persisted_data_->GetUsageStatsEnabled(),
-      persisted_data_->GetCohort(enterprise_companion::kCompanionAppId),
-      base::BindOnce(&OutOfProcessPolicyFetcher::OnConnected,
-                     base::WrapRefCounted(this), reason));
+  base::ThreadPool::PostTaskAndReplyWithResult(
+      FROM_HERE, {base::MayBlock()},
+      base::BindOnce([] { return AnyAppEnablesUsageStats(GetUpdaterScope()); }),
+      base::BindOnce(
+          [](base::TimeDelta connection_timeout, const std::string& cohort_id,
+             base::OnceCallback<void(
+                 std::unique_ptr<mojo::IsolatedConnection>,
+                 mojo::Remote<
+                     enterprise_companion::mojom::EnterpriseCompanion>)>
+                 callback,
+             bool enable_usage_stats) {
+            enterprise_companion::ConnectAndLaunchServer(
+                base::DefaultClock::GetInstance(), connection_timeout,
+                enable_usage_stats, cohort_id, std::move(callback));
+          },
+          connection_timeout_,
+          persisted_data_->GetCohort(enterprise_companion::kCompanionAppId),
+          base::BindOnce(&OutOfProcessPolicyFetcher::OnConnected,
+                         base::WrapRefCounted(this), reason)));
 }
 
 void OutOfProcessPolicyFetcher::OnConnected(
diff --git a/chrome/updater/prefs.cc b/chrome/updater/prefs.cc
index e0b82e3..8dd4795 100644
--- a/chrome/updater/prefs.cc
+++ b/chrome/updater/prefs.cc
@@ -32,6 +32,7 @@
 #include "components/prefs/pref_service.h"
 #include "components/prefs/pref_service_factory.h"
 #include "components/update_client/update_client.h"
+#include "persisted_data.h"
 
 namespace updater {
 
@@ -121,6 +122,8 @@
     return nullptr;
   }
 
+  MigrateObsoletePersistedDataPrefs(pref_service.get());
+
   return base::MakeRefCounted<UpdaterPrefsImpl>(
       *global_prefs_dir, std::move(lock), std::move(pref_service));
 }
diff --git a/chrome/updater/test/integration_tests.cc b/chrome/updater/test/integration_tests.cc
index 19ac438..69d8332 100644
--- a/chrome/updater/test/integration_tests.cc
+++ b/chrome/updater/test/integration_tests.cc
@@ -5336,6 +5336,35 @@
   ASSERT_NO_FATAL_FAILURE(Uninstall());
 }
 
+TEST_F(IntegrationTestMsi, InstallViaCommandLineTwice) {
+  const base::FilePath crx_path = GetInstallerPath(kMsiCrx);
+
+  for (int i = 0; i < 2; ++i) {
+    if (!i) {
+      // The updater only sends a ping for the first install.
+      ExpectInstallEvent(*test_server_, kUpdaterAppId);
+    }
+    ExpectAppsUpdateSequence(
+        UpdaterScope::kSystem, test_server_.get(),
+        /*request_attributes=*/{},
+        {
+            AppUpdateExpectation(
+                {}, kMsiAppId,
+                i ? kMsiUpdatedVersion : base::Version(kNullVersion),
+                kMsiUpdatedVersion,
+                /*is_install=*/true,
+                /*should_update=*/true, false, "", "", crx_path),
+        });
+    ASSERT_NO_FATAL_FAILURE(InstallUpdaterAndApp(
+        kMsiAppId, /*is_silent_install=*/true, "usagestats=1"));
+  }
+
+  ExpectAppInstalled(kMsiAppId, kMsiUpdatedVersion);
+
+  ASSERT_NO_FATAL_FAILURE(ExpectUninstallPing(test_server_.get()));
+  ASSERT_NO_FATAL_FAILURE(Uninstall());
+}
+
 TEST_F(IntegrationTestMsi, Upgrade) {
   ExpectInstallEvent(*test_server_, kUpdaterAppId);
   ASSERT_NO_FATAL_FAILURE(Install());
@@ -5433,11 +5462,6 @@
       ExpectInstallEvent(*test_server_, kUpdaterAppId);
     }
     ExpectInstallEvent(*test_server_, kMsiAppId);
-    if (i) {
-      // TODO(crbug.com/413081282): offline overinstalls result in an extra app
-      // ping, which needs to be fixed.
-      ExpectInstallEvent(*test_server_, kMsiAppId);
-    }
     ASSERT_NO_FATAL_FAILURE(InstallUpdaterAndApp(
         kMsiAppId, /*is_silent_install=*/true,
         /*tag=*/
diff --git a/chrome/updater/test/integration_tests_impl.cc b/chrome/updater/test/integration_tests_impl.cc
index f34ce44..3a6ed1db 100644
--- a/chrome/updater/test/integration_tests_impl.cc
+++ b/chrome/updater/test/integration_tests_impl.cc
@@ -828,6 +828,7 @@
   std::set<base::FilePath> downloaded_crxes;
   for (const AppUpdateExpectation& app : apps) {
     if ((app.should_update || app.always_serve_crx) &&
+        (app.from_version != app.to_version) &&
         !base::Contains(downloaded_crxes, app.crx_relative_path)) {
       // Download requests for apps that install/update
       const base::FilePath crx_path = exe_path.Append(app.crx_relative_path);
diff --git a/chrome/updater/update_service_impl_impl.cc b/chrome/updater/update_service_impl_impl.cc
index e9542fbd..91cf51b 100644
--- a/chrome/updater/update_service_impl_impl.cc
+++ b/chrome/updater/update_service_impl_impl.cc
@@ -55,9 +55,9 @@
 #include "chrome/updater/remove_uninstalled_apps_task.h"
 #include "chrome/updater/update_block_check.h"
 #include "chrome/updater/update_service.h"
-#include "chrome/updater/update_usage_stats_task.h"
 #include "chrome/updater/updater_scope.h"
 #include "chrome/updater/updater_version.h"
+#include "chrome/updater/usage_stats_permissions.h"
 #include "chrome/updater/util/util.h"
 #include "components/policy/core/common/policy_types.h"
 #include "components/prefs/pref_service.h"
@@ -849,10 +849,6 @@
       base::BindOnce(&RemoveUninstalledAppsTask::Run,
                      base::MakeRefCounted<RemoveUninstalledAppsTask>(
                          config_, GetUpdaterScope())));
-  new_tasks.push_back(base::BindOnce(
-      &UpdateUsageStatsTask::Run,
-      base::MakeRefCounted<UpdateUsageStatsTask>(
-          GetUpdaterScope(), config_->GetUpdaterPersistedData())));
   new_tasks.push_back(MakeChangeOwnersTask(config_->GetUpdaterPersistedData(),
                                            GetUpdaterScope()));
 
@@ -1270,14 +1266,6 @@
       config_->GetUpdaterPersistedData()->GetBrandCode(app_id), pv,
       config_->GetUpdaterPersistedData()->GetExistenceCheckerPath(app_id));
 
-  // Pre-register the app in case there is no registration for it. This app
-  // registration is removed later if `new_install` is `true and if the app
-  // install encounters an error.
-  RegistrationRequest request;
-  request.app_id = app_id;
-  request.lang = language;
-  config_->GetUpdaterPersistedData()->RegisterApp(request);
-
   const base::Version installer_version([&install_settings]() -> std::string {
     std::unique_ptr<base::Value> install_settings_deserialized =
         JSONStringValueDeserializer(install_settings)
@@ -1312,8 +1300,7 @@
       base::BindOnce(
           [](const AppInfo& app_info, const base::FilePath& installer_path,
              const std::string& install_args, const std::string& install_data,
-             base::RepeatingCallback<void(const UpdateState&)> state_update,
-             bool usage_stats_enabled) {
+             base::RepeatingCallback<void(const UpdateState&)> state_update) {
             base::ScopedTempDir temp_dir;
             if (!temp_dir.CreateUniqueTempDir()) {
               return InstallerResult(
@@ -1330,7 +1317,10 @@
             return RunApplicationInstaller(
                 app_info, installer_path, install_args,
                 WriteInstallerDataToTempFile(temp_dir.GetPath(), install_data),
-                usage_stats_enabled, kWaitForAppInstaller,
+                /*usage_stats_enabled=*/
+                IsUpdaterOrCompanionApp(app_info.app_id) &&
+                    AnyAppEnablesUsageStats(GetUpdaterScope()),
+                kWaitForAppInstaller,
                 base::BindRepeating(
                     [](base::RepeatingCallback<void(const UpdateState&)>
                            state_update,
@@ -1344,9 +1334,7 @@
                     },
                     state_update, app_info.app_id));
           },
-          app_info, installer_path, install_args, install_data, state_update,
-          IsUpdaterOrCompanionApp(app_info.app_id) &&
-              config_->GetUpdaterPersistedData()->GetUsageStatsEnabled()),
+          app_info, installer_path, install_args, install_data, state_update),
       base::BindOnce(
           [](scoped_refptr<Configurator> config,
              scoped_refptr<PersistedData> persisted_data,
diff --git a/chrome/updater/update_usage_stats_task.cc b/chrome/updater/update_usage_stats_task.cc
deleted file mode 100644
index f6b6a2e..0000000
--- a/chrome/updater/update_usage_stats_task.cc
+++ /dev/null
@@ -1,47 +0,0 @@
-// Copyright 2022 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "chrome/updater/update_usage_stats_task.h"
-
-#include "base/functional/callback.h"
-#include "base/memory/scoped_refptr.h"
-#include "base/sequence_checker.h"
-#include "base/task/thread_pool.h"
-#include "chrome/updater/crash_client.h"
-#include "chrome/updater/persisted_data.h"
-#include "chrome/updater/updater_scope.h"
-#include "third_party/crashpad/crashpad/client/crash_report_database.h"
-#include "third_party/crashpad/crashpad/client/settings.h"
-
-namespace updater {
-
-UpdateUsageStatsTask::UpdateUsageStatsTask(
-    UpdaterScope scope,
-    scoped_refptr<PersistedData> persisted_data)
-    : scope_(scope), persisted_data_(persisted_data) {}
-
-UpdateUsageStatsTask::~UpdateUsageStatsTask() = default;
-
-void UpdateUsageStatsTask::SetUsageStatsEnabled(
-    scoped_refptr<PersistedData> persisted_data,
-    bool enabled) {
-  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  persisted_data->SetUsageStatsEnabled(enabled);
-  CrashClient::GetInstance()->SetUploadsEnabled(enabled);
-}
-
-void UpdateUsageStatsTask::Run(base::OnceClosure callback) {
-  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-
-  base::ThreadPool::PostTaskAndReplyWithResult(
-      FROM_HERE, {base::MayBlock()},
-      base::BindOnce(
-          [](UpdaterScope scope) { return AnyAppEnablesUsageStats(scope); },
-          scope_),
-      base::BindOnce(&UpdateUsageStatsTask::SetUsageStatsEnabled, this,
-                     persisted_data_)
-          .Then(std::move(callback)));
-}
-
-}  // namespace updater
diff --git a/chrome/updater/updater.cc b/chrome/updater/updater.cc
index f7dca12..47cb02a 100644
--- a/chrome/updater/updater.cc
+++ b/chrome/updater/updater.cc
@@ -37,9 +37,9 @@
 #include "chrome/updater/crash_client.h"
 #include "chrome/updater/crash_reporter.h"
 #include "chrome/updater/ipc/ipc_support.h"
-#include "chrome/updater/update_usage_stats_task.h"
 #include "chrome/updater/updater_scope.h"
 #include "chrome/updater/updater_version.h"
+#include "chrome/updater/usage_stats_permissions.h"
 #include "chrome/updater/util/util.h"
 #include "components/crash/core/common/crash_key.h"
 #include "components/crash/core/common/crash_keys.h"
diff --git a/chrome/updater/update_usage_stats_task.h b/chrome/updater/usage_stats_permissions.h
similarity index 69%
rename from chrome/updater/update_usage_stats_task.h
rename to chrome/updater/usage_stats_permissions.h
index 0ffc409..cc218cc8 100644
--- a/chrome/updater/update_usage_stats_task.h
+++ b/chrome/updater/usage_stats_permissions.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 CHROME_UPDATER_UPDATE_USAGE_STATS_TASK_H_
-#define CHROME_UPDATER_UPDATE_USAGE_STATS_TASK_H_
+#ifndef CHROME_UPDATER_USAGE_STATS_PERMISSIONS_H_
+#define CHROME_UPDATER_USAGE_STATS_PERMISSIONS_H_
 
 #include <memory>
 #include <optional>
@@ -27,8 +27,6 @@
 
 namespace updater {
 
-class PersistedData;
-
 // Returns true if any app besides Omaha 4 or CECA is allowed to send usage
 // stats. The function looks at apps installed on the system to check if they
 // have usage stats enabled. This information is stored in the registry on
@@ -65,27 +63,6 @@
     std::optional<std::string> event_logging_permission_provider);
 #endif
 
-class UpdateUsageStatsTask
-    : public base::RefCountedThreadSafe<UpdateUsageStatsTask> {
- public:
-  UpdateUsageStatsTask(UpdaterScope scope,
-                       scoped_refptr<PersistedData> persisted_data);
-  void Run(base::OnceClosure callback);
-
- private:
-  friend class base::RefCountedThreadSafe<UpdateUsageStatsTask>;
-  FRIEND_TEST_ALL_PREFIXES(UpdateUsageStatsTaskTest, NoApps);
-  FRIEND_TEST_ALL_PREFIXES(UpdateUsageStatsTaskTest, OneAppEnabled);
-  FRIEND_TEST_ALL_PREFIXES(UpdateUsageStatsTaskTest, ZeroAppsEnabled);
-  virtual ~UpdateUsageStatsTask();
-  void SetUsageStatsEnabled(scoped_refptr<PersistedData> persisted_data,
-                            bool enabled);
-
-  SEQUENCE_CHECKER(sequence_checker_);
-  const UpdaterScope scope_;
-  scoped_refptr<PersistedData> persisted_data_;
-};
-
 }  // namespace updater
 
-#endif  // CHROME_UPDATER_UPDATE_USAGE_STATS_TASK_H_
+#endif  // CHROME_UPDATER_USAGE_STATS_PERMISSIONS_H_
diff --git a/chrome/updater/update_usage_stats_task_linux.cc b/chrome/updater/usage_stats_permissions_linux.cc
similarity index 92%
rename from chrome/updater/update_usage_stats_task_linux.cc
rename to chrome/updater/usage_stats_permissions_linux.cc
index 1391bdad..f64a276 100644
--- a/chrome/updater/update_usage_stats_task_linux.cc
+++ b/chrome/updater/usage_stats_permissions_linux.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "chrome/updater/update_usage_stats_task.h"
+#include "chrome/updater/usage_stats_permissions.h"
 
 #include <memory>
 #include <optional>
diff --git a/chrome/updater/update_usage_stats_task_mac.mm b/chrome/updater/usage_stats_permissions_mac.mm
similarity index 98%
rename from chrome/updater/update_usage_stats_task_mac.mm
rename to chrome/updater/usage_stats_permissions_mac.mm
index a0c96626..9c9cbc1 100644
--- a/chrome/updater/update_usage_stats_task_mac.mm
+++ b/chrome/updater/usage_stats_permissions_mac.mm
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "chrome/updater/update_usage_stats_task.h"
+#include "chrome/updater/usage_stats_permissions.h"
 
 #include <algorithm>
 #include <memory>
diff --git a/chrome/updater/update_usage_stats_task_unittest.cc b/chrome/updater/usage_stats_permissions_unittest.cc
similarity index 92%
rename from chrome/updater/update_usage_stats_task_unittest.cc
rename to chrome/updater/usage_stats_permissions_unittest.cc
index 54a6e546..27484a2a 100644
--- a/chrome/updater/update_usage_stats_task_unittest.cc
+++ b/chrome/updater/usage_stats_permissions_unittest.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "chrome/updater/update_usage_stats_task.h"
+#include "chrome/updater/usage_stats_permissions.h"
 
 #include <memory>
 #include <optional>
@@ -45,7 +45,7 @@
 
 namespace updater {
 
-class UpdateUsageStatsTaskTest : public testing::Test {
+class UsageStatsPermissionsTest : public testing::Test {
  protected:
   std::string fake_permission_provider_ = "UsageStatsTestPermissionProvider";
 #if BUILDFLAG(IS_MAC)
@@ -171,29 +171,29 @@
 };
 
 #if BUILDFLAG(IS_LINUX)
-TEST_F(UpdateUsageStatsTaskTest, LinuxAlwaysFalse) {
+TEST_F(UsageStatsPermissionsTest, LinuxAlwaysFalse) {
   ASSERT_FALSE(AnyAppEnablesUsageStats(scope_));
   ASSERT_FALSE(RemoteEventLoggingAllowed(scope_, fake_permission_provider_));
 }
 #else
 
-TEST_F(UpdateUsageStatsTaskTest, NoApps) {
+TEST_F(UsageStatsPermissionsTest, NoApps) {
   ASSERT_FALSE(AnyAppEnablesUsageStats());
 }
 
-TEST_F(UpdateUsageStatsTaskTest, OneAppDisabled) {
+TEST_F(UsageStatsPermissionsTest, OneAppDisabled) {
   SetAppUsageStats("app1", false, scope_);
   SetAppUsageStats("app2", false, scope_);
   ASSERT_FALSE(AnyAppEnablesUsageStats());
 }
 
-TEST_F(UpdateUsageStatsTaskTest, OneAppEnabled) {
+TEST_F(UsageStatsPermissionsTest, OneAppEnabled) {
   SetAppUsageStats("app1", true, scope_);
   SetAppUsageStats("app2", false, scope_);
   ASSERT_TRUE(AnyAppEnablesUsageStats());
 }
 
-TEST_F(UpdateUsageStatsTaskTest, UserInstallIgnoresSystem) {
+TEST_F(UsageStatsPermissionsTest, UserInstallIgnoresSystem) {
   if (IsSystemInstall(scope_)) {
     GTEST_SKIP() << "Not applicable to system-scoped installs";
   }
@@ -202,7 +202,7 @@
   ASSERT_FALSE(AnyAppEnablesUsageStats());
 }
 
-TEST_F(UpdateUsageStatsTaskTest, SystemInstallLooksAtUser) {
+TEST_F(UsageStatsPermissionsTest, SystemInstallLooksAtUser) {
   if (!IsSystemInstall(scope_)) {
     GTEST_SKIP() << "Not applicable to user-scoped installs";
   }
@@ -211,12 +211,12 @@
   ASSERT_TRUE(AnyAppEnablesUsageStats());
 }
 
-TEST_F(UpdateUsageStatsTaskTest, PermissionProviderAllowsRemoteLogging) {
+TEST_F(UsageStatsPermissionsTest, PermissionProviderAllowsRemoteLogging) {
   SetAppUsageStats(fake_permission_provider_, true, scope_);
   ASSERT_TRUE(RemoteEventLoggingAllowed());
 }
 
-TEST_F(UpdateUsageStatsTaskTest,
+TEST_F(UsageStatsPermissionsTest,
        PermissionProviderAllowsRemoteLoggingWithCECAAndUpdater) {
   SetUpdaterUsageStats(true, scope_);
   SetCECAUsageStats(true, scope_);
@@ -224,14 +224,14 @@
   ASSERT_TRUE(RemoteEventLoggingAllowed());
 }
 
-TEST_F(UpdateUsageStatsTaskTest, UsageStatsProviderChecksPermissionProvider) {
+TEST_F(UsageStatsPermissionsTest, UsageStatsProviderChecksPermissionProvider) {
   SetUpdaterUsageStats(true, scope_);
   SetCECAUsageStats(true, scope_);
   SetAppUsageStats(fake_permission_provider_, false, scope_);
   ASSERT_FALSE(RemoteEventLoggingAllowed());
 }
 
-TEST_F(UpdateUsageStatsTaskTest,
+TEST_F(UsageStatsPermissionsTest,
        PermissionProviderDisallowsRemoteLoggingWithOtherAppDisabled) {
   SetUpdaterUsageStats(true, scope_);
   SetCECAUsageStats(true, scope_);
@@ -240,7 +240,7 @@
   ASSERT_FALSE(RemoteEventLoggingAllowed());
 }
 
-TEST_F(UpdateUsageStatsTaskTest,
+TEST_F(UsageStatsPermissionsTest,
        PermissionProviderDisallowsRemoteLoggingWithOtherAppEnabled) {
   SetUpdaterUsageStats(true, scope_);
   SetCECAUsageStats(true, scope_);
@@ -249,7 +249,7 @@
   ASSERT_FALSE(RemoteEventLoggingAllowed());
 }
 
-TEST_F(UpdateUsageStatsTaskTest,
+TEST_F(UsageStatsPermissionsTest,
        SystemPermissionProviderAllowsRemoteLoggingWithUserAppEnabled) {
   if (!IsSystemInstall(scope_)) {
     GTEST_SKIP() << "Not applicable to user-scoped installs";
diff --git a/chrome/updater/update_usage_stats_task_win.cc b/chrome/updater/usage_stats_permissions_win.cc
similarity index 98%
rename from chrome/updater/update_usage_stats_task_win.cc
rename to chrome/updater/usage_stats_permissions_win.cc
index dd1eea6..469f8327 100644
--- a/chrome/updater/update_usage_stats_task_win.cc
+++ b/chrome/updater/usage_stats_permissions_win.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "chrome/updater/update_usage_stats_task.h"
+#include "chrome/updater/usage_stats_permissions.h"
 
 #include <algorithm>
 #include <cwchar>
diff --git a/chrome/updater/win/installer/installer.cc b/chrome/updater/win/installer/installer.cc
index f3f2e050..f5b9305 100644
--- a/chrome/updater/win/installer/installer.cc
+++ b/chrome/updater/win/installer/installer.cc
@@ -48,9 +48,9 @@
 #include "chrome/updater/constants.h"
 #include "chrome/updater/ping_configurator.h"
 #include "chrome/updater/tag.h"
-#include "chrome/updater/update_usage_stats_task.h"
 #include "chrome/updater/updater_branding.h"
 #include "chrome/updater/updater_scope.h"
+#include "chrome/updater/usage_stats_permissions.h"
 #include "chrome/updater/util/util.h"
 #include "chrome/updater/util/win_util.h"
 #include "chrome/updater/win/installer/configuration.h"
diff --git a/chrome/updater/win/installer_api.cc b/chrome/updater/win/installer_api.cc
index 544b213..29cea781 100644
--- a/chrome/updater/win/installer_api.cc
+++ b/chrome/updater/win/installer_api.cc
@@ -581,14 +581,14 @@
 
 base::Version LookupVersion(UpdaterScope scope,
                             const std::string& app_id,
-                            const base::FilePath& version_path,
-                            const std::string& version_key,
+                            const base::FilePath& /*version_path*/,
+                            const std::string& /*version_key*/,
                             const base::Version& default_value) {
   std::wstring pv;
   if (base::win::RegKey(UpdaterScopeToHKeyRoot(scope),
                         GetAppClientsKey(app_id).c_str(), Wow6432(KEY_READ))
           .ReadValue(kRegValuePV, &pv) == ERROR_SUCCESS) {
-    base::Version value_version = base::Version(base::WideToUTF8(pv));
+    const base::Version value_version = base::Version(base::WideToUTF8(pv));
     return value_version.IsValid() ? value_version : default_value;
   }
   return default_value;
diff --git a/chromeos/CHROMEOS_LKGM b/chromeos/CHROMEOS_LKGM
index 741bf82f..1480393 100644
--- a/chromeos/CHROMEOS_LKGM
+++ b/chromeos/CHROMEOS_LKGM
@@ -1 +1 @@
-16309.0.0-1069363
\ No newline at end of file
+16309.0.0-1069370
\ No newline at end of file
diff --git a/chromeos/ash/components/kcer/kcer_nss/kcer_nss_fuzzer.cc b/chromeos/ash/components/kcer/kcer_nss/kcer_nss_fuzzer.cc
index adb16c3..35f925bb 100644
--- a/chromeos/ash/components/kcer/kcer_nss/kcer_nss_fuzzer.cc
+++ b/chromeos/ash/components/kcer/kcer_nss/kcer_nss_fuzzer.cc
@@ -505,18 +505,21 @@
   }
   if (GetBool()) {
     std::vector<std::string> policy_oids;
+    CBB policy_identifier;
+    CBB_init(&policy_identifier, /*initial_capacity=*/10);
     while (GetBool()) {
       std::vector<std::string> oid_parts;
       while (GetBool()) {
         oid_parts.push_back(base::NumberToString(GetUint64()));
       }
-      if (!oid_parts.empty()) {
-        policy_oids.push_back(base::JoinString(oid_parts, "."));
+      std::string oid = base::JoinString(oid_parts, ".");
+      // Check that the OID will be accepted as valid before adding it.
+      if (CBB_add_asn1_oid_from_text(&policy_identifier, oid.data(),
+                                     oid.size())) {
+        policy_oids.push_back(oid);
       }
     }
-    if (!policy_oids.empty()) {
-      cert_builder_->SetCertificatePolicies(policy_oids);
-    }
+    cert_builder_->SetCertificatePolicies(policy_oids);
   }
   if (GetBool()) {
     std::vector<std::pair<std::string, std::string>> policy_mappings;
diff --git a/chromeos/ash/components/mantis/media_app/mantis_untrusted_service_manager_unittest.cc b/chromeos/ash/components/mantis/media_app/mantis_untrusted_service_manager_unittest.cc
index f07d277..2c737155 100644
--- a/chromeos/ash/components/mantis/media_app/mantis_untrusted_service_manager_unittest.cc
+++ b/chromeos/ash/components/mantis/media_app/mantis_untrusted_service_manager_unittest.cc
@@ -23,6 +23,7 @@
 #include "chromeos/ash/components/specialized_features/feature_access_checker.h"
 #include "chromeos/services/machine_learning/public/cpp/fake_service_connection.h"
 #include "chromeos/services/machine_learning/public/mojom/text_classifier.mojom.h"
+#include "components/prefs/pref_registry_simple.h"
 #include "components/prefs/testing_pref_service.h"
 #include "mojo/public/cpp/bindings/pending_receiver.h"
 #include "mojo/public/cpp/bindings/pending_remote.h"
diff --git a/clank b/clank
index 725f656..d7c11e7 160000
--- a/clank
+++ b/clank
@@ -1 +1 @@
-Subproject commit 725f65605ed6b3b5379a158a33842f299714dbf2
+Subproject commit d7c11e788469c544db99807d9d272a651b39437f
diff --git a/components/android_autofill/DEPS b/components/android_autofill/DEPS
index 87923b9..b1d5f28e 100644
--- a/components/android_autofill/DEPS
+++ b/components/android_autofill/DEPS
@@ -2,6 +2,7 @@
   "+components/autofill",
   "+components/password_manager/core/browser/password_form.h",
   "+components/password_manager/core/browser/form_parsing",
+  "+components/credential_management",
   "+components/prefs",
   "+components/user_prefs",
   "+components/webauthn/android",
diff --git a/components/android_autofill/browser/BUILD.gn b/components/android_autofill/browser/BUILD.gn
index 5ec70162..06cb2e9 100644
--- a/components/android_autofill/browser/BUILD.gn
+++ b/components/android_autofill/browser/BUILD.gn
@@ -116,6 +116,9 @@
     ":jni_headers",
     "//components/autofill/android:keyboard_suppressor",
     "//components/autofill/content/browser",
+    "//components/credential_management:browser",
+    "//components/credential_management/android:browser_sources",
+    "//components/credential_management/android:features",
     "//components/password_manager/core/browser/form_parsing",
     "//components/prefs",
     "//components/security_state/content",
diff --git a/components/android_autofill/browser/android_autofill_client.cc b/components/android_autofill/browser/android_autofill_client.cc
index 739c7e48..13f35a63 100644
--- a/components/android_autofill/browser/android_autofill_client.cc
+++ b/components/android_autofill/browser/android_autofill_client.cc
@@ -23,6 +23,8 @@
 #include "components/autofill/core/browser/webdata/autofill_webdata_service.h"
 #include "components/autofill/core/common/aliases.h"
 #include "components/autofill/core/common/autofill_features.h"
+#include "components/credential_management/android/features.h"
+#include "components/credential_management/android/third_party_credential_manager_impl.h"
 #include "components/prefs/pref_service.h"
 #include "components/security_state/content/security_state_tab_helper.h"
 #include "components/user_prefs/user_prefs.h"
@@ -46,7 +48,11 @@
 }
 
 AndroidAutofillClient::AndroidAutofillClient(content::WebContents* web_contents)
-    : autofill::ContentAutofillClient(web_contents) {}
+    : autofill::ContentAutofillClient(web_contents),
+      content_credential_manager_(
+          std::make_unique<
+              credential_management::ThirdPartyCredentialManagerImpl>(
+              web_contents->GetPrimaryMainFrame())) {}
 
 AndroidAutofillClient::~AndroidAutofillClient() {
   HideAutofillSuggestions(autofill::SuggestionHidingReason::kTabGone);
@@ -288,4 +294,14 @@
   return base::WrapUnique(new autofill::AndroidAutofillManager(&driver));
 }
 
+credential_management::ContentCredentialManager*
+AndroidAutofillClient::GetContentCredentialManager() {
+  if (base::FeatureList::IsEnabled(
+          credential_management::features::
+              kCredentialManagementThirdPartyWebApiRequestForwarding)) {
+    return &content_credential_manager_;
+  }
+  return nullptr;
+}
+
 }  // namespace android_autofill
diff --git a/components/android_autofill/browser/android_autofill_client.h b/components/android_autofill/browser/android_autofill_client.h
index 4f4cfd5..9f694bb 100644
--- a/components/android_autofill/browser/android_autofill_client.h
+++ b/components/android_autofill/browser/android_autofill_client.h
@@ -21,6 +21,7 @@
 #include "components/autofill/core/browser/data_manager/valuables/valuables_data_manager.h"
 #include "components/autofill/core/browser/metrics/form_interactions_ukm_logger.h"
 #include "components/autofill/core/browser/payments/legal_message_line.h"
+#include "components/credential_management/content_credential_manager.h"
 #include "content/public/browser/web_contents_user_data.h"
 #include "ui/android/view_android.h"
 
@@ -137,6 +138,9 @@
       base::PassKey<autofill::ContentAutofillDriver> pass_key,
       autofill::ContentAutofillDriver& driver) final;
 
+  credential_management::ContentCredentialManager* GetContentCredentialManager()
+      override;
+
  protected:
   // Protected for testing.
   explicit AndroidAutofillClient(content::WebContents* web_contents);
@@ -156,6 +160,9 @@
   autofill::autofill_metrics::FormInteractionsUkmLogger
       form_interactions_ukm_logger_{this};
 
+  // Content credential manager to handle navigator.credentials calls.
+  credential_management::ContentCredentialManager content_credential_manager_;
+
   base::WeakPtrFactory<AndroidAutofillClient> weak_ptr_factory_{this};
 };
 
diff --git a/components/attribution_reporting/destination_set.cc b/components/attribution_reporting/destination_set.cc
index 3fb81b0..cb7e90c 100644
--- a/components/attribution_reporting/destination_set.cc
+++ b/components/attribution_reporting/destination_set.cc
@@ -11,7 +11,6 @@
 
 #include "base/check.h"
 #include "base/containers/flat_set.h"
-#include "base/functional/overloaded.h"
 #include "base/types/expected.h"
 #include "base/types/expected_macros.h"
 #include "base/values.h"
@@ -20,6 +19,7 @@
 #include "components/attribution_reporting/suitable_origin.h"
 #include "mojo/public/cpp/bindings/default_construct_tag.h"
 #include "net/base/schemeful_site.h"
+#include "third_party/abseil-cpp/absl/functional/overload.h"
 
 namespace attribution_reporting {
 
@@ -70,7 +70,7 @@
     return base::ok();
   };
 
-  RETURN_IF_ERROR(v->Visit(base::Overloaded{
+  RETURN_IF_ERROR(v->Visit(absl::Overload{
       [&](const std::string& str) {
         return append_if_valid(
             str, SourceRegistrationError::kDestinationUntrustworthy);
diff --git a/components/attribution_reporting/registration_header_error.cc b/components/attribution_reporting/registration_header_error.cc
index 0e0e3b75..b2430e9 100644
--- a/components/attribution_reporting/registration_header_error.cc
+++ b/components/attribution_reporting/registration_header_error.cc
@@ -6,15 +6,15 @@
 
 #include <variant>
 
-#include "base/functional/overloaded.h"
 #include "components/attribution_reporting/constants.h"
 #include "components/attribution_reporting/source_registration_error.mojom-forward.h"
 #include "components/attribution_reporting/trigger_registration_error.mojom-forward.h"
+#include "third_party/abseil-cpp/absl/functional/overload.h"
 
 namespace attribution_reporting {
 
 std::string_view RegistrationHeaderError::HeaderName() const {
-  return std::visit(base::Overloaded{
+  return std::visit(absl::Overload{
                         [](mojom::SourceRegistrationError) {
                           return kAttributionReportingRegisterSourceHeader;
                         },
diff --git a/components/attribution_reporting/registration_header_error_mojom_traits.cc b/components/attribution_reporting/registration_header_error_mojom_traits.cc
index 7e21cc7..616aed6d 100644
--- a/components/attribution_reporting/registration_header_error_mojom_traits.cc
+++ b/components/attribution_reporting/registration_header_error_mojom_traits.cc
@@ -6,11 +6,11 @@
 
 #include <variant>
 
-#include "base/functional/overloaded.h"
 #include "components/attribution_reporting/registration_header_error.h"
 #include "components/attribution_reporting/registration_header_error.mojom-shared.h"
 #include "mojo/public/cpp/bindings/struct_traits.h"
 #include "mojo/public/cpp/bindings/union_traits.h"
+#include "third_party/abseil-cpp/absl/functional/overload.h"
 
 namespace mojo {
 
@@ -38,7 +38,7 @@
     GetTag(
         const attribution_reporting::RegistrationHeaderErrorDetails& details) {
   return std::visit(
-      base::Overloaded{
+      absl::Overload{
           [](attribution_reporting::mojom::SourceRegistrationError) {
             return attribution_reporting::mojom::
                 RegistrationHeaderErrorDetailsDataView::Tag::kSourceError;
diff --git a/components/autofill/android/BUILD.gn b/components/autofill/android/BUILD.gn
index 535d7f35..f1cc23c5 100644
--- a/components/autofill/android/BUILD.gn
+++ b/components/autofill/android/BUILD.gn
@@ -247,6 +247,7 @@
     "java/src/org/chromium/components/autofill/AutofillDropdownAdapter.java",
     "java/src/org/chromium/components/autofill/AutofillPopup.java",
     "java/src/org/chromium/components/autofill/AutofillProfile.java",
+    "java/src/org/chromium/components/autofill/AutofillProfilePayload.java",
     "java/src/org/chromium/components/autofill/AutofillSuggestion.java",
     "java/src/org/chromium/components/autofill/DropdownKeyValue.java",
     "java/src/org/chromium/components/autofill/LoyaltyCard.java",
@@ -265,6 +266,7 @@
     "java/src/org/chromium/components/autofill/AutofillAddressEditorUiInfo.java",
     "java/src/org/chromium/components/autofill/AutofillAddressUiComponent.java",
     "java/src/org/chromium/components/autofill/AutofillProfile.java",
+    "java/src/org/chromium/components/autofill/AutofillProfilePayload.java",
     "java/src/org/chromium/components/autofill/DropdownKeyValue.java",
     "java/src/org/chromium/components/autofill/LoyaltyCard.java",
     "java/src/org/chromium/components/autofill/SubKeyRequester.java",
diff --git a/components/autofill/android/java/src/org/chromium/components/autofill/AutofillProfilePayload.java b/components/autofill/android/java/src/org/chromium/components/autofill/AutofillProfilePayload.java
new file mode 100644
index 0000000..c884ffd9
--- /dev/null
+++ b/components/autofill/android/java/src/org/chromium/components/autofill/AutofillProfilePayload.java
@@ -0,0 +1,27 @@
+// 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.components.autofill;
+
+import org.jni_zero.CalledByNative;
+import org.jni_zero.JNINamespace;
+import org.jni_zero.JniType;
+
+import org.chromium.build.annotations.NullMarked;
+import org.chromium.components.autofill.AutofillSuggestion.Payload;
+
+@JNINamespace("autofill")
+@NullMarked
+public final class AutofillProfilePayload implements Payload {
+    private final String mGuid;
+
+    @CalledByNative
+    private AutofillProfilePayload(@JniType("std::string") String guid) {
+        mGuid = guid;
+    }
+
+    public String getGuid() {
+        return mGuid;
+    }
+}
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 db74e2b9..d41a2221 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
@@ -33,6 +33,9 @@
     private final @Nullable GURL mCustomIconUrl;
     private final @Nullable String mGuid;
     private final boolean mIsLocalPaymentsMethod;
+    private final @Nullable Payload mPayload;
+
+    public static sealed interface Payload permits AutofillProfilePayload {}
 
     /**
      * Constructs a Autofill suggestion container. Use the {@link AutofillSuggestion.Builder}
@@ -52,6 +55,9 @@
      * @param guid The payment method identifier associated with the suggestion.
      * @param isLocalPaymentsMethod Whether the payments method associated with the suggestion is
      *     local.
+     * @param payload Additional data passed with the suggestion. Currently only
+     *     AutofillProfilePayload may passed. New payloads can be added by implementing the {@link
+     *     AutofillSuggestion.Payload} interface.
      */
     @VisibleForTesting
     public AutofillSuggestion(
@@ -69,7 +75,8 @@
             @Nullable String iphDescriptionText,
             @Nullable GURL customIconUrl,
             @Nullable String guid,
-            boolean isLocalPaymentsMethod) {
+            boolean isLocalPaymentsMethod,
+            @Nullable Payload payload) {
         mLabel = label;
         mSecondaryLabel = secondaryLabel;
         mSublabel = sublabel;
@@ -85,6 +92,7 @@
         mCustomIconUrl = customIconUrl;
         mGuid = guid;
         mIsLocalPaymentsMethod = isLocalPaymentsMethod;
+        mPayload = payload;
     }
 
     @Override
@@ -166,6 +174,10 @@
         return mIphDescriptionText;
     }
 
+    public @Nullable Payload getPayload() {
+        return mPayload;
+    }
+
     @Override
     public boolean equals(Object o) {
         if (this == o) {
@@ -189,7 +201,8 @@
                 && Objects.equals(this.mIphDescriptionText, other.mIphDescriptionText)
                 && Objects.equals(this.mCustomIconUrl, other.mCustomIconUrl)
                 && Objects.equals(this.mGuid, other.mGuid)
-                && this.mIsLocalPaymentsMethod == other.mIsLocalPaymentsMethod;
+                && this.mIsLocalPaymentsMethod == other.mIsLocalPaymentsMethod
+                && Objects.equals(this.mPayload, other.mPayload);
     }
 
     /** Builder for the {@link AutofillSuggestion}. */
@@ -209,6 +222,7 @@
         private int mSuggestionType;
         private @Nullable String mGuid;
         private boolean mIsLocalPaymentsMethod;
+        private @Nullable Payload mPayload;
 
         public Builder setIconId(int iconId) {
             this.mIconId = iconId;
@@ -285,6 +299,11 @@
             return this;
         }
 
+        public Builder setPayload(Payload payload) {
+            this.mPayload = payload;
+            return this;
+        }
+
         public AutofillSuggestion build() {
             assert mSuggestionType == SuggestionType.SEPARATOR || !TextUtils.isEmpty(mLabel)
                     : "Only separators may have an empty label.";
@@ -305,7 +324,8 @@
                     mIphDescriptionText,
                     mCustomIconUrl,
                     mGuid,
-                    mIsLocalPaymentsMethod);
+                    mIsLocalPaymentsMethod,
+                    mPayload);
         }
     }
 }
diff --git a/components/autofill/content/browser/BUILD.gn b/components/autofill/content/browser/BUILD.gn
index 903cad0..e523618 100644
--- a/components/autofill/content/browser/BUILD.gn
+++ b/components/autofill/content/browser/BUILD.gn
@@ -48,6 +48,7 @@
   deps = [
     ":autofill_shared_storage_proto",
     "//base:i18n",
+    "//components/credential_management:browser",
     "//components/os_crypt/sync",
     "//components/prefs",
     "//components/profile_metrics",
@@ -105,6 +106,7 @@
   public_deps = [
     ":browser",
     "//components/autofill/core/browser:test_support",
+    "//components/credential_management:browser",
     "//content/public/browser",
     "//content/test:test_support",
   ]
diff --git a/components/autofill/content/browser/DEPS b/components/autofill/content/browser/DEPS
index 0021f65..e508e8b 100644
--- a/components/autofill/content/browser/DEPS
+++ b/components/autofill/content/browser/DEPS
@@ -5,6 +5,7 @@
   "+components/profile_metrics",
   "+components/services/storage",
   "+components/webauthn",
+  "+components/credential_management",
   "+crypto/random.h",
   "+gpu/config/gpu_info.h",
   "+services/device/public",
diff --git a/components/autofill/content/browser/content_autofill_client.h b/components/autofill/content/browser/content_autofill_client.h
index f481d4e9..893ba46b 100644
--- a/components/autofill/content/browser/content_autofill_client.h
+++ b/components/autofill/content/browser/content_autofill_client.h
@@ -12,6 +12,9 @@
 #include "content/public/browser/web_contents.h"
 #include "content/public/browser/web_contents_user_data.h"
 
+namespace credential_management {
+class ContentCredentialManager;
+}
 namespace autofill {
 
 // Common base class for those AutofillClients that have the //content layer.
@@ -36,6 +39,11 @@
       base::PassKey<ContentAutofillDriver> pass_key,
       ContentAutofillDriver& driver) = 0;
 
+  // Returns the ContentCredentialManager for the WebContents that handles
+  // navigator.credentials requests or nullptr if none is available.
+  virtual credential_management::ContentCredentialManager*
+  GetContentCredentialManager() = 0;
+
  private:
   friend class content::WebContentsUserData<ContentAutofillClient>;
 
diff --git a/components/autofill/content/browser/content_autofill_driver.cc b/components/autofill/content/browser/content_autofill_driver.cc
index 6993d23..d1f6161 100644
--- a/components/autofill/content/browser/content_autofill_driver.cc
+++ b/components/autofill/content/browser/content_autofill_driver.cc
@@ -72,10 +72,7 @@
     unstripped_url = rfh.GetLastCommittedOrigin().GetURL();
   }
   form.set_url(StripAuthAndParams(unstripped_url));
-  if (base::FeatureList::IsEnabled(
-          features::kAutofillIncludeUrlInCrowdsourcing)) {
-    form.set_full_url(StripAuth(unstripped_url));
-  }
+  form.set_full_url(StripAuth(unstripped_url));
 
   // The form signature must be calculated after setting FormData::url.
   FormSignature signature = CalculateFormSignature(form);
diff --git a/components/autofill/content/browser/content_autofill_driver_unittest.cc b/components/autofill/content/browser/content_autofill_driver_unittest.cc
index ee79be6..ed0aec7 100644
--- a/components/autofill/content/browser/content_autofill_driver_unittest.cc
+++ b/components/autofill/content/browser/content_autofill_driver_unittest.cc
@@ -547,9 +547,6 @@
 };
 
 TEST_F(ContentAutofillDriverTest, Lift_Form) {
-  base::test::ScopedFeatureList features;
-  features.InitAndDisableFeature(features::kAutofillIncludeUrlInCrowdsourcing);
-
   NavigateAndCommit(GURL("https://username:password@a.test/path?query#hash"));
   FormData form;
   test_api(form).Append(FormFieldData());
@@ -557,7 +554,7 @@
 
   EXPECT_EQ(form.host_frame(), frame_token());
   EXPECT_EQ(form.url(), GURL("https://a.test/path"));
-  EXPECT_EQ(form.full_url(), GURL());
+  EXPECT_EQ(form.full_url(), GURL("https://a.test/path?query#hash"));
   EXPECT_EQ(form.main_frame_origin(),
             web_contents()->GetPrimaryMainFrame()->GetLastCommittedOrigin());
   EXPECT_EQ(form.main_frame_origin(),
@@ -566,20 +563,6 @@
   EXPECT_EQ(form.fields().front().host_frame(), frame_token());
 }
 
-// Tests that if `kAutofillIncludeUrlInCrowdsourcing` is enabled, the
-// FormData::full_url() returns the current URL stripped of auth parameters.
-TEST_F(ContentAutofillDriverTest, Lift_Form_WithUrlCrowdsourcing) {
-  base::test::ScopedFeatureList features{
-      features::kAutofillIncludeUrlInCrowdsourcing};
-
-  NavigateAndCommit(GURL("https://username:password@a.test/path?query#hash"));
-  FormData form;
-  test_api(form).Append(FormFieldData());
-  test_api(driver()).LiftForTest(form);
-
-  EXPECT_EQ(form.full_url(), GURL("https://a.test/path?query#hash"));
-}
-
 // Test that forms in "about:" without parents have an empty FormData::url.
 TEST_F(ContentAutofillDriverTest, Lift_Form_AboutScheme) {
   NavigateAndCommit(GURL("about:blank"));
diff --git a/components/autofill/content/browser/test_content_autofill_client.cc b/components/autofill/content/browser/test_content_autofill_client.cc
index 9c4f109..56370aa 100644
--- a/components/autofill/content/browser/test_content_autofill_client.cc
+++ b/components/autofill/content/browser/test_content_autofill_client.cc
@@ -18,4 +18,9 @@
   return std::make_unique<BrowserAutofillManager>(&driver);
 }
 
+credential_management::ContentCredentialManager*
+TestContentAutofillClient::GetContentCredentialManager() {
+  return nullptr;
+}
+
 }  // namespace autofill
diff --git a/components/autofill/content/browser/test_content_autofill_client.h b/components/autofill/content/browser/test_content_autofill_client.h
index ffe2858..e2b3a62 100644
--- a/components/autofill/content/browser/test_content_autofill_client.h
+++ b/components/autofill/content/browser/test_content_autofill_client.h
@@ -26,6 +26,9 @@
   std::unique_ptr<AutofillManager> CreateManager(
       base::PassKey<ContentAutofillDriver> pass_key,
       ContentAutofillDriver& driver) override;
+
+  credential_management::ContentCredentialManager* GetContentCredentialManager()
+      override;
 };
 
 }  // namespace autofill
diff --git a/components/autofill/content/renderer/password_autofill_agent.cc b/components/autofill/content/renderer/password_autofill_agent.cc
index ef545de1..7ca3231a 100644
--- a/components/autofill/content/renderer/password_autofill_agent.cc
+++ b/components/autofill/content/renderer/password_autofill_agent.cc
@@ -1030,8 +1030,11 @@
     std::move(callback).Run(std::nullopt);
     return;
   }
-  std::optional<FormData> form_data = GetFormDataFromWebForm(
-      last_element.GetOwningFormForAutofill(), /*form_cache=*/{});
+
+  const WebFormElement& form = last_element.GetOwningFormForAutofill();
+  std::optional<FormData> form_data =
+      form ? GetFormDataFromWebForm(form, /*form_cache=*/{})
+           : GetFormDataFromUnownedInputElements(/*form_cache=*/{});
   if (!form_data) {
     std::move(callback).Run(std::nullopt);
     return;
diff --git a/components/autofill/core/browser/crowdsourcing/autofill_crowdsourcing_encoding.cc b/components/autofill/core/browser/crowdsourcing/autofill_crowdsourcing_encoding.cc
index bcc78a7..23ce37bc 100644
--- a/components/autofill/core/browser/crowdsourcing/autofill_crowdsourcing_encoding.cc
+++ b/components/autofill/core/browser/crowdsourcing/autofill_crowdsourcing_encoding.cc
@@ -322,16 +322,12 @@
   // 0 is the default value for fields that do not allow free input, while
   // `kDefaultMaxLength` is the default value for fields that allow free input.
   if (field.max_length() != 0 &&
-      field.max_length() != FormFieldData::kDefaultMaxLength &&
-      base::FeatureList::IsEnabled(
-          features::kAutofillIncludeMaxLengthInCrowdsourcing)) {
+      field.max_length() != FormFieldData::kDefaultMaxLength) {
     encode_value(RandomizedEncoder::kFieldMaxLength,
                  base::NumberToString(field.max_length()),
                  metadata->mutable_max_length());
   }
-  if (field.IsSelectElement() &&
-      base::FeatureList::IsEnabled(
-          features::kAutofillIncludeSelectOptionsInCrowdsourcing)) {
+  if (field.IsSelectElement()) {
     auto add_option = [&](const SelectOption& option) {
       auto* proto_option = metadata->add_select_option();
       if (!option.text.empty()) {
diff --git a/components/autofill/core/browser/crowdsourcing/autofill_crowdsourcing_encoding_unittest.cc b/components/autofill/core/browser/crowdsourcing/autofill_crowdsourcing_encoding_unittest.cc
index 80e73e8..32cf281 100644
--- a/components/autofill/core/browser/crowdsourcing/autofill_crowdsourcing_encoding_unittest.cc
+++ b/components/autofill/core/browser/crowdsourcing/autofill_crowdsourcing_encoding_unittest.cc
@@ -1433,11 +1433,6 @@
        {SelectOption{.value = u"we are the same",
                      .text = u"we are the same"}}}};
 
-  base::test::ScopedFeatureList feature_list;
-  feature_list.InitWithFeatures(
-      {features::kAutofillIncludeMaxLengthInCrowdsourcing,
-       features::kAutofillIncludeSelectOptionsInCrowdsourcing},
-      {});
   FormData form;
   form.set_id_attribute(u"form-id");
   form.set_url(GURL("http://www.foo.com/"));
@@ -1643,81 +1638,6 @@
   }
 }
 
-// Tests that the maxlength attribute is not encoded if
-// `features::kAutofillIncludeMaxLengthInCrowdsourcing` is disabled.
-TEST_F(AutofillCrowdsourcingEncoding, MaxLengthIsNotSentIfFeatureIsOff) {
-  base::test::ScopedFeatureList feature_list;
-  feature_list.InitAndDisableFeature(
-      features::kAutofillIncludeMaxLengthInCrowdsourcing);
-  FormData form;
-  form.set_id_attribute(u"form-id");
-  {
-    FormFieldData field;
-    field.set_id_attribute(u"field-id");
-    field.set_max_length(123);
-    test_api(form).Append(field);
-  }
-
-  FormStructure form_structure(form);
-  for (auto& field : form_structure) {
-    field->set_host_form_signature(form_structure.form_signature());
-  }
-
-  EncodeUploadRequestOptions options;
-  options.encoder = RandomizedEncoder(
-      "seed for testing", AutofillRandomizedValue_EncodingType_ALL_BITS,
-      /*anonymous_url_collection_is_enabled=*/true);
-  options.available_field_types = {NO_SERVER_DATA};
-  options.observed_submission = true;
-
-  std::vector<AutofillUploadContents> uploads =
-      EncodeUploadRequest(form_structure, options);
-  ASSERT_EQ(uploads.size(), 1u);
-  AutofillUploadContents& upload = uploads.front();
-  ASSERT_EQ(upload.field_data_size(), 1);
-  ASSERT_TRUE(upload.field_data(0).has_randomized_field_metadata());
-  EXPECT_FALSE(
-      upload.field_data(0).randomized_field_metadata().has_max_length());
-}
-
-// Tests that select options are not encoded if
-// `features::kAutofillIncludeSelectOptionsInCrowdsourcing` is disabled.
-TEST_F(AutofillCrowdsourcingEncoding, SelectOptionsAreNotSentIfFeatureIsOff) {
-  base::test::ScopedFeatureList feature_list;
-  feature_list.InitAndDisableFeature(
-      features::kAutofillIncludeSelectOptionsInCrowdsourcing);
-  FormData form;
-  form.set_id_attribute(u"form-id");
-  {
-    FormFieldData field;
-    field.set_id_attribute(u"field-id");
-    field.set_options({SelectOption{.value = u"value", .text = u"some text"}});
-    field.set_form_control_type(FormControlType::kSelectOne);
-    test_api(form).Append(field);
-  }
-
-  FormStructure form_structure(form);
-  for (auto& field : form_structure) {
-    field->set_host_form_signature(form_structure.form_signature());
-  }
-
-  EncodeUploadRequestOptions options;
-  options.encoder = RandomizedEncoder(
-      "seed for testing", AutofillRandomizedValue_EncodingType_ALL_BITS,
-      /*anonymous_url_collection_is_enabled=*/true);
-  options.available_field_types = {NO_SERVER_DATA};
-  options.observed_submission = true;
-
-  std::vector<AutofillUploadContents> uploads =
-      EncodeUploadRequest(form_structure, options);
-  ASSERT_EQ(uploads.size(), 1u);
-  AutofillUploadContents& upload = uploads.front();
-  ASSERT_EQ(upload.field_data_size(), 1);
-  ASSERT_TRUE(upload.field_data(0).has_randomized_field_metadata());
-  EXPECT_EQ(
-      upload.field_data(0).randomized_field_metadata().select_option_size(), 0);
-}
-
 TEST_F(AutofillCrowdsourcingEncoding, Metadata_OnlySendFullUrlWithUserConsent) {
   for (bool has_consent : {true, false}) {
     SCOPED_TRACE(testing::Message() << " has_consent=" << has_consent);
diff --git a/components/autofill/core/browser/data_model/autofill_ai/entity_instance.cc b/components/autofill/core/browser/data_model/autofill_ai/entity_instance.cc
index 8bc8400a..abd4f700 100644
--- a/components/autofill/core/browser/data_model/autofill_ai/entity_instance.cc
+++ b/components/autofill/core/browser/data_model/autofill_ai/entity_instance.cc
@@ -189,7 +189,8 @@
       info_);
 }
 
-FieldTypeSet AttributeInstance::GetDatabaseStoredTypes() const {
+FieldTypeSet AttributeInstance::GetDatabaseStoredTypes(
+    base::PassKey<EntityTable> pass_key) const {
   return std::visit(
       absl::Overload{
           [&](const CountryInfo&) { return FieldTypeSet{type_.field_type()}; },
diff --git a/components/autofill/core/browser/data_model/autofill_ai/entity_instance.h b/components/autofill/core/browser/data_model/autofill_ai/entity_instance.h
index a3d9a42..6f91cb7 100644
--- a/components/autofill/core/browser/data_model/autofill_ai/entity_instance.h
+++ b/components/autofill/core/browser/data_model/autofill_ai/entity_instance.h
@@ -165,7 +165,8 @@
 
   // Returns the types which are stored in the database for this attribute
   // to be able to correctly reconstruct it at database loading time.
-  FieldTypeSet GetDatabaseStoredTypes() const;
+  FieldTypeSet GetDatabaseStoredTypes(
+      base::PassKey<EntityTable> pass_key) const;
 
   // This is a no-op for unstructured attributes, and for structured attributes
   // the function propagates changes in a component to its subcomponents. This
diff --git a/components/autofill/core/browser/data_model/valuables/loyalty_card.cc b/components/autofill/core/browser/data_model/valuables/loyalty_card.cc
index 2c7d942..7610f41 100644
--- a/components/autofill/core/browser/data_model/valuables/loyalty_card.cc
+++ b/components/autofill/core/browser/data_model/valuables/loyalty_card.cc
@@ -31,6 +31,7 @@
 
 bool LoyaltyCard::IsValid() const {
   return !id_->empty() && !loyalty_card_number_.empty() &&
+         !merchant_name_.empty() &&
          (program_logo_.is_empty() || program_logo_.is_valid());
 }
 
diff --git a/components/autofill/core/browser/filling/filling_product.cc b/components/autofill/core/browser/filling/filling_product.cc
index 240c504..73bebc1e 100644
--- a/components/autofill/core/browser/filling/filling_product.cc
+++ b/components/autofill/core/browser/filling/filling_product.cc
@@ -52,7 +52,6 @@
     case SuggestionType::kDevtoolsTestAddressByCountry:
     case SuggestionType::kDevtoolsTestAddressEntry:
     case SuggestionType::kManageAddress:
-    case SuggestionType::kHomeAndWorkAddressEntry:
       return FillingProduct::kAddress;
     case SuggestionType::kBnplEntry:
     case SuggestionType::kCreditCardEntry:
diff --git a/components/autofill/core/browser/foundations/browser_autofill_manager.cc b/components/autofill/core/browser/foundations/browser_autofill_manager.cc
index f678152e..dbfdc858 100644
--- a/components/autofill/core/browser/foundations/browser_autofill_manager.cc
+++ b/components/autofill/core/browser/foundations/browser_autofill_manager.cc
@@ -308,7 +308,6 @@
     case SuggestionType::kDevtoolsTestAddressEntry:
     case SuggestionType::kFillAutofillAi:
     case SuggestionType::kPendingStateSignin:
-    case SuggestionType::kHomeAndWorkAddressEntry:
       NOTREACHED();
   }
   NOTREACHED();
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 7902ad7..bedccde 100644
--- a/components/autofill/core/browser/suggestions/payments/payments_suggestion_generator.cc
+++ b/components/autofill/core/browser/suggestions/payments/payments_suggestion_generator.cc
@@ -1637,7 +1637,6 @@
     case SuggestionType::kBnplEntry:
     case SuggestionType::kPendingStateSignin:
     case SuggestionType::kLoyaltyCardEntry:
-    case SuggestionType::kHomeAndWorkAddressEntry:
       return false;
   }
 }
diff --git a/components/autofill/core/browser/suggestions/suggestion.cc b/components/autofill/core/browser/suggestions/suggestion.cc
index d48e6c6..7e797cd6 100644
--- a/components/autofill/core/browser/suggestions/suggestion.cc
+++ b/components/autofill/core/browser/suggestions/suggestion.cc
@@ -14,6 +14,12 @@
 #include "components/autofill/core/browser/data_model/payments/credit_card.h"
 #include "components/autofill/core/browser/suggestions/suggestion_type.h"
 
+#if BUILDFLAG(IS_ANDROID)
+#include "base/android/jni_android.h"
+#include "base/android/jni_string.h"
+#include "components/autofill/android/main_autofill_jni_headers/AutofillProfilePayload_jni.h"
+#endif  // BUILDFLAG(IS_ANDROID)
+
 namespace autofill {
 
 namespace {
@@ -240,6 +246,14 @@
 
 Suggestion::AutofillProfilePayload::~AutofillProfilePayload() = default;
 
+#if BUILDFLAG(IS_ANDROID)
+base::android::ScopedJavaLocalRef<jobject>
+Suggestion::AutofillProfilePayload::CreateJavaObject() const {
+  JNIEnv* env = base::android::AttachCurrentThread();
+  return Java_AutofillProfilePayload_Constructor(env, guid.value());
+}
+#endif  // BUILDFLAG(IS_ANDROID)
+
 Suggestion::IdentityCredentialPayload::IdentityCredentialPayload() = default;
 Suggestion::IdentityCredentialPayload::IdentityCredentialPayload(
     GURL configURL,
diff --git a/components/autofill/core/browser/suggestions/suggestion.h b/components/autofill/core/browser/suggestions/suggestion.h
index 66c8017..c5480199 100644
--- a/components/autofill/core/browser/suggestions/suggestion.h
+++ b/components/autofill/core/browser/suggestions/suggestion.h
@@ -28,6 +28,10 @@
 #include "ui/gfx/image/image.h"
 #include "url/gurl.h"
 
+#if BUILDFLAG(IS_ANDROID)
+#include "base/android/scoped_java_ref.h"
+#endif  // BUILDFLAG(IS_ANDROID)
+
 namespace autofill {
 
 struct Suggestion {
@@ -143,6 +147,10 @@
     AutofillProfilePayload& operator=(AutofillProfilePayload&&);
     ~AutofillProfilePayload();
 
+#if BUILDFLAG(IS_ANDROID)
+    base::android::ScopedJavaLocalRef<jobject> CreateJavaObject() const;
+#endif  // BUILDFLAG(IS_ANDROID)
+
     friend bool operator==(const AutofillProfilePayload&,
                            const AutofillProfilePayload&) = default;
 
diff --git a/components/autofill/core/browser/suggestions/suggestion_type.cc b/components/autofill/core/browser/suggestions/suggestion_type.cc
index 8521710..bb104e3 100644
--- a/components/autofill/core/browser/suggestions/suggestion_type.cc
+++ b/components/autofill/core/browser/suggestions/suggestion_type.cc
@@ -110,8 +110,6 @@
       return "kPendingStateSignin";
     case SuggestionType::kLoyaltyCardEntry:
       return "kLoyaltyCardEntry";
-    case SuggestionType::kHomeAndWorkAddressEntry:
-      return "kHomeAndWorkAddressEntry";
   }
   NOTREACHED();
 }
diff --git a/components/autofill/core/browser/suggestions/suggestion_type.h b/components/autofill/core/browser/suggestions/suggestion_type.h
index 5c3fc17..32d90be 100644
--- a/components/autofill/core/browser/suggestions/suggestion_type.h
+++ b/components/autofill/core/browser/suggestions/suggestion_type.h
@@ -121,7 +121,7 @@
   kLoyaltyCardEntry = 67,
 
   // Home & Work suggestions.
-  kHomeAndWorkAddressEntry = 69,
+  // kHomeAndWorkAddressEntry = 69, // DEPRECATED
 
   // Webauthn suggestions.
   kWebauthnCredential = 43,
@@ -159,7 +159,7 @@
   kPendingStateSignin = 65,
 
   // Next ID: 70
-  kMaxValue = kHomeAndWorkAddressEntry
+  kMaxValue = 68
 };
 // LINT.ThenChange(/tools/metrics/histograms/metadata/autofill/enums.xml:SuggestionType)
 
diff --git a/components/autofill/core/browser/ui/autofill_external_delegate.cc b/components/autofill/core/browser/ui/autofill_external_delegate.cc
index 4e0cb29..225f383fc 100644
--- a/components/autofill/core/browser/ui/autofill_external_delegate.cc
+++ b/components/autofill/core/browser/ui/autofill_external_delegate.cc
@@ -231,7 +231,6 @@
     case SuggestionType::kAddressEntry:
     case SuggestionType::kAddressEntryOnTyping:
     case SuggestionType::kAddressFieldByFieldFilling:
-    case SuggestionType::kHomeAndWorkAddressEntry:
     case SuggestionType::kCreditCardEntry:
     case SuggestionType::kDevtoolsTestAddresses:
     case SuggestionType::kSaveAndFillCreditCardEntry:
@@ -550,7 +549,6 @@
 #endif
       break;
     case SuggestionType::kAddressEntry:
-    case SuggestionType::kHomeAndWorkAddressEntry:
     case SuggestionType::kCreditCardEntry:
     case SuggestionType::kDevtoolsTestAddressEntry:
       FillAutofillFormData(
@@ -699,7 +697,6 @@
     case SuggestionType::kAddressEntry:
     case SuggestionType::kAddressFieldByFieldFilling:
     case SuggestionType::kDevtoolsTestAddressEntry:
-    case SuggestionType::kHomeAndWorkAddressEntry:
       DidAcceptAddressSuggestion(suggestion, metadata);
       break;
     case SuggestionType::kCreditCardEntry:
@@ -935,7 +932,6 @@
     // These SuggestionTypes are various types which can appear in the first
     // level suggestion to fill an address or credit card field.
     case SuggestionType::kAddressEntry:
-    case SuggestionType::kHomeAndWorkAddressEntry:
     case SuggestionType::kAddressFieldByFieldFilling: {
       const std::string guid =
           std::get<Suggestion::AutofillProfilePayload>(suggestion.payload)
diff --git a/components/autofill/core/browser/webdata/autofill_ai/entity_table.cc b/components/autofill/core/browser/webdata/autofill_ai/entity_table.cc
index 99effb8..67e37ed 100644
--- a/components/autofill/core/browser/webdata/autofill_ai/entity_table.cc
+++ b/components/autofill/core/browser/webdata/autofill_ai/entity_table.cc
@@ -235,7 +235,7 @@
 
 bool EntityTable::AddAttribute(const EntityInstance& entity,
                                const AttributeInstance& attribute) {
-  for (FieldType type : attribute.GetDatabaseStoredTypes()) {
+  for (FieldType type : attribute.GetDatabaseStoredTypes(/*pass_key=*/{})) {
     sql::Statement s;
     InsertBuilder(db(), s, attributes::kTableName,
                   {attributes::kEntityGuid, attributes::kAttributeType,
diff --git a/components/autofill/core/browser/webdata/valuables/valuables_sync_util.cc b/components/autofill/core/browser/webdata/valuables/valuables_sync_util.cc
index 5b1a60cf..99e3b64 100644
--- a/components/autofill/core/browser/webdata/valuables/valuables_sync_util.cc
+++ b/components/autofill/core/browser/webdata/valuables/valuables_sync_util.cc
@@ -68,6 +68,7 @@
 
   return !specifics.id().empty() && specifics.has_loyalty_card() &&
          !specifics.loyalty_card().loyalty_card_number().empty() &&
+         !specifics.loyalty_card().merchant_name().empty() &&
          HasEmptyOrValidProgramLogo(specifics);
 }
 
diff --git a/components/autofill/core/browser/webdata/valuables/valuables_sync_util_unittest.cc b/components/autofill/core/browser/webdata/valuables/valuables_sync_util_unittest.cc
index dec0c4e0..8d924d5d 100644
--- a/components/autofill/core/browser/webdata/valuables/valuables_sync_util_unittest.cc
+++ b/components/autofill/core/browser/webdata/valuables/valuables_sync_util_unittest.cc
@@ -105,6 +105,7 @@
       TestLoyaltyCardSpecifics(kId1, kInvalidProgramLogo)));
   EXPECT_FALSE(AreAutofillLoyaltyCardSpecificsValid(
       TestLoyaltyCardSpecifics(kId1, kValidProgramLogo, /*number=*/"")));
+
   EXPECT_TRUE(
       AreAutofillLoyaltyCardSpecificsValid(TestLoyaltyCardSpecifics(kId1)));
 
@@ -113,6 +114,10 @@
   EXPECT_TRUE(AreAutofillLoyaltyCardSpecificsValid(specifics));
   EXPECT_TRUE(AreAutofillLoyaltyCardSpecificsValid(
       TestLoyaltyCardSpecifics(kId1, /*program_logo=*/"")));
+
+  sync_pb::AutofillValuableSpecifics empty_merchant_name_specifics =
+      TestLoyaltyCardSpecifics(kId1);
+  empty_merchant_name_specifics.mutable_loyalty_card()->clear_merchant_name();
 }
 
 TEST_F(LoyaltyCardSyncUtilTest, TrimAutofillValuableSpecificsDataForCaching) {
diff --git a/components/autofill/core/browser/webdata/valuables/valuables_table_unittest.cc b/components/autofill/core/browser/webdata/valuables/valuables_table_unittest.cc
index acf5189d..41940169 100644
--- a/components/autofill/core/browser/webdata/valuables/valuables_table_unittest.cc
+++ b/components/autofill/core/browser/webdata/valuables/valuables_table_unittest.cc
@@ -103,6 +103,14 @@
   EXPECT_THAT(valuables_table().GetLoyaltyCards(), IsEmpty());
 }
 
+TEST_F(ValuablesTableTest, AddOrUpdateLoyaltyCard_EmptyMerchantName) {
+  LoyaltyCard card1 = test::CreateLoyaltyCard();
+  card1.set_merchant_name("");
+
+  EXPECT_FALSE(valuables_table().SetLoyaltyCards({card1}));
+  EXPECT_THAT(valuables_table().GetLoyaltyCards(), IsEmpty());
+}
+
 TEST_F(ValuablesTableTest, RemoveLoyaltyCard) {
   const LoyaltyCard card1 = test::CreateLoyaltyCard();
   const LoyaltyCard card2 = test::CreateLoyaltyCard2();
diff --git a/components/autofill/core/common/BUILD.gn b/components/autofill/core/common/BUILD.gn
index a34d251e..6db53dd 100644
--- a/components/autofill/core/common/BUILD.gn
+++ b/components/autofill/core/common/BUILD.gn
@@ -49,6 +49,7 @@
     "logging/log_buffer.cc",
     "logging/log_buffer.h",
     "logging/log_macros.h",
+    "logging/stream_operator_util.h",
     "metrics_enums.h",
     "password_form_fill_data.cc",
     "password_form_fill_data.h",
diff --git a/components/autofill/core/common/autofill_features.cc b/components/autofill/core/common/autofill_features.cc
index 79e7ac5..c31638a 100644
--- a/components/autofill/core/common/autofill_features.cc
+++ b/components/autofill/core/common/autofill_features.cc
@@ -367,25 +367,6 @@
         &kAutofillImprovedLabels,
         "autofill_improved_labels_with_differentiating_labels_in_front", false};
 
-// If enabled, we include a `FormFieldData`'s maxlength in crowdsourcing votes.
-// TODO(crbug.com/393995180): Clean up in M137.
-BASE_FEATURE(kAutofillIncludeMaxLengthInCrowdsourcing,
-             "AutofillIncludeMaxLengthInCrowdsourcing",
-             base::FEATURE_ENABLED_BY_DEFAULT);
-
-// If enabled, we include a <select>'s first, second, and last <option> in
-// crowdsourcing votes.
-// TODO(crbug.com/393999140): Clean up in M137.
-BASE_FEATURE(kAutofillIncludeSelectOptionsInCrowdsourcing,
-             "AutofillIncludeSelectOptionsInCrowdsourcing",
-             base::FEATURE_ENABLED_BY_DEFAULT);
-
-// If enabled, we include a `FormData`'s URL in crowdsourcing votes.
-// TODO(crbug.com/385043924): Clean up in M137.
-BASE_FEATURE(kAutofillIncludeUrlInCrowdsourcing,
-             "AutofillIncludeUrlInCrowdsourcing",
-             base::FEATURE_ENABLED_BY_DEFAULT);
-
 // If enabled, the new suggestion generation logic is used.
 // TODO(crbug.com/409962888): Remove once launched.
 BASE_FEATURE(kAutofillNewSuggestionGeneration,
diff --git a/components/autofill/core/common/autofill_features.h b/components/autofill/core/common/autofill_features.h
index 99485b23..7dfaf0f 100644
--- a/components/autofill/core/common/autofill_features.h
+++ b/components/autofill/core/common/autofill_features.h
@@ -132,12 +132,6 @@
 extern const base::FeatureParam<bool>
     kAutofillImprovedLabelsParamWithDifferentiatingLabelsInFrontParam;
 COMPONENT_EXPORT(AUTOFILL)
-BASE_DECLARE_FEATURE(kAutofillIncludeMaxLengthInCrowdsourcing);
-COMPONENT_EXPORT(AUTOFILL)
-BASE_DECLARE_FEATURE(kAutofillIncludeSelectOptionsInCrowdsourcing);
-COMPONENT_EXPORT(AUTOFILL)
-BASE_DECLARE_FEATURE(kAutofillIncludeUrlInCrowdsourcing);
-COMPONENT_EXPORT(AUTOFILL)
 BASE_DECLARE_FEATURE(kAutofillNewSuggestionGeneration);
 COMPONENT_EXPORT(AUTOFILL)
 BASE_DECLARE_FEATURE(kAutofillSupportPhoneticNameForJP);
diff --git a/components/autofill/core/common/form_data.cc b/components/autofill/core/common/form_data.cc
index 7c304ff..25666620e 100644
--- a/components/autofill/core/common/form_data.cc
+++ b/components/autofill/core/common/form_data.cc
@@ -18,6 +18,7 @@
 #include "base/strings/utf_string_conversions.h"
 #include "components/autofill/core/common/form_field_data.h"
 #include "components/autofill/core/common/logging/log_buffer.h"
+#include "components/autofill/core/common/logging/stream_operator_util.h"
 
 namespace autofill {
 
@@ -129,14 +130,46 @@
 }
 
 std::ostream& operator<<(std::ostream& os, const FormData& form) {
-  os << base::UTF16ToUTF8(form.name()) << " " << form.url() << " "
-     << form.action() << " " << form.main_frame_origin() << " " << "Fields:";
-  for (const FormFieldData& field : form.fields()) {
-    os << field << ",";
+  return internal::PrintWithIndentation(os, form, /*indentation=*/0);
+}
+
+namespace internal {
+
+std::ostream& PrintWithIndentation(std::ostream& os,
+                                   const FormData& form,
+                                   int indentation,
+                                   std::string_view title) {
+  std::string space = std::string(indentation, ' ');
+  os << space << "{";
+  if (!title.empty()) {
+    os << " /*" << title << "*/";
   }
+  os << '\n';
+#define PRINT_PROPERTY(property)                                            \
+  os << space << "  " << #property << ": " << PrintWrapper(form.property()) \
+     << ",\n"
+  PRINT_PROPERTY(global_id);
+  PRINT_PROPERTY(name);
+  PRINT_PROPERTY(url);
+  PRINT_PROPERTY(main_frame_origin);
+#undef PRINT_PROPERTY
+  os << space << "  fields: [ /*length " << form.fields().size() << "*/\n";
+  for (size_t i = 0; i < form.fields().size(); ++i) {
+    internal::PrintWithIndentation(
+        os, form.fields()[i], /*indentation=*/indentation + 4,
+        base::StrCat({"FormFieldData index ", base::NumberToString(i)}));
+    if (i < form.fields().size()) {
+      os << ",";
+    }
+    os << '\n';
+  }
+  os << space << "  ]\n";
+  os << space << "}";
   return os;
 }
 
+}  // namespace internal
+
 const FormFieldData* FormData::FindFieldByGlobalId(
     const FieldGlobalId& global_id) const {
   auto fields_it =
diff --git a/components/autofill/core/common/form_data.h b/components/autofill/core/common/form_data.h
index 9b9f0b3..bf6031a3 100644
--- a/components/autofill/core/common/form_data.h
+++ b/components/autofill/core/common/form_data.h
@@ -208,8 +208,7 @@
   void set_url(GURL url) { url_ = std::move(url); }
 
   // The full URL, including query parameters and fragment.
-  // If `kAutofillIncludeUrlInCrowdsourcing` is disabled, this value should only
-  // be set for password forms. This value should not be sent via mojo.
+  // This value should not be sent via mojo.
   const GURL& full_url() const { return full_url_; }
   void set_full_url(GURL full_url) { full_url_ = std::move(full_url); }
 
@@ -375,9 +374,15 @@
 // Whether any of the fields in |form| is a non-empty password field.
 bool FormHasNonEmptyPasswordField(const FormData& form);
 
-// For testing.
 std::ostream& operator<<(std::ostream& os, const FormData& form);
 
+namespace internal {
+std::ostream& PrintWithIndentation(std::ostream& os,
+                                   const FormData& field,
+                                   int indentation,
+                                   std::string_view title = "FormData");
+}  // namespace internal
+
 #if defined(UNIT_TEST)
 inline bool operator==(const FormData& lhs, const FormData& rhs) {
   return FormData::DeepEqual(lhs, rhs);
diff --git a/components/autofill/core/common/form_field_data.cc b/components/autofill/core/common/form_field_data.cc
index 524e7144..087da9c 100644
--- a/components/autofill/core/common/form_field_data.cc
+++ b/components/autofill/core/common/form_field_data.cc
@@ -21,6 +21,7 @@
 #include "components/autofill/core/common/autofill_features.h"
 #include "components/autofill/core/common/autofill_util.h"
 #include "components/autofill/core/common/logging/log_buffer.h"
+#include "components/autofill/core/common/logging/stream_operator_util.h"
 
 // TODO(crbug.com/41422062): Clean up the (de)serialization code.
 
@@ -628,34 +629,57 @@
 }
 
 std::ostream& operator<<(std::ostream& os, const FormFieldData& field) {
-  return os << "label='" << field.label() << "' "
-            << "unique_Id=" << field.global_id() << " " << "origin='"
-            << field.origin().Serialize() << "' " << "name='" << field.name()
-            << "' " << "id_attribute='" << field.id_attribute() << "' "
-            << "name_attribute='" << field.name_attribute() << "' " << "value='"
-            << field.value() << "' " << "control='" << field.form_control_type()
-            << "' " << "autocomplete='" << field.autocomplete_attribute()
-            << "' " << "parsed_autocomplete='"
-            << (field.parsed_autocomplete()
-                    ? field.parsed_autocomplete()->ToString()
-                    : "")
-            << "' " << "placeholder='" << field.placeholder() << "' "
-            << "max_length=" << field.max_length() << " " << "css_classes='"
-            << field.css_classes() << "' "
-            << "autofilled=" << field.is_autofilled() << " "
-            << "check_status=" << field.check_status() << " "
-            << "is_focusable=" << field.is_focusable() << " "
-            << "should_autocomplete=" << field.should_autocomplete() << " "
-            << "role=" << field.role() << " "
-            << "text_direction=" << field.text_direction() << " "
-            << "is_enabled=" << field.is_enabled() << " "
-            << "is_readonly=" << field.is_readonly() << " "
-            << "user_input=" << field.user_input() << " "
-            << "properties_mask=" << field.properties_mask() << " "
-            << "label_source=" << field.label_source() << " "
-            << "bounds=" << field.bounds().ToString();
+  return internal::PrintWithIndentation(os, field, /*indentation=*/0);
 }
 
+namespace internal {
+
+std::ostream& PrintWithIndentation(std::ostream& os,
+                                   const FormFieldData& field,
+                                   int indentation,
+                                   std::string_view title) {
+  std::string space = std::string(indentation, ' ');
+  os << space << "{";
+  if (!title.empty()) {
+    os << " /*" << title << "*/";
+  }
+  os << '\n';
+#define PRINT_PROPERTY(property)                                             \
+  os << space << "  " << #property << ": " << PrintWrapper(field.property()) \
+     << ",\n"
+  PRINT_PROPERTY(global_id);
+  PRINT_PROPERTY(label);
+  PRINT_PROPERTY(origin);
+  PRINT_PROPERTY(name);
+  PRINT_PROPERTY(id_attribute);
+  PRINT_PROPERTY(name_attribute);
+  PRINT_PROPERTY(value);
+  PRINT_PROPERTY(form_control_type);
+  PRINT_PROPERTY(autocomplete_attribute);
+  PRINT_PROPERTY(parsed_autocomplete);
+  PRINT_PROPERTY(placeholder);
+  PRINT_PROPERTY(max_length);
+  PRINT_PROPERTY(css_classes);
+  PRINT_PROPERTY(is_autofilled);
+  PRINT_PROPERTY(check_status);
+  PRINT_PROPERTY(is_focusable);
+  PRINT_PROPERTY(should_autocomplete);
+  PRINT_PROPERTY(role);
+  PRINT_PROPERTY(text_direction);
+  PRINT_PROPERTY(is_enabled);
+  PRINT_PROPERTY(is_readonly);
+  PRINT_PROPERTY(is_focusable);
+  PRINT_PROPERTY(is_visible);
+  PRINT_PROPERTY(user_input);
+  PRINT_PROPERTY(label_source);
+  PRINT_PROPERTY(bounds);
+#undef PRINT_PROPERTY
+  os << space << "}";
+  return os;
+}
+
+}  // namespace internal
+
 LogBuffer& operator<<(LogBuffer& buffer, const FormFieldData& field) {
   buffer << Tag{"table"};
   buffer << Tr{} << "Name:" << field.name();
diff --git a/components/autofill/core/common/form_field_data.h b/components/autofill/core/common/form_field_data.h
index db6e566c..a5c7de21 100644
--- a/components/autofill/core/common/form_field_data.h
+++ b/components/autofill/core/common/form_field_data.h
@@ -659,12 +659,18 @@
 bool DeserializeFormFieldData(base::PickleIterator* pickle_iterator,
                               FormFieldData* form_field_data);
 
-// So we can compare FormFieldDatas with EXPECT_EQ().
 std::ostream& operator<<(std::ostream& os, const FormFieldData& field);
 
 // Produces a <table> element with information about the form.
 LogBuffer& operator<<(LogBuffer& buffer, const FormFieldData& form);
 
+namespace internal {
+std::ostream& PrintWithIndentation(std::ostream& os,
+                                   const FormFieldData& field,
+                                   int indentation = 0,
+                                   std::string_view title = "FormFieldData");
+}  // namespace internal
+
 }  // namespace autofill
 
 #endif  // COMPONENTS_AUTOFILL_CORE_COMMON_FORM_FIELD_DATA_H_
diff --git a/components/autofill/core/common/logging/stream_operator_util.h b/components/autofill/core/common/logging/stream_operator_util.h
new file mode 100644
index 0000000..1d24730
--- /dev/null
+++ b/components/autofill/core/common/logging/stream_operator_util.h
@@ -0,0 +1,76 @@
+// 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 COMPONENTS_AUTOFILL_CORE_COMMON_LOGGING_STREAM_OPERATOR_UTIL_H_
+#define COMPONENTS_AUTOFILL_CORE_COMMON_LOGGING_STREAM_OPERATOR_UTIL_H_
+
+#include <optional>
+#include <ostream>
+#include <utility>
+
+#include "base/memory/stack_allocated.h"
+#include "base/types/cxx23_to_underlying.h"
+#include "url/origin.h"
+
+namespace autofill {
+
+// A wrapper for overloading operator<<() without interfering with existing
+// overloads.
+//
+// For example, to serialize
+//   std::string x = "hello world";
+// to
+//   "hello world"
+// we can overload
+//   std::ostream& operator<<(std::ostream& os, PrintWrapper<std::string> pw) {
+//     return os << '"' << pw.x << '"';
+//   }
+template <typename T>
+struct PrintWrapper {
+  STACK_ALLOCATED();
+
+ public:
+  const T& x;
+};
+
+template <typename T>
+std::ostream& operator<<(std::ostream& os, PrintWrapper<T> pw) {
+  if constexpr (requires(const T& x) {
+                  { os << x } -> std::same_as<std::ostream&>;
+                }) {
+    constexpr const char* kQuote =
+        std::is_integral_v<T> || std::is_enum_v<T> ? "" : "\"";
+    return os << kQuote << pw.x << kQuote;
+  }
+  if constexpr (requires(T x) {
+                  { os << x.ToString() } -> std::same_as<std::ostream&>;
+                }) {
+    return os << PrintWrapper(pw.x.ToString());
+  }
+  if constexpr (std::is_enum_v<T>) {
+    return os << base::to_underlying(pw.x);
+  }
+}
+
+template <typename T>
+std::ostream& operator<<(std::ostream& os, PrintWrapper<std::optional<T>> pw) {
+  if (pw.x) {
+    return os << "std::optional(" << PrintWrapper(*pw.x) << ")";
+  } else {
+    return os << "std::nullopt";
+  }
+}
+
+inline std::ostream& operator<<(std::ostream& os,
+                                PrintWrapper<url::Origin> pw) {
+  return os << PrintWrapper(pw.x.Serialize());
+}
+
+inline std::ostream& operator<<(std::ostream& os, PrintWrapper<bool> pw) {
+  return os << (pw.x ? "true" : "false");
+}
+
+}  // namespace autofill
+
+#endif  // COMPONENTS_AUTOFILL_CORE_COMMON_LOGGING_STREAM_OPERATOR_UTIL_H_
diff --git a/components/back_forward_cache/back_forward_cache_disable.cc b/components/back_forward_cache/back_forward_cache_disable.cc
index 45132c1..6e4068d 100644
--- a/components/back_forward_cache/back_forward_cache_disable.cc
+++ b/components/back_forward_cache/back_forward_cache_disable.cc
@@ -25,7 +25,7 @@
       return "browser::DomDistiller_SelfDeletingRequestDelegate";
     case DisabledReasonId::kOfflinePage:
       return "OfflinePage";
-    case DisabledReasonId::kChromePasswordManagerClient_BindCredentialManager:
+    case DisabledReasonId::kContentCredentialManager_BindCredentialManager:
       return "ChromePasswordManagerClient::BindCredentialManager";
     case DisabledReasonId::kPermissionRequestManager:
       return "PermissionRequestManager";
diff --git a/components/back_forward_cache/disabled_reason_id.h b/components/back_forward_cache/disabled_reason_id.h
index baaf5f3..a3d19a60 100644
--- a/components/back_forward_cache/disabled_reason_id.h
+++ b/components/back_forward_cache/disabled_reason_id.h
@@ -25,7 +25,7 @@
   kDomDistiller_SelfDeletingRequestDelegate = 6,
   kOomInterventionTabHelper = 7,
   kOfflinePage = 8,
-  kChromePasswordManagerClient_BindCredentialManager = 9,
+  kContentCredentialManager_BindCredentialManager = 9,
   kPermissionRequestManager = 10,
   // Modal dialog such as form resubmittion or http password dialog is shown for
   // the page.
diff --git a/components/credential_management/BUILD.gn b/components/credential_management/BUILD.gn
index abfc1b5..a8774e6e 100644
--- a/components/credential_management/BUILD.gn
+++ b/components/credential_management/BUILD.gn
@@ -12,8 +12,10 @@
 
   deps = [
     "//base",
-    "//components/password_manager/core/browser:browser",
+    "//components/back_forward_cache",
+    "//components/password_manager/core/browser",
     "//components/password_manager/core/common",
+    "//content/public/browser",
     "//content/public/common",
   ]
 }
@@ -24,7 +26,7 @@
   deps = [
     ":browser",
     "//base/test:test_support",
-    "//components/password_manager/core/common:common",
+    "//components/password_manager/core/common",
     "//testing/gmock",
   ]
 }
diff --git a/components/credential_management/DEPS b/components/credential_management/DEPS
index 4a2ad539..722d64e4 100644
--- a/components/credential_management/DEPS
+++ b/components/credential_management/DEPS
@@ -1,7 +1,9 @@
 include_rules = [
+  "+components/back_forward_cache/back_forward_cache_disable.h",
   "+mojo/public/cpp/bindings",
   "+third_party/blink/public/mojom",
   "+components/password_manager/core/common/credential_manager_types.h",
   "+components/password_manager/core/browser/credential_manager_impl.h",
   "+third_party/jni_zero/jni_zero.h",
+  "+content/public/browser",
 ]
diff --git a/components/credential_management/android/third_party_credential_manager_impl.cc b/components/credential_management/android/third_party_credential_manager_impl.cc
index 0ab5d3d..4f1798f 100644
--- a/components/credential_management/android/third_party_credential_manager_impl.cc
+++ b/components/credential_management/android/third_party_credential_manager_impl.cc
@@ -4,6 +4,7 @@
 #include "components/credential_management/android/third_party_credential_manager_impl.h"
 
 #include "base/android/jni_callback.h"
+#include "base/check_deref.h"
 #include "base/functional/bind.h"
 #include "base/notimplemented.h"
 
@@ -11,16 +12,15 @@
 
 ThirdPartyCredentialManagerImpl::ThirdPartyCredentialManagerImpl(
     content::RenderFrameHost* render_frame_host)
-    : DocumentUserData(render_frame_host),
-      bridge_(std::make_unique<ThirdPartyCredentialManagerBridge>()) {}
+    : bridge_(std::make_unique<ThirdPartyCredentialManagerBridge>()),
+      render_frame_host_(CHECK_DEREF(render_frame_host)) {}
 
 ThirdPartyCredentialManagerImpl::ThirdPartyCredentialManagerImpl(
     base::PassKey<class ThirdPartyCredentialManagerImplTest>,
     content::RenderFrameHost* render_frame_host,
     std::unique_ptr<CredentialManagerBridge> bridge)
-    : DocumentUserData(render_frame_host), bridge_(std::move(bridge)) {}
-
-DOCUMENT_USER_DATA_KEY_IMPL(ThirdPartyCredentialManagerImpl);
+    : bridge_(std::move(bridge)),
+      render_frame_host_(CHECK_DEREF(render_frame_host)) {}
 
 ThirdPartyCredentialManagerImpl::~ThirdPartyCredentialManagerImpl() = default;
 
@@ -30,7 +30,7 @@
   std::u16string username = credential.id.value_or(u"");
   std::u16string password = credential.password.value_or(u"");
   bridge_->Store(username, password,
-                 render_frame_host().GetLastCommittedOrigin().Serialize(),
+                 render_frame_host_->GetLastCommittedOrigin().Serialize(),
                  std::move(callback));
 }
 
@@ -93,7 +93,7 @@
   }
 
   bridge_->Get(ShouldAllowAutoSelect(mediation), include_passwords, federations,
-               render_frame_host().GetLastCommittedOrigin().Serialize(),
+               render_frame_host_->GetLastCommittedOrigin().Serialize(),
                std::move(callback));
 }
 
diff --git a/components/credential_management/android/third_party_credential_manager_impl.h b/components/credential_management/android/third_party_credential_manager_impl.h
index e79067c6..0ef7f39 100644
--- a/components/credential_management/android/third_party_credential_manager_impl.h
+++ b/components/credential_management/android/third_party_credential_manager_impl.h
@@ -5,18 +5,16 @@
 #ifndef COMPONENTS_CREDENTIAL_MANAGEMENT_ANDROID_THIRD_PARTY_CREDENTIAL_MANAGER_IMPL_H_
 #define COMPONENTS_CREDENTIAL_MANAGEMENT_ANDROID_THIRD_PARTY_CREDENTIAL_MANAGER_IMPL_H_
 
+#include "base/memory/raw_ref.h"
 #include "components/credential_management/android/third_party_credential_manager_bridge.h"
 #include "components/credential_management/credential_manager_interface.h"
-#include "content/public/browser/document_user_data.h"
 #include "content/public/browser/render_frame_host.h"
 #include "third_party/blink/public/mojom/credentialmanagement/credential_manager.mojom.h"
 
 namespace credential_management {
 
 // Class implementing Credential Manager methods for Clank in 3P mode.
-class ThirdPartyCredentialManagerImpl
-    : public content::DocumentUserData<ThirdPartyCredentialManagerImpl>,
-      public CredentialManagerInterface {
+class ThirdPartyCredentialManagerImpl : public CredentialManagerInterface {
  public:
   explicit ThirdPartyCredentialManagerImpl(
       content::RenderFrameHost* render_frame_host);
@@ -24,8 +22,6 @@
       base::PassKey<class ThirdPartyCredentialManagerImplTest>,
       content::RenderFrameHost* render_frame_host,
       std::unique_ptr<CredentialManagerBridge> bridge);
-  friend DocumentUserData;
-  DOCUMENT_USER_DATA_KEY_DECL();
 
   ThirdPartyCredentialManagerImpl(const ThirdPartyCredentialManagerImpl&) =
       delete;
@@ -45,6 +41,7 @@
 
  private:
   std::unique_ptr<CredentialManagerBridge> bridge_;
+  const raw_ref<content::RenderFrameHost> render_frame_host_;
 };
 
 }  // namespace credential_management
diff --git a/components/credential_management/content_credential_manager.cc b/components/credential_management/content_credential_manager.cc
index f33c486..dff96f68 100644
--- a/components/credential_management/content_credential_manager.cc
+++ b/components/credential_management/content_credential_manager.cc
@@ -7,7 +7,10 @@
 #include <utility>
 
 #include "base/functional/bind.h"
+#include "components/back_forward_cache/back_forward_cache_disable.h"
 #include "components/password_manager/core/browser/credential_manager_impl.h"
+#include "content/public/browser/render_process_host.h"
+#include "content/public/browser/web_contents.h"
 #include "mojo/public/cpp/bindings/message.h"
 
 namespace credential_management {
@@ -21,7 +24,43 @@
 ContentCredentialManager::~ContentCredentialManager() = default;
 
 void ContentCredentialManager::BindRequest(
+    content::RenderFrameHost* frame_host,
     mojo::PendingReceiver<blink::mojom::CredentialManager> receiver) {
+  // Only valid for the main frame.
+  if (frame_host->GetParent()) {
+    return;
+  }
+
+  content::WebContents* web_contents =
+      content::WebContents::FromRenderFrameHost(frame_host);
+  DCHECK(web_contents);
+
+  // Only valid for the currently committed RenderFrameHost, and not, e.g. old
+  // zombie RFH's being swapped out following cross-origin navigations.
+  if (web_contents->GetPrimaryMainFrame() != frame_host) {
+    return;
+  }
+
+  // The ContentCredentialManager will not bind the mojo interface for
+  // non-primary frames, e.g. BackForwardCache, Prerenderer, since the
+  // MojoBinderPolicy prevents this interface from being granted.
+  DCHECK_EQ(frame_host->GetLifecycleState(),
+            content::RenderFrameHost::LifecycleState::kActive);
+
+  // Disable BackForwardCache for this page.
+  // This is necessary because ContentCredentialManager::DisconnectBinding()
+  // will be called when the page is navigated away from, leaving it
+  // in an unusable state if the page is restored from the BackForwardCache.
+  //
+  // It looks like in order to remove this workaround, we probably just need to
+  // make the CredentialManager mojo API rebind on the renderer side when the
+  // next call is made, if it has become disconnected.
+  // TODO(crbug.com/40653684): Remove this workaround.
+  content::BackForwardCache::DisableForRenderFrameHost(
+      frame_host, back_forward_cache::DisabledReason(
+                      back_forward_cache::DisabledReasonId::
+                          kContentCredentialManager_BindCredentialManager));
+
   if (receiver_.is_bound()) {
     mojo::ReportBadMessage("CredentialManager is already bound.");
     return;
diff --git a/components/credential_management/content_credential_manager.h b/components/credential_management/content_credential_manager.h
index 500ed19..d51d4d6 100644
--- a/components/credential_management/content_credential_manager.h
+++ b/components/credential_management/content_credential_manager.h
@@ -9,6 +9,7 @@
 
 #include "components/credential_management/credential_manager_interface.h"
 #include "components/password_manager/core/common/credential_manager_types.h"
+#include "content/public/browser/render_frame_host.h"
 #include "mojo/public/cpp/bindings/pending_receiver.h"
 #include "mojo/public/cpp/bindings/receiver.h"
 #include "third_party/blink/public/mojom/credentialmanagement/credential_manager.mojom.h"
@@ -36,6 +37,7 @@
   ~ContentCredentialManager() override;
 
   void BindRequest(
+      content::RenderFrameHost* frame_host,
       mojo::PendingReceiver<blink::mojom::CredentialManager> receiver);
   bool HasBinding() const;
   void DisconnectBinding();
diff --git a/components/cronet/android/BUILD.gn b/components/cronet/android/BUILD.gn
index a39bec4..acfcb4bc 100644
--- a/components/cronet/android/BUILD.gn
+++ b/components/cronet/android/BUILD.gn
@@ -350,6 +350,8 @@
     "api/src/org/chromium/net/NetworkException.java",
     "api/src/org/chromium/net/NetworkQualityRttListener.java",
     "api/src/org/chromium/net/NetworkQualityThroughputListener.java",
+    "api/src/org/chromium/net/Proxy.java",
+    "api/src/org/chromium/net/ProxyOptions.java",
     "api/src/org/chromium/net/QuicException.java",
     "api/src/org/chromium/net/QuicOptions.java",
     "api/src/org/chromium/net/RequestFinishedInfo.java",
@@ -1384,6 +1386,7 @@
     "test/javatests/src/org/chromium/net/NetworkChangesTest.java",
     "test/javatests/src/org/chromium/net/NetworkErrorLoggingTest.java",
     "test/javatests/src/org/chromium/net/PkpTest.java",
+    "test/javatests/src/org/chromium/net/ProxyTest.java",
     "test/javatests/src/org/chromium/net/QuicTest.java",
     "test/javatests/src/org/chromium/net/RequestFinishedInfoTest.java",
     "test/javatests/src/org/chromium/net/SharedDictionaryTest.java",
diff --git a/components/cronet/android/api.txt b/components/cronet/android/api.txt
index dd438268..77a907d 100644
--- a/components/cronet/android/api.txt
+++ b/components/cronet/android/api.txt
@@ -99,6 +99,7 @@
   public org.chromium.net.CronetEngine$Builder setDnsOptions(org.chromium.net.DnsOptions$Builder);
   public org.chromium.net.CronetEngine$Builder setDnsOptions(org.chromium.net.DnsOptions);
   public org.chromium.net.CronetEngine$Builder setLibraryLoader(org.chromium.net.CronetEngine$Builder$LibraryLoader);
+  public org.chromium.net.CronetEngine$Builder setProxyOptions(org.chromium.net.ProxyOptions);
   public org.chromium.net.CronetEngine$Builder setQuicOptions(org.chromium.net.QuicOptions$Builder);
   public org.chromium.net.CronetEngine$Builder setQuicOptions(org.chromium.net.QuicOptions);
   public org.chromium.net.CronetEngine$Builder setStoragePath(java.lang.String);
@@ -329,6 +330,7 @@
   public org.chromium.net.ICronetEngineBuilder enableNetworkQualityEstimator(boolean);
   public org.chromium.net.ICronetEngineBuilder setConnectionMigrationOptions(org.chromium.net.ConnectionMigrationOptions);
   public org.chromium.net.ICronetEngineBuilder setDnsOptions(org.chromium.net.DnsOptions);
+  public org.chromium.net.ICronetEngineBuilder setProxyOptions(org.chromium.net.ProxyOptions);
   public org.chromium.net.ICronetEngineBuilder setQuicOptions(org.chromium.net.QuicOptions);
   public org.chromium.net.ICronetEngineBuilder setThreadPriority(int);
   public org.chromium.net.ICronetEngineBuilder();
@@ -366,6 +368,28 @@
   public java.util.concurrent.Executor getExecutor();
   public org.chromium.net.NetworkQualityThroughputListener(java.util.concurrent.Executor);
 }
+public abstract class org.chromium.net.Proxy$Callback {
+  public abstract boolean onTunnelHeadersReceived(java.util.List<java.util.Map$Entry<java.lang.String, java.lang.String>>, int);
+  public abstract java.util.List<java.util.Map$Entry<java.lang.String, java.lang.String>> onBeforeTunnelRequest();
+  public org.chromium.net.Proxy$Callback();
+}
+public interface org.chromium.net.Proxy$Scheme extends java.lang.annotation.Annotation {
+}
+public final class org.chromium.net.Proxy {
+  public int getPort();
+  public int getScheme();
+  public java.lang.String getHost();
+  public org.chromium.net.Proxy$Callback getCallback();
+  public org.chromium.net.Proxy(int, java.lang.String, int, org.chromium.net.Proxy$Callback);
+  public static final int HTTP;
+  public static final int HTTPS;
+}
+public interface org.chromium.net.ProxyOptions$Experimental extends java.lang.annotation.Annotation {
+}
+public final class org.chromium.net.ProxyOptions {
+  public java.util.List<org.chromium.net.Proxy> getProxyList();
+  public org.chromium.net.ProxyOptions(java.util.List<org.chromium.net.Proxy>);
+}
 public abstract class org.chromium.net.QuicException extends org.chromium.net.NetworkException {
   protected org.chromium.net.QuicException(java.lang.String, java.lang.Throwable);
   public abstract int getQuicDetailedErrorCode();
@@ -640,4 +664,4 @@
   public static org.chromium.net.apihelpers.UrlRequestCallbacks$CallbackAndResponseFuturePair<java.lang.String, org.chromium.net.apihelpers.StringCronetCallback> forStringBody(org.chromium.net.apihelpers.RedirectHandler);
   public static org.chromium.net.apihelpers.UrlRequestCallbacks$CallbackAndResponseFuturePair<org.json.JSONObject, org.chromium.net.apihelpers.JsonCronetCallback> forJsonBody(org.chromium.net.apihelpers.RedirectHandler);
 }
-Stamp: ded30b9fbe22dff6cc175cd483aa1e4b
+Stamp: 6c13ced51784773f05f4402c9f164984
diff --git a/components/cronet/android/api/src/org/chromium/net/CronetEngine.java b/components/cronet/android/api/src/org/chromium/net/CronetEngine.java
index 69794de..f8e956a 100644
--- a/components/cronet/android/api/src/org/chromium/net/CronetEngine.java
+++ b/components/cronet/android/api/src/org/chromium/net/CronetEngine.java
@@ -10,6 +10,7 @@
 import android.os.SystemClock;
 import android.util.Log;
 
+import androidx.annotation.NonNull;
 import androidx.annotation.VisibleForTesting;
 
 import org.json.JSONObject;
@@ -29,6 +30,7 @@
 import java.util.Date;
 import java.util.Iterator;
 import java.util.List;
+import java.util.Objects;
 import java.util.Set;
 import java.util.concurrent.Executor;
 
@@ -506,6 +508,36 @@
             return setConnectionMigrationOptions(connectionMigrationOptionsBuilder.build());
         }
 
+        /**
+         * Configures proxying behavior for connection establishment. This affects all connections
+         * established by a {@link CronetEngine} as a consequence of {@link UrlRequest} being
+         * started. For more details, see the documentation of {@link ProxyOptions}.
+         *
+         * <p>This is currently a no-op, setting this will have no effects on Cronet.
+         *
+         * <p>Warning: DO NOT USE without reaching out to Cronet maintainers first. This is
+         * experimental and subject to change.
+         *
+         * <p>Note: The Android OS can already define a "system" proxy configurations. This config
+         * might have been obtained by the user, from some enterprise profile configuration, or
+         * (most likely) from some network autoconfiguration (e.g., Web Proxy Auto-Discovery
+         * Protocol). Proxy configurations configured via this API and system ones are mutually
+         * exclusive. When specifying {@link ProxyOptions} you are overriding the system
+         * configuration, this can cause connectivity problems (e.g., the internet might no longer
+         * be reachable). To increase the chances of success, in case a fail-open configuration is
+         * provided, Cronet will use the system proxy configuration as a fallback, instead of trying
+         * to establish a non-proxied connection.
+         *
+         * @param proxyOptions ProxyOptions to be used for connections established by the {@link
+         *     CronetEngine} created by this builder.
+         * @return the builder to facilitate chaining.
+         */
+        @ProxyOptions.Experimental
+        public Builder setProxyOptions(@NonNull ProxyOptions proxyOptions) {
+            mBuilderDelegate.setProxyOptions(Objects.requireNonNull(proxyOptions));
+            return this;
+        }
+
         protected ExperimentalCronetEngine buildExperimental() {
             int implLevel = getImplApiLevel(mBuilderDelegate);
             if (implLevel != -1 && implLevel < getMaximumApiLevel()) {
diff --git a/components/cronet/android/api/src/org/chromium/net/ICronetEngineBuilder.java b/components/cronet/android/api/src/org/chromium/net/ICronetEngineBuilder.java
index f340a87d..8abfdba 100644
--- a/components/cronet/android/api/src/org/chromium/net/ICronetEngineBuilder.java
+++ b/components/cronet/android/api/src/org/chromium/net/ICronetEngineBuilder.java
@@ -66,6 +66,10 @@
         return this;
     }
 
+    public ICronetEngineBuilder setProxyOptions(ProxyOptions proxyOptions) {
+        return this;
+    }
+
     public abstract ICronetEngineBuilder setExperimentalOptions(String options);
 
     public abstract ICronetEngineBuilder setLibraryLoader(
diff --git a/components/cronet/android/api/src/org/chromium/net/Proxy.java b/components/cronet/android/api/src/org/chromium/net/Proxy.java
new file mode 100644
index 0000000..cfa7f58
--- /dev/null
+++ b/components/cronet/android/api/src/org/chromium/net/Proxy.java
@@ -0,0 +1,123 @@
+// 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.net;
+
+import androidx.annotation.IntDef;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+
+/**
+ * Represents a proxy that can be used by Cronet. Throughout this file we say "tunnel establishment
+ * request". This is intentionally vague: establishing connections via a proxy is a complicated
+ * subject, how it is established depends on many implementation details (e.g., it could end up
+ * being a GET with connection upgrade, a CONNECT or an extended CONNECT).
+ */
+public final class Proxy {
+
+    /**
+     * Types of proxies supported by {@link Proxy}. Specifies how Cronet establishes a connection to
+     * the proxy. Note this only affects how Cronet connects to the proxy, not how it connects to
+     * the final destination. Cronet will always negotiate an end-to-end secure connection to the
+     * destination if the destination uses HTTPS, regardless of the scheme used by the proxy.
+     */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({HTTP, HTTPS})
+    public @interface Scheme {}
+
+    /** Establish a plaintext connection to the proxy itself. */
+    public static final int HTTP = 0;
+
+    /** Establish a secure connection to the proxy itself. */
+    public static final int HTTPS = 1;
+
+    /** Controls tunnel establishment requests. */
+    public abstract static class Callback {
+        /**
+         * Called before sending a tunnel establishment request. Allows adding headers that will be
+         * sent only to the proxy as part of the tunnel establishment request. They will not be
+         * added to the actual HTTP requests that will go through the proxy.
+         *
+         * <p>Warning: This will be called directly on Cronet's network thread, do not block. If
+         * computing the headers is going to take a non-negligible amount of time, cancel and retry
+         * the request once they are ready.
+         *
+         * @return A list of headers to be added to the tunnel establishment request. This list can
+         *     be empty, in which case no headers will be added. If {@code null} is returned, the
+         *     tunnel connection will be canceled. TODO(https://crbug.com/422428959): Once the
+         *     implementation has landed, document what happens after canceling. Do we fallback onto
+         *     the next proxy in the list? Do we not fallback and just fail the HTTP request that
+         *     triggered the tunnel establishment request?
+         */
+        public abstract @Nullable List<Map.Entry<String, String>> onBeforeTunnelRequest();
+
+        /**
+         * Called after receiving a response to the tunnel establishment request. Allows reading
+         * headers and status code of the response to the tunnel establishment request. This will
+         * not be called for the actual HTTP requests that will go through the proxy.
+         *
+         * <p>Warning: This will be called directly on Cronet's network thread, do not block.
+         *
+         * @param responseHeaders The list of headers contained in the response to the tunnel
+         *     establishment request.
+         * @param statusCode The HTTP status code contained in the response to the tunnel
+         *     establishment request. TODO(https://crbug.com/422429606): Once the implementation has
+         *     landed, document whether this gets called in case of failure.
+         * @return {@code true} to allow using the tunnel connection to proxy requests. {@code
+         *     false} to cancel the tunnel connection. TODO(https://crbug.com/422428959): Once the
+         *     implementation has landed, document what happens after canceling. Do we fallback onto
+         *     the next proxy in the list? Do we not fallback and just fail the HTTP request that
+         *     triggered the tunnel establishment request?
+         */
+        public abstract boolean onTunnelHeadersReceived(
+                @NonNull List<Map.Entry<String, String>> responseHeaders, int statusCode);
+    }
+
+    /**
+     * Constructs a new proxy.
+     *
+     * @param scheme Type of proxy, as defined in {@link Scheme}.
+     * @param host Hostname of the proxy.
+     * @param port Port of the proxy.
+     * @param callback Callback, as defined in {@link Callback}, that gets invoked on different
+     *     events.
+     */
+    public Proxy(@Scheme int scheme, @NonNull String host, int port, @NonNull Callback callback) {
+        this.mScheme = scheme;
+        this.mHost = Objects.requireNonNull(host);
+        this.mPort = port;
+        this.mCallback = Objects.requireNonNull(callback);
+    }
+
+    /** Returns the {@link Scheme} of this proxy. */
+    public @Scheme int getScheme() {
+        return mScheme;
+    }
+
+    /** Returns the hostname of this proxy. */
+    public @NonNull String getHost() {
+        return mHost;
+    }
+
+    /** Returns the port of this proxy. */
+    public int getPort() {
+        return mPort;
+    }
+
+    /** Returns the {@link Callback} of this proxy. */
+    public @NonNull Callback getCallback() {
+        return mCallback;
+    }
+
+    private final @Scheme int mScheme;
+    private final @NonNull String mHost;
+    private final int mPort;
+    private final @NonNull Callback mCallback;
+}
diff --git a/components/cronet/android/api/src/org/chromium/net/ProxyOptions.java b/components/cronet/android/api/src/org/chromium/net/ProxyOptions.java
new file mode 100644
index 0000000..01747cb
--- /dev/null
+++ b/components/cronet/android/api/src/org/chromium/net/ProxyOptions.java
@@ -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.
+
+package org.chromium.net;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.RequiresOptIn;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+
+/** Defines a proxy configuration that can be used by Cronet. */
+public final class ProxyOptions {
+
+    /**
+     * Constructs a proxy configuration out of a list of {@link Proxy}. Proxies in the list will be
+     * used in order. Proxy in position n+1 will be used only if we failed to use proxy in position
+     * n. A {@code null} list element represents a DIRECT connection, in which case no proxying will
+     * take place. This can be used to define fail-open/fail-closed semantics: if the all of the
+     * proxies specified in the list happen to be unreachable, adding (or not adding) a {@code null}
+     * element at the end of the list will control whether non-proxied connections are allowed.
+     *
+     * @param proxyList The list of {@link Proxy} that defines this configuration.
+     */
+    public ProxyOptions(@NonNull List<Proxy> proxyList) {
+        this.mProxyList = new ArrayList<>(Objects.requireNonNull(proxyList));
+    }
+
+    /** Returns the list of proxies that are part of this proxy configuration. */
+    public @NonNull List<Proxy> getProxyList() {
+        return Collections.unmodifiableList(mProxyList);
+    }
+
+    /**
+     * An annotation for APIs which are not considered stable yet.
+     *
+     * <p>Experimental APIs are subject to change, breakage, or removal at any time and may not be
+     * production ready.
+     *
+     * <p>It's highly recommended to reach out to Cronet maintainers (<code>net-dev@chromium.org
+     * </code>) before using one of the APIs annotated as experimental outside of debugging and
+     * proof-of-concept code.
+     *
+     * <p>By using an Experimental API, applications acknowledge that they are doing so at their own
+     * risk.
+     */
+    @RequiresOptIn
+    public @interface Experimental {}
+
+    private final @NonNull List<Proxy> mProxyList;
+}
diff --git a/components/cronet/android/api_version.txt b/components/cronet/android/api_version.txt
index 7facc89..81b5c5d 100644
--- a/components/cronet/android/api_version.txt
+++ b/components/cronet/android/api_version.txt
@@ -1 +1 @@
-36
+37
diff --git a/components/cronet/android/test/javatests/src/org/chromium/net/ProxyTest.java b/components/cronet/android/test/javatests/src/org/chromium/net/ProxyTest.java
new file mode 100644
index 0000000..45bc682
--- /dev/null
+++ b/components/cronet/android/test/javatests/src/org/chromium/net/ProxyTest.java
@@ -0,0 +1,173 @@
+// 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.net;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertThrows;
+
+import static org.chromium.net.truth.UrlResponseInfoSubject.assertThat;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import org.chromium.base.test.util.Batch;
+import org.chromium.net.CronetTestRule.CronetImplementation;
+import org.chromium.net.CronetTestRule.IgnoreFor;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/** Test Cronet proxy support. */
+@RunWith(AndroidJUnit4.class)
+@Batch(Batch.UNIT_TESTS)
+@IgnoreFor(
+        implementations = {CronetImplementation.AOSP_PLATFORM, CronetImplementation.FALLBACK},
+        reason =
+                "This feature flag has not reached platform Cronet yet. Fallback provides no proxy"
+                        + " support.")
+public class ProxyTest {
+    @Rule public final CronetTestRule mTestRule = CronetTestRule.withManualEngineStartup();
+
+    @Before
+    public void setUp() throws Exception {
+        NativeTestServer.startNativeTestServer(mTestRule.getTestFramework().getContext());
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        NativeTestServer.shutdownNativeTestServer();
+    }
+
+    static class TestProxyCallback extends Proxy.Callback {
+        public AtomicBoolean mCallbackWasInvoked = new AtomicBoolean(false);
+
+        @Override
+        public @Nullable List<Map.Entry<String, String>> onBeforeTunnelRequest() {
+            mCallbackWasInvoked.set(true);
+            return null;
+        }
+
+        @Override
+        public boolean onTunnelHeadersReceived(
+                @NonNull List<Map.Entry<String, String>> responseHeaders, int statusCode) {
+            mCallbackWasInvoked.set(true);
+            return false;
+        }
+    }
+
+    @Test
+    @SmallTest
+    public void testSetProxyOptions_nullCallback_throws() {
+        assertThrows(
+                NullPointerException.class,
+                () ->
+                        new Proxy(
+                                /* scheme= */ Proxy.HTTPS,
+                                /* host= */ "this-hostname-does-not-exist.com",
+                                /* port= */ 8080,
+                                /* callback= */ null));
+    }
+
+    @Test
+    @SmallTest
+    public void testSetProxyOptions_nullHost_throws() {
+        TestProxyCallback proxyCallback = new TestProxyCallback();
+        assertThrows(
+                NullPointerException.class,
+                () ->
+                        new Proxy(
+                                /* scheme= */ Proxy.HTTPS,
+                                /* host= */ null,
+                                /* port= */ 8080,
+                                /* callback= */ proxyCallback));
+    }
+
+    @Test
+    @SmallTest
+    public void testSetProxyOptions_direct_requestSucceeds() {
+        TestProxyCallback proxyCallback = new TestProxyCallback();
+        mTestRule
+                .getTestFramework()
+                .applyEngineBuilderPatch(
+                        (builder) ->
+                                builder.setProxyOptions(
+                                        new ProxyOptions(Arrays.asList((Proxy) null))));
+        ExperimentalCronetEngine cronetEngine = mTestRule.getTestFramework().startEngine();
+        TestUrlRequestCallback callback = new TestUrlRequestCallback();
+        UrlRequest.Builder urlRequestBuilder =
+                cronetEngine.newUrlRequestBuilder(
+                        NativeTestServer.getSuccessURL(), callback, callback.getExecutor());
+        urlRequestBuilder.build().start();
+        callback.blockForDone();
+        assertThat(callback.getResponseInfoWithChecks()).hasHttpStatusCodeThat().isEqualTo(200);
+        assertThat(proxyCallback.mCallbackWasInvoked.get()).isFalse();
+    }
+
+    @Test
+    @SmallTest
+    public void testSetProxyOptions_notExistingProxyWithDirectFallback_requestSucceeds() {
+        TestProxyCallback proxyCallback = new TestProxyCallback();
+        mTestRule
+                .getTestFramework()
+                .applyEngineBuilderPatch(
+                        (builder) ->
+                                builder.setProxyOptions(
+                                        new ProxyOptions(
+                                                Arrays.asList(
+                                                        new Proxy(
+                                                                /* scheme= */ Proxy.HTTPS,
+                                                                /* host= */ "this-hostname-does-not-exist.com",
+                                                                /* port= */ 8080,
+                                                                /* callback= */ proxyCallback),
+                                                        null))));
+        ExperimentalCronetEngine cronetEngine = mTestRule.getTestFramework().startEngine();
+        TestUrlRequestCallback callback = new TestUrlRequestCallback();
+        UrlRequest.Builder urlRequestBuilder =
+                cronetEngine.newUrlRequestBuilder(
+                        NativeTestServer.getSuccessURL(), callback, callback.getExecutor());
+        urlRequestBuilder.build().start();
+        callback.blockForDone();
+        assertThat(callback.getResponseInfoWithChecks()).hasHttpStatusCodeThat().isEqualTo(200);
+        assertThat(proxyCallback.mCallbackWasInvoked.get()).isFalse();
+    }
+
+    @Test
+    @SmallTest
+    public void testSetProxyOptionsIsANoop_notExistingProxy_requestSucceeds() {
+        TestProxyCallback proxyCallback = new TestProxyCallback();
+        mTestRule
+                .getTestFramework()
+                .applyEngineBuilderPatch(
+                        (builder) ->
+                                builder.setProxyOptions(
+                                        new ProxyOptions(
+                                                Arrays.asList(
+                                                        new Proxy(
+                                                                /* scheme= */ Proxy.HTTPS,
+                                                                /* host= */ "this-hostname-does-not-exist.com",
+                                                                /* port= */ 8080,
+                                                                /* callback= */ proxyCallback)))));
+        ExperimentalCronetEngine cronetEngine = mTestRule.getTestFramework().startEngine();
+        TestUrlRequestCallback callback = new TestUrlRequestCallback();
+        UrlRequest.Builder urlRequestBuilder =
+                cronetEngine.newUrlRequestBuilder(
+                        NativeTestServer.getSuccessURL(), callback, callback.getExecutor());
+        urlRequestBuilder.build().start();
+        callback.blockForDone();
+        assertThat(callback.getResponseInfoWithChecks()).hasHttpStatusCodeThat().isEqualTo(200);
+        assertThat(proxyCallback.mCallbackWasInvoked.get()).isFalse();
+    }
+}
diff --git a/components/password_manager/core/browser/BUILD.gn b/components/password_manager/core/browser/BUILD.gn
index 66c2aaac..abee242b 100644
--- a/components/password_manager/core/browser/BUILD.gn
+++ b/components/password_manager/core/browser/BUILD.gn
@@ -20,8 +20,6 @@
 
 static_library("browser") {
   sources = [
-    "browser_credential_manager_factory.cc",
-    "browser_credential_manager_factory.h",
     "browser_save_password_progress_logger.cc",
     "browser_save_password_progress_logger.h",
     "credential_cache.cc",
diff --git a/components/password_manager/core/browser/browser_credential_manager_factory.cc b/components/password_manager/core/browser/browser_credential_manager_factory.cc
deleted file mode 100644
index 96a7f7e4..0000000
--- a/components/password_manager/core/browser/browser_credential_manager_factory.cc
+++ /dev/null
@@ -1,18 +0,0 @@
-// 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 "components/password_manager/core/browser/browser_credential_manager_factory.h"
-
-#include "components/password_manager/core/browser/credential_manager_impl.h"
-
-namespace password_manager {
-BrowserCredentialManagerFactory::BrowserCredentialManagerFactory(
-    PasswordManagerClient* client)
-    : client_(client) {}
-
-std::unique_ptr<credential_management::CredentialManagerInterface>
-BrowserCredentialManagerFactory::CreateCredentialManager() {
-  return std::make_unique<password_manager::CredentialManagerImpl>(client_);
-}
-}  // namespace password_manager
diff --git a/components/password_manager/core/browser/browser_credential_manager_factory.h b/components/password_manager/core/browser/browser_credential_manager_factory.h
deleted file mode 100644
index 80f9d7fd..0000000
--- a/components/password_manager/core/browser/browser_credential_manager_factory.h
+++ /dev/null
@@ -1,36 +0,0 @@
-// 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 COMPONENTS_PASSWORD_MANAGER_CORE_BROWSER_BROWSER_CREDENTIAL_MANAGER_FACTORY_H_
-#define COMPONENTS_PASSWORD_MANAGER_CORE_BROWSER_BROWSER_CREDENTIAL_MANAGER_FACTORY_H_
-
-#include "base/memory/raw_ptr.h"
-#include "components/credential_management/credential_manager_factory_interface.h"
-#include "components/password_manager/core/browser/password_manager_client.h"
-
-namespace password_manager {
-class PasswordManagerClient;
-
-// The factory is used to create an instance of `CredentialManagerImpl` that's
-// used by `ContentCredentialManager` to implement Credential Management API
-// methods. This factory is browser-specific and doesn't exist on WebView.
-class BrowserCredentialManagerFactory
-    : public credential_management::CredentialManagerFactoryInterface {
- public:
-  explicit BrowserCredentialManagerFactory(PasswordManagerClient* client);
-  BrowserCredentialManagerFactory(const BrowserCredentialManagerFactory&) =
-      delete;
-  BrowserCredentialManagerFactory& operator=(
-      const BrowserCredentialManagerFactory&) = delete;
-
-  std::unique_ptr<credential_management::CredentialManagerInterface>
-  CreateCredentialManager() override;
-
- private:
-  raw_ptr<PasswordManagerClient> client_;
-};
-
-}  // namespace password_manager
-
-#endif  // COMPONENTS_PASSWORD_MANAGER_CORE_BROWSER_BROWSER_CREDENTIAL_MANAGER_FACTORY_H_
diff --git a/components/password_manager/core/browser/password_autofill_manager.cc b/components/password_manager/core/browser/password_autofill_manager.cc
index 8ead63b..48ec842 100644
--- a/components/password_manager/core/browser/password_autofill_manager.cc
+++ b/components/password_manager/core/browser/password_autofill_manager.cc
@@ -73,10 +73,6 @@
 using autofill::password_generation::PasswordGenerationType;
 using IsLoading = autofill::Suggestion::IsLoading;
 
-// This covers the 95th percentile on desktop platforms. See
-// `PasswordManager.PasskeyRetrievalWaitDuration` metric.
-constexpr base::TimeDelta kWaitForPasskeysDelay = base::Milliseconds(4000);
-
 // If `suggestion` was made for an empty username, then return the empty
 // string, otherwise return `suggestion`.
 std::u16string GetUsernameFromSuggestion(const std::u16string& suggestion) {
@@ -420,8 +416,11 @@
               &PasswordAutofillManager::ContinueShowingPasswordSuggestions,
               GetWeakPtr(), element_id, text_direction, typed_username,
               show_webauthn_credentials, show_identity_credentials, bounds);
-          wait_for_passkeys_timer_.Start(FROM_HERE, kWaitForPasskeysDelay,
-                                         std::move(continue_callback));
+          wait_for_passkeys_timer_.Start(
+              FROM_HERE,
+              base::Milliseconds(
+                  features::kDelaySuggestionsOnAutofocusTimeout.Get()),
+              std::move(continue_callback));
 
           // If passkeys become available before the timer expires, this closure
           // runs. It is similar to `continue_callback` but it has to check that
diff --git a/components/password_manager/core/common/password_manager_features.cc b/components/password_manager/core/common/password_manager_features.cc
index 81bb1e7..74937b4 100644
--- a/components/password_manager/core/common/password_manager_features.cc
+++ b/components/password_manager/core/common/password_manager_features.cc
@@ -8,10 +8,34 @@
 #include "build/blink_buildflags.h"
 #include "build/build_config.h"
 
+namespace {
+// This covers the 95th percentile on desktop platforms. See
+// `PasswordManager.PasskeyRetrievalWaitDuration` metric.
+// On Android the target is to get retrieval times under this threshold.
+constexpr int kDefaultDelaySuggestionsTimeout = 4000;
+}  // namespace
+
 namespace password_manager::features {
 // NOTE: It is strongly recommended to use UpperCamelCase style for feature
 //       names, e.g. "MyGreatFeature".
 
+// Only relevant when `kShowSuggestionsOnAutofocus` is on. This prevents
+// suggestions from being shown while waiting for passkeys to become available,
+// if the popup was triggered by autofocus without user interaction. It is
+// enabled by default and can be turned off if it is found to cause any
+// problems.
+BASE_FEATURE(kDelaySuggestionsOnAutofocusWaitingForPasskeys,
+             "DelaysSuggestionsOnAutofocusWaitingForPasskeys",
+             base::FEATURE_ENABLED_BY_DEFAULT);
+
+// Timeout value used when kDelaySuggestionsOnAutofocusWaitingForPasskeys is
+// enabled.
+BASE_FEATURE_PARAM(int,
+                   kDelaySuggestionsOnAutofocusTimeout,
+                   &kDelaySuggestionsOnAutofocusWaitingForPasskeys,
+                   "timeout_ms",
+                   kDefaultDelaySuggestionsTimeout);
+
 #if BUILDFLAG(IS_IOS)
 // Enables password bottom sheet to be triggered on autofocus events (on iOS).
 BASE_FEATURE(kIOSPasswordBottomSheetAutofocus,
@@ -30,15 +54,6 @@
              "ShowSuggestionsOnAutofocus",
              base::FEATURE_DISABLED_BY_DEFAULT);
 
-// Only relevant when `kShowSuggestionsOnAutofocus` is on. This prevents
-// suggestions from being shown while waiting for passkeys to become available,
-// if the popup was triggered by autofocus without user interaction. It is
-// enabled by default and can be turned off if it is found to cause any
-// problems.
-BASE_FEATURE(kDelaySuggestionsOnAutofocusWaitingForPasskeys,
-             "DelaysSuggestionsOnAutofocusWaitingForPasskeys",
-             base::FEATURE_ENABLED_BY_DEFAULT);
-
 // Field trial identifier for password generation requirements.
 const char kGenerationRequirementsFieldTrial[] =
     "PasswordGenerationRequirements";
diff --git a/components/password_manager/core/common/password_manager_features.h b/components/password_manager/core/common/password_manager_features.h
index d7715e4..29a111b 100644
--- a/components/password_manager/core/common/password_manager_features.h
+++ b/components/password_manager/core/common/password_manager_features.h
@@ -17,12 +17,13 @@
 
 // All features in alphabetical order. The features should be documented
 // alongside the definition of their values in the .cc file.
+BASE_DECLARE_FEATURE(kDelaySuggestionsOnAutofocusWaitingForPasskeys);
+BASE_DECLARE_FEATURE_PARAM(int, kDelaySuggestionsOnAutofocusTimeout);
 #if BUILDFLAG(IS_IOS)
 BASE_DECLARE_FEATURE(kIOSPasswordBottomSheetAutofocus);
 #endif  // IS_IOS
 BASE_DECLARE_FEATURE(kNoPasswordSuggestionFiltering);
 BASE_DECLARE_FEATURE(kShowSuggestionsOnAutofocus);
-BASE_DECLARE_FEATURE(kDelaySuggestionsOnAutofocusWaitingForPasskeys);
 
 // All features parameters are in alphabetical order.
 
diff --git a/components/permissions_strings.grdp b/components/permissions_strings.grdp
index 5214284..542bfb7 100644
--- a/components/permissions_strings.grdp
+++ b/components/permissions_strings.grdp
@@ -477,7 +477,7 @@
   </if>
   <if expr="is_chromeos">
     <message name="IDS_SMART_CARD_PERMISSION_PROMPT" desc="Text on dialog that asks the user for permission to access a smart card reader device and the card inserted in it (or presented to it, if contactless).">
-      Control readers named <ph name="ReaderName">$1<ex>HID Omnikey</ex></ph> and smart cards used with them
+      Control authentication devices named <ph name="ReaderName">$1<ex>HID Omnikey</ex></ph> and smart cards used with them
     </message>
     <message name="IDS_SMART_CARD_PERMISSION_ALWAYS_ALLOW" desc="Label on button to always allow access to this smart card reader and any card inserted in (or presented to) it.">
       Always allow, with any card
diff --git a/components/permissions_strings_grdp/IDS_SMART_CARD_PERMISSION_PROMPT.png.sha1 b/components/permissions_strings_grdp/IDS_SMART_CARD_PERMISSION_PROMPT.png.sha1
index ad2ccdc..556389a 100644
--- a/components/permissions_strings_grdp/IDS_SMART_CARD_PERMISSION_PROMPT.png.sha1
+++ b/components/permissions_strings_grdp/IDS_SMART_CARD_PERMISSION_PROMPT.png.sha1
@@ -1 +1 @@
-62841d481b95ca881b12e3af787eb7625495a174
\ No newline at end of file
+6a5693399b8bd871ff62ba921224ea997d8ff4d7
\ No newline at end of file
diff --git a/components/plus_addresses/settings/plus_address_setting_sync_util.cc b/components/plus_addresses/settings/plus_address_setting_sync_util.cc
index 58361dc9..7cd5576 100644
--- a/components/plus_addresses/settings/plus_address_setting_sync_util.cc
+++ b/components/plus_addresses/settings/plus_address_setting_sync_util.cc
@@ -7,7 +7,7 @@
 #include <string>
 #include <variant>
 
-#include "base/functional/overloaded.h"
+#include "third_party/abseil-cpp/absl/functional/overload.h"
 
 namespace plus_addresses {
 
@@ -16,7 +16,7 @@
     std::variant<bool, const char*, int32_t> value) {
   sync_pb::PlusAddressSettingSpecifics specifics;
   specifics.set_name(std::string(name));
-  std::visit(base::Overloaded{
+  std::visit(absl::Overload{
                  [&](bool value) { specifics.set_bool_value(value); },
                  [&](const char* value) { specifics.set_string_value(value); },
                  [&](int32_t value) { specifics.set_int_value(value); }},
diff --git a/components/privacy_sandbox/privacy_sandbox_features.cc b/components/privacy_sandbox/privacy_sandbox_features.cc
index e4ddfd7..228f476 100644
--- a/components/privacy_sandbox/privacy_sandbox_features.cc
+++ b/components/privacy_sandbox/privacy_sandbox_features.cc
@@ -11,12 +11,15 @@
 #if BUILDFLAG(IS_ANDROID)
 BASE_FEATURE(kPrivacySandboxAdsNoticeCCT,
              "PrivacySandboxAdsNoticeCCT",
-             base::FEATURE_DISABLED_BY_DEFAULT);
+             base::FEATURE_ENABLED_BY_DEFAULT);
 
 const char kPrivacySandboxAdsNoticeCCTAppIdName[] = "app-id";
+const char kAndroidGoogleSearchAppIdName[] =
+    "com.google.android.googlequicksearchbox";
 
 const base::FeatureParam<std::string> kPrivacySandboxAdsNoticeCCTAppId{
-    &kPrivacySandboxAdsNoticeCCT, kPrivacySandboxAdsNoticeCCTAppIdName, ""};
+    &kPrivacySandboxAdsNoticeCCT, kPrivacySandboxAdsNoticeCCTAppIdName,
+    kAndroidGoogleSearchAppIdName};
 #endif  // BUILDFLAG(IS_ANDROID)
 
 BASE_FEATURE(kPrivacySandboxSettings4,
diff --git a/components/sessions/core/session_service_commands.h b/components/sessions/core/session_service_commands.h
index fe83f2d..1e64fdb 100644
--- a/components/sessions/core/session_service_commands.h
+++ b/components/sessions/core/session_service_commands.h
@@ -8,6 +8,7 @@
 #include <map>
 #include <memory>
 #include <optional>
+#include <set>
 #include <string>
 
 #include "components/sessions/core/command_storage_manager.h"
diff --git a/components/viz/common/features.cc b/components/viz/common/features.cc
index fa0f23c..0c5bd19 100644
--- a/components/viz/common/features.cc
+++ b/components/viz/common/features.cc
@@ -424,6 +424,12 @@
              "LastVSyncArgsKillswitch",
              base::FEATURE_DISABLED_BY_DEFAULT);
 
+// Enables IPCs to directly target Viz's compositor thread for non-root
+// CompositorFrameSink messages without hopping through the IO thread first.
+BASE_FEATURE(kVizDirectCompositorThreadIpcNonRoot,
+             "VizDirectCompositorThreadIpcNonRoot",
+             base::FEATURE_DISABLED_BY_DEFAULT);
+
 // Null Hypothesis test for viz. This will be used in an meta experiment to
 // judge finch variation.
 BASE_FEATURE(kVizNullHypothesis,
@@ -458,6 +464,10 @@
   return base::FeatureList::IsEnabled(kDelegatedCompositing);
 }
 
+bool IsVizDirectCompositorThreadIpcNonRootEnabled() {
+  return base::FeatureList::IsEnabled(kVizDirectCompositorThreadIpcNonRoot);
+}
+
 #if BUILDFLAG(IS_ANDROID)
 bool IsDynamicColorGamutEnabled() {
   if (viz::AlwaysUseWideColorGamut())
diff --git a/components/viz/common/features.h b/components/viz/common/features.h
index 375b1ea..cad9e0d 100644
--- a/components/viz/common/features.h
+++ b/components/viz/common/features.h
@@ -121,6 +121,7 @@
 VIZ_COMMON_EXPORT BASE_DECLARE_FEATURE(kSingleVideoFrameRateThrottling);
 VIZ_COMMON_EXPORT BASE_DECLARE_FEATURE(kBatchMainThreadReleaseCallbacks);
 VIZ_COMMON_EXPORT BASE_DECLARE_FEATURE(kLastVSyncArgsKillswitch);
+VIZ_COMMON_EXPORT BASE_DECLARE_FEATURE(kVizDirectCompositorThreadIpcNonRoot);
 VIZ_COMMON_EXPORT BASE_DECLARE_FEATURE(kVizNullHypothesis);
 #if BUILDFLAG(IS_CHROMEOS)
 VIZ_COMMON_EXPORT BASE_DECLARE_FEATURE(kCrosContentAdjustedRefreshRate);
@@ -147,6 +148,7 @@
 #endif
 VIZ_COMMON_EXPORT int DrawQuadSplitLimit();
 VIZ_COMMON_EXPORT bool IsDelegatedCompositingEnabled();
+VIZ_COMMON_EXPORT bool IsVizDirectCompositorThreadIpcNonRootEnabled();
 #if BUILDFLAG(IS_WIN)
 VIZ_COMMON_EXPORT bool ShouldRemoveRedirectionBitmap();
 #endif
diff --git a/components/viz/service/display/frame_interval_decider.cc b/components/viz/service/display/frame_interval_decider.cc
index 8f0af6fe..55d8658 100644
--- a/components/viz/service/display/frame_interval_decider.cc
+++ b/components/viz/service/display/frame_interval_decider.cc
@@ -9,7 +9,6 @@
 #include <utility>
 #include <variant>
 
-#include "base/functional/overloaded.h"
 #include "base/memory/ptr_util.h"
 #include "base/metrics/histogram_functions.h"
 #include "base/rand_util.h"
@@ -17,6 +16,7 @@
 #include "components/viz/common/quads/frame_interval_inputs.h"
 #include "components/viz/service/surfaces/surface.h"
 #include "components/viz/service/surfaces/surface_manager.h"
+#include "third_party/abseil-cpp/absl/functional/overload.h"
 
 namespace viz {
 
@@ -47,7 +47,7 @@
 void FrameIntervalDecider::UpdateSettings(
     Settings settings,
     std::vector<std::unique_ptr<FrameIntervalMatcher>> matchers) {
-  std::visit(base::Overloaded(
+  std::visit(absl::Overload(
                  [](const std::monostate& monostate) {},
                  [](const FixedIntervalSettings& fixed_interval_settings) {
                    CHECK(!fixed_interval_settings.supported_intervals.empty());
@@ -105,7 +105,7 @@
   // If nothing matched, use the default.
   if (!match_result) {
     match_result = std::visit(
-        base::Overloaded(
+        absl::Overload(
             [](const std::monostate& monostate) -> Result {
               return FrameIntervalClass::kDefault;
             },
@@ -164,7 +164,7 @@
     return true;
   }
   return std::visit(
-      base::Overloaded(
+      absl::Overload(
           [&](FrameIntervalClass from_frame_interval_class) {
             if (!std::holds_alternative<FrameIntervalClass>(to.value())) {
               return true;
diff --git a/components/viz/service/display/frame_interval_matchers.cc b/components/viz/service/display/frame_interval_matchers.cc
index 58b7a0c..75b4f9a 100644
--- a/components/viz/service/display/frame_interval_matchers.cc
+++ b/components/viz/service/display/frame_interval_matchers.cc
@@ -8,11 +8,11 @@
 #include <utility>
 #include <variant>
 
-#include "base/functional/overloaded.h"
 #include "base/strings/stringprintf.h"
 #include "base/trace_event/typed_macros.h"
 #include "components/viz/common/quads/frame_interval_inputs.h"
 #include "media/filters/video_cadence_estimator.h"
+#include "third_party/abseil-cpp/absl/functional/overload.h"
 
 namespace viz {
 
@@ -57,7 +57,7 @@
   }
 
   base::TimeDelta interval = std::visit(
-      base::Overloaded(
+      absl::Overload(
           [&](const std::monostate& monostate) {
             // If no intervals settings are given, then just return the content
             // interval.
@@ -193,7 +193,7 @@
 // static
 std::string FrameIntervalMatcher::ResultToString(const Result& result) {
   return std::visit(
-      base::Overloaded(
+      absl::Overload(
           [](FrameIntervalClass frame_interval_class) -> std::string {
             switch (frame_interval_class) {
               case FrameIntervalClass::kBoost:
@@ -261,7 +261,7 @@
         (matcher_inputs.aggregated_frame_time - inputs.frame_time) <
             matcher_inputs.settings->ignore_frame_sink_timeout) {
       return std::visit(
-          base::Overloaded(
+          absl::Overload(
               [](const std::monostate& monostate) -> Result {
                 return FrameIntervalClass::kBoost;
               },
@@ -331,7 +331,7 @@
   }
 
   base::TimeDelta interval = std::visit(
-      base::Overloaded(
+      absl::Overload(
           [&](const std::monostate& monostate) { return min_interval.value(); },
           [&](const FixedIntervalSettings& fixed_interval_settings) {
             // Pick closest supported interval amongst discrete list.
@@ -387,7 +387,7 @@
         (matcher_inputs.aggregated_frame_time - inputs.frame_time) <
             matcher_inputs.settings->ignore_frame_sink_timeout) {
       return std::visit(
-          base::Overloaded(
+          absl::Overload(
               [](const std::monostate& monostate) -> Result {
                 return FrameIntervalClass::kBoost;
               },
diff --git a/components/viz/service/frame_sinks/compositor_frame_sink_impl.cc b/components/viz/service/frame_sinks/compositor_frame_sink_impl.cc
index cfa9e24..893f715 100644
--- a/components/viz/service/frame_sinks/compositor_frame_sink_impl.cc
+++ b/components/viz/service/frame_sinks/compositor_frame_sink_impl.cc
@@ -14,6 +14,7 @@
 #include "base/memory/raw_ref.h"
 #include "base/threading/platform_thread.h"
 #include "build/build_config.h"
+#include "components/viz/common/features.h"
 #include "components/viz/service/frame_sinks/frame_sink_bundle_impl.h"
 #include "components/viz/service/frame_sinks/frame_sink_manager_impl.h"
 #include "services/viz/public/mojom/compositing/layer_context.mojom.h"
@@ -97,7 +98,7 @@
     FrameSinkManagerImpl* frame_sink_manager,
     const FrameSinkId& frame_sink_id,
     std::optional<FrameSinkBundleId> bundle_id,
-    mojo::PendingReceiver<mojom::CompositorFrameSink> receiver,
+    mojo::PendingReceiver<mojom::CompositorFrameSink> interface_receiver,
     mojo::PendingRemote<mojom::CompositorFrameSinkClient> client)
     : compositor_frame_sink_client_(std::move(client)),
       proxying_client_(
@@ -106,16 +107,26 @@
                                                     frame_sink_id,
                                                     *bundle_id)
               : nullptr),
-      compositor_frame_sink_receiver_(this, std::move(receiver)),
+      compositor_frame_sink_receiver_(std::in_place_type<Receiver>, this),
       support_(std::make_unique<CompositorFrameSinkSupport>(
           proxying_client_ ? proxying_client_.get()
                            : compositor_frame_sink_client_.get(),
           frame_sink_manager,
           frame_sink_id,
           false /* is_root */)) {
-  compositor_frame_sink_receiver_.set_disconnect_handler(
-      base::BindOnce(&CompositorFrameSinkImpl::OnClientConnectionLost,
-                     base::Unretained(this)));
+  if (mojo::IsDirectReceiverSupported() &&
+      features::IsVizDirectCompositorThreadIpcNonRootEnabled()) {
+    compositor_frame_sink_receiver_.emplace<DirectReceiver>(
+        mojo::DirectReceiverKey{}, this);
+  }
+  std::visit(
+      [&](auto& receiver) {
+        receiver.Bind(std::move(interface_receiver));
+        receiver.set_disconnect_handler(
+            base::BindOnce(&CompositorFrameSinkImpl::OnClientConnectionLost,
+                           base::Unretained(this)));
+      },
+      compositor_frame_sink_receiver_);
   if (bundle_id.has_value()) {
     support_->SetBundle(*bundle_id);
   }
@@ -174,8 +185,12 @@
       CompositorFrameSinkSupport::GetSubmitResultAsString(result);
   DLOG(ERROR) << "SubmitCompositorFrame failed for " << local_surface_id
               << " because " << reason;
-  compositor_frame_sink_receiver_.ResetWithReason(static_cast<uint32_t>(result),
-                                                  reason);
+
+  std::visit(
+      [&](auto& receiver) {
+        receiver.ResetWithReason(static_cast<uint32_t>(result), reason);
+      },
+      compositor_frame_sink_receiver_);
 }
 
 void CompositorFrameSinkImpl::DidNotProduceFrame(
diff --git a/components/viz/service/frame_sinks/compositor_frame_sink_impl.h b/components/viz/service/frame_sinks/compositor_frame_sink_impl.h
index fa01b44..a019fcc 100644
--- a/components/viz/service/frame_sinks/compositor_frame_sink_impl.h
+++ b/components/viz/service/frame_sinks/compositor_frame_sink_impl.h
@@ -7,6 +7,7 @@
 
 #include <memory>
 #include <optional>
+#include <variant>
 #include <vector>
 
 #include "base/memory/read_only_shared_memory_region.h"
@@ -15,6 +16,7 @@
 #include "components/viz/common/surfaces/frame_sink_id.h"
 #include "components/viz/common/surfaces/local_surface_id.h"
 #include "components/viz/service/frame_sinks/compositor_frame_sink_support.h"
+#include "mojo/public/cpp/bindings/direct_receiver.h"
 #include "mojo/public/cpp/bindings/pending_receiver.h"
 #include "mojo/public/cpp/bindings/pending_remote.h"
 #include "mojo/public/cpp/bindings/receiver.h"
@@ -77,7 +79,9 @@
   mojo::Remote<mojom::CompositorFrameSinkClient> compositor_frame_sink_client_;
   std::unique_ptr<mojom::CompositorFrameSinkClient> proxying_client_;
 
-  mojo::Receiver<mojom::CompositorFrameSink> compositor_frame_sink_receiver_;
+  using Receiver = mojo::Receiver<mojom::CompositorFrameSink>;
+  using DirectReceiver = mojo::DirectReceiver<mojom::CompositorFrameSink>;
+  std::variant<Receiver, DirectReceiver> compositor_frame_sink_receiver_;
 
   // Must be destroyed before |compositor_frame_sink_client_|. This must never
   // change for the lifetime of CompositorFrameSinkImpl.
diff --git a/components/viz/service/frame_sinks/root_compositor_frame_sink_impl.cc b/components/viz/service/frame_sinks/root_compositor_frame_sink_impl.cc
index ab6be2d..e3c3ce2b 100644
--- a/components/viz/service/frame_sinks/root_compositor_frame_sink_impl.cc
+++ b/components/viz/service/frame_sinks/root_compositor_frame_sink_impl.cc
@@ -10,7 +10,6 @@
 #include <vector>
 
 #include "base/containers/flat_set.h"
-#include "base/functional/overloaded.h"
 #include "base/logging.h"
 #include "base/memory/ptr_util.h"
 #include "base/memory/raw_ptr.h"
@@ -29,6 +28,7 @@
 #include "components/viz/service/frame_sinks/frame_sink_manager_impl.h"
 #include "components/viz/service/hit_test/hit_test_aggregator.h"
 #include "services/viz/public/mojom/compositing/layer_context.mojom.h"
+#include "third_party/abseil-cpp/absl/functional/overload.h"
 #include "ui/base/ozone_buildflags.h"
 #include "ui/gfx/geometry/skia_conversions.h"
 
@@ -732,7 +732,7 @@
   base::TimeDelta interval;
   std::pair<base::TimeDelta, gfx::SurfaceControlFrameRateCompatibility>
       interval_and_compat = std::visit(
-          base::Overloaded(
+          absl::Overload(
               [this](FrameIntervalDecider::FrameIntervalClass
                          frame_interval_class) {
                 switch (frame_interval_class) {
@@ -768,7 +768,7 @@
   decided_display_frame_rate_compat_ = compat;
 #else
   base::TimeDelta interval = std::visit(
-      base::Overloaded(
+      absl::Overload(
           [](FrameIntervalDecider::FrameIntervalClass frame_interval_class) {
             switch (frame_interval_class) {
               case FrameIntervalDecider::FrameIntervalClass::kBoost:
diff --git a/components/webauthn/android/webauthn_cred_man_delegate.cc b/components/webauthn/android/webauthn_cred_man_delegate.cc
index e1eb0eb..34c1c430 100644
--- a/components/webauthn/android/webauthn_cred_man_delegate.cc
+++ b/components/webauthn/android/webauthn_cred_man_delegate.cc
@@ -36,6 +36,11 @@
     base::RepeatingCallback<void(bool)> full_assertion_request) {
   has_passkeys_ = has_passkeys ? kHasPasskeys : kNoPasskeys;
   show_cred_man_ui_callback_ = std::move(full_assertion_request);
+
+  std::vector<base::OnceClosure> notification_closures;
+  if (credentials_available_closure_) {
+    std::move(credentials_available_closure_).Run();
+  }
 }
 
 void WebAuthnCredManDelegate::OnCredManUiClosed(bool success) {
@@ -86,6 +91,20 @@
   std::move(filling_callback_).Run(username, password);
 }
 
+void WebAuthnCredManDelegate::RequestNotificationWhenCredentialsReady(
+    base::OnceClosure closure) {
+  if (has_passkeys_ != kNotReady) {
+    std::move(closure).Run();
+    return;
+  }
+  CHECK(!credentials_available_closure_);
+  credentials_available_closure_ = std::move(closure);
+}
+
+base::WeakPtr<WebAuthnCredManDelegate> WebAuthnCredManDelegate::AsWeakPtr() {
+  return weak_ptr_factory_.GetWeakPtr();
+}
+
 // static
 WebAuthnCredManDelegate::CredManEnabledMode
 WebAuthnCredManDelegate::CredManMode() {
diff --git a/components/webauthn/android/webauthn_cred_man_delegate.h b/components/webauthn/android/webauthn_cred_man_delegate.h
index b7c3ce5c..d6af6f3 100644
--- a/components/webauthn/android/webauthn_cred_man_delegate.h
+++ b/components/webauthn/android/webauthn_cred_man_delegate.h
@@ -9,6 +9,7 @@
 #include <string>
 
 #include "base/functional/callback.h"
+#include "base/memory/weak_ptr.h"
 #include "base/types/strong_alias.h"
 
 namespace content {
@@ -85,8 +86,17 @@
   virtual void FillUsernameAndPassword(const std::u16string& username,
                                        const std::u16string& password);
 
+  // Callers of this method will be notified via `closure` when the credential
+  // list from CredMan is available. `closure` can be invoked mmediately if the
+  // passkey list has already been received. This CHECKs if called twice
+  // without the first having resolved.
+  virtual void RequestNotificationWhenCredentialsReady(
+      base::OnceClosure closure);
+
   static CredManEnabledMode CredManMode();
 
+  virtual base::WeakPtr<WebAuthnCredManDelegate> AsWeakPtr();
+
 #if defined(UNIT_TEST)
   static void override_cred_man_support_for_testing(int support) {
     cred_man_support_ = support;
@@ -100,11 +110,16 @@
   base::OnceCallback<void(const std::u16string&, const std::u16string&)>
       filling_callback_;
 
+  // Callback awaiting notification of credentials being available.
+  base::OnceClosure credentials_available_closure_;
+
   // Trakcks whether the PasskeysArrivedAfterAutofillDisplay metric has been
   // recorded.
   bool passkeys_after_fill_recorded_ = false;
 
   static std::optional<int> cred_man_support_;
+
+  base::WeakPtrFactory<WebAuthnCredManDelegate> weak_ptr_factory_{this};
 };
 
 }  // namespace webauthn
diff --git a/content/browser/BUILD.gn b/content/browser/BUILD.gn
index 873c2ce..9379bc7 100644
--- a/content/browser/BUILD.gn
+++ b/content/browser/BUILD.gn
@@ -176,7 +176,7 @@
     "//content/browser/process_internals:mojo_bindings",
     "//content/browser/resources:resources",
     "//content/browser/resources/media:resources",
-    "//content/browser/tracing/trace_report:mojo_bindings",
+    "//content/browser/tracing/traces_internals:mojo_bindings",
     "//content/browser/webrtc/resources",
     "//content/common",
     "//content/common:buildflags",
@@ -2302,11 +2302,11 @@
     "tracing/memory_instrumentation_util.h",
     "tracing/startup_tracing_controller.cc",
     "tracing/startup_tracing_controller.h",
-    "tracing/trace_report/trace_report_database.cc",
-    "tracing/trace_report/trace_report_database.h",
-    "tracing/trace_report/trace_report_handler.cc",
-    "tracing/trace_report/trace_report_handler.h",
-    "tracing/trace_report/trace_upload_list.h",
+    "tracing/trace_report_database.cc",
+    "tracing/trace_report_database.h",
+    "tracing/trace_upload_list.h",
+    "tracing/traces_internals/traces_internals_handler.cc",
+    "tracing/traces_internals/traces_internals_handler.h",
     "tracing/tracing_controller_impl.cc",
     "tracing/tracing_controller_impl.h",
     "tracing/tracing_controller_impl_data_endpoint.cc",
@@ -3583,8 +3583,8 @@
   if (!is_android && !is_ios && !is_fuchsia) {
     sources += [
       # Trace report internal UI is not adapted for mobile use.
-      "tracing/trace_report/trace_report_internals_ui.cc",
-      "tracing/trace_report/trace_report_internals_ui.h",
+      "tracing/traces_internals/traces_internals_ui.cc",
+      "tracing/traces_internals/traces_internals_ui.h",
     ]
   }
 
diff --git a/content/browser/browser_interface_binders.cc b/content/browser/browser_interface_binders.cc
index eda13ba3..222e12d1 100644
--- a/content/browser/browser_interface_binders.cc
+++ b/content/browser/browser_interface_binders.cc
@@ -64,8 +64,8 @@
 #include "content/browser/shared_storage/shared_storage_worklet_host.h"
 #include "content/browser/speech/speech_recognition_dispatcher_host.h"
 #include "content/browser/storage_access/storage_access_handle.h"
-#include "content/browser/tracing/trace_report/trace_report.mojom.h"
-#include "content/browser/tracing/trace_report/trace_report_internals_ui.h"
+#include "content/browser/tracing/traces_internals/traces_internals.mojom.h"
+#include "content/browser/tracing/traces_internals/traces_internals_ui.h"
 #include "content/browser/wake_lock/wake_lock_service_impl.h"
 #include "content/browser/web_contents/file_chooser_impl.h"
 #include "content/browser/web_contents/web_contents_impl.h"
@@ -1252,8 +1252,8 @@
                                          QuotaInternalsUI>(map);
 #if !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS) && !BUILDFLAG(IS_FUCHSIA)
   RegisterWebUIControllerInterfaceBinder<
-      trace_report::mojom::TraceReportHandlerFactory, TraceReportInternalsUI>(
-      map);
+      traces_internals::mojom::TracesInternalsHandlerFactory,
+      TracesInternalsUI>(map);
 #endif
 #if BUILDFLAG(ENABLE_VR)
   RegisterWebUIControllerInterfaceBinder<webxr::mojom::WebXrInternalsHandler,
diff --git a/content/browser/devtools/protocol/page_handler.cc b/content/browser/devtools/protocol/page_handler.cc
index 5273cd66..1fc8a5cc 100644
--- a/content/browser/devtools/protocol/page_handler.cc
+++ b/content/browser/devtools/protocol/page_handler.cc
@@ -2089,7 +2089,7 @@
           return Page::BackForwardCacheNotRestoredReasonEnum::
               EmbedderOfflinePage;
         case back_forward_cache::DisabledReasonId::
-            kChromePasswordManagerClient_BindCredentialManager:
+            kContentCredentialManager_BindCredentialManager:
           return Page::BackForwardCacheNotRestoredReasonEnum::
               EmbedderChromePasswordManagerClientBindCredentialManager;
         case back_forward_cache::DisabledReasonId::kPermissionRequestManager:
diff --git a/content/browser/resources/traces_internals/BUILD.gn b/content/browser/resources/traces_internals/BUILD.gn
index 00a4e8e7..6143af09 100644
--- a/content/browser/resources/traces_internals/BUILD.gn
+++ b/content/browser/resources/traces_internals/BUILD.gn
@@ -52,8 +52,8 @@
   grd_prefix = "traces_internals"
 
   static_files = [
-    "trace_report_internals.css",
-    "trace_report_internals.html",
+    "traces_internals.css",
+    "traces_internals.html",
   ]
 
   ts_files = [
@@ -61,7 +61,7 @@
     "app.ts",
     "trace_report.html.ts",
     "trace_report.ts",
-    "trace_report_browser_proxy.ts",
+    "traces_browser_proxy.ts",
     "trace_report_list.html.ts",
     "trace_report_list.ts",
     "tracing_scenarios_config.html.ts",
@@ -94,10 +94,10 @@
 
   mojo_files_deps = [
     ":config_proto_gen",
-    "//content/browser/tracing/trace_report:mojo_bindings_ts__generator",
+    "//content/browser/tracing/traces_internals:mojo_bindings_ts__generator",
   ]
   mojo_files = [
-    "$root_gen_dir/content/browser/tracing/trace_report/trace_report.mojom-webui.ts",
+    "$root_gen_dir/content/browser/tracing/traces_internals/traces_internals.mojom-webui.ts",
     "$proto_ts_root/third_party/perfetto/protos/perfetto/config/perfetto_config.ts",
   ]
 
diff --git a/content/browser/resources/traces_internals/app.html.ts b/content/browser/resources/traces_internals/app.html.ts
index 80d4b84..9644f26 100644
--- a/content/browser/resources/traces_internals/app.html.ts
+++ b/content/browser/resources/traces_internals/app.html.ts
@@ -4,9 +4,9 @@
 
 import {html} from '//resources/lit/v3_0/lit.rollup.js';
 
-import type {TraceReportAppElement} from './app.js';
+import type {TracesAppElement} from './app.js';
 
-export function getHtml(this: TraceReportAppElement) {
+export function getHtml(this: TracesAppElement) {
   // clang-format off
   return html`
   <cr-tabs .tabNames="${this.tabNames_}" .selected="${this.selected_}"
diff --git a/content/browser/resources/traces_internals/app.ts b/content/browser/resources/traces_internals/app.ts
index 3a931bb..b4244e9 100644
--- a/content/browser/resources/traces_internals/app.ts
+++ b/content/browser/resources/traces_internals/app.ts
@@ -19,9 +19,9 @@
   path: string;
 }
 
-export class TraceReportAppElement extends CrLitElement {
+export class TracesAppElement extends CrLitElement {
   static get is() {
-    return 'trace-report-app';
+    return 'traces-app';
   }
 
   override render() {
@@ -98,8 +98,8 @@
 
 declare global {
   interface HTMLElementTagNameMap {
-    'trace-report-app': TraceReportAppElement;
+    'traces-app': TracesAppElement;
   }
 }
 
-customElements.define(TraceReportAppElement.is, TraceReportAppElement);
+customElements.define(TracesAppElement.is, TracesAppElement);
diff --git a/content/browser/resources/traces_internals/trace_recorder.ts b/content/browser/resources/traces_internals/trace_recorder.ts
index ec037bb..4734e624 100644
--- a/content/browser/resources/traces_internals/trace_recorder.ts
+++ b/content/browser/resources/traces_internals/trace_recorder.ts
@@ -13,7 +13,7 @@
 
 import {getCss} from './trace_recorder.css.js';
 import {getHtml} from './trace_recorder.html.js';
-import {TraceReportBrowserProxy} from './trace_report_browser_proxy.js';
+import {TracesBrowserProxy} from './traces_browser_proxy.js';
 
 enum TracingState {
   IDLE = 'Idle',
@@ -49,8 +49,7 @@
     };
   }
 
-  private browserProxy_: TraceReportBrowserProxy =
-      TraceReportBrowserProxy.getInstance();
+  private browserProxy_: TracesBrowserProxy = TracesBrowserProxy.getInstance();
   // Bound method for router events
   private boundLoadConfigFromUrl_ = this.loadConfigFromUrl_.bind(this);
   // Bound method for onTraceComplete listener
diff --git a/content/browser/resources/traces_internals/trace_report.html.ts b/content/browser/resources/traces_internals/trace_report.html.ts
index 3e50302..236bf30 100644
--- a/content/browser/resources/traces_internals/trace_report.html.ts
+++ b/content/browser/resources/traces_internals/trace_report.html.ts
@@ -5,7 +5,7 @@
 import {html, nothing} from '//resources/lit/v3_0/lit.rollup.js';
 
 import type {TraceReportElement} from './trace_report.js';
-import {ReportUploadState} from './trace_report.mojom-webui.js';
+import {ReportUploadState} from './traces_internals.mojom-webui.js';
 
 export function getHtml(this: TraceReportElement) {
   // clang-format off
diff --git a/content/browser/resources/traces_internals/trace_report.ts b/content/browser/resources/traces_internals/trace_report.ts
index c10e33a..33e3966 100644
--- a/content/browser/resources/traces_internals/trace_report.ts
+++ b/content/browser/resources/traces_internals/trace_report.ts
@@ -13,10 +13,10 @@
 
 import {getCss} from './trace_report.css.js';
 import {getHtml} from './trace_report.html.js';
-import type {ClientTraceReport} from './trace_report.mojom-webui.js';
-import {ReportUploadState, SkipUploadReason} from './trace_report.mojom-webui.js';
-import {TraceReportBrowserProxy} from './trace_report_browser_proxy.js';
 import {Notification, NotificationType} from './trace_report_list.js';
+import {TracesBrowserProxy} from './traces_browser_proxy.js';
+import type {ClientTraceReport} from './traces_internals.mojom-webui.js';
+import {ReportUploadState, SkipUploadReason} from './traces_internals.mojom-webui.js';
 
 // Create the temporary element here to hold the data to download the trace
 // since it is only obtained after downloadData_ is called. This way we can
@@ -50,8 +50,8 @@
     };
   }
 
-  private traceReportProxy_: TraceReportBrowserProxy =
-      TraceReportBrowserProxy.getInstance();
+  private traceReportProxy_: TracesBrowserProxy =
+      TracesBrowserProxy.getInstance();
 
   protected accessor trace: ClientTraceReport = {
     // Dummy ClientTraceReport
diff --git a/content/browser/resources/traces_internals/trace_report_browser_proxy.ts b/content/browser/resources/traces_internals/trace_report_browser_proxy.ts
deleted file mode 100644
index cd64df1..0000000
--- a/content/browser/resources/traces_internals/trace_report_browser_proxy.ts
+++ /dev/null
@@ -1,28 +0,0 @@
-/* Copyright 2023 The Chromium Authors
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file. */
-
-import {PageCallbackRouter, PageHandlerRemote, TraceReportHandlerFactory} from './trace_report.mojom-webui.js';
-
-/** Holds Mojo interfaces for communication with the browser process. */
-export class TraceReportBrowserProxy {
-  callbackRouter: PageCallbackRouter = new PageCallbackRouter();
-  handler: PageHandlerRemote = new PageHandlerRemote();
-
-  constructor() {
-    const factory = TraceReportHandlerFactory.getRemote();
-    factory.createPageHandler(
-        this.callbackRouter.$.bindNewPipeAndPassRemote(),
-        this.handler.$.bindNewPipeAndPassReceiver());
-  }
-
-  static getInstance(): TraceReportBrowserProxy {
-    return instance || (instance = new TraceReportBrowserProxy());
-  }
-
-  static setInstance(obj: TraceReportBrowserProxy): void {
-    instance = obj;
-  }
-}
-
-let instance: TraceReportBrowserProxy|null = null;
diff --git a/content/browser/resources/traces_internals/trace_report_list.html.ts b/content/browser/resources/traces_internals/trace_report_list.html.ts
index a2fe51b..737f5ab 100644
--- a/content/browser/resources/traces_internals/trace_report_list.html.ts
+++ b/content/browser/resources/traces_internals/trace_report_list.html.ts
@@ -4,8 +4,8 @@
 
 import {html, nothing} from 'chrome://resources/lit/v3_0/lit.rollup.js';
 
-import type {ClientTraceReport} from './trace_report.mojom-webui.js';
 import type {TraceReportListElement} from './trace_report_list.js';
+import type {ClientTraceReport} from './traces_internals.mojom-webui.js';
 
 function getReportHtml(this: TraceReportListElement) {
   // clang-format off
diff --git a/content/browser/resources/traces_internals/trace_report_list.ts b/content/browser/resources/traces_internals/trace_report_list.ts
index a0e077a..6badf8a 100644
--- a/content/browser/resources/traces_internals/trace_report_list.ts
+++ b/content/browser/resources/traces_internals/trace_report_list.ts
@@ -16,11 +16,11 @@
 import {assert} from '//resources/js/assert.js';
 import {CrLitElement} from '//resources/lit/v3_0/lit.rollup.js';
 
-import type {ClientTraceReport} from './trace_report.mojom-webui.js';
-import {TraceReportBrowserProxy} from './trace_report_browser_proxy.js';
 import {getCss} from './trace_report_list.css.js';
 import {getHtml} from './trace_report_list.html.js';
 // clang-format on
+import {TracesBrowserProxy} from './traces_browser_proxy.js';
+import type {ClientTraceReport} from './traces_internals.mojom-webui.js';
 
 export enum NotificationType {
   UPDATE = 'Update',
@@ -65,8 +65,8 @@
     };
   }
 
-  private traceReportProxy_: TraceReportBrowserProxy =
-      TraceReportBrowserProxy.getInstance();
+  private traceReportProxy_: TracesBrowserProxy =
+      TracesBrowserProxy.getInstance();
   protected accessor traces_: ClientTraceReport[] = [];
   protected accessor isLoading_: boolean = false;
   protected notification_?: Readonly<Notification>;
diff --git a/content/browser/resources/traces_internals/traces_browser_proxy.ts b/content/browser/resources/traces_internals/traces_browser_proxy.ts
new file mode 100644
index 0000000..a79d119
--- /dev/null
+++ b/content/browser/resources/traces_internals/traces_browser_proxy.ts
@@ -0,0 +1,28 @@
+/* Copyright 2023 The Chromium Authors
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file. */
+
+import {PageCallbackRouter, PageHandlerRemote, TracesInternalsHandlerFactory} from './traces_internals.mojom-webui.js';
+
+/** Holds Mojo interfaces for communication with the browser process. */
+export class TracesBrowserProxy {
+  callbackRouter: PageCallbackRouter = new PageCallbackRouter();
+  handler: PageHandlerRemote = new PageHandlerRemote();
+
+  constructor() {
+    const factory = TracesInternalsHandlerFactory.getRemote();
+    factory.createPageHandler(
+        this.callbackRouter.$.bindNewPipeAndPassRemote(),
+        this.handler.$.bindNewPipeAndPassReceiver());
+  }
+
+  static getInstance(): TracesBrowserProxy {
+    return instance || (instance = new TracesBrowserProxy());
+  }
+
+  static setInstance(obj: TracesBrowserProxy): void {
+    instance = obj;
+  }
+}
+
+let instance: TracesBrowserProxy|null = null;
diff --git a/content/browser/resources/traces_internals/trace_report_internals.css b/content/browser/resources/traces_internals/traces_internals.css
similarity index 100%
rename from content/browser/resources/traces_internals/trace_report_internals.css
rename to content/browser/resources/traces_internals/traces_internals.css
diff --git a/content/browser/resources/traces_internals/trace_report_internals.html b/content/browser/resources/traces_internals/traces_internals.html
similarity index 76%
rename from content/browser/resources/traces_internals/trace_report_internals.html
rename to content/browser/resources/traces_internals/traces_internals.html
index bf085c4..e00e5bd 100644
--- a/content/browser/resources/traces_internals/trace_report_internals.html
+++ b/content/browser/resources/traces_internals/traces_internals.html
@@ -5,10 +5,10 @@
   <title>Local Traces Page</title>
   <link rel="stylesheet" href="chrome://resources/css/md_colors.css">
   <link rel="stylesheet" href="chrome://resources/css/text_defaults.css">
-  <link rel="stylesheet" href="trace_report_internals.css">
+  <link rel="stylesheet" href="traces_internals.css">
   <script type="module" src="app.js"></script>
 </head>
 <body>
-  <trace-report-app></trace-report-app>
+  <traces-app></traces-app>
 </body>
 </html>
\ No newline at end of file
diff --git a/content/browser/resources/traces_internals/tracing_scenario.ts b/content/browser/resources/traces_internals/tracing_scenario.ts
index 5d3f5095..0c0e956 100644
--- a/content/browser/resources/traces_internals/tracing_scenario.ts
+++ b/content/browser/resources/traces_internals/tracing_scenario.ts
@@ -6,8 +6,8 @@
 
 import {CrLitElement} from '//resources/lit/v3_0/lit.rollup.js';
 
-import type {Scenario} from './trace_report.mojom-webui.js';
-import {TracingScenarioState} from './trace_report.mojom-webui.js';
+import type {Scenario} from './traces_internals.mojom-webui.js';
+import {TracingScenarioState} from './traces_internals.mojom-webui.js';
 import {getCss} from './tracing_scenario.css.js';
 import {getHtml} from './tracing_scenario.html.js';
 
diff --git a/content/browser/resources/traces_internals/tracing_scenarios_config.ts b/content/browser/resources/traces_internals/tracing_scenarios_config.ts
index ad2f742..e9c7779a 100644
--- a/content/browser/resources/traces_internals/tracing_scenarios_config.ts
+++ b/content/browser/resources/traces_internals/tracing_scenarios_config.ts
@@ -14,8 +14,8 @@
 import {CrLitElement} from '//resources/lit/v3_0/lit.rollup.js';
 import type {BigBuffer} from '//resources/mojo/mojo/public/mojom/base/big_buffer.mojom-webui.js';
 
-import type {Scenario} from './trace_report.mojom-webui.js';
-import {TraceReportBrowserProxy} from './trace_report_browser_proxy.js';
+import {TracesBrowserProxy} from './traces_browser_proxy.js';
+import type {Scenario} from './traces_internals.mojom-webui.js';
 import {getCss} from './tracing_scenarios_config.css.js';
 import {getHtml} from './tracing_scenarios_config.html.js';
 
@@ -53,8 +53,8 @@
     };
   }
 
-  private traceReportProxy_: TraceReportBrowserProxy =
-      TraceReportBrowserProxy.getInstance();
+  private traceReportProxy_: TracesBrowserProxy =
+      TracesBrowserProxy.getInstance();
 
   private refreshIntervalId_: number = 0;
 
diff --git a/content/browser/tracing/background_tracing_manager_browsertest.cc b/content/browser/tracing/background_tracing_manager_browsertest.cc
index e1aaf505..85cb069 100644
--- a/content/browser/tracing/background_tracing_manager_browsertest.cc
+++ b/content/browser/tracing/background_tracing_manager_browsertest.cc
@@ -296,8 +296,8 @@
   {
     auto all_scenarios =
         BackgroundTracingManagerImpl::GetInstance().GetAllScenarios();
-    std::vector<trace_report::mojom::ScenarioPtr> expected;
-    auto scenario = trace_report::mojom::Scenario::New();
+    std::vector<traces_internals::mojom::ScenarioPtr> expected;
+    auto scenario = traces_internals::mojom::Scenario::New();
     scenario->scenario_name = "test_scenario";
     scenario->is_local_scenario = true;
     scenario->is_enabled = false;
diff --git a/content/browser/tracing/background_tracing_manager_impl.cc b/content/browser/tracing/background_tracing_manager_impl.cc
index 63188e5..9746b73 100644
--- a/content/browser/tracing/background_tracing_manager_impl.cc
+++ b/content/browser/tracing/background_tracing_manager_impl.cc
@@ -31,8 +31,8 @@
 #include "components/variations/hashing.h"
 #include "content/browser/tracing/background_tracing_agent_client_impl.h"
 #include "content/browser/tracing/background_tracing_rule.h"
-#include "content/browser/tracing/trace_report/trace_report_database.h"
-#include "content/browser/tracing/trace_report/trace_upload_list.h"
+#include "content/browser/tracing/trace_report_database.h"
+#include "content/browser/tracing/trace_upload_list.h"
 #include "content/browser/tracing/tracing_controller_impl.h"
 #include "content/browser/tracing/triggers_data_source.h"
 #include "content/common/child_process.mojom.h"
@@ -621,11 +621,11 @@
   return added_scenarios;
 }
 
-std::vector<trace_report::mojom::ScenarioPtr>
+std::vector<traces_internals::mojom::ScenarioPtr>
 BackgroundTracingManagerImpl::GetAllScenarios() const {
-  std::vector<trace_report::mojom::ScenarioPtr> result;
+  std::vector<traces_internals::mojom::ScenarioPtr> result;
   auto toMojoScenario = [this](TracingScenario* scenario) {
-    auto new_scenario = trace_report::mojom::Scenario::New();
+    auto new_scenario = traces_internals::mojom::Scenario::New();
     new_scenario->scenario_name = scenario->scenario_name();
     new_scenario->description = scenario->description();
     new_scenario->is_local_scenario = scenario->is_local_scenario();
diff --git a/content/browser/tracing/background_tracing_manager_impl.h b/content/browser/tracing/background_tracing_manager_impl.h
index b7f054e..ba05ea0 100644
--- a/content/browser/tracing/background_tracing_manager_impl.h
+++ b/content/browser/tracing/background_tracing_manager_impl.h
@@ -21,9 +21,9 @@
 #include "base/timer/timer.h"
 #include "base/token.h"
 #include "base/trace_event/named_trigger.h"
-#include "content/browser/tracing/trace_report/trace_report.mojom.h"
-#include "content/browser/tracing/trace_report/trace_report_database.h"
-#include "content/browser/tracing/trace_report/trace_upload_list.h"
+#include "content/browser/tracing/trace_report_database.h"
+#include "content/browser/tracing/trace_upload_list.h"
+#include "content/browser/tracing/traces_internals/traces_internals.mojom.h"
 #include "content/browser/tracing/tracing_scenario.h"
 #include "content/common/content_export.h"
 #include "content/public/browser/background_tracing_manager.h"
@@ -146,8 +146,8 @@
 
   // Returns the list of scenario hashes and names that were saved,
   // whether or not enabled.
-  CONTENT_EXPORT std::vector<trace_report::mojom::ScenarioPtr> GetAllScenarios()
-      const;
+  CONTENT_EXPORT std::vector<traces_internals::mojom::ScenarioPtr>
+  GetAllScenarios() const;
 
   std::vector<std::string> OverwritePresetScenarios(
       const perfetto::protos::gen::ChromeFieldTracingConfig& config,
diff --git a/content/browser/tracing/trace_report/trace_report_internals_ui.h b/content/browser/tracing/trace_report/trace_report_internals_ui.h
deleted file mode 100644
index 0a0a724d..0000000
--- a/content/browser/tracing/trace_report/trace_report_internals_ui.h
+++ /dev/null
@@ -1,68 +0,0 @@
-// Copyright 2023 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef CONTENT_BROWSER_TRACING_TRACE_REPORT_TRACE_REPORT_INTERNALS_UI_H_
-#define CONTENT_BROWSER_TRACING_TRACE_REPORT_TRACE_REPORT_INTERNALS_UI_H_
-
-#include "content/browser/tracing/trace_report/trace_report.mojom.h"
-#include "content/public/browser/internal_webui_config.h"
-#include "content/public/browser/web_ui_controller.h"
-#include "content/public/common/url_constants.h"
-
-namespace content {
-
-class RenderFrameHost;
-class TraceReportHandler;
-class TraceReportInternalsUI;
-
-// WebUIConfig for the chrome://traces page.
-class TraceReportInternalsUIConfig
-    : public DefaultInternalWebUIConfig<TraceReportInternalsUI> {
- public:
-  TraceReportInternalsUIConfig()
-      : DefaultInternalWebUIConfig(kChromeUITracesInternalsHost) {}
-};
-
-// Temporary WebUIConfig to also register legacy chrome://traces-internals URL.
-class TraceReportInternalsLegacyUIConfig
-    : public DefaultInternalWebUIConfig<TraceReportInternalsUI> {
- public:
-  TraceReportInternalsLegacyUIConfig()
-      : DefaultInternalWebUIConfig("traces-internals") {}
-};
-
-// WebUIController for the chrome://traces page.
-class CONTENT_EXPORT TraceReportInternalsUI
-    : public WebUIController,
-      public trace_report::mojom::TraceReportHandlerFactory {
- public:
-  explicit TraceReportInternalsUI(content::WebUI* web_ui, const GURL& url);
-  ~TraceReportInternalsUI() override;
-
-  TraceReportInternalsUI(const TraceReportInternalsUI&) = delete;
-  TraceReportInternalsUI& operator=(const TraceReportInternalsUI&) = delete;
-
-  void BindInterface(
-      mojo::PendingReceiver<trace_report::mojom::TraceReportHandlerFactory>
-          receiver);
-
-  // WebUIController:
-  void WebUIRenderFrameCreated(RenderFrameHost* rfh) override;
-
- private:
-  // trace_report::mojom::PageHandlerFactory:
-  void CreatePageHandler(mojo::PendingRemote<trace_report::mojom::Page> page,
-                         mojo::PendingReceiver<trace_report::mojom::PageHandler>
-                             receiver) override;
-
-  std::unique_ptr<TraceReportHandler> ui_handler_;
-  mojo::Receiver<trace_report::mojom::TraceReportHandlerFactory>
-      page_factory_receiver_{this};
-
-  WEB_UI_CONTROLLER_TYPE_DECL();
-};
-
-}  // namespace content
-
-#endif  // CONTENT_BROWSER_TRACING_TRACE_REPORT_TRACE_REPORT_INTERNALS_UI_H_
diff --git a/content/browser/tracing/trace_report/trace_report_database.cc b/content/browser/tracing/trace_report_database.cc
similarity index 99%
rename from content/browser/tracing/trace_report/trace_report_database.cc
rename to content/browser/tracing/trace_report_database.cc
index b766f16..61b5ae8e 100644
--- a/content/browser/tracing/trace_report/trace_report_database.cc
+++ b/content/browser/tracing/trace_report_database.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "content/browser/tracing/trace_report/trace_report_database.h"
+#include "content/browser/tracing/trace_report_database.h"
 
 #include <optional>
 #include <string>
diff --git a/content/browser/tracing/trace_report/trace_report_database.h b/content/browser/tracing/trace_report_database.h
similarity index 96%
rename from content/browser/tracing/trace_report/trace_report_database.h
rename to content/browser/tracing/trace_report_database.h
index 017fbb3..8202a75 100644
--- a/content/browser/tracing/trace_report/trace_report_database.h
+++ b/content/browser/tracing/trace_report_database.h
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef CONTENT_BROWSER_TRACING_TRACE_REPORT_TRACE_REPORT_DATABASE_H_
-#define CONTENT_BROWSER_TRACING_TRACE_REPORT_TRACE_REPORT_DATABASE_H_
+#ifndef CONTENT_BROWSER_TRACING_TRACE_REPORT_DATABASE_H_
+#define CONTENT_BROWSER_TRACING_TRACE_REPORT_DATABASE_H_
 
 #include <optional>
 #include <string>
@@ -186,4 +186,4 @@
 
 }  // namespace content
 
-#endif  // CONTENT_BROWSER_TRACING_TRACE_REPORT_TRACE_REPORT_DATABASE_H_
+#endif  // CONTENT_BROWSER_TRACING_TRACE_REPORT_DATABASE_H_
diff --git a/content/browser/tracing/trace_report/trace_report_database_unittest.cc b/content/browser/tracing/trace_report_database_unittest.cc
similarity index 99%
rename from content/browser/tracing/trace_report/trace_report_database_unittest.cc
rename to content/browser/tracing/trace_report_database_unittest.cc
index f9c6c0e51..2deba1d7 100644
--- a/content/browser/tracing/trace_report/trace_report_database_unittest.cc
+++ b/content/browser/tracing/trace_report_database_unittest.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "content/browser/tracing/trace_report/trace_report_database.h"
+#include "content/browser/tracing/trace_report_database.h"
 
 #include <optional>
 #include <string>
diff --git a/content/browser/tracing/trace_report/trace_upload_list.h b/content/browser/tracing/trace_upload_list.h
similarity index 83%
rename from content/browser/tracing/trace_report/trace_upload_list.h
rename to content/browser/tracing/trace_upload_list.h
index 7430862..699ef3c 100644
--- a/content/browser/tracing/trace_report/trace_upload_list.h
+++ b/content/browser/tracing/trace_upload_list.h
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef CONTENT_BROWSER_TRACING_TRACE_REPORT_TRACE_UPLOAD_LIST_H_
-#define CONTENT_BROWSER_TRACING_TRACE_REPORT_TRACE_UPLOAD_LIST_H_
+#ifndef CONTENT_BROWSER_TRACING_TRACE_UPLOAD_LIST_H_
+#define CONTENT_BROWSER_TRACING_TRACE_UPLOAD_LIST_H_
 
 #include <memory>
 #include <optional>
@@ -13,7 +13,7 @@
 #include "base/functional/callback.h"
 #include "base/time/time.h"
 #include "base/token.h"
-#include "content/browser/tracing/trace_report/trace_report_database.h"
+#include "content/browser/tracing/trace_report_database.h"
 #include "content/common/content_export.h"
 
 namespace content {
@@ -46,4 +46,4 @@
 
 }  // namespace content
 
-#endif  // CONTENT_BROWSER_TRACING_TRACE_REPORT_TRACE_UPLOAD_LIST_H_
+#endif  // CONTENT_BROWSER_TRACING_TRACE_UPLOAD_LIST_H_
diff --git a/content/browser/tracing/trace_report/BUILD.gn b/content/browser/tracing/traces_internals/BUILD.gn
similarity index 61%
rename from content/browser/tracing/trace_report/BUILD.gn
rename to content/browser/tracing/traces_internals/BUILD.gn
index afc3cea1..18ca16eb 100644
--- a/content/browser/tracing/trace_report/BUILD.gn
+++ b/content/browser/tracing/traces_internals/BUILD.gn
@@ -5,7 +5,7 @@
 import("//mojo/public/tools/bindings/mojom.gni")
 
 mojom("mojo_bindings") {
-  sources = [ "trace_report.mojom" ]
+  sources = [ "traces_internals.mojom" ]
   public_deps = [ "//mojo/public/mojom/base" ]
   webui_module_path = "/"
 
@@ -17,26 +17,24 @@
     {
       types = [
         {
-          mojom = "trace_report.mojom.ReportUploadState"
+          mojom = "traces_internals.mojom.ReportUploadState"
           cpp = "::content::ReportUploadState"
         },
         {
-          mojom = "trace_report.mojom.SkipUploadReason"
+          mojom = "traces_internals.mojom.SkipUploadReason"
           cpp = "::content::SkipUploadReason"
         },
         {
-          mojom = "trace_report.mojom.TracingScenarioState"
+          mojom = "traces_internals.mojom.TracingScenarioState"
           cpp = "::content::TracingScenario::State"
         },
       ]
       traits_headers = [
-        "//content/browser/tracing/trace_report/trace_report_mojom_traits.h",
-        "//content/browser/tracing/trace_report/trace_report_database.h",
+        "//content/browser/tracing/traces_internals/traces_internals_mojom_traits.h",
+        "//content/browser/tracing/trace_report_database.h",
         "//content/browser/tracing/tracing_scenario.h",
       ]
-      traits_sources = [
-        "//content/browser/tracing/trace_report/trace_report_mojom_traits.cc",
-      ]
+      traits_sources = [ "//content/browser/tracing/traces_internals/traces_internals_mojom_traits.cc" ]
       traits_deps = [ "//sql:sql_name_variants" ]
     },
   ]
diff --git a/content/browser/tracing/trace_report/OWNERS b/content/browser/tracing/traces_internals/OWNERS
similarity index 100%
rename from content/browser/tracing/trace_report/OWNERS
rename to content/browser/tracing/traces_internals/OWNERS
diff --git a/content/browser/tracing/trace_report/trace_report.mojom b/content/browser/tracing/traces_internals/traces_internals.mojom
similarity index 96%
rename from content/browser/tracing/trace_report/trace_report.mojom
rename to content/browser/tracing/traces_internals/traces_internals.mojom
index 352f564e..c791ac6 100644
--- a/content/browser/tracing/trace_report/trace_report.mojom
+++ b/content/browser/tracing/traces_internals/traces_internals.mojom
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-module trace_report.mojom;
+module traces_internals.mojom;
 
 import "mojo/public/mojom/base/big_buffer.mojom";
 import "mojo/public/mojom/base/time.mojom";
@@ -11,7 +11,7 @@
 import "url/mojom/url.mojom";
 
 // Keep in sync with ReportUploadState from
-// content/browser/tracing/trace_report/trace_report_database.h
+// content/browser/tracing/trace_report_database.h
 enum ReportUploadState {
   kNotUploaded = 0,
   kPending = 1,
@@ -20,7 +20,7 @@
 };
 
 // Keep in sync with SkipUploadReason from
-// content/browser/tracing/trace_report/trace_report_database.h
+// content/browser/tracing/trace_report_database.h
 enum SkipUploadReason {
   kNoSkip = 0,
   kSizeLimitExceeded = 1,
@@ -88,7 +88,7 @@
 };
 
 // Used by the WebUI page to bootstrap bidirectional communication.
-interface TraceReportHandlerFactory {
+interface TracesInternalsHandlerFactory {
   // The WebUI calls this method when the page is first initialized.
   CreatePageHandler(pending_remote<Page> page,
                     pending_receiver<PageHandler> handler);
diff --git a/content/browser/tracing/trace_report/trace_report_handler.cc b/content/browser/tracing/traces_internals/traces_internals_handler.cc
similarity index 80%
rename from content/browser/tracing/trace_report/trace_report_handler.cc
rename to content/browser/tracing/traces_internals/traces_internals_handler.cc
index 35ed403..ae57abb 100644
--- a/content/browser/tracing/trace_report/trace_report_handler.cc
+++ b/content/browser/tracing/traces_internals/traces_internals_handler.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "content/browser/tracing/trace_report/trace_report_handler.h"
+#include "content/browser/tracing/traces_internals/traces_internals_handler.h"
 
 #include <optional>
 #include <utility>
@@ -13,8 +13,8 @@
 #include "components/tracing/common/background_tracing_state_manager.h"
 #include "components/tracing/common/tracing_scenarios_config.h"
 #include "content/browser/tracing/background_tracing_manager_impl.h"
-#include "content/browser/tracing/trace_report/trace_report_database.h"
-#include "content/browser/tracing/trace_report/trace_upload_list.h"
+#include "content/browser/tracing/trace_report_database.h"
+#include "content/browser/tracing/trace_upload_list.h"
 #include "content/public/browser/background_tracing_manager.h"
 #include "content/public/browser/tracing_delegate.h"
 #include "mojo/public/cpp/base/big_buffer.h"
@@ -101,9 +101,9 @@
 
 }  // namespace
 
-TraceReportHandler::TraceReportHandler(
-    mojo::PendingReceiver<trace_report::mojom::PageHandler> receiver,
-    mojo::PendingRemote<trace_report::mojom::Page> page)
+TracesInternalsHandler::TracesInternalsHandler(
+    mojo::PendingReceiver<traces_internals::mojom::PageHandler> receiver,
+    mojo::PendingRemote<traces_internals::mojom::Page> page)
     : task_runner_(base::SequencedTaskRunner::GetCurrentDefault()),
       receiver_(this, std::move(receiver)),
       page_(std::move(page)),
@@ -115,9 +115,9 @@
   MaybeSetupPresetTracingFromFieldTrial();
 }
 
-TraceReportHandler::TraceReportHandler(
-    mojo::PendingReceiver<trace_report::mojom::PageHandler> receiver,
-    mojo::PendingRemote<trace_report::mojom::Page> page,
+TracesInternalsHandler::TracesInternalsHandler(
+    mojo::PendingReceiver<traces_internals::mojom::PageHandler> receiver,
+    mojo::PendingRemote<traces_internals::mojom::Page> page,
     TraceUploadList& trace_upload_list,
     BackgroundTracingManagerImpl& background_tracing_manager,
     TracingDelegate* tracing_delegate)
@@ -131,25 +131,26 @@
   MaybeSetupPresetTracingFromFieldTrial();
 }
 
-TraceReportHandler::~TraceReportHandler() = default;
+TracesInternalsHandler::~TracesInternalsHandler() = default;
 
-void TraceReportHandler::DeleteSingleTrace(const base::Token& uuid,
-                                           DeleteSingleTraceCallback callback) {
+void TracesInternalsHandler::DeleteSingleTrace(
+    const base::Token& uuid,
+    DeleteSingleTraceCallback callback) {
   trace_upload_list_->DeleteSingleTrace(uuid, std::move(callback));
 }
 
-void TraceReportHandler::DeleteAllTraces(DeleteAllTracesCallback callback) {
+void TracesInternalsHandler::DeleteAllTraces(DeleteAllTracesCallback callback) {
   trace_upload_list_->DeleteAllTraces(std::move(callback));
 }
 
-void TraceReportHandler::UserUploadSingleTrace(
+void TracesInternalsHandler::UserUploadSingleTrace(
     const base::Token& uuid,
     UserUploadSingleTraceCallback callback) {
   trace_upload_list_->UserUploadSingleTrace(uuid, std::move(callback));
 }
 
-void TraceReportHandler::DownloadTrace(const base::Token& uuid,
-                                       DownloadTraceCallback callback) {
+void TracesInternalsHandler::DownloadTrace(const base::Token& uuid,
+                                           DownloadTraceCallback callback) {
   trace_upload_list_->DownloadTrace(
       uuid, base::BindOnce(
                 [](DownloadTraceCallback callback,
@@ -164,8 +165,9 @@
                 std::move(callback)));
 }
 
-void TraceReportHandler::StartTraceSession(mojo_base::BigBuffer config_pb,
-                                           StartTraceSessionCallback callback) {
+void TracesInternalsHandler::StartTraceSession(
+    mojo_base::BigBuffer config_pb,
+    StartTraceSessionCallback callback) {
   if (tracing_session_) {
     std::move(callback).Run(false);
     return;
@@ -187,25 +189,26 @@
       [task_runner = task_runner_, weak_ptr = weak_factory_.GetWeakPtr()]() {
         task_runner->PostTask(
             FROM_HERE,
-            base::BindOnce(&TraceReportHandler::OnTracingStart, weak_ptr));
+            base::BindOnce(&TracesInternalsHandler::OnTracingStart, weak_ptr));
       });
   tracing_session_->SetOnErrorCallback(
       [task_runner = task_runner_,
        weak_ptr = weak_factory_.GetWeakPtr()](perfetto::TracingError error) {
         task_runner->PostTask(
-            FROM_HERE, base::BindOnce(&TraceReportHandler::OnTracingError,
+            FROM_HERE, base::BindOnce(&TracesInternalsHandler::OnTracingError,
                                       weak_ptr, error));
       });
   tracing_session_->SetOnStopCallback(
       [task_runner = task_runner_, weak_ptr = weak_factory_.GetWeakPtr()]() {
         task_runner->PostTask(
             FROM_HERE,
-            base::BindOnce(&TraceReportHandler::OnTracingStop, weak_ptr));
+            base::BindOnce(&TracesInternalsHandler::OnTracingStop, weak_ptr));
       });
   tracing_session_->Start();
 }
 
-void TraceReportHandler::CloneTraceSession(CloneTraceSessionCallback callback) {
+void TracesInternalsHandler::CloneTraceSession(
+    CloneTraceSessionCallback callback) {
   if (!tracing_session_) {
     std::move(callback).Run(std::nullopt);
     return;
@@ -226,7 +229,8 @@
       });
 }
 
-void TraceReportHandler::StopTraceSession(StopTraceSessionCallback callback) {
+void TracesInternalsHandler::StopTraceSession(
+    StopTraceSessionCallback callback) {
   if (!tracing_session_) {
     std::move(callback).Run(false);
     return;
@@ -235,7 +239,7 @@
   tracing_session_->Stop();
 }
 
-void TraceReportHandler::GetBufferUsage(GetBufferUsageCallback callback) {
+void TracesInternalsHandler::GetBufferUsage(GetBufferUsageCallback callback) {
   if (!tracing_session_ || on_buffer_usage_callback_) {
     std::move(callback).Run(false, 0, false);
     return;
@@ -246,20 +250,21 @@
       [task_runner = task_runner_, weak_ptr = weak_factory_.GetWeakPtr()](
           perfetto::TracingSession::GetTraceStatsCallbackArgs args) {
         tracing::ReadTraceStats(
-            args, base::BindOnce(&TraceReportHandler::OnBufferUsage, weak_ptr),
+            args,
+            base::BindOnce(&TracesInternalsHandler::OnBufferUsage, weak_ptr),
             task_runner);
       });
 }
 
-void TraceReportHandler::OnBufferUsage(bool success,
-                                       float percent_full,
-                                       bool data_loss) {
+void TracesInternalsHandler::OnBufferUsage(bool success,
+                                           float percent_full,
+                                           bool data_loss) {
   if (on_buffer_usage_callback_) {
     std::move(on_buffer_usage_callback_).Run(success, percent_full, data_loss);
   }
 }
 
-void TraceReportHandler::OnTracingError(perfetto::TracingError error) {
+void TracesInternalsHandler::OnTracingError(perfetto::TracingError error) {
   if (start_callback_) {
     std::move(start_callback_).Run(false);
   }
@@ -269,42 +274,42 @@
   page_->OnTraceComplete(std::nullopt);
 }
 
-void TraceReportHandler::OnTracingStop() {
+void TracesInternalsHandler::OnTracingStop() {
   if (stop_callback_) {
     std::move(stop_callback_).Run(true);
   }
   auto trace_reader = base::MakeRefCounted<TraceReader>(
       std::move(tracing_session_),
-      base::BindOnce(&TraceReportHandler::OnTraceComplete,
+      base::BindOnce(&TracesInternalsHandler::OnTraceComplete,
                      weak_factory_.GetWeakPtr()),
       task_runner_);
   TraceReader::ReadTrace(std::move(trace_reader));
 }
 
-void TraceReportHandler::OnTracingStart() {
+void TracesInternalsHandler::OnTracingStart() {
   if (start_callback_) {
     std::move(start_callback_).Run(true);
   }
 }
 
-void TraceReportHandler::OnTraceComplete(
+void TracesInternalsHandler::OnTraceComplete(
     std::optional<mojo_base::BigBuffer> serialized_trace) {
   page_->OnTraceComplete(std::move(serialized_trace));
 }
 
-void TraceReportHandler::GetAllTraceReports(
+void TracesInternalsHandler::GetAllTraceReports(
     GetAllTraceReportsCallback callback) {
   trace_upload_list_->GetAllTraceReports(
-      base::BindOnce(&TraceReportHandler::OnGetAllReportsTaskComplete,
+      base::BindOnce(&TracesInternalsHandler::OnGetAllReportsTaskComplete,
                      weak_factory_.GetWeakPtr(), std::move(callback)));
 }
 
-void TraceReportHandler::OnGetAllReportsTaskComplete(
+void TracesInternalsHandler::OnGetAllReportsTaskComplete(
     GetAllTraceReportsCallback callback,
     std::vector<ClientTraceReport> results) {
-  std::vector<trace_report::mojom::ClientTraceReportPtr> reports;
+  std::vector<traces_internals::mojom::ClientTraceReportPtr> reports;
   for (const auto& report : results) {
-    reports.push_back(trace_report::mojom::ClientTraceReport::New(
+    reports.push_back(traces_internals::mojom::ClientTraceReport::New(
         report.uuid, report.creation_time, report.scenario_name,
         report.upload_rule_name, report.upload_rule_value, report.total_size,
         report.upload_state, report.upload_time, report.skip_reason,
@@ -313,11 +318,11 @@
   std::move(callback).Run(std::move(reports));
 }
 
-void TraceReportHandler::GetAllScenarios(GetAllScenariosCallback callback) {
+void TracesInternalsHandler::GetAllScenarios(GetAllScenariosCallback callback) {
   std::move(callback).Run(background_tracing_manager_->GetAllScenarios());
 }
 
-void TraceReportHandler::SetEnabledScenarios(
+void TracesInternalsHandler::SetEnabledScenarios(
     const std::vector<std::string>& new_config,
     SetEnabledScenariosCallback callback) {
   auto response = background_tracing_manager_->SetEnabledScenarios(new_config);
@@ -328,18 +333,18 @@
   std::move(callback).Run(std::move(response));
 }
 
-void TraceReportHandler::GetPrivacyFilterEnabled(
+void TracesInternalsHandler::GetPrivacyFilterEnabled(
     GetPrivacyFilterEnabledCallback callback) {
   std::move(callback).Run(tracing::BackgroundTracingStateManager::GetInstance()
                               .privacy_filter_enabled());
 }
 
-void TraceReportHandler::SetPrivacyFilterEnabled(bool enable) {
+void TracesInternalsHandler::SetPrivacyFilterEnabled(bool enable) {
   tracing::BackgroundTracingStateManager::GetInstance().UpdatePrivacyFilter(
       enable);
 }
 
-void TraceReportHandler::SetScenariosConfigFromString(
+void TracesInternalsHandler::SetScenariosConfigFromString(
     const std::string& config_string,
     SetScenariosConfigFromStringCallback callback) {
   auto field_tracing_config =
@@ -351,7 +356,7 @@
   std::move(callback).Run(SetScenariosConfig(std::move(*field_tracing_config)));
 }
 
-void TraceReportHandler::SetScenariosConfigFromBuffer(
+void TracesInternalsHandler::SetScenariosConfigFromBuffer(
     mojo_base::BigBuffer config_pb,
     SetScenariosConfigFromBufferCallback callback) {
   auto field_tracing_config =
@@ -363,7 +368,7 @@
   std::move(callback).Run(SetScenariosConfig(std::move(*field_tracing_config)));
 }
 
-bool TraceReportHandler::SetScenariosConfig(
+bool TracesInternalsHandler::SetScenariosConfig(
     const perfetto::protos::gen::ChromeFieldTracingConfig& config) {
   content::BackgroundTracingManager::DataFiltering data_filtering =
       tracing::BackgroundTracingStateManager::GetInstance()
@@ -380,7 +385,7 @@
   return true;
 }
 
-void TraceReportHandler::MaybeSetupPresetTracingFromFieldTrial() {
+void TracesInternalsHandler::MaybeSetupPresetTracingFromFieldTrial() {
   if (tracing::IsBackgroundTracingEnabledFromCommandLine()) {
     return;
   }
@@ -398,7 +403,7 @@
 }
 
 #if BUILDFLAG(IS_WIN)
-void TraceReportHandler::GetSystemTracingState(
+void TracesInternalsHandler::GetSystemTracingState(
     GetSystemTracingStateCallback callback) {
   if (!tracing_delegate_) {
     std::move(callback).Run(/*service_supported=*/false,
@@ -408,7 +413,7 @@
   tracing_delegate_->GetSystemTracingState(std::move(callback));
 }
 
-void TraceReportHandler::GetSecurityShieldIconUrl(
+void TracesInternalsHandler::GetSecurityShieldIconUrl(
     GetSecurityShieldIconUrlCallback callback) {
   base::ThreadPool::PostTaskAndReplyWithResult(
       FROM_HERE, base::BindOnce(&gfx::win::GetElevationIcon),
@@ -424,7 +429,7 @@
           std::move(callback)));
 }
 
-void TraceReportHandler::EnableSystemTracing(
+void TracesInternalsHandler::EnableSystemTracing(
     EnableSystemTracingCallback callback) {
   if (!tracing_delegate_) {
     std::move(callback).Run(/*success=*/false);
@@ -433,7 +438,7 @@
   tracing_delegate_->EnableSystemTracing(std::move(callback));
 }
 
-void TraceReportHandler::DisableSystemTracing(
+void TracesInternalsHandler::DisableSystemTracing(
     DisableSystemTracingCallback callback) {
   if (!tracing_delegate_) {
     std::move(callback).Run(/*success=*/false);
@@ -444,7 +449,7 @@
 #endif  // BUILDFLAG(IS_WIN)
 
 std::unique_ptr<perfetto::TracingSession>
-TraceReportHandler::CreateTracingSession() {
+TracesInternalsHandler::CreateTracingSession() {
   return perfetto::Tracing::NewTrace(perfetto::BackendType::kCustomBackend);
 }
 
diff --git a/content/browser/tracing/trace_report/trace_report_handler.h b/content/browser/tracing/traces_internals/traces_internals_handler.h
similarity index 76%
rename from content/browser/tracing/trace_report/trace_report_handler.h
rename to content/browser/tracing/traces_internals/traces_internals_handler.h
index 0948cfd..d0694555 100644
--- a/content/browser/tracing/trace_report/trace_report_handler.h
+++ b/content/browser/tracing/traces_internals/traces_internals_handler.h
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef CONTENT_BROWSER_TRACING_TRACE_REPORT_TRACE_REPORT_HANDLER_H_
-#define CONTENT_BROWSER_TRACING_TRACE_REPORT_TRACE_REPORT_HANDLER_H_
+#ifndef CONTENT_BROWSER_TRACING_TRACES_INTERNALS_TRACES_INTERNALS_HANDLER_H_
+#define CONTENT_BROWSER_TRACING_TRACES_INTERNALS_TRACES_INTERNALS_HANDLER_H_
 
 #include "base/memory/raw_ref.h"
 #include "base/task/task_runner.h"
@@ -11,8 +11,8 @@
 #include "base/unguessable_token.h"
 #include "build/build_config.h"
 #include "content/browser/tracing/background_tracing_manager_impl.h"
-#include "content/browser/tracing/trace_report/trace_report.mojom.h"
-#include "content/browser/tracing/trace_report/trace_upload_list.h"
+#include "content/browser/tracing/trace_upload_list.h"
+#include "content/browser/tracing/traces_internals/traces_internals.mojom.h"
 #include "content/common/content_export.h"
 #include "content/public/browser/background_tracing_manager.h"
 #include "mojo/public/cpp/bindings/pending_receiver.h"
@@ -22,18 +22,18 @@
 
 namespace content {
 // Handles communication between the browser and chrome://traces.
-class CONTENT_EXPORT TraceReportHandler
-    : public trace_report::mojom::PageHandler {
+class CONTENT_EXPORT TracesInternalsHandler
+    : public traces_internals::mojom::PageHandler {
  public:
-  TraceReportHandler(
-      mojo::PendingReceiver<trace_report::mojom::PageHandler> receiver,
-      mojo::PendingRemote<trace_report::mojom::Page> page);
+  TracesInternalsHandler(
+      mojo::PendingReceiver<traces_internals::mojom::PageHandler> receiver,
+      mojo::PendingRemote<traces_internals::mojom::Page> page);
 
-  TraceReportHandler(const TraceReportHandler&) = delete;
-  TraceReportHandler& operator=(const TraceReportHandler&) = delete;
-  ~TraceReportHandler() override;
+  TracesInternalsHandler(const TracesInternalsHandler&) = delete;
+  TracesInternalsHandler& operator=(const TracesInternalsHandler&) = delete;
+  ~TracesInternalsHandler() override;
 
-  // trace_report::mojom::TraceReportHandler:
+  // trace_report::mojom::TracesInternalsHandler:
   // Get all the trace report currently stored locally
   void StartTraceSession(mojo_base::BigBuffer config_pb,
                          StartTraceSessionCallback callback) override;
@@ -71,9 +71,9 @@
 #endif  // BUILDFLAG(IS_WIN)
 
  protected:
-  TraceReportHandler(
-      mojo::PendingReceiver<trace_report::mojom::PageHandler> receiver,
-      mojo::PendingRemote<trace_report::mojom::Page> page,
+  TracesInternalsHandler(
+      mojo::PendingReceiver<traces_internals::mojom::PageHandler> receiver,
+      mojo::PendingRemote<traces_internals::mojom::Page> page,
       TraceUploadList& trace_upload_list,
       BackgroundTracingManagerImpl& background_tracing_manager,
       TracingDelegate* tracing_delegate);
@@ -94,8 +94,8 @@
   void OnBufferUsage(bool success, float percent_full, bool data_loss);
 
   scoped_refptr<base::SequencedTaskRunner> task_runner_;
-  mojo::Receiver<trace_report::mojom::PageHandler> receiver_;
-  mojo::Remote<trace_report::mojom::Page> page_;
+  mojo::Receiver<traces_internals::mojom::PageHandler> receiver_;
+  mojo::Remote<traces_internals::mojom::Page> page_;
 
   // Used to perform actions with on a single trace_report_database instance.
   const raw_ref<TraceUploadList> trace_upload_list_;
@@ -108,9 +108,9 @@
   StopTraceSessionCallback stop_callback_;
   GetBufferUsageCallback on_buffer_usage_callback_;
 
-  base::WeakPtrFactory<TraceReportHandler> weak_factory_{this};
+  base::WeakPtrFactory<TracesInternalsHandler> weak_factory_{this};
 };
 
 }  // namespace content
 
-#endif  // CONTENT_BROWSER_TRACING_TRACE_REPORT_TRACE_REPORT_HANDLER_H_
+#endif  // CONTENT_BROWSER_TRACING_TRACES_INTERNALS_TRACES_INTERNALS_HANDLER_H_
diff --git a/content/browser/tracing/trace_report/trace_report_handler_unittest.cc b/content/browser/tracing/traces_internals/traces_internals_handler_unittest.cc
similarity index 76%
rename from content/browser/tracing/trace_report/trace_report_handler_unittest.cc
rename to content/browser/tracing/traces_internals/traces_internals_handler_unittest.cc
index 6f188a6a..425b7f14 100644
--- a/content/browser/tracing/trace_report/trace_report_handler_unittest.cc
+++ b/content/browser/tracing/traces_internals/traces_internals_handler_unittest.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "content/browser/tracing/trace_report/trace_report_handler.h"
+#include "content/browser/tracing/traces_internals/traces_internals_handler.h"
 
 #include "base/base_paths.h"
 #include "base/path_service.h"
@@ -11,8 +11,8 @@
 #include "base/test/test_proto_loader.h"
 #include "base/token.h"
 #include "content/browser/tracing/test_tracing_session.h"
-#include "content/browser/tracing/trace_report/trace_report.mojom.h"
-#include "content/browser/tracing/trace_report/trace_upload_list.h"
+#include "content/browser/tracing/trace_upload_list.h"
+#include "content/browser/tracing/traces_internals/traces_internals.mojom.h"
 #include "content/public/browser/background_tracing_manager.h"
 #include "content/public/browser/tracing_delegate.h"
 #include "content/public/test/browser_task_environment.h"
@@ -61,12 +61,12 @@
               (override));
 };
 
-class MockTracePage : public trace_report::mojom::Page {
+class MockTracePage : public traces_internals::mojom::Page {
  public:
   MockTracePage() = default;
   ~MockTracePage() override = default;
 
-  mojo::PendingRemote<trace_report::mojom::Page> BindAndGetRemote() {
+  mojo::PendingRemote<traces_internals::mojom::Page> BindAndGetRemote() {
     CHECK(!receiver_.is_bound());
     return receiver_.BindNewPipeAndPassRemote();
   }
@@ -76,7 +76,7 @@
               (std::optional<mojo_base::BigBuffer>),
               (override));
 
-  mojo::Receiver<trace_report::mojom::Page> receiver_{this};
+  mojo::Receiver<traces_internals::mojom::Page> receiver_{this};
 };
 
 class MockTracingDelegate : public TracingDelegate {
@@ -99,19 +99,19 @@
 #endif
 };
 
-class TraceReportHandlerForTesting : public TraceReportHandler {
+class TracesInternalsHandlerForTesting : public TracesInternalsHandler {
  public:
-  TraceReportHandlerForTesting(
-      mojo::PendingReceiver<trace_report::mojom::PageHandler> receiver,
-      mojo::PendingRemote<trace_report::mojom::Page> page,
+  TracesInternalsHandlerForTesting(
+      mojo::PendingReceiver<traces_internals::mojom::PageHandler> receiver,
+      mojo::PendingRemote<traces_internals::mojom::Page> page,
       TraceUploadList& trace_upload_list,
       BackgroundTracingManagerImpl& background_tracing_manager,
       TracingDelegate* tracing_delegate)
-      : TraceReportHandler(std::move(receiver),
-                           std::move(page),
-                           trace_upload_list,
-                           background_tracing_manager,
-                           tracing_delegate) {}
+      : TracesInternalsHandler(std::move(receiver),
+                               std::move(page),
+                               trace_upload_list,
+                               background_tracing_manager,
+                               tracing_delegate) {}
 
  protected:
   std::unique_ptr<perfetto::TracingSession> CreateTracingSession() override {
@@ -119,19 +119,19 @@
   }
 };
 
-// A fixture to test TraceReportHandler.
-class TraceReportHandlerTest : public testing::Test {
+// A fixture to test TracesInternalsHandler.
+class TracesInternalsHandlerTest : public testing::Test {
  public:
-  TraceReportHandlerTest() = default;
-  ~TraceReportHandlerTest() override = default;
+  TracesInternalsHandlerTest() = default;
+  ~TracesInternalsHandlerTest() override = default;
 
   void SetUp() override {
     background_tracing_manager_ =
         std::make_unique<BackgroundTracingManagerImpl>();
     // Expect the Database to be opened before executing each test.
     EXPECT_CALL(fake_trace_upload_list_, OpenDatabaseIfExists());
-    handler_ = std::make_unique<TraceReportHandlerForTesting>(
-        mojo::PendingReceiver<trace_report::mojom::PageHandler>(),
+    handler_ = std::make_unique<TracesInternalsHandlerForTesting>(
+        mojo::PendingReceiver<traces_internals::mojom::PageHandler>(),
         mock_page_.BindAndGetRemote(), fake_trace_upload_list_,
         *background_tracing_manager_, &mock_tracing_delegate_);
   }
@@ -142,16 +142,16 @@
   testing::StrictMock<FakeTraceUploadList> fake_trace_upload_list_;
   testing::NiceMock<MockTracePage> mock_page_;
   testing::NiceMock<MockTracingDelegate> mock_tracing_delegate_;
-  std::unique_ptr<TraceReportHandler> handler_;
+  std::unique_ptr<TracesInternalsHandler> handler_;
 };
 
-TEST_F(TraceReportHandlerTest, TracingStartStop) {
+TEST_F(TracesInternalsHandlerTest, TracingStartStop) {
   auto trace_config =
       ParseTraceConfigFromText(R"pb(
         data_sources: { config: { name: "org.chromium.trace_metadata" } }
       )pb")
           .SerializeAsString();
-  base::MockCallback<TraceReportHandler::StartTraceSessionCallback>
+  base::MockCallback<TracesInternalsHandler::StartTraceSessionCallback>
       start_callback;
   handler_->StartTraceSession(mojo_base::BigBuffer(base::as_bytes(
                                   base::span<const char>(trace_config))),
@@ -164,7 +164,7 @@
     run_loop_start.Run();
   }
 
-  base::MockCallback<TraceReportHandler::StopTraceSessionCallback>
+  base::MockCallback<TracesInternalsHandler::StopTraceSessionCallback>
       stop_callback;
   handler_->StopTraceSession(stop_callback.Get());
   {
@@ -183,7 +183,7 @@
   }
 }
 
-TEST_F(TraceReportHandlerTest, TracingTimer) {
+TEST_F(TracesInternalsHandlerTest, TracingTimer) {
   auto trace_config = ParseTraceConfigFromText(R"pb(
                         data_sources: { config: { name: "Stop" } }
                       )pb")
@@ -205,12 +205,12 @@
   run_loop.Run();
 }
 
-TEST_F(TraceReportHandlerTest, TracingStartFail) {
+TEST_F(TracesInternalsHandlerTest, TracingStartFail) {
   auto trace_config = ParseTraceConfigFromText(R"pb(
                         data_sources: { config: { name: "Invalid" } }
                       )pb")
                           .SerializeAsString();
-  base::MockCallback<TraceReportHandler::StartTraceSessionCallback>
+  base::MockCallback<TracesInternalsHandler::StartTraceSessionCallback>
       start_callback;
   handler_->StartTraceSession(mojo_base::BigBuffer(base::as_bytes(
                                   base::span<const char>(trace_config))),
@@ -224,13 +224,13 @@
   }
 }
 
-TEST_F(TraceReportHandlerTest, TracingClone) {
+TEST_F(TracesInternalsHandlerTest, TracingClone) {
   auto trace_config =
       ParseTraceConfigFromText(R"pb(
         data_sources: { config: { name: "org.chromium.trace_metadata" } }
       )pb")
           .SerializeAsString();
-  base::MockCallback<TraceReportHandler::StartTraceSessionCallback>
+  base::MockCallback<TracesInternalsHandler::StartTraceSessionCallback>
       start_callback;
   handler_->StartTraceSession(mojo_base::BigBuffer(base::as_bytes(
                                   base::span<const char>(trace_config))),
@@ -243,7 +243,7 @@
     run_loop_start.Run();
   }
 
-  base::MockCallback<TraceReportHandler::CloneTraceSessionCallback>
+  base::MockCallback<TracesInternalsHandler::CloneTraceSessionCallback>
       clone_callback;
   handler_->CloneTraceSession(clone_callback.Get());
   {
@@ -261,13 +261,13 @@
   }
 }
 
-TEST_F(TraceReportHandlerTest, TracingBufferUsage) {
+TEST_F(TracesInternalsHandlerTest, TracingBufferUsage) {
   auto trace_config =
       ParseTraceConfigFromText(R"pb(
         data_sources: { config: { name: "org.chromium.trace_metadata" } }
       )pb")
           .SerializeAsString();
-  base::MockCallback<TraceReportHandler::StartTraceSessionCallback>
+  base::MockCallback<TracesInternalsHandler::StartTraceSessionCallback>
       start_callback;
   handler_->StartTraceSession(mojo_base::BigBuffer(base::as_bytes(
                                   base::span<const char>(trace_config))),
@@ -280,7 +280,7 @@
     run_loop_start.Run();
   }
 
-  base::MockCallback<TraceReportHandler::GetBufferUsageCallback>
+  base::MockCallback<TracesInternalsHandler::GetBufferUsageCallback>
       buffer_callback;
   handler_->GetBufferUsage(buffer_callback.Get());
   {
@@ -293,8 +293,9 @@
   }
 }
 
-TEST_F(TraceReportHandlerTest, GetAllTraceReports) {
-  base::MockCallback<TraceReportHandler::GetAllTraceReportsCallback> callback;
+TEST_F(TracesInternalsHandlerTest, GetAllTraceReports) {
+  base::MockCallback<TracesInternalsHandler::GetAllTraceReportsCallback>
+      callback;
 
   EXPECT_CALL(fake_trace_upload_list_, GetAllTraceReports)
       .WillOnce([](FakeTraceUploadList::GetReportsCallback callback) {
@@ -317,13 +318,13 @@
       });
 
   EXPECT_CALL(callback, Run)
-      .WillOnce([](std::vector<trace_report::mojom::ClientTraceReportPtr>
+      .WillOnce([](std::vector<traces_internals::mojom::ClientTraceReportPtr>
                        all_reports) { EXPECT_EQ(all_reports.size(), 2u); });
   handler_->GetAllTraceReports(callback.Get());
 }
 
-TEST_F(TraceReportHandlerTest, DeleteAllTraces) {
-  base::MockCallback<TraceReportHandler::DeleteAllTracesCallback> callback;
+TEST_F(TracesInternalsHandlerTest, DeleteAllTraces) {
+  base::MockCallback<TracesInternalsHandler::DeleteAllTracesCallback> callback;
 
   EXPECT_CALL(fake_trace_upload_list_, DeleteAllTraces)
       .WillOnce([](FakeTraceUploadList::FinishedProcessingCallback callback) {
@@ -333,9 +334,10 @@
   handler_->DeleteAllTraces(callback.Get());
 }
 
-TEST_F(TraceReportHandlerTest, DeleteSingleTrace) {
+TEST_F(TracesInternalsHandlerTest, DeleteSingleTrace) {
   auto uuid = base::Token::CreateRandom();
-  base::MockCallback<TraceReportHandler::DeleteSingleTraceCallback> callback;
+  base::MockCallback<TracesInternalsHandler::DeleteSingleTraceCallback>
+      callback;
 
   EXPECT_CALL(fake_trace_upload_list_, DeleteSingleTrace)
       .WillOnce(
@@ -348,9 +350,9 @@
   handler_->DeleteSingleTrace(uuid, callback.Get());
 }
 
-TEST_F(TraceReportHandlerTest, UserUploadSingleTrace) {
+TEST_F(TracesInternalsHandlerTest, UserUploadSingleTrace) {
   auto uuid = base::Token::CreateRandom();
-  base::MockCallback<TraceReportHandler::UserUploadSingleTraceCallback>
+  base::MockCallback<TracesInternalsHandler::UserUploadSingleTraceCallback>
       callback;
 
   EXPECT_CALL(fake_trace_upload_list_, UserUploadSingleTrace)
@@ -364,9 +366,9 @@
   handler_->UserUploadSingleTrace(uuid, callback.Get());
 }
 
-TEST_F(TraceReportHandlerTest, DownloadTrace) {
+TEST_F(TracesInternalsHandlerTest, DownloadTrace) {
   auto uuid = base::Token::CreateRandom();
-  base::MockCallback<TraceReportHandler::DownloadTraceCallback> callback;
+  base::MockCallback<TracesInternalsHandler::DownloadTraceCallback> callback;
 
   const auto result = std::optional<base::span<const char>>("PROTO RESULT");
 
@@ -378,34 +380,33 @@
             std::move(callback).Run(result);
           });
   EXPECT_CALL(callback, Run)
-      .WillOnce(
-          [&result](std::optional<mojo_base::BigBuffer> converted_value) {
-            EXPECT_EQ(std::string_view(
-                          reinterpret_cast<char*>(converted_value->data()),
-                          converted_value->size()),
-                      std::string_view(result->data(), result->size()));
-          });
+      .WillOnce([&result](std::optional<mojo_base::BigBuffer> converted_value) {
+        EXPECT_EQ(
+            std::string_view(reinterpret_cast<char*>(converted_value->data()),
+                             converted_value->size()),
+            std::string_view(result->data(), result->size()));
+      });
   handler_->DownloadTrace(uuid, callback.Get());
 }
 
 #if BUILDFLAG(IS_WIN)
-// Tests that TraceReportHandler delegates GetSystemTracingState to the
+// Tests that TracesInternalsHandler delegates GetSystemTracingState to the
 // TracingDelegate.
-TEST_F(TraceReportHandlerTest, GetSystemTracingState) {
+TEST_F(TracesInternalsHandlerTest, GetSystemTracingState) {
   EXPECT_CALL(mock_tracing_delegate_, GetSystemTracingState(testing::_));
   handler_->GetSystemTracingState({});
 }
 
-// Tests that TraceReportHandler delegates EnableSystemTracing to the
+// Tests that TracesInternalsHandler delegates EnableSystemTracing to the
 // TracingDelegate.
-TEST_F(TraceReportHandlerTest, EnableSystemTracing) {
+TEST_F(TracesInternalsHandlerTest, EnableSystemTracing) {
   EXPECT_CALL(mock_tracing_delegate_, EnableSystemTracing(testing::_));
   handler_->EnableSystemTracing({});
 }
 
-// Tests that TraceReportHandler delegates DisableSystemTracing to the
+// Tests that TracesInternalsHandler delegates DisableSystemTracing to the
 // TracingDelegate.
-TEST_F(TraceReportHandlerTest, DisableSystemTracing) {
+TEST_F(TracesInternalsHandlerTest, DisableSystemTracing) {
   EXPECT_CALL(mock_tracing_delegate_, DisableSystemTracing(testing::_));
   handler_->DisableSystemTracing({});
 }
diff --git a/content/browser/tracing/trace_report/trace_report_mojom_traits.cc b/content/browser/tracing/traces_internals/traces_internals_mojom_traits.cc
similarity index 97%
rename from content/browser/tracing/trace_report/trace_report_mojom_traits.cc
rename to content/browser/tracing/traces_internals/traces_internals_mojom_traits.cc
index 4196e78..ac9f60f 100644
--- a/content/browser/tracing/trace_report/trace_report_mojom_traits.cc
+++ b/content/browser/tracing/traces_internals/traces_internals_mojom_traits.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "content/browser/tracing/trace_report/trace_report_mojom_traits.h"
+#include "content/browser/tracing/traces_internals/traces_internals_mojom_traits.h"
 
 namespace mojo {
 
diff --git a/content/browser/tracing/trace_report/trace_report_mojom_traits.h b/content/browser/tracing/traces_internals/traces_internals_mojom_traits.h
similarity index 63%
rename from content/browser/tracing/trace_report/trace_report_mojom_traits.h
rename to content/browser/tracing/traces_internals/traces_internals_mojom_traits.h
index 7198633..eb2e46e 100644
--- a/content/browser/tracing/trace_report/trace_report_mojom_traits.h
+++ b/content/browser/tracing/traces_internals/traces_internals_mojom_traits.h
@@ -2,22 +2,18 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef CONTENT_BROWSER_TRACING_TRACE_REPORT_TRACE_REPORT_MOJOM_TRAITS_H_
-#define CONTENT_BROWSER_TRACING_TRACE_REPORT_TRACE_REPORT_MOJOM_TRAITS_H_
+#ifndef CONTENT_BROWSER_TRACING_TRACES_INTERNALS_TRACES_INTERNALS_MOJOM_TRAITS_H_
+#define CONTENT_BROWSER_TRACING_TRACES_INTERNALS_TRACES_INTERNALS_MOJOM_TRAITS_H_
 
-#include "content/browser/tracing/trace_report/trace_report.mojom.h"
-#include "content/browser/tracing/trace_report/trace_report_database.h"
+#include "content/browser/tracing/trace_report_database.h"
+#include "content/browser/tracing/traces_internals/traces_internals.mojom.h"
 #include "content/browser/tracing/tracing_scenario.h"
 
 namespace mojo {
 
-namespace {
-
-using ReportUploadState = trace_report::mojom::ReportUploadState;
-using SkipUploadReason = trace_report::mojom::SkipUploadReason;
-using TracingScenarioState = trace_report::mojom::TracingScenarioState;
-
-}  // namespace
+using ReportUploadState = traces_internals::mojom::ReportUploadState;
+using SkipUploadReason = traces_internals::mojom::SkipUploadReason;
+using TracingScenarioState = traces_internals::mojom::TracingScenarioState;
 
 template <>
 struct EnumTraits<ReportUploadState, content::ReportUploadState> {
@@ -42,4 +38,4 @@
 
 }  // namespace mojo
 
-#endif  // CONTENT_BROWSER_TRACING_TRACE_REPORT_TRACE_REPORT_MOJOM_TRAITS_H_
+#endif  // CONTENT_BROWSER_TRACING_TRACES_INTERNALS_TRACES_INTERNALS_MOJOM_TRAITS_H_
diff --git a/content/browser/tracing/trace_report/trace_report_internals_ui.cc b/content/browser/tracing/traces_internals/traces_internals_ui.cc
similarity index 61%
rename from content/browser/tracing/trace_report/trace_report_internals_ui.cc
rename to content/browser/tracing/traces_internals/traces_internals_ui.cc
index 668dee4..624c11a3 100644
--- a/content/browser/tracing/trace_report/trace_report_internals_ui.cc
+++ b/content/browser/tracing/traces_internals/traces_internals_ui.cc
@@ -2,11 +2,11 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "content/browser/tracing/trace_report/trace_report_internals_ui.h"
+#include "content/browser/tracing/traces_internals/traces_internals_ui.h"
 
 #include "content/browser/renderer_host/render_frame_host_impl.h"
-#include "content/browser/tracing/trace_report/trace_report.mojom.h"
-#include "content/browser/tracing/trace_report/trace_report_handler.h"
+#include "content/browser/tracing/traces_internals/traces_internals.mojom.h"
+#include "content/browser/tracing/traces_internals/traces_internals_handler.h"
 #include "content/browser/webui/web_ui_impl.h"
 #include "content/grit/traces_internals_resources.h"
 #include "content/grit/traces_internals_resources_map.h"
@@ -19,13 +19,13 @@
 
 namespace content {
 
-TraceReportInternalsUI::TraceReportInternalsUI(WebUI* web_ui, const GURL& url)
+TracesInternalsUI::TracesInternalsUI(WebUI* web_ui, const GURL& url)
     : WebUIController(web_ui) {
   WebUIDataSource* source = WebUIDataSource::CreateAndAdd(
       web_ui->GetWebContents()->GetBrowserContext(), url.host());
 
   source->AddResourcePaths(kTracesInternalsResources);
-  source->AddResourcePath("", IDR_TRACES_INTERNALS_TRACE_REPORT_INTERNALS_HTML);
+  source->AddResourcePath("", IDR_TRACES_INTERNALS_TRACES_INTERNALS_HTML);
 
   // Add TrustedTypes policies necessary for using Polymer.
   source->OverrideContentSecurityPolicy(
@@ -36,30 +36,30 @@
       "lit-html-desktop;");
 }
 
-TraceReportInternalsUI::~TraceReportInternalsUI() = default;
+TracesInternalsUI::~TracesInternalsUI() = default;
 
-void TraceReportInternalsUI::WebUIRenderFrameCreated(
+void TracesInternalsUI::WebUIRenderFrameCreated(
     RenderFrameHost* render_frame_host) {
   // Enable the JavaScript Mojo bindings in the renderer process, so the JS
   // code can call the Mojo APIs exposed by this WebUI.
   render_frame_host->EnableMojoJsBindings(nullptr);
 }
 
-void TraceReportInternalsUI::BindInterface(
-    mojo::PendingReceiver<trace_report::mojom::TraceReportHandlerFactory>
-        receiver) {
+void TracesInternalsUI::BindInterface(
+    mojo::PendingReceiver<
+        traces_internals::mojom::TracesInternalsHandlerFactory> receiver) {
   page_factory_receiver_.reset();
   page_factory_receiver_.Bind(std::move(receiver));
 }
 
-void TraceReportInternalsUI::CreatePageHandler(
-    mojo::PendingRemote<trace_report::mojom::Page> page,
-    mojo::PendingReceiver<trace_report::mojom::PageHandler> receiver) {
+void TracesInternalsUI::CreatePageHandler(
+    mojo::PendingRemote<traces_internals::mojom::Page> page,
+    mojo::PendingReceiver<traces_internals::mojom::PageHandler> receiver) {
   DCHECK(page);
-  ui_handler_ = std::make_unique<TraceReportHandler>(std::move(receiver),
-                                                     std::move(page));
+  ui_handler_ = std::make_unique<TracesInternalsHandler>(std::move(receiver),
+                                                         std::move(page));
 }
 
-WEB_UI_CONTROLLER_TYPE_IMPL(TraceReportInternalsUI)
+WEB_UI_CONTROLLER_TYPE_IMPL(TracesInternalsUI)
 
 }  // namespace content
diff --git a/content/browser/tracing/traces_internals/traces_internals_ui.h b/content/browser/tracing/traces_internals/traces_internals_ui.h
new file mode 100644
index 0000000..1621c3d
--- /dev/null
+++ b/content/browser/tracing/traces_internals/traces_internals_ui.h
@@ -0,0 +1,69 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CONTENT_BROWSER_TRACING_TRACES_INTERNALS_TRACES_INTERNALS_UI_H_
+#define CONTENT_BROWSER_TRACING_TRACES_INTERNALS_TRACES_INTERNALS_UI_H_
+
+#include "content/browser/tracing/traces_internals/traces_internals.mojom.h"
+#include "content/public/browser/internal_webui_config.h"
+#include "content/public/browser/web_ui_controller.h"
+#include "content/public/common/url_constants.h"
+
+namespace content {
+
+class RenderFrameHost;
+class TracesInternalsHandler;
+class TracesInternalsUI;
+
+// WebUIConfig for the chrome://traces page.
+class TracesInternalsUIConfig
+    : public DefaultInternalWebUIConfig<TracesInternalsUI> {
+ public:
+  TracesInternalsUIConfig()
+      : DefaultInternalWebUIConfig(kChromeUITracesInternalsHost) {}
+};
+
+// Temporary WebUIConfig to also register legacy chrome://traces-internals URL.
+class TracesInternalsLegacyUIConfig
+    : public DefaultInternalWebUIConfig<TracesInternalsUI> {
+ public:
+  TracesInternalsLegacyUIConfig()
+      : DefaultInternalWebUIConfig("traces-internals") {}
+};
+
+// WebUIController for the chrome://traces page.
+class CONTENT_EXPORT TracesInternalsUI
+    : public WebUIController,
+      public traces_internals::mojom::TracesInternalsHandlerFactory {
+ public:
+  explicit TracesInternalsUI(content::WebUI* web_ui, const GURL& url);
+  ~TracesInternalsUI() override;
+
+  TracesInternalsUI(const TracesInternalsUI&) = delete;
+  TracesInternalsUI& operator=(const TracesInternalsUI&) = delete;
+
+  void BindInterface(
+      mojo::PendingReceiver<
+          traces_internals::mojom::TracesInternalsHandlerFactory> receiver);
+
+  // WebUIController:
+  void WebUIRenderFrameCreated(RenderFrameHost* rfh) override;
+
+ private:
+  // traces_internals::mojom::PageHandlerFactory:
+  void CreatePageHandler(
+      mojo::PendingRemote<traces_internals::mojom::Page> page,
+      mojo::PendingReceiver<traces_internals::mojom::PageHandler> receiver)
+      override;
+
+  std::unique_ptr<TracesInternalsHandler> ui_handler_;
+  mojo::Receiver<traces_internals::mojom::TracesInternalsHandlerFactory>
+      page_factory_receiver_{this};
+
+  WEB_UI_CONTROLLER_TYPE_DECL();
+};
+
+}  // namespace content
+
+#endif  // CONTENT_BROWSER_TRACING_TRACES_INTERNALS_TRACES_INTERNALS_UI_H_
diff --git a/content/browser/webui/content_web_ui_configs.cc b/content/browser/webui/content_web_ui_configs.cc
index 3f36e89a..0e6e19c 100644
--- a/content/browser/webui/content_web_ui_configs.cc
+++ b/content/browser/webui/content_web_ui_configs.cc
@@ -21,7 +21,7 @@
 #include "content/public/browser/webui_config_map.h"
 
 #if !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS)
-#include "content/browser/tracing/trace_report/trace_report_internals_ui.h"
+#include "content/browser/tracing/traces_internals/traces_internals_ui.h"
 #include "content/browser/tracing/tracing_ui.h"
 #endif
 
@@ -52,8 +52,8 @@
 #endif
 
 #if !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS) && !BUILDFLAG(IS_FUCHSIA)
-  map.AddWebUIConfig(std::make_unique<TraceReportInternalsUIConfig>());
-  map.AddWebUIConfig(std::make_unique<TraceReportInternalsLegacyUIConfig>());
+  map.AddWebUIConfig(std::make_unique<TracesInternalsUIConfig>());
+  map.AddWebUIConfig(std::make_unique<TracesInternalsLegacyUIConfig>());
   map.AddWebUIConfig(std::make_unique<TracingUIConfig>());
 #endif
 }
diff --git a/content/child/runtime_features.cc b/content/child/runtime_features.cc
index 5e189cb..33453d2f 100644
--- a/content/child/runtime_features.cc
+++ b/content/child/runtime_features.cc
@@ -29,6 +29,7 @@
 #include "device/vr/buildflags/buildflags.h"
 #include "gpu/config/gpu_finch_features.h"
 #include "gpu/config/gpu_switches.h"
+#include "media/audio/audio_features.h"
 #include "media/base/media_switches.h"
 #include "net/base/features.h"
 #include "services/device/public/cpp/device_features.h"
@@ -191,6 +192,10 @@
            raw_ref(features::kUseAXPositionForDocumentMarkers)},
           {wf::EnableAOMAriaRelationshipProperties,
            raw_ref(features::kEnableAriaElementReflection)},
+#if BUILDFLAG(IS_ANDROID)
+          {wf::EnableAudioOutputDevices,
+           raw_ref(features::kAAudioPerStreamDeviceSelection)},
+#endif
           {wf::EnableBackgroundFetch, raw_ref(features::kBackgroundFetch)},
           {wf::EnableBoundaryEventDispatchTracksNodeRemoval,
            raw_ref(blink::features::kBoundaryEventDispatchTracksNodeRemoval)},
diff --git a/content/test/BUILD.gn b/content/test/BUILD.gn
index 3814759..99172168 100644
--- a/content/test/BUILD.gn
+++ b/content/test/BUILD.gn
@@ -1878,7 +1878,7 @@
     "//content/browser/attribution_reporting:mojo_bindings",
     "//content/browser/attribution_reporting:registration_result_mojom",
     "//content/browser/background_sync:background_sync_proto",
-    "//content/browser/tracing/trace_report:mojo_bindings",
+    "//content/browser/tracing/traces_internals:mojo_bindings",
     "//content/child:for_content_tests",
     "//content/gpu",
     "//content/public/browser",
@@ -2895,8 +2895,8 @@
     "../browser/storage_partition_impl_unittest.cc",
     "../browser/tracing/background_tracing_manager_unittest.cc",
     "../browser/tracing/background_tracing_rule_unittest.cc",
-    "../browser/tracing/trace_report/trace_report_database_unittest.cc",
-    "../browser/tracing/trace_report/trace_report_handler_unittest.cc",
+    "../browser/tracing/trace_report_database_unittest.cc",
+    "../browser/tracing/traces_internals/traces_internals_handler_unittest.cc",
     "../browser/tracing/tracing_scenario_unittest.cc",
     "../browser/usb/web_usb_service_impl_unittest.cc",
     "../browser/web_contents/aura/gesture_nav_simple_unittest.cc",
@@ -3206,7 +3206,7 @@
     "//content/browser/notifications:notification_proto",
     "//content/browser/private_aggregation:for_content_tests",
     "//content/browser/tpcd_heuristics:unit_tests",
-    "//content/browser/tracing/trace_report:mojo_bindings",
+    "//content/browser/tracing/traces_internals:mojo_bindings",
     "//content/child:for_content_tests",
     "//content/common:for_content_tests",
     "//content/gpu",
diff --git a/device/bluetooth/floss/bluetooth_local_gatt_characteristic_floss.cc b/device/bluetooth/floss/bluetooth_local_gatt_characteristic_floss.cc
index f36a2a2..eef2a19 100644
--- a/device/bluetooth/floss/bluetooth_local_gatt_characteristic_floss.cc
+++ b/device/bluetooth/floss/bluetooth_local_gatt_characteristic_floss.cc
@@ -252,7 +252,7 @@
       FROM_HERE, kResponseTimeout,
       base::BindOnce(
           &BluetoothLocalGattCharacteristicFloss::OnWriteRequestCallback,
-          weak_ptr_factory_.GetWeakPtr(), request_id, std::ref(value),
+          weak_ptr_factory_.GetWeakPtr(), request_id, base::OwnedRef(value),
           needs_response, /*success=*/false));
 
   if (is_prepared_write) {
@@ -260,12 +260,12 @@
         device, characteristic, value, offset, /*has_subsequent_request=*/true,
         base::BindOnce(
             &BluetoothLocalGattCharacteristicFloss::OnWriteRequestCallback,
-            weak_ptr_factory_.GetWeakPtr(), request_id, std::ref(value),
+            weak_ptr_factory_.GetWeakPtr(), request_id, base::OwnedRef(value),
             needs_response,
             /*success=*/true),
         base::BindOnce(
             &BluetoothLocalGattCharacteristicFloss::OnWriteRequestCallback,
-            weak_ptr_factory_.GetWeakPtr(), request_id, std::ref(value),
+            weak_ptr_factory_.GetWeakPtr(), request_id, base::OwnedRef(value),
             needs_response,
             /*success=*/false));
   } else {
@@ -273,12 +273,12 @@
         device, characteristic, value, offset,
         base::BindOnce(
             &BluetoothLocalGattCharacteristicFloss::OnWriteRequestCallback,
-            weak_ptr_factory_.GetWeakPtr(), request_id, std::ref(value),
+            weak_ptr_factory_.GetWeakPtr(), request_id, base::OwnedRef(value),
             needs_response,
             /*success=*/true),
         base::BindOnce(
             &BluetoothLocalGattCharacteristicFloss::OnWriteRequestCallback,
-            weak_ptr_factory_.GetWeakPtr(), request_id, std::ref(value),
+            weak_ptr_factory_.GetWeakPtr(), request_id, base::OwnedRef(value),
             needs_response,
             /*success=*/false));
   }
diff --git a/device/bluetooth/floss/bluetooth_local_gatt_descriptor_floss.cc b/device/bluetooth/floss/bluetooth_local_gatt_descriptor_floss.cc
index 6835a54..ef58f255 100644
--- a/device/bluetooth/floss/bluetooth_local_gatt_descriptor_floss.cc
+++ b/device/bluetooth/floss/bluetooth_local_gatt_descriptor_floss.cc
@@ -228,17 +228,17 @@
       FROM_HERE, kResponseTimeout,
       base::BindOnce(&BluetoothLocalGattDescriptorFloss::OnWriteRequestCallback,
                      weak_ptr_factory_.GetWeakPtr(), request_id,
-                     std::ref(value), needs_response, /*success=*/false));
+                     base::OwnedRef(value), needs_response, /*success=*/false));
 
   delegate->OnDescriptorWriteRequest(
       device, descriptor, value, offset,
       base::BindOnce(&BluetoothLocalGattDescriptorFloss::OnWriteRequestCallback,
                      weak_ptr_factory_.GetWeakPtr(), request_id,
-                     std::ref(value), needs_response,
+                     base::OwnedRef(value), needs_response,
                      /*success=*/true),
       base::BindOnce(&BluetoothLocalGattDescriptorFloss::OnWriteRequestCallback,
                      weak_ptr_factory_.GetWeakPtr(), request_id,
-                     std::ref(value), needs_response,
+                     base::OwnedRef(value), needs_response,
                      /*success=*/false));
 }
 
diff --git a/docs/ui/starter_guide.md b/docs/ui/starter_guide.md
index 911146e8..c7ee829 100644
--- a/docs/ui/starter_guide.md
+++ b/docs/ui/starter_guide.md
@@ -12,7 +12,7 @@
 * **Ignorance is ok.** UI is a specialized field, and if you haven't worked with
 it before, there's no reason you should know everything. Even if you have done
 UI development on other products or on the web, the Views toolkit has some
-differences that might leave yourself scratching your head. Don't spend too long
+differences that might leave you scratching your head. Don't spend too long
 trying to figure things out yourself;
 [ask questions](https://chromium.googlesource.com/chromium/src/+/main/docs/ui/ask/index.md)
 early and often, before you discover in code review that that lovely solution
@@ -36,7 +36,7 @@
 
 * **Build for the long term.** Chrome has a rapid release schedule in part so we
 don't feel pressure to ship changes before they're ready. It's easy for
-engineers to ship UI and leave it unmaintained, adding to the deifficulty of
+engineers to ship UI and leave it unmaintained, adding to the difficulty of
 future refactors and stylistic changes. So expect reviewers to set a high bar.
 Ask how to structure your classes in keeping with MVC (Model-View-Controller)
 principles. Write automated tests for your feature, including both functional
diff --git a/infra/config/generated/builders/ci/android-x86-code-coverage/targets/chromium.coverage.json b/infra/config/generated/builders/ci/android-x86-code-coverage/targets/chromium.coverage.json
index 2f88cc4..f3ea28a0 100644
--- a/infra/config/generated/builders/ci/android-x86-code-coverage/targets/chromium.coverage.json
+++ b/infra/config/generated/builders/ci/android-x86-code-coverage/targets/chromium.coverage.json
@@ -1645,7 +1645,7 @@
           "--use-cmd-decoder=validating",
           "--use-persistent-shell",
           "--avd-config=../../tools/android/avd/proto/generic_android26.textpb",
-          "--test-launcher-filter-file=../../testing/buildbot/filters/android.emulator_o_p_10.gl_tests.filter",
+          "--test-launcher-filter-file=../../testing/buildbot/filters/android.emulator_10.gl_tests.filter",
           "--gs-results-bucket=chromium-result-details",
           "--recover-devices"
         ],
diff --git a/infra/config/subprojects/chromium/ci/chromium.coverage.star b/infra/config/subprojects/chromium/ci/chromium.coverage.star
index 0f28457..7142ff0 100644
--- a/infra/config/subprojects/chromium/ci/chromium.coverage.star
+++ b/infra/config/subprojects/chromium/ci/chromium.coverage.star
@@ -351,7 +351,7 @@
             # Keep this same as android-oreo-x86-rel
             "gl_tests_validating": targets.mixin(
                 args = [
-                    "--test-launcher-filter-file=../../testing/buildbot/filters/android.emulator_o_p_10.gl_tests.filter",
+                    "--test-launcher-filter-file=../../testing/buildbot/filters/android.emulator_10.gl_tests.filter",
                 ],
             ),
             # Keep this same as android-oreo-x86-rel
diff --git a/internal b/internal
index 75e9277e..56087f1 160000
--- a/internal
+++ b/internal
@@ -1 +1 @@
-Subproject commit 75e9277e77423a7d6d1d97d3d868ac0840932433
+Subproject commit 56087f1ee64af85f055c3dffe1d540ea90626924
diff --git a/ios/chrome/browser/app_launcher/model/app_launcher_browser_agent_unittest.mm b/ios/chrome/browser/app_launcher/model/app_launcher_browser_agent_unittest.mm
index f0ae1c0..4736e45 100644
--- a/ios/chrome/browser/app_launcher/model/app_launcher_browser_agent_unittest.mm
+++ b/ios/chrome/browser/app_launcher/model/app_launcher_browser_agent_unittest.mm
@@ -32,6 +32,7 @@
 #import "testing/gtest/include/gtest/gtest.h"
 #import "testing/platform_test.h"
 #import "third_party/ocmock/OCMock/OCMock.h"
+#import "third_party/ocmock/gtest_support.h"
 #import "url/gurl.h"
 
 using app_launcher_overlays::AllowAppLaunchResponse;
@@ -85,6 +86,9 @@
     [application_ stopMocking];
     CloseAllWebStates(*browser_->GetWebStateList(),
                       WebStateList::CLOSE_NO_FLAGS);
+    EXPECT_OCMOCK_VERIFY(
+        app_launcher_tab_helper_browser_presentation_provider_);
+    EXPECT_OCMOCK_VERIFY(application_);
   }
 
   // Returns the AppLauncherBrowserAgent.
diff --git a/ios/chrome/browser/authentication/ui_bundled/signin/signin_coordinator_egtest.mm b/ios/chrome/browser/authentication/ui_bundled/signin/signin_coordinator_egtest.mm
index cb1361b2..fa68890 100644
--- a/ios/chrome/browser/authentication/ui_bundled/signin/signin_coordinator_egtest.mm
+++ b/ios/chrome/browser/authentication/ui_bundled/signin/signin_coordinator_egtest.mm
@@ -417,6 +417,11 @@
 // Sign-in opened from: tab switcher.
 // Interrupted at: user consent.
 - (void)testDismissSigninFromTabSwitcher {
+  // TODO(crbug.com/422731851): Test is failing on iPad. Re-enable when fixed.
+  if ([ChromeEarlGrey isIPadIdiom]) {
+    EARL_GREY_TEST_DISABLED(@"Failing on iPad.");
+  }
+
   // When Tab Groups is the third panel (i.e. when Tab Group Sync is enabled),
   // Recent Tabs is not reachable from the Tab Grid. So the sign-in flow is not
   // supported with Tab Group Sync enabled.
@@ -432,6 +437,11 @@
 // Sign-in opened from: tab switcher.
 // Interrupted at: identity picker.
 - (void)testDismissSigninFromTabSwitcherFromIdentityPicker {
+  // TODO(crbug.com/422731851): Test is failing on iPad. Re-enable when fixed.
+  if ([ChromeEarlGrey isIPadIdiom]) {
+    EARL_GREY_TEST_DISABLED(@"Failing on iPad.");
+  }
+
   // When Tab Groups is the third panel (i.e. when Tab Group Sync is enabled),
   // Recent Tabs is not reachable from the Tab Grid. So the sign-in flow is not
   // supported with Tab Group Sync enabled.
diff --git a/ios/chrome/browser/autofill/ui_bundled/authentication/otp_input_dialog_mediator_unittest.mm b/ios/chrome/browser/autofill/ui_bundled/authentication/otp_input_dialog_mediator_unittest.mm
index 7271a8e..3738b31 100644
--- a/ios/chrome/browser/autofill/ui_bundled/authentication/otp_input_dialog_mediator_unittest.mm
+++ b/ios/chrome/browser/autofill/ui_bundled/authentication/otp_input_dialog_mediator_unittest.mm
@@ -99,7 +99,7 @@
   EXPECT_OCMOCK_VERIFY((id)consumer_);
 }
 
-// TODO(crbug.com/421873301): re-enable.
+// TODO(crbug.com/422436003): re-enable.
 TEST_P(OtpInputDialogMediatorTest, DISABLED_DidTapConfirmButton) {
   NSString* otp = @"123456";
   OCMExpect([consumer_ showPendingState]);
@@ -117,7 +117,7 @@
   [mediator_->AsMutator() didTapCancelButton];
 }
 
-// TODO(crbug.com/421873301): re-enable
+// TODO(crbug.com/422435813): re-enable
 TEST_P(OtpInputDialogMediatorTest, DISABLED_OnOtpInputChanges) {
   OCMExpect([consumer_ setConfirmButtonEnabled:NO]);
 
diff --git a/ios/chrome/browser/autofill/ui_bundled/manual_fill/BUILD.gn b/ios/chrome/browser/autofill/ui_bundled/manual_fill/BUILD.gn
index 0c550f3..4f00c691 100644
--- a/ios/chrome/browser/autofill/ui_bundled/manual_fill/BUILD.gn
+++ b/ios/chrome/browser/autofill/ui_bundled/manual_fill/BUILD.gn
@@ -351,6 +351,7 @@
     "//ios/chrome/browser/autofill/ui_bundled:eg_test_support+eg2",
     "//ios/chrome/browser/autofill/ui_bundled/form_input_accessory:eg_test_support+eg2",
     "//ios/chrome/browser/autofill/ui_bundled/manual_fill:eg_test_support+eg2",
+    "//ios/chrome/browser/infobars/ui_bundled/banners:public",
     "//ios/chrome/browser/metrics/model:eg_test_support+eg2",
     "//ios/chrome/browser/passwords/ui_bundled/bottom_sheet:eg_test_support+eg2",
     "//ios/chrome/browser/plus_addresses/ui:eg_test_support+eg2",
diff --git a/ios/chrome/browser/autofill/ui_bundled/manual_fill/password_view_controller_egtest.mm b/ios/chrome/browser/autofill/ui_bundled/manual_fill/password_view_controller_egtest.mm
index 3834e86..ac51164 100644
--- a/ios/chrome/browser/autofill/ui_bundled/manual_fill/password_view_controller_egtest.mm
+++ b/ios/chrome/browser/autofill/ui_bundled/manual_fill/password_view_controller_egtest.mm
@@ -8,12 +8,14 @@
 #import "base/test/ios/wait_util.h"
 #import "components/password_manager/core/browser/password_ui_utils.h"
 #import "components/strings/grit/components_strings.h"
+#import "components/sync/base/features.h"
 #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/form_input_accessory/form_input_accessory_app_interface.h"
 #import "ios/chrome/browser/autofill/ui_bundled/manual_fill/manual_fill_constants.h"
 #import "ios/chrome/browser/autofill/ui_bundled/manual_fill/manual_fill_matchers.h"
+#import "ios/chrome/browser/infobars/ui_bundled/banners/infobar_banner_constants.h"
 #import "ios/chrome/browser/metrics/model/metrics_app_interface.h"
 #import "ios/chrome/browser/passwords/ui_bundled/bottom_sheet/password_suggestion_bottom_sheet_app_interface.h"
 #import "ios/chrome/browser/settings/ui_bundled/google_services/manage_sync_settings_constants.h"
@@ -275,6 +277,15 @@
     config.features_disabled.push_back(kIOSKeyboardAccessoryUpgradeForIPad);
   }
 
+  // TODO(crbug.com/371189341): Test fails on device.
+#if TARGET_IPHONE_SIMULATOR
+  if ([self isRunningTest:@selector
+            (testPasswordGenerationFallbackSignedInEncryptionError)]) {
+    config.features_enabled.push_back(
+        syncer::kSyncTrustedVaultInfobarImprovements);
+  }
+#endif  // TARGET_IPHONE_SIMULATOR
+
   return config;
 }
 
@@ -1094,6 +1105,11 @@
 
   [self loadLoginPage];
 
+  // Swipe up the sync infobar error.
+  [[EarlGrey selectElementWithMatcher:grey_accessibilityID(
+                                          kInfobarBannerViewIdentifier)]
+      performAction:grey_swipeFastInDirection(kGREYDirectionUp)];
+
   // Bring up the keyboard.
   [[EarlGrey selectElementWithMatcher:chrome_test_util::WebViewMatcher()]
       performAction:TapWebElementWithId(kFormElementPassword)];
diff --git a/ios/chrome/browser/collaboration/model/messaging/infobar/collaboration_group_infobar_delegate.mm b/ios/chrome/browser/collaboration/model/messaging/infobar/collaboration_group_infobar_delegate.mm
index 6f769f16..a2d3677 100644
--- a/ios/chrome/browser/collaboration/model/messaging/infobar/collaboration_group_infobar_delegate.mm
+++ b/ios/chrome/browser/collaboration/model/messaging/infobar/collaboration_group_infobar_delegate.mm
@@ -206,7 +206,7 @@
   if (attributions.empty()) {
     return std::nullopt;
   }
-  return attributions.front().id;
+  return attributions.at(0).id;
 }
 
 infobars::InfoBarDelegate::InfoBarIdentifier
diff --git a/ios/chrome/browser/ntp/ui_bundled/new_tab_page_mediator.mm b/ios/chrome/browser/ntp/ui_bundled/new_tab_page_mediator.mm
index cc7b158..225633e 100644
--- a/ios/chrome/browser/ntp/ui_bundled/new_tab_page_mediator.mm
+++ b/ios/chrome/browser/ntp/ui_bundled/new_tab_page_mediator.mm
@@ -196,6 +196,17 @@
                                             self.templateURLService)];
   [self.headerConsumer
       setVoiceSearchIsEnabled:ios::provider::IsVoiceSearchEnabled()];
+
+  const TemplateURL* defaultSearchEngine =
+      self.templateURLService->GetDefaultSearchProvider();
+  NSString* dseName =
+      defaultSearchEngine
+          ? [NSString
+                cr_fromString16:defaultSearchEngine
+                                    ->AdjustedShortNameForLocaleDirection()]
+          : @"";
+  [self.headerConsumer setDefaultSearchEngineName:dseName];
+
   [self updateAccountImage];
   [self updateAccountErrorBadge];
   [self startObservingPrefs];
diff --git a/ios/chrome/browser/passwords/model/password_controller_egtest.mm b/ios/chrome/browser/passwords/model/password_controller_egtest.mm
index eae7208..bdbad9ba 100644
--- a/ios/chrome/browser/passwords/model/password_controller_egtest.mm
+++ b/ios/chrome/browser/passwords/model/password_controller_egtest.mm
@@ -23,6 +23,7 @@
 #import "components/plus_addresses/features.h"
 #import "components/policy/core/common/policy_loader_ios_constants.h"
 #import "components/strings/grit/components_strings.h"
+#import "components/sync/base/features.h"
 #import "components/sync/base/user_selectable_type.h"
 #import "components/sync/service/sync_prefs.h"
 #import "ios/chrome/browser/authentication/ui_bundled/signin_earl_grey.h"
@@ -251,6 +252,15 @@
         password_manager::features::kMarkAllCredentialsAsLeaked);
   }
 
+// TODO(crbug.com/371189341): Test fails on device.
+#if TARGET_IPHONE_SIMULATOR
+  if ([self isRunningTest:@selector
+            (testPasswordGenerationWhileSignedInWithError)]) {
+    config.features_enabled.push_back(
+        syncer::kSyncTrustedVaultInfobarImprovements);
+  }
+#endif  // TARGET_IPHONE_SIMULATOR
+
   // The proactive password suggestion bottom sheet isn't tested here, it
   // is tested in its own suite in password_suggestion_egtest.mm.
   config.features_disabled.push_back(
@@ -643,6 +653,11 @@
   [ChromeEarlGrey loadURL:self.testServer->GetURL("/simple_signup_form.html")];
   [ChromeEarlGrey waitForWebStateContainingText:"Signup form."];
 
+  // Swipe up the sync infobar error.
+  [[EarlGrey selectElementWithMatcher:grey_accessibilityID(
+                                          kInfobarBannerViewIdentifier)]
+      performAction:grey_swipeFastInDirection(kGREYDirectionUp)];
+
   // Verify that the target field is empty.
   NSString* emptyFieldCondition =
       [NSString stringWithFormat:@"document.getElementById('%s').value === ''",
diff --git a/ios/chrome/browser/settings/ui_bundled/cells/inline_promo_cell.h b/ios/chrome/browser/settings/ui_bundled/cells/inline_promo_cell.h
index 5b5aae3..67acefc 100644
--- a/ios/chrome/browser/settings/ui_bundled/cells/inline_promo_cell.h
+++ b/ios/chrome/browser/settings/ui_bundled/cells/inline_promo_cell.h
@@ -5,6 +5,7 @@
 #ifndef IOS_CHROME_BROWSER_SETTINGS_UI_BUNDLED_CELLS_INLINE_PROMO_CELL_H_
 #define IOS_CHROME_BROWSER_SETTINGS_UI_BUNDLED_CELLS_INLINE_PROMO_CELL_H_
 
+#import "ios/chrome/browser/shared/ui/elements/new_feature_badge_view.h"
 #import "ios/chrome/browser/shared/ui/table_view/cells/table_view_cell.h"
 
 // TableViewCell with:
@@ -23,6 +24,9 @@
 // Button to dismiss the promo.
 @property(nonatomic, readonly) UIButton* closeButton;
 
+// New feature badge that is overlaying part of the promo image view.
+@property(nonatomic, readonly) NewFeatureBadgeView* badgeView;
+
 // Image view of the cell.
 @property(nonatomic, readonly) UIImageView* promoImageView;
 
diff --git a/ios/chrome/browser/settings/ui_bundled/cells/inline_promo_cell.mm b/ios/chrome/browser/settings/ui_bundled/cells/inline_promo_cell.mm
index 4867922..aaa197c 100644
--- a/ios/chrome/browser/settings/ui_bundled/cells/inline_promo_cell.mm
+++ b/ios/chrome/browser/settings/ui_bundled/cells/inline_promo_cell.mm
@@ -68,9 +68,6 @@
 }  // namespace
 
 @implementation InlinePromoCell {
-  // New feature badge that is overlaying part of the promo image view.
-  NewFeatureBadgeView* _badgeView;
-
   // View containing the image view and the new feature badge.
   UIView* _badgedImageView;
 
diff --git a/ios/chrome/browser/settings/ui_bundled/cells/inline_promo_item.h b/ios/chrome/browser/settings/ui_bundled/cells/inline_promo_item.h
index 0ca31e2..0daf9bb 100644
--- a/ios/chrome/browser/settings/ui_bundled/cells/inline_promo_item.h
+++ b/ios/chrome/browser/settings/ui_bundled/cells/inline_promo_item.h
@@ -30,6 +30,9 @@
 // Whether or not the cell should be configured with its wide layout.
 @property(nonatomic, assign) BOOL shouldHaveWideLayout;
 
+// Whether or not the badge should be displayed. `YES` by default.
+@property(nonatomic, assign) BOOL shouldDisplayBadge;
+
 @end
 
 #endif  // IOS_CHROME_BROWSER_SETTINGS_UI_BUNDLED_CELLS_INLINE_PROMO_ITEM_H_
diff --git a/ios/chrome/browser/settings/ui_bundled/cells/inline_promo_item.mm b/ios/chrome/browser/settings/ui_bundled/cells/inline_promo_item.mm
index fd64c8a..6a8eae1 100644
--- a/ios/chrome/browser/settings/ui_bundled/cells/inline_promo_item.mm
+++ b/ios/chrome/browser/settings/ui_bundled/cells/inline_promo_item.mm
@@ -14,6 +14,7 @@
   if (self) {
     self.cellClass = [InlinePromoCell class];
     _shouldShowCloseButton = YES;
+    _shouldDisplayBadge = YES;
     _enabled = YES;
   }
   return self;
@@ -25,6 +26,7 @@
            withStyler:(ChromeTableViewStyler*)styler {
   [super configureCell:cell withStyler:styler];
   cell.closeButton.hidden = !self.shouldShowCloseButton;
+  cell.badgeView.hidden = !self.shouldDisplayBadge;
   cell.promoImageView.image = self.promoImage;
   cell.promoTextLabel.text = self.promoText;
   cell.enabled = self.enabled;
diff --git a/ios/chrome/browser/settings/ui_bundled/cells/inline_promo_item_unittest.mm b/ios/chrome/browser/settings/ui_bundled/cells/inline_promo_item_unittest.mm
index 1a47505..a06d6ed 100644
--- a/ios/chrome/browser/settings/ui_bundled/cells/inline_promo_item_unittest.mm
+++ b/ios/chrome/browser/settings/ui_bundled/cells/inline_promo_item_unittest.mm
@@ -108,6 +108,26 @@
   EXPECT_TRUE(promo_cell.closeButton.hidden);
 }
 
+// Tests that the badge visibility follows the item's `shouldDisplayBadge`
+// property.
+TEST_F(InlinePromoItemTest, BadgeVisibility) {
+  InlinePromoItem* item = [[InlinePromoItem alloc] initWithType:0];
+  item.promoImage = DefaultSymbolWithPointSize(@"tortoise.fill", 16);
+  item.promoText = @"Test text";
+  item.moreInfoButtonTitle = @"Button Title";
+
+  id cell = [[[item cellClass] alloc] init];
+  InlinePromoCell* promo_cell =
+      base::apple::ObjCCastStrict<InlinePromoCell>(cell);
+
+  [item configureCell:cell withStyler:[[ChromeTableViewStyler alloc] init]];
+  EXPECT_FALSE(promo_cell.badgeView.hidden);
+
+  item.shouldDisplayBadge = NO;
+  [item configureCell:cell withStyler:[[ChromeTableViewStyler alloc] init]];
+  EXPECT_TRUE(promo_cell.badgeView.hidden);
+}
+
 // Tests that the cell is as expected when disabled.
 TEST_F(InlinePromoItemTest, DisableCell) {
   InlinePromoItem* item = [[InlinePromoItem alloc] initWithType:0];
diff --git a/ios/chrome/browser/settings/ui_bundled/password/password_manager_view_controller.mm b/ios/chrome/browser/settings/ui_bundled/password/password_manager_view_controller.mm
index 59352ccb..a9d7008 100644
--- a/ios/chrome/browser/settings/ui_bundled/password/password_manager_view_controller.mm
+++ b/ios/chrome/browser/settings/ui_bundled/password/password_manager_view_controller.mm
@@ -721,6 +721,7 @@
   _trustedVaultWidgetPromoItem =
       [[InlinePromoItem alloc] initWithType:ItemTypeTrustedVaultWidgetPromo];
   _trustedVaultWidgetPromoItem.shouldShowCloseButton = NO;
+  _trustedVaultWidgetPromoItem.shouldDisplayBadge = NO;
   _trustedVaultWidgetPromoItem.promoImage =
       [UIImage imageNamed:kPasswordManagerTrustedVaultWidgetPromoImage];
   _trustedVaultWidgetPromoItem.promoText = l10n_util::GetNSStringF(
diff --git a/ios/chrome/browser/tab_switcher/ui_bundled/BUILD.gn b/ios/chrome/browser/tab_switcher/ui_bundled/BUILD.gn
index baf157f..6202a967 100644
--- a/ios/chrome/browser/tab_switcher/ui_bundled/BUILD.gn
+++ b/ios/chrome/browser/tab_switcher/ui_bundled/BUILD.gn
@@ -18,6 +18,10 @@
   sources = [
     "tab_group_item.h",
     "tab_group_item.mm",
+    "tab_group_item_fetch_info.h",
+    "tab_group_item_fetch_info.mm",
+    "tab_group_item_utils.h",
+    "tab_group_item_utils.mm",
     "tab_snapshot_and_favicon.h",
     "tab_snapshot_and_favicon.mm",
     "tab_snapshot_and_favicon_configurator.h",
diff --git a/ios/chrome/browser/tab_switcher/ui_bundled/tab_grid/grid/base_grid_mediator.mm b/ios/chrome/browser/tab_switcher/ui_bundled/tab_grid/grid/base_grid_mediator.mm
index 22996b8a..27d2890 100644
--- a/ios/chrome/browser/tab_switcher/ui_bundled/tab_grid/grid/base_grid_mediator.mm
+++ b/ios/chrome/browser/tab_switcher/ui_bundled/tab_grid/grid/base_grid_mediator.mm
@@ -2008,9 +2008,11 @@
   return nil;
 }
 
-- (void)fetchTabGroupItemInfo:(TabGroupItem*)tabGroupItem
-                   completion:
-                       (GroupTabSnapshotAndFaviconCompletionBlock)completion {
+- (void)
+    fetchTabGroupItemSnapshotsAndFavicons:(TabGroupItem*)tabGroupItem
+                               completion:
+                                   (GroupTabSnapshotAndFaviconCompletionBlock)
+                                       completion {
   WebStateList* webStateList = self.webStateList;
   // If this is called during a search result, it may contain items from other
   // windows or from the inactive browser.
diff --git a/ios/chrome/browser/tab_switcher/ui_bundled/tab_grid/grid/base_grid_mediator_items_provider.h b/ios/chrome/browser/tab_switcher/ui_bundled/tab_grid/grid/base_grid_mediator_items_provider.h
index 5df5157..951b800f 100644
--- a/ios/chrome/browser/tab_switcher/ui_bundled/tab_grid/grid/base_grid_mediator_items_provider.h
+++ b/ios/chrome/browser/tab_switcher/ui_bundled/tab_grid/grid/base_grid_mediator_items_provider.h
@@ -14,11 +14,11 @@
 class WebStateID;
 }  // namespace web
 
-// Block invoked when a TabSnapshotAndFavicon fetching operation completes. The
-// `groupTabInfos` is nil if the operation failed.
+// Block invoked when a TabSnapshotAndFavicon fetching operation completes.
 typedef void (^GroupTabSnapshotAndFaviconCompletionBlock)(
     TabGroupItem* item,
-    NSArray<TabSnapshotAndFavicon*>* groupTabInfos);
+    NSInteger tabIndex,
+    TabSnapshotAndFavicon* tabSnapshotAndFavicon);
 
 // Protocol allowing to get information of the grid model.
 @protocol BaseGridMediatorItemProvider
@@ -33,11 +33,14 @@
 // Returns the facePile view associated with the `itemID`.
 - (UIView*)facePileViewForItem:(GridItemIdentifier*)itemID;
 
-// Fetches the `tabGroupItem` snapshot and favicon, then executes the given
-// `completion` block.
-- (void)fetchTabGroupItemInfo:(TabGroupItem*)tabGroupItem
-                   completion:
-                       (GroupTabSnapshotAndFaviconCompletionBlock)completion;
+// Fetches snapshots and favicons for the tabs within `tabGroupItem`.
+// The `completion` block is called multiple times, executing each time a
+// snapshot or favicon for an individual tab is fetched.
+- (void)
+    fetchTabGroupItemSnapshotsAndFavicons:(TabGroupItem*)tabGroupItem
+                               completion:
+                                   (GroupTabSnapshotAndFaviconCompletionBlock)
+                                       completion;
 
 @end
 
diff --git a/ios/chrome/browser/tab_switcher/ui_bundled/tab_grid/grid/base_grid_view_controller.mm b/ios/chrome/browser/tab_switcher/ui_bundled/tab_grid/grid/base_grid_view_controller.mm
index ca166e7..bc6ced1 100644
--- a/ios/chrome/browser/tab_switcher/ui_bundled/tab_grid/grid/base_grid_view_controller.mm
+++ b/ios/chrome/browser/tab_switcher/ui_bundled/tab_grid/grid/base_grid_view_controller.mm
@@ -1684,15 +1684,15 @@
   cell.activityLabelData =
       [self.gridProvider activityLabelDataForItem:groupItemIdentifier];
 
-  auto completionBlock = ^(
-      TabGroupItem* innerItem,
-      NSArray<TabSnapshotAndFavicon*>* tabSnapshotsAndFavicons) {
+  auto completionBlock = ^(TabGroupItem* innerItem, NSInteger tabIndex,
+                           TabSnapshotAndFavicon* tabSnapshotAndFavicon) {
     if ([cell.itemIdentifier.tabGroupItem isEqual:innerItem]) {
-      [cell configureWithSnapshotsAndFavicons:tabSnapshotsAndFavicons
-                               totalTabsCount:innerItem.numberOfTabsInGroup];
+      [cell configureTabSnapshotAndFavicon:tabSnapshotAndFavicon
+                                  tabIndex:tabIndex];
     }
   };
-  [self.gridProvider fetchTabGroupItemInfo:item completion:completionBlock];
+  [self.gridProvider fetchTabGroupItemSnapshotsAndFavicons:item
+                                                completion:completionBlock];
 }
 
 // Configures `cell`'s identifier and title synchronously, and favicon and
diff --git a/ios/chrome/browser/tab_switcher/ui_bundled/tab_grid/grid/group_grid_cell.h b/ios/chrome/browser/tab_switcher/ui_bundled/tab_grid/grid/group_grid_cell.h
index 5e5a591..29e0380e 100644
--- a/ios/chrome/browser/tab_switcher/ui_bundled/tab_grid/grid/group_grid_cell.h
+++ b/ios/chrome/browser/tab_switcher/ui_bundled/tab_grid/grid/group_grid_cell.h
@@ -34,15 +34,13 @@
 @property(nonatomic, assign) CGFloat opacity;
 // The current state which the cell should display.
 @property(nonatomic, assign) GridCellState state;
-
 // The face pile, to be set externally.
 @property(nonatomic, strong) UIView* facePile;
 
-// Configures every tab of the group with a given snapshot/favicon pairs and
-// passes the total tabs count to the bottomTrailingView.
-- (void)configureWithSnapshotsAndFavicons:
-            (NSArray<TabSnapshotAndFavicon*>*)snapshotsAndFavicons
-                           totalTabsCount:(NSInteger)totalTabsCount;
+// Assigns a `TabSnapshotAndFavicon` object to a specific `tabIndex`
+- (void)configureTabSnapshotAndFavicon:
+            (TabSnapshotAndFavicon*)tabSnapshotAndFavicon
+                              tabIndex:(NSInteger)tabIndex;
 
 // Returns all tab views that compose this tab group view in the order they're
 // presented.
diff --git a/ios/chrome/browser/tab_switcher/ui_bundled/tab_grid/grid/group_grid_cell.mm b/ios/chrome/browser/tab_switcher/ui_bundled/tab_grid/grid/group_grid_cell.mm
index 8bd4de6..c01adf90 100644
--- a/ios/chrome/browser/tab_switcher/ui_bundled/tab_grid/grid/group_grid_cell.mm
+++ b/ios/chrome/browser/tab_switcher/ui_bundled/tab_grid/grid/group_grid_cell.mm
@@ -29,7 +29,7 @@
 namespace {
 
 // The size of symbol icons.
-NSInteger kIconSymbolPointSize = 13;
+const CGFloat kIconSymbolPointSize = 13;
 
 // Offsets the top and bottom snapshot views.
 const CGFloat kSnapshotViewLeadingOffset = 4;
@@ -89,10 +89,8 @@
     contentView.layer.masksToBounds = YES;
     [self setupTopBar];
     _groupSnapshotsView = [[TabGroupSnapshotsView alloc]
-        initWithTabSnapshotsAndFavicons:nil
-                                   size:0
-                                  light:self.theme == GridThemeLight
-                                   cell:YES];
+        initWithLightInterface:self.theme == GridThemeLight
+                          cell:YES];
     _groupSnapshotsView.translatesAutoresizingMaskIntoConstraints = NO;
 
     _closeTapTargetButton =
@@ -233,6 +231,20 @@
 
 #pragma mark - Public
 
+- (void)configureTabSnapshotAndFavicon:
+            (TabSnapshotAndFavicon*)tabSnapshotAndFavicon
+                              tabIndex:(NSInteger)tabIndex {
+  CHECK_LE(tabIndex, _tabsCount);
+  [_groupSnapshotsView configureTabSnapshotAndFavicon:tabSnapshotAndFavicon
+                                             tabIndex:tabIndex];
+}
+
+- (NSArray<UIView*>*)allGroupTabViews {
+  return [_groupSnapshotsView allGroupTabViews];
+}
+
+#pragma mark - Setters
+
 // Updates the theme to either dark or light. Updating is only done if the
 // current theme is not the desired theme.
 - (void)setTheme:(GridTheme)theme {
@@ -267,24 +279,6 @@
   _groupColor = groupColor;
 }
 
-- (void)configureWithSnapshotsAndFavicons:
-            (NSArray<TabSnapshotAndFavicon*>*)snapshotsAndFavicons
-                           totalTabsCount:(NSInteger)totalTabsCount {
-  CHECK_LE((int)snapshotsAndFavicons.count, totalTabsCount);
-  [_groupSnapshotsView
-      configureTabGroupSnapshotsViewWithTabSnapshotsAndFavicons:
-          snapshotsAndFavicons
-                                                           size:totalTabsCount];
-}
-
-- (NSArray<UIView*>*)allGroupTabViews {
-  return [_groupSnapshotsView allGroupTabViews];
-}
-
-- (void)setTabsCount:(NSInteger)tabsCount {
-  _tabsCount = tabsCount;
-}
-
 - (void)setTitle:(NSString*)title {
   _titleLabel.text = title;
   self.accessibilityLabel = l10n_util::GetNSStringF(
@@ -319,6 +313,11 @@
   [self updateTopBarConstraints];
 }
 
+- (void)setTabsCount:(NSInteger)tabsCount {
+  _tabsCount = tabsCount;
+  _groupSnapshotsView.tabsCount = tabsCount;
+}
+
 #pragma mark - Private
 
 // Sets up the top bar with icon, title, and close button.
diff --git a/ios/chrome/browser/tab_switcher/ui_bundled/tab_grid/tab_groups/create_tab_group_mediator.mm b/ios/chrome/browser/tab_switcher/ui_bundled/tab_grid/tab_groups/create_tab_group_mediator.mm
index 1053244..0cf17e7b 100644
--- a/ios/chrome/browser/tab_switcher/ui_bundled/tab_grid/tab_groups/create_tab_group_mediator.mm
+++ b/ios/chrome/browser/tab_switcher/ui_bundled/tab_grid/tab_groups/create_tab_group_mediator.mm
@@ -45,8 +45,6 @@
   raw_ptr<WebStateList> _webStateList;
   // Tab group to edit.
   raw_ptr<const TabGroup> _tabGroup;
-  // Array of all snapshots and favicons of the group.
-  NSMutableArray<TabSnapshotAndFavicon*>* _tabSnapshotsAndFavicons;
   // Item to fetch pictures.
   TabGroupItem* _groupItem;
   // Helper class to configure tab item images.
@@ -79,14 +77,13 @@
     _consumer = consumer;
     [_consumer setDefaultGroupColor:TabGroup::DefaultColorForNewTabGroup(
                                         _webStateList)];
+    [_consumer setTabsCount:_identifiers.size()];
     _tabImagesConfigurator =
         std::make_unique<TabSnapshotAndFaviconConfigurator>(faviconLoader);
     ProfileIOS* profile = browser->GetProfile();
     BrowserList* browserList = BrowserListFactory::GetForProfile(profile);
 
-    _tabSnapshotsAndFavicons = [[NSMutableArray alloc] init];
-
-    NSUInteger numberOfRequestedImages = 0;
+    NSInteger numberOfRequestedImages = 0;
     for (web::WebStateID identifier : identifiers) {
       if (numberOfRequestedImages >= 7) {
         break;
@@ -111,12 +108,13 @@
                              WebStateSearchCriteria{.identifier = identifier});
       }
 
+      NSInteger tabIndexRequested = numberOfRequestedImages;
       __weak CreateTabGroupMediator* weakSelf = self;
       _tabImagesConfigurator->FetchSingleSnapshotAndFaviconFromWebState(
           currentWebStateList->GetWebStateAt(index),
           ^(TabSnapshotAndFavicon* tabSnapshotAndFavicon) {
-            [weakSelf addTabSnapshotAndFavicon:tabSnapshotAndFavicon];
-            [weakSelf updateConsumer];
+            [weakSelf configureTabSnapshotAndFavicon:tabSnapshotAndFavicon
+                                            tabIndex:tabIndexRequested];
           });
       numberOfRequestedImages++;
     }
@@ -149,13 +147,6 @@
         std::make_unique<TabSnapshotAndFaviconConfigurator>(faviconLoader);
 
     __weak CreateTabGroupMediator* weakSelf = self;
-    _tabImagesConfigurator->FetchSnapshotAndFaviconForTabGroupItem(
-        _groupItem, _webStateList,
-        ^(TabGroupItem* item,
-          NSArray<TabSnapshotAndFavicon*>* tabSnapshotsAndFavicons) {
-          [weakSelf setTabSnapshotsAndFavicons:tabSnapshotsAndFavicons];
-          [weakSelf updateConsumer];
-        });
 
     // Do not use the helper to get the following values as the title helper do
     // not return nil but the number of tabs. In this case, we want nil so it do
@@ -163,6 +154,15 @@
     tab_groups::TabGroupVisualData visualData = _tabGroup->visual_data();
     [_consumer setDefaultGroupColor:visualData.color()];
     [_consumer setGroupTitle:base::SysUTF16ToNSString(visualData.title())];
+    [_consumer setTabsCount:_tabGroup->range().count()];
+
+    _tabImagesConfigurator->FetchSnapshotAndFaviconForTabGroupItem(
+        _groupItem, _webStateList,
+        ^(TabGroupItem* item, NSInteger tabIndex,
+          TabSnapshotAndFavicon* tabSnapshotAndFavicon) {
+          [weakSelf configureTabSnapshotAndFavicon:tabSnapshotAndFavicon
+                                          tabIndex:tabIndex];
+        });
   }
   return self;
 }
@@ -253,27 +253,13 @@
   }
 }
 
-#pragma mark - Private helpers
+#pragma mark - Private
 
-// Adds the given `tabSnapshotAndFavicon` to the GroupTabInfo array.
-- (void)addTabSnapshotAndFavicon:(TabSnapshotAndFavicon*)tabSnapshotAndFavicon {
-  [_tabSnapshotsAndFavicons addObject:tabSnapshotAndFavicon];
-}
-
-// Sets the _tabSnapshotsAndFavicons array with `tabSnapshotsAndFavicons`.
-- (void)setTabSnapshotsAndFavicons:
-    (NSArray<TabSnapshotAndFavicon*>*)tabSnapshotsAndFavicons {
-  _tabSnapshotsAndFavicons =
-      [[NSMutableArray alloc] initWithArray:tabSnapshotsAndFavicons];
-}
-
-// Sends to the consumer the needed pictures and the number of items to display
-// it properly.
-- (void)updateConsumer {
-  NSInteger numberOfItem =
-      _tabGroup ? _tabGroup->range().count() : _identifiers.size();
-  [_consumer setTabSnapshotsAndFavicons:_tabSnapshotsAndFavicons
-                  numberOfSelectedItems:numberOfItem];
+// Configures the `tabSnapshotAndFavicon` for the tab at `tabIndex`.
+- (void)configureTabSnapshotAndFavicon:
+            (TabSnapshotAndFavicon*)tabSnapshotAndFavicon
+                              tabIndex:(NSInteger)tabIndex {
+  [_consumer setSnapshotAndFavicon:tabSnapshotAndFavicon tabIndex:tabIndex];
 }
 
 @end
diff --git a/ios/chrome/browser/tab_switcher/ui_bundled/tab_grid/tab_groups/create_tab_group_mediator_unittest.mm b/ios/chrome/browser/tab_switcher/ui_bundled/tab_grid/tab_groups/create_tab_group_mediator_unittest.mm
index 1fd6310..a236d2fc 100644
--- a/ios/chrome/browser/tab_switcher/ui_bundled/tab_grid/tab_groups/create_tab_group_mediator_unittest.mm
+++ b/ios/chrome/browser/tab_switcher/ui_bundled/tab_grid/tab_groups/create_tab_group_mediator_unittest.mm
@@ -20,12 +20,14 @@
 - (void)setDefaultGroupColor:(tab_groups::TabGroupColorId)color {
 }
 
-- (void)setTabSnapshotsAndFavicons:
-            (NSArray<TabSnapshotAndFavicon*>*)tabSnapshotsAndFavicons
-             numberOfSelectedItems:(NSInteger)numberOfSelectedItems {
+- (void)setGroupTitle:(NSString*)title {
 }
 
-- (void)setGroupTitle:(NSString*)title {
+- (void)setSnapshotAndFavicon:(TabSnapshotAndFavicon*)tabSnapshotAndFavicon
+                     tabIndex:(NSInteger)tabIndex {
+}
+
+- (void)setTabsCount:(NSInteger)tabsCount {
 }
 
 @end
diff --git a/ios/chrome/browser/tab_switcher/ui_bundled/tab_grid/tab_groups/create_tab_group_view_controller.mm b/ios/chrome/browser/tab_switcher/ui_bundled/tab_grid/tab_groups/create_tab_group_view_controller.mm
index 7eefa92..9b7970f 100644
--- a/ios/chrome/browser/tab_switcher/ui_bundled/tab_grid/tab_groups/create_tab_group_view_controller.mm
+++ b/ios/chrome/browser/tab_switcher/ui_bundled/tab_grid/tab_groups/create_tab_group_view_controller.mm
@@ -115,16 +115,14 @@
   UIButton* _selectedButton;
   // Default color.
   tab_groups::TabGroupColorId _defaultColor;
-  // Array of all snapshots and favicons of the group.
-  NSArray<TabSnapshotAndFavicon*>* _tabSnapshotsAndFavicons;
   // Snapshots views container.
   UIView* _snapshotsContainer;
   // Whether it is to edit a group (vs creation).
   BOOL _editMode;
   // Whether the user is syncing tabs.
   BOOL _tabSynced;
-  // Number of selected items.
-  NSInteger _numberOfSelectedItems;
+  // Number of tabs in the group.
+  NSInteger _tabsCount;
   // Title of the group.
   NSString* _title;
 
@@ -155,6 +153,14 @@
     _editMode = editMode;
     _tabSynced = tabSynced;
 
+    // Create the `_snapshotsView` early. Favicon and snapshot fetches begin
+    // before the view loads, and `_snapshotsView` is updated incrementally as
+    // each item is fetched.
+    _snapshotsView = [[TabGroupSnapshotsView alloc]
+        initWithLightInterface:self.traitCollection.userInterfaceStyle ==
+                               UIUserInterfaceStyleLight
+                          cell:NO];
+
     [self createColorSelectionButtons];
     CHECK_NE([_colorSelectionButtons count], 0u)
         << "The available color list for tab group is empty.";
@@ -780,13 +786,6 @@
   snapshotsBackground.layer.cornerRadius = kSnapshotViewCornerRadius;
   snapshotsBackground.opaque = NO;
 
-  _snapshotsView = [[TabGroupSnapshotsView alloc]
-      initWithTabSnapshotsAndFavicons:_tabSnapshotsAndFavicons
-                                 size:_numberOfSelectedItems
-                                light:self.traitCollection.userInterfaceStyle ==
-                                      UIUserInterfaceStyleLight
-                                 cell:NO];
-
   [snapshotsBackground addSubview:_snapshotsView];
 
   NSLayoutConstraint* backgroundHeightConstraint =
@@ -833,7 +832,7 @@
 
 // Activates or deactivates the appropriate constraints.
 - (void)applyConstraints {
-  if (_numberOfSelectedItems == 1) {
+  if (_tabsCount == 1) {
     [NSLayoutConstraint deactivateConstraints:_multipleSnapshotsConstraints];
     [NSLayoutConstraint activateConstraints:_singleSnapshotConstraints];
   } else {
@@ -865,23 +864,21 @@
   _defaultColor = color;
 }
 
-- (void)setTabSnapshotsAndFavicons:
-            (NSArray<TabSnapshotAndFavicon*>*)tabSnapshotsAndFavicons
-             numberOfSelectedItems:(NSInteger)numberOfSelectedItems {
-  _tabSnapshotsAndFavicons = tabSnapshotsAndFavicons;
-  _numberOfSelectedItems = numberOfSelectedItems;
-  [_snapshotsView
-      configureTabGroupSnapshotsViewWithTabSnapshotsAndFavicons:
-          tabSnapshotsAndFavicons
-                                                           size:
-                                                               _numberOfSelectedItems];
-  [self applyConstraints];
-}
-
 - (void)setGroupTitle:(NSString*)title {
   _title = title;
 }
 
+- (void)setSnapshotAndFavicon:(TabSnapshotAndFavicon*)tabSnapshotAndFavicon
+                     tabIndex:(NSInteger)tabIndex {
+  [_snapshotsView configureTabSnapshotAndFavicon:tabSnapshotAndFavicon
+                                        tabIndex:tabIndex];
+}
+
+- (void)setTabsCount:(NSInteger)tabsCount {
+  _snapshotsView.tabsCount = tabsCount;
+  _tabsCount = tabsCount;
+}
+
 #pragma mark - Accessibility
 
 - (BOOL)accessibilityPerformEscape {
diff --git a/ios/chrome/browser/tab_switcher/ui_bundled/tab_grid/tab_groups/group_tab_view.h b/ios/chrome/browser/tab_switcher/ui_bundled/tab_grid/tab_groups/group_tab_view.h
index a21754a..7322424 100644
--- a/ios/chrome/browser/tab_switcher/ui_bundled/tab_grid/tab_groups/group_tab_view.h
+++ b/ios/chrome/browser/tab_switcher/ui_bundled/tab_grid/tab_groups/group_tab_view.h
@@ -8,16 +8,20 @@
 #import <UIKit/UIKit.h>
 
 // The GroupTabView is a UIView that displays combinations of snapshots,
-// favicons and a label displaying the remaining unshowed tabs in a group. It is
-// used in 3 different configurations, this view can be reconfigured without the
-// need to be recreated.
+// favicons, and a label indicating the number of remaining unsent tabs in a
+// group. This view can be reused and reconfigured for different display modes
+// without needing to be recreated. When reusing an instance,
+// `hideAllAttribute:s` must be called to clear its previous content.
+//
 // - Snapshot configuration: in this configuration, `GroupTabView` is similar to
 // TopAlignedImageView. The associated favicon is placed on bottom right on top
 // of the snapshot.
+//
 // - Favicons configuration: in this configuration, `GroupTabView` will display
 // from 1 to 4 "favicon view" disposed on square shape. The "favicon view" is a
 // UIImageView with the favicon as its image. The image will be centered. This
 // configuration is called only when there is only favicons to display.
+//
 // - Favicons with tabs num configuration: When the number of tabs in a group
 // exceeds 7, GroupTabView will display the same configuration as the favicons
 // one, but the last element is a UILabel with different configurations (+N when
@@ -33,13 +37,15 @@
 // be nil.
 - (void)configureWithSnapshot:(UIImage*)snapshot favicon:(UIImage*)favicon;
 
-// Configures the view with the given list of `favicons`.
-- (void)configureWithFavicons:(NSArray<UIImage*>*)favicons;
+// Configures the view with the given `favicon`.
+- (void)configureWithFavicon:(UIImage*)favicon
+                faviconIndex:(NSInteger)faviconIndex;
 
-// Configures the view with the given list of favicons and the number of
-// elements left. `favicons` must have 3 elements.
-- (void)configureWithFavicons:(NSArray<UIImage*>*)favicons
-          remainingTabsNumber:(NSInteger)remainingTabsNumber;
+// Configures the view with the given `favicon` and the number of
+// elements left.
+- (void)configureWithFavicon:(UIImage*)favicon
+                faviconIndex:(NSInteger)faviconIndex
+         remainingTabsNumber:(NSInteger)remainingTabsNumber;
 
 // Hides all the views/label and clears their contents (image/attributedText).
 - (void)hideAllAttributes;
diff --git a/ios/chrome/browser/tab_switcher/ui_bundled/tab_grid/tab_groups/group_tab_view.mm b/ios/chrome/browser/tab_switcher/ui_bundled/tab_grid/tab_groups/group_tab_view.mm
index 166c892..3f5fd2b 100644
--- a/ios/chrome/browser/tab_switcher/ui_bundled/tab_grid/tab_groups/group_tab_view.mm
+++ b/ios/chrome/browser/tab_switcher/ui_bundled/tab_grid/tab_groups/group_tab_view.mm
@@ -195,28 +195,27 @@
 - (void)configureWithSnapshot:(UIImage*)snapshot favicon:(UIImage*)favicon {
   [self hideAllAttributes];
   _snapshotView.image = snapshot;
+  _snapshotView.hidden = NO;
   if (favicon && !CGSizeEqualToSize(favicon.size, CGSizeZero)) {
     _snapshotFaviconImageView.image = favicon;
     _snapshotFaviconView.hidden = NO;
   }
-  _snapshotView.hidden = NO;
 }
 
-- (void)configureWithFavicons:(NSArray<UIImage*>*)favicons {
-  [self hideAllAttributes];
-  CHECK_LE([favicons count], [_viewList count]);
-
-  for (NSUInteger i = 0; i < [favicons count]; ++i) {
-    _viewList[i].hidden = NO;
-    _imageViewList[i].hidden = NO;
-    _imageViewList[i].image = favicons[i];
-  }
+- (void)configureWithFavicon:(UIImage*)favicon
+                faviconIndex:(NSInteger)faviconIndex {
+  _viewList[faviconIndex].hidden = NO;
+  _imageViewList[faviconIndex].hidden = NO;
+  _imageViewList[faviconIndex].image = favicon;
 }
 
-- (void)configureWithFavicons:(NSArray<UIImage*>*)favicons
-          remainingTabsNumber:(NSInteger)remainingTabsNumber {
-  [self configureWithFavicons:favicons];
-  _viewList[3].hidden = NO;
+- (void)configureWithFavicon:(UIImage*)favicon
+                faviconIndex:(NSInteger)faviconIndex
+         remainingTabsNumber:(NSInteger)remainingTabsNumber {
+  CHECK_LT(faviconIndex, 3)
+      << "The last favicon slot is reserved for the '+N' indicator.";
+  [self configureWithFavicon:favicon faviconIndex:faviconIndex];
+  _viewList.lastObject.hidden = NO;
   _bottomTrailingLabel.hidden = NO;
   _bottomTrailingLabel.attributedText = TextForTabGroupCount(
       static_cast<int>(remainingTabsNumber), kTabGridButtonFontSize);
diff --git a/ios/chrome/browser/tab_switcher/ui_bundled/tab_grid/tab_groups/tab_group_creation_consumer.h b/ios/chrome/browser/tab_switcher/ui_bundled/tab_grid/tab_groups/tab_group_creation_consumer.h
index fe1c0b70..955c3b1 100644
--- a/ios/chrome/browser/tab_switcher/ui_bundled/tab_grid/tab_groups/tab_group_creation_consumer.h
+++ b/ios/chrome/browser/tab_switcher/ui_bundled/tab_grid/tab_groups/tab_group_creation_consumer.h
@@ -15,13 +15,17 @@
 
 // Sets the default group color. Should be called before viewDidLoad.
 - (void)setDefaultGroupColor:(tab_groups::TabGroupColorId)color;
-// Sets snapshots, favicons and the total number of selected items.
-- (void)setTabSnapshotsAndFavicons:
-            (NSArray<TabSnapshotAndFavicon*>*)tabSnapshotsAndFavicons
-             numberOfSelectedItems:(NSInteger)numberOfSelectedItems;
+
 // Sets the default group color. Should be called before viewDidLoad.
 - (void)setGroupTitle:(NSString*)title;
 
+// Sets the `tabSnapshotAndFavicon` at `tabIndex`.
+- (void)setSnapshotAndFavicon:(TabSnapshotAndFavicon*)tabSnapshotAndFavicon
+                     tabIndex:(NSInteger)tabIndex;
+
+// Sets the number of tabs in the group.
+- (void)setTabsCount:(NSInteger)tabsCount;
+
 @end
 
 #endif  // IOS_CHROME_BROWSER_TAB_SWITCHER_UI_BUNDLED_TAB_GRID_TAB_GROUPS_TAB_GROUP_CREATION_CONSUMER_H_
diff --git a/ios/chrome/browser/tab_switcher/ui_bundled/tab_grid/tab_groups/tab_group_snapshots_view.h b/ios/chrome/browser/tab_switcher/ui_bundled/tab_grid/tab_groups/tab_group_snapshots_view.h
index a219515..13b08ffe 100644
--- a/ios/chrome/browser/tab_switcher/ui_bundled/tab_grid/tab_groups/tab_group_snapshots_view.h
+++ b/ios/chrome/browser/tab_switcher/ui_bundled/tab_grid/tab_groups/tab_group_snapshots_view.h
@@ -10,20 +10,24 @@
 @class GroupTabView;
 @class TabSnapshotAndFavicon;
 
-// View controller that manages the tab group sample view with multiples
-// snapshots.
+// View that manages the tab group sample view with multiples snapshots.
 @interface TabGroupSnapshotsView : UIView
 
-- (instancetype)initWithTabSnapshotsAndFavicons:
-                    (NSArray<TabSnapshotAndFavicon*>*)tabSnapshotsAndFavicons
-                                           size:(NSUInteger)size
-                                          light:(BOOL)isLight
-                                           cell:(BOOL)isCell;
+// Number of tabs displayed in the snapshot
+@property(nonatomic, assign) NSInteger tabsCount;
 
-- (void)configureTabGroupSnapshotsViewWithTabSnapshotsAndFavicons:
-            (NSArray<TabSnapshotAndFavicon*>*)tabGroupInfos
-                                                             size:(NSUInteger)
-                                                                      size;
+// Initializes the view.
+// - `isLight`: `YES` for light UI interface.
+// - `isCell`: `YES` if the view is used as a cell.
+- (instancetype)initWithLightInterface:(BOOL)isLight
+                                  cell:(BOOL)isCell NS_DESIGNATED_INITIALIZER;
+
+- (instancetype)initWithFrame:(CGRect)frame NS_UNAVAILABLE;
+- (instancetype)initWithCoder:(NSCoder*)coder NS_UNAVAILABLE;
+
+- (void)configureTabSnapshotAndFavicon:
+            (TabSnapshotAndFavicon*)tabSnapshotAndFavicon
+                              tabIndex:(NSInteger)tabIndex;
 
 // Returns all tab views that compose this tab group view in the order they're
 // presented.
diff --git a/ios/chrome/browser/tab_switcher/ui_bundled/tab_grid/tab_groups/tab_group_snapshots_view.mm b/ios/chrome/browser/tab_switcher/ui_bundled/tab_grid/tab_groups/tab_group_snapshots_view.mm
index ba9825e..b92a4da 100644
--- a/ios/chrome/browser/tab_switcher/ui_bundled/tab_grid/tab_groups/tab_group_snapshots_view.mm
+++ b/ios/chrome/browser/tab_switcher/ui_bundled/tab_grid/tab_groups/tab_group_snapshots_view.mm
@@ -8,66 +8,59 @@
 #import "ios/chrome/browser/shared/ui/elements/top_aligned_image_view.h"
 #import "ios/chrome/browser/shared/ui/util/uikit_ui_util.h"
 #import "ios/chrome/browser/tab_switcher/ui_bundled/tab_grid/tab_groups/group_tab_view.h"
+#import "ios/chrome/browser/tab_switcher/ui_bundled/tab_group_item_utils.h"
 #import "ios/chrome/browser/tab_switcher/ui_bundled/tab_snapshot_and_favicon.h"
 #import "ios/chrome/common/ui/colors/semantic_color_names.h"
 #import "ios/chrome/common/ui/elements/gradient_view.h"
 #import "ios/chrome/common/ui/util/constraints_ui_util.h"
 
 namespace {
+
 constexpr CGFloat kSpacing = 4;
 constexpr CGFloat kFinalViewCornerRadius = 16;
+
+// Total number of GroupTabView in this view.
+constexpr NSInteger kGroupTabViewCount = 4;
+// Number of GroupTabView displayed per line.
+constexpr NSInteger kGroupTabViewLineCount = 2;
+// Maximum number of favicons visible in the summary tile when some tabs are
+// hidden behind "+N" additional tabs.
+constexpr NSInteger kMaxSummaryFaviconVisible = 3;
+
 }  // namespace
 
 @implementation TabGroupSnapshotsView {
   BOOL _isLight;
   BOOL _isCell;
-  NSArray<TabSnapshotAndFavicon*>* _tabSnapshotsAndFavicons;
-  NSUInteger _tabGroupTabNumber;
+  NSMutableArray<TabSnapshotAndFavicon*>* _tabSnapshotsAndFavicons;
   UIStackView* _firstLine;
   UIStackView* _secondLine;
   GroupTabView* _singleView;
 }
 
-- (instancetype)initWithTabSnapshotsAndFavicons:
-                    (NSArray<TabSnapshotAndFavicon*>*)tabSnapshotsAndFavicons
-                                           size:(NSUInteger)size
-                                          light:(BOOL)isLight
-                                           cell:(BOOL)isCell {
+- (instancetype)initWithLightInterface:(BOOL)isLight cell:(BOOL)isCell {
   self = [super initWithFrame:CGRectZero];
   if (self) {
-    CHECK_LE([tabSnapshotsAndFavicons count], size);
     self.translatesAutoresizingMaskIntoConstraints = NO;
     _isLight = isLight;
     _isCell = isCell;
 
     NSMutableArray<GroupTabView*>* finalViews =
-        [[NSMutableArray alloc] initWithArray:@[
-          [[GroupTabView alloc] initWithIsCell:isCell],
-          [[GroupTabView alloc] initWithIsCell:isCell],
-          [[GroupTabView alloc] initWithIsCell:isCell],
-          [[GroupTabView alloc] initWithIsCell:isCell]
-        ]];
+        [[NSMutableArray alloc] initWithCapacity:kGroupTabViewCount];
+    for (NSInteger i = 0; i < kGroupTabViewCount; ++i) {
+      [finalViews addObject:[[GroupTabView alloc] initWithIsCell:isCell]];
+    }
 
     UIStackView* snapshotsView = [self squaredViews:finalViews];
 
     [self addSubview:snapshotsView];
     AddSameConstraints(snapshotsView, self);
 
-    [self configureTabGroupSnapshotsViewWithTabSnapshotsAndFavicons:
-              tabSnapshotsAndFavicons
-                                                               size:size];
-
     if (!_isCell) {
       _singleView = [[GroupTabView alloc] initWithIsCell:_isCell];
       _singleView.translatesAutoresizingMaskIntoConstraints = NO;
-      [_singleView
-          configureWithSnapshot:tabSnapshotsAndFavicons.firstObject.snapshot
-                        favicon:tabSnapshotsAndFavicons.firstObject.favicon];
       [self addSubview:_singleView];
       AddSameConstraints(_singleView, self);
-      _singleView.hidden = size > 1;
-      _firstLine.hidden = size == 1;
-      _secondLine.hidden = size == 1;
     }
 
     if (@available(iOS 17, *)) {
@@ -83,35 +76,112 @@
       arrayByAddingObjectsFromArray:_secondLine.arrangedSubviews];
 }
 
-#pragma mark - Private Helpers
+#pragma mark - Public
 
-// Returns a range computed with `start` index, `length` and the tab group's
-// tabs number. To compute the range, it compute if there is element left or
-// not. For example, by default we have a range of 3 element, but if there is
-// only 4 element in total, then the range will take the last one, but if there
-// is 5 element in total, then the range will only take 3 elements.
-- (NSRange)computedRangeStartIndex:(NSUInteger)start
-          lengthWithoutLastElement:(NSUInteger)length {
-  NSUInteger computedNumberOfElement = start + length + 1;
-  if (computedNumberOfElement == _tabGroupTabNumber &&
-      computedNumberOfElement <= [_tabSnapshotsAndFavicons count]) {
-    length += 1;
-  }
-  return NSMakeRange(start, length);
+- (void)configureTabSnapshotAndFavicon:
+            (TabSnapshotAndFavicon*)tabSnapshotAndFavicon
+                              tabIndex:(NSInteger)tabIndex {
+  _tabSnapshotsAndFavicons[tabIndex] = tabSnapshotAndFavicon;
+  [self configureTabSnapshotAndFaviconForTabIndex:tabIndex];
 }
 
-// Returns the list of favicons pictures from the given array of `infos`.
-- (NSMutableArray<UIImage*>*)faviconsFromRange:(NSRange)range {
-  NSMutableArray<UIImage*>* faviconsSubArray = [[NSMutableArray alloc] init];
-  for (TabSnapshotAndFavicon* info :
-       [_tabSnapshotsAndFavicons subarrayWithRange:range]) {
-    if (info.favicon) {
-      [faviconsSubArray addObject:info.favicon];
-    } else {
-      [faviconsSubArray addObject:[[UIImage alloc] init]];
-    }
+#pragma mark - Setters
+
+- (void)setTabsCount:(NSInteger)tabsCount {
+  _tabsCount = tabsCount;
+
+  // Reinitialize `_tabSnapshotsAndFavicons`.
+  _tabSnapshotsAndFavicons =
+      [[NSMutableArray alloc] initWithCapacity:_tabsCount];
+  for (NSInteger i = 0; i < _tabsCount; i++) {
+    [_tabSnapshotsAndFavicons addObject:[[TabSnapshotAndFavicon alloc] init]];
   }
-  return faviconsSubArray;
+  [self updateViews];
+}
+
+#pragma mark - Private Helpers
+
+// Configures the GroupTabView for the tab at `tabIndex`.
+- (void)configureTabSnapshotAndFaviconForTabIndex:(NSInteger)tabIndex {
+  CHECK(tabIndex < _tabsCount);
+
+  TabSnapshotAndFavicon* tabSnapshotAndFavicon =
+      _tabSnapshotsAndFavicons[tabIndex];
+
+  // If `_singleView` is visible, configure it.
+  if (_singleView && !_singleView.hidden) {
+    CHECK_EQ(_tabsCount, 1);
+    CHECK_EQ(tabIndex, 0);
+    [_singleView configureWithSnapshot:tabSnapshotAndFavicon.snapshot
+                               favicon:tabSnapshotAndFavicon.favicon];
+    return;
+  }
+
+  NSArray<GroupTabView*>* groupViews = [self allGroupTabViews];
+  BOOL compactHeight = [self compactHeight];
+  bool isOnSummaryTile =
+      IsTabOnSummaryTile(tabIndex, _tabsCount, compactHeight);
+
+  // Configure the view with the snapshot and the favicon.
+  if (!isOnSummaryTile) {
+    GroupTabView* view = groupViews[tabIndex];
+    [view configureWithSnapshot:tabSnapshotAndFavicon.snapshot
+                        favicon:tabSnapshotAndFavicon.favicon];
+    return;
+  }
+
+  NSInteger faviconIndex =
+      SummaryFaviconSlotForTabIndex(tabIndex, _tabsCount, compactHeight);
+
+  // This favicon is skipped if its slot is reserved for the '+N' indicator
+  // when the total number of tabs exceeds the maximum individual visuals.
+  // For instance, a group with 7 tabs shows all favicons, but with 8 tabs,
+  // the last two favicons are hidden in favor of the '+N' indicator.
+  if (_tabsCount > MaxIndividualTabVisuals(compactHeight) &&
+      faviconIndex >= kMaxSummaryFaviconVisible) {
+    return;
+  }
+
+  GroupTabView* summaryView = compactHeight
+                                  ? groupViews[kGroupTabViewLineCount - 1]
+                                  : groupViews[kGroupTabViewCount - 1];
+  NSInteger hiddenTabsCount = SummaryHiddenTabsCount(_tabsCount, compactHeight);
+
+  // Configure the view with only the favicon if all favions can be displayed
+  // in the remaning view.
+  if (hiddenTabsCount == 0) {
+    [summaryView configureWithFavicon:tabSnapshotAndFavicon.favicon
+                         faviconIndex:faviconIndex];
+    return;
+  }
+
+  // Otherwise configure the view with the favicon and the remaining tabs
+  // number.
+  [summaryView configureWithFavicon:tabSnapshotAndFavicon.favicon
+                       faviconIndex:faviconIndex
+                remainingTabsNumber:hiddenTabsCount];
+}
+
+// Updates view properties and reconfigures sub views.
+- (void)updateViews {
+  if (!_isCell && (_tabsCount == 1)) {
+    _singleView.hidden = NO;
+    _firstLine.hidden = YES;
+    _secondLine.hidden = YES;
+  } else {
+    _singleView.hidden = YES;
+    _firstLine.hidden = NO;
+    _secondLine.hidden = [self compactHeight];
+  }
+  // Reinitialize group views attributes.
+  for (GroupTabView* view in [self allGroupTabViews]) {
+    [view hideAllAttributes];
+  }
+
+  // Reconfigure group views for each tabs.
+  for (NSInteger index = 0; index < _tabsCount; index++) {
+    [self configureTabSnapshotAndFaviconForTabIndex:index];
+  }
 }
 
 // Returns a configured stack view that correspond to a line in the final
@@ -128,12 +198,11 @@
 // Returns a stack view that put the views, given in parameters, aligned in
 // square.
 - (UIStackView*)squaredViews:(NSMutableArray<GroupTabView*>*)views {
-  CHECK_EQ([views count], 4u);
   _firstLine = [self lineViews];
   _secondLine = [self lineViews];
 
-  for (NSUInteger i = 0; i < 4; i++) {
-    if (i < 2) {
+  for (NSInteger i = 0; i < kGroupTabViewCount; i++) {
+    if (i < kGroupTabViewLineCount) {
       [_firstLine addArrangedSubview:views[i]];
     } else {
       [_secondLine addArrangedSubview:views[i]];
@@ -163,68 +232,6 @@
          _isCell;
 }
 
-- (void)configureTabGroupSnapshotsViewWithTabSnapshotsAndFavicons:
-            (NSArray<TabSnapshotAndFavicon*>*)tabSnapshotsAndFavicons
-                                                             size:(NSUInteger)
-                                                                      size {
-  _tabSnapshotsAndFavicons = tabSnapshotsAndFavicons;
-  _tabGroupTabNumber = size;
-  if (!_isCell && (size == 1)) {
-    _singleView.hidden = NO;
-    _firstLine.hidden = YES;
-    _secondLine.hidden = YES;
-    [_singleView
-        configureWithSnapshot:_tabSnapshotsAndFavicons.firstObject.snapshot
-                      favicon:_tabSnapshotsAndFavicons.firstObject.favicon];
-  } else {
-    _singleView.hidden = YES;
-    _firstLine.hidden = NO;
-    _secondLine.hidden = NO;
-    [self updateViews];
-  }
-}
-
-- (void)updateViews {
-  NSRange snapshotsViewRange = [self
-       computedRangeStartIndex:0
-      lengthWithoutLastElement:([self compactHeight]
-                                    ? MIN(1, [_tabSnapshotsAndFavicons count])
-                                    : MIN(3,
-                                          [_tabSnapshotsAndFavicons count]))];
-  NSRange faviconsViewRange =
-      [self computedRangeStartIndex:NSMaxRange(snapshotsViewRange)
-           lengthWithoutLastElement:MIN(3, [_tabSnapshotsAndFavicons count] -
-                                               NSMaxRange(snapshotsViewRange))];
-
-  _secondLine.hidden = [self compactHeight];
-
-  NSUInteger index = snapshotsViewRange.location;
-  for (GroupTabView* view in [self allGroupTabViews]) {
-    if (index >= [_tabSnapshotsAndFavicons count]) {
-      [view hideAllAttributes];
-      continue;
-    }
-
-    TabSnapshotAndFavicon* tabSnapshotAndFavicon =
-        _tabSnapshotsAndFavicons[index];
-    if (index < NSMaxRange(snapshotsViewRange)) {
-      [view configureWithSnapshot:tabSnapshotAndFavicon.snapshot
-                          favicon:tabSnapshotAndFavicon.favicon];
-    } else if (index < _tabGroupTabNumber) {
-      NSMutableArray<UIImage*>* faviconImages =
-          [self faviconsFromRange:faviconsViewRange];
-      if (NSMaxRange(faviconsViewRange) < _tabGroupTabNumber) {
-        [view configureWithFavicons:faviconImages
-                remainingTabsNumber:(_tabGroupTabNumber -
-                                     NSMaxRange(faviconsViewRange))];
-      } else {
-        [view configureWithFavicons:faviconImages];
-      }
-    }
-    ++index;
-  }
-}
-
 #pragma mark - UITraitEnvironment
 
 #if !defined(__IPHONE_17_0) || __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_17_0
diff --git a/ios/chrome/browser/tab_switcher/ui_bundled/tab_group_item_fetch_info.h b/ios/chrome/browser/tab_switcher/ui_bundled/tab_group_item_fetch_info.h
new file mode 100644
index 0000000..bb2696a
--- /dev/null
+++ b/ios/chrome/browser/tab_switcher/ui_bundled/tab_group_item_fetch_info.h
@@ -0,0 +1,33 @@
+// 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_TAB_SWITCHER_UI_BUNDLED_TAB_GROUP_ITEM_FETCH_INFO_H_
+#define IOS_CHROME_BROWSER_TAB_SWITCHER_UI_BUNDLED_TAB_GROUP_ITEM_FETCH_INFO_H_
+
+#import <Foundation/Foundation.h>
+
+// Model object used to fetch favicons and snapshots of a group item.
+// Stores the request ID and a counter for remaining sub-fetches.
+@interface TabGroupItemFetchInfo : NSObject
+
+// Unique ID for this fetch request.
+@property(nonatomic, strong, readonly) NSUUID* requestID;
+
+// Initializes with a request ID and the total expected fetches.
+// requestID: The unique ID for this fetch.
+// initialCount: The starting count of sub-fetches.
+- (instancetype)initWithRequestID:(NSUUID*)requestID
+                initialFetchCount:(NSInteger)initialCount
+    NS_DESIGNATED_INITIALIZER;
+- (instancetype)init NS_UNAVAILABLE;
+
+// Decrements the counter and returns the new value.
+- (NSInteger)decrementRemainingFetches;
+
+// Reads the current remaining fetch count.
+- (NSInteger)currentRemainingFetches;
+
+@end
+
+#endif  // IOS_CHROME_BROWSER_TAB_SWITCHER_UI_BUNDLED_TAB_GROUP_ITEM_FETCH_INFO_H_
diff --git a/ios/chrome/browser/tab_switcher/ui_bundled/tab_group_item_fetch_info.mm b/ios/chrome/browser/tab_switcher/ui_bundled/tab_group_item_fetch_info.mm
new file mode 100644
index 0000000..2f2f69f
--- /dev/null
+++ b/ios/chrome/browser/tab_switcher/ui_bundled/tab_group_item_fetch_info.mm
@@ -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.
+
+#import "ios/chrome/browser/tab_switcher/ui_bundled/tab_group_item_fetch_info.h"
+
+@implementation TabGroupItemFetchInfo {
+  // Track the remaining fetch count.
+  NSInteger _remainingFetchesCount;
+}
+
+- (instancetype)initWithRequestID:(NSUUID*)requestID
+                initialFetchCount:(NSInteger)initialCount {
+  self = [super init];
+  if (self) {
+    _requestID = requestID;
+    _remainingFetchesCount = initialCount;
+  }
+  return self;
+}
+
+- (NSInteger)decrementRemainingFetches {
+  return --_remainingFetchesCount;
+}
+
+- (NSInteger)currentRemainingFetches {
+  return _remainingFetchesCount;
+}
+
+@end
diff --git a/ios/chrome/browser/tab_switcher/ui_bundled/tab_group_item_utils.h b/ios/chrome/browser/tab_switcher/ui_bundled/tab_group_item_utils.h
new file mode 100644
index 0000000..a10277fe
--- /dev/null
+++ b/ios/chrome/browser/tab_switcher/ui_bundled/tab_group_item_utils.h
@@ -0,0 +1,80 @@
+// 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_TAB_SWITCHER_UI_BUNDLED_TAB_GROUP_ITEM_UTILS_H_
+#define IOS_CHROME_BROWSER_TAB_SWITCHER_UI_BUNDLED_TAB_GROUP_ITEM_UTILS_H_
+
+#import <Foundation/Foundation.h>
+
+// Visual representation of a TabGroupItem's layout. This helps illustrate how
+// the fetch logic applies.
+//
+// +--------------------------------+
+// | [ FacePile ] [Title...]   [X]  |
+// +--------------------------------+
+// |+-------------+  +-------------+|
+// ||             |  |             ||
+// ||  Tab 1      |  |  Tab 2      ||
+// || (Snap+Favi) |  | (Snap+Favi) ||
+// ||             |  |             ||
+// |+-------------+  +-------------+|
+// |+-------------+  +-------------+|
+// ||             |  |  F1   F2    ||
+// ||  Tab 3      |  |  F3   +N    ||
+// || (Snap+Favi) |  |  (Summary)  ||
+// ||             |  |             ||
+// |+-------------+  +-------------+|
+// +--------------------------------+
+//
+// 'Snap+Favi' indicates a tile displaying both a snapshot and a favicon.
+// 'F1', 'F2', 'F3' represent individual favicons displayed, and '+N' indicates
+// a count of additional hidden tabs.
+//
+// Compact Height Mode:
+// In 'compact_height' mode, the bottom row of tiles is hidden. Tab 2 transforms
+// into the summary tile for remaining tabs, displaying favicons and '+N'.
+// +--------------------------------+
+// | [ FacePile ] [Title...]   [X]  |
+// +--------------------------------+
+// |+-------------+  +-------------+|
+// ||             |  |  F1   F2    ||
+// ||  Tab 1      |  |  F3   +N    ||
+// || (Snap+Favi) |  |  (Summary)  ||
+// ||             |  |             ||
+// |+-------------+  +-------------+|
+// +--------------------------------+
+
+// The maximum number of individual tabs that are displayed in the TabGroupItem.
+// Tabs beyond this count are  represented by "+N".
+NSInteger MaxIndividualTabVisuals(BOOL compact_height);
+
+// Calculates the number of individual tabs that will be visually represented
+// within a TabGroupItem. This excludes tabs summarized by the "+N" indicator.
+NSInteger TabGroupItemDisplayedVisualCount(NSInteger tab_count);
+
+// Calculates total snapshot and favicon fetch requests count for a tab group.
+NSInteger TabGroupItemFetchRequestsCount(NSInteger tab_count);
+
+// Whether a snapshot should be fetched for `tab_index` within `tab_count`.
+BOOL ShouldFetchSnapshotForTabInGroup(NSInteger tab_index, NSInteger tab_count);
+
+// Determines whether the `tab_index` within `tab_count` will be represented in
+// the summary tile.
+// `compact_height` indicates if the bottom row of tiles is hidden.
+BOOL IsTabOnSummaryTile(NSInteger tab_index,
+                        NSInteger tab_count,
+                        BOOL compact_height);
+
+// Determines the relative favicon slot index within the summary tile for a
+// given `tab_index` within `tab_count`.
+// `compact_height` indicates if the bottom row of tiles is hidden.
+NSInteger SummaryFaviconSlotForTabIndex(NSInteger tab_index,
+                                        NSInteger tab_count,
+                                        BOOL compact_height);
+
+// Determines the count of additional hidden tabs.
+// `compact_height` indicates if the bottom row of tiles is hidden.
+NSInteger SummaryHiddenTabsCount(NSInteger tab_count, BOOL compact_height);
+
+#endif  // IOS_CHROME_BROWSER_TAB_SWITCHER_UI_BUNDLED_TAB_GROUP_ITEM_UTILS_H_
diff --git a/ios/chrome/browser/tab_switcher/ui_bundled/tab_group_item_utils.mm b/ios/chrome/browser/tab_switcher/ui_bundled/tab_group_item_utils.mm
new file mode 100644
index 0000000..af5e40a0
--- /dev/null
+++ b/ios/chrome/browser/tab_switcher/ui_bundled/tab_group_item_utils.mm
@@ -0,0 +1,88 @@
+// 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/tab_switcher/ui_bundled/tab_group_item_utils.h"
+
+#import "base/check_op.h"
+
+// The maximum number of individual tabs that are displayed in regular height.
+const NSInteger kMaxIndividualTabVisuals = 7;
+// The maximum number of individual tabs that are displayed in compact height.
+const NSInteger kMaxIndividualTabVisualsCompactHeight = 5;
+
+NSInteger MaxIndividualTabVisuals(BOOL compact_height) {
+  return compact_height ? kMaxIndividualTabVisualsCompactHeight
+                        : kMaxIndividualTabVisuals;
+}
+
+NSInteger TabGroupItemDisplayedVisualCount(NSInteger tab_count) {
+  if (tab_count > kMaxIndividualTabVisuals) {
+    return kMaxIndividualTabVisuals - 1;
+  }
+  return tab_count;
+}
+
+NSInteger TabGroupItemFetchRequestsCount(NSInteger tab_count) {
+  if (tab_count <= 4) {
+    // For <= 4 tabs: Fetch favicon + snapshot (2 requests) for every tab.
+    return tab_count * 2;
+  }
+  if (tab_count <= kMaxIndividualTabVisuals) {
+    // For <= 7 tabs: First 3 tabs get favicon + snapshot (6 requests total).
+    // Remaining tabs get only favicon (1 request each).
+    return 6 + (tab_count - 3);
+  }
+  // For > 7 tabs: First 3 tabs get favicon + snapshot (6 requests total).
+  // Next 3 tabs get only favicon (3 requests total), reserving a slot for
+  // count.
+  return 9;
+}
+
+BOOL ShouldFetchSnapshotForTabInGroup(NSInteger tab_index,
+                                      NSInteger tab_count) {
+  if (tab_count <= 4) {
+    CHECK_LT(tab_index, 4);
+    // For groups with 1 to 4 tabs: Each tab  maps directly to its own
+    // corresponding tile index.
+    return tab_index < tab_count;
+  }
+  // For groups with 5 or more tabs, a snapshot is only fetched for the first 3
+  // tabs.
+  return tab_index < 3;
+}
+
+//  Determines whether the tab at `tab_index` will be represented by a slot
+//  within the combined summary tile
+BOOL IsTabOnSummaryTile(NSInteger tab_index,
+                        NSInteger tab_count,
+                        BOOL compact_height) {
+  if (compact_height) {
+    return tab_count > 2 && tab_index >= 1;
+  }
+  return tab_count > 4 && tab_index >= 3;
+}
+
+NSInteger SummaryFaviconSlotForTabIndex(NSInteger tab_index,
+                                        NSInteger tab_count,
+                                        BOOL compact_height) {
+  if (compact_height) {
+    return tab_index - 1;
+  }
+  return tab_index - 3;
+}
+
+NSInteger SummaryHiddenTabsCount(NSInteger tab_count, BOOL compact_height) {
+  if (compact_height) {
+    if (tab_count <= kMaxIndividualTabVisualsCompactHeight) {
+      return 0;
+    }
+    // For > 5 tabs: Only the first 4 tabs are visible.
+    return tab_count - 4;
+  }
+  if (tab_count <= kMaxIndividualTabVisuals) {
+    return 0;
+  }
+  // For > 7 tabs: Only the first 6 tabs are visible.
+  return tab_count - 6;
+}
diff --git a/ios/chrome/browser/tab_switcher/ui_bundled/tab_snapshot_and_favicon_configurator.h b/ios/chrome/browser/tab_switcher/ui_bundled/tab_snapshot_and_favicon_configurator.h
index a1307d0..9d3e220 100644
--- a/ios/chrome/browser/tab_switcher/ui_bundled/tab_snapshot_and_favicon_configurator.h
+++ b/ios/chrome/browser/tab_switcher/ui_bundled/tab_snapshot_and_favicon_configurator.h
@@ -11,6 +11,7 @@
 
 class FaviconLoader;
 @class TabGroupItem;
+@class TabGroupItemFetchInfo;
 @class TabSnapshotAndFavicon;
 class WebStateList;
 @class WebStateTabSwitcherItem;
@@ -33,18 +34,18 @@
 
   // Fetches snapshots and favicons for all tabs within a `tab_group_item`
   // and its associated `web_state_list` asynchronously.
-  // The `completion` block is called once all information for the tabs
-  // in the group has been fetched.
+  // The `completion` block is invoked twice per tabIndex: once when the
+  // snapshot has been fetched, and again when the favicon has been fetched.
   void FetchSnapshotAndFaviconForTabGroupItem(
       TabGroupItem* group_item,
       WebStateList* web_state_list,
-      void (^completion)(
-          TabGroupItem* item,
-          NSArray<TabSnapshotAndFavicon*>* tab_snapshots_and_favicons));
+      void (^completion)(TabGroupItem* item,
+                         NSInteger tabIndex,
+                         TabSnapshotAndFavicon* tabSnapshotAndFavicon));
 
   // Fetches the snapshot and favicon for a single `web_state` asynchronously.
-  // The `completion` block is called once the information for the web state
-  // has been fetched.
+  // The `completion` block is invoked twice: once when the snapshot has been
+  // fetched, and again when the favicon has been fetched.
   void FetchSingleSnapshotAndFaviconFromWebState(
       web::WebState* web_state,
       void (^completion)(TabSnapshotAndFavicon* tab_snapshot_and_favicon));
@@ -71,29 +72,22 @@
   void FetchSnapshotAndFaviconFromWebState(
       TabGroupItem* group_item,
       web::WebState* web_state,
-      NSMutableDictionary<NSNumber*, TabSnapshotAndFavicon*>*
-          tab_snapshots_and_favicons,
-      NSUInteger request_index,
-      NSUInteger number_of_requests,
+      NSInteger request_index,
       NSUUID* request_id,
-      void (^completion)(
-          TabGroupItem* item,
-          NSArray<TabSnapshotAndFavicon*>* tab_snapshots_and_favicons));
+      void (^completion)(TabGroupItem* item,
+                         NSInteger tabIndex,
+                         TabSnapshotAndFavicon* tabSnapshotAndFavicon));
 
-  // Called when the snapshot and/or favicon for a web state has been fetched.
-  // Checks if all information has been collected and calls the `completion`
-  // block once, with all the information.
+  // Called when a snapshot or favicon for a web state has been fetched.
+  // This updates the fetch status and executes the `completion` block.
   void OnSnapshotAndFaviconFromWebStateFetched(
       TabGroupItem* group_item,
       TabSnapshotAndFavicon* tab_snapshot_and_favicon,
-      NSMutableDictionary<NSNumber*, TabSnapshotAndFavicon*>*
-          tab_snapshots_and_favicons,
-      NSUInteger request_index,
-      NSUInteger number_of_requests,
+      NSInteger request_index,
       NSUUID* request_id,
-      void (^completion)(
-          TabGroupItem* item,
-          NSArray<TabSnapshotAndFavicon*>* tab_snapshots_and_favicons));
+      void (^completion)(TabGroupItem* item,
+                         NSInteger tabIndex,
+                         TabSnapshotAndFavicon* tabSnapshotAndFavicon));
 
   // Fetches the snapshot and favicon for `tab_item`.
   // If `fetch_snapshot` is false, only the favicon will be fetched.
@@ -108,10 +102,10 @@
 
   raw_ptr<FaviconLoader> favicon_loader_ = nullptr;
 
-  // Stores the UUIDs of in-progress TabGroupItem fetch requests, keyed by the
-  // item's tabGroupIdentifier. This is used to cancel previous fetches if a new
-  // one starts for the same item.
-  NSMutableDictionary<NSValue*, NSUUID*>* group_item_fetches_;
+  // Stores the TabGroupItemFetchInfo of in-progress TabGroupItem fetch
+  // requests, keyed by the item's tabGroupIdentifier. This is used to cancel
+  // previous fetches if a new one starts for the same item.
+  NSMutableDictionary<NSValue*, TabGroupItemFetchInfo*>* group_item_fetches_;
 };
 
 #endif  // IOS_CHROME_BROWSER_TAB_SWITCHER_UI_BUNDLED_TAB_SNAPSHOT_AND_FAVICON_CONFIGURATOR_H_
diff --git a/ios/chrome/browser/tab_switcher/ui_bundled/tab_snapshot_and_favicon_configurator.mm b/ios/chrome/browser/tab_switcher/ui_bundled/tab_snapshot_and_favicon_configurator.mm
index fcbbd1a..963c70a 100644
--- a/ios/chrome/browser/tab_switcher/ui_bundled/tab_snapshot_and_favicon_configurator.mm
+++ b/ios/chrome/browser/tab_switcher/ui_bundled/tab_snapshot_and_favicon_configurator.mm
@@ -15,6 +15,8 @@
 #import "ios/chrome/browser/snapshots/model/snapshot_tab_helper.h"
 #import "ios/chrome/browser/tab_switcher/ui_bundled/item_utils.h"
 #import "ios/chrome/browser/tab_switcher/ui_bundled/tab_group_item.h"
+#import "ios/chrome/browser/tab_switcher/ui_bundled/tab_group_item_fetch_info.h"
+#import "ios/chrome/browser/tab_switcher/ui_bundled/tab_group_item_utils.h"
 #import "ios/chrome/browser/tab_switcher/ui_bundled/tab_snapshot_and_favicon.h"
 #import "ios/chrome/browser/tab_switcher/ui_bundled/web_state_tab_switcher_item.h"
 #import "ios/chrome/common/ui/colors/semantic_color_names.h"
@@ -47,32 +49,31 @@
 void TabSnapshotAndFaviconConfigurator::FetchSnapshotAndFaviconForTabGroupItem(
     TabGroupItem* group_item,
     WebStateList* web_state_list,
-    void (^completion)(
-        TabGroupItem* item,
-        NSArray<TabSnapshotAndFavicon*>* tab_snapshots_and_favicons)) {
+    void (^completion)(TabGroupItem* item,
+                       NSInteger tabIndex,
+                       TabSnapshotAndFavicon* tabSnapshotAndFavicon)) {
   const TabGroup* tab_group = group_item.tabGroup;
   if (!tab_group || !web_state_list) {
-    completion(group_item, nil);
     return;
   }
+  NSInteger tab_count = tab_group->range().count();
+  NSInteger number_of_requests = TabGroupItemDisplayedVisualCount(tab_count);
 
   // Overwrite any previous request for this item.
   NSUUID* request_id = [NSUUID UUID];
+  TabGroupItemFetchInfo* fetch_info = [[TabGroupItemFetchInfo alloc]
+      initWithRequestID:request_id
+      initialFetchCount:TabGroupItemFetchRequestsCount(tab_count)];
   group_item_fetches_[TabGroupIdentifierKeyforTabGroupItem(group_item)] =
-      request_id;
+      fetch_info;
 
-  NSMutableDictionary<NSNumber*, TabSnapshotAndFavicon*>* tab_group_infos =
-      [[NSMutableDictionary alloc] init];
-  NSUInteger first_index = tab_group->range().range_begin();
-  NSUInteger number_of_requests = MIN(7, tab_group->range().count());
-
-  for (NSUInteger request_index = 0; request_index < number_of_requests;
+  NSInteger first_index = tab_group->range().range_begin();
+  for (NSInteger request_index = 0; request_index < number_of_requests;
        request_index++) {
     web::WebState* web_state =
         web_state_list->GetWebStateAt(first_index + request_index);
     CHECK(web_state);
-    FetchSnapshotAndFaviconFromWebState(group_item, web_state, tab_group_infos,
-                                        request_index, number_of_requests,
+    FetchSnapshotAndFaviconFromWebState(group_item, web_state, request_index,
                                         request_id, completion);
   }
 }
@@ -81,20 +82,14 @@
     FetchSingleSnapshotAndFaviconFromWebState(
         web::WebState* web_state,
         void (^completion)(TabSnapshotAndFavicon* tab_snapshot_and_favicon)) {
-  auto inner_completion =
-      ^(TabGroupItem* item,
-        NSArray<TabSnapshotAndFavicon*>* tab_snapshots_and_favicons) {
-        CHECK(tab_snapshots_and_favicons.count == 1);
-        completion([tab_snapshots_and_favicons firstObject]);
-      };
+  auto inner_completion = ^(TabGroupItem* item, NSInteger tabIndex,
+                            TabSnapshotAndFavicon* tabSnapshotAndFavicon) {
+    completion(tabSnapshotAndFavicon);
+  };
 
-  // For single tab fetches, we don't track them in `group_item_fetches_`
-  // as they are not associated with a TabGroupItem that can be re-fetched.
   FetchSnapshotAndFaviconFromWebState(
       /*group_item=*/nil, web_state,
-      /*tab_group_infos=*/[[NSMutableDictionary alloc] init],
-      /*request_index=*/0, /*number_of_requests=*/1, /*request_id=*/nil,
-      inner_completion);
+      /*request_index=*/0, /*request_id=*/nil, inner_completion);
 }
 
 void TabSnapshotAndFaviconConfigurator::
@@ -119,27 +114,30 @@
 void TabSnapshotAndFaviconConfigurator::FetchSnapshotAndFaviconFromWebState(
     TabGroupItem* group_item,
     web::WebState* web_state,
-    NSMutableDictionary<NSNumber*, TabSnapshotAndFavicon*>* tab_group_infos,
-    NSUInteger request_index,
-    NSUInteger number_of_requests,
+    NSInteger request_index,
     NSUUID* request_id,
-    void (^completion)(
-        TabGroupItem* item,
-        NSArray<TabSnapshotAndFavicon*>* tab_snapshots_and_favicons)) {
+    void (^completion)(TabGroupItem* item,
+                       NSInteger tabIndex,
+                       TabSnapshotAndFavicon* tabSnapshotAndFavicon)) {
   TabSnapshotAndFavicon* tab_snapshot_and_favicon =
       [[TabSnapshotAndFavicon alloc] init];
 
   // Fetch the snapshot.
-  SnapshotTabHelper::FromWebState(web_state)->RetrieveColorSnapshot(
-      ^(UIImage* snapshot) {
-        // If there is no available snapshot, configure the snapshot to be an
-        // empty image in order to pass
-        // `OnSnapshotAndFaviconFromWebStateFetched` checks.
-        tab_snapshot_and_favicon.snapshot = snapshot ?: [[UIImage alloc] init];
-        OnSnapshotAndFaviconFromWebStateFetched(
-            group_item, tab_snapshot_and_favicon, tab_group_infos,
-            request_index, number_of_requests, request_id, completion);
-      });
+  bool shouldFetchSnapshot = true;
+  const TabGroup* tab_group = group_item.tabGroup;
+  if (tab_group) {
+    shouldFetchSnapshot = ShouldFetchSnapshotForTabInGroup(
+        request_index, tab_group->range().count());
+  }
+  if (shouldFetchSnapshot) {
+    SnapshotTabHelper::FromWebState(web_state)->RetrieveColorSnapshot(
+        ^(UIImage* snapshot) {
+          tab_snapshot_and_favicon.snapshot = snapshot;
+          OnSnapshotAndFaviconFromWebStateFetched(
+              group_item, tab_snapshot_and_favicon, request_index, request_id,
+              completion);
+        });
+  }
 
   UIImageConfiguration* configuration = [UIImageSymbolConfiguration
       configurationWithPointSize:kFaviconSize
@@ -152,8 +150,8 @@
     tab_snapshot_and_favicon.favicon =
         CustomSymbolWithConfiguration(kChromeProductSymbol, configuration);
     OnSnapshotAndFaviconFromWebStateFetched(
-        group_item, tab_snapshot_and_favicon, tab_group_infos, request_index,
-        number_of_requests, request_id, completion);
+        group_item, tab_snapshot_and_favicon, request_index, request_id,
+        completion);
     return;
   }
 
@@ -165,8 +163,8 @@
     if (!favicon.IsEmpty()) {
       tab_snapshot_and_favicon.favicon = favicon.ToUIImage();
       OnSnapshotAndFaviconFromWebStateFetched(
-          group_item, tab_snapshot_and_favicon, tab_group_infos, request_index,
-          number_of_requests, request_id, completion);
+          group_item, tab_snapshot_and_favicon, request_index, request_id,
+          completion);
       return;
     }
   }
@@ -177,8 +175,8 @@
   if (!favicon_loader_) {
     tab_snapshot_and_favicon.favicon = default_favicon;
     OnSnapshotAndFaviconFromWebStateFetched(
-        group_item, tab_snapshot_and_favicon, tab_group_infos, request_index,
-        number_of_requests, request_id, completion);
+        group_item, tab_snapshot_and_favicon, request_index, request_id,
+        completion);
     return;
   }
 
@@ -197,59 +195,44 @@
           tab_snapshot_and_favicon.favicon = default_favicon;
         }
         OnSnapshotAndFaviconFromWebStateFetched(
-            group_item, tab_snapshot_and_favicon, tab_group_infos,
-            request_index, number_of_requests, request_id, completion);
+            group_item, tab_snapshot_and_favicon, request_index, request_id,
+            completion);
       });
 }
 
 void TabSnapshotAndFaviconConfigurator::OnSnapshotAndFaviconFromWebStateFetched(
     TabGroupItem* group_item,
     TabSnapshotAndFavicon* tab_snapshot_and_favicon,
-    NSMutableDictionary<NSNumber*, TabSnapshotAndFavicon*>* tab_group_infos,
-    NSUInteger request_index,
-    NSUInteger number_of_requests,
+    NSInteger request_index,
     NSUUID* request_id,
-    void (^completion)(
-        TabGroupItem* item,
-        NSArray<TabSnapshotAndFavicon*>* tab_snapshots_and_favicons)) {
-  // Check if all data have been fetched before proceeding.
-  if (!tab_snapshot_and_favicon.snapshot || !tab_snapshot_and_favicon.favicon) {
+    void (^completion)(TabGroupItem* item,
+                       NSInteger tabIndex,
+                       TabSnapshotAndFavicon* tabSnapshotAndFavicon)) {
+  if (!group_item) {
+    completion(nil, request_index, tab_snapshot_and_favicon);
     return;
   }
 
-  // Check that the group still exists after asynchronous operations.
-  if (group_item && !group_item.tabGroup) {
+  NSValue* group_key = TabGroupIdentifierKeyforTabGroupItem(group_item);
+
+  // Check if the fetch is still the active one before proceeding.
+  TabGroupItemFetchInfo* current_fetch_info = group_item_fetches_[group_key];
+  if (![current_fetch_info.requestID isEqual:request_id]) {
+    return;
+  }
+  [current_fetch_info decrementRemainingFetches];
+
+  // If all fetches are completed, remove the `current_fetch_info` from
+  // dictionary.
+  if ([current_fetch_info currentRemainingFetches] == 0) {
+    [group_item_fetches_ removeObjectForKey:group_key];
+  }
+  // Check that the group still exists.
+  if (!group_item.tabGroup) {
     return;
   }
 
-  // Check if the fetch for this `group_item` is still the active one before
-  // proceeding.
-  if (group_item &&
-      ![group_item_fetches_[TabGroupIdentifierKeyforTabGroupItem(group_item)]
-          isEqual:request_id]) {
-    return;
-  }
-
-  tab_group_infos[@(request_index)] = tab_snapshot_and_favicon;
-  if (tab_group_infos.count != number_of_requests) {
-    return;
-  }
-
-  auto comparator = ^NSComparisonResult(NSNumber* obj1, NSNumber* obj2) {
-    return [obj1 compare:obj2];
-  };
-  NSMutableArray* infos = [NSMutableArray array];
-  for (NSNumber* key in
-       [[tab_group_infos allKeys] sortedArrayUsingComparator:comparator]) {
-    [infos addObject:tab_group_infos[key]];
-  }
-
-  // Remove the current item from the dictionary as the fetch is complete.
-  if (group_item) {
-    [group_item_fetches_
-        removeObjectForKey:TabGroupIdentifierKeyforTabGroupItem(group_item)];
-  }
-  completion(group_item, infos);
+  completion(group_item, request_index, tab_snapshot_and_favicon);
 }
 
 void TabSnapshotAndFaviconConfigurator::
diff --git a/ios/chrome/browser/tab_switcher/ui_bundled/tab_snapshot_and_favicon_configurator_unittest.mm b/ios/chrome/browser/tab_switcher/ui_bundled/tab_snapshot_and_favicon_configurator_unittest.mm
index 600e04cc..d219b67 100644
--- a/ios/chrome/browser/tab_switcher/ui_bundled/tab_snapshot_and_favicon_configurator_unittest.mm
+++ b/ios/chrome/browser/tab_switcher/ui_bundled/tab_snapshot_and_favicon_configurator_unittest.mm
@@ -29,18 +29,6 @@
   return std::make_unique<TestFaviconLoader>();
 }
 
-// Checks that the `TabSnapshotAndFavicon` in `tab_snapshots_and_favicons` are
-// correctly populated.
-void CheckTabSnapshotsAndFavicons(
-    NSArray<TabSnapshotAndFavicon*>* tab_snapshots_and_favicons,
-    int expected_count) {
-  ASSERT_EQ((int)tab_snapshots_and_favicons.count, expected_count);
-  for (TabSnapshotAndFavicon* tab in tab_snapshots_and_favicons) {
-    ASSERT_TRUE(tab.favicon);
-    ASSERT_TRUE(tab.snapshot);
-  }
-}
-
 }  // namespace
 
 // Test fixture for TabSnapshotAndFaviconConfigurator.
@@ -103,19 +91,41 @@
 // Tests the default use case of `FetchSnapshotAndFaviconForTabGroupItem:`.
 TEST_F(TabSnapshotAndFaviconConfiguratorTest,
        FetchSnapshotAndFaviconForTabGroupItem) {
-  __block BOOL completion_block_called = NO;
-  auto completion_block =
-      ^(TabGroupItem* item,
-        NSArray<TabSnapshotAndFavicon*>* tab_snapshots_and_favicons) {
-        ASSERT_EQ(item, tab_group_item_);
-        CheckTabSnapshotsAndFavicons(tab_snapshots_and_favicons, 2);
-        completion_block_called = YES;
-      };
+  __block int completion_block_called = 0;
+
+  auto completion_block = ^(TabGroupItem* item, NSInteger tabIndex,
+                            TabSnapshotAndFavicon* tabSnapshotAndFavicon) {
+    completion_block_called++;
+  };
   _configurator->FetchSnapshotAndFaviconForTabGroupItem(
       tab_group_item_, web_state_list_, completion_block);
   ASSERT_TRUE(base::test::ios::WaitUntilConditionOrTimeout(
       TestTimeouts::action_timeout(), ^bool() {
-        return completion_block_called;
+        return completion_block_called == 4;
+      }));
+}
+
+// Tests the use case of `FetchSnapshotAndFaviconForTabGroupItem:` for a large
+// group.
+TEST_F(TabSnapshotAndFaviconConfiguratorTest,
+       FetchSnapshotAndFaviconForTabGroupItemlargeGroup) {
+  for (int index = 0; index < 8; index++) {
+    AppendNewWebState();
+  }
+  web_state_list_->MoveToGroup({2, 3, 4, 5, 6, 7, 8, 9},
+                               tab_group_item_.tabGroup);
+  ASSERT_EQ(tab_group_item_.tabGroup->range().count(), 10);
+
+  __block int completion_block_called = 0;
+  auto completion_block = ^(TabGroupItem* item, NSInteger tabIndex,
+                            TabSnapshotAndFavicon* tabSnapshotAndFavicon) {
+    completion_block_called++;
+  };
+  _configurator->FetchSnapshotAndFaviconForTabGroupItem(
+      tab_group_item_, web_state_list_, completion_block);
+  ASSERT_TRUE(base::test::ios::WaitUntilConditionOrTimeout(
+      TestTimeouts::action_timeout(), ^bool() {
+        return completion_block_called == 9;
       }));
 }
 
@@ -124,7 +134,6 @@
        FetchSingleSnapshotAndFaviconFromWebState) {
   __block BOOL completion_block_called = NO;
   auto completion_block = ^(TabSnapshotAndFavicon* tab_snapshot_and_favicon) {
-    CheckTabSnapshotsAndFavicons(@[ tab_snapshot_and_favicon ], 1);
     completion_block_called = YES;
   };
   _configurator->FetchSingleSnapshotAndFaviconFromWebState(
diff --git a/ios/chrome/browser/unit_conversion/ui_bundled/unit_conversion_mediator_unittest.mm b/ios/chrome/browser/unit_conversion/ui_bundled/unit_conversion_mediator_unittest.mm
index 9fb72ebb..fc1a09e7 100644
--- a/ios/chrome/browser/unit_conversion/ui_bundled/unit_conversion_mediator_unittest.mm
+++ b/ios/chrome/browser/unit_conversion/ui_bundled/unit_conversion_mediator_unittest.mm
@@ -72,10 +72,12 @@
     PlatformTest::SetUp();
     mediator_ = [[UnitConversionMediator alloc] initWithService:&service_];
     helper_ = [[TestUnitConversionProviderTestHelper alloc] init];
+    [(id)consumer_mock_ setExpectationOrderMatters:YES];
     ios::provider::test::SetUnitConversionProviderTestHelper(helper_);
   }
 
   void TearDown() override {
+    EXPECT_OCMOCK_VERIFY(consumer_mock_);
     service_.Shutdown();
     ios::provider::test::SetUnitConversionProviderTestHelper(nil);
     PlatformTest::TearDown();
@@ -85,28 +87,26 @@
   UnitConversionMediator* mediator_;
   TestUnitConversionProviderTestHelper* helper_;
   UnitConversionService service_;
+  id<UnitConversionConsumer> consumer_mock_ =
+      OCMStrictProtocolMock(@protocol(UnitConversionConsumer));
 };
 
 // Tests that the conversion and the updates are handled correctly when the
 // source/traget unit conversion is possible (source and target are of the same
 // type).
 TEST_F(UnitConversionMediatorTest, TestPossibleUnitTypeChange) {
-  id consumer_mock = OCMStrictProtocolMock(@protocol(UnitConversionConsumer));
-  mediator_.consumer = consumer_mock;
+  mediator_.consumer = consumer_mock_;
 
-  [consumer_mock setExpectationOrderMatters:YES];
-  OCMExpect([consumer_mock updateSourceUnit:[NSUnitMass kilograms] reload:NO]);
-  OCMExpect([consumer_mock updateTargetUnit:[NSUnitMass grams] reload:NO]);
-  OCMExpect([consumer_mock updateSourceUnitValue:kSourceUnitValue reload:NO]);
-  OCMExpect([consumer_mock updateTargetUnitValue:kExpectedTargetUnitValue
-                                          reload:NO]);
-  OCMExpect([consumer_mock updateUnitTypeTitle:ios::provider::kUnitTypeMass]);
+  [((id)consumer_mock_) setExpectationOrderMatters:YES];
+  OCMExpect([consumer_mock_ updateSourceUnit:[NSUnitMass kilograms] reload:NO]);
+  OCMExpect([consumer_mock_ updateTargetUnit:[NSUnitMass grams] reload:NO]);
+  OCMExpect([consumer_mock_ updateSourceUnitValue:kSourceUnitValue reload:NO]);
+  OCMExpect([consumer_mock_ updateTargetUnitValue:kExpectedTargetUnitValue
+                                           reload:NO]);
+  OCMExpect([consumer_mock_ updateUnitTypeTitle:ios::provider::kUnitTypeMass]);
 
   [mediator_ unitTypeDidChange:ios::provider::kUnitTypeMass
                      unitValue:kSourceUnitValue];
-
-  // Test.
-  EXPECT_OCMOCK_VERIFY(consumer_mock);
 }
 
 // Tests that the metrics are recorded correctly after a unit type change
@@ -114,20 +114,18 @@
 TEST_F(UnitConversionMediatorTest, TestUnitTypeAndSourceUnitChanges) {
   base::UserActionTester user_action_tester;
   base::HistogramTester histogram_tester;
-  id consumer_mock = OCMStrictProtocolMock(@protocol(UnitConversionConsumer));
-  mediator_.consumer = consumer_mock;
+  mediator_.consumer = consumer_mock_;
   NSUnitMass* source_unit = [NSUnitMass kilograms];
   NSUnitMass* target_unit = [NSUnitMass grams];
-  [consumer_mock setExpectationOrderMatters:YES];
-  OCMExpect([consumer_mock updateSourceUnit:source_unit reload:NO]);
-  OCMExpect([consumer_mock updateTargetUnit:target_unit reload:NO]);
-  OCMExpect([consumer_mock updateSourceUnitValue:kSourceUnitValue reload:NO]);
-  OCMExpect([consumer_mock updateTargetUnitValue:kExpectedTargetUnitValue
-                                          reload:NO]);
-  OCMExpect([consumer_mock updateUnitTypeTitle:ios::provider::kUnitTypeMass]);
-  OCMExpect([consumer_mock updateSourceUnit:source_unit reload:YES]);
-  OCMExpect([consumer_mock updateTargetUnitValue:kExpectedTargetUnitValue
-                                          reload:YES]);
+  OCMExpect([consumer_mock_ updateSourceUnit:source_unit reload:NO]);
+  OCMExpect([consumer_mock_ updateTargetUnit:target_unit reload:NO]);
+  OCMExpect([consumer_mock_ updateSourceUnitValue:kSourceUnitValue reload:NO]);
+  OCMExpect([consumer_mock_ updateTargetUnitValue:kExpectedTargetUnitValue
+                                           reload:NO]);
+  OCMExpect([consumer_mock_ updateUnitTypeTitle:ios::provider::kUnitTypeMass]);
+  OCMExpect([consumer_mock_ updateSourceUnit:source_unit reload:YES]);
+  OCMExpect([consumer_mock_ updateTargetUnitValue:kExpectedTargetUnitValue
+                                           reload:YES]);
 
   [mediator_ unitTypeDidChange:ios::provider::kUnitTypeMass
                      unitValue:kSourceUnitValue];
@@ -139,7 +137,6 @@
   [mediator_ reportMetrics];
 
   // Test.
-  EXPECT_OCMOCK_VERIFY(consumer_mock);
 
   // Metric Test
   EXPECT_EQ(
@@ -162,48 +159,37 @@
 // when the conversion is impossible (source and target unit are not of the same
 // type).
 TEST_F(UnitConversionMediatorTest, TestImpossibleUnitTypeChange) {
-  id consumer_mock = OCMStrictProtocolMock(@protocol(UnitConversionConsumer));
-  mediator_.consumer = consumer_mock;
-
-  [consumer_mock setExpectationOrderMatters:YES];
+  mediator_.consumer = consumer_mock_;
   [mediator_ unitTypeDidChange:ios::provider::kUnitTypeArea
                      unitValue:kSourceUnitValue];
   // Test.
-  EXPECT_OCMOCK_VERIFY(consumer_mock);
 }
 
 // Tests that no conversion and no consumer unit value update is taking place
 // when the source/target are nil.
 TEST_F(UnitConversionMediatorTest, TestNilUnitTypeChange) {
-  id consumer_mock = OCMStrictProtocolMock(@protocol(UnitConversionConsumer));
-  mediator_.consumer = consumer_mock;
-
-  [consumer_mock setExpectationOrderMatters:YES];
+  mediator_.consumer = consumer_mock_;
   [mediator_ unitTypeDidChange:ios::provider::kUnitTypeUnknown
                      unitValue:kSourceUnitValue];
   // Test.
-  EXPECT_OCMOCK_VERIFY(consumer_mock);
 }
 
 // Tests that the source unit change is handled properly when the conversion is
 // possible between source and target unit.
 TEST_F(UnitConversionMediatorTest, TestPossibleSourceUnitChange) {
   base::HistogramTester histogram_tester;
-  id consumer_mock = OCMStrictProtocolMock(@protocol(UnitConversionConsumer));
-  mediator_.consumer = consumer_mock;
-  [consumer_mock setExpectationOrderMatters:YES];
+  mediator_.consumer = consumer_mock_;
   NSUnitMass* source_unit = [NSUnitMass kilograms];
   NSUnitMass* target_unit = [NSUnitMass grams];
-  OCMExpect([consumer_mock updateSourceUnit:source_unit reload:YES]);
-  OCMExpect([consumer_mock updateTargetUnitValue:kExpectedTargetUnitValue
-                                          reload:YES]);
+  OCMExpect([consumer_mock_ updateSourceUnit:source_unit reload:YES]);
+  OCMExpect([consumer_mock_ updateTargetUnitValue:kExpectedTargetUnitValue
+                                           reload:YES]);
   [mediator_ sourceUnitDidChange:source_unit
                       targetUnit:target_unit
                        unitValue:kSourceUnitValue
                         unitType:ios::provider::kUnitTypeMass];
   [mediator_ reportMetrics];
   // Test.
-  EXPECT_OCMOCK_VERIFY(consumer_mock);
   histogram_tester.ExpectBucketCount(
       kSourceUnitChangeBeforeUnitTypeChangeHistogram,
       UnitConversionActionTypes::kMassImperial, 1);
@@ -220,8 +206,7 @@
 // Tests that no update is taking place after a source unit change when the
 // conversion is impossible (source/target units are of different types).
 TEST_F(UnitConversionMediatorTest, TestImpossibleSourceUnitChange) {
-  id consumer_mock = OCMStrictProtocolMock(@protocol(UnitConversionConsumer));
-  mediator_.consumer = consumer_mock;
+  mediator_.consumer = consumer_mock_;
   NSUnitMass* source_unit = [NSUnitMass kilograms];
   NSUnitArea* target_unit = [NSUnitArea squareMiles];
   [mediator_ sourceUnitDidChange:source_unit
@@ -229,21 +214,18 @@
                        unitValue:kSourceUnitValue
                         unitType:ios::provider::kUnitTypeMass];
   // Test.
-  EXPECT_OCMOCK_VERIFY(consumer_mock);
 }
 
 // Tests that the target unit change is handled properly when the conversion is
 // possible between source and target unit.
 TEST_F(UnitConversionMediatorTest, TestPossibleTargetUnitChange) {
   base::HistogramTester histogram_tester;
-  id consumer_mock = OCMStrictProtocolMock(@protocol(UnitConversionConsumer));
-  mediator_.consumer = consumer_mock;
+  mediator_.consumer = consumer_mock_;
   NSUnitMass* source_unit = [NSUnitMass kilograms];
   NSUnitMass* target_unit = [NSUnitMass grams];
-  [consumer_mock setExpectationOrderMatters:YES];
-  OCMExpect([consumer_mock updateTargetUnit:target_unit reload:YES]);
-  OCMExpect([consumer_mock updateTargetUnitValue:kExpectedTargetUnitValue
-                                          reload:YES]);
+  OCMExpect([consumer_mock_ updateTargetUnit:target_unit reload:YES]);
+  OCMExpect([consumer_mock_ updateTargetUnitValue:kExpectedTargetUnitValue
+                                           reload:YES]);
 
   [mediator_ targetUnitDidChange:target_unit
                       sourceUnit:source_unit
@@ -251,7 +233,6 @@
                         unitType:ios::provider::kUnitTypeMass];
   [mediator_ reportMetrics];
   // Test.
-  EXPECT_OCMOCK_VERIFY(consumer_mock);
   histogram_tester.ExpectBucketCount(
       kTargetUnitChangeHistogram, UnitConversionActionTypes::kMassImperial, 1);
   histogram_tester.ExpectTotalCount(kTargetUnitChangeHistogram, 1);
@@ -260,8 +241,7 @@
 // Tests that no update is taking place after a target unit change when the
 // conversion is impossible (source/target units are of different types).
 TEST_F(UnitConversionMediatorTest, TestImpossibleTargetUnitChange) {
-  id consumer_mock = OCMStrictProtocolMock(@protocol(UnitConversionConsumer));
-  mediator_.consumer = consumer_mock;
+  mediator_.consumer = consumer_mock_;
   NSUnitMass* source_unit = [NSUnitMass kilograms];
   NSUnitArea* target_unit = [NSUnitArea squareMiles];
   [mediator_ targetUnitDidChange:target_unit
@@ -269,27 +249,23 @@
                        unitValue:kSourceUnitValue
                         unitType:ios::provider::kUnitTypeMass];
   // Test.
-  EXPECT_OCMOCK_VERIFY(consumer_mock);
 }
 
 // Tests that the source field unit is handled properly when the string to
 // NSNumber cast is possible.
 TEST_F(UnitConversionMediatorTest, TestValidSourceUnitValueFieldChange) {
   base::UserActionTester user_action_tester;
-  id consumer_mock = OCMStrictProtocolMock(@protocol(UnitConversionConsumer));
-  mediator_.consumer = consumer_mock;
+  mediator_.consumer = consumer_mock_;
   NSUnitMass* source_unit = [NSUnitMass kilograms];
   NSUnitMass* target_unit = [NSUnitMass grams];
-  [consumer_mock setExpectationOrderMatters:YES];
-  OCMExpect([consumer_mock updateTargetUnitValue:kExpectedTargetUnitValue
-                                          reload:YES]);
+  OCMExpect([consumer_mock_ updateTargetUnitValue:kExpectedTargetUnitValue
+                                           reload:YES]);
 
   [mediator_ sourceUnitValueFieldDidChange:kValidSourceUnitValueField
                                 sourceUnit:source_unit
                                 targetUnit:target_unit];
   [mediator_ reportMetrics];
   // Test.
-  EXPECT_OCMOCK_VERIFY(consumer_mock);
   EXPECT_EQ(user_action_tester.GetActionCount(
                 "IOS.UnitConversion.SourceUnitValueChange"),
             1);
@@ -299,12 +275,11 @@
 // field value is invalid and that the target unit field is updated with the
 // default value 0.
 TEST_F(UnitConversionMediatorTest, TestInvalidSourceUnitValueFieldChange) {
-  id consumer_mock = OCMStrictProtocolMock(@protocol(UnitConversionConsumer));
-  mediator_.consumer = consumer_mock;
+  mediator_.consumer = consumer_mock_;
   NSUnitMass* source_unit = [NSUnitMass kilograms];
   NSUnitMass* target_unit = [NSUnitMass grams];
-  OCMExpect([consumer_mock updateTargetUnitValue:kExpectedDefaultValue
-                                          reload:YES]);
+  OCMExpect([consumer_mock_ updateTargetUnitValue:kExpectedDefaultValue
+                                           reload:YES]);
   [mediator_ sourceUnitValueFieldDidChange:kInvalidUnitValueField
                                 sourceUnit:source_unit
                                 targetUnit:target_unit];
@@ -314,19 +289,17 @@
 // NSNumber cast is possible.
 TEST_F(UnitConversionMediatorTest, TestValidTargetUnitValueFieldChange) {
   base::UserActionTester user_action_tester;
-  id consumer_mock = OCMStrictProtocolMock(@protocol(UnitConversionConsumer));
-  mediator_.consumer = consumer_mock;
+  mediator_.consumer = consumer_mock_;
   NSUnitMass* source_unit = [NSUnitMass kilograms];
   NSUnitMass* target_unit = [NSUnitMass grams];
-  OCMExpect([consumer_mock updateSourceUnitValue:kExpectedSourceUnitValue
-                                          reload:YES]);
+  OCMExpect([consumer_mock_ updateSourceUnitValue:kExpectedSourceUnitValue
+                                           reload:YES]);
 
   [mediator_ targetUnitValueFieldDidChange:kValidTargetUnitValueField
                                 sourceUnit:source_unit
                                 targetUnit:target_unit];
   [mediator_ reportMetrics];
   // Test.
-  EXPECT_OCMOCK_VERIFY(consumer_mock);
   EXPECT_EQ(user_action_tester.GetActionCount(
                 "IOS.UnitConversion.TargetUnitValueChange"),
             1);
@@ -336,12 +309,11 @@
 // field value is invalid and that the source unit field is updated with the
 // default value 0.
 TEST_F(UnitConversionMediatorTest, TestInvalidTargetUnitValueFieldChange) {
-  id consumer_mock = OCMStrictProtocolMock(@protocol(UnitConversionConsumer));
-  mediator_.consumer = consumer_mock;
+  mediator_.consumer = consumer_mock_;
   NSUnitMass* source_unit = [NSUnitMass kilograms];
   NSUnitMass* target_unit = [NSUnitMass grams];
-  OCMExpect([consumer_mock updateSourceUnitValue:kExpectedDefaultValue
-                                          reload:YES]);
+  OCMExpect([consumer_mock_ updateSourceUnitValue:kExpectedDefaultValue
+                                           reload:YES]);
   [mediator_ targetUnitValueFieldDidChange:kInvalidUnitValueField
                                 sourceUnit:source_unit
                                 targetUnit:target_unit];
diff --git a/ios/web/download/crw_web_view_download_unittest.mm b/ios/web/download/crw_web_view_download_unittest.mm
index f6975ce13..65810765 100644
--- a/ios/web/download/crw_web_view_download_unittest.mm
+++ b/ios/web/download/crw_web_view_download_unittest.mm
@@ -13,36 +13,45 @@
 #import "testing/gtest_mac.h"
 #import "testing/platform_test.h"
 #import "third_party/ocmock/OCMock/OCMock.h"
+#import "third_party/ocmock/gtest_support.h"
 #import "url/gurl.h"
 
 // Test fixture for testing CRWWebViewDownloadTest class.
 class CRWWebViewDownloadTest : public PlatformTest {
+  void TearDown() override {
+    EXPECT_OCMOCK_VERIFY((id)web_view_);
+    EXPECT_OCMOCK_VERIFY((id)wk_download_);
+    EXPECT_OCMOCK_VERIFY((id)delegate_);
+    PlatformTest::TearDown();
+  }
+
  protected:
   web::WebTaskEnvironment task_environment_;
+  WKWebView* web_view_ = OCMStrictClassMock([WKWebView class]);
+  WKDownload* wk_download_ = OCMStrictClassMock([WKDownload class]);
+  id<CRWWebViewDownloadDelegate> delegate_ =
+      OCMStrictProtocolMock(@protocol(CRWWebViewDownloadDelegate));
 };
 
 TEST_F(CRWWebViewDownloadTest, TestDownloadHTTPFile) {
   NSURLRequest* request = [[NSURLRequest alloc]
       initWithURL:[NSURL URLWithString:@"https://example.test"]];
-  id web_view = OCMStrictClassMock([WKWebView class]);
-  id wk_download = OCMStrictClassMock([WKDownload class]);
-  id delegate = OCMStrictProtocolMock(@protocol(CRWWebViewDownloadDelegate));
   CRWWebViewDownload* download =
       [[CRWWebViewDownload alloc] initWithPath:@"/path/foo/bar"
                                        request:request
-                                       webview:web_view
-                                      delegate:delegate];
+                                       webview:web_view_
+                                      delegate:delegate_];
 
   __block bool start_called = false;
-  [[web_view expect]
+  OCMExpect([web_view_
       startDownloadUsingRequest:request
               completionHandler:[OCMArg checkWithBlock:^(void (^completion)(
                                     WKDownload* download)) {
-                completion(wk_download);
+                completion(wk_download_);
                 start_called = true;
                 return YES;
-              }]];
-  [[wk_download expect] setDelegate:[OCMArg any]];
+              }]]);
+  OCMExpect([wk_download_ setDelegate:[OCMArg any]]);
   [download startDownload];
   ASSERT_TRUE(base::test::ios::WaitUntilConditionOrTimeout(
       base::test::ios::kWaitForUIElementTimeout, ^bool() {
@@ -51,9 +60,6 @@
 }
 
 TEST_F(CRWWebViewDownloadTest, TestDownloadLocalFile) {
-  id web_view = OCMStrictClassMock([WKWebView class]);
-  id delegate = OCMStrictProtocolMock(@protocol(CRWWebViewDownloadDelegate));
-
   base::ScopedTempDir scoped_temp_dir;
   ASSERT_TRUE(scoped_temp_dir.CreateUniqueTempDir());
 
@@ -74,12 +80,12 @@
   CRWWebViewDownload* download = [[CRWWebViewDownload alloc]
       initWithPath:base::SysUTF8ToNSString(dest.value())
            request:request
-           webview:web_view
-          delegate:delegate];
+           webview:web_view_
+          delegate:delegate_];
   __block bool finish_called = false;
-  [[[delegate expect] andDo:^(NSInvocation* invocation) {
+  OCMExpect([delegate_ downloadDidFinish]).andDo(^(NSInvocation* invocation) {
     finish_called = true;
-  }] downloadDidFinish];
+  });
   [download startDownload];
   task_environment_.RunUntilIdle();
   ASSERT_TRUE(base::test::ios::WaitUntilConditionOrTimeout(
diff --git a/ios_internal b/ios_internal
index 798f900..c98c232 160000
--- a/ios_internal
+++ b/ios_internal
@@ -1 +1 @@
-Subproject commit 798f900e0762d708b11a6342d5b3a9099c7ae736
+Subproject commit c98c23282a810917184ef42c8f5c8724474266e9
diff --git a/media/media_options.gni b/media/media_options.gni
index ae57faa..93c40af 100644
--- a/media/media_options.gni
+++ b/media/media_options.gni
@@ -111,7 +111,7 @@
   enable_dav1d_decoder = use_blink
 
   # Enable browser managed persistent metadata storage for EME persistent
-  # session and persistent usage record session.
+  # session.
   enable_media_drm_storage = is_android || is_castos
 
   # Enable HLS manifest parser and demuxer.
diff --git a/media/renderers/paint_canvas_video_renderer.cc b/media/renderers/paint_canvas_video_renderer.cc
index b29fbe92..175858b 100644
--- a/media/renderers/paint_canvas_video_renderer.cc
+++ b/media/renderers/paint_canvas_video_renderer.cc
@@ -171,9 +171,13 @@
   auto scoped_si_access =
       si_texture->BeginAccess(source_sync_token, /*readonly=*/true);
 
-  // TODO(crbug.com/378688985): This assumes Video is always unpremultiplied,
-  // which might be incorrect if VideoFrame originates from WebGL canvas.
-  const bool do_premultiply_alpha = dst_alpha_type == kPremul_SkAlphaType;
+  const bool do_premultiply_alpha =
+      dst_alpha_type == kPremul_SkAlphaType &&
+      source_shared_image->alpha_type() == kUnpremul_SkAlphaType;
+  const bool do_unpremultiply_alpha =
+      dst_alpha_type == kUnpremul_SkAlphaType &&
+      source_shared_image->alpha_type() == kPremul_SkAlphaType;
+
   const bool do_flip_y = source_shared_image->surface_origin() != dst_origin;
   if (visible_rect != gfx::Rect(coded_size)) {
     // Must reallocate the destination texture and copy only a sub-portion.
@@ -189,15 +193,16 @@
     // coordinate space, but CopySubTextureCHROMIUM requires it to be in texture
     // space, so this is incorrect if `source_shared_image` origin is bottom
     // left.
-    gl->CopySubTextureCHROMIUM(
-        scoped_si_access->texture_id(), 0, target, texture, level, 0, 0,
-        visible_rect.x(), visible_rect.y(), visible_rect.width(),
-        visible_rect.height(), do_flip_y, do_premultiply_alpha, false);
+    gl->CopySubTextureCHROMIUM(scoped_si_access->texture_id(), 0, target,
+                               texture, level, 0, 0, visible_rect.x(),
+                               visible_rect.y(), visible_rect.width(),
+                               visible_rect.height(), do_flip_y,
+                               do_premultiply_alpha, do_unpremultiply_alpha);
 
   } else {
     gl->CopyTextureCHROMIUM(scoped_si_access->texture_id(), 0, target, texture,
                             level, internal_format, type, do_flip_y,
-                            do_premultiply_alpha, false);
+                            do_premultiply_alpha, do_unpremultiply_alpha);
   }
   return gpu::SharedImageTexture::ScopedAccess::EndAccess(
       std::move(scoped_si_access));
@@ -1495,7 +1500,8 @@
   auto [rgb_shared_image, rgb_sync_token, status] =
       rgb_shared_image_cache_->GetOrCreateSharedImage(
           video_frame.get(), raster_context_provider, src_usage,
-          SHARED_IMAGE_FORMAT, video_frame->CompatRGBColorSpace());
+          SHARED_IMAGE_FORMAT, kPremul_SkAlphaType,
+          video_frame->CompatRGBColorSpace());
   CHECK(rgb_shared_image);
 
   // Wait on the `rgb_sync_token` passed from the cache that may have been
@@ -1604,7 +1610,8 @@
   auto [rgb_shared_image, rgb_sync_token, status] =
       rgb_shared_image_cache_->GetOrCreateSharedImage(
           video_frame.get(), raster_context_provider, src_usage,
-          SHARED_IMAGE_FORMAT, video_frame->CompatRGBColorSpace());
+          SHARED_IMAGE_FORMAT, kPremul_SkAlphaType,
+          video_frame->CompatRGBColorSpace());
   CHECK(rgb_shared_image);
 
   // On the source Raster context, do the YUV->RGB conversion.
diff --git a/media/renderers/video_frame_shared_image_cache.cc b/media/renderers/video_frame_shared_image_cache.cc
index 28dd2d2..b3c2eddb 100644
--- a/media/renderers/video_frame_shared_image_cache.cc
+++ b/media/renderers/video_frame_shared_image_cache.cc
@@ -73,7 +73,8 @@
       VideoPixelFormatToSharedImageFormat(video_frame->format());
   CHECK(format.is_multi_plane());
   return GetOrCreateSharedImage(video_frame, raster_context_provider, usage,
-                                format, video_frame->ColorSpace());
+                                format, kUnpremul_SkAlphaType,
+                                video_frame->ColorSpace());
 }
 
 VideoFrameSharedImageCache::CachedData
@@ -82,6 +83,7 @@
     viz::RasterContextProvider* raster_context_provider,
     const gpu::SharedImageUsageSet& usage,
     const viz::SharedImageFormat& format,
+    SkAlphaType alpha_type,
     const gfx::ColorSpace& color_space) {
   if (shared_image_ && provider_ == raster_context_provider) {
     // Return the cached shared image if it is the same video frame.
@@ -92,7 +94,8 @@
     // image data.
     if (video_frame->coded_size() == shared_image_->size() &&
         color_space == shared_image_->color_space() &&
-        format == shared_image_->format() && usage == shared_image_->usage()) {
+        format == shared_image_->format() && usage == shared_image_->usage() &&
+        alpha_type == shared_image_->alpha_type()) {
       return {shared_image_, sync_token_, Status::kMatchedSharedImageMetaData};
     }
   }
@@ -107,7 +110,7 @@
 
   shared_image_ = sii->CreateSharedImage(
       {format, video_frame->coded_size(), color_space, kTopLeft_GrSurfaceOrigin,
-       kUnpremul_SkAlphaType, usage, "VideoFrameSharedImageCache"},
+       alpha_type, usage, "VideoFrameSharedImageCache"},
       gpu::kNullSurfaceHandle);
   CHECK(shared_image_);
   video_frame_id_ = video_frame->unique_id();
diff --git a/media/renderers/video_frame_shared_image_cache.h b/media/renderers/video_frame_shared_image_cache.h
index 81d041a..3fdc9d6e 100644
--- a/media/renderers/video_frame_shared_image_cache.h
+++ b/media/renderers/video_frame_shared_image_cache.h
@@ -59,6 +59,7 @@
       viz::RasterContextProvider* raster_context_provider,
       const gpu::SharedImageUsageSet& usage,
       const viz::SharedImageFormat& format,
+      SkAlphaType alpha_type,
       const gfx::ColorSpace& color_space);
 
   // Calls the above method but passes in video frame color space and YUV format
diff --git a/mojo/public/cpp/bindings/direct_receiver.h b/mojo/public/cpp/bindings/direct_receiver.h
index 179b148b..c873767 100644
--- a/mojo/public/cpp/bindings/direct_receiver.h
+++ b/mojo/public/cpp/bindings/direct_receiver.h
@@ -8,6 +8,7 @@
 #include <cstdint>
 #include <map>
 #include <memory>
+#include <string_view>
 #include <utility>
 
 #include "base/component_export.h"
@@ -32,6 +33,10 @@
 class AsyncLayerTreeFrameSink;
 }
 
+namespace viz {
+class CompositorFrameSinkImpl;
+}
+
 namespace mojo {
 
 namespace internal {
@@ -111,6 +116,7 @@
   friend class cc::mojo_embedder::AsyncLayerTreeFrameSink;
   friend class mojo::test::direct_receiver_unittest::ServiceImpl;
   friend class blink::WidgetInputHandlerImpl;
+  friend class viz::CompositorFrameSinkImpl;
 };
 
 // DirectReceiver is a wrapper around the standard Receiver<T> type that always
@@ -158,6 +164,11 @@
                                        : std::move(receiver));
   }
 
+  void ResetWithReason(uint32_t custom_reason_code,
+                       std::string_view description) {
+    receiver_.ResetWithReason(custom_reason_code, description);
+  }
+
   internal::ThreadLocalNode& node_for_testing() { return *node_; }
   Receiver<T>& receiver_for_testing() { return receiver_; }
 
diff --git a/skia/config/SkUserConfig.h b/skia/config/SkUserConfig.h
index bf1f04c6..7dc7e073 100644
--- a/skia/config/SkUserConfig.h
+++ b/skia/config/SkUserConfig.h
@@ -135,6 +135,8 @@
 
 #define SK_AVOID_SLOW_RASTER_PIPELINE_BLURS
 
+#define SK_SUPPORT_UNSPANNED_APIS
+
 ///////////////////////// Imported from BUILD.gn and skia_common.gypi
 
 /* In some places Skia can use static initializers for global initialization,
diff --git a/testing/variations/fieldtrial_testing_config.json b/testing/variations/fieldtrial_testing_config.json
index e14e955b..653d4faf 100644
--- a/testing/variations/fieldtrial_testing_config.json
+++ b/testing/variations/fieldtrial_testing_config.json
@@ -14489,6 +14489,26 @@
             ]
         }
     ],
+    "MemoryPurgeOnFreezeLimit": [
+        {
+            "platforms": [
+                "android",
+                "android_webview",
+                "chromeos",
+                "linux",
+                "mac",
+                "windows"
+            ],
+            "experiments": [
+                {
+                    "name": "Enabled",
+                    "enable_features": [
+                        "MemoryPurgeOnFreezeLimit"
+                    ]
+                }
+            ]
+        }
+    ],
     "MemorySaverModeRenderTuning": [
         {
             "platforms": [
@@ -18260,24 +18280,6 @@
             ]
         }
     ],
-    "PrivacySandboxAdsNoticeCCT": [
-        {
-            "platforms": [
-                "android"
-            ],
-            "experiments": [
-                {
-                    "name": "Enabled_AGSA",
-                    "params": {
-                        "app-id": "com.google.android.googlequicksearchbox"
-                    },
-                    "enable_features": [
-                        "PrivacySandboxAdsNoticeCCT"
-                    ]
-                }
-            ]
-        }
-    ],
     "PrivacySandboxAdsNoticeCCTSurvey": [
         {
             "platforms": [
@@ -25064,6 +25066,28 @@
             ]
         }
     ],
+    "V8LateHeapLimitCheck": [
+        {
+            "platforms": [
+                "android",
+                "android_weblayer",
+                "android_webview",
+                "chromeos",
+                "fuchsia",
+                "linux",
+                "mac",
+                "windows"
+            ],
+            "experiments": [
+                {
+                    "name": "Enabled",
+                    "enable_features": [
+                        "V8Flag_late_heap_limit_check"
+                    ]
+                }
+            ]
+        }
+    ],
     "V8LocalCompileHints": [
         {
             "platforms": [
@@ -25503,6 +25527,21 @@
             ]
         }
     ],
+    "VizDirectCompositorThreadIpcNonRoot": [
+        {
+            "platforms": [
+                "android"
+            ],
+            "experiments": [
+                {
+                    "name": "Enabled",
+                    "enable_features": [
+                        "VizDirectCompositorThreadIpcNonRoot"
+                    ]
+                }
+            ]
+        }
+    ],
     "VulkanV2": [
         {
             "platforms": [
diff --git a/third_party/androidx/build.gradle b/third_party/androidx/build.gradle
index d2cb9b8..1781ee66 100644
--- a/third_party/androidx/build.gradle
+++ b/third_party/androidx/build.gradle
@@ -305,7 +305,7 @@
     google()
     maven {
         // This URL is generated by the fetch_all_androidx.py script.
-        url 'https://androidx.dev/snapshots/builds/13600066/artifacts/repository'
+        url 'https://androidx.dev/snapshots/builds/13604076/artifacts/repository'
     }
     mavenCentral()
 }
diff --git a/third_party/androidx/committed/libs/androidx_compose_foundation_foundation_android/androidx_compose_foundation_foundation_android.info b/third_party/androidx/committed/libs/androidx_compose_foundation_foundation_android/androidx_compose_foundation_foundation_android.info
index 5db640f..2b5aa62f 100644
--- a/third_party/androidx/committed/libs/androidx_compose_foundation_foundation_android/androidx_compose_foundation_foundation_android.info
+++ b/third_party/androidx/committed/libs/androidx_compose_foundation_foundation_android/androidx_compose_foundation_foundation_android.info
@@ -10,6 +10,7 @@
 is_manifest_empty = true
 manifest_package = "androidx.compose.foundation"
 resources = [
+  "res/values/values.xml",
   "res/values-af/values-af.xml",
   "res/values-am/values-am.xml",
   "res/values-ar/values-ar.xml",
@@ -30,14 +31,14 @@
   "res/values-en-rGB/values-en-rGB.xml",
   "res/values-en-rIN/values-en-rIN.xml",
   "res/values-en-rXC/values-en-rXC.xml",
-  "res/values-es-rUS/values-es-rUS.xml",
   "res/values-es/values-es.xml",
+  "res/values-es-rUS/values-es-rUS.xml",
   "res/values-et/values-et.xml",
   "res/values-eu/values-eu.xml",
   "res/values-fa/values-fa.xml",
   "res/values-fi/values-fi.xml",
-  "res/values-fr-rCA/values-fr-rCA.xml",
   "res/values-fr/values-fr.xml",
+  "res/values-fr-rCA/values-fr-rCA.xml",
   "res/values-gl/values-gl.xml",
   "res/values-gu/values-gu.xml",
   "res/values-hi/values-hi.xml",
@@ -70,9 +71,9 @@
   "res/values-or/values-or.xml",
   "res/values-pa/values-pa.xml",
   "res/values-pl/values-pl.xml",
+  "res/values-pt/values-pt.xml",
   "res/values-pt-rBR/values-pt-rBR.xml",
   "res/values-pt-rPT/values-pt-rPT.xml",
-  "res/values-pt/values-pt.xml",
   "res/values-ro/values-ro.xml",
   "res/values-ru/values-ru.xml",
   "res/values-si/values-si.xml",
@@ -94,8 +95,7 @@
   "res/values-zh-rCN/values-zh-rCN.xml",
   "res/values-zh-rHK/values-zh-rHK.xml",
   "res/values-zh-rTW/values-zh-rTW.xml",
-  "res/values-zu/values-zu.xml",
-  "res/values/values.xml"
+  "res/values-zu/values-zu.xml"
 ]
 subjar_tuples = []
 subjars = []
diff --git a/third_party/angle b/third_party/angle
index ef31b3e..25fc504 160000
--- a/third_party/angle
+++ b/third_party/angle
@@ -1 +1 @@
-Subproject commit ef31b3ed1c0ec1a9fbcfd8849c2ad001eb53abfb
+Subproject commit 25fc50465713d5c524fc49dda81e80624fd676a5
diff --git a/third_party/blink/common/navigation/preloading_headers.cc b/third_party/blink/common/navigation/preloading_headers.cc
index 5cd0b08..98c5ac5 100644
--- a/third_party/blink/common/navigation/preloading_headers.cc
+++ b/third_party/blink/common/navigation/preloading_headers.cc
@@ -12,4 +12,9 @@
   return sec_purpose_header && sec_purpose_header->starts_with("prefetch");
 }
 
+bool IsSecPurposeForPrerender(std::optional<std::string> sec_purpose_header) {
+  return sec_purpose_header &&
+         sec_purpose_header->starts_with("prefetch;prerender");
+}
+
 }  // namespace blink
diff --git a/third_party/blink/common/navigation/preloading_headers_unittest.cc b/third_party/blink/common/navigation/preloading_headers_unittest.cc
index d6f469f5..e2533b3 100644
--- a/third_party/blink/common/navigation/preloading_headers_unittest.cc
+++ b/third_party/blink/common/navigation/preloading_headers_unittest.cc
@@ -19,4 +19,16 @@
   EXPECT_FALSE(IsSecPurposeForPrefetch(""));
 }
 
+TEST(PreloadingHeadersTest, IsSecPurposeForPrerender) {
+  EXPECT_FALSE(IsSecPurposeForPrerender(kSecPurposePrefetchHeaderValue));
+  EXPECT_FALSE(IsSecPurposeForPrerender(
+      kSecPurposePrefetchAnonymousClientIpHeaderValue));
+  EXPECT_TRUE(
+      IsSecPurposeForPrerender(kSecPurposePrefetchPrerenderHeaderValue));
+  EXPECT_TRUE(
+      IsSecPurposeForPrerender(kSecPurposePrefetchPrerenderPreviewHeaderValue));
+  EXPECT_FALSE(IsSecPurposeForPrerender(std::nullopt));
+  EXPECT_FALSE(IsSecPurposeForPrerender(""));
+}
+
 }  // namespace blink
diff --git a/third_party/blink/public/common/navigation/preloading_headers.h b/third_party/blink/public/common/navigation/preloading_headers.h
index 54fba61..7c17745 100644
--- a/third_party/blink/public/common/navigation/preloading_headers.h
+++ b/third_party/blink/public/common/navigation/preloading_headers.h
@@ -29,6 +29,14 @@
 BLINK_COMMON_EXPORT bool IsSecPurposeForPrefetch(
     std::optional<std::string> sec_purpose_header_value);
 
+// Returns true if the given `Sec-Purpose` request header value is for
+// prerender. Note: this assumes the header value is set by Chromium
+// implementation using the header value
+// `kSecPurposePrefetchPrerenderHeaderValue` below, as this method doesn't
+// perform full structured header value parsing.
+BLINK_COMMON_EXPORT bool IsSecPurposeForPrerender(
+    std::optional<std::string> sec_purpose_header_value);
+
 // This value indicates that the request is a prefetch request made directly to
 // the server.
 inline constexpr char kSecPurposePrefetchHeaderValue[] = "prefetch";
diff --git a/third_party/blink/renderer/core/css/css_identifier_value_mappings.h b/third_party/blink/renderer/core/css/css_identifier_value_mappings.h
index 21effca0..50caa632 100644
--- a/third_party/blink/renderer/core/css/css_identifier_value_mappings.h
+++ b/third_party/blink/renderer/core/css/css_identifier_value_mappings.h
@@ -1646,6 +1646,8 @@
       return kContainsSize;
     case CSSValueID::kInlineSize:
       return kContainsInlineSize;
+    case CSSValueID::kViewTransition:
+      return kContainsViewTransition;
     default:
       break;
   }
diff --git a/third_party/blink/renderer/core/css/css_properties.json5 b/third_party/blink/renderer/core/css/css_properties.json5
index 81c838e1..c2eabe4 100644
--- a/third_party/blink/renderer/core/css/css_properties.json5
+++ b/third_party/blink/renderer/core/css/css_properties.json5
@@ -2878,13 +2878,13 @@
       name: "contain",
       property_methods: ["ParseSingleValue", "CSSValueFromComputedStyleInternal"],
       field_group: "*",
-      field_size: 5,
+      field_size: 6,
       field_template: "primitive",
       default_value: "kContainsNone",
       name_for_methods: "Contain",
       type_name: "unsigned",
       converter: "ConvertFlags<Containment>",
-      keywords: ["none", "strict", "content", "size", "layout", "style", "paint", "inline-size", "block-size"],
+      keywords: ["none", "strict", "content", "size", "layout", "style", "paint", "inline-size", "block-size", "view-transition"],
       typedom_types: ["Keyword"],
       computable: false,
       valid_for_permission_element: true,
diff --git a/third_party/blink/renderer/core/css/css_value_keywords.json5 b/third_party/blink/renderer/core/css/css_value_keywords.json5
index f54ff1c..e7a46d1b 100644
--- a/third_party/blink/renderer/core/css/css_value_keywords.json5
+++ b/third_party/blink/renderer/core/css/css_value_keywords.json5
@@ -1639,6 +1639,7 @@
     "size",
     "block-size",
     "inline-size",
+    "view-transition",
 
     // @container
     // size
diff --git a/third_party/blink/renderer/core/css/properties/computed_style_utils.cc b/third_party/blink/renderer/core/css/properties/computed_style_utils.cc
index 6cc2d00..7bd2a4e8 100644
--- a/third_party/blink/renderer/core/css/properties/computed_style_utils.cc
+++ b/third_party/blink/renderer/core/css/properties/computed_style_utils.cc
@@ -60,6 +60,7 @@
 #include "third_party/blink/renderer/core/layout/grid/layout_grid.h"
 #include "third_party/blink/renderer/core/layout/layout_block.h"
 #include "third_party/blink/renderer/core/layout/layout_box.h"
+#include "third_party/blink/renderer/core/layout/svg/layout_svg_viewport_container.h"
 #include "third_party/blink/renderer/core/layout/svg/transform_helper.h"
 #include "third_party/blink/renderer/core/style/computed_style_constants.h"
 #include "third_party/blink/renderer/core/style/position_area.h"
@@ -2207,6 +2208,9 @@
 static bool IsSVGObjectWithWidthAndHeight(const LayoutObject& layout_object) {
   DCHECK(layout_object.IsSVGChild());
   return layout_object.IsSVGImage() || layout_object.IsSVGForeignObject() ||
+         (layout_object.IsSVGViewportContainer() &&
+          RuntimeEnabledFeatures::
+              WidthAndHeightAsPresentationAttributesOnNestedSvgEnabled()) ||
          (layout_object.IsSVGShape() &&
           IsA<SVGRectElement>(layout_object.GetNode()));
 }
@@ -2214,7 +2218,14 @@
 gfx::SizeF ComputedStyleUtils::UsedBoxSize(const LayoutObject& layout_object) {
   if (layout_object.IsSVGChild() &&
       IsSVGObjectWithWidthAndHeight(layout_object)) {
-    gfx::SizeF size = layout_object.ObjectBoundingBox().size();
+    auto* viewport_container =
+        DynamicTo<LayoutSVGViewportContainer>(layout_object);
+    gfx::SizeF size =
+        viewport_container &&
+                RuntimeEnabledFeatures::
+                    WidthAndHeightAsPresentationAttributesOnNestedSvgEnabled()
+            ? viewport_container->Viewport().size()
+            : layout_object.ObjectBoundingBox().size();
     // The object bounding box does not have zoom applied. Multiply with zoom
     // here since we'll divide by it when we produce the CSS value.
     size.Scale(layout_object.StyleRef().EffectiveZoom());
diff --git a/third_party/blink/renderer/core/css/properties/longhands/longhands_custom.cc b/third_party/blink/renderer/core/css/properties/longhands/longhands_custom.cc
index 0cd0786b..1bf1283 100644
--- a/third_party/blink/renderer/core/css/properties/longhands/longhands_custom.cc
+++ b/third_party/blink/renderer/core/css/properties/longhands/longhands_custom.cc
@@ -2693,10 +2693,10 @@
   CSSIdentifierValue* layout = nullptr;
   CSSIdentifierValue* style = nullptr;
   CSSIdentifierValue* paint = nullptr;
+  CSSIdentifierValue* view_transition = nullptr;
   while (true) {
     id = stream.Peek().Id();
     if ((id == CSSValueID::kSize ||
-
          id == CSSValueID::kInlineSize) &&
         !size) {
       size = css_parsing_utils::ConsumeIdent(stream);
@@ -2706,6 +2706,9 @@
       style = css_parsing_utils::ConsumeIdent(stream);
     } else if (id == CSSValueID::kPaint && !paint) {
       paint = css_parsing_utils::ConsumeIdent(stream);
+    } else if (id == CSSValueID::kViewTransition && !view_transition &&
+               RuntimeEnabledFeatures::ScopedViewTransitionsEnabled()) {
+      view_transition = css_parsing_utils::ConsumeIdent(stream);
     } else {
       break;
     }
@@ -2723,6 +2726,9 @@
   if (paint) {
     list->Append(*paint);
   }
+  if (view_transition) {
+    list->Append(*view_transition);
+  }
   if (!list->length()) {
     return nullptr;
   }
@@ -2762,6 +2768,9 @@
   if (style.Contain() & kContainsPaint) {
     list->Append(*CSSIdentifierValue::Create(CSSValueID::kPaint));
   }
+  if (style.Contain() & kContainsViewTransition) {
+    list->Append(*CSSIdentifierValue::Create(CSSValueID::kViewTransition));
+  }
   DCHECK(list->length());
   return list;
 }
diff --git a/third_party/blink/renderer/core/css/resolver/style_resolver.cc b/third_party/blink/renderer/core/css/resolver/style_resolver.cc
index 96d89c2..5b7a622 100644
--- a/third_party/blink/renderer/core/css/resolver/style_resolver.cc
+++ b/third_party/blink/renderer/core/css/resolver/style_resolver.cc
@@ -126,6 +126,7 @@
 #include "third_party/blink/renderer/core/style/style_initial_data.h"
 #include "third_party/blink/renderer/core/style_property_shorthand.h"
 #include "third_party/blink/renderer/core/svg/svg_element.h"
+#include "third_party/blink/renderer/core/svg/svg_svg_element.h"
 #include "third_party/blink/renderer/platform/heap/garbage_collected.h"
 #include "third_party/blink/renderer/platform/instrumentation/use_counter.h"
 #include "third_party/blink/renderer/platform/runtime_enabled_features.h"
@@ -1228,6 +1229,21 @@
         element.PresentationAttributeStyle(),
         CascadeOrigin::kAuthorPresentationalHint);
 
+    if (RuntimeEnabledFeatures::
+            WidthAndHeightAsPresentationAttributesOnNestedSvgEnabled() &&
+        state.GetStyledElement() != &element) {
+      auto* svg_svg_element =
+          DynamicTo<SVGSVGElement>(state.GetStyledElement());
+
+      if (svg_svg_element) {
+        collector.AddElementStyleProperties(
+            svg_svg_element
+                ->CreateWidthAndHeightPresentationAttributeStyleIfNeeded(
+                    element),
+            CascadeOrigin::kAuthorPresentationalHint);
+      }
+    }
+
     // Now we check additional mapped declarations.
     // Tables and table cells share an additional mapped rule that must be
     // applied after all attributes, since their mapped style depends on the
diff --git a/third_party/blink/renderer/core/layout/layout_object.cc b/third_party/blink/renderer/core/layout/layout_object.cc
index 0465a24..78771bd 100644
--- a/third_party/blink/renderer/core/layout/layout_object.cc
+++ b/third_party/blink/renderer/core/layout/layout_object.cc
@@ -2206,6 +2206,9 @@
   if (IsLayoutBlockFlow() && ChildrenInline() && SlowFirstChild()) {
     attributes.push_back("children-inline");
   }
+  if (IsMulticolContainer()) {
+    attributes.push_back("multicol");
+  }
   if (!attributes.empty()) {
     name.Append(" (");
     name.Append(attributes[0]);
diff --git a/third_party/blink/renderer/core/layout/physical_fragment_test.cc b/third_party/blink/renderer/core/layout/physical_fragment_test.cc
index ef2fc13..aa4560b 100644
--- a/third_party/blink/renderer/core/layout/physical_fragment_test.cc
+++ b/third_party/blink/renderer/core/layout/physical_fragment_test.cc
@@ -116,7 +116,7 @@
   Box (out-of-flow-positioned block-flow)(self paint) offset:unplaced size:800x600 LayoutView #document
     Box (block-flow-root block-flow)(self paint) offset:0,0 size:800x66 LayoutBlockFlow HTML
       Box (block-flow) offset:8,8 size:784x50 LayoutBlockFlow BODY
-        Box (block-flow-root block-flow) offset:0,0 size:784x50 LayoutBlockFlow DIV id='multicol'
+        Box (block-flow-root block-flow) offset:0,0 size:784x50 LayoutBlockFlow (multicol) DIV id='multicol'
           Box (column block-flow) offset:0,0 size:260.65625x50
             Box (block-flow) offset:0,0 size:260.65625x50 LayoutBlockFlow DIV id='child'
           Box (column block-flow) offset:261.65625,0 size:260.65625x50
diff --git a/third_party/blink/renderer/core/layout/svg/layout_svg_viewport_container.cc b/third_party/blink/renderer/core/layout/svg/layout_svg_viewport_container.cc
index 3ac675235..f49f8016 100644
--- a/third_party/blink/renderer/core/layout/svg/layout_svg_viewport_container.cc
+++ b/third_party/blink/renderer/core/layout/svg/layout_svg_viewport_container.cc
@@ -33,6 +33,19 @@
 
 namespace blink {
 
+namespace {
+
+float ResolveViewportDimension(const Length& dimension,
+                               const SVGViewportResolver& viewport_resolver,
+                               const ComputedStyle& style,
+                               SVGLengthMode mode) {
+  const Length kOneHundredPercent(100, Length::Type::kPercent);
+  const Length& length = dimension.IsAuto() ? kOneHundredPercent : dimension;
+  return ValueForLength(length, viewport_resolver, style, mode);
+}
+
+}  // namespace
+
 LayoutSVGViewportContainer::LayoutSVGViewportContainer(SVGSVGElement* node)
     : LayoutSVGContainer(node) {}
 
@@ -48,10 +61,30 @@
     const auto* svg = To<SVGSVGElement>(GetElement());
     SVGLengthContext length_context(svg);
     gfx::RectF old_viewport = viewport_;
-    viewport_.SetRect(svg->x()->CurrentValue()->Value(length_context),
-                      svg->y()->CurrentValue()->Value(length_context),
-                      svg->width()->CurrentValue()->Value(length_context),
-                      svg->height()->CurrentValue()->Value(length_context));
+
+    float resolved_x = svg->x()->CurrentValue()->Value(length_context);
+    float resolved_y = svg->y()->CurrentValue()->Value(length_context);
+    float resolved_width;
+    float resolved_height;
+
+    if (RuntimeEnabledFeatures::
+            WidthAndHeightAsPresentationAttributesOnNestedSvgEnabled()) {
+      const SVGViewportResolver viewport_resolver(*this);
+      const ComputedStyle& style = StyleRef();
+
+      resolved_width = ResolveViewportDimension(
+          style.Width(), viewport_resolver, style, SVGLengthMode::kWidth);
+
+      resolved_height = ResolveViewportDimension(
+          style.Height(), viewport_resolver, style, SVGLengthMode::kHeight);
+
+    } else {
+      resolved_width = svg->width()->CurrentValue()->Value(length_context);
+      resolved_height = svg->height()->CurrentValue()->Value(length_context);
+    }
+
+    viewport_.SetRect(resolved_x, resolved_y, resolved_width, resolved_height);
+
     if (old_viewport != viewport_) {
       // The transform depends on viewport values.
       SetNeedsTransformUpdate();
diff --git a/third_party/blink/renderer/core/style/computed_style_constants.h b/third_party/blink/renderer/core/style/computed_style_constants.h
index b0f61763..c7c7698 100644
--- a/third_party/blink/renderer/core/style/computed_style_constants.h
+++ b/third_party/blink/renderer/core/style/computed_style_constants.h
@@ -293,7 +293,7 @@
                          int(kInternalAutoFlowDirectionColumn)
 };
 
-static const size_t kContainmentBits = 5;
+static const size_t kContainmentBits = 6;
 enum Containment {
   kContainsNone = 0x0,
   kContainsLayout = 0x1,
@@ -301,6 +301,7 @@
   kContainsPaint = 0x4,
   kContainsBlockSize = 0x8,
   kContainsInlineSize = 0x10,
+  kContainsViewTransition = 0x20,
   kContainsSize = kContainsBlockSize | kContainsInlineSize,
   kContainsStrict =
       kContainsStyle | kContainsLayout | kContainsPaint | kContainsSize,
diff --git a/third_party/blink/renderer/core/svg/svg_svg_element.cc b/third_party/blink/renderer/core/svg/svg_svg_element.cc
index 2976a3f..7db0428 100644
--- a/third_party/blink/renderer/core/svg/svg_svg_element.cc
+++ b/third_party/blink/renderer/core/svg/svg_svg_element.cc
@@ -59,6 +59,7 @@
 #include "third_party/blink/renderer/core/svg/svg_point_tear_off.h"
 #include "third_party/blink/renderer/core/svg/svg_preserve_aspect_ratio.h"
 #include "third_party/blink/renderer/core/svg/svg_rect_tear_off.h"
+#include "third_party/blink/renderer/core/svg/svg_symbol_element.h"
 #include "third_party/blink/renderer/core/svg/svg_transform.h"
 #include "third_party/blink/renderer/core/svg/svg_transform_list.h"
 #include "third_party/blink/renderer/core/svg/svg_transform_tear_off.h"
@@ -160,6 +161,52 @@
   return zoom_and_pan == kSVGZoomAndPanMagnify;
 }
 
+// There are few cases when the width and height attributes on an inner `svg`
+// may need to be collected explicitly as styles.
+//
+// Case 1: The width and height attributes on the `use` element override the
+// values for the corresponding attributes on a referenced `svg` or `symbol`
+// element when determining the used value for that property on the instance
+// root element. [1]
+//
+// Case 2:If no width or height attributes are specified on the `use` element,
+// corresponding reference element's width or height is used. For `svg` element
+// since width and height are presentation attributes now, they are collected
+// as styles but for `symbol` since width and height currently are not collected
+// as styles so for `symbol` element we need to collect these styles
+// explicitly. (crbug.com/41413321)
+//
+//[1] (https://svgwg.org/svg2-draft/struct.html#UseElement)
+CSSPropertyValueSet*
+SVGSVGElement::CreateWidthAndHeightPresentationAttributeStyleIfNeeded(
+    const Element& original_element) {
+  if (IsOutermostSVGSVGElement()) {
+    return nullptr;
+  }
+
+  if (InUseShadowTree()) {
+    auto* use_element = DynamicTo<SVGUseElement>(ParentOrShadowHostElement());
+
+    if (use_element && (use_element->width()->IsSpecified() ||
+                        use_element->height()->IsSpecified() ||
+                        IsA<SVGSymbolElement>(original_element))) {
+      HeapVector<CSSPropertyValue, 8> values;
+      SVGAnimatedPropertyBase* properties[]{width_.Get(), height_.Get()};
+
+      for (SVGAnimatedPropertyBase* property : properties) {
+        if (const CSSValue* css_value = property->CssValue()) {
+          AddPropertyToPresentationAttributeStyle(
+              values, property->CssPropertyId(), *css_value);
+        }
+      }
+
+      return ImmutableCSSPropertyValueSet::Create(values, kSVGAttributeMode);
+    }
+  }
+
+  return nullptr;
+}
+
 void SVGSVGElement::ParseAttribute(const AttributeModificationParams& params) {
   const QualifiedName& name = params.name;
   const AtomicString& value = params.new_value;
@@ -204,9 +251,13 @@
 }
 
 bool SVGSVGElement::IsPresentationAttribute(const QualifiedName& name) const {
-  if ((name == svg_names::kWidthAttr || name == svg_names::kHeightAttr) &&
-      !IsOutermostSVGSVGElement())
-    return false;
+  if (!RuntimeEnabledFeatures::
+          WidthAndHeightAsPresentationAttributesOnNestedSvgEnabled()) {
+    if ((name == svg_names::kWidthAttr || name == svg_names::kHeightAttr) &&
+        !IsOutermostSVGSVGElement()) {
+      return false;
+    }
+  }
   return SVGGraphicsElement::IsPresentationAttribute(name);
 }
 
@@ -214,13 +265,17 @@
     const QualifiedName& name,
     const AtomicString& value,
     HeapVector<CSSPropertyValue, 8>& style) {
-  // We shouldn't collect style for 'width' and 'height' on inner <svg>, so
-  // bail here in that case to avoid having the generic logic in SVGElement
-  // picking it up.
-  if ((name == svg_names::kWidthAttr || name == svg_names::kHeightAttr) &&
-      !IsOutermostSVGSVGElement()) {
-    return;
+  if (!RuntimeEnabledFeatures::
+          WidthAndHeightAsPresentationAttributesOnNestedSvgEnabled()) {
+    // We shouldn't collect style for 'width' and 'height' on inner <svg>, so
+    // bail here in that case to avoid having the generic logic in SVGElement
+    // picking it up.
+    if ((name == svg_names::kWidthAttr || name == svg_names::kHeightAttr) &&
+        !IsOutermostSVGSVGElement()) {
+      return;
+    }
   }
+
   SVGGraphicsElement::CollectStyleForPresentationAttribute(name, value, style);
 }
 
@@ -246,6 +301,10 @@
         UpdatePresentationAttributeStyle(params.property);
         if (layout_object)
           To<LayoutSVGRoot>(layout_object)->IntrinsicSizingInfoChanged();
+      } else if (
+          RuntimeEnabledFeatures::
+              WidthAndHeightAsPresentationAttributesOnNestedSvgEnabled()) {
+        UpdatePresentationAttributeStyle(params.property);
       }
     } else {
       UpdatePresentationAttributeStyle(params.property);
diff --git a/third_party/blink/renderer/core/svg/svg_svg_element.h b/third_party/blink/renderer/core/svg/svg_svg_element.h
index 3f291dd..fb6bbbbf 100644
--- a/third_party/blink/renderer/core/svg/svg_svg_element.h
+++ b/third_party/blink/renderer/core/svg/svg_svg_element.h
@@ -111,6 +111,9 @@
 
   bool ZoomAndPanEnabled() const;
 
+  CSSPropertyValueSet* CreateWidthAndHeightPresentationAttributeStyleIfNeeded(
+      const Element& original_element);
+
   SVGAnimatedLength* x() const { return x_.Get(); }
   SVGAnimatedLength* y() const { return y_.Get(); }
   SVGAnimatedLength* width() const { return width_.Get(); }
diff --git a/third_party/blink/renderer/core/view_transition/view_transition_style_tracker.cc b/third_party/blink/renderer/core/view_transition/view_transition_style_tracker.cc
index f6fdcdfe5f..44fc6998 100644
--- a/third_party/blink/renderer/core/view_transition/view_transition_style_tracker.cc
+++ b/third_party/blink/renderer/core/view_transition/view_transition_style_tracker.cc
@@ -704,8 +704,6 @@
     paint_layer = document_->GetLayoutView()->PaintingLayer();
   }
 
-  // PaintLayer* paint_layer = document_->GetLayoutView()->PaintingLayer();
-
   AddTransitionElementsFromCSSRecursive(
       paint_layer, document_.Get(), containing_group_stack,
       /*nearest_group_with_contain=*/g_null_atom);
@@ -766,6 +764,17 @@
   auto& root_object = root->GetLayoutObject();
   auto& root_style = root_object.StyleRef();
 
+  if ((root_style.Contain() & kContainsViewTransition) && element_ &&
+      (root_object.GetNode() != *element_)) {
+    // Having "contain: view-transition" on a descendant of the scoped element
+    // halts propagation of tag discovery into the descendant's subtree.
+    // If the scoped element itself has "contain: view-transition", the tag
+    // discovery process proceeds normally.
+    // TODO(crbug.com/422522044): Should "contain: strict" include
+    // view-transition
+    return;
+  }
+
   const auto& view_transition_name = root_style.ViewTransitionName();
   AtomicString current_name;
   if (view_transition_name && !root_object.IsFragmented()) {
@@ -800,8 +809,9 @@
     }
   }
 
-  if (root_object.ChildPaintBlockedByDisplayLock())
+  if (root_object.ChildPaintBlockedByDisplayLock()) {
     return;
+  }
 
   if (current_name) {
     containing_group_stack.push_back(current_name);
diff --git a/third_party/blink/renderer/platform/runtime_enabled_features.json5 b/third_party/blink/renderer/platform/runtime_enabled_features.json5
index c4e603f2..caedf1be 100644
--- a/third_party/blink/renderer/platform/runtime_enabled_features.json5
+++ b/third_party/blink/renderer/platform/runtime_enabled_features.json5
@@ -559,8 +559,10 @@
     },
     {
       name: "AudioOutputDevices",
-      // Android does not yet support switching of audio output devices
+      // Android support for switching audio output devices is not stable
       status: {"Android": "", "default": "stable"},
+      public: true,
+      base_feature: "none"
     },
     {
       name: "AudioVideoTracks",
@@ -5791,6 +5793,10 @@
       public: true,
       status: "experimental",
     },
+    {
+      name: "WidthAndHeightAsPresentationAttributesOnNestedSvg",
+      status: "experimental",
+    },
     // If enabled, window.default[Ss]tatus will be supported. This is disabled
     // by default, and is here to allow this behavior to be re-enabled via Finch
     // in case of problems. This flag should be removed by Q1 2023, assuming
diff --git a/third_party/blink/web_tests/TestExpectations b/third_party/blink/web_tests/TestExpectations
index da031d3..7418d6c 100644
--- a/third_party/blink/web_tests/TestExpectations
+++ b/third_party/blink/web_tests/TestExpectations
@@ -4359,10 +4359,6 @@
 # Fails when run under --disable-site-isolation-trials.
 crbug.com/1317079 [ Linux ] external/wpt/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/window-iframe-messagechannel.https.html [ Failure Pass ]
 
-# Test timing out when SharedArrayBuffer is disabled by default.
-# See https://crbug.com/40175672
-crbug.com/40175672 http/tests/devtools/sources/debugger-breakpoints/set-breakpoint-while-blocking-main-thread.js [ Skip Timeout ]
-
 # Test output varies depending on the bot. A single expectation file doesn't
 # work.
 
@@ -6115,11 +6111,6 @@
 crbug.com/1312164 webaudio/internals/audiocontext-gc.html [ Failure Skip ]
 crbug.com/1312164 webaudio/internals/audioworkletnode-gc.https.html [ Failure Skip ]
 
-
-# Tests that need updates due to rounding math in layout
-crbug.com/40219666 http/tests/devtools/console/console-viewport-indices.js [ Failure ]
-crbug.com/40219666 http/tests/devtools/console/viewport-testing/console-stick-to-bottom-expand-object.js [ Failure ]
-
 # Sheriff 2022-03-23: More flaky tests.
 crbug.com/1309483 [ Win ] virtual/gpu-rasterization/images/directly-composited-image-orientation.html [ Failure Pass ]
 crbug.com/1309633 [ Debug Mac13-arm64 ] external/wpt/FileAPI/url/url-lifetime.html [ Failure ]
@@ -6629,8 +6620,6 @@
 # Sheriff 2022-10-07
 crbug.com/1372556 [ Linux ] external/wpt/css/css-text/text-transform/text-transform-capitalize-* [ Failure Pass ]
 
-crbug.com/40240869 http/tests/devtools/sources/debugger-ui/debugger-save-to-temp-var.js [ Crash Failure Pass Timeout ]
-
 
 # Sheriff 2022-10-12
 # Temporarily disable tests to investigate test_driver.click issue
@@ -7395,8 +7384,6 @@
 crbug.com/1449995 [ Mac13-arm64 ] virtual/text-antialias/trailing-white-space-2.html [ Failure Pass ]
 crbug.com/1449995 [ Mac13-arm64 ] virtual/text-antialias/whitespace/pre-wrap-last-char.html [ Failure Pass ]
 
-crbug.com/40269140 http/tests/devtools/application-panel/resources-panel-iframe-idb.js [ Failure Pass ]
-
 # Sheriff 2023-05-30
 crbug.com/1450020 [ Mac12 Release ] compositing/geometry/preserve-3d-switching.html [ Failure Pass ]
 crbug.com/1450020 [ Mac12-arm64 Release ] compositing/geometry/preserve-3d-switching.html [ Failure Pass ]
@@ -7574,10 +7561,6 @@
 # Gardener 2023-10-13
 crbug.com/1492371 [ Mac ] virtual/shared-storage-fenced-frame-mparch/http/tests/inspector-protocol/shared-storage/event-breakpoints.js [ Pass Timeout ]
 
-
-crbug.com/40276793 http/tests/devtools/security/interstitial-sidebar.js [ Failure Pass ]
-crbug.com/40276793 http/tests/devtools/security/mixed-content-sidebar.js [ Failure Pass ]
-
 # Expected to fail 2023-11-13
 [ Win ] virtual/media-foundation-for-clear-dcomp/media/video-preload-first-frame-presented.html [ Failure Timeout ]
 
diff --git a/third_party/blink/web_tests/external/wpt/css/css-contain/parsing/contain-invalid.html b/third_party/blink/web_tests/external/wpt/css/css-contain/parsing/contain-invalid.html
index 9f96bbc..d73e4b4 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-contain/parsing/contain-invalid.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-contain/parsing/contain-invalid.html
@@ -25,6 +25,7 @@
 test_invalid_value("contain", "paint content");
 test_invalid_value("contain", "size inline-size");
 test_invalid_value("contain", "inline-size inline-size");
+test_invalid_value("contain", "view-transition view-transition");
 </script>
 </body>
 </html>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-contain/parsing/contain-valid.tentative.html b/third_party/blink/web_tests/external/wpt/css/css-contain/parsing/contain-valid.tentative.html
new file mode 100644
index 0000000..da35874
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-contain/parsing/contain-valid.tentative.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<html>
+<link rel="help" href="https://drafts.csswg.org/css-contain-3/#contain-property">
+<!-- TODO link scoped-view-transition-spec once available -->
+<meta name="assert" content="contain supports view-transition'.">
+<meta name="assert" content="contain serializes in canonical order.">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/css/support/parsing-testcommon.js"></script>
+</head>
+<body>
+</body>
+<script>
+/* Note this test is an extension of contain-valid. Once
+   contain: view-transition" is resolved, this test should be moved or
+   replaced, depending on the resolution. */
+test_valid_value("contain", "view-transition");
+test_valid_value("contain", "view-transition layout", "layout view-transition");
+test_valid_value("contain",
+                 "paint view-transition style",
+                 "style paint view-transition");
+</script>
+</html>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-view-transitions/scoped/contain-view-transition-name-discovery.html b/third_party/blink/web_tests/external/wpt/css/css-view-transitions/scoped/contain-view-transition-name-discovery.html
new file mode 100644
index 0000000..10a1a11
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-view-transitions/scoped/contain-view-transition-name-discovery.html
@@ -0,0 +1,106 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <meta charset="utf-8">
+  <!-- TODO update link -->
+  <link rel="help" href="https://www.w3.org/TR/css-view-transitions-2/">
+  <meta name="viewport" content="width=device-width, initial-scale=1">
+  <title>Contain and name discovery</title>
+</head>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<style>
+  #outer {
+    view-transition-name: outer;
+  }
+
+  #inner {
+    view-transition-name: inner;
+  }
+
+  .containment {
+    contain: view-transition;
+  }
+</style>
+<body>
+  <div id="outer">
+    <div id="inner"></div>
+  </div>
+</body>
+<script>
+  function capturedNames() {
+    const regexp = /\((?<name>[^\)]+)/;
+    const names = document.getAnimations()
+                     .map(a => a.effect.pseudoElement)
+                     .map(name => name.match(regexp).groups.name);
+    return [...new Set(names)].sort();
+  }
+
+  promise_test(async t => {
+    const outer = document.getElementById('outer');
+    const inner = document.getElementById('inner');
+
+    let vt = document.startViewTransition(() => {});
+    await vt.ready;
+    assert_array_equals(
+        capturedNames(), ['inner', 'outer', 'root'],
+        'Retrieve all names in absence of contain style');
+    vt.skipTransition();
+
+    document.documentElement.classList.toggle('containment');
+    vt = document.startViewTransition(() => {});
+    await vt.ready;
+    assert_array_equals(
+        capturedNames(), ['inner', 'outer', 'root'],
+        'contain on the root element does not affect tag discovery');
+    vt.skipTransition();
+
+    outer.classList.toggle('containment');
+    vt = document.startViewTransition(() => {});
+    await vt.ready;
+    assert_array_equals(
+      capturedNames(), ['root'],
+      'contain on outer element blocks tag discovery in subtree');
+    vt.skipTransition();
+
+    vt = outer.startViewTransition(() => {});
+    await vt.ready;
+    assert_array_equals(
+        capturedNames(), ['inner', 'outer'],
+        'contain on the scoped element includes self');
+    vt.skipTransition();
+
+    vt = inner.startViewTransition(() => {});
+    await vt.ready;
+    assert_array_equals(
+        capturedNames(), ['inner'],
+        'contain on ancestor of the scoped element does not affect tag ' +
+        'discovery');
+    vt.skipTransition();
+
+    outer.classList.toggle('containment');
+    inner.classList.toggle('containment');
+    vt = document.startViewTransition(() => {});
+    await vt.ready;
+    assert_array_equals(
+        capturedNames(), ['outer', 'root'],
+        'contain on inner element still permits tag discovery of ancestor ' +
+        'elements');
+    vt.skipTransition();
+
+    vt = outer.startViewTransition(() => {});
+    await vt.ready;
+    assert_array_equals(
+        capturedNames(), ['outer'],
+        'contain on inner element limits tag discovery on outer element');
+    vt.skipTransition();
+
+    vt = inner.startViewTransition(() => {});
+    await vt.ready;
+    assert_array_equals(
+        capturedNames(), ['inner'],
+        'contain permits tag discovery on scoped element');
+    vt.skipTransition();
+  }, 'Contain: view-transition blocks propagation of name discovery.');
+</script>
+</html>
diff --git a/third_party/blink/web_tests/external/wpt/svg/geometry/parsing/height-computed-expected.txt b/third_party/blink/web_tests/external/wpt/svg/geometry/parsing/height-computed-expected.txt
deleted file mode 100644
index 258dba9..0000000
--- a/third_party/blink/web_tests/external/wpt/svg/geometry/parsing/height-computed-expected.txt
+++ /dev/null
@@ -1,11 +0,0 @@
-This is a testharness.js-based test.
-[FAIL] SVG Geometry Properties: getComputedStyle().height, <svg> initial
-  assert_equals: initial expected "100px" but got "auto"
-[FAIL] SVG Geometry Properties: getComputedStyle().height, <svg> presentation attribute
-  assert_equals: presentation attribute expected "200px" but got "auto"
-[FAIL] SVG Geometry Properties: getComputedStyle().height, <svg> inline style (auto)
-  assert_equals: inline style (auto) expected "100px" but got "auto"
-[FAIL] SVG Geometry Properties: getComputedStyle().height, <svg> inline style (percentage)
-  assert_equals: inline style (percentage) expected "50px" but got "50%"
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/external/wpt/svg/geometry/parsing/width-computed-expected.txt b/third_party/blink/web_tests/external/wpt/svg/geometry/parsing/width-computed-expected.txt
deleted file mode 100644
index 107d21b..0000000
--- a/third_party/blink/web_tests/external/wpt/svg/geometry/parsing/width-computed-expected.txt
+++ /dev/null
@@ -1,11 +0,0 @@
-This is a testharness.js-based test.
-[FAIL] SVG Geometry Properties: getComputedStyle().width, <svg> initial
-  assert_equals: initial expected "200px" but got "auto"
-[FAIL] SVG Geometry Properties: getComputedStyle().width, <svg> presentation attribute
-  assert_equals: presentation attribute expected "100px" but got "auto"
-[FAIL] SVG Geometry Properties: getComputedStyle().width, <svg> inline style (auto)
-  assert_equals: inline style (auto) expected "200px" but got "auto"
-[FAIL] SVG Geometry Properties: getComputedStyle().width, <svg> inline style (percentage)
-  assert_equals: inline style (percentage) expected "100px" but got "50%"
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/external/wpt/svg/painting/reftests/small-nested-viewbox.html b/third_party/blink/web_tests/external/wpt/svg/painting/reftests/small-nested-viewbox.html
index 3d9fb8c..308835c 100644
--- a/third_party/blink/web_tests/external/wpt/svg/painting/reftests/small-nested-viewbox.html
+++ b/third_party/blink/web_tests/external/wpt/svg/painting/reftests/small-nested-viewbox.html
@@ -5,14 +5,14 @@
 <link rel="author" title="Emilio Cobos Álvarez" href="mailto:emilio@crisal.io">
 <link rel="match" href="small-nested-viewbox-ref.html">
 <style>
-  svg {
+  svg#root {
     width: 16px;
     padding: 4px;
     background: #ccc;
     color: #000;
   }
 </style>
-<svg viewBox="0 0 4 4">
+<svg id="root" viewBox="0 0 4 4">
   <svg viewBox='0 0 256 256'>
     <rect width="256" height="256" x="0" y="0" fill="green" />
   </svg>
diff --git a/third_party/blink/web_tests/external/wpt/svg/styling/nested-svg-sizing-expected.svg b/third_party/blink/web_tests/external/wpt/svg/styling/nested-svg-sizing-expected.svg
new file mode 100644
index 0000000..a8fa521
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/svg/styling/nested-svg-sizing-expected.svg
@@ -0,0 +1,5 @@
+<svg width="100" height="100" xmlns="http://www.w3.org/2000/svg">
+  <svg width="50" height="50">
+    <circle cx="50" cy="50" r="40" fill="green" />
+  </svg>
+</svg>
diff --git a/third_party/blink/web_tests/external/wpt/svg/styling/nested-svg-sizing-with-use-expected.svg b/third_party/blink/web_tests/external/wpt/svg/styling/nested-svg-sizing-with-use-expected.svg
new file mode 100644
index 0000000..daef973a
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/svg/styling/nested-svg-sizing-with-use-expected.svg
@@ -0,0 +1,5 @@
+<svg width="400" height="400" xmlns="http://www.w3.org/2000/svg">
+  <svg id="target" width="50" height="50">
+    <circle cx="50" cy="50" r="40" fill="green" />
+  </svg>
+</svg>
diff --git a/third_party/blink/web_tests/external/wpt/svg/styling/nested-svg-sizing-with-use.svg b/third_party/blink/web_tests/external/wpt/svg/styling/nested-svg-sizing-with-use.svg
new file mode 100644
index 0000000..5f3c4ee
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/svg/styling/nested-svg-sizing-with-use.svg
@@ -0,0 +1,12 @@
+<svg width="400" height="400" xmlns="http://www.w3.org/2000/svg" xmlns:html="http://www.w3.org/1999/xhtml">
+  <title>Width and Height as presentation attributes on nested svg</title>
+  <html:link rel="help" href="https://svgwg.org/svg2-draft/styling.html#TermPresentationAttribute"/>
+  <html:link rel="match"  href="nested-svg-sizing-with-use-expected.svg" />
+  <defs>
+    <svg id="target" style="width:50px; height:50px;">
+    <circle cx="50" cy="50" r="40" fill="green" />
+    </svg>
+  </defs>
+
+  <use href="#target"/>
+</svg>
diff --git a/third_party/blink/web_tests/external/wpt/svg/styling/nested-svg-sizing.svg b/third_party/blink/web_tests/external/wpt/svg/styling/nested-svg-sizing.svg
new file mode 100644
index 0000000..6c8c45f6
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/svg/styling/nested-svg-sizing.svg
@@ -0,0 +1,8 @@
+<svg width="100" height="100" xmlns="http://www.w3.org/2000/svg" xmlns:html="http://www.w3.org/1999/xhtml">
+  <title>Width and Height as presentation attributes on nested svg</title>
+  <html:link rel="help" href="https://svgwg.org/svg2-draft/styling.html#TermPresentationAttribute"/>
+  <html:link rel="match"  href="nested-svg-sizing-expected.svg" />
+  <svg style="width:50px;height:50px;">
+    <circle cx="50" cy="50" r="40" fill="green" />
+  </svg>
+</svg>
diff --git a/third_party/blink/web_tests/external/wpt/svg/styling/presentation-attributes-special-cases-expected.txt b/third_party/blink/web_tests/external/wpt/svg/styling/presentation-attributes-special-cases-expected.txt
index 7eedacb..87d03c6 100644
--- a/third_party/blink/web_tests/external/wpt/svg/styling/presentation-attributes-special-cases-expected.txt
+++ b/third_party/blink/web_tests/external/wpt/svg/styling/presentation-attributes-special-cases-expected.txt
@@ -1,9 +1,6 @@
 This is a testharness.js-based test.
-[FAIL] x, y, width, and height presentation attributes supported on svg element
-  assert_true: Presentation attribute width="1" should be supported on svg element expected true got false
 [FAIL] x, y, width, and height presentation attributes supported on symbol element
   assert_true: Presentation attribute x="1" should be supported on symbol element expected true got false
 [FAIL] x, y, width, and height presentation attributes supported on use element
   assert_true: Presentation attribute width="1" should be supported on use element expected true got false
 Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/external/wpt/webnn/conformance_tests/not_equal.https.any.js b/third_party/blink/web_tests/external/wpt/webnn/conformance_tests/not_equal.https.any.js
index 3bba726..5aed124 100644
--- a/third_party/blink/web_tests/external/wpt/webnn/conformance_tests/not_equal.https.any.js
+++ b/third_party/blink/web_tests/external/wpt/webnn/conformance_tests/not_equal.https.any.js
@@ -22,6 +22,7 @@
 };
 
 const notEqualTests = [
+  // float32 tests
   {
     'name': 'notEqual float32 0D scalar',
     'graph': {
@@ -524,13 +525,471 @@
         }
       }
     }
+  },
+
+  // float16 tests
+  {
+    'name': 'notEqual float16 0D scalar',
+    'graph': {
+      'inputs': {
+        'inputA': {
+          'data': [-0.62841796875],
+          'descriptor': {shape: [], dataType: 'float16'}
+        },
+        'inputB': {
+          'data': [-4.41796875],
+          'descriptor': {shape: [], dataType: 'float16'}
+        }
+      },
+      'operators': [{
+        'name': 'notEqual',
+        'arguments': [{'a': 'inputA'}, {'b': 'inputB'}],
+        'outputs': 'output'
+      }],
+      'expectedOutputs': {
+        'output': {'data': [1], 'descriptor': {shape: [], dataType: 'uint8'}}
+      }
+    }
+  },
+  {
+    'name': 'notEqual float16 1D constant tensors',
+    'graph': {
+      'inputs': {
+        'inputA': {
+          'data': [
+            2.806640625, 5.58984375,   2.85546875,  4.99609375,  0.97265625,
+            -4.7421875,  2.806640625,  5.58984375,  -5.109375,   6.625,
+            -2.3203125,  -7.0546875,   2.806640625, 5.58984375,  4.98046875,
+            -5.44140625, 1.1455078125, 7.7734375,   2.806640625, 5.58984375,
+            -6.24609375, -2.849609375, -2.6953125,  5.81640625
+          ],
+          'descriptor': {shape: [24], dataType: 'float16'},
+          'constant': true
+        },
+        'inputB': {
+          'data': [
+            2.806640625,   5.58984375,  -4.83984375,     4.99609375,
+            0.97265625,    -6.171875,   2.806640625,     5.58984375,
+            7.765625,      -4.30859375, -5.89453125,     -8.53125,
+            2.806640625,   5.58984375,  0.1783447265625, -4.48046875,
+            0.68212890625, -6.6875,     2.806640625,     5.58984375,
+            -9.0390625,    -1.97265625, -3.01171875,     3.626953125
+          ],
+          'descriptor': {shape: [24], dataType: 'float16'},
+          'constant': true
+        }
+      },
+      'operators': [{
+        'name': 'notEqual',
+        'arguments': [{'a': 'inputA'}, {'b': 'inputB'}],
+        'outputs': 'output'
+      }],
+      'expectedOutputs': {
+        'output': {
+          'data': [
+            0, 0, 1, 0, 0, 1, 0, 0, 1, 1, 1, 1,
+            0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1
+          ],
+          'descriptor': {shape: [24], dataType: 'uint8'}
+        }
+      }
+    }
+  },
+  {
+    'name': 'notEqual float16 1D tensors',
+    'graph': {
+      'inputs': {
+        'inputA': {
+          'data': [
+            2.806640625, 5.58984375,   2.85546875,  4.99609375,  0.97265625,
+            -4.7421875,  2.806640625,  5.58984375,  -5.109375,   6.625,
+            -2.3203125,  -7.0546875,   2.806640625, 5.58984375,  4.98046875,
+            -5.44140625, 1.1455078125, 7.7734375,   2.806640625, 5.58984375,
+            -6.24609375, -2.849609375, -2.6953125,  5.81640625
+          ],
+          'descriptor': {shape: [24], dataType: 'float16'}
+        },
+        'inputB': {
+          'data': [
+            2.806640625,   5.58984375,  -4.83984375,     4.99609375,
+            0.97265625,    -6.171875,   2.806640625,     5.58984375,
+            7.765625,      -4.30859375, -5.89453125,     -8.53125,
+            2.806640625,   5.58984375,  0.1783447265625, -4.48046875,
+            0.68212890625, -6.6875,     2.806640625,     5.58984375,
+            -9.0390625,    -1.97265625, -3.01171875,     3.626953125
+          ],
+          'descriptor': {shape: [24], dataType: 'float16'}
+        }
+      },
+      'operators': [{
+        'name': 'notEqual',
+        'arguments': [{'a': 'inputA'}, {'b': 'inputB'}],
+        'outputs': 'output'
+      }],
+      'expectedOutputs': {
+        'output': {
+          'data': [
+            0, 0, 1, 0, 0, 1, 0, 0, 1, 1, 1, 1,
+            0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1
+          ],
+          'descriptor': {shape: [24], dataType: 'uint8'}
+        }
+      }
+    }
+  },
+  {
+    'name': 'notEqual float16 2D tensors',
+    'graph': {
+      'inputs': {
+        'inputA': {
+          'data': [
+            2.806640625, 5.58984375,   2.85546875,  4.99609375,  0.97265625,
+            -4.7421875,  2.806640625,  5.58984375,  -5.109375,   6.625,
+            -2.3203125,  -7.0546875,   2.806640625, 5.58984375,  4.98046875,
+            -5.44140625, 1.1455078125, 7.7734375,   2.806640625, 5.58984375,
+            -6.24609375, -2.849609375, -2.6953125,  5.81640625
+          ],
+          'descriptor': {shape: [4, 6], dataType: 'float16'}
+        },
+        'inputB': {
+          'data': [
+            2.806640625,   5.58984375,  -4.83984375,     4.99609375,
+            0.97265625,    -6.171875,   2.806640625,     5.58984375,
+            7.765625,      -4.30859375, -5.89453125,     -8.53125,
+            2.806640625,   5.58984375,  0.1783447265625, -4.48046875,
+            0.68212890625, -6.6875,     2.806640625,     5.58984375,
+            -9.0390625,    -1.97265625, -3.01171875,     3.626953125
+          ],
+          'descriptor': {shape: [4, 6], dataType: 'float16'}
+        }
+      },
+      'operators': [{
+        'name': 'notEqual',
+        'arguments': [{'a': 'inputA'}, {'b': 'inputB'}],
+        'outputs': 'output'
+      }],
+      'expectedOutputs': {
+        'output': {
+          'data': [
+            0, 0, 1, 0, 0, 1, 0, 0, 1, 1, 1, 1,
+            0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1
+          ],
+          'descriptor': {shape: [4, 6], dataType: 'uint8'}
+        }
+      }
+    }
+  },
+  {
+    'name': 'notEqual float16 3D tensors',
+    'graph': {
+      'inputs': {
+        'inputA': {
+          'data': [
+            2.806640625, 5.58984375,   2.85546875,  4.99609375,  0.97265625,
+            -4.7421875,  2.806640625,  5.58984375,  -5.109375,   6.625,
+            -2.3203125,  -7.0546875,   2.806640625, 5.58984375,  4.98046875,
+            -5.44140625, 1.1455078125, 7.7734375,   2.806640625, 5.58984375,
+            -6.24609375, -2.849609375, -2.6953125,  5.81640625
+          ],
+          'descriptor': {shape: [2, 3, 4], dataType: 'float16'}
+        },
+        'inputB': {
+          'data': [
+            2.806640625,   5.58984375,  -4.83984375,     4.99609375,
+            0.97265625,    -6.171875,   2.806640625,     5.58984375,
+            7.765625,      -4.30859375, -5.89453125,     -8.53125,
+            2.806640625,   5.58984375,  0.1783447265625, -4.48046875,
+            0.68212890625, -6.6875,     2.806640625,     5.58984375,
+            -9.0390625,    -1.97265625, -3.01171875,     3.626953125
+          ],
+          'descriptor': {shape: [2, 3, 4], dataType: 'float16'}
+        }
+      },
+      'operators': [{
+        'name': 'notEqual',
+        'arguments': [{'a': 'inputA'}, {'b': 'inputB'}],
+        'outputs': 'output'
+      }],
+      'expectedOutputs': {
+        'output': {
+          'data': [
+            0, 0, 1, 0, 0, 1, 0, 0, 1, 1, 1, 1,
+            0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1
+          ],
+          'descriptor': {shape: [2, 3, 4], dataType: 'uint8'}
+        }
+      }
+    }
+  },
+  {
+    'name': 'notEqual float16 4D tensors',
+    'graph': {
+      'inputs': {
+        'inputA': {
+          'data': [
+            2.806640625, 5.58984375,   2.85546875,  4.99609375,  0.97265625,
+            -4.7421875,  2.806640625,  5.58984375,  -5.109375,   6.625,
+            -2.3203125,  -7.0546875,   2.806640625, 5.58984375,  4.98046875,
+            -5.44140625, 1.1455078125, 7.7734375,   2.806640625, 5.58984375,
+            -6.24609375, -2.849609375, -2.6953125,  5.81640625
+          ],
+          'descriptor': {shape: [2, 2, 2, 3], dataType: 'float16'}
+        },
+        'inputB': {
+          'data': [
+            2.806640625,   5.58984375,  -4.83984375,     4.99609375,
+            0.97265625,    -6.171875,   2.806640625,     5.58984375,
+            7.765625,      -4.30859375, -5.89453125,     -8.53125,
+            2.806640625,   5.58984375,  0.1783447265625, -4.48046875,
+            0.68212890625, -6.6875,     2.806640625,     5.58984375,
+            -9.0390625,    -1.97265625, -3.01171875,     3.626953125
+          ],
+          'descriptor': {shape: [2, 2, 2, 3], dataType: 'float16'}
+        }
+      },
+      'operators': [{
+        'name': 'notEqual',
+        'arguments': [{'a': 'inputA'}, {'b': 'inputB'}],
+        'outputs': 'output'
+      }],
+      'expectedOutputs': {
+        'output': {
+          'data': [
+            0, 0, 1, 0, 0, 1, 0, 0, 1, 1, 1, 1,
+            0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1
+          ],
+          'descriptor': {shape: [2, 2, 2, 3], dataType: 'uint8'}
+        }
+      }
+    }
+  },
+  {
+    'name': 'notEqual float16 5D tensors',
+    'graph': {
+      'inputs': {
+        'inputA': {
+          'data': [
+            2.806640625, 5.58984375,   2.85546875,  4.99609375,  0.97265625,
+            -4.7421875,  2.806640625,  5.58984375,  -5.109375,   6.625,
+            -2.3203125,  -7.0546875,   2.806640625, 5.58984375,  4.98046875,
+            -5.44140625, 1.1455078125, 7.7734375,   2.806640625, 5.58984375,
+            -6.24609375, -2.849609375, -2.6953125,  5.81640625
+          ],
+          'descriptor': {shape: [2, 2, 1, 2, 3], dataType: 'float16'}
+        },
+        'inputB': {
+          'data': [
+            2.806640625,   5.58984375,  -4.83984375,     4.99609375,
+            0.97265625,    -6.171875,   2.806640625,     5.58984375,
+            7.765625,      -4.30859375, -5.89453125,     -8.53125,
+            2.806640625,   5.58984375,  0.1783447265625, -4.48046875,
+            0.68212890625, -6.6875,     2.806640625,     5.58984375,
+            -9.0390625,    -1.97265625, -3.01171875,     3.626953125
+          ],
+          'descriptor': {shape: [2, 2, 1, 2, 3], dataType: 'float16'}
+        }
+      },
+      'operators': [{
+        'name': 'notEqual',
+        'arguments': [{'a': 'inputA'}, {'b': 'inputB'}],
+        'outputs': 'output'
+      }],
+      'expectedOutputs': {
+        'output': {
+          'data': [
+            0, 0, 1, 0, 0, 1, 0, 0, 1, 1, 1, 1,
+            0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1
+          ],
+          'descriptor': {shape: [2, 2, 1, 2, 3], dataType: 'uint8'}
+        }
+      }
+    }
+  },
+  {
+    'name': 'notEqual float16 broadcast 0D to 4D',
+    'graph': {
+      'inputs': {
+        'inputA': {
+          'data': [2.806640625],
+          'descriptor': {shape: [], dataType: 'float16'}
+        },
+        'inputB': {
+          'data': [
+            2.806640625, 5.58984375,   2.85546875,  4.99609375,  0.97265625,
+            -4.7421875,  2.806640625,  5.58984375,  -5.109375,   6.625,
+            -2.3203125,  -7.0546875,   2.806640625, 5.58984375,  4.98046875,
+            -5.44140625, 1.1455078125, 7.7734375,   2.806640625, 5.58984375,
+            -6.24609375, -2.849609375, -2.6953125,  5.81640625
+          ],
+          'descriptor': {shape: [2, 2, 2, 3], dataType: 'float16'}
+        }
+      },
+      'operators': [{
+        'name': 'notEqual',
+        'arguments': [{'a': 'inputA'}, {'b': 'inputB'}],
+        'outputs': 'output'
+      }],
+      'expectedOutputs': {
+        'output': {
+          'data': [
+            0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1,
+            0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1
+          ],
+          'descriptor': {shape: [2, 2, 2, 3], dataType: 'uint8'}
+        }
+      }
+    }
+  },
+  {
+    'name': 'notEqual float16 broadcast 1D to 4D',
+    'graph': {
+      'inputs': {
+        'inputA': {
+          'data': [2.806640625],
+          'descriptor': {shape: [1], dataType: 'float16'}
+        },
+        'inputB': {
+          'data': [
+            2.806640625, 5.58984375,   2.85546875,  4.99609375,  0.97265625,
+            -4.7421875,  2.806640625,  5.58984375,  -5.109375,   6.625,
+            -2.3203125,  -7.0546875,   2.806640625, 5.58984375,  4.98046875,
+            -5.44140625, 1.1455078125, 7.7734375,   2.806640625, 5.58984375,
+            -6.24609375, -2.849609375, -2.6953125,  5.81640625
+          ],
+          'descriptor': {shape: [2, 2, 2, 3], dataType: 'float16'}
+        }
+      },
+      'operators': [{
+        'name': 'notEqual',
+        'arguments': [{'a': 'inputA'}, {'b': 'inputB'}],
+        'outputs': 'output'
+      }],
+      'expectedOutputs': {
+        'output': {
+          'data': [
+            0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1,
+            0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1
+          ],
+          'descriptor': {shape: [2, 2, 2, 3], dataType: 'uint8'}
+        }
+      }
+    }
+  },
+  {
+    'name': 'notEqual float16 broadcast 2D to 4D',
+    'graph': {
+      'inputs': {
+        'inputA': {
+          'data': [
+            2.806640625, 5.58984375,   2.85546875,  4.99609375,  0.97265625,
+            -4.7421875,  2.806640625,  5.58984375,  -5.109375,   6.625,
+            -2.3203125,  -7.0546875,   2.806640625, 5.58984375,  4.98046875,
+            -5.44140625, 1.1455078125, 7.7734375,   2.806640625, 5.58984375,
+            -6.24609375, -2.849609375, -2.6953125,  5.81640625
+          ],
+          'descriptor': {shape: [2, 2, 2, 3], dataType: 'float16'}
+        },
+        'inputB': {
+          'data': [
+            2.806640625, 5.58984375, -4.9609375, -2.86328125, -3.01171875,
+            3.626953125
+          ],
+          'descriptor': {shape: [2, 3], dataType: 'float16'}
+        }
+      },
+      'operators': [{
+        'name': 'notEqual',
+        'arguments': [{'a': 'inputA'}, {'b': 'inputB'}],
+        'outputs': 'output'
+      }],
+      'expectedOutputs': {
+        'output': {
+          'data': [
+            0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1,
+            0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1
+          ],
+          'descriptor': {shape: [2, 2, 2, 3], dataType: 'uint8'}
+        }
+      }
+    }
+  },
+  {
+    'name': 'notEqual float16 broadcast 3D to 4D',
+    'graph': {
+      'inputs': {
+        'inputA': {
+          'data': [
+            2.806640625, 5.58984375,   2.85546875,  4.99609375,  0.97265625,
+            -4.7421875,  2.806640625,  5.58984375,  -5.109375,   6.625,
+            -2.3203125,  -7.0546875,   2.806640625, 5.58984375,  4.98046875,
+            -5.44140625, 1.1455078125, 7.7734375,   2.806640625, 5.58984375,
+            -6.24609375, -2.849609375, -2.6953125,  5.81640625
+          ],
+          'descriptor': {shape: [2, 2, 2, 3], dataType: 'float16'}
+        },
+        'inputB': {
+          'data': [2.806640625, 5.58984375, -9.0390625, 3.626953125],
+          'descriptor': {shape: [2, 2, 1], dataType: 'float16'}
+        }
+      },
+      'operators': [{
+        'name': 'notEqual',
+        'arguments': [{'a': 'inputA'}, {'b': 'inputB'}],
+        'outputs': 'output'
+      }],
+      'expectedOutputs': {
+        'output': {
+          'data': [
+            0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+            0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1
+          ],
+          'descriptor': {shape: [2, 2, 2, 3], dataType: 'uint8'}
+        }
+      }
+    }
+  },
+  {
+    'name': 'notEqual float16 broadcast 4D to 4D',
+    'graph': {
+      'inputs': {
+        'inputA': {
+          'data': [2.806640625],
+          'descriptor': {shape: [1, 1, 1, 1], dataType: 'float16'}
+        },
+        'inputB': {
+          'data': [
+            2.806640625, 5.58984375,   2.85546875,  4.99609375,  0.97265625,
+            -4.7421875,  2.806640625,  5.58984375,  -5.109375,   6.625,
+            -2.3203125,  -7.0546875,   2.806640625, 5.58984375,  4.98046875,
+            -5.44140625, 1.1455078125, 7.7734375,   2.806640625, 5.58984375,
+            -6.24609375, -2.849609375, -2.6953125,  5.81640625
+          ],
+          'descriptor': {shape: [2, 2, 2, 3], dataType: 'float16'}
+        }
+      },
+      'operators': [{
+        'name': 'notEqual',
+        'arguments': [{'a': 'inputA'}, {'b': 'inputB'}],
+        'outputs': 'output'
+      }],
+      'expectedOutputs': {
+        'output': {
+          'data': [
+            0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1,
+            0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1
+          ],
+          'descriptor': {shape: [2, 2, 2, 3], dataType: 'uint8'}
+        }
+      }
+    }
   }
 ];
 
 if (navigator.ml) {
   notEqualTests.forEach((test) => {
     webnn_conformance_test(
-        buildAndExecuteGraph, getNotEqualPrecisionTolerance, test);
+        buildAndExecuteGraph, getNotEqualPrecisionTolerance, test,
+        /*cast_to_supported_type=*/true);
   });
 } else {
   test(() => assert_implements(navigator.ml, 'missing navigator.ml'));
diff --git a/third_party/blink/web_tests/fast/multicol/composited-layer-expected.txt b/third_party/blink/web_tests/fast/multicol/composited-layer-expected.txt
index cc7b546b..fc70d425 100644
--- a/third_party/blink/web_tests/fast/multicol/composited-layer-expected.txt
+++ b/third_party/blink/web_tests/fast/multicol/composited-layer-expected.txt
@@ -7,7 +7,7 @@
 "backgroundColor": "#FFFFFF"
 },
 {
-"name": "LayoutBlockFlow DIV id='multicol'",
+"name": "LayoutBlockFlow (multicol) DIV id='multicol'",
 "bounds": [200, 100],
 "drawsContent": false,
 "transform": 1
diff --git a/third_party/blink/web_tests/http/tests/devtools/application-panel/resources-panel-iframe-idb-expected.txt b/third_party/blink/web_tests/http/tests/devtools/application-panel/resources-panel-iframe-idb-expected.txt
index 95e5e5be..37b79048 100644
--- a/third_party/blink/web_tests/http/tests/devtools/application-panel/resources-panel-iframe-idb-expected.txt
+++ b/third_party/blink/web_tests/http/tests/devtools/application-panel/resources-panel-iframe-idb-expected.txt
@@ -2,45 +2,28 @@
 
 Initial tree...
 
-Application
- Manifest
-  Identity
-  Presentation
-  Protocol Handlers
-  Icons
- Service Workers
- Storage
 Storage
- Local Storage
-  http://127.0.0.1:8000/
-  http://devtools.oopif.test:8000/^0http://127.0.0.1
- Session Storage
-  http://127.0.0.1:8000/
-  http://devtools.oopif.test:8000/^0http://127.0.0.1
+ Local storage
+  http://127.0.0.1:8000
+  http://devtools.oopif.test:8000
+ Session storage
+  http://127.0.0.1:8000
+  http://devtools.oopif.test:8000
+ Extension storage
  IndexedDB
   Database-iframe
    Database-iframe
   Database-main-frame
- Web SQL
  Cookies
   http://127.0.0.1:8000
   http://devtools.oopif.test:8000
- Private State Tokens
- Interest Groups
- Shared Storage
+ Private state tokens
+ Interest groups
+ Shared storage
   http://127.0.0.1:8000
   http://devtools.oopif.test:8000
- Cache Storage
-Background Services
- Back/forward cache
- Background Fetch
- Background Sync
- Bounce Tracking Mitigations
- Notifications
- Payment Handler
- Periodic Background Sync
- Push Messaging
- Reporting API
+ Cache storage
+ Storage buckets
 Frames
  top
   indexeddb-in-iframe.html
@@ -49,85 +32,51 @@
 
 Remove iframe from page...
 
-Application
- Manifest
-  Identity
-  Presentation
-  Protocol Handlers
-  Icons
- Service Workers
- Storage
 Storage
- Local Storage
-  http://127.0.0.1:8000/
- Session Storage
-  http://127.0.0.1:8000/
+ Local storage
+  http://127.0.0.1:8000
+ Session storage
+  http://127.0.0.1:8000
+ Extension storage
  IndexedDB
   Database-main-frame
- Web SQL
  Cookies
   http://127.0.0.1:8000
   http://devtools.oopif.test:8000
- Private State Tokens
- Interest Groups
- Shared Storage
+ Private state tokens
+ Interest groups
+ Shared storage
   http://127.0.0.1:8000
- Cache Storage
-Background Services
- Back/forward cache
- Background Fetch
- Background Sync
- Bounce Tracking Mitigations
- Notifications
- Payment Handler
- Periodic Background Sync
- Push Messaging
- Reporting API
+ Cache storage
+ Storage buckets
 Frames
  top
   inspected-page.html
 
 Add iframe to page again...
 
-Application
- Manifest
-  Identity
-  Presentation
-  Protocol Handlers
-  Icons
- Service Workers
- Storage
 Storage
- Local Storage
-  http://127.0.0.1:8000/
-  http://devtools.oopif.test:8000/^0http://127.0.0.1
- Session Storage
-  http://127.0.0.1:8000/
-  http://devtools.oopif.test:8000/^0http://127.0.0.1
+ Local storage
+  http://127.0.0.1:8000
+  http://devtools.oopif.test:8000
+ Session storage
+  http://127.0.0.1:8000
+  http://devtools.oopif.test:8000
+ Extension storage
  IndexedDB
   Database-main-frame
   Database-iframe
    Database-iframe
- Web SQL
  Cookies
   http://127.0.0.1:8000
   http://devtools.oopif.test:8000
- Private State Tokens
- Interest Groups
- Shared Storage
+ Private state tokens
+ Interest groups
+ Shared storage
   http://127.0.0.1:8000
   http://devtools.oopif.test:8000
- Cache Storage
-Background Services
- Back/forward cache
- Background Fetch
- Background Sync
- Bounce Tracking Mitigations
- Notifications
- Payment Handler
- Periodic Background Sync
- Push Messaging
- Reporting API
+ Cache storage
+ Storage buckets
 Frames
  top
   indexeddb-in-iframe.html
diff --git a/third_party/blink/web_tests/http/tests/devtools/application-panel/resources-panel-iframe-idb.js b/third_party/blink/web_tests/http/tests/devtools/application-panel/resources-panel-iframe-idb.js
index c760023d..c39f7cc 100644
--- a/third_party/blink/web_tests/http/tests/devtools/application-panel/resources-panel-iframe-idb.js
+++ b/third_party/blink/web_tests/http/tests/devtools/application-panel/resources-panel-iframe-idb.js
@@ -2,50 +2,67 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-import {TestRunner} from 'test_runner';
 import {ApplicationTestRunner} from 'application_test_runner';
 import {ConsoleTestRunner} from 'console_test_runner';
-
 import * as Common from 'devtools/core/common/common.js';
 import * as Application from 'devtools/panels/application/application.js';
+import {TestRunner} from 'test_runner';
 
 (async function() {
-  TestRunner.addResult(`Tests Application Panel's handling of storages in iframes.\n`);
-  // Note: every test that uses a storage API must manually clean-up state from previous tests.
+  TestRunner.addResult(
+      `Tests Application Panel's handling of storages in iframes.\n`);
+  // Note: every test that uses a storage API must manually clean-up state from
+  // previous tests.
   await ApplicationTestRunner.resetState();
 
   await TestRunner.showPanel('resources');
 
   function createIndexedDBInMainFrame(callback) {
     var mainFrameId = TestRunner.resourceTreeModel.mainFrame.id;
-    var model = TestRunner.mainTarget.model(Application.IndexedDBModel.IndexedDBModel);
-    ApplicationTestRunner.createDatabase(mainFrameId, 'Database-main-frame', () => {
-      var event = model.addEventListener(Application.IndexedDBModel.Events.DatabaseAdded, () => {
-        Common.EventTarget.removeEventListeners([event]);
-        callback();
-      });
-      model.refreshDatabaseNames();
-    });
+    var model =
+        TestRunner.mainTarget.model(Application.IndexedDBModel.IndexedDBModel);
+    ApplicationTestRunner.createDatabase(
+        mainFrameId, 'Database-main-frame', () => {
+          var event = model.addEventListener(
+              Application.IndexedDBModel.Events.DatabaseAdded, () => {
+                Common.EventTarget.removeEventListeners([event]);
+                callback();
+              });
+          model.refreshDatabaseNames();
+        });
   }
 
   function dumpTree(node, level) {
     for (var child of node.children()) {
-      TestRunner.addResult(' '.repeat(level) + child.listItemElement.textContent);
+      const childText = child.listItemElement.textContent;
+      if (level === 0 && !['Storage', 'Frames'].includes(childText)) {
+        continue;
+      }
+      TestRunner.addResult(' '.repeat(level) + childText);
       dumpTree(child, level + 1);
     }
   }
 
   // create IndexedDB in iframe
-  // We are enforcing OOPIFs here, so that we are sure that the bots on all platforms create an OOPIF.
-  // This would not be guaranteed otherwise and could potentially result in a different display name
-  // for the iframe in the frame tree.
-  await TestRunner.addIframe('http://devtools.oopif.test:8000/devtools/application-panel/resources/indexeddb-in-iframe.html', {'id': 'indexeddb_page'});
+  // We are enforcing OOPIFs here, so that we are sure that the bots on all
+  // platforms create an OOPIF. This would not be guaranteed otherwise and could
+  // potentially result in a different display name for the iframe in the frame
+  // tree.
+  await TestRunner.addIframe(
+      'http://devtools.oopif.test:8000/devtools/application-panel/resources/indexeddb-in-iframe.html',
+      {'id': 'indexeddb_page'});
 
-  await TestRunner.addSnifferPromise(Application.ResourcesPanel.ResourcesPanel.instance().sidebar.indexedDBListTreeElement, 'indexedDBLoadedForTest');
+  await TestRunner.addSnifferPromise(
+      Application.ResourcesPanel.ResourcesPanel.instance()
+          .sidebar.indexedDBListTreeElement,
+      'indexedDBLoadedForTest');
 
   // create IndexedDB in main frame
   await new Promise(createIndexedDBInMainFrame);
-  await TestRunner.addSnifferPromise(Application.ResourcesPanel.ResourcesPanel.instance().sidebar.indexedDBListTreeElement, 'indexedDBLoadedForTest');
+  await TestRunner.addSnifferPromise(
+      Application.ResourcesPanel.ResourcesPanel.instance()
+          .sidebar.indexedDBListTreeElement,
+      'indexedDBLoadedForTest');
 
   const view = Application.ResourcesPanel.ResourcesPanel.instance();
 
@@ -64,8 +81,13 @@
   dumpTree(view.sidebar.sidebarTree.rootElement(), 0);
 
   TestRunner.addResult('\nAdd iframe to page again...\n');
-  await TestRunner.addIframe('http://devtools.oopif.test:8000/devtools/application-panel/resources/indexeddb-in-iframe.html', {'id': 'indexeddb_page'});
-  await TestRunner.addSnifferPromise(Application.ResourcesPanel.ResourcesPanel.instance().sidebar.indexedDBListTreeElement, 'indexedDBLoadedForTest');
+  await TestRunner.addIframe(
+      'http://devtools.oopif.test:8000/devtools/application-panel/resources/indexeddb-in-iframe.html',
+      {'id': 'indexeddb_page'});
+  await TestRunner.addSnifferPromise(
+      Application.ResourcesPanel.ResourcesPanel.instance()
+          .sidebar.indexedDBListTreeElement,
+      'indexedDBLoadedForTest');
 
   dumpTree(view.sidebar.sidebarTree.rootElement(), 0);
 
diff --git a/third_party/blink/web_tests/http/tests/devtools/console/console-viewport-indices-expected.txt b/third_party/blink/web_tests/http/tests/devtools/console/console-viewport-indices-expected.txt
deleted file mode 100644
index 8386edae..0000000
--- a/third_party/blink/web_tests/http/tests/devtools/console/console-viewport-indices-expected.txt
+++ /dev/null
@@ -1,45 +0,0 @@
-Verifies viewport's visible and active message ranges.
-
-
-Running: testEmptyViewport
-Expected and actual visible ranges match
-
-Running: testFirstLastVisibleIndices
-Logging 100 Normal messages
-Force item to be first: 0
-Expected and actual visible ranges match
-Force item to be first: 1
-Expected and actual visible ranges match
-Scroll a bit down: 15px
-Expected and actual visible ranges match
-Force item to be last: 50
-Expected and actual visible ranges match
-Force item to be last: 99
-Expected and actual visible ranges match
-
-Running: testMultilineMessages
-Logging 100 Multiline messages
-Force item to be first: 0
-Expected and actual visible ranges match
-Force item to be first: 1
-Expected and actual visible ranges match
-Scroll a bit down: 15px
-Expected and actual visible ranges match
-Force item to be last: 50
-Expected and actual visible ranges match
-Force item to be last: 99
-Expected and actual visible ranges match
-
-Running: testSlightlyBiggerMessages
-Logging 100 SlightlyBigger messages
-Force item to be first: 0
-Expected and actual visible ranges match
-Force item to be first: 1
-Expected and actual visible ranges match
-Scroll a bit down: 15px
-Expected and actual visible ranges match
-Force item to be last: 50
-Expected and actual visible ranges match
-Force item to be last: 99
-Expected and actual visible ranges match
-
diff --git a/third_party/blink/web_tests/http/tests/devtools/console/console-viewport-indices.js b/third_party/blink/web_tests/http/tests/devtools/console/console-viewport-indices.js
deleted file mode 100644
index 0286804..0000000
--- a/third_party/blink/web_tests/http/tests/devtools/console/console-viewport-indices.js
+++ /dev/null
@@ -1,143 +0,0 @@
-// Copyright 2018 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-import {TestRunner} from 'test_runner';
-import {ConsoleTestRunner} from 'console_test_runner';
-
-import * as Platform from 'devtools/core/platform/platform.js';
-import * as Console from 'devtools/panels/console/console.js';
-
-(async function() {
-    TestRunner.addResult(`Verifies viewport's visible and active message ranges.\n`);
-    await TestRunner.showPanel('console');
-    await TestRunner.evaluateInPagePromise(`
-        function addNormalMessages(count)
-        {
-            for (var i = 0; i < count; ++i)
-                console.log("Message #" + i);
-        }
-
-        function addMultilineMessages(count)
-        {
-            for (var i = 0; i < count; ++i)
-                console.log("Message\\n#" + i);
-        }
-
-        function addSlightlyBiggerMessages(count)
-        {
-            for (var i = 0; i < count; ++i)
-              console.log('%cMessage #' + i, 'padding-bottom: 1px');
-        }
-
-        //# sourceURL=console-viewport-indices.js
-      `);
-
-    ConsoleTestRunner.fixConsoleViewportDimensions(600, 200);
-    var consoleView = Console.ConsoleView.ConsoleView.instance();
-    var viewport = consoleView.viewport;
-
-    function logMessages(count, type) {
-      TestRunner.addResult(`Logging ${count} ${type} messages`);
-      return new Promise(resolve => {
-        var awaitingMessagesCount = count;
-        function messageAdded() {
-          if (!--awaitingMessagesCount) {
-            viewport.invalidate();
-            resolve();
-          } else {
-            ConsoleTestRunner.addConsoleSniffer(messageAdded, false);
-          }
-        }
-        ConsoleTestRunner.addConsoleSniffer(messageAdded, false);
-        TestRunner.evaluateInPage(Platform.StringUtilities.sprintf(`add${type}Messages(%d)`, count));
-      });
-    }
-
-    function dumpVisibleIndices() {
-      var {first, last, count} = ConsoleTestRunner.visibleIndices();
-      var activeTotal = viewport.firstActiveIndex === -1 ? 0 : (viewport.lastActiveIndex - viewport.firstActiveIndex + 1);
-      var calculatedFirst = viewport.firstVisibleIndex();
-      var calculatedLast = viewport.lastVisibleIndex();
-      var calculatedTotal = calculatedFirst === -1 ? 0 : (calculatedLast - calculatedFirst + 1);
-      if (calculatedFirst !== first || calculatedLast !== last) {
-        TestRunner.addResult('TEST ENDED IN ERROR: viewport is calculated incorrect visible indices!');
-        TestRunner.addResult(`Calculated visible range: ${calculatedFirst} to ${calculatedLast}, Total: ${calculatedTotal}
-Actual visible range: ${first} to ${last}, Total: ${count}`);
-        TestRunner.completeTest();
-      } else {
-        TestRunner.addResult(`Expected and actual visible ranges match`);
-      }
-    }
-
-    function forceItemAndDump(index, first) {
-      TestRunner.addResult(`Force item to be ${first ? 'first' : 'last'}: ${index}`);
-      if (first)
-        viewport.forceScrollItemToBeFirst(index);
-      else
-        viewport.forceScrollItemToBeLast(index);
-      dumpVisibleIndices();
-    }
-
-    TestRunner.runTestSuite([
-      async function testEmptyViewport(next) {
-        Console.ConsoleView.ConsoleView.clearConsole();
-        dumpVisibleIndices();
-        next();
-      },
-
-      async function testFirstLastVisibleIndices(next) {
-        Console.ConsoleView.ConsoleView.clearConsole();
-        await logMessages(100, 'Normal');
-
-        forceItemAndDump(0, true);
-        forceItemAndDump(1, true);
-
-        var lessThanOneRowHeight = consoleView.minimumRowHeight() - 1;
-        TestRunner.addResult(`Scroll a bit down: ${lessThanOneRowHeight}px`);
-        viewport.element.scrollTop += lessThanOneRowHeight;
-        viewport.refresh();
-        dumpVisibleIndices();
-
-        forceItemAndDump(50, false);
-        forceItemAndDump(99, false);
-        next();
-      },
-
-      async function testMultilineMessages(next) {
-        Console.ConsoleView.ConsoleView.clearConsole();
-        await logMessages(100, 'Multiline');
-
-        forceItemAndDump(0, true);
-        forceItemAndDump(1, true);
-
-        var lessThanOneRowHeight = consoleView.minimumRowHeight() - 1;
-        TestRunner.addResult(`Scroll a bit down: ${lessThanOneRowHeight}px`);
-        viewport.element.scrollTop += lessThanOneRowHeight;
-        viewport.refresh();
-        dumpVisibleIndices();
-
-        forceItemAndDump(50, false);
-        forceItemAndDump(99, false);
-        next();
-      },
-
-      async function testSlightlyBiggerMessages(next) {
-        Console.ConsoleView.ConsoleView.clearConsole();
-        await logMessages(100, 'SlightlyBigger');
-
-        forceItemAndDump(0, true);
-        forceItemAndDump(1, true);
-
-        var lessThanOneRowHeight = consoleView.minimumRowHeight() - 1;
-        TestRunner.addResult(`Scroll a bit down: ${lessThanOneRowHeight}px`);
-        viewport.element.scrollTop += lessThanOneRowHeight;
-        viewport.refresh();
-        dumpVisibleIndices();
-
-        forceItemAndDump(50, false);
-        forceItemAndDump(99, false);
-        next();
-      }
-    ]);
-  })();
diff --git a/third_party/blink/web_tests/http/tests/devtools/console/viewport-testing/console-stick-to-bottom-expand-object-expected.txt b/third_party/blink/web_tests/http/tests/devtools/console/viewport-testing/console-stick-to-bottom-expand-object-expected.txt
deleted file mode 100644
index c978ab2..0000000
--- a/third_party/blink/web_tests/http/tests/devtools/console/viewport-testing/console-stick-to-bottom-expand-object-expected.txt
+++ /dev/null
@@ -1,22 +0,0 @@
-Verifies viewport stick-to-bottom behavior when prompt has space below editable area.
-
-Message count: 150
-
-Running: testExpandLastVisibleObjectRemainsInView
-
-Force selecting index 149
-Is at bottom: true, should stick: true, selected element is fully visible? true
-Expanding object
-Is at bottom: false, should stick: false, selected element is fully visible? true
-Collapsing object
-Is at bottom: true, should stick: false, selected element is fully visible? true
-
-Running: testExpandFirstVisibleObjectRemainsInView
-
-Force selecting index 146
-Is at bottom: false, should stick: false, selected element is fully visible? true
-Expanding object
-Is at bottom: false, should stick: false, selected element is fully visible? true
-Collapsing object
-Is at bottom: false, should stick: false, selected element is fully visible? true
-
diff --git a/third_party/blink/web_tests/http/tests/devtools/console/viewport-testing/console-stick-to-bottom-expand-object.js b/third_party/blink/web_tests/http/tests/devtools/console/viewport-testing/console-stick-to-bottom-expand-object.js
deleted file mode 100644
index ec55b939..0000000
--- a/third_party/blink/web_tests/http/tests/devtools/console/viewport-testing/console-stick-to-bottom-expand-object.js
+++ /dev/null
@@ -1,84 +0,0 @@
-// Copyright 2018 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-import {TestRunner} from 'test_runner';
-import {ConsoleTestRunner} from 'console_test_runner';
-
-import * as Console from 'devtools/panels/console/console.js';
-
-(async function() {
-  TestRunner.addResult(`Verifies viewport stick-to-bottom behavior when prompt has space below editable area.\n`);
-  await TestRunner.showPanel('console');
-  await ConsoleTestRunner.waitUntilConsoleEditorLoaded();
-  ConsoleTestRunner.fixConsoleViewportDimensions(600, 200);
-  await TestRunner.evaluateInPagePromise(`
-      for (var i = 0; i < 150; ++i)
-        console.log({id: "#" + i, anotherKey: true, toGrowExpandedHeight: true});
-
-      //# sourceURL=console-viewport-stick-to-bottom-expand-object.js
-    `);
-  await ConsoleTestRunner.waitForConsoleMessagesPromise(150);
-  await ConsoleTestRunner.waitForPendingViewportUpdates();
-
-  const consoleView = Console.ConsoleView.ConsoleView.instance();
-  const viewport = consoleView.viewport;
-
-  TestRunner.runTestSuite([
-    async function testExpandLastVisibleObjectRemainsInView(next) {
-      const index = consoleView.visibleViewMessages.length - 1;
-      forceSelect(index);
-      dumpInfo();
-
-      TestRunner.addResult('Expanding object');
-      const objectSection = consoleView.visibleViewMessages[index].selectableChildren[0];
-      objectSection.objectTreeElement().expand();
-      await ConsoleTestRunner.waitForRemoteObjectsConsoleMessagesPromise();
-      dumpInfo();
-
-      TestRunner.addResult('Collapsing object');
-      objectSection.objectTreeElement().collapse();
-      dumpInfo();
-
-      next();
-    },
-
-    async function testExpandFirstVisibleObjectRemainsInView(next) {
-      const index = viewport.firstVisibleIndex() + 1;   // add 1 for first "fully visible" item
-      forceSelect(index);
-      dumpInfo();
-
-      TestRunner.addResult('Expanding object');
-      const objectSection = consoleView.visibleViewMessages[index].selectableChildren[0];
-      objectSection.objectTreeElement().expand();
-      dumpInfo();
-
-      TestRunner.addResult('Collapsing object');
-      objectSection.objectTreeElement().collapse();
-      dumpInfo();
-
-      next();
-    },
-  ]);
-
-  function dumpInfo() {
-    viewport.refresh();
-    let infoText =
-      'Is at bottom: ' + TestRunner.isScrolledToBottom(viewport.element) + ', should stick: ' + viewport.stickToBottom();
-    const selectedElement = viewport.renderedElementAt(viewport.virtualSelectedIndex);
-    if (selectedElement) {
-      const selectedRect = selectedElement.getBoundingClientRect();
-      const viewportRect = viewport.element.getBoundingClientRect();
-      const fullyVisible = (selectedRect.top + 2.5 >= viewportRect.top && selectedRect.bottom - 2.5 <= viewportRect.bottom);
-      infoText += ', selected element is fully visible? ' + fullyVisible;
-    }
-    TestRunner.addResult(infoText);
-  }
-
-  function forceSelect(index) {
-    TestRunner.addResult(`\nForce selecting index ${index}`);
-    viewport.virtualSelectedIndex = index;
-    viewport.contentElement().focus();
-    viewport.updateFocusedItem();
-  }
-})();
diff --git a/third_party/blink/web_tests/http/tests/devtools/security/interstitial-sidebar-expected.txt b/third_party/blink/web_tests/http/tests/devtools/security/interstitial-sidebar-expected.txt
deleted file mode 100644
index 122abc7..0000000
--- a/third_party/blink/web_tests/http/tests/devtools/security/interstitial-sidebar-expected.txt
+++ /dev/null
@@ -1,452 +0,0 @@
-error: http://127.0.0.1:8000/devtools/resources/inspected-page.html
-error: http://127.0.0.1:8000/devtools/resources/inspected-page.html
-Tests that the sidebar origin list disappears and appers when an interstitial is shown or hidden.
-
-Before interstitial is shown:
-<DIV >
-    <#document-fragment >
-        <STYLE >
-        </STYLE>
-        <STYLE >
-        </STYLE>
-        <STYLE >
-        </STYLE>
-        <STYLE >
-        </STYLE>
-        <STYLE >
-        </STYLE>
-        <STYLE >
-        </STYLE>
-        <STYLE >
-        </STYLE>
-        <STYLE >
-        </STYLE>
-        <DIV class=tree-outline-disclosure >
-            <OL class=tree-outline role=tree tabindex=-1 jslog=Tree >
-                <LI jslog=TreeItem; parent: parentTreeItem; track: click, keydown: ArrowUp|ArrowDown|ArrowLeft|ArrowRight|Backspace|Delete|Enter|Space|Home|End role=treeitem class=security-main-view-sidebar-tree-item selected force-white-icons title=Overview tabindex=0 aria-selected=true >
-                    <DIV class=selection fill >
-                    </DIV>
-                    <DIV class=leading-icons icons-container >
-                        <DEVTOOLS-ICON role=presentation name=indeterminate-question-box class=lock-icon lock-icon-unknown >
-                            <#document-fragment >
-                                <SPAN style=--icon-url: var(--image-file-indeterminate-question-box); >
-                                </SPAN>
-                                <STYLE >
-                                </STYLE>
-                            </#document-fragment>
-                        </DEVTOOLS-ICON>
-                    </DIV>
-                    <SPAN class=tree-element-title >
-                    </SPAN>
-                    <SPAN class=title >
-Overview
-                    </SPAN>
-                </LI>
-                <OL class=children role=group >
-                </OL>
-                <LI title=Main origin jslog=TreeItem; parent: parentTreeItem; track: click, keydown: ArrowUp|ArrowDown|ArrowLeft|ArrowRight|Backspace|Delete|Enter|Space|Home|End role=treeitem class=parent always-parent expanded security-sidebar-origins aria-expanded=true >
-                    <DIV class=selection fill >
-                    </DIV>
-                    <SPAN class=tree-element-title >
-Main origin
-                    </SPAN>
-                </LI>
-                <OL class=children expanded role=group aria-label=Main origin >
-                    <LI title=Reload to view details jslog=TreeItem; parent: parentTreeItem; track: click, keydown: ArrowUp|ArrowDown|ArrowLeft|ArrowRight|Backspace|Delete|Enter|Space|Home|End role=treeitem class=security-main-view-reload-message hidden >
-                        <DIV class=selection fill >
-                        </DIV>
-                        <SPAN class=tree-element-title >
-Reload to view details
-                        </SPAN>
-                    </LI>
-                    <OL class=children hidden role=group >
-                    </OL>
-                </OL>
-                <LI title=Non-secure origins jslog=TreeItem; parent: parentTreeItem; track: click, keydown: ArrowUp|ArrowDown|ArrowLeft|ArrowRight|Backspace|Delete|Enter|Space|Home|End role=treeitem class=parent always-parent expanded security-sidebar-origins hidden aria-expanded=true >
-                    <DIV class=selection fill >
-                    </DIV>
-                    <SPAN class=tree-element-title >
-Non-secure origins
-                    </SPAN>
-                </LI>
-                <OL class=children expanded hidden role=group aria-label=Non-secure origins >
-                </OL>
-                <LI title=Secure origins jslog=TreeItem; parent: parentTreeItem; track: click, keydown: ArrowUp|ArrowDown|ArrowLeft|ArrowRight|Backspace|Delete|Enter|Space|Home|End role=treeitem class=parent always-parent expanded security-sidebar-origins aria-expanded=true >
-                    <DIV class=selection fill >
-                    </DIV>
-                    <SPAN class=tree-element-title >
-Secure origins
-                    </SPAN>
-                </LI>
-                <OL class=children expanded role=group aria-label=Secure origins >
-                    <LI jslog=TreeItem; parent: parentTreeItem; track: click, keydown: ArrowUp|ArrowDown|ArrowLeft|ArrowRight|Backspace|Delete|Enter|Space|Home|End role=treeitem class=security-sidebar-tree-item title=https://foo.test >
-                        <DIV class=selection fill >
-                        </DIV>
-                        <DIV class=leading-icons icons-container >
-                            <DEVTOOLS-ICON role=presentation name=lock class=security-property security-property-secure >
-                                <#document-fragment >
-                                    <SPAN style=--icon-url: var(--image-file-lock); >
-                                    </SPAN>
-                                    <STYLE >
-                                    </STYLE>
-                                </#document-fragment>
-                            </DEVTOOLS-ICON>
-                        </DIV>
-                        <SPAN class=tree-element-title >
-                        </SPAN>
-                        <SPAN class=highlighted-url >
-                            <SPAN class=url-scheme-secure >
-https
-                            </SPAN>
-                            <SPAN class=url-scheme-separator >
-://
-                            </SPAN>
-                            <SPAN >
-foo.test
-                            </SPAN>
-                        </SPAN>
-                    </LI>
-                    <OL class=children role=group >
-                    </OL>
-                    <LI jslog=TreeItem; parent: parentTreeItem; track: click, keydown: ArrowUp|ArrowDown|ArrowLeft|ArrowRight|Backspace|Delete|Enter|Space|Home|End role=treeitem class=security-sidebar-tree-item title=https://bar.test >
-                        <DIV class=selection fill >
-                        </DIV>
-                        <DIV class=leading-icons icons-container >
-                            <DEVTOOLS-ICON role=presentation name=lock class=security-property security-property-secure >
-                                <#document-fragment >
-                                    <SPAN style=--icon-url: var(--image-file-lock); >
-                                    </SPAN>
-                                    <STYLE >
-                                    </STYLE>
-                                </#document-fragment>
-                            </DEVTOOLS-ICON>
-                        </DIV>
-                        <SPAN class=tree-element-title >
-                        </SPAN>
-                        <SPAN class=highlighted-url >
-                            <SPAN class=url-scheme-secure >
-https
-                            </SPAN>
-                            <SPAN class=url-scheme-separator >
-://
-                            </SPAN>
-                            <SPAN >
-bar.test
-                            </SPAN>
-                        </SPAN>
-                    </LI>
-                    <OL class=children role=group >
-                    </OL>
-                </OL>
-                <LI title=Unknown / canceled jslog=TreeItem; parent: parentTreeItem; track: click, keydown: ArrowUp|ArrowDown|ArrowLeft|ArrowRight|Backspace|Delete|Enter|Space|Home|End role=treeitem class=parent always-parent expanded security-sidebar-origins hidden aria-expanded=true >
-                    <DIV class=selection fill >
-                    </DIV>
-                    <SPAN class=tree-element-title >
-Unknown / canceled
-                    </SPAN>
-                </LI>
-                <OL class=children expanded hidden role=group aria-label=Unknown / canceled >
-                </OL>
-            </OL>
-        </DIV>
-    </#document-fragment>
-</DIV>
-After interstitial is shown:
-<DIV >
-    <#document-fragment >
-        <STYLE >
-        </STYLE>
-        <STYLE >
-        </STYLE>
-        <STYLE >
-        </STYLE>
-        <STYLE >
-        </STYLE>
-        <STYLE >
-        </STYLE>
-        <STYLE >
-        </STYLE>
-        <STYLE >
-        </STYLE>
-        <STYLE >
-        </STYLE>
-        <DIV class=tree-outline-disclosure >
-            <OL class=tree-outline role=tree tabindex=-1 jslog=Tree >
-                <LI jslog=TreeItem; parent: parentTreeItem; track: click, keydown: ArrowUp|ArrowDown|ArrowLeft|ArrowRight|Backspace|Delete|Enter|Space|Home|End role=treeitem class=security-main-view-sidebar-tree-item selected force-white-icons title=Overview tabindex=0 aria-selected=true >
-                    <DIV class=selection fill >
-                    </DIV>
-                    <DIV class=leading-icons icons-container >
-                        <DEVTOOLS-ICON role=presentation name=indeterminate-question-box class=lock-icon lock-icon-unknown >
-                            <#document-fragment >
-                                <SPAN style=--icon-url: var(--image-file-indeterminate-question-box); >
-                                </SPAN>
-                                <STYLE >
-                                </STYLE>
-                            </#document-fragment>
-                        </DEVTOOLS-ICON>
-                    </DIV>
-                    <SPAN class=tree-element-title >
-                    </SPAN>
-                    <SPAN class=title >
-Overview
-                    </SPAN>
-                </LI>
-                <OL class=children role=group >
-                </OL>
-                <LI title=Main origin jslog=TreeItem; parent: parentTreeItem; track: click, keydown: ArrowUp|ArrowDown|ArrowLeft|ArrowRight|Backspace|Delete|Enter|Space|Home|End role=treeitem class=parent always-parent expanded security-sidebar-origins hidden aria-expanded=true >
-                    <DIV class=selection fill >
-                    </DIV>
-                    <SPAN class=tree-element-title >
-Main origin
-                    </SPAN>
-                </LI>
-                <OL class=children expanded hidden role=group aria-label=Main origin >
-                    <LI title=Reload to view details jslog=TreeItem; parent: parentTreeItem; track: click, keydown: ArrowUp|ArrowDown|ArrowLeft|ArrowRight|Backspace|Delete|Enter|Space|Home|End role=treeitem class=security-main-view-reload-message hidden >
-                        <DIV class=selection fill >
-                        </DIV>
-                        <SPAN class=tree-element-title >
-Reload to view details
-                        </SPAN>
-                    </LI>
-                    <OL class=children hidden role=group >
-                    </OL>
-                </OL>
-                <LI title=Non-secure origins jslog=TreeItem; parent: parentTreeItem; track: click, keydown: ArrowUp|ArrowDown|ArrowLeft|ArrowRight|Backspace|Delete|Enter|Space|Home|End role=treeitem class=parent always-parent expanded security-sidebar-origins hidden aria-expanded=true >
-                    <DIV class=selection fill >
-                    </DIV>
-                    <SPAN class=tree-element-title >
-Non-secure origins
-                    </SPAN>
-                </LI>
-                <OL class=children expanded hidden role=group aria-label=Non-secure origins >
-                </OL>
-                <LI title=Secure origins jslog=TreeItem; parent: parentTreeItem; track: click, keydown: ArrowUp|ArrowDown|ArrowLeft|ArrowRight|Backspace|Delete|Enter|Space|Home|End role=treeitem class=parent always-parent expanded security-sidebar-origins hidden aria-expanded=true >
-                    <DIV class=selection fill >
-                    </DIV>
-                    <SPAN class=tree-element-title >
-Secure origins
-                    </SPAN>
-                </LI>
-                <OL class=children expanded hidden role=group aria-label=Secure origins >
-                    <LI jslog=TreeItem; parent: parentTreeItem; track: click, keydown: ArrowUp|ArrowDown|ArrowLeft|ArrowRight|Backspace|Delete|Enter|Space|Home|End role=treeitem class=security-sidebar-tree-item title=https://foo.test >
-                        <DIV class=selection fill >
-                        </DIV>
-                        <DIV class=leading-icons icons-container >
-                            <DEVTOOLS-ICON role=presentation name=lock class=security-property security-property-secure >
-                                <#document-fragment >
-                                    <SPAN style=--icon-url: var(--image-file-lock); >
-                                    </SPAN>
-                                    <STYLE >
-                                    </STYLE>
-                                </#document-fragment>
-                            </DEVTOOLS-ICON>
-                        </DIV>
-                        <SPAN class=tree-element-title >
-                        </SPAN>
-                        <SPAN class=highlighted-url >
-                            <SPAN class=url-scheme-secure >
-https
-                            </SPAN>
-                            <SPAN class=url-scheme-separator >
-://
-                            </SPAN>
-                            <SPAN >
-foo.test
-                            </SPAN>
-                        </SPAN>
-                    </LI>
-                    <OL class=children role=group >
-                    </OL>
-                    <LI jslog=TreeItem; parent: parentTreeItem; track: click, keydown: ArrowUp|ArrowDown|ArrowLeft|ArrowRight|Backspace|Delete|Enter|Space|Home|End role=treeitem class=security-sidebar-tree-item title=https://bar.test >
-                        <DIV class=selection fill >
-                        </DIV>
-                        <DIV class=leading-icons icons-container >
-                            <DEVTOOLS-ICON role=presentation name=lock class=security-property security-property-secure >
-                                <#document-fragment >
-                                    <SPAN style=--icon-url: var(--image-file-lock); >
-                                    </SPAN>
-                                    <STYLE >
-                                    </STYLE>
-                                </#document-fragment>
-                            </DEVTOOLS-ICON>
-                        </DIV>
-                        <SPAN class=tree-element-title >
-                        </SPAN>
-                        <SPAN class=highlighted-url >
-                            <SPAN class=url-scheme-secure >
-https
-                            </SPAN>
-                            <SPAN class=url-scheme-separator >
-://
-                            </SPAN>
-                            <SPAN >
-bar.test
-                            </SPAN>
-                        </SPAN>
-                    </LI>
-                    <OL class=children role=group >
-                    </OL>
-                </OL>
-                <LI title=Unknown / canceled jslog=TreeItem; parent: parentTreeItem; track: click, keydown: ArrowUp|ArrowDown|ArrowLeft|ArrowRight|Backspace|Delete|Enter|Space|Home|End role=treeitem class=parent always-parent expanded security-sidebar-origins hidden aria-expanded=true >
-                    <DIV class=selection fill >
-                    </DIV>
-                    <SPAN class=tree-element-title >
-Unknown / canceled
-                    </SPAN>
-                </LI>
-                <OL class=children expanded hidden role=group aria-label=Unknown / canceled >
-                </OL>
-            </OL>
-        </DIV>
-    </#document-fragment>
-</DIV>
-After interstitial is hidden:
-<DIV >
-    <#document-fragment >
-        <STYLE >
-        </STYLE>
-        <STYLE >
-        </STYLE>
-        <STYLE >
-        </STYLE>
-        <STYLE >
-        </STYLE>
-        <STYLE >
-        </STYLE>
-        <STYLE >
-        </STYLE>
-        <STYLE >
-        </STYLE>
-        <STYLE >
-        </STYLE>
-        <DIV class=tree-outline-disclosure >
-            <OL class=tree-outline role=tree tabindex=-1 jslog=Tree >
-                <LI jslog=TreeItem; parent: parentTreeItem; track: click, keydown: ArrowUp|ArrowDown|ArrowLeft|ArrowRight|Backspace|Delete|Enter|Space|Home|End role=treeitem class=security-main-view-sidebar-tree-item selected force-white-icons title=Overview tabindex=0 aria-selected=true >
-                    <DIV class=selection fill >
-                    </DIV>
-                    <DIV class=leading-icons icons-container >
-                        <DEVTOOLS-ICON role=presentation name=indeterminate-question-box class=lock-icon lock-icon-unknown >
-                            <#document-fragment >
-                                <SPAN style=--icon-url: var(--image-file-indeterminate-question-box); >
-                                </SPAN>
-                                <STYLE >
-                                </STYLE>
-                            </#document-fragment>
-                        </DEVTOOLS-ICON>
-                    </DIV>
-                    <SPAN class=tree-element-title >
-                    </SPAN>
-                    <SPAN class=title >
-Overview
-                    </SPAN>
-                </LI>
-                <OL class=children role=group >
-                </OL>
-                <LI title=Main origin jslog=TreeItem; parent: parentTreeItem; track: click, keydown: ArrowUp|ArrowDown|ArrowLeft|ArrowRight|Backspace|Delete|Enter|Space|Home|End role=treeitem class=parent always-parent expanded security-sidebar-origins aria-expanded=true >
-                    <DIV class=selection fill >
-                    </DIV>
-                    <SPAN class=tree-element-title >
-Main origin
-                    </SPAN>
-                </LI>
-                <OL class=children expanded role=group aria-label=Main origin >
-                    <LI title=Reload to view details jslog=TreeItem; parent: parentTreeItem; track: click, keydown: ArrowUp|ArrowDown|ArrowLeft|ArrowRight|Backspace|Delete|Enter|Space|Home|End role=treeitem class=security-main-view-reload-message hidden >
-                        <DIV class=selection fill >
-                        </DIV>
-                        <SPAN class=tree-element-title >
-Reload to view details
-                        </SPAN>
-                    </LI>
-                    <OL class=children hidden role=group >
-                    </OL>
-                </OL>
-                <LI title=Non-secure origins jslog=TreeItem; parent: parentTreeItem; track: click, keydown: ArrowUp|ArrowDown|ArrowLeft|ArrowRight|Backspace|Delete|Enter|Space|Home|End role=treeitem class=parent always-parent expanded security-sidebar-origins aria-expanded=true >
-                    <DIV class=selection fill >
-                    </DIV>
-                    <SPAN class=tree-element-title >
-Non-secure origins
-                    </SPAN>
-                </LI>
-                <OL class=children expanded role=group aria-label=Non-secure origins >
-                </OL>
-                <LI title=Secure origins jslog=TreeItem; parent: parentTreeItem; track: click, keydown: ArrowUp|ArrowDown|ArrowLeft|ArrowRight|Backspace|Delete|Enter|Space|Home|End role=treeitem class=parent always-parent expanded security-sidebar-origins aria-expanded=true >
-                    <DIV class=selection fill >
-                    </DIV>
-                    <SPAN class=tree-element-title >
-Secure origins
-                    </SPAN>
-                </LI>
-                <OL class=children expanded role=group aria-label=Secure origins >
-                    <LI jslog=TreeItem; parent: parentTreeItem; track: click, keydown: ArrowUp|ArrowDown|ArrowLeft|ArrowRight|Backspace|Delete|Enter|Space|Home|End role=treeitem class=security-sidebar-tree-item title=https://foo.test >
-                        <DIV class=selection fill >
-                        </DIV>
-                        <DIV class=leading-icons icons-container >
-                            <DEVTOOLS-ICON role=presentation name=lock class=security-property security-property-secure >
-                                <#document-fragment >
-                                    <SPAN style=--icon-url: var(--image-file-lock); >
-                                    </SPAN>
-                                    <STYLE >
-                                    </STYLE>
-                                </#document-fragment>
-                            </DEVTOOLS-ICON>
-                        </DIV>
-                        <SPAN class=tree-element-title >
-                        </SPAN>
-                        <SPAN class=highlighted-url >
-                            <SPAN class=url-scheme-secure >
-https
-                            </SPAN>
-                            <SPAN class=url-scheme-separator >
-://
-                            </SPAN>
-                            <SPAN >
-foo.test
-                            </SPAN>
-                        </SPAN>
-                    </LI>
-                    <OL class=children role=group >
-                    </OL>
-                    <LI jslog=TreeItem; parent: parentTreeItem; track: click, keydown: ArrowUp|ArrowDown|ArrowLeft|ArrowRight|Backspace|Delete|Enter|Space|Home|End role=treeitem class=security-sidebar-tree-item title=https://bar.test >
-                        <DIV class=selection fill >
-                        </DIV>
-                        <DIV class=leading-icons icons-container >
-                            <DEVTOOLS-ICON role=presentation name=lock class=security-property security-property-secure >
-                                <#document-fragment >
-                                    <SPAN style=--icon-url: var(--image-file-lock); >
-                                    </SPAN>
-                                    <STYLE >
-                                    </STYLE>
-                                </#document-fragment>
-                            </DEVTOOLS-ICON>
-                        </DIV>
-                        <SPAN class=tree-element-title >
-                        </SPAN>
-                        <SPAN class=highlighted-url >
-                            <SPAN class=url-scheme-secure >
-https
-                            </SPAN>
-                            <SPAN class=url-scheme-separator >
-://
-                            </SPAN>
-                            <SPAN >
-bar.test
-                            </SPAN>
-                        </SPAN>
-                    </LI>
-                    <OL class=children role=group >
-                    </OL>
-                </OL>
-                <LI title=Unknown / canceled jslog=TreeItem; parent: parentTreeItem; track: click, keydown: ArrowUp|ArrowDown|ArrowLeft|ArrowRight|Backspace|Delete|Enter|Space|Home|End role=treeitem class=parent always-parent expanded security-sidebar-origins aria-expanded=true >
-                    <DIV class=selection fill >
-                    </DIV>
-                    <SPAN class=tree-element-title >
-Unknown / canceled
-                    </SPAN>
-                </LI>
-                <OL class=children expanded role=group aria-label=Unknown / canceled >
-                </OL>
-            </OL>
-        </DIV>
-    </#document-fragment>
-</DIV>
-
diff --git a/third_party/blink/web_tests/http/tests/devtools/security/interstitial-sidebar.js b/third_party/blink/web_tests/http/tests/devtools/security/interstitial-sidebar.js
deleted file mode 100644
index c75e8e7f..0000000
--- a/third_party/blink/web_tests/http/tests/devtools/security/interstitial-sidebar.js
+++ /dev/null
@@ -1,47 +0,0 @@
-// Copyright 2017 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-import {TestRunner} from 'test_runner';
-import {SecurityTestRunner} from 'security_test_runner';
-
-import * as SDK from 'devtools/core/sdk/sdk.js';
-import * as Security from 'devtools/panels/security/security.js';
-
-(async function() {
-  TestRunner.addResult(
-      `Tests that the sidebar origin list disappears and appers when an interstitial is shown or hidden.\n`);
-  await TestRunner.showPanel('security');
-
-  var request1 = SDK.NetworkRequest.NetworkRequest.create(
-      0, 'https://foo.test/', 'https://foo.test', 0, 0, null);
-  request1.setSecurityState(Protocol.Security.SecurityState.Secure);
-  SecurityTestRunner.dispatchRequestFinished(request1);
-
-  var request2 = SDK.NetworkRequest.NetworkRequest.create(
-      0, 'https://bar.test/foo.jpg', 'https://bar.test', 0, 0, null);
-  request2.setSecurityState(Protocol.Security.SecurityState.Secure);
-  SecurityTestRunner.dispatchRequestFinished(request2);
-
-  TestRunner.addResult('Before interstitial is shown:');
-  TestRunner.dumpDeepInnerHTML(Security.SecurityPanel.SecurityPanel.instance().sidebarTree.element);
-
-  // Test that the sidebar is hidden when an interstitial is shown. https://crbug.com/559150
-  TestRunner.mainTarget.model(SDK.ResourceTreeModel.ResourceTreeModel)
-      .dispatchEventToListeners(SDK.ResourceTreeModel.Events.InterstitialShown);
-  // Simulate a request finishing after the interstitial is shown, to make sure that doesn't show up in the sidebar.
-  var request3 = SDK.NetworkRequest.NetworkRequest.create(
-      0, 'https://bar.test/foo.jpg', 'https://bar.test', 0, 0, null);
-  request3.setSecurityState(Protocol.Security.SecurityState.Unknown);
-  SecurityTestRunner.dispatchRequestFinished(request3);
-  TestRunner.addResult('After interstitial is shown:');
-  TestRunner.dumpDeepInnerHTML(Security.SecurityPanel.SecurityPanel.instance().sidebarTree.element);
-
-  // Test that the sidebar is shown again when the interstitial is hidden. https://crbug.com/559150
-  TestRunner.mainTarget.model(SDK.ResourceTreeModel.ResourceTreeModel)
-      .dispatchEventToListeners(SDK.ResourceTreeModel.Events.InterstitialHidden);
-  TestRunner.addResult('After interstitial is hidden:');
-  TestRunner.dumpDeepInnerHTML(Security.SecurityPanel.SecurityPanel.instance().sidebarTree.element);
-
-  TestRunner.completeTest();
-})();
diff --git a/third_party/blink/web_tests/http/tests/devtools/security/mixed-content-sidebar-expected.txt b/third_party/blink/web_tests/http/tests/devtools/security/mixed-content-sidebar-expected.txt
deleted file mode 100644
index 29807ff..0000000
--- a/third_party/blink/web_tests/http/tests/devtools/security/mixed-content-sidebar-expected.txt
+++ /dev/null
@@ -1,154 +0,0 @@
-error: http://127.0.0.1:8000/devtools/resources/inspected-page.html
-error: http://127.0.0.1:8000/devtools/resources/inspected-page.html
-Tests that the sidebar uses the correct styling for mixed content subresources.
-
-Origin sidebar:
-<DIV >
-    <#document-fragment >
-        <STYLE >
-        </STYLE>
-        <STYLE >
-        </STYLE>
-        <STYLE >
-        </STYLE>
-        <STYLE >
-        </STYLE>
-        <STYLE >
-        </STYLE>
-        <STYLE >
-        </STYLE>
-        <STYLE >
-        </STYLE>
-        <STYLE >
-        </STYLE>
-        <DIV class=tree-outline-disclosure >
-            <OL class=tree-outline role=tree tabindex=-1 jslog=Tree >
-                <LI jslog=TreeItem; parent: parentTreeItem; track: click, keydown: ArrowUp|ArrowDown|ArrowLeft|ArrowRight|Backspace|Delete|Enter|Space|Home|End role=treeitem class=security-main-view-sidebar-tree-item selected force-white-icons title=Overview tabindex=0 aria-selected=true >
-                    <DIV class=selection fill >
-                    </DIV>
-                    <DIV class=leading-icons icons-container >
-                        <DEVTOOLS-ICON role=presentation name=indeterminate-question-box class=lock-icon lock-icon-neutral >
-                            <#document-fragment >
-                                <SPAN style=--icon-url: var(--image-file-indeterminate-question-box); >
-                                </SPAN>
-                                <STYLE >
-                                </STYLE>
-                            </#document-fragment>
-                        </DEVTOOLS-ICON>
-                    </DIV>
-                    <SPAN class=tree-element-title >
-                    </SPAN>
-                    <SPAN class=title >
-Overview
-                    </SPAN>
-                </LI>
-                <OL class=children role=group >
-                </OL>
-                <LI title=Main origin jslog=TreeItem; parent: parentTreeItem; track: click, keydown: ArrowUp|ArrowDown|ArrowLeft|ArrowRight|Backspace|Delete|Enter|Space|Home|End role=treeitem class=parent always-parent expanded security-sidebar-origins aria-expanded=true >
-                    <DIV class=selection fill >
-                    </DIV>
-                    <SPAN class=tree-element-title >
-Main origin
-                    </SPAN>
-                </LI>
-                <OL class=children expanded role=group aria-label=Main origin >
-                    <LI title=Reload to view details jslog=TreeItem; parent: parentTreeItem; track: click, keydown: ArrowUp|ArrowDown|ArrowLeft|ArrowRight|Backspace|Delete|Enter|Space|Home|End role=treeitem class=security-main-view-reload-message hidden >
-                        <DIV class=selection fill >
-                        </DIV>
-                        <SPAN class=tree-element-title >
-Reload to view details
-                        </SPAN>
-                    </LI>
-                    <OL class=children hidden role=group >
-                    </OL>
-                </OL>
-                <LI title=Non-secure origins jslog=TreeItem; parent: parentTreeItem; track: click, keydown: ArrowUp|ArrowDown|ArrowLeft|ArrowRight|Backspace|Delete|Enter|Space|Home|End role=treeitem class=parent always-parent expanded security-sidebar-origins aria-expanded=true >
-                    <DIV class=selection fill >
-                    </DIV>
-                    <SPAN class=tree-element-title >
-Non-secure origins
-                    </SPAN>
-                </LI>
-                <OL class=children expanded role=group aria-label=Non-secure origins >
-                    <LI jslog=TreeItem; parent: parentTreeItem; track: click, keydown: ArrowUp|ArrowDown|ArrowLeft|ArrowRight|Backspace|Delete|Enter|Space|Home|End role=treeitem class=security-sidebar-tree-item title=http://foo.test >
-                        <DIV class=selection fill >
-                        </DIV>
-                        <DIV class=leading-icons icons-container >
-                            <DEVTOOLS-ICON role=presentation name=warning class=security-property security-property-insecure >
-                                <#document-fragment >
-                                    <SPAN style=--icon-url: var(--image-file-warning); >
-                                    </SPAN>
-                                    <STYLE >
-                                    </STYLE>
-                                </#document-fragment>
-                            </DEVTOOLS-ICON>
-                        </DIV>
-                        <SPAN class=tree-element-title >
-                        </SPAN>
-                        <SPAN class=highlighted-url >
-                            <SPAN class=url-scheme-insecure >
-http
-                            </SPAN>
-                            <SPAN class=url-scheme-separator >
-://
-                            </SPAN>
-                            <SPAN >
-foo.test
-                            </SPAN>
-                        </SPAN>
-                    </LI>
-                    <OL class=children role=group >
-                    </OL>
-                    <LI jslog=TreeItem; parent: parentTreeItem; track: click, keydown: ArrowUp|ArrowDown|ArrowLeft|ArrowRight|Backspace|Delete|Enter|Space|Home|End role=treeitem class=security-sidebar-tree-item title=http://bar.test >
-                        <DIV class=selection fill >
-                        </DIV>
-                        <DIV class=leading-icons icons-container >
-                            <DEVTOOLS-ICON role=presentation name=warning class=security-property security-property-insecure >
-                                <#document-fragment >
-                                    <SPAN style=--icon-url: var(--image-file-warning); >
-                                    </SPAN>
-                                    <STYLE >
-                                    </STYLE>
-                                </#document-fragment>
-                            </DEVTOOLS-ICON>
-                        </DIV>
-                        <SPAN class=tree-element-title >
-                        </SPAN>
-                        <SPAN class=highlighted-url >
-                            <SPAN class=url-scheme-insecure >
-http
-                            </SPAN>
-                            <SPAN class=url-scheme-separator >
-://
-                            </SPAN>
-                            <SPAN >
-bar.test
-                            </SPAN>
-                        </SPAN>
-                    </LI>
-                    <OL class=children role=group >
-                    </OL>
-                </OL>
-                <LI title=Secure origins jslog=TreeItem; parent: parentTreeItem; track: click, keydown: ArrowUp|ArrowDown|ArrowLeft|ArrowRight|Backspace|Delete|Enter|Space|Home|End role=treeitem class=parent always-parent expanded security-sidebar-origins hidden aria-expanded=true >
-                    <DIV class=selection fill >
-                    </DIV>
-                    <SPAN class=tree-element-title >
-Secure origins
-                    </SPAN>
-                </LI>
-                <OL class=children expanded hidden role=group aria-label=Secure origins >
-                </OL>
-                <LI title=Unknown / canceled jslog=TreeItem; parent: parentTreeItem; track: click, keydown: ArrowUp|ArrowDown|ArrowLeft|ArrowRight|Backspace|Delete|Enter|Space|Home|End role=treeitem class=parent always-parent expanded security-sidebar-origins hidden aria-expanded=true >
-                    <DIV class=selection fill >
-                    </DIV>
-                    <SPAN class=tree-element-title >
-Unknown / canceled
-                    </SPAN>
-                </LI>
-                <OL class=children expanded hidden role=group aria-label=Unknown / canceled >
-                </OL>
-            </OL>
-        </DIV>
-    </#document-fragment>
-</DIV>
-
diff --git a/third_party/blink/web_tests/http/tests/devtools/security/mixed-content-sidebar.js b/third_party/blink/web_tests/http/tests/devtools/security/mixed-content-sidebar.js
deleted file mode 100644
index fd9f457..0000000
--- a/third_party/blink/web_tests/http/tests/devtools/security/mixed-content-sidebar.js
+++ /dev/null
@@ -1,38 +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.
-
-import {TestRunner} from 'test_runner';
-import {SecurityTestRunner} from 'security_test_runner';
-
-import * as SDK from 'devtools/core/sdk/sdk.js';
-import * as Security from 'devtools/panels/security/security.js';
-
-(async function() {
-  TestRunner.addResult(
-      `Tests that the sidebar uses the correct styling for mixed content subresources.\n`);
-  await TestRunner.showPanel('security');
-
-  const pageVisibleSecurityState = new Security.SecurityModel.PageVisibleSecurityState(
-    Protocol.Security.SecurityState.Neutral, null, null,
-    ['displayed-mixed-content', 'ran-mixed-content']);
-  TestRunner.mainTarget.model(Security.SecurityModel.SecurityModel)
-      .dispatchEventToListeners(
-        Security.SecurityModel.Events.VisibleSecurityStateChanged,
-        pageVisibleSecurityState);
-
-  var passive = SDK.NetworkRequest.NetworkRequest.create(
-      0, 'http://foo.test', 'https://foo.test', 0, 0, null);
-  passive.mixedContentType = 'optionally-blockable';
-  SecurityTestRunner.dispatchRequestFinished(passive);
-
-  var active = SDK.NetworkRequest.NetworkRequest.create(
-      0, 'http://bar.test', 'https://bar.test', 0, 0, null);
-  active.mixedContentType = 'blockable';
-  SecurityTestRunner.dispatchRequestFinished(active);
-
-  TestRunner.addResult('Origin sidebar:');
-  TestRunner.dumpDeepInnerHTML(Security.SecurityPanel.SecurityPanel.instance().sidebarTree.element);
-
-  TestRunner.completeTest();
-})();
diff --git a/third_party/blink/web_tests/http/tests/devtools/sources/debugger-breakpoints/resources/blocking-main-thread.js b/third_party/blink/web_tests/http/tests/devtools/sources/debugger-breakpoints/resources/blocking-main-thread-worker.php
similarity index 72%
rename from third_party/blink/web_tests/http/tests/devtools/sources/debugger-breakpoints/resources/blocking-main-thread.js
rename to third_party/blink/web_tests/http/tests/devtools/sources/debugger-breakpoints/resources/blocking-main-thread-worker.php
index a89111d..0552945 100644
--- a/third_party/blink/web_tests/http/tests/devtools/sources/debugger-breakpoints/resources/blocking-main-thread.js
+++ b/third_party/blink/web_tests/http/tests/devtools/sources/debugger-breakpoints/resources/blocking-main-thread-worker.php
@@ -1,7 +1,7 @@
-// Copyright 2020 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
+<?php
+header('Cross-Origin-Embedder-Policy: require-corp');
+header('Content-type: text/javascript');
+?>
 self.addEventListener('message', (m) => {
   const sharedArray = new Int32Array(m.data);
   var i = 0;
diff --git a/third_party/blink/web_tests/http/tests/devtools/sources/debugger-breakpoints/resources/blocking-main-thread.html b/third_party/blink/web_tests/http/tests/devtools/sources/debugger-breakpoints/resources/blocking-main-thread.php
similarity index 64%
rename from third_party/blink/web_tests/http/tests/devtools/sources/debugger-breakpoints/resources/blocking-main-thread.html
rename to third_party/blink/web_tests/http/tests/devtools/sources/debugger-breakpoints/resources/blocking-main-thread.php
index d4d49cd..325b9391 100644
--- a/third_party/blink/web_tests/http/tests/devtools/sources/debugger-breakpoints/resources/blocking-main-thread.html
+++ b/third_party/blink/web_tests/http/tests/devtools/sources/debugger-breakpoints/resources/blocking-main-thread.php
@@ -1,13 +1,13 @@
-<!--
-  Copyright 2020 The Chromium Authors
-  Use of this source code is governed by a BSD-style license that can be
-  found in the LICENSE file.
--->
+<?php
+header('Cross-Origin-Opener-Policy: same-origin');
+header('Cross-Origin-Embedder-Policy: require-corp');
+header('Content-Type: text/html');
+?>
 <html>
 <script>
   const sab = new SharedArrayBuffer(1024);
-  const sharedArray = new Int32Array(sab);
-  const worker = new Worker('blocking-main-thread.js');
+  const sharedArray = new Int32Array(sab.buffer);
+  const worker = new Worker('blocking-main-thread-worker.php');
   worker.postMessage(sab);
 
   function checkWorkerReady() {
diff --git a/third_party/blink/web_tests/http/tests/devtools/sources/debugger-breakpoints/set-breakpoint-while-blocking-main-thread-expected.txt b/third_party/blink/web_tests/http/tests/devtools/sources/debugger-breakpoints/set-breakpoint-while-blocking-main-thread-expected.txt
index a5d11b34..6597696 100644
--- a/third_party/blink/web_tests/http/tests/devtools/sources/debugger-breakpoints/set-breakpoint-while-blocking-main-thread-expected.txt
+++ b/third_party/blink/web_tests/http/tests/devtools/sources/debugger-breakpoints/set-breakpoint-while-blocking-main-thread-expected.txt
@@ -2,13 +2,10 @@
 
 
 Running: testSetBreakpoint
-Breakpoint sidebar pane 
-blocking-main-thread.js:13checked  Atomics.store(sharedArray, i, 1);
-Reloading page.
+blocking-main-thread-worker.php:line 9:   Atomics.store(sharedArray, i, 1);
 Page reloaded.
 Script execution paused.
-Breakpoint sidebar pane 
-blocking-main-thread.html:11checked  worker.postMessage(sab);
-blocking-main-thread.js:13checked breakpoint hit  Atomics.store(sharedArray, i, 1);
+blocking-main-thread-worker.php:line 9:   Atomics.store(sharedArray, i, 1);
+blocking-main-thread.php:line 6:   worker.postMessage(sab);
 Script execution resumed.
 
diff --git a/third_party/blink/web_tests/http/tests/devtools/sources/debugger-breakpoints/set-breakpoint-while-blocking-main-thread.js b/third_party/blink/web_tests/http/tests/devtools/sources/debugger-breakpoints/set-breakpoint-while-blocking-main-thread.js
index 6ae325b..f8ddd88 100644
--- a/third_party/blink/web_tests/http/tests/devtools/sources/debugger-breakpoints/set-breakpoint-while-blocking-main-thread.js
+++ b/third_party/blink/web_tests/http/tests/devtools/sources/debugger-breakpoints/set-breakpoint-while-blocking-main-thread.js
@@ -5,41 +5,44 @@
 import {TestRunner} from 'test_runner';
 import {SourcesTestRunner} from 'sources_test_runner';
 
-import * as Root from 'devtools/core/root/root.js';
+import * as SourcesComponents from 'devtools/panels/sources/components/components.js';
+import * as RenderCoordinator from 'devtools/ui/components/render_coordinator/render_coordinator.js';
+
+async function dumpBreakpointSidebarPane() {
+  var pane = SourcesComponents.BreakpointsView.BreakpointsView.instance();
+  await SourcesComponents.BreakpointsView.BreakpointsSidebarController.instance().update();
+  await RenderCoordinator.done();
+  const groupHeader = pane.shadowRoot?.querySelectorAll('[role="group"]');
+  for (let i = 0; i < groupHeader?.length; ++i) {
+    const title = groupHeader[i].querySelector('.group-header-title')?.textContent;
+    const line = groupHeader[i].querySelector('.breakpoint-item .location')?.textContent;
+    const code = groupHeader[i].querySelector('.breakpoint-item .code-snippet')?.textContent;
+    TestRunner.addResult(`${title}:line ${line}: ${code}`);
+  }
+}
 
 (async function() {
-  // This test is testing the old breakpoint sidebar pane. Make sure to
-  // turn off the new breakpoint pane experiment.
-  Root.Runtime.experiments.setEnabled('breakpointView', false);
   TestRunner.addResult(`Tests setting breakpoint when main thread blocks.\n`);
   await TestRunner.showPanel('sources');
-  await TestRunner.navigatePromise('resources/blocking-main-thread.html');
+  TestRunner.navigate('resources/blocking-main-thread.php');
 
   SourcesTestRunner.runDebuggerTestSuite([
     async function testSetBreakpoint(next) {
-
-      // The debugger plugin needs to be retrieved before pausing, otherwise we
-      // cannot set a breakpoint on the main thread during pause.
-      var mainThreadSource = await SourcesTestRunner.showScriptSourcePromise(
-          'blocking-main-thread.html');
-      const plugin = SourcesTestRunner.debuggerPlugin(mainThreadSource);
-
       SourcesTestRunner.showScriptSource(
-          'blocking-main-thread.js', didShowWorkerSource);
+            'blocking-main-thread-worker.php', didShowWorkerSource);
 
       async function didShowWorkerSource(sourceFrame) {
-        await SourcesTestRunner.createNewBreakpoint(sourceFrame, 12, '', true);
-        await SourcesTestRunner.waitBreakpointSidebarPane();
-        SourcesTestRunner.dumpBreakpointSidebarPane();
+        await SourcesTestRunner.setBreakpoint(sourceFrame, 8, '', true);
+        await dumpBreakpointSidebarPane();
         SourcesTestRunner.waitUntilPaused(paused);
-        TestRunner.addResult('Reloading page.');
         TestRunner.reloadPage();
       }
 
       async function paused() {
-        plugin.createNewBreakpoint(10, '', true);
-        await SourcesTestRunner.waitBreakpointSidebarPane();
-        SourcesTestRunner.dumpBreakpointSidebarPane();
+        var mainThreadSource = await SourcesTestRunner.showScriptSourcePromise(
+          'blocking-main-thread.php');
+        await SourcesTestRunner.setBreakpoint(mainThreadSource, 5, '', true);
+        await dumpBreakpointSidebarPane();
         next();
       }
     }
diff --git a/third_party/blink/web_tests/http/tests/devtools/sources/debugger-ui/debugger-save-to-temp-var.js b/third_party/blink/web_tests/http/tests/devtools/sources/debugger-ui/debugger-save-to-temp-var.js
index 41bda31..6fa378d4 100644
--- a/third_party/blink/web_tests/http/tests/devtools/sources/debugger-ui/debugger-save-to-temp-var.js
+++ b/third_party/blink/web_tests/http/tests/devtools/sources/debugger-ui/debugger-save-to-temp-var.js
@@ -53,11 +53,11 @@
   SourcesTestRunner.setQuiet(true);
   SourcesTestRunner.startDebuggerTest(step1);
 
-  TestRunner.addResult('Number of expressions: ' + expressions.length);
-  TestRunner.addResult('Names [temp3..temp7] are reserved\n');
-
   function step1() {
     SourcesTestRunner.runTestFunctionAndWaitUntilPaused(didPause);
+
+    TestRunner.addResult('Number of expressions: ' + expressions.length);
+    TestRunner.addResult('Names [temp3..temp7] are reserved\n');
   }
 
   function didPause() {
@@ -73,11 +73,16 @@
 
     function didEvaluate(result) {
       TestRunner.assertTrue(!result.exceptionDetails, 'FAIL: was thrown. Expression: ' + expression);
-      SDK.consoleModel.saveToTempVariable(UIModule.Context.Context.instance().flavor(SDK.RuntimeModel.RuntimeModel.ExecutionContext), result.object);
+      const executionContext = UIModule.Context.Context.instance().flavor(
+          SDK.RuntimeModel.ExecutionContext);
+      const consoleModel =
+          executionContext.target().model(SDK.ConsoleModel.ConsoleModel);
+      consoleModel.saveToTempVariable(executionContext, result.object);
       ConsoleTestRunner.waitUntilNthMessageReceived(2, evaluateNext);
     }
 
-    UIModule.Context.Context.instance().flavor(SDK.RuntimeModel.RuntimeModel.ExecutionContext)
+    UIModule.Context.Context.instance()
+        .flavor(SDK.RuntimeModel.ExecutionContext)
         .evaluate({expression: expression, objectGroup: 'console'})
         .then(didEvaluate);
   }
diff --git a/third_party/blink/web_tests/paint/invalidation/multicol/multicol-as-paint-container-expected.txt b/third_party/blink/web_tests/paint/invalidation/multicol/multicol-as-paint-container-expected.txt
index ccecb591..d3620f3 100644
--- a/third_party/blink/web_tests/paint/invalidation/multicol/multicol-as-paint-container-expected.txt
+++ b/third_party/blink/web_tests/paint/invalidation/multicol/multicol-as-paint-container-expected.txt
@@ -7,7 +7,7 @@
       "backgroundColor": "#FFFFFF"
     },
     {
-      "name": "LayoutBlockFlow DIV id='target'",
+      "name": "LayoutBlockFlow (multicol) DIV id='target'",
       "bounds": [630, 180],
       "backfaceVisibility": "hidden",
       "invalidations": [
diff --git a/third_party/blink/web_tests/platform/mac-mac15-arm64/virtual/webnn-service-on-cpu/external/wpt/webnn/conformance_tests/not_equal.https.any_cpu-expected.txt b/third_party/blink/web_tests/platform/mac-mac15-arm64/virtual/webnn-service-on-cpu/external/wpt/webnn/conformance_tests/not_equal.https.any_cpu-expected.txt
index 3415057..d2490db 100644
--- a/third_party/blink/web_tests/platform/mac-mac15-arm64/virtual/webnn-service-on-cpu/external/wpt/webnn/conformance_tests/not_equal.https.any_cpu-expected.txt
+++ b/third_party/blink/web_tests/platform/mac-mac15-arm64/virtual/webnn-service-on-cpu/external/wpt/webnn/conformance_tests/not_equal.https.any_cpu-expected.txt
@@ -1,27 +1,3 @@
 This is a testharness.js-based test.
-[FAIL] notEqual float32 0D scalar
-  promise_test: Unhandled rejection with value: object "TypeError: Unsupported data type, output 'output' data type uint8 must be one of [float32,float16,int32]."
-[FAIL] notEqual float32 1D constant tensors
-  promise_test: Unhandled rejection with value: object "TypeError: Unsupported data type, output 'output' data type uint8 must be one of [float32,float16,int32]."
-[FAIL] notEqual float32 1D tensors
-  promise_test: Unhandled rejection with value: object "TypeError: Unsupported data type, output 'output' data type uint8 must be one of [float32,float16,int32]."
-[FAIL] notEqual float32 2D tensors
-  promise_test: Unhandled rejection with value: object "TypeError: Unsupported data type, output 'output' data type uint8 must be one of [float32,float16,int32]."
-[FAIL] notEqual float32 3D tensors
-  promise_test: Unhandled rejection with value: object "TypeError: Unsupported data type, output 'output' data type uint8 must be one of [float32,float16,int32]."
-[FAIL] notEqual float32 4D tensors
-  promise_test: Unhandled rejection with value: object "TypeError: Unsupported data type, output 'output' data type uint8 must be one of [float32,float16,int32]."
-[FAIL] notEqual float32 5D tensors
-  promise_test: Unhandled rejection with value: object "TypeError: Unsupported data type, output 'output' data type uint8 must be one of [float32,float16,int32]."
-[FAIL] notEqual float32 broadcast 0D to 4D
-  promise_test: Unhandled rejection with value: object "TypeError: Unsupported data type, output 'output' data type uint8 must be one of [float32,float16,int32]."
-[FAIL] notEqual float32 broadcast 1D to 4D
-  promise_test: Unhandled rejection with value: object "TypeError: Unsupported data type, output 'output' data type uint8 must be one of [float32,float16,int32]."
-[FAIL] notEqual float32 broadcast 2D to 4D
-  promise_test: Unhandled rejection with value: object "TypeError: Unsupported data type, output 'output' data type uint8 must be one of [float32,float16,int32]."
-[FAIL] notEqual float32 broadcast 3D to 4D
-  promise_test: Unhandled rejection with value: object "TypeError: Unsupported data type, output 'output' data type uint8 must be one of [float32,float16,int32]."
-[FAIL] notEqual float32 broadcast 4D to 4D
-  promise_test: Unhandled rejection with value: object "TypeError: Unsupported data type, output 'output' data type uint8 must be one of [float32,float16,int32]."
 Harness: the test ran to completion.
 
diff --git a/third_party/blink/web_tests/platform/mac-mac15-arm64/virtual/webnn-service-on-npu/external/wpt/webnn/conformance_tests/not_equal.https.any_npu-expected.txt b/third_party/blink/web_tests/platform/mac-mac15-arm64/virtual/webnn-service-on-npu/external/wpt/webnn/conformance_tests/not_equal.https.any_npu-expected.txt
index 3415057..d2490db 100644
--- a/third_party/blink/web_tests/platform/mac-mac15-arm64/virtual/webnn-service-on-npu/external/wpt/webnn/conformance_tests/not_equal.https.any_npu-expected.txt
+++ b/third_party/blink/web_tests/platform/mac-mac15-arm64/virtual/webnn-service-on-npu/external/wpt/webnn/conformance_tests/not_equal.https.any_npu-expected.txt
@@ -1,27 +1,3 @@
 This is a testharness.js-based test.
-[FAIL] notEqual float32 0D scalar
-  promise_test: Unhandled rejection with value: object "TypeError: Unsupported data type, output 'output' data type uint8 must be one of [float32,float16,int32]."
-[FAIL] notEqual float32 1D constant tensors
-  promise_test: Unhandled rejection with value: object "TypeError: Unsupported data type, output 'output' data type uint8 must be one of [float32,float16,int32]."
-[FAIL] notEqual float32 1D tensors
-  promise_test: Unhandled rejection with value: object "TypeError: Unsupported data type, output 'output' data type uint8 must be one of [float32,float16,int32]."
-[FAIL] notEqual float32 2D tensors
-  promise_test: Unhandled rejection with value: object "TypeError: Unsupported data type, output 'output' data type uint8 must be one of [float32,float16,int32]."
-[FAIL] notEqual float32 3D tensors
-  promise_test: Unhandled rejection with value: object "TypeError: Unsupported data type, output 'output' data type uint8 must be one of [float32,float16,int32]."
-[FAIL] notEqual float32 4D tensors
-  promise_test: Unhandled rejection with value: object "TypeError: Unsupported data type, output 'output' data type uint8 must be one of [float32,float16,int32]."
-[FAIL] notEqual float32 5D tensors
-  promise_test: Unhandled rejection with value: object "TypeError: Unsupported data type, output 'output' data type uint8 must be one of [float32,float16,int32]."
-[FAIL] notEqual float32 broadcast 0D to 4D
-  promise_test: Unhandled rejection with value: object "TypeError: Unsupported data type, output 'output' data type uint8 must be one of [float32,float16,int32]."
-[FAIL] notEqual float32 broadcast 1D to 4D
-  promise_test: Unhandled rejection with value: object "TypeError: Unsupported data type, output 'output' data type uint8 must be one of [float32,float16,int32]."
-[FAIL] notEqual float32 broadcast 2D to 4D
-  promise_test: Unhandled rejection with value: object "TypeError: Unsupported data type, output 'output' data type uint8 must be one of [float32,float16,int32]."
-[FAIL] notEqual float32 broadcast 3D to 4D
-  promise_test: Unhandled rejection with value: object "TypeError: Unsupported data type, output 'output' data type uint8 must be one of [float32,float16,int32]."
-[FAIL] notEqual float32 broadcast 4D to 4D
-  promise_test: Unhandled rejection with value: object "TypeError: Unsupported data type, output 'output' data type uint8 must be one of [float32,float16,int32]."
 Harness: the test ran to completion.
 
diff --git a/third_party/blink/web_tests/platform/mac-mac15-arm64/virtual/webnn-service-with-gpu/external/wpt/webnn/conformance_tests/not_equal.https.any_gpu-expected.txt b/third_party/blink/web_tests/platform/mac-mac15-arm64/virtual/webnn-service-with-gpu/external/wpt/webnn/conformance_tests/not_equal.https.any_gpu-expected.txt
index 3415057..d2490db 100644
--- a/third_party/blink/web_tests/platform/mac-mac15-arm64/virtual/webnn-service-with-gpu/external/wpt/webnn/conformance_tests/not_equal.https.any_gpu-expected.txt
+++ b/third_party/blink/web_tests/platform/mac-mac15-arm64/virtual/webnn-service-with-gpu/external/wpt/webnn/conformance_tests/not_equal.https.any_gpu-expected.txt
@@ -1,27 +1,3 @@
 This is a testharness.js-based test.
-[FAIL] notEqual float32 0D scalar
-  promise_test: Unhandled rejection with value: object "TypeError: Unsupported data type, output 'output' data type uint8 must be one of [float32,float16,int32]."
-[FAIL] notEqual float32 1D constant tensors
-  promise_test: Unhandled rejection with value: object "TypeError: Unsupported data type, output 'output' data type uint8 must be one of [float32,float16,int32]."
-[FAIL] notEqual float32 1D tensors
-  promise_test: Unhandled rejection with value: object "TypeError: Unsupported data type, output 'output' data type uint8 must be one of [float32,float16,int32]."
-[FAIL] notEqual float32 2D tensors
-  promise_test: Unhandled rejection with value: object "TypeError: Unsupported data type, output 'output' data type uint8 must be one of [float32,float16,int32]."
-[FAIL] notEqual float32 3D tensors
-  promise_test: Unhandled rejection with value: object "TypeError: Unsupported data type, output 'output' data type uint8 must be one of [float32,float16,int32]."
-[FAIL] notEqual float32 4D tensors
-  promise_test: Unhandled rejection with value: object "TypeError: Unsupported data type, output 'output' data type uint8 must be one of [float32,float16,int32]."
-[FAIL] notEqual float32 5D tensors
-  promise_test: Unhandled rejection with value: object "TypeError: Unsupported data type, output 'output' data type uint8 must be one of [float32,float16,int32]."
-[FAIL] notEqual float32 broadcast 0D to 4D
-  promise_test: Unhandled rejection with value: object "TypeError: Unsupported data type, output 'output' data type uint8 must be one of [float32,float16,int32]."
-[FAIL] notEqual float32 broadcast 1D to 4D
-  promise_test: Unhandled rejection with value: object "TypeError: Unsupported data type, output 'output' data type uint8 must be one of [float32,float16,int32]."
-[FAIL] notEqual float32 broadcast 2D to 4D
-  promise_test: Unhandled rejection with value: object "TypeError: Unsupported data type, output 'output' data type uint8 must be one of [float32,float16,int32]."
-[FAIL] notEqual float32 broadcast 3D to 4D
-  promise_test: Unhandled rejection with value: object "TypeError: Unsupported data type, output 'output' data type uint8 must be one of [float32,float16,int32]."
-[FAIL] notEqual float32 broadcast 4D to 4D
-  promise_test: Unhandled rejection with value: object "TypeError: Unsupported data type, output 'output' data type uint8 must be one of [float32,float16,int32]."
 Harness: the test ran to completion.
 
diff --git a/third_party/blink/web_tests/platform/mac/virtual/webnn-service-with-gpu/external/wpt/webnn/conformance_tests/not_equal.https.any_gpu-expected.txt b/third_party/blink/web_tests/platform/mac/virtual/webnn-service-with-gpu/external/wpt/webnn/conformance_tests/not_equal.https.any_gpu-expected.txt
index 1478c00..03ce36b 100644
--- a/third_party/blink/web_tests/platform/mac/virtual/webnn-service-with-gpu/external/wpt/webnn/conformance_tests/not_equal.https.any_gpu-expected.txt
+++ b/third_party/blink/web_tests/platform/mac/virtual/webnn-service-with-gpu/external/wpt/webnn/conformance_tests/not_equal.https.any_gpu-expected.txt
@@ -1,5 +1,7 @@
 This is a testharness.js-based test.
 [FAIL] notEqual float32 5D tensors
   promise_test: Unhandled rejection with value: object "TypeError: Failed to execute 'notEqual' on 'MLGraphBuilder': Unsupported rank 5 for argument a (must be at most 4)."
+[FAIL] notEqual float16 5D tensors
+  promise_test: Unhandled rejection with value: object "TypeError: Failed to execute 'notEqual' on 'MLGraphBuilder': Unsupported rank 5 for argument a (must be at most 4)."
 Harness: the test ran to completion.
 
diff --git a/third_party/blink/web_tests/virtual/webnn-service-on-cpu/external/wpt/webnn/conformance_tests/not_equal.https.any_cpu-expected.txt b/third_party/blink/web_tests/virtual/webnn-service-on-cpu/external/wpt/webnn/conformance_tests/not_equal.https.any_cpu-expected.txt
index 1478c00..03ce36b 100644
--- a/third_party/blink/web_tests/virtual/webnn-service-on-cpu/external/wpt/webnn/conformance_tests/not_equal.https.any_cpu-expected.txt
+++ b/third_party/blink/web_tests/virtual/webnn-service-on-cpu/external/wpt/webnn/conformance_tests/not_equal.https.any_cpu-expected.txt
@@ -1,5 +1,7 @@
 This is a testharness.js-based test.
 [FAIL] notEqual float32 5D tensors
   promise_test: Unhandled rejection with value: object "TypeError: Failed to execute 'notEqual' on 'MLGraphBuilder': Unsupported rank 5 for argument a (must be at most 4)."
+[FAIL] notEqual float16 5D tensors
+  promise_test: Unhandled rejection with value: object "TypeError: Failed to execute 'notEqual' on 'MLGraphBuilder': Unsupported rank 5 for argument a (must be at most 4)."
 Harness: the test ran to completion.
 
diff --git a/third_party/blink/web_tests/virtual/webnn-service-on-npu/external/wpt/webnn/conformance_tests/not_equal.https.any_npu-expected.txt b/third_party/blink/web_tests/virtual/webnn-service-on-npu/external/wpt/webnn/conformance_tests/not_equal.https.any_npu-expected.txt
index 1478c00..03ce36b 100644
--- a/third_party/blink/web_tests/virtual/webnn-service-on-npu/external/wpt/webnn/conformance_tests/not_equal.https.any_npu-expected.txt
+++ b/third_party/blink/web_tests/virtual/webnn-service-on-npu/external/wpt/webnn/conformance_tests/not_equal.https.any_npu-expected.txt
@@ -1,5 +1,7 @@
 This is a testharness.js-based test.
 [FAIL] notEqual float32 5D tensors
   promise_test: Unhandled rejection with value: object "TypeError: Failed to execute 'notEqual' on 'MLGraphBuilder': Unsupported rank 5 for argument a (must be at most 4)."
+[FAIL] notEqual float16 5D tensors
+  promise_test: Unhandled rejection with value: object "TypeError: Failed to execute 'notEqual' on 'MLGraphBuilder': Unsupported rank 5 for argument a (must be at most 4)."
 Harness: the test ran to completion.
 
diff --git a/third_party/compiler-rt/src b/third_party/compiler-rt/src
index 735ec98f..d1f4ff49 160000
--- a/third_party/compiler-rt/src
+++ b/third_party/compiler-rt/src
@@ -1 +1 @@
-Subproject commit 735ec98fa1f709cfc3c650f6295d42e4ef4700f6
+Subproject commit d1f4ff49d7d71c2501d32848c9eeb707f857bd4a
diff --git a/third_party/dawn b/third_party/dawn
index aeb65a2..f7920e1 160000
--- a/third_party/dawn
+++ b/third_party/dawn
@@ -1 +1 @@
-Subproject commit aeb65a2a36257d72a85f57acee2f81b73c38d00a
+Subproject commit f7920e1b27c8803c054b22569bc27a96bc20f83f
diff --git a/third_party/devtools-frontend/src b/third_party/devtools-frontend/src
index aad6291..ef9aad9 160000
--- a/third_party/devtools-frontend/src
+++ b/third_party/devtools-frontend/src
@@ -1 +1 @@
-Subproject commit aad6291d99a6187f851db465b533cc4e86a0bf65
+Subproject commit ef9aad93988e3ff4149c5ea6e337abf234dc74de
diff --git a/third_party/ipcz/src/ipcz/router.cc b/third_party/ipcz/src/ipcz/router.cc
index a472024..4d9a5395 100644
--- a/third_party/ipcz/src/ipcz/router.cc
+++ b/third_party/ipcz/src/ipcz/router.cc
@@ -437,6 +437,7 @@
   if (data) {
     *data = parcel->data_view().data();
   }
+  absl::MutexLock lock(&mutex_);
   if (!pending_puts_) {
     pending_puts_ = std::make_unique<PendingTransactionSet>();
   }
@@ -455,15 +456,18 @@
     return IPCZ_RESULT_INVALID_ARGUMENT;
   }
 
-  if (!pending_puts_) {
-    return IPCZ_RESULT_INVALID_ARGUMENT;
-  }
-
   std::unique_ptr<Parcel> parcel;
-  if (aborted) {
-    parcel = pending_puts_->FinalizeForPut(transaction, 0);
-  } else {
-    parcel = pending_puts_->FinalizeForPut(transaction, num_bytes_produced);
+  {
+    absl::MutexLock lock(&mutex_);
+    if (!pending_puts_) {
+      return IPCZ_RESULT_INVALID_ARGUMENT;
+    }
+
+    if (aborted) {
+      parcel = pending_puts_->FinalizeForPut(transaction, 0);
+    } else {
+      parcel = pending_puts_->FinalizeForPut(transaction, num_bytes_produced);
+    }
   }
 
   if (!parcel) {
diff --git a/third_party/ipcz/src/ipcz/router.h b/third_party/ipcz/src/ipcz/router.h
index 3c537f43..f3bebcc 100644
--- a/third_party/ipcz/src/ipcz/router.h
+++ b/third_party/ipcz/src/ipcz/router.h
@@ -458,8 +458,8 @@
   // The set of pending get transactions in progress on this router.
   std::unique_ptr<PendingTransactionSet> pending_gets_ ABSL_GUARDED_BY(mutex_);
 
-  // The set of pending get transactions in progress on this router.
-  std::unique_ptr<PendingTransactionSet> pending_puts_;
+  // The set of pending put transactions in progress on this router.
+  std::unique_ptr<PendingTransactionSet> pending_puts_ ABSL_GUARDED_BY(mutex_);
 };
 
 }  // namespace ipcz
diff --git a/third_party/llvm-libc/src b/third_party/llvm-libc/src
index 644c27c..39b4cdb 160000
--- a/third_party/llvm-libc/src
+++ b/third_party/llvm-libc/src
@@ -1 +1 @@
-Subproject commit 644c27c66c4f297f86ef9adab0dcf2867dbaa572
+Subproject commit 39b4cdb3bd41dee179765787b2a5a0cc49f3fb98
diff --git a/third_party/perfetto b/third_party/perfetto
index e6c9e81d..557fafc 160000
--- a/third_party/perfetto
+++ b/third_party/perfetto
@@ -1 +1 @@
-Subproject commit e6c9e81d08071f80004b089861406cb88ec05bae
+Subproject commit 557fafc38445fdde33d19b1cf2d784686e51d120
diff --git a/third_party/rust/temporal_capi/BUILD.gn b/third_party/rust/temporal_capi/BUILD.gn
index b567cd9..31d5c74 100644
--- a/third_party/rust/temporal_capi/BUILD.gn
+++ b/third_party/rust/temporal_capi/BUILD.gn
@@ -1,13 +1,33 @@
 # v8 depends on temporal_capi, which may occasionally update in ways that
 # change its API. Since temporal_capi lives in the chromium repo, v8 cannot
 # be atomically upgraded alongside temporal_capi changes. Instead, we have some
-# defines here that let v8 detect which version of temporal_capi is vendored.
+# `defines` here that let v8 detect which version of temporal_capi is vendored.
 #
 # In case we are vendoring multiple versions of temporal_capi, it is acceptable
-# for this array to contain multiple defines.
+# to have multiple `defines`.
 #
-# It is not mandatory to update this array every time temporal_capi updates; it
-# is just a convenient tool to use as needed.
+# It is not mandatory to update the `defines` list every time temporal_capi
+# updates; it is just a convenient tool to use as needed.
 config("temporal_capi_versioning_defines") {
   defines = [ "TEMPORAL_CAPI_VERSION_0_0_8" ]
 }
+
+# v8 includes headers and targets from paths that include a specific crate epoch
+# in a directory name (e.g. `v0_0_8` or [currently, not quite correctly] `v0_0`).
+# To insulate v8 from the exact paths:
+#
+# * We have some indirection via `group` / `public_deps` to forward to the
+#   right target
+# * We set `include_dirs` by depending on the right config via `public_configs`
+#
+# We also opportunistically have a public dependency on the `defines` `config` above.
+group("temporal_capi") {
+  public_configs = [
+    ":temporal_capi_include_dirs",
+    ":temporal_capi_versioning_defines",
+  ]
+  public_deps = [ "//third_party/rust/temporal_capi/v0_0:lib" ]
+}
+config("temporal_capi_include_dirs") {
+  include_dirs = [ "//third_party/rust/chromium_crates_io/vendor/temporal_capi-v0_0/bindings/cpp" ]
+}
diff --git a/tools/clang/spanify/Spanifier.cpp b/tools/clang/spanify/Spanifier.cpp
index f074ccdc..cc330ed 100644
--- a/tools/clang/spanify/Spanifier.cpp
+++ b/tools/clang/spanify/Spanifier.cpp
@@ -63,6 +63,29 @@
 
 const char kStringViewIncludePath[] = "string_view";
 
+// Precedence values for EmitReplacement.
+//
+// The `extract_edits.py` script sorts multiple insertions at the same code
+// location by these precedence values in ascending numerical order.
+//
+// Paired insertions (e.g., an opening and its corresponding closing bracket)
+// typically use a precedence of `+K` for the "opening" part and `-K` for the
+// "closing" part, where K is one of the constants defined below. This is
+// because, for a given position, we usually want to close the bracket before
+// opening a new one. A higher precedence value is used when the replacement
+// has a higher tie with the expression.
+enum Precedence {
+  kNeutralPrecedence = 0,
+
+  // Lower priority (weaker ties to the target)
+  kDecaySpanToPointerPrecedence,
+  kAdaptBinaryOperationPrecedence,
+  kEmitSingleVariableSpanPrecedence,
+  kAdaptBinaryPlusEqOperationPrecedence,
+  kAppendDataCallPrecedence,
+  // Higher priority (stronger ties to the target)
+};
+
 // This iterates over function parameters and matches the ones that match
 // parm_var_decl_matcher.
 AST_MATCHER_P(clang::FunctionDecl,
@@ -411,7 +434,8 @@
 static std::string GetReplacementDirective(
     const clang::SourceRange& replacement_range,
     std::string replacement_text,
-    const clang::SourceManager& source_manager) {
+    const clang::SourceManager& source_manager,
+    int precedence = kNeutralPrecedence) {
   clang::tooling::Replacement replacement(
       source_manager, clang::CharSourceRange::getCharRange(replacement_range),
       replacement_text);
@@ -422,9 +446,9 @@
   // `./apply-edits.py` expects `\n` to be escaped as '\0'.
   std::replace(replacement_text.begin(), replacement_text.end(), '\n', '\0');
 
-  return llvm::formatv("r:::{0}:::{1}:::{2}:::{3}", file_path,
+  return llvm::formatv("r:::{0}:::{1}:::{2}:::{3}:::{4}", file_path,
                        replacement.getOffset(), replacement.getLength(),
-                       replacement_text);
+                       precedence, replacement_text);
 }
 
 std::string GetIncludeDirective(const clang::SourceRange replacement_range,
@@ -769,13 +793,15 @@
     end_replacement_text = ")[0]";
   }
 
-  EmitReplacement(GetRHS(result),
-                  GetReplacementDirective(begin_range, begin_replacement_text,
-                                          source_manager));
+  EmitReplacement(
+      GetRHS(result),
+      GetReplacementDirective(begin_range, begin_replacement_text,
+                              source_manager, -kDecaySpanToPointerPrecedence));
 
   EmitReplacement(
       GetRHS(result),
-      GetReplacementDirective(end_range, end_replacement_text, source_manager));
+      GetReplacementDirective(end_range, end_replacement_text, source_manager,
+                              kDecaySpanToPointerPrecedence));
 }
 
 static clang::SourceLocation GetBinaryOperationOperatorLoc(
@@ -838,7 +864,7 @@
                  llvm::formatv("base::span<{0}>(",
                                GetTypeAsString(rhs_array_type->getInnerType(),
                                                ast_context)),
-                 source_manager));
+                 source_manager, kAdaptBinaryOperationPrecedence));
     // Emit the closing `)` of `base::span(...)` below.
   }
 
@@ -862,7 +888,7 @@
                source_range,
                llvm::formatv("{0}.subspan({1})", rhs_array_type ? ")" : "",
                              initial_text.substr(1)),
-               source_manager));
+               source_manager, -kAdaptBinaryOperationPrecedence));
 
   // It's possible we emitted a rewrite that creates a temporary but
   // unnamed `base::span` (issue 408018846). This could end up being
@@ -889,8 +915,8 @@
   auto lhs_expr_range = getExprRange(lhs_expr, source_manager, lang_opts);
   auto binary_op_rhs_range =
       getExprRange(binary_op_RHS, source_manager, lang_opts);
-  auto source_range =
-      clang::SourceRange(lhs_expr_range.getEnd(), binary_op_rhs_range.getEnd());
+  auto source_range = clang::SourceRange(lhs_expr_range.getEnd(),
+                                         binary_op_rhs_range.getBegin());
   std::string lhs_expr_text =
       clang::Lexer::getSourceText(
           clang::CharSourceRange::getCharRange(lhs_expr_range), source_manager,
@@ -902,12 +928,17 @@
           source_manager, lang_opts)
           .str();
 
-  std::string replacement_text =
-      "=" + lhs_expr_text + ".subspan(" + binary_op_rhs_text + ")";
+  const std::string& key = GetRHS(result);
 
   EmitReplacement(
-      GetRHS(result),
-      GetReplacementDirective(source_range, replacement_text, source_manager));
+      key, GetReplacementDirective(
+               source_range, "=" + lhs_expr_text + ".subspan(", source_manager,
+               kAdaptBinaryPlusEqOperationPrecedence));
+
+  EmitReplacement(key,
+                  GetReplacementDirective(
+                      clang::SourceRange(binary_op_rhs_range.getEnd()), ")",
+                      source_manager, -kAdaptBinaryPlusEqOperationPrecedence));
 }
 
 // Handles boolean operations that need to be adapted after a span rewrite.
@@ -979,13 +1010,15 @@
     // Insert enclosing parenthesis for expressions with UnaryOperators
     auto begin_range = clang::SourceRange(getSourceRange(result).getBegin());
     EmitReplacement(GetRHS(result),
-                    GetReplacementDirective(begin_range, "(", source_manager));
+                    GetReplacementDirective(begin_range, "(", source_manager,
+                                            kAppendDataCallPrecedence));
     replacement_text = ").data()";
   }
 
   EmitReplacement(
       GetRHS(result),
-      GetReplacementDirective(rep_range, replacement_text, source_manager));
+      GetReplacementDirective(rep_range, replacement_text, source_manager,
+                              -kAppendDataCallPrecedence));
 }
 
 // Given that we want to emit `.subspan(expr)`,
@@ -1137,12 +1170,13 @@
   clang::SourceRange expr_range = {expr->getBeginLoc()};
   std::string type = GetTypeAsString(operand_decl->getType(), ast_context);
   std::string replacement_text = llvm::formatv("base::span<{0}, 1>(", type);
-  EmitReplacement(key, GetReplacementDirective(expr_range, replacement_text,
-                                               source_manager));
+  EmitReplacement(
+      key, GetReplacementDirective(expr_range, replacement_text, source_manager,
+                                   kEmitSingleVariableSpanPrecedence));
   EmitReplacement(
       key, GetReplacementDirective(
                getExprRange(operand_expr, source_manager, lang_opts).getEnd(),
-               ", 1u)", source_manager));
+               ", 1u)", source_manager, -kEmitSingleVariableSpanPrecedence));
 }
 
 // Rewrites unsafe third-party member function calls to helper macro calls.
@@ -1393,13 +1427,17 @@
     // Insert enclosing parenthesis for expressions with UnaryOperators
     auto begin_range = clang::SourceRange(getSourceRange(result).getBegin());
     EmitFrontier(lhs_key, rhs_key,
-                 GetReplacementDirective(begin_range, "(", source_manager));
+                 GetReplacementDirective(begin_range, "(", source_manager,
+                                         kAppendDataCallPrecedence));
     replacement_text = ").data()";
   }
 
+  // Use kAppendDataCallPrecedence because some rewrites will be duplicates of
+  // the ones in AppendDataCall().
   EmitFrontier(
       lhs_key, rhs_key,
-      GetReplacementDirective(rep_range, replacement_text, source_manager));
+      GetReplacementDirective(rep_range, replacement_text, source_manager,
+                              -kAppendDataCallPrecedence));
 }
 
 // Generate a class name for rewriting unnamed struct/class types. This is
diff --git a/tools/clang/spanify/extract_edits.py b/tools/clang/spanify/extract_edits.py
index 9952500..00476f4 100755
--- a/tools/clang/spanify/extract_edits.py
+++ b/tools/clang/spanify/extract_edits.py
@@ -35,14 +35,31 @@
   e 0001450:8-AxbSn3 0008303:GWkNbhQ4
   e 0001518:BUQKDaXe 0008244:DBKYJas7
   e 0001518:L97i_bwg 0008303:GWkNbhQ4
-  f 0001450:8-AxbSn3 0008303:GWkNbhQ4 r:::../../base/memory/shared_memory_mapping.h:::8684:::0:::.data()
-  f 0001518:BUQKDaXe 0008244:DBKYJas7 r:::../../base/memory/shared_memory_mapping.h:::8535:::15:::(data() + size()).data()
-  f 0001518:L97i_bwg 0008303:GWkNbhQ4 r:::../../base/memory/shared_memory_mapping.h:::8686:::15:::(data() + size()).data()
+  f 0001450:8-AxbSn3 0008303:GWkNbhQ4 r:::../../base/memory/shared_memory_mapping.h:::8684:::0:::0:::.data()
+  f 0001518:BUQKDaXe 0008244:DBKYJas7 r:::../../base/memory/shared_memory_mapping.h:::8535:::15:::0:::(data() + size()).data()
+  f 0001518:L97i_bwg 0008303:GWkNbhQ4 r:::../../base/memory/shared_memory_mapping.h:::8686:::15:::0:::(data() + size()).data()
   r 0001518:BUQKDaXe include-user-header:::../../base/containers/checked_iterators.h:::-1:::-1:::base/containers/span.h
-  r 0001518:BUQKDaXe r:::../../base/containers/checked_iterators.h:::1518:::9:::base::span<const unsigned char>
-  r 0001946:gKWdIpwv r:::../../base/containers/checked_iterators.h:::1946:::9:::base::span<const unsigned char>
+  r 0001518:BUQKDaXe r:::../../base/containers/checked_iterators.h:::1518:::9:::0:::base::span<const unsigned char>
+  r 0001946:gKWdIpwv r:::../../base/containers/checked_iterators.h:::1946:::9:::0:::base::span<const unsigned char>
 ```
 
+**Important Note on "r:::" Replacement Directive:**
+The `replacement_directive` strings starting with `r:::` (which can appear in
+`r` lines or as the third argument in `f` lines) have been extended and are
+slightly different from the format accepted by apply_edits.py. There is an
+additional `precedence` field before the replacement text.
+
+This `<precedence>` value is used by `extract_edits.py` to merge conflicting
+insertions. If multiple `r` directives are insertions (i.e., `<length>` is
+"0") and target the exact same file and offset:
+    Their `<text>` components are merged into a single replacement.
+    Directives with a lower numerical `<precedence>` value have their text
+    inserted *earlier* (further to the left) in the final merged text.
+
+The `<precedence>` field is **removed** by `extract_edits.py` from all `r`
+directives before they are included in the final list of edits output by this
+script.
+
 extract_edits.py takes input that is concatenated from multiple tool
 invocations and extract just the edits with the following steps:
 1- Construct the adjacency list of nodes
@@ -71,6 +88,7 @@
 
 from os.path import expanduser
 import pprint
+from collections import defaultdict
 
 
 # The connected components in the graph. This is useful to split the rewrite
@@ -226,17 +244,103 @@
 def assert_valid_replacement(replacement: str):
     try:
         parts = replacement.split(':::')
-        assert len(parts) == 5
-        assert parts[0] in [
+        directive_type = parts[0]
+
+        assert directive_type in [
             'r', 'include-user-header', 'include-system-header'
-        ]
-        assert parts[1] != ''  # File path
-        int(parts[2].isdigit())  # Offset
-        int(parts[3].isdigit())  # Length
+        ], f"Unknown directive type '{directive_type}'"
+
+        assert len(parts) > 1
+        assert parts[1] != '', "File path must not be empty."
+
+        if directive_type == 'r':
+            assert len(
+                parts
+            ) == 6, f"Directive 'r' must have 6 parts, got {len(parts)}"
+
+            # Validate offset.
+            assert parts[2].isdigit()
+
+            # Validate length.
+            assert parts[3].isdigit()
+
+            # Validate precedence. Can be a negative or positive integer.
+            try:
+                int(parts[4])  # Check if it's a valid integer representation
+            except ValueError:
+                raise AssertionError(
+                    f"Precedence '{parts[4]}' must be a valid integer string.")
+        else:
+            assert len(parts) == 5
     except:
         # Augment the error with the replacement text for better debugging.
         assert False, f"Invalid replacement: \"{replacement}\""
 
+
+def merge_insertions_and_remove_precedence_field(changes: set) -> set:
+    """
+    Merges conflicting insertions at the same code location.
+
+    The merge order is determined as follows:
+    Handles "associativity" by grouping insertions based on precedence sign.
+        The final text is formed by concatenating all positive-precedence
+        insertions (i.e., "closing" parts), then zero-precedence, then all
+        negative-precedence insertions ("opening" parts). This ensures that
+        a closing bracket from a left expression is placed before an opening
+        bracket from a right expression (e.g., `...>[...`).
+    Also removes the precedence field from the final replacement directives.
+    """
+    replacements_by_range = defaultdict(list)
+    result = set()
+
+    for change in changes:
+        assert_valid_replacement(change)
+        parts = change.split(':::')
+        directive_type = parts[0]
+
+        if directive_type == 'r':
+            _, file_path, offset, length, precedence, text = parts
+            precedence = int(precedence)
+            # Key identifies the exact code range being replaced
+            key = (file_path, offset, length)
+            replacements_by_range[key].append((precedence, text))
+        else:
+            result.add(change)
+
+    for key, candidates in replacements_by_range.items():
+        assert candidates, "A key should always have at least one candidate."
+
+        file_path, offset, length = key
+
+        if len(candidates) == 1:
+            # No conflict.
+            _, text = candidates[0]
+            reconstructed_directive = f"r:::{file_path}:::{offset}:::{length}:::{text}"
+            result.add(reconstructed_directive)
+            continue
+
+        if int(length) == 0:
+            # Conflicting insertion detected.
+
+            # Assert uniqueness of precedence values to ensure determinism.
+            precedences = [p for p, _ in candidates]
+            assert len(precedences) == len(
+                set(precedences)
+            ), "Conflicting insertions need to have unique precedece values."
+
+            merged_texts = [t for _, t in sorted(candidates)]
+            reconstructed_directive = f"r:::{file_path}:::{offset}:::{length}:::{''.join(merged_texts)}"
+            result.add(reconstructed_directive)
+        else:
+            # Conflicting non-insertion replacement. This is an unresolvable
+            # conflict for now. Just remove the precedence field.
+            for _, text in candidates:
+                reconstructed_directive = f"r:::{file_path}:::{offset}:::{length}:::{text}"
+                result.add(reconstructed_directive)
+
+    return result
+
+
 def main():
     # Since the tool is invoked from multiple compile units, we are using sets
     # to deduplicate what was visible from multiple compile units.
@@ -381,13 +485,16 @@
     ]
 
     for index, component in enumerate(component_with_changes):
-        for text in component.changes:
+        merged_component_changes = merge_insertions_and_remove_precedence_field(
+            component.changes)
+
+        for text in merged_component_changes:
             print(text)
 
-        summary_file.write(f'patch_{index}: {len(component.changes)}\n')
+        summary_file.write(f'patch_{index}: {len(merged_component_changes)}\n')
 
         with open(expanduser(f'~/scratch/patch_{index}.txt'), 'w') as f:
-            f.write('\n'.join(component.changes))
+            f.write('\n'.join(merged_component_changes))
 
     summary_file.close()
 
diff --git a/tools/gritsettings/resource_ids.spec b/tools/gritsettings/resource_ids.spec
index acf219dc..7058888 100644
--- a/tools/gritsettings/resource_ids.spec
+++ b/tools/gritsettings/resource_ids.spec
@@ -168,6 +168,10 @@
     "META": {"sizes": {"includes": [10],}},
     "includes": [2740],
   },
+  "<(SHARED_INTERMEDIATE_DIR)/chrome/browser/resources/actor_internals/resources.grd": {
+    "META": {"sizes": {"includes": [10],}},
+    "includes": [2750],
+  },
   "<(SHARED_INTERMEDIATE_DIR)/chrome/browser/resources/app_home/resources.grd": {
     "META": {"sizes": {"includes": [20]}},
     "includes": [2760],
diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml
index e66e834..dc11747 100644
--- a/tools/metrics/histograms/enums.xml
+++ b/tools/metrics/histograms/enums.xml
@@ -909,7 +909,7 @@
   <int value="196615" label="kEmbedder-kOomInterventionTabHelper"/>
   <int value="196616" label="kEmbedder-kOfflinePage"/>
   <int value="196617"
-      label="kEmbedder-kChromePasswordManagerClient_BindCredentialManager"/>
+      label="kEmbedder-kContentCredentialManager_BindCredentialManager"/>
   <int value="196618" label="kEmbedder-kPermissionRequestManager"/>
   <int value="196619" label="kEmbedder-kModalDialog"/>
   <int value="196620" label="kEmbedder-kExtensions"/>
diff --git a/tools/metrics/histograms/metadata/autofill/enums.xml b/tools/metrics/histograms/metadata/autofill/enums.xml
index d2e4363..639cce4 100644
--- a/tools/metrics/histograms/metadata/autofill/enums.xml
+++ b/tools/metrics/histograms/metadata/autofill/enums.xml
@@ -4037,7 +4037,7 @@
   <int value="66" label="Identity Credentials"/>
   <int value="67" label="Loyalty card entry"/>
   <int value="68" label="Manage Loyalty Cards information"/>
-  <int value="69" label="Home and Work address suggestion"/>
+  <int value="69" label="(Obsolete) Home and Work address suggestion"/>
 </enum>
 
 <!-- LINT.ThenChange(/components/autofill/core/browser/suggestions/suggestion_type.h:SuggestionType) -->
diff --git a/tools/metrics/histograms/metadata/custom_tabs/OWNERS b/tools/metrics/histograms/metadata/custom_tabs/OWNERS
index 3c73151a1..939e9a458 100644
--- a/tools/metrics/histograms/metadata/custom_tabs/OWNERS
+++ b/tools/metrics/histograms/metadata/custom_tabs/OWNERS
@@ -2,7 +2,6 @@
 
 # Prefer sending CLs to the owners listed below.
 # Use chromium-metrics-reviews@google.com as a backup.
-eirage@chromium.org
 lizeb@chromium.org
 mvanouwerkerk@chromium.org
-sinansahin@google.com
\ No newline at end of file
+sinansahin@google.com
diff --git a/tools/metrics/histograms/metadata/media/enums.xml b/tools/metrics/histograms/metadata/media/enums.xml
index 6f3b518..e2159b7 100644
--- a/tools/metrics/histograms/metadata/media/enums.xml
+++ b/tools/metrics/histograms/metadata/media/enums.xml
@@ -561,7 +561,7 @@
   <int value="0" label="Unknown"/>
   <int value="1" label="Temporary"/>
   <int value="2" label="Persistent License"/>
-  <int value="3" label="Persistent Usage Record"/>
+  <int value="3" label="(Obsolete) Persistent Usage Record"/>
 </enum>
 
 <enum name="EnumerateDevicesResult">
diff --git a/tools/metrics/histograms/metadata/password/enums.xml b/tools/metrics/histograms/metadata/password/enums.xml
index 9807e7e..194b603f 100644
--- a/tools/metrics/histograms/metadata/password/enums.xml
+++ b/tools/metrics/histograms/metadata/password/enums.xml
@@ -728,12 +728,6 @@
              credentials report"/>
 </enum>
 
-<enum name="PasswordBubbleFollowupType">
-  <int value="0" label="Safe state"/>
-  <int value="1" label="More passwords to fix"/>
-  <int value="2" label="Unsafe state"/>
-</enum>
-
 <enum name="PasswordChangeFlowState">
   <int value="0" label="Offering password change"/>
   <int value="1" label="Waiting for privacy notice acceptance"/>
diff --git a/tools/metrics/histograms/metadata/password/histograms.xml b/tools/metrics/histograms/metadata/password/histograms.xml
index cada7f0..a815bc2 100644
--- a/tools/metrics/histograms/metadata/password/histograms.xml
+++ b/tools/metrics/histograms/metadata/password/histograms.xml
@@ -317,16 +317,6 @@
   </summary>
 </histogram>
 
-<histogram name="PasswordBubble.CompromisedBubble.Type"
-    enum="PasswordBubbleFollowupType" expires_after="2025-07-06">
-  <owner>vasilii@chromium.org</owner>
-  <owner>kazinova@google.com</owner>
-  <summary>
-    The state of the bubble shown after users saves/updates a password. It
-    notifies the user about leftover compromised passwords.
-  </summary>
-</histogram>
-
 <histogram name="PasswordBubble.DisplayDisposition"
     enum="PasswordBubbleDisplayDisposition" expires_after="2025-10-12">
   <owner>vasilii@chromium.org</owner>
diff --git a/tools/metrics/histograms/metadata/permissions/histograms.xml b/tools/metrics/histograms/metadata/permissions/histograms.xml
index 63dbb52..f0c5813df 100644
--- a/tools/metrics/histograms/metadata/permissions/histograms.xml
+++ b/tools/metrics/histograms/metadata/permissions/histograms.xml
@@ -194,7 +194,7 @@
 <!-- LINT.IfChange(PredictionModels) -->
 
 <variants name="PredictionModels">
-  <variant name="Aiv3"/>
+  <variant name="AIv3"/>
   <variant name="OnDevicePredictionService"/>
   <variant name="PredictionService"/>
 </variants>
diff --git a/tools/metrics/histograms/metadata/ui/enums.xml b/tools/metrics/histograms/metadata/ui/enums.xml
index bb78a4f9..859f7d8 100644
--- a/tools/metrics/histograms/metadata/ui/enums.xml
+++ b/tools/metrics/histograms/metadata/ui/enums.xml
@@ -848,6 +848,7 @@
   <int value="1076364348" label="chrome://web-app-internals/"/>
   <int value="1084212752" label="chrome://personalization/"/>
   <int value="1109407387" label="chrome://keyboardoverlay/"/>
+  <int value="1131279479" label="chrome://actor-internals/"/>
   <int value="1131405887" label="chrome://taskscheduler-internals/"/>
   <int value="1134924982" label="chrome://app-icon/"/>
   <int value="1145000455" label="chrome://arc-overview-tracing/"/>
diff --git a/tools/perf/core/perfetto_binary_roller/binary_deps.json b/tools/perf/core/perfetto_binary_roller/binary_deps.json
index 5eae972..4b9c077 100644
--- a/tools/perf/core/perfetto_binary_roller/binary_deps.json
+++ b/tools/perf/core/perfetto_binary_roller/binary_deps.json
@@ -5,8 +5,8 @@
             "full_remote_path": "perfetto-luci-artifacts/v50.1/linux-arm64/trace_processor_shell"
         },
         "win": {
-            "hash": "10fc9712cfb061d8c3c72a0763b8a4144c2694ea",
-            "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/win/e6c9e81d08071f80004b089861406cb88ec05bae/trace_processor_shell.exe"
+            "hash": "04cbab8845b573ce0310a7cf9350291bb5219411",
+            "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/win/4f93293dbb8f7728c40e34ce8098892a35988790/trace_processor_shell.exe"
         },
         "linux_arm": {
             "hash": "99f971ca131f6d11c73f4b918099d434bdd8093c",
@@ -21,8 +21,8 @@
             "full_remote_path": "perfetto-luci-artifacts/v50.1/mac-arm64/trace_processor_shell"
         },
         "linux": {
-            "hash": "a5cbade8ad62d782bd42a167a68a531d7c51dbfa",
-            "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/linux/e6c9e81d08071f80004b089861406cb88ec05bae/trace_processor_shell"
+            "hash": "3516ed04e4cd3e7730785b16937bb8bb85625db1",
+            "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/linux/4f93293dbb8f7728c40e34ce8098892a35988790/trace_processor_shell"
         }
     },
     "power_profile.sql": {
diff --git a/v8 b/v8
index b61ffce..3e4d2e9 160000
--- a/v8
+++ b/v8
@@ -1 +1 @@
-Subproject commit b61ffce56a3dd06638bdeda9524a831ab67ac741
+Subproject commit 3e4d2e9bda03056d1e4f1434f95777aeb03fbe1f