diff --git a/DEPS b/DEPS
index 5097d10a..19fbf6d 100644
--- a/DEPS
+++ b/DEPS
@@ -290,11 +290,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.
-  'src_internal_revision': '495944671d1f0f6b653bcf7b09c4b21b4b43aa78',
+  'src_internal_revision': 'd7516c780898c612c1ff9c1034fb2d79f39949f0',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling Skia
   # and whatever else without interference from each other.
-  'skia_revision': 'b977488cbee621bae41ef3921a2c409b6bf8e4ab',
+  'skia_revision': '264332c5ec8db66b2e0d2c8fedf72ed09361765d',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling V8
   # and whatever else without interference from each other.
@@ -310,7 +310,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling PDFium
   # and whatever else without interference from each other.
-  'pdfium_revision': '441eafa8b862fb3aa21e2c3c59daf2892d694099',
+  'pdfium_revision': '0db284a42a94da8579c1491ee9756ea66dfbf75c',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling BoringSSL
   # and whatever else without interference from each other.
@@ -326,7 +326,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling googletest
   # and whatever else without interference from each other.
-  'googletest_revision': '309dab8d4bbfcef0ef428762c6fec7172749de0f',
+  'googletest_revision': '7e17b15f1547bb8dd9c2fed91043b7af3437387f',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling lighttpd
   # and whatever else without interference from each other.
@@ -378,7 +378,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': '26524631b64cbecd4e12a02ba13f177ae90735ad',
+  'devtools_frontend_revision': '4fc8b1ccc06c830adf41d9ba9ad7fbc648ebba12',
   # 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.
@@ -402,7 +402,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': '4b97c56324e5b31394618a40b9946c73ac24abcf',
+  'dawn_revision': '26b0c00765b409bb057e3b221de04f6f7b25fef7',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
@@ -506,11 +506,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':    '3a9c43119ae6b3251df16ea2ee362b41c822fe6e',
+  'llvm_libc_revision':    '35acb9ccce9146434680d179b47caf14d1d310b1',
   # 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': '0378b4d5676bcf174e02493e0dd4d1507987014d',
+  'compiler_rt_revision': '9d7069a223da41e867c1ac0bd5c10cbf7398ee85',
 
   # If you change this, also update the libc++ revision in
   # //buildtools/deps_revisions.gni.
@@ -1177,7 +1177,7 @@
       'packages': [
           {
               'package': 'chromium/chrome/android/orderfiles/arm',
-              'version': 'W1Kz2U48Kf-OqiOuC8qtR7D5jDLJ8hfsDO_F_qOjTBEC',
+              'version': 'mxc0dKAEWE1lllA1rnBBTVUTBAx1K2u2CwWLmLH-dFgC',
           },
       ],
       'condition': 'checkout_android',
@@ -1442,7 +1442,7 @@
       'packages': [
         {
           'package': 'chromium/third_party/updater/chromium_linux64',
-          'version': 'version:2@1488001',
+          'version': 'version:2@1489012',
         },
       ],
   },
@@ -1453,7 +1453,7 @@
       'packages': [
         {
           'package': 'chromium/third_party/updater/chromium_mac_amd64',
-          'version': 'version:2@1488009',
+          'version': 'version:2@1489013',
         },
       ],
   },
@@ -1464,7 +1464,7 @@
       'packages': [
         {
           'package': 'chromium/third_party/updater/chromium_mac_arm64',
-          'version': 'version:2@1488005',
+          'version': 'version:2@1489004',
         },
       ],
   },
@@ -1508,7 +1508,7 @@
       'packages': [
         {
           'package': 'chromium/third_party/enterprise_companion/chromium_linux64',
-          'version': 'WBMFALvQmIA6TMT7Gs3iad_Lb8xjppF9P1qeoKvIM20C',
+          'version': 'Pbk58B-NQD7jU8HtmIJUh2DjDvfKvq5MKrWrISJFelkC',
         },
       ],
   },
@@ -1519,7 +1519,7 @@
       'packages': [
         {
           'package': 'chromium/third_party/enterprise_companion/chromium_mac_amd64',
-          'version': 'FknqZ0tqVyCiZZpT52d3fE3DY2f9vEmQL955aDr6C5QC',
+          'version': '0rs_kLuWsIoPIpDdoMjMDis-eP-BWd8HmpIPgPk5S2IC',
         },
       ],
   },
@@ -1530,7 +1530,7 @@
       'packages': [
         {
           'package': 'chromium/third_party/enterprise_companion/chromium_mac_arm64',
-          'version': 'klFUYxZxt0ZQPVH3nINHgdnbY78rOLJ9mweNyD-vIoEC',
+          'version': 'AgQYcKZuH6HLTvZ3FbjKj1Q0wP9XMGSjXfXOzIBaYLUC',
         },
       ],
   },
@@ -1541,7 +1541,7 @@
       'packages': [
         {
           'package': 'chromium/third_party/enterprise_companion/chromium_win_x86',
-          'version': 'GYaSUBcGN9Xdfl9EinwRKFQbT3Wuz-NMMeIZ3OLcZQcC',
+          'version': '6of4-4m1gTK2nRaaBOtOhsnT_YrDK66bFN0TXhg45QIC',
         },
       ],
   },
@@ -1552,7 +1552,7 @@
       'packages': [
         {
           'package': 'chromium/third_party/enterprise_companion/chromium_win_x86_64',
-          'version': 'OwGlGNC7sDsPG4Y8q4FPOJBtGHHjmwS6oelxlaafafEC',
+          'version': 'f7jv8JtIrSh4R0mdUJLbtWNeS-g39EoP-t7eb92f2ZIC',
         },
       ],
   },
@@ -1588,7 +1588,7 @@
     'packages': [
       {
         'package': 'chromium/chrome/test/data/variations/cipd',
-        'version': 'egRDc9LQYnWhH--FZxzd8JaETWxMIXUkE_QvUB2Vb7YC',
+        'version': 'osXWbc5sWm22zIH1DBf6OH98SPGgP2R9UaG30M3FhDIC',
       },
     ],
     'dep_type': 'cipd',
@@ -1599,7 +1599,7 @@
 
   'src/clank': {
     'url': Var('chrome_git') + '/clank/internal/apps.git' + '@' +
-    '6d479618f334071254f22d7006cc1328c317f36b',
+    '2f10656fc4a8dbf6a3f39d2c279a6f882b81d376',
     'condition': 'checkout_android and checkout_src_internal',
   },
 
@@ -1753,7 +1753,7 @@
     'packages': [
       {
           'package': 'chromium/third_party/androidx',
-          'version': 'qvnG76Wz_191zpLBc4olnUl4jm6QFu94HP9o-7etiBQC',
+          'version': 'BACv9nvjYHclYYrU1Pk9-XkJsLiHfEGN8cAHz8OWGkwC',
       },
     ],
     'condition': 'checkout_android and non_git_source',
@@ -1846,7 +1846,7 @@
       'packages': [
           {
                'package': 'chromium/third_party/android_build_tools/lint',
-               'version': 'cdtWzgG_m_S0CSeCCsD021lqpzCKCUGXuxF5dC0WF64C',
+               'version': '0IDsNxRcDy86JZq-Qrpga3ZDtWrFmPwZNrKeto7oRugC',
           },
       ],
       'condition': 'checkout_android and non_git_source',
@@ -1857,7 +1857,7 @@
       'packages': [
           {
                'package': 'chromium/third_party/android_build_tools/manifest_merger',
-               'version': 'QrijdvfUX3yiheOkhxHyGfIIU95vhUEmVJQuuz3BzEMC',
+               'version': 'gQksmk7jMihnJTRFAJ9fd4m24OyVWrjBd08JFZR0jpcC',
           },
       ],
       'condition': 'checkout_android and non_git_source',
@@ -2059,7 +2059,7 @@
   # Tools used when building Chrome for Chrome OS. This affects both the Simple
   # Chrome workflow, as well as the chromeos-chrome ebuild.
   'src/third_party/chromite': {
-      'url': Var('chromium_git') + '/chromiumos/chromite.git' + '@' + '24384ed41afa31690853277445c62c3c2e3ecda5',
+      'url': Var('chromium_git') + '/chromiumos/chromite.git' + '@' + 'eecb3d62cbf51e097396f0c9adbc0758c1519967',
       'condition': 'checkout_chromeos',
   },
 
@@ -2800,7 +2800,7 @@
   },
 
   'src/third_party/re2/src':
-    Var('chromium_git') + '/external/github.com/google/re2.git' + '@' + '6e9f66fb1290b8c298a66a1803305789cbac0e88',
+    Var('chromium_git') + '/external/github.com/google/re2.git' + '@' + '79741d66ed4a4b0787a28e3899db6335de5dabef',
 
   'src/third_party/r8/cipd': {
       'packages': [
@@ -2994,7 +2994,7 @@
     Var('chromium_git') + '/external/khronosgroup/webgl.git' + '@' + 'c01b768bce4a143e152c1870b6ba99ea6267d2b0',
 
   'src/third_party/webgpu-cts/src':
-    Var('chromium_git') + '/external/github.com/gpuweb/cts.git' + '@' + '5b477670f53e5fefcf4bd829a2952013ef9d1953',
+    Var('chromium_git') + '/external/github.com/gpuweb/cts.git' + '@' + '26530c7da1c48ea5d0bf4c5da83b15b996eb522f',
 
   'src/third_party/webpagereplay':
     Var('chromium_git') + '/webpagereplay.git' + '@' + Var('webpagereplay_revision'),
@@ -3122,7 +3122,7 @@
     'packages': [
       {
         'package': 'chromeos_internal/apps/boca_app/app',
-        'version': 'w5XHCls6R1G2WI88MTnNzMoVfyqy8XpzODoyzL32U_8C',
+        'version': 'Ts3Yv4K_qYffh106rhw1-hD6kwJPNLDojq-UDYMVLtcC',
       },
     ],
     'condition': 'checkout_chromeos and checkout_src_internal',
@@ -3554,7 +3554,7 @@
 
   'src/chrome/browser/platform_experience/win': {
       'url': Var('chrome_git') + '/chrome/browser/platform_experience/win.git' + '@' +
-        'e947bd49c411a3c31bca6d6cf08c27767ed6c8c5',
+        '47650f3a9a6f66c92e1069dfe2db3db1c48f76d3',
       'condition': 'checkout_src_internal',
   },
 
@@ -3697,7 +3697,7 @@
 
   'src/components/optimization_guide/internal': {
       'url': Var('chrome_git') + '/chrome/components/optimization_guide.git' + '@' +
-        '2a21ca633ac74e58197d23619bab4d2e4997e318',
+        'a1b6a0bb7276cee3d161aba543b0a24f8a087d5c',
       'condition': 'checkout_src_internal',
   },
 
@@ -3763,7 +3763,7 @@
 
   'src/ios_internal':  {
       'url': Var('chrome_git') + '/chrome/ios_internal.git' + '@' +
-        '065e00ac78fc26a82ff3ced278f588693dd700af',
+        '0d3b8b6b0a45531fe2f7717be2f2227d7acf9f4b',
       'condition': 'checkout_ios and checkout_src_internal',
   },
 
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 2b09932c..cc619097 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
@@ -1026,7 +1026,7 @@
     public void startUpWebView(
             @NonNull WebViewStartUpCallback callback,
             boolean shouldRunUiThreadStartUpTasks,
-            @Nullable Set<String> profilesToLoad) {
+            Set<String> profilesToLoad) {
         if (Looper.myLooper() == Looper.getMainLooper()) {
             throw new IllegalStateException(
                     "startUpWebView should not be called on the Android main looper");
diff --git a/android_webview/support_library/boundary_interfaces/src/org/chromium/support_lib_boundary/WebViewStartUpConfigBoundaryInterface.java b/android_webview/support_library/boundary_interfaces/src/org/chromium/support_lib_boundary/WebViewStartUpConfigBoundaryInterface.java
index b4b05d0..98d446ca 100644
--- a/android_webview/support_library/boundary_interfaces/src/org/chromium/support_lib_boundary/WebViewStartUpConfigBoundaryInterface.java
+++ b/android_webview/support_library/boundary_interfaces/src/org/chromium/support_lib_boundary/WebViewStartUpConfigBoundaryInterface.java
@@ -5,9 +5,7 @@
 package org.chromium.support_lib_boundary;
 
 import org.jspecify.annotations.NullMarked;
-import org.jspecify.annotations.Nullable;
 
-import java.util.Set;
 import java.util.concurrent.Executor;
 
 /** Boundary interface for WebViewStartUpConfig. */
@@ -15,13 +13,8 @@
 public interface WebViewStartUpConfigBoundaryInterface {
     Executor getBackgroundExecutor();
 
-    /** Whether to run only parts of startup that doesn't block the UI thread. */
-    boolean shouldRunUiThreadStartUpTasks();
-
     /**
-     * Returns the set of profile names to load during startup.
-     *
-     * @return A set of profile names, which may include the default profile.
+     * Whether to run only parts of startup that doesn't block the UI thread.
      */
-    @Nullable Set<String> getProfileNamesToLoad();
+    boolean shouldRunUiThreadStartUpTasks();
 }
diff --git a/android_webview/support_library/java/src/org/chromium/support_lib_glue/SupportLibWebViewChromiumFactory.java b/android_webview/support_library/java/src/org/chromium/support_lib_glue/SupportLibWebViewChromiumFactory.java
index ac674e0c..7729d37 100644
--- a/android_webview/support_library/java/src/org/chromium/support_lib_glue/SupportLibWebViewChromiumFactory.java
+++ b/android_webview/support_library/java/src/org/chromium/support_lib_glue/SupportLibWebViewChromiumFactory.java
@@ -750,9 +750,7 @@
                                         supportLibResult));
                     };
             mAwInit.startUpWebView(
-                    callback,
-                    webViewStartUpConfig.shouldRunUiThreadStartUpTasks(),
-                    webViewStartUpConfig.getProfileNamesToLoad());
+                    callback, webViewStartUpConfig.shouldRunUiThreadStartUpTasks(), null);
         }
     }
 }
diff --git a/android_webview/test/data/web_tests/webexposed/global-interface-listing-expected.txt b/android_webview/test/data/web_tests/webexposed/global-interface-listing-expected.txt
index 6b8dcdf9..0a08189 100644
--- a/android_webview/test/data/web_tests/webexposed/global-interface-listing-expected.txt
+++ b/android_webview/test/data/web_tests/webexposed/global-interface-listing-expected.txt
@@ -356,6 +356,11 @@
     getter port
     method constructor
     setter onprocessorerror
+interface AutomationDelegate
+    attribute @@toStringTag
+    method constructor
+    method registerTool
+    method unregisterTool
 interface BackForwardCacheRestoration : PerformanceEntry
     attribute @@toStringTag
     getter pageshowEventEnd
@@ -12041,6 +12046,7 @@
     attribute awConsole
     attribute globalThis
     attribute propertyNamesInGlobal
+    getter automationDelegate
     getter caches
     getter clientInformation
     getter closed
@@ -12287,6 +12293,7 @@
     method structuredClone
     method webkitCancelAnimationFrame
     method webkitRequestAnimationFrame
+    setter automationDelegate
     setter clientInformation
     setter devicePixelRatio
     setter event
diff --git a/ash/constants/notifier_catalogs.h b/ash/constants/notifier_catalogs.h
index b713716..324c4443 100644
--- a/ash/constants/notifier_catalogs.h
+++ b/ash/constants/notifier_catalogs.h
@@ -216,7 +216,8 @@
   kFaceGazeActive = 197,
   kUsbPeripheralDeviceOrEndpointLimit = 198,
   kDemoMode = 199,
-  kMaxValue = kDemoMode
+  kArcDlcInstall = 200,
+  kMaxValue = kArcDlcInstall
 };
 
 // A living catalog that registers system nudges.
diff --git a/build/mac/find_sdk.py b/build/mac/find_sdk.py
index 3dcc4d5..c83a3d1 100755
--- a/build/mac/find_sdk.py
+++ b/build/mac/find_sdk.py
@@ -76,12 +76,14 @@
     raise SdkError('Install Xcode, launch it, accept the license ' +
       'agreement, and run `sudo xcode-select -s /path/to/Xcode.app` ' +
       'to continue.')
-  sdks = [re.findall('^MacOSX(\d+\.\d+)\.sdk$', s) for s in os.listdir(sdk_dir)]
+  sdk_dir_list = os.listdir(sdk_dir)
+  sdks = [re.findall('^MacOSX(\d+\.\d+)\.sdk$', s) for s in sdk_dir_list]
   sdks = [s[0] for s in sdks if s]  # [['10.5'], ['10.6']] => ['10.5', '10.6']
   sdks = [s for s in sdks  # ['10.5', '10.6'] => ['10.6']
           if parse_version(s) >= parse_version(min_sdk_version)]
   if not sdks:
-    raise Exception('No %s+ SDK found' % min_sdk_version)
+    raise Exception(
+        f'No {min_sdk_version}+ SDK found. {sdk_dir} contains: {sdk_dir_list}')
   best_sdk = sorted(sdks, key=parse_version)[0]
   sdk_name = 'MacOSX' + best_sdk + '.sdk'
   sdk_path = os.path.join(sdk_dir, sdk_name)
diff --git a/cc/mojom/render_frame_metadata.mojom b/cc/mojom/render_frame_metadata.mojom
index 11a83f82..95980de 100644
--- a/cc/mojom/render_frame_metadata.mojom
+++ b/cc/mojom/render_frame_metadata.mojom
@@ -118,7 +118,7 @@
   bool has_transparent_background;
 };
 
-[EnableIf=is_android]
+[EnableIf=is_android|is_ios]
 enum RootScrollOffsetUpdateFrequency {
   // The client will only be notified of root scroll offsets via the regular
   // RenderFrameMetadata updates. This is the default behavior. See
@@ -151,7 +151,7 @@
   // In other words, if this is sent, *only* the root-scroll-offset has changed
   // and the client is not sent a OnRenderFrameMetadataChanged() for the frame.
   // Used on Android for acessibility and GestureListenerManager.
-  [EnableIf=is_android]
+  [EnableIf=is_android|is_ios]
   UpdateRootScrollOffsetUpdateFrequency(
       RootScrollOffsetUpdateFrequency frequency);
 
@@ -175,6 +175,6 @@
   OnFrameSubmissionForTesting(uint32 frame_token);
 
   // Only called if UpdateRootScrollOffsetUpdateFrequency() has been called.
-  [EnableIf=is_android]
+  [EnableIf=is_android|is_ios]
   OnRootScrollOffsetChanged(gfx.mojom.PointF root_scroll_offset);
 };
diff --git a/cc/trees/layer_tree_host_impl.cc b/cc/trees/layer_tree_host_impl.cc
index d53e358..22e30999 100644
--- a/cc/trees/layer_tree_host_impl.cc
+++ b/cc/trees/layer_tree_host_impl.cc
@@ -333,7 +333,7 @@
     client_->SetWaitingForScrollEvent(false);
   }
 
-#if BUILDFLAG(IS_ANDROID)
+#if BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_IOS)
   if (render_frame_metadata_observer_) {
     render_frame_metadata_observer_->DidEndScroll();
   }
diff --git a/cc/trees/layer_tree_host_impl_unittest.cc b/cc/trees/layer_tree_host_impl_unittest.cc
index 70cb633..457303e 100644
--- a/cc/trees/layer_tree_host_impl_unittest.cc
+++ b/cc/trees/layer_tree_host_impl_unittest.cc
@@ -16827,7 +16827,7 @@
       compositor_frame_metadata->send_frame_token_to_embedder = true;
     last_metadata_ = render_frame_metadata;
   }
-#if BUILDFLAG(IS_ANDROID)
+#if BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_IOS)
   void DidEndScroll() override {}
 #endif
 
diff --git a/cc/trees/layer_tree_host_unittest.cc b/cc/trees/layer_tree_host_unittest.cc
index e9bddb91..7430e19 100644
--- a/cc/trees/layer_tree_host_unittest.cc
+++ b/cc/trees/layer_tree_host_unittest.cc
@@ -8819,7 +8819,7 @@
       target_->OnRenderFrameSubmission(render_frame_metadata,
                                        compositor_frame_metadata, force_send);
     }
-#if BUILDFLAG(IS_ANDROID)
+#if BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_IOS)
     void DidEndScroll() override { target_->DidEndScroll(); }
 #endif
 
@@ -8874,7 +8874,7 @@
     if (force_send)
       num_force_sends_++;
   }
-#if BUILDFLAG(IS_ANDROID)
+#if BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_IOS)
   void DidEndScroll() override {}
 #endif
 
@@ -9077,7 +9077,7 @@
       target_->OnRenderFrameSubmission(render_frame_metadata,
                                        compositor_frame_metadata, force_send);
     }
-#if BUILDFLAG(IS_ANDROID)
+#if BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_IOS)
     void DidEndScroll() override { target_->DidEndScroll(); }
 #endif
 
@@ -9154,7 +9154,7 @@
     ExpectMetadata(render_frame_metadata.delegated_ink_metadata,
                    compositor_frame_metadata->delegated_ink_metadata.get());
   }
-#if BUILDFLAG(IS_ANDROID)
+#if BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_IOS)
   void DidEndScroll() override {}
 #endif
 
@@ -10980,7 +10980,7 @@
         const RenderFrameMetadata& render_frame_metadata,
         viz::CompositorFrameMetadata* compositor_frame_metadata,
         bool force_send) override {}
-#if BUILDFLAG(IS_ANDROID)
+#if BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_IOS)
     void DidEndScroll() override {}
 #endif
 
diff --git a/cc/trees/render_frame_metadata_observer.h b/cc/trees/render_frame_metadata_observer.h
index 74ff372..b825160 100644
--- a/cc/trees/render_frame_metadata_observer.h
+++ b/cc/trees/render_frame_metadata_observer.h
@@ -38,7 +38,7 @@
       viz::CompositorFrameMetadata* compositor_frame_metadata,
       bool force_send) = 0;
 
-#if BUILDFLAG(IS_ANDROID)
+#if BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_IOS)
   // Notification of the scroll end event.
   virtual void DidEndScroll() = 0;
 #endif
diff --git a/chrome/VERSION b/chrome/VERSION
index 7f780ba..f3d65a3 100644
--- a/chrome/VERSION
+++ b/chrome/VERSION
@@ -1,4 +1,4 @@
 MAJOR=140
 MINOR=0
-BUILD=7305
+BUILD=7306
 PATCH=0
diff --git a/chrome/android/chrome_java_sources.gni b/chrome/android/chrome_java_sources.gni
index 53650a1..405f207 100644
--- a/chrome/android/chrome_java_sources.gni
+++ b/chrome/android/chrome_java_sources.gni
@@ -441,7 +441,6 @@
   "java/src/org/chromium/chrome/browser/customtabs/CustomTabCookiesFetcher.java",
   "java/src/org/chromium/chrome/browser/customtabs/CustomTabDelegateFactory.java",
   "java/src/org/chromium/chrome/browser/customtabs/CustomTabDownloadObserver.java",
-  "java/src/org/chromium/chrome/browser/customtabs/CustomTabFeatureOverridesManager.java",
   "java/src/org/chromium/chrome/browser/customtabs/CustomTabFileUtils.java",
   "java/src/org/chromium/chrome/browser/customtabs/CustomTabIncognitoManager.java",
   "java/src/org/chromium/chrome/browser/customtabs/CustomTabIntentDataProvider.java",
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupsPaneUnitTest.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupsPaneUnitTest.java
index fcd24be..186b7956 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupsPaneUnitTest.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupsPaneUnitTest.java
@@ -177,10 +177,7 @@
     }
 
     @Test
-    @EnableFeatures({
-        ChromeFeatureList.EDGE_TO_EDGE_BOTTOM_CHIN,
-        ChromeFeatureList.DRAW_KEY_NATIVE_EDGE_TO_EDGE
-    })
+    @EnableFeatures({ChromeFeatureList.EDGE_TO_EDGE_BOTTOM_CHIN})
     public void testEdgeToEdgePadAdjuster_BeforeLoadHint() {
         mEdgeToEdgeSupplier.set(mEdgeToEdgeController);
         assertFalse(mEdgeToEdgeSupplier.hasObservers());
@@ -192,10 +189,7 @@
     }
 
     @Test
-    @EnableFeatures({
-        ChromeFeatureList.EDGE_TO_EDGE_BOTTOM_CHIN,
-        ChromeFeatureList.DRAW_KEY_NATIVE_EDGE_TO_EDGE
-    })
+    @EnableFeatures({ChromeFeatureList.EDGE_TO_EDGE_BOTTOM_CHIN})
     public void testEdgeToEdgePadAdjuster_AfterLoadHint() {
         mTabGroupsPane.notifyLoadHint(LoadHint.HOT);
         assertTrue(mEdgeToEdgeSupplier.hasObservers());
@@ -205,10 +199,7 @@
     }
 
     @Test
-    @EnableFeatures({
-        ChromeFeatureList.EDGE_TO_EDGE_BOTTOM_CHIN,
-        ChromeFeatureList.DRAW_KEY_NATIVE_EDGE_TO_EDGE
-    })
+    @EnableFeatures({ChromeFeatureList.EDGE_TO_EDGE_BOTTOM_CHIN})
     public void testEdgeToEdgePadAdjuster_ChangeController() {
         mTabGroupsPane.notifyLoadHint(LoadHint.HOT);
         mEdgeToEdgeSupplier.set(mEdgeToEdgeController);
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListEditorAddToGroupAction.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListEditorAddToGroupAction.java
index 1ce5b91e..b5b0633 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListEditorAddToGroupAction.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListEditorAddToGroupAction.java
@@ -48,14 +48,7 @@
                 }
 
                 @Override
-                public void didCreateGroup(
-                        List<Tab> tabs,
-                        List<Integer> tabOriginalIndex,
-                        List<Integer> tabOriginalRootId,
-                        List<Token> tabOriginalTabGroupId,
-                        @Nullable String destinationGroupTitle,
-                        int destinationGroupColorId,
-                        boolean destinationGroupTitleCollapsed) {
+                public void didCreateNewGroup(Tab destinationTab, TabGroupModelFilter filter) {
                     updateText();
                 }
             };
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherPaneCoordinatorUnitTest.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherPaneCoordinatorUnitTest.java
index 029d38f..96d1e5e4 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherPaneCoordinatorUnitTest.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherPaneCoordinatorUnitTest.java
@@ -429,10 +429,7 @@
     }
 
     @Test
-    @EnableFeatures({
-        ChromeFeatureList.DRAW_KEY_NATIVE_EDGE_TO_EDGE,
-        ChromeFeatureList.EDGE_TO_EDGE_BOTTOM_CHIN
-    })
+    @EnableFeatures({ChromeFeatureList.EDGE_TO_EDGE_BOTTOM_CHIN})
     public void testEdgeToEdgePadAdjuster() {
         int originalPadding = mCoordinator.getContainerViewModelForTesting().get(BOTTOM_PADDING);
         var padAdjuster = mCoordinator.getEdgeToEdgePadAdjusterForTesting();
@@ -453,10 +450,7 @@
     }
 
     @Test
-    @DisableFeatures({
-        ChromeFeatureList.DRAW_KEY_NATIVE_EDGE_TO_EDGE,
-        ChromeFeatureList.EDGE_TO_EDGE_BOTTOM_CHIN
-    })
+    @DisableFeatures({ChromeFeatureList.EDGE_TO_EDGE_BOTTOM_CHIN})
     public void testEdgeToEdgePadAdjuster_FeatureDisabled() {
         mEdgeToEdgeSupplier.set(mEdgeToEdgeController);
         var padAdjuster = mCoordinator.getEdgeToEdgePadAdjusterForTesting();
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/UndoGroupSnackbarController.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/UndoGroupSnackbarController.java
index dc86afd2..9967f45 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/UndoGroupSnackbarController.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/UndoGroupSnackbarController.java
@@ -31,8 +31,8 @@
 import java.util.Locale;
 
 /**
- * A controller that listens to {@link TabGroupModelFilterObserver#didCreateGroup(List, List, List)}
- * and shows a undo snackbar.
+ * A controller that listens to {@link TabGroupModelFilterObserver#showUndoGroupSnackbar} and shows
+ * a undo snackbar.
  */
 @NullMarked
 public class UndoGroupSnackbarController implements SnackbarManager.SnackbarController {
@@ -92,7 +92,7 @@
                     }
 
                     @Override
-                    public void didCreateGroup(
+                    public void showUndoGroupSnackbar(
                             List<Tab> tabs,
                             List<Integer> tabOriginalIndex,
                             List<Integer> originalRootId,
@@ -119,7 +119,7 @@
                                             destinationGroupColorId,
                                             destinationGroupTitleCollapsed));
                         }
-                        showUndoGroupSnackbar(tabUndoInfo);
+                        showUndoGroupSnackbarInternal(tabUndoInfo);
                     }
                 };
 
@@ -171,7 +171,7 @@
         mTabModelSelectorTabModelObserver.destroy();
     }
 
-    private void showUndoGroupSnackbar(List<TabUndoInfo> tabUndoInfo) {
+    private void showUndoGroupSnackbarInternal(List<TabUndoInfo> tabUndoInfo) {
         int mergedGroupSize =
                 currentFilter().getTabCountForGroup(tabUndoInfo.get(0).tab.getTabGroupId());
 
diff --git a/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/SelectableTabListEditorTest.java b/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/SelectableTabListEditorTest.java
index 6e87a9e..fc8800e3 100644
--- a/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/SelectableTabListEditorTest.java
+++ b/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/SelectableTabListEditorTest.java
@@ -1575,10 +1575,7 @@
 
     @Test
     @MediumTest
-    @Features.EnableFeatures({
-        ChromeFeatureList.EDGE_TO_EDGE_BOTTOM_CHIN,
-        ChromeFeatureList.DRAW_KEY_NATIVE_EDGE_TO_EDGE
-    })
+    @Features.EnableFeatures({ChromeFeatureList.EDGE_TO_EDGE_BOTTOM_CHIN})
     public void testEdgeToEdgePadAdjuster() {
         prepareBlankTab(2, false);
         List<Tab> tabs = getTabsInCurrentTabModel();
diff --git a/chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/tab_management/ArchivedTabsDialogCoordinatorUnitTest.java b/chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/tab_management/ArchivedTabsDialogCoordinatorUnitTest.java
index dc72b2ca..151b063 100644
--- a/chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/tab_management/ArchivedTabsDialogCoordinatorUnitTest.java
+++ b/chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/tab_management/ArchivedTabsDialogCoordinatorUnitTest.java
@@ -297,10 +297,7 @@
     }
 
     @Test
-    @EnableFeatures({
-        ChromeFeatureList.DRAW_KEY_NATIVE_EDGE_TO_EDGE,
-        ChromeFeatureList.EDGE_TO_EDGE_BOTTOM_CHIN
-    })
+    @EnableFeatures({ChromeFeatureList.EDGE_TO_EDGE_BOTTOM_CHIN})
     public void testEdgeToEdgePadAdjuster() {
         EdgeToEdgePadAdjuster padAdjuster = mCoordinator.getEdgeToEdgePadAdjusterForTesting();
         assertNotNull("Pad adjuster should be created when feature enabled.", padAdjuster);
@@ -321,10 +318,7 @@
     }
 
     @Test
-    @DisableFeatures({
-        ChromeFeatureList.DRAW_KEY_NATIVE_EDGE_TO_EDGE,
-        ChromeFeatureList.EDGE_TO_EDGE_BOTTOM_CHIN
-    })
+    @DisableFeatures({ChromeFeatureList.EDGE_TO_EDGE_BOTTOM_CHIN})
     public void testEdgeToEdgePadAdjuster_FeatureDisabled() {
         mEdgeToEdgeSupplier.set(mEdgeToEdgeController);
         var padAdjuster = mCoordinator.getEdgeToEdgePadAdjusterForTesting();
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java
index 7c2c34c2..d37b921e 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java
@@ -1395,7 +1395,7 @@
                 if (ChromeFeatureList.getFieldTrialParamByFeatureAsBoolean(
                         ChromeFeatureList.GROUP_SUGGESTION_SERVICE,
                         GroupSuggestionsPromotionCoordinator.CREATE_SUGGESTIONS_PROMOTION_UI_PARAM,
-                        true)) {
+                        false)) {
                     mGroupSuggestionsPromotionCoordinator =
                             new GroupSuggestionsPromotionCoordinator(
                                     this,
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/app/tab_activity_glue/ActivityTabWebContentsDelegateAndroid.java b/chrome/android/java/src/org/chromium/chrome/browser/app/tab_activity_glue/ActivityTabWebContentsDelegateAndroid.java
index 95b523a7..7a2ed444 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/app/tab_activity_glue/ActivityTabWebContentsDelegateAndroid.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/app/tab_activity_glue/ActivityTabWebContentsDelegateAndroid.java
@@ -236,6 +236,7 @@
         Tab tab =
                 tabCreator.createTabWithWebContents(
                         mTab,
+                        /* shouldPin= */ false,
                         webContents,
                         TabLaunchType.FROM_LONGPRESS_FOREGROUND,
                         url,
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/app/tabmodel/HeadlessTabCreator.java b/chrome/android/java/src/org/chromium/chrome/browser/app/tabmodel/HeadlessTabCreator.java
index 9748433d..3bbb98c 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/app/tabmodel/HeadlessTabCreator.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/app/tabmodel/HeadlessTabCreator.java
@@ -94,6 +94,7 @@
     @Override
     public Tab createTabWithWebContents(
             @Nullable Tab parent,
+            boolean shouldPin,
             WebContents webContents,
             @TabLaunchType int type,
             GURL url,
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/BaseCustomTabActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/BaseCustomTabActivity.java
index 861c170..d0e558b 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/BaseCustomTabActivity.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/BaseCustomTabActivity.java
@@ -157,7 +157,6 @@
     private Verifier mVerifier;
     private FullscreenManager mFullscreenManager;
     private CustomTabMinimizationManagerHolder mMinimizationManagerHolder;
-    private CustomTabFeatureOverridesManager mCustomTabFeatureOverridesManager;
     private boolean mWarmupOnDestroy;
     private TabObserverRegistrar mTabObserverRegistrar;
     private CustomTabObserver mCustomTabObserver;
@@ -376,7 +375,6 @@
                         mBackPressManager,
                         () -> getCustomTabActivityTabController(),
                         () -> getCustomTabMinimizationManagerHolder().getMinimizationManager(),
-                        () -> getCustomTabFeatureOverridesManager(),
                         () -> getCustomTabActivityNavigationController().openCurrentUrlInBrowser(),
                         getEdgeToEdgeManager(),
                         getAppHeaderCoordinator(),
@@ -1298,14 +1296,6 @@
         return mCustomTabActivityClientConnectionKeeper;
     }
 
-    private CustomTabFeatureOverridesManager getCustomTabFeatureOverridesManager() {
-        if (mCustomTabFeatureOverridesManager == null) {
-            mCustomTabFeatureOverridesManager =
-                    new CustomTabFeatureOverridesManager(getIntentDataProvider());
-        }
-        return mCustomTabFeatureOverridesManager;
-    }
-
     private CustomTabOrientationController getCustomTabOrientationController() {
         if (mCustomTabOrientationController == null) {
             mCustomTabOrientationController =
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/BaseCustomTabRootUiCoordinator.java b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/BaseCustomTabRootUiCoordinator.java
index 94c7fc05..d11bc2f 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/BaseCustomTabRootUiCoordinator.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/BaseCustomTabRootUiCoordinator.java
@@ -128,7 +128,6 @@
     private final Supplier<BrowserServicesIntentDataProvider> mIntentDataProvider;
     private final Supplier<CustomTabActivityTabController> mTabController;
     private final Supplier<CustomTabMinimizeDelegate> mMinimizeDelegateSupplier;
-    private final Supplier<CustomTabFeatureOverridesManager> mFeatureOverridesManagerSupplier;
     private final SearchActivityClient mCustomTabSearchClient;
 
     private CustomTabHeightStrategy mCustomTabHeightStrategy;
@@ -186,7 +185,6 @@
      * @param tabController Activity tab controller.
      * @param minimizeDelegateSupplier Supplies the {@link CustomTabMinimizeDelegate} used to
      *     minimize the tab.
-     * @param featureOverridesManagerSupplier Supplies the {@link CustomTabFeatureOverridesManager}.
      * @param openInBrowserRunnable Runnable opening the current tab in BrApp.
      * @param edgeToEdgeManager Manages core edge-to-edge state and logic.
      * @param desktopWindowStateManager Provides information about desktop windowing state.
@@ -226,7 +224,6 @@
             @NonNull BackPressManager backPressManager,
             @NonNull Supplier<CustomTabActivityTabController> tabController,
             @NonNull Supplier<CustomTabMinimizeDelegate> minimizeDelegateSupplier,
-            @NonNull Supplier<CustomTabFeatureOverridesManager> featureOverridesManagerSupplier,
             @NonNull Runnable openInBrowserRunnable,
             @NonNull EdgeToEdgeManager edgeToEdgeManager,
             @Nullable DesktopWindowStateManager desktopWindowStateManager,
@@ -310,7 +307,6 @@
 
         mTabController = tabController;
         mMinimizeDelegateSupplier = minimizeDelegateSupplier;
-        mFeatureOverridesManagerSupplier = featureOverridesManagerSupplier;
         mOpenInBrowserRunnable = openInBrowserRunnable;
         // TODO(crbug.com/41481778): move this RootUiCoordinator once this flag is removed.
         if (ChromeFeatureList.sCctTabModalDialog.isEnabled()) {
@@ -452,9 +448,6 @@
                 mActivity,
                 () -> mAppMenuCoordinator != null ? mAppMenuCoordinator.getAppMenuHandler() : null,
                 mIntentDataProvider.get());
-        if (ChromeFeatureList.sCctIntentFeatureOverrides.isEnabled()) {
-            toolbar.setFeatureOverridesManager(mFeatureOverridesManagerSupplier.get());
-        }
         var cpac = getContextualPageActionController();
         if (cpac != null) cpac.setButtonVisibilitySupplier(toolbar::shouldShowOptionalButton);
         View coordinator = mActivity.findViewById(R.id.coordinator);
@@ -790,8 +783,6 @@
     protected boolean supportsEdgeToEdge() {
         // Currently edge to edge only supports CCT media viewer.
         return EdgeToEdgeUtils.isEdgeToEdgeBottomChinEnabled(mActivity)
-                && EdgeToEdgeUtils.isDrawKeyNativePageToEdgeEnabled()
-                && !ChromeFeatureList.sDrawKeyNativeEdgeToEdgeDisableCctMediaViewerE2e.getValue()
                 && mIntentDataProvider.get() != null
                 && mIntentDataProvider.get().shouldEnableEmbeddedMediaExperience();
     }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabFeatureOverridesManager.java b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabFeatureOverridesManager.java
deleted file mode 100644
index 6148253..0000000
--- a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabFeatureOverridesManager.java
+++ /dev/null
@@ -1,94 +0,0 @@
-// Copyright 2024 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-package org.chromium.chrome.browser.customtabs;
-
-import android.content.Intent;
-
-import org.chromium.base.CommandLine;
-import org.chromium.base.IntentUtils;
-import org.chromium.base.Log;
-import org.chromium.base.ResettersForTesting;
-import org.chromium.chrome.browser.browserservices.intents.BrowserServicesIntentDataProvider;
-import org.chromium.chrome.browser.flags.ChromeFeatureList;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Map;
-import java.util.Set;
-
-/**
- * Class that allows the Custom Tab client to override features for a single session. Note that this
- * class is meant to be used for experimentation purposes only, and the features will be removed
- * from the `ALLOWED_FEATURES` list once they fully ship to Stable.
- */
-public class CustomTabFeatureOverridesManager {
-    private static final String TAG = "CTFeatureOvrdMgr";
-    private static final Set<String> ALLOWED_FEATURES = new HashSet<>();
-
-    private static Set<String> sAllowedFeaturesForTesting;
-
-    private Map<String, Boolean> mFeatureOverrides;
-
-    CustomTabFeatureOverridesManager(BrowserServicesIntentDataProvider intentDataProvider) {
-        if (ChromeFeatureList.sCctIntentFeatureOverrides.isEnabled()
-                && (CommandLine.getInstance().hasSwitch("cct-client-firstparty-override")
-                        || intentDataProvider.isTrustedIntent())) {
-            setUpFeatureOverrides(
-                    intentDataProvider.getIntent(),
-                    sAllowedFeaturesForTesting != null
-                            ? sAllowedFeaturesForTesting
-                            : ALLOWED_FEATURES);
-        }
-    }
-
-    /**
-     * @param feature The feature to check for an override value.
-     * @return Whether the feature is overridden and enabled, null if it's not overridden or if
-     *     overrides aren't allowed.
-     */
-    public Boolean isFeatureEnabled(String feature) {
-        if (mFeatureOverrides == null || mFeatureOverrides.isEmpty()) return null;
-        return mFeatureOverrides.get(feature);
-    }
-
-    private void setUpFeatureOverrides(Intent intent, Set<String> allowedFeatures) {
-        mFeatureOverrides = new HashMap<>();
-        ArrayList<String> enabledFeatures =
-                IntentUtils.safeGetStringArrayListExtra(
-                        intent, CustomTabIntentDataProvider.EXPERIMENTS_ENABLE);
-        ArrayList<String> disabledFeatures =
-                IntentUtils.safeGetStringArrayListExtra(
-                        intent, CustomTabIntentDataProvider.EXPERIMENTS_DISABLE);
-        if (enabledFeatures != null) {
-            for (var feature : enabledFeatures) {
-                if (!allowedFeatures.contains(feature)) {
-                    Log.e(TAG, "The feature " + feature + " is not allowed to be overridden.");
-                    continue;
-                }
-                mFeatureOverrides.put(feature, true);
-            }
-        }
-        if (disabledFeatures != null) {
-            for (var feature : disabledFeatures) {
-                if (!allowedFeatures.contains(feature)) {
-                    Log.e(TAG, "The feature " + feature + " is not allowed to be overridden.");
-                    continue;
-                }
-                if (mFeatureOverrides.containsKey(feature)) {
-                    mFeatureOverrides.put(feature, null);
-                    Log.e(TAG, "There are conflicting override values for the feature " + feature);
-                    continue;
-                }
-                mFeatureOverrides.put(feature, false);
-            }
-        }
-    }
-
-    public static void setAllowedFeaturesForTesting(Set<String> allowedFeatures) {
-        sAllowedFeaturesForTesting = allowedFeatures;
-        ResettersForTesting.register(() -> sAllowedFeaturesForTesting = null);
-    }
-}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabIntentDataProvider.java b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabIntentDataProvider.java
index 1eec23aa..012dc7b 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabIntentDataProvider.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabIntentDataProvider.java
@@ -677,8 +677,6 @@
                                 ScreenOrientation.DEFAULT));
 
         mGsaExperimentIds = IntentUtils.safeGetIntArrayExtra(intent, EXPERIMENT_IDS);
-        boolean usingDynamicFeatures =
-                CustomTabsConnection.getInstance().setupDynamicFeatures(intent);
 
         mBreakPointDp = getActivityBreakPointFromIntent(intent);
         mInitialActivityHeight = getInitialActivityHeightFromIntent(intent);
@@ -705,7 +703,7 @@
         mSideSheetRoundedCornersPosition =
                 getActivitySideSheetRoundedCornersPositionFromIntent(intent);
 
-        logCustomTabFeatures(intent, colorScheme, usingDynamicFeatures);
+        logCustomTabFeatures(intent, colorScheme);
         String packageName = getClientPackageNameFromSessionOrCallingActivity(mIntent, mSession);
         RecordHistogram.recordBooleanHistogram(
                 "CustomTabs.HasNonSpoofablePackageName", !TextUtils.isEmpty(packageName));
@@ -1071,11 +1069,8 @@
      *
      * @param intent The intent used to launch the CCT.
      * @param colorScheme The requested color scheme to use with the CCT.
-     * @param isUsingDynamicFeatures Whether the intent specified Features to dynamically enable or
-     *     disable.
      */
-    private void logCustomTabFeatures(
-            Intent intent, int colorScheme, boolean isUsingDynamicFeatures) {
+    private void logCustomTabFeatures(Intent intent, int colorScheme) {
         CustomTabsFeatureUsage featureUsage = new CustomTabsFeatureUsage();
 
         // Ordering: Log all the features ordered by CustomTabsFeature enum, when they apply.
@@ -1193,9 +1188,6 @@
             featureUsage.log(CustomTabsFeature.EXTRA_ADDITIONAL_TRUSTED_ORIGINS);
         }
         if (mEnableUrlBarHiding) featureUsage.log(CustomTabsFeature.EXTRA_ENABLE_URLBAR_HIDING);
-        if (isUsingDynamicFeatures) {
-            featureUsage.log(CustomTabsFeature.EXTRA_INTENT_FEATURE_OVERRIDES);
-        }
         if (showSideSheetMaximizeButton()) {
             featureUsage.log(CustomTabsFeature.EXTRA_ACTIVITY_SIDE_SHEET_ENABLE_MAXIMIZATION);
         }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabsConnection.java b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabsConnection.java
index b5b16c2b..b4659bf 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabsConnection.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabsConnection.java
@@ -88,7 +88,6 @@
 import org.chromium.components.embedder_support.util.UrlConstants;
 import org.chromium.components.externalauth.ExternalAuthUtils;
 import org.chromium.components.user_prefs.UserPrefs;
-import org.chromium.components.variations.SyntheticTrialAnnotationMode;
 import org.chromium.content_public.browser.BrowserStartupController;
 import org.chromium.content_public.browser.WebContents;
 import org.chromium.content_public.common.Referrer;
@@ -208,8 +207,6 @@
         "Invalid referrer for session"
     };
 
-    private static final String SYNTHETIC_FIELDTRIAL_CCT_EXPERIMENT_OVERRIDE =
-            "CCT_EXPERIMENT_OVERRIDE";
     private static CustomTabsConnection sInstance;
     private @Nullable String mTrustedPublisherUrlPackage;
 
@@ -231,13 +228,6 @@
     // |ON_RESIZED_CALLLBACK| which cares about height only.
     private int mPrevHeight;
 
-    /** Whether Dynamic Features are enabled. CCT Intents can override the feature set. */
-    private boolean mIsDynamicIntentFeatureOverridesEnabled =
-            ChromeFeatureList.sCctIntentFeatureOverrides.isEnabled();
-
-    @Nullable private List<String> mDynamicEnabledFeatures;
-    @Nullable private List<String> mDynamicDisabledFeatures;
-
     // Async tab prewarming can cause flakiness in tests when it runs after test shutdown and
     // triggers LifetimeAsserts.
     @VisibleForTesting public static boolean sSkipTabPrewarmingForTesting;
@@ -1611,109 +1601,6 @@
         return true;
     }
 
-    /** Resets dynamic experiment features that can be enabled/disabled via an Intent. */
-    @VisibleForTesting
-    void resetDynamicFeatures() {
-        mDynamicEnabledFeatures = null;
-        mDynamicDisabledFeatures = null;
-    }
-
-    /**
-     * Does setup of dynamic experiment features that can be enabled/disabled via an Intent.
-     *
-     * @param intent The {@link Intent} that is active, to be scanned for enable/disable Extras.
-     * @return Whether the setup will actually change the active feature set.
-     */
-    boolean setupDynamicFeatures(Intent intent) {
-        SessionHolder<?> session = SessionHolder.getSessionHolderFromIntent(intent);
-        if (!mIsDynamicIntentFeatureOverridesEnabled
-                || (!CustomTabIntentDataProvider.isTrustedCustomTab(intent, session)
-                        && !CommandLine.getInstance()
-                                .hasSwitch("cct-client-firstparty-override"))) {
-            return false;
-        }
-        return setupDynamicFeaturesInternal(intent);
-    }
-
-    @VisibleForTesting
-    boolean setupDynamicFeaturesInternal(Intent intent) {
-        // TODO(crbug.com/40884078) Add support for separate dynamic experiments per session!
-        // Early exits if any CCT client app has already set or cleared dynamic experiments.
-        if (mDynamicEnabledFeatures != null || mDynamicDisabledFeatures != null) return false;
-
-        ArrayList<String> enabledExperiments =
-                IntentUtils.safeGetStringArrayListExtra(
-                        intent, CustomTabIntentDataProvider.EXPERIMENTS_ENABLE);
-        ArrayList<String> disabledExperiments =
-                IntentUtils.safeGetStringArrayListExtra(
-                        intent, CustomTabIntentDataProvider.EXPERIMENTS_DISABLE);
-        if (!areExperimentsSupported(enabledExperiments, disabledExperiments)) return false;
-
-        mDynamicEnabledFeatures = enabledExperiments;
-        mDynamicDisabledFeatures = disabledExperiments;
-        if (UmaSessionStats.isMetricsServiceAvailable()) {
-            boolean isEnabling = enabledExperiments != null;
-            String groupPrefix = isEnabling ? "Enable_" : "Disable_";
-            List<String> featuresUsed = isEnabling ? enabledExperiments : disabledExperiments;
-            String groupName = groupPrefix + String.join("_", featuresUsed);
-            UmaSessionStats.registerSyntheticFieldTrial(
-                    SYNTHETIC_FIELDTRIAL_CCT_EXPERIMENT_OVERRIDE,
-                    groupName,
-                    SyntheticTrialAnnotationMode.CURRENT_LOG);
-        } else {
-            Log.w(TAG, "The Metrics Service is not available, so no synthetic field trial");
-        }
-        return true;
-    }
-
-    /**
-     * Determines whether the given enable and disable features are currently supported.
-     * @param enabledExperiments A list of Features to enable.
-     * @param disabledExperiments A list of Features to disable.
-     * @return Whether this set of Features is allowed to be overridden by an Intent.
-     */
-    @VisibleForTesting
-    boolean areExperimentsSupported(
-            List<String> enabledExperiments, List<String> disabledExperiments) {
-        return false;
-    }
-
-    // TODO(crbug.com/40274032): Remove this and other dynamic feature related methods.
-    /**
-     * Determines if the given Feature is enabled after factoring in active Intent overrides.
-     *
-     * @see #setupDynamicFeatures
-     * @param featureName The Feature to check if it's enabled.
-     * @return Whether the given Feature is effectively enabled given active overrides.
-     */
-    public boolean isDynamicFeatureEnabled(String featureName) {
-        if (mIsDynamicIntentFeatureOverridesEnabled) {
-            if (mDynamicEnabledFeatures != null && mDynamicEnabledFeatures.contains(featureName)) {
-                return true;
-            }
-            if (mDynamicDisabledFeatures != null
-                    && mDynamicDisabledFeatures.contains(featureName)) {
-                return false;
-            }
-        }
-        Log.e(TAG, "Unsupported Feature!");
-        return false;
-    }
-
-    @VisibleForTesting
-    void setIsDynamicFeaturesEnabled(boolean isDynamicFeaturesEnabled) {
-        mIsDynamicIntentFeatureOverridesEnabled = isDynamicFeaturesEnabled;
-    }
-
-    /**
-     * Returns whether the given feature is enabled with Intent overrides.
-     * @param featureName The feature to check.
-     * @return Whether the feature is enabled with Intent overrides.
-     */
-    public boolean isDynamicFeatureEnabledWithOverrides(String featureName) {
-        return mDynamicEnabledFeatures != null && mDynamicEnabledFeatures.contains(featureName);
-    }
-
     /**
      * @return The {@link Bundle} to use as extra to {@link
      *     CustomTabsCallback#onNavigationEvent(int, Bundle)}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabsFeatureUsage.java b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabsFeatureUsage.java
index c573772..86d5288 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabsFeatureUsage.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabsFeatureUsage.java
@@ -67,7 +67,6 @@
         CustomTabsFeature.EXTRA_ADDITIONAL_TRUSTED_ORIGINS,
         CustomTabsFeature.EXTRA_ENABLE_URLBAR_HIDING,
         CustomTabsFeature.EXTRA_AUTO_TRANSLATE_LANGUAGE,
-        CustomTabsFeature.EXTRA_INTENT_FEATURE_OVERRIDES,
         CustomTabsFeature.CTF_PARTIAL_SIDE_SHEET,
         CustomTabsFeature.EXTRA_ACTIVITY_SIDE_SHEET_BREAKPOINT_DP,
         CustomTabsFeature.EXTRA_INITIAL_ACTIVITY_WIDTH_PX,
@@ -138,7 +137,7 @@
         int EXTRA_ADDITIONAL_TRUSTED_ORIGINS = 40;
         int EXTRA_ENABLE_URLBAR_HIDING = 41;
         int EXTRA_AUTO_TRANSLATE_LANGUAGE = 42;
-        int EXTRA_INTENT_FEATURE_OVERRIDES = 43;
+        // int EXTRA_INTENT_FEATURE_OVERRIDES = 43; Deprecated.
         int CTF_PARTIAL_SIDE_SHEET = 44;
         int EXTRA_ACTIVITY_SIDE_SHEET_BREAKPOINT_DP = 45;
         int EXTRA_INITIAL_ACTIVITY_WIDTH_PX = 46;
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/features/toolbar/CustomTabToolbar.java b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/features/toolbar/CustomTabToolbar.java
index a636401..8a92b08 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/features/toolbar/CustomTabToolbar.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/features/toolbar/CustomTabToolbar.java
@@ -29,7 +29,6 @@
 import android.text.TextUtils;
 import android.text.style.ForegroundColorSpan;
 import android.util.AttributeSet;
-import android.util.Pair;
 import android.util.TypedValue;
 import android.view.ActionMode;
 import android.view.Gravity;
@@ -76,7 +75,6 @@
 import org.chromium.chrome.browser.browserservices.intents.BrowserServicesIntentDataProvider;
 import org.chromium.chrome.browser.browserservices.intents.BrowserServicesIntentDataProvider.CustomTabProfileType;
 import org.chromium.chrome.browser.browserservices.intents.CustomButtonParams.ButtonType;
-import org.chromium.chrome.browser.customtabs.CustomTabFeatureOverridesManager;
 import org.chromium.chrome.browser.customtabs.CustomTabIntentDataProvider.CustomTabsButtonState;
 import org.chromium.chrome.browser.customtabs.CustomTabsConnection;
 import org.chromium.chrome.browser.customtabs.features.CustomTabDimensionUtils;
@@ -183,7 +181,6 @@
     private @Nullable CustomTabCaptureStateToken mLastCustomTabCaptureStateToken;
     private final ObserverList<Callback<Integer>> mContainerVisibilityChangeObserverList =
             new ObserverList<>();
-    private @Nullable CustomTabFeatureOverridesManager mFeatureOverridesManager;
     private final boolean mIsRtl;
 
     // Whether the maximization button should be shown when it can. Set to {@code true}
@@ -329,9 +326,7 @@
         mButtonVisibilityRule.addButton(ButtonId.MENU, findViewById(R.id.menu_button), true);
         mLocationBar.onFinishInflate(this);
 
-        if (!ChromeFeatureList.sCctIntentFeatureOverrides.isEnabled()) {
-            maybeInitMinimizeButton();
-        }
+        maybeInitMinimizeButton();
     }
 
     @Override
@@ -681,14 +676,6 @@
         setMaximizeButtonVisibility();
     }
 
-    public void setFeatureOverridesManager(CustomTabFeatureOverridesManager manager) {
-        if (mFeatureOverridesManager != null) return;
-
-        mFeatureOverridesManager = manager;
-
-        maybeInitMinimizeButton();
-    }
-
     /**
      * Sets the {@link CustomTabMinimizeDelegate} to allow the toolbar to minimize the tab.
      *
@@ -1711,27 +1698,17 @@
             if (!show) return;
 
             mVariantForFallbackMenu = buttonVariant;
-            var menuInfo = getHighlightMenuInfo(buttonVariant);
-            assert menuInfo != null : "Menu item for the optional toolbar action should be found";
-            int menuId = menuInfo.first;
+            int menuId = getHighlightMenuId(buttonVariant);
+            assert menuId > 0 : "Menu item for the optional toolbar action should be found";
 
             mAppMenuHandler.get().setMenuHighlight(menuId, false);
-            View menuIcon = mMenuButton.findViewById(R.id.menu_button);
-            menuIcon.setContentDescription(
-                    getContext().getString(R.string.accessibility_custom_tab_menu_with_dot));
             if (mAppMenuObserver != null) mAppMenuHandler.get().removeObserver(mAppMenuObserver);
             mAppMenuObserver =
                     new AppMenuObserver() {
                         @Override
                         public void onMenuVisibilityChanged(boolean isVisible) {
                             // TODO(crbug.com/424807997): Do this toggling in MenuButton MVC.
-                            if (isVisible) {
-                                resetOptionalButtonState(/* resetFallbackMenu= */ false);
-                                String menuTitle = getContext().getString(menuInfo.second);
-                                int textId = R.string.accessibility_custom_tab_menu_item_highlight;
-                                String highlightedMenu = getContext().getString(textId, menuTitle);
-                                mAppMenuHandler.get().setContentDescription(highlightedMenu);
-                            }
+                            if (isVisible) resetOptionalButtonState(/* resetFallbackMenu= */ false);
                         }
 
                         @Override
@@ -1740,8 +1717,7 @@
             mAppMenuHandler.get().addObserver(mAppMenuObserver);
         }
 
-        private Pair<Integer, Integer> getHighlightMenuInfo(
-                @AdaptiveToolbarButtonVariant int buttonVariant) {
+        private int getHighlightMenuId(@AdaptiveToolbarButtonVariant int buttonVariant) {
             return switch (buttonVariant) {
                 case AdaptiveToolbarButtonVariant.PRICE_TRACKING -> {
                     // Figure out which of the two menu items (enable/disable) appears and needs
@@ -1751,18 +1727,13 @@
                             (AppMenuPropertiesDelegateImpl)
                                     mAppMenuHandler.get().getMenuPropertiesDelegate();
                     var showEnabled = appMenuDelegate.getPriceTrackingMenuItemInfo(getCurrentTab());
-                    if (showEnabled == null) yield null;
+                    if (showEnabled == null) yield -1;
                     yield showEnabled
-                            ? Pair.create(
-                                    R.id.enable_price_tracking_menu_id,
-                                    R.string.enable_price_tracking_menu_item)
-                            : Pair.create(
-                                    R.id.disable_price_tracking_menu_id,
-                                    R.string.disable_price_tracking_menu_item);
+                            ? R.id.enable_price_tracking_menu_id
+                            : R.id.disable_price_tracking_menu_id;
                 }
-                case AdaptiveToolbarButtonVariant.PRICE_INSIGHTS -> Pair.create(
-                        R.id.price_insights_menu_id, R.string.price_insights_title);
-                default -> null;
+                case AdaptiveToolbarButtonVariant.PRICE_INSIGHTS -> R.id.price_insights_menu_id;
+                default -> -1;
             };
         }
 
@@ -1791,13 +1762,7 @@
 
             // Hides the menu dot, and turns off the highlight on the fallback menu item.
             View indicator = mMenuButton.findViewById(R.id.menu_dot);
-            if (indicator.getVisibility() != View.GONE) {
-                indicator.setVisibility(View.GONE);
-                View menuIcon = mMenuButton.findViewById(R.id.menu_button);
-                menuIcon.setContentDescription(
-                        getContext().getString(R.string.accessibility_toolbar_btn_menu));
-                mAppMenuHandler.get().setContentDescription(null);
-            }
+            indicator.setVisibility(View.GONE);
             if (resetFallbackMenu) {
                 mVariantForFallbackMenu = AdaptiveToolbarButtonVariant.UNKNOWN;
             }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/firstrun/FirstRunActivityBase.java b/chrome/android/java/src/org/chromium/chrome/browser/firstrun/FirstRunActivityBase.java
index 741bf23..705e47a 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/firstrun/FirstRunActivityBase.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/firstrun/FirstRunActivityBase.java
@@ -5,16 +5,21 @@
 package org.chromium.chrome.browser.firstrun;
 
 import android.app.Activity;
+import android.app.ActivityOptions;
 import android.app.PendingIntent;
 import android.app.PendingIntent.CanceledException;
 import android.content.ActivityNotFoundException;
 import android.content.Intent;
+import android.os.Build;
 import android.os.Bundle;
+import android.view.WindowMetrics;
 
+import org.chromium.base.FeatureList;
 import org.chromium.base.IntentUtils;
 import org.chromium.base.Log;
 import org.chromium.build.annotations.NullMarked;
 import org.chromium.chrome.browser.customtabs.CustomTabsConnection;
+import org.chromium.chrome.browser.flags.ChromeFeatureList;
 import org.chromium.chrome.browser.metrics.SimpleStartupForegroundSessionDetector;
 import org.chromium.chrome.browser.metrics.UmaUtils;
 import org.chromium.chrome.browser.profiles.ProfileManagerUtils;
@@ -136,7 +141,30 @@
 
             // Use the PendingIntent to send the intent that originally launched Chrome. The intent
             // will go back to the ChromeLauncherActivity, which will route it accordingly.
-            pendingIntent.send(Activity.RESULT_OK, onFinished, null);
+            boolean isFeatureListInitialized = FeatureList.isNativeInitialized();
+            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R
+                    && isFeatureListInitialized
+                    && ChromeFeatureList.isEnabled(
+                            ChromeFeatureList.ANDROID_FIRST_RUN_LAUNCH_BOUNDS)) {
+
+                ActivityOptions options = ActivityOptions.makeBasic();
+                WindowMetrics windowMetrics =
+                        getWindow().getWindowManager().getCurrentWindowMetrics();
+                options.setLaunchBounds(windowMetrics.getBounds());
+                pendingIntent.send(
+                        this,
+                        Activity.RESULT_OK,
+                        /* intent= */ null,
+                        onFinished,
+                        /* handler= */ null,
+                        /* requiredPermission= */ null,
+                        options.toBundle());
+            } else {
+                if (!isFeatureListInitialized) {
+                    Log.w(TAG, "Pending intent sent before feature list initialized.");
+                }
+                pendingIntent.send(Activity.RESULT_OK, onFinished, /* handler= */ null);
+            }
 
             // Use fade-out animation for the transition from this activity to the original intent.
             overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out);
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ntp/IncognitoNewTabPage.java b/chrome/android/java/src/org/chromium/chrome/browser/ntp/IncognitoNewTabPage.java
index 02acc126..f9dacd6b 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ntp/IncognitoNewTabPage.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ntp/IncognitoNewTabPage.java
@@ -162,7 +162,7 @@
 
     @Override
     public boolean supportsEdgeToEdge() {
-        return !ChromeFeatureList.sDrawKeyNativeEdgeToEdgeDisableIncognitoNtpE2e.getValue();
+        return true;
     }
 
     // InvalidationAwareThumbnailProvider
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ntp/NewTabPage.java b/chrome/android/java/src/org/chromium/chrome/browser/ntp/NewTabPage.java
index 3737640..1da18af 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ntp/NewTabPage.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ntp/NewTabPage.java
@@ -1098,7 +1098,7 @@
 
     @Override
     public boolean supportsEdgeToEdge() {
-        return !ChromeFeatureList.sDrawKeyNativeEdgeToEdgeDisableNtpE2e.getValue();
+        return true;
     }
 
     @Override
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ntp/RecentTabsPage.java b/chrome/android/java/src/org/chromium/chrome/browser/ntp/RecentTabsPage.java
index 9150637..ea6fa550 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ntp/RecentTabsPage.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ntp/RecentTabsPage.java
@@ -20,7 +20,6 @@
 import org.chromium.build.annotations.Nullable;
 import org.chromium.chrome.R;
 import org.chromium.chrome.browser.browser_controls.BrowserControlsStateProvider;
-import org.chromium.chrome.browser.flags.ChromeFeatureList;
 import org.chromium.chrome.browser.native_page.ContextMenuManager;
 import org.chromium.chrome.browser.native_page.NativePageNavigationDelegate;
 import org.chromium.chrome.browser.tab_ui.InvalidationAwareThumbnailProvider;
@@ -191,7 +190,7 @@
 
     @Override
     public boolean supportsEdgeToEdge() {
-        return !ChromeFeatureList.sDrawKeyNativeEdgeToEdgeDisableRecentTabsE2e.getValue();
+        return true;
     }
 
     @Override
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/suggestions/tile/tile_edit_dialog/CustomTileEditView.java b/chrome/android/java/src/org/chromium/chrome/browser/suggestions/tile/tile_edit_dialog/CustomTileEditView.java
index 7f541c9..59d80b0 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/suggestions/tile/tile_edit_dialog/CustomTileEditView.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/suggestions/tile/tile_edit_dialog/CustomTileEditView.java
@@ -104,10 +104,10 @@
     @Override
     public void onClick(PropertyModel modelDialogModel, int buttonType) {
         if (buttonType == ModalDialogProperties.ButtonType.POSITIVE) {
-            @Nullable Editable title = mNameField.getText();
+            @Nullable Editable name = mNameField.getText();
             @Nullable Editable urlText = mUrlField.getText();
             mMediatorDelegate.onSave(
-                    (title == null) ? "" : title.toString(),
+                    (name == null) ? "" : name.toString(),
                     (urlText == null) ? "" : urlText.toString());
         } else {
             mMediatorDelegate.onCancel();
@@ -157,6 +157,8 @@
     @Override
     public void focusOnName() {
         mNameField.requestFocus();
+        @Nullable Editable name = mNameField.getText();
+        mNameField.setSelection((name == null) ? 0 : name.length());
         KeyboardUtils.showKeyboard(mNameField);
     }
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tab/DEPS b/chrome/android/java/src/org/chromium/chrome/browser/tab/DEPS
index bd57472..c6c31ad 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/tab/DEPS
+++ b/chrome/android/java/src/org/chromium/chrome/browser/tab/DEPS
@@ -1,6 +1,7 @@
 include_rules = [
   "-chrome",
   "+chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupCreationDialogManager.java",
+  "+chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabShareUtils.java",
   "+chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabUiFeatureUtilities.java",
   "+chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabUiThemeProvider.java",
   "+chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/PriceTrackingUtilities.java",
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tab/TabAndroidTestHelper.java b/chrome/android/java/src/org/chromium/chrome/browser/tab/TabAndroidTestHelper.java
index 08d5e65..4d2fea0 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/tab/TabAndroidTestHelper.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/tab/TabAndroidTestHelper.java
@@ -41,7 +41,8 @@
                 new HeadlessTabDelegateFactory(),
                 /* initiallyHidden= */ true,
                 /* tabState= */ null,
-                /* initializeRenderer= */ false);
+                /* initializeRenderer= */ false,
+                /* isPinned= */ false);
         return tab;
     }
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tab/TabArchiverImpl.java b/chrome/android/java/src/org/chromium/chrome/browser/tab/TabArchiverImpl.java
index 82a52cb..1872d90 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/tab/TabArchiverImpl.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/tab/TabArchiverImpl.java
@@ -34,6 +34,7 @@
 import org.chromium.chrome.browser.tabmodel.TabModel;
 import org.chromium.chrome.browser.tabmodel.TabModelSelector;
 import org.chromium.chrome.browser.tabmodel.TabModelUtils;
+import org.chromium.chrome.browser.tasks.tab_management.TabShareUtils;
 import org.chromium.components.tab_group_sync.LocalTabGroupId;
 import org.chromium.components.tab_group_sync.SavedTabGroup;
 import org.chromium.components.tab_group_sync.TabGroupSyncService;
@@ -547,6 +548,13 @@
             TabGroupModelFilter regularTabGroupModelFilter,
             Map<GURL, Long> tabUrlToLastActiveTimestampMap,
             Tab tab) {
+        // Do not archived shared tab groups, defined by a null collaboration ID.
+        if (TabShareUtils.getCollaborationIdOrNull(
+                        tab.getId(), regularTabGroupModelFilter.getTabModel(), mTabGroupSyncService)
+                != null) {
+            return false;
+        }
+
         List<Tab> relatedTabList = regularTabGroupModelFilter.getTabsInGroup(tab.getTabGroupId());
         for (Tab relatedTab : relatedTabList) {
             if (!isTabEligibleForArchive(tabUrlToLastActiveTimestampMap, relatedTab)) {
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tab/TabBuilder.java b/chrome/android/java/src/org/chromium/chrome/browser/tab/TabBuilder.java
index b249815..7fff269 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/tab/TabBuilder.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/tab/TabBuilder.java
@@ -37,6 +37,7 @@
     private boolean mInitializeRenderer;
     private @Nullable TabState mTabState;
     private @Nullable Callback<Tab> mPreInitializeAction;
+    private boolean mIsPinned;
 
     public TabBuilder(Profile profile) {
         mProfile = profile;
@@ -157,6 +158,11 @@
         return this;
     }
 
+    public TabBuilder setInitialPinState(boolean isPinned) {
+        mIsPinned = isPinned;
+        return this;
+    }
+
     public Tab build() {
         assert mLaunchType != null : "TabBuilder#setLaunchType() must be called.";
 
@@ -201,7 +207,8 @@
                 mDelegateFactory,
                 mInitiallyHidden,
                 mTabState,
-                mInitializeRenderer);
+                mInitializeRenderer,
+                mIsPinned);
         return tab;
     }
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tab/TabImpl.java b/chrome/android/java/src/org/chromium/chrome/browser/tab/TabImpl.java
index 2be73fab..97fcb11 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/tab/TabImpl.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/tab/TabImpl.java
@@ -1221,7 +1221,8 @@
             TabDelegateFactory delegateFactory,
             boolean initiallyHidden,
             @Nullable TabState tabState,
-            boolean initializeRenderer) {
+            boolean initializeRenderer,
+            boolean isPinned) {
         try {
             TraceEvent.begin("Tab.initialize");
 
@@ -1231,6 +1232,7 @@
 
             mTabLaunchTypeAtCreation = mLaunchType;
             mCreationState = creationState;
+            mIsPinned = isPinned;
 
             // If applicable set up for a lazy background tab load.
             mPendingLoadParams = loadUrlParams;
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/ArchivedTabCreator.java b/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/ArchivedTabCreator.java
index 1b3fc4a..4a63865f 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/ArchivedTabCreator.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/ArchivedTabCreator.java
@@ -105,6 +105,7 @@
     @Override
     public Tab createTabWithWebContents(
             @Nullable Tab parent,
+            boolean shouldPin,
             WebContents webContents,
             @TabLaunchType int type,
             GURL url,
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/ChromeTabCreator.java b/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/ChromeTabCreator.java
index f217638d..f9b6a52 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/ChromeTabCreator.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/ChromeTabCreator.java
@@ -458,6 +458,7 @@
     @Override
     public @Nullable Tab createTabWithWebContents(
             @Nullable Tab parent,
+            boolean shouldPin,
             WebContents webContents,
             @TabLaunchType int type,
             GURL url,
@@ -493,6 +494,7 @@
                                 .setWebContents(webContents)
                                 .setDelegateFactory(delegateFactory)
                                 .setInitiallyHidden(!openInForeground)
+                                .setInitialPinState(shouldPin)
                                 .build();
                 creationState = TabCreationState.FROZEN_FOR_LAZY_LOAD;
             } else {
@@ -504,6 +506,7 @@
                                 .setWebContents(webContents)
                                 .setDelegateFactory(delegateFactory)
                                 .setInitiallyHidden(!openInForeground)
+                                .setInitialPinState(shouldPin)
                                 .build();
                 creationState =
                         openInForeground
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/TabModelImpl.java b/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/TabModelImpl.java
index 12b983f..4214da10 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/TabModelImpl.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/TabModelImpl.java
@@ -323,7 +323,19 @@
                                     && type == TabLaunchType.FROM_LONGPRESS_BACKGROUND);
 
             index = mOrderController.determineInsertionIndex(type, index, tab);
-            assert index <= mTabs.size();
+            if (tab.getIsPinned()) {
+                int firstNonPinnedTabIndex = mPinnedTabReorderManager.findFirstNonPinnedTabIndex();
+                if (firstNonPinnedTabIndex == INVALID_TAB_INDEX) {
+                    // All tabs are pinned or the model is empty, next valid non-pinned index is at
+                    // the end of the list.
+                    firstNonPinnedTabIndex = mTabs.size();
+                }
+
+                // Insert in next non-pinned index if index wasn't handled in
+                // TabModelOrderController.
+                if (index == INVALID_TAB_INDEX) index = firstNonPinnedTabIndex;
+                assert index <= firstNonPinnedTabIndex;
+            }
 
             if (tab.isIncognito() != isIncognito()) {
                 throw new IllegalStateException("Attempting to open tab in wrong model");
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/TabModelJniBridge.java b/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/TabModelJniBridge.java
index 37e027335c..ef1c7c8 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/TabModelJniBridge.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/TabModelJniBridge.java
@@ -28,6 +28,7 @@
 import org.chromium.chrome.browser.multiwindow.MultiWindowUtils;
 import org.chromium.chrome.browser.profiles.Profile;
 import org.chromium.chrome.browser.tab.Tab;
+import org.chromium.chrome.browser.tab.TabId;
 import org.chromium.chrome.browser.tab.TabLaunchType;
 import org.chromium.chrome.browser.tabwindow.TabWindowManager;
 import org.chromium.content_public.browser.LoadUrlParams;
@@ -435,10 +436,14 @@
      */
     @CalledByNative
     public void duplicateTab(@JniType("TabAndroid*") Tab parentTab, WebContents webContents) {
-        // TODO(crbug.com/415351293): Copy pinned state once implemented.
+        // TODO(crbug.com/431997520): Insert tab next to parent instead of next to the other
+        // children tabs.
         getTabCreator()
                 .createTabWithWebContents(
-                        parentTab, webContents, TabLaunchType.FROM_TAB_LIST_INTERFACE);
+                        parentTab,
+                        parentTab.getIsPinned(),
+                        webContents,
+                        TabLaunchType.FROM_TAB_LIST_INTERFACE);
     }
 
     /**
@@ -490,6 +495,22 @@
         getTabUngrouper().ungroupTabs(tabs, /* trailing= */ true, /* allowDialog= */ false);
     }
 
+    @CalledByNative
+    protected void pinTab(@JniType("TabAndroid*") Tab tab) {
+        @TabId int tabId = tab.getId();
+        if (tabId == Tab.INVALID_TAB_ID) return;
+
+        pinTab(tabId);
+    }
+
+    @CalledByNative
+    protected void unpinTab(@JniType("TabAndroid*") Tab tab) {
+        @TabId int tabId = tab.getId();
+        if (tabId == Tab.INVALID_TAB_ID) return;
+
+        unpinTab(tabId);
+    }
+
     @NativeMethods
     @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
     public interface Natives {
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/ToolbarManager.java b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/ToolbarManager.java
index 853652f..fe0e516 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/ToolbarManager.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/ToolbarManager.java
@@ -1075,7 +1075,7 @@
                         this::shouldSuppressToolbarLongPress,
                         mActivityLifecycleDispatcher,
                         mWindowAndroid,
-                        () -> getUrlBarTextWithoutAutocomplete(),
+                        () -> mLocationBarModel.getUrlOfVisibleNavigationEntry(),
                         () -> getUrlBarViewRectProvider());
         OnLongClickListener onLongClickListener =
                 mToolbarLongPressMenuHandler.getOnLongClickListener();
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/app/edge_to_edge/EdgeToEdgeInstrumentationTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/app/edge_to_edge/EdgeToEdgeInstrumentationTest.java
index d329e55..8d25569d 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/app/edge_to_edge/EdgeToEdgeInstrumentationTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/app/edge_to_edge/EdgeToEdgeInstrumentationTest.java
@@ -71,7 +71,6 @@
 @MinAndroidSdkLevel(Build.VERSION_CODES.R)
 @EnableFeatures({
     ChromeFeatureList.DRAW_CUTOUT_EDGE_TO_EDGE,
-    ChromeFeatureList.DRAW_KEY_NATIVE_EDGE_TO_EDGE,
     ChromeFeatureList.EDGE_TO_EDGE_BOTTOM_CHIN
 })
 public class EdgeToEdgeInstrumentationTest {
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/app/edge_to_edge/EdgeToEdgePTTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/app/edge_to_edge/EdgeToEdgePTTest.java
index 32c0da36..a847417 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/app/edge_to_edge/EdgeToEdgePTTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/app/edge_to_edge/EdgeToEdgePTTest.java
@@ -91,7 +91,6 @@
 
     @Test
     @MediumTest
-    @EnableFeatures({ChromeFeatureList.DRAW_KEY_NATIVE_EDGE_TO_EDGE})
     public void fromNtpToRegularPage() {
         // Start the page on NTP, chin is not visible.
         var newTabPage = mCtaTestRule.startOnNtp();
@@ -116,7 +115,6 @@
 
     @Test
     @MediumTest
-    @EnableFeatures({ChromeFeatureList.DRAW_KEY_NATIVE_EDGE_TO_EDGE})
     public void fromNtpToTabSwitcher() {
         // Start the page on NTP, chin is not visible.
         var newTabPage = mCtaTestRule.startOnNtp();
@@ -140,7 +138,6 @@
 
     @Test
     @MediumTest
-    @EnableFeatures({ChromeFeatureList.DRAW_KEY_NATIVE_EDGE_TO_EDGE})
     public void fromNtpToOptInPage() {
         // Start the page on NTP, chin is not visible.
         var newTabPage = mCtaTestRule.startOnNtp();
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/tab/TabArchiverTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/tab/TabArchiverTest.java
index a51ab60..5a1a713 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/tab/TabArchiverTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/tab/TabArchiverTest.java
@@ -502,6 +502,68 @@
     @Test
     @MediumTest
     @EnableFeatures(ChromeFeatureList.ANDROID_TAB_DECLUTTER_ARCHIVE_TAB_GROUPS)
+    public void testSharedTabGroupsAreNotArchived() {
+        String syncId = "sync_id";
+        SavedTabGroup savedTabGroup = new SavedTabGroup();
+        savedTabGroup.syncId = syncId;
+        savedTabGroup.collaborationId = "collabId1";
+        SavedTabGroupTab savedTabGroupTab1 = new SavedTabGroupTab();
+        SavedTabGroupTab savedTabGroupTab2 = new SavedTabGroupTab();
+        savedTabGroup.savedTabs = Arrays.asList(savedTabGroupTab1, savedTabGroupTab2);
+        when(mTabGroupSyncService.getGroup(any(LocalTabGroupId.class))).thenReturn(savedTabGroup);
+
+        sActivityTestRule.loadUrlInNewTab(
+                sActivityTestRule.getTestServer().getURL(TEST_PATH), /* incognito= */ false);
+
+        runOnUiThreadBlocking(
+                () -> {
+                    // Set the tab to expire after 2 hour to simplify testing.
+                    mTabArchiveSettings.setArchiveTimeDeltaHours(2);
+                });
+
+        // Set the clock to 2 hour after 0.
+        doReturn(TimeUnit.HOURS.toMillis(2)).when(mClock).currentTimeMillis();
+        // Set the timestamp for the tabs to 0, it should be archived.
+        // Set the navigation timestamp for the tab to 1 to pass user active check.
+        TabImpl tab1 = ((TabImpl) mRegularTabModel.getTabAt(0));
+        tab1.setTimestampMillisForTesting(0);
+        tab1.setLastNavigationCommittedTimestampMillis(TimeUnit.HOURS.toMillis(1));
+
+        // Simulate the first tab being added to a group.
+        runOnUiThreadBlocking(
+                () -> {
+                    TabGroupModelFilter filter =
+                            mRegularTabModelSelector
+                                    .getTabGroupModelFilterProvider()
+                                    .getTabGroupModelFilter(false);
+                    filter.createSingleTabGroup(tab1);
+                });
+
+        assertEquals(2, mRegularTabModel.getCount());
+        assertEquals(0, mArchivedTabModel.getCount());
+
+        HistogramWatcher watcher =
+                HistogramWatcher.newBuilder()
+                        .expectNoRecords("Tabs.TabArchived.TabCount")
+                        .expectNoRecords("TabGroups.TabGroupDeclutter.ArchivedTabGroups")
+                        .expectNoRecords("TabGroups.TabGroupDeclutter.ArchivedTabGroupTabCount")
+                        .build();
+        // The grouped tab should not be archived.
+        runOnUiThreadBlocking(
+                () ->
+                        mTabArchiver.doArchivePass(
+                                sActivityTestRule
+                                        .getActivity()
+                                        .getTabModelSelectorSupplier()
+                                        .get()));
+        CriteriaHelper.pollUiThread(() -> 2 == mRegularTabModel.getCount());
+        assertEquals(0, mArchivedTabModel.getCount());
+        watcher.assertExpected();
+    }
+
+    @Test
+    @MediumTest
+    @EnableFeatures(ChromeFeatureList.ANDROID_TAB_DECLUTTER_ARCHIVE_TAB_GROUPS)
     public void testTabsAreNotArchived_userNotActive() {
         sActivityTestRule.loadUrlInNewTab(
                 sActivityTestRule.getTestServer().getURL(TEST_PATH), /* incognito= */ false);
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/tabmodel/TabModelImplTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/tabmodel/TabModelImplTest.java
index 16ea5c36..11469b2 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/tabmodel/TabModelImplTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/tabmodel/TabModelImplTest.java
@@ -9,7 +9,6 @@
 import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertThrows;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.times;
@@ -54,9 +53,7 @@
 import org.chromium.url.GURL;
 
 import java.util.ArrayList;
-import java.util.HashSet;
 import java.util.List;
-import java.util.Set;
 
 /** Tests for {@link TabModelImpl}. */
 @RunWith(ChromeJUnit4ClassRunner.class)
@@ -317,6 +314,125 @@
 
     @Test
     @SmallTest
+    public void testDuplicateTab_PinnedTab() {
+        String url = "https://www.chromium.org/chromium-projects/";
+        GURL gurl1 = new GURL("https://www.example.com/");
+        mPage.openNewTabFast().loadWebPageProgrammatically(url);
+        // 0:Tab0 | 1:Tab1 (tabToDuplicate)
+
+        ThreadUtils.runOnUiThreadBlocking(
+                () -> {
+                    assertEquals(2, mTabModelJni.getCount());
+                    Tab tab0 = mTabModelJni.getTabAt(/* index= */ 0);
+                    Tab tab1 = mTabModelJni.getTabAt(/* index= */ 1);
+
+                    GURL gurl0 = new GURL(url);
+                    assertEquals(gurl0, tab1.getUrl());
+                    assertNull(tab1.getTabGroupId());
+                    assertFalse(tab1.getIsPinned());
+
+                    mTabModelJni.pinTab(tab1);
+                    mTabModelJni.pinTab(tab0);
+                    assertEquals(0, mTabModelJni.indexOf(tab1));
+                    assertEquals(1, mTabModelJni.indexOf(tab0));
+                    assertTrue(tab1.getIsPinned());
+                    assertTrue(tab0.getIsPinned());
+                    // [0:Tab1 (tabToDuplicate)] | [1:Tab0]
+
+                    mTabModelJni.duplicateTabForTesting(tab1);
+                    assertEquals(3, mTabModelJni.getCount());
+                    assertEquals(0, mTabModelJni.indexOf(tab1));
+                    assertEquals(2, mTabModelJni.indexOf(tab0));
+                    // [0:Tab1 (tabToDuplicate)] | [1:Tab2 (duplicatedTab)] | [2:Tab0]
+
+                    // Assert duplicatedTab (tab2)
+                    Tab tab2 = mTabModelJni.getTabAt(/* index= */ 1);
+                    assertEquals(tab1.getId(), tab2.getParentId());
+                    assertEquals(gurl0, tab2.getUrl());
+                    assertEquals(
+                            TabLaunchType.FROM_TAB_LIST_INTERFACE,
+                            tab2.getTabLaunchTypeAtCreation());
+                    assertTrue(tab2.getIsPinned());
+                    assertNull(tab2.getTabGroupId());
+                    assertNull(tab1.getTabGroupId());
+
+                    mTabModelJni.openTabProgrammatically(gurl1, 3);
+                    assertEquals(4, mTabModelJni.getCount());
+                    Tab tab3 = mTabModelJni.getTabAt(3);
+                    assertEquals(gurl1, tab3.getUrl());
+                    // [0:Tab1] | [1:Tab2] | [2:Tab0] | 3:Tab3
+
+                    mTabModelJni.unpinTab(tab1);
+                    assertEquals(2, mTabModelJni.indexOf(tab1));
+                    // [0:Tab2] | [1:Tab0] | 2:Tab1 | 3:Tab3
+
+                    mTabModelJni.duplicateTabForTesting(tab2);
+                    assertEquals(5, mTabModelJni.getCount());
+                    assertEquals(0, mTabModelJni.indexOf(tab2));
+                    assertEquals(2, mTabModelJni.indexOf(tab0));
+                    assertEquals(3, mTabModelJni.indexOf(tab1));
+                    assertEquals(4, mTabModelJni.indexOf(tab3));
+                    // [0:Tab2 (tabToDuplicate)] | [1:Tab4 (duplicatedTab)] | [2:Tab0] | 3:Tab1
+                    // | 4:Tab3
+
+                    // Assert duplicatedTab (tab4)
+                    Tab tab4 = mTabModelJni.getTabAt(/* index= */ 1);
+                    assertEquals(tab2.getId(), tab4.getParentId());
+                    assertEquals(gurl0, tab4.getUrl());
+                    assertEquals(
+                            TabLaunchType.FROM_TAB_LIST_INTERFACE,
+                            tab2.getTabLaunchTypeAtCreation());
+                    assertTrue(tab4.getIsPinned());
+                    assertNull(tab4.getTabGroupId());
+                    assertNull(tab2.getTabGroupId());
+
+                    // Clean-up (otherwise next tests will fail)
+                    mTabModelJni.unpinTab(tab2);
+                    mTabModelJni.unpinTab(tab4);
+                    mTabModelJni.unpinTab(tab0);
+                });
+    }
+
+    @Test
+    @SmallTest
+    public void testPinUnpinTab() {
+        createTabs(2);
+
+        ThreadUtils.runOnUiThreadBlocking(
+                () -> {
+                    assertEquals(3, mTabModelJni.getCount());
+                    Tab tab0 = mTabModelJni.getTabAt(/* index= */ 0);
+                    Tab tab1 = mTabModelJni.getTabAt(/* index= */ 1);
+                    Tab tab2 = mTabModelJni.getTabAt(/* index= */ 2);
+                    assertFalse(tab0.getIsPinned());
+                    assertFalse(tab1.getIsPinned());
+                    assertFalse(tab2.getIsPinned());
+
+                    mTabModelJni.pinTab(tab1);
+                    assertTrue(tab1.getIsPinned());
+                    assertEquals(0, mTabModelJni.indexOf(tab1));
+                    assertEquals(1, mTabModelJni.indexOf(tab0));
+                    assertEquals(2, mTabModelJni.indexOf(tab2));
+
+                    mTabModelJni.pinTab(tab2);
+                    assertTrue(tab2.getIsPinned());
+                    assertEquals(0, mTabModelJni.indexOf(tab1));
+                    assertEquals(1, mTabModelJni.indexOf(tab2));
+                    assertEquals(2, mTabModelJni.indexOf(tab0));
+
+                    mTabModelJni.unpinTab(tab1);
+                    assertFalse(tab1.getIsPinned());
+                    assertEquals(0, mTabModelJni.indexOf(tab2));
+                    assertEquals(1, mTabModelJni.indexOf(tab1));
+                    assertEquals(2, mTabModelJni.indexOf(tab0));
+
+                    // Clean-up (otherwise next tests will fail)
+                    mTabModelJni.unpinTab(tab2);
+                });
+    }
+
+    @Test
+    @SmallTest
     public void testMoveTabToIndex() {
         // Programmatically set up the tab state (PT is flaky)
         createTabs(2);
@@ -1138,97 +1254,6 @@
                 });
     }
 
-    @Test
-    @SmallTest
-    @Features.EnableFeatures(ChromeFeatureList.ANDROID_TAB_HIGHLIGHTING)
-    @DisabledTest(message = "https://crbug.com/432500572")
-    public void testHighlightTabs_assertionFailsWithEmptyList() {
-        ThreadUtils.runOnUiThreadBlocking(
-                () -> {
-                    Tab tabToActivate = mTabModelJni.getTabAt(0);
-                    List<Tab> emptyList = new ArrayList<>();
-
-                    AssertionError e =
-                            assertThrows(
-                                    AssertionError.class,
-                                    () -> mTabModelJni.highlightTabs(tabToActivate, emptyList));
-                    assertEquals("The provided tab list cannot be empty.", e.getMessage());
-                });
-    }
-
-    @Test
-    @SmallTest
-    @Features.EnableFeatures(ChromeFeatureList.ANDROID_TAB_HIGHLIGHTING)
-    public void testHighlightTabs_assertionFailsWithNullTabToActivate() {
-        ThreadUtils.runOnUiThreadBlocking(
-                () -> {
-                    List<Tab> tabs = new ArrayList<>();
-                    tabs.add(mTabModelJni.getTabAt(0));
-
-                    AssertionError e =
-                            assertThrows(
-                                    AssertionError.class,
-                                    () -> mTabModelJni.highlightTabs(null, tabs));
-                    assertEquals("tabToActivate cannot be null", e.getMessage());
-                });
-    }
-
-    @Test
-    @SmallTest
-    @Features.EnableFeatures(ChromeFeatureList.ANDROID_TAB_HIGHLIGHTING)
-    public void testHighlightTabs_assertionFailsWithMismatchedTabToActivate() {
-        createTab();
-
-        ThreadUtils.runOnUiThreadBlocking(
-                () -> {
-                    Tab tab0 = mTabModelJni.getTabAt(0);
-                    Tab tabToActivate = mTabModelJni.getTabAt(1);
-
-                    // Create a list that does NOT contain the tab we intend to activate.
-                    List<Tab> listWithoutTabToActivate = new ArrayList<>();
-                    listWithoutTabToActivate.add(tab0);
-
-                    AssertionError e =
-                            assertThrows(
-                                    AssertionError.class,
-                                    () ->
-                                            mTabModelJni.highlightTabs(
-                                                    tabToActivate, listWithoutTabToActivate));
-                    assertEquals("tabToActivate not found in tab list", e.getMessage());
-                });
-    }
-
-    @Test
-    @SmallTest
-    @Features.EnableFeatures(ChromeFeatureList.ANDROID_TAB_HIGHLIGHTING)
-    public void testSetTabsMultiSelected_assertionFailsWithNoActiveTabInSet() {
-        createTabs(2);
-
-        ThreadUtils.runOnUiThreadBlocking(
-                () -> {
-                    TabModel tabModel =
-                            mActivityTestRule.getActivity().getTabModelSelector().getModel(false);
-                    // Set active tab to Tab1 (index 1)
-                    tabModel.setIndex(1, TabSelectionType.FROM_USER);
-                    assertEquals("Active tab index should be 1.", 1, tabModel.index());
-
-                    Tab tabToSelect = tabModel.getTabAt(2);
-                    Set<Integer> selection = new HashSet<>();
-                    selection.add(tabToSelect.getId());
-
-                    // Attempt to set a selection that does not include the active tab (Tab1).
-                    AssertionError e =
-                            assertThrows(
-                                    AssertionError.class,
-                                    () -> tabModel.setTabsMultiSelected(selection, true));
-
-                    assertEquals(
-                            "If the selection is not empty, the current tab must always be"
-                                    + " present within the set.",
-                            e.getMessage());
-                });
-    }
-
     private void assertMoveTabToIndex(
             int oldIndex, int newIndex, int expectedIndex, boolean movingInsideGroup) {
         Tab oldIndexTab = mTabModelJni.getTabAt(oldIndex);
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/tabmodel/TabbedModeTabPersistencePolicyTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/tabmodel/TabbedModeTabPersistencePolicyTest.java
index 100a3d8..364b7ee0 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/tabmodel/TabbedModeTabPersistencePolicyTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/tabmodel/TabbedModeTabPersistencePolicyTest.java
@@ -160,7 +160,8 @@
                                         return new GURL("https://www.google.com");
                                     }
                                 };
-                        tab.initialize(null, null, null, null, null, null, false, null, false);
+                        tab.initialize(
+                                null, null, null, null, null, null, false, null, false, false);
                         return tab;
                     }
                 };
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/undo_tab_close_snackbar/UndoBarControllerTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/undo_tab_close_snackbar/UndoBarControllerTest.java
index 0b79c70..004cb526 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/undo_tab_close_snackbar/UndoBarControllerTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/undo_tab_close_snackbar/UndoBarControllerTest.java
@@ -21,10 +21,8 @@
 import org.chromium.base.ThreadUtils;
 import org.chromium.base.test.util.Batch;
 import org.chromium.base.test.util.CommandLineFlags;
-import org.chromium.base.test.util.Features.DisableFeatures;
 import org.chromium.base.test.util.Restriction;
 import org.chromium.chrome.browser.ChromeTabbedActivity;
-import org.chromium.chrome.browser.flags.ChromeFeatureList;
 import org.chromium.chrome.browser.flags.ChromeSwitches;
 import org.chromium.chrome.browser.tab.Tab;
 import org.chromium.chrome.browser.tabmodel.TabClosureParams;
@@ -49,9 +47,6 @@
 @RunWith(ChromeJUnit4ClassRunner.class)
 @Batch(Batch.PER_CLASS)
 @CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE})
-// DRAW_KEY_NATIVE_EDGE_TO_EDGE is a cached flag that is reset between batch runs, which results
-// in breakage when trying to reset the test environment back to the original state between tests.
-@DisableFeatures(ChromeFeatureList.DRAW_KEY_NATIVE_EDGE_TO_EDGE)
 public class UndoBarControllerTest {
     @Rule
     public AutoResetCtaTransitTestRule mActivityTestRule =
diff --git a/chrome/android/junit/BUILD.gn b/chrome/android/junit/BUILD.gn
index 3d46caa..9bf879c 100644
--- a/chrome/android/junit/BUILD.gn
+++ b/chrome/android/junit/BUILD.gn
@@ -614,7 +614,6 @@
       "src/org/chromium/chrome/browser/customtabs/CustomTabAppMenuPropertiesDelegateUnitTest.java",
       "src/org/chromium/chrome/browser/customtabs/CustomTabBottomBarDelegateUnitTest.java",
       "src/org/chromium/chrome/browser/customtabs/CustomTabCookiesFetcherUnitTest.java",
-      "src/org/chromium/chrome/browser/customtabs/CustomTabFeatureOverridesManagerUnitTest.java",
       "src/org/chromium/chrome/browser/customtabs/CustomTabFileUtilsUnitTest.java",
       "src/org/chromium/chrome/browser/customtabs/CustomTabIntentDataProviderTest.java",
       "src/org/chromium/chrome/browser/customtabs/CustomTabNavigationBarControllerTest.java",
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/app/tab_activity_glue/ActivityTabWebContentsDelegateAndroidUnitTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/app/tab_activity_glue/ActivityTabWebContentsDelegateAndroidUnitTest.java
index cf58638..ad6406b7 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/app/tab_activity_glue/ActivityTabWebContentsDelegateAndroidUnitTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/app/tab_activity_glue/ActivityTabWebContentsDelegateAndroidUnitTest.java
@@ -269,7 +269,8 @@
         doReturn(Token.createRandom()).when(parentTab).getTabGroupId();
         doReturn(newTab)
                 .when(mTabCreator)
-                .createTabWithWebContents(any(), any(), anyInt(), any(), anyBoolean());
+                .createTabWithWebContents(
+                        any(), anyBoolean(), any(), anyInt(), any(), anyBoolean());
         doReturn(true).when(mTabGroupModelFilter).isTabInTabGroup(any());
         doReturn(true).when(mTabGroupModelFilter).isTabModelRestored();
         Map<WebContents, Tab> tabMap = Map.of(mWebContents, parentTab, newWebContents, newTab);
@@ -295,7 +296,8 @@
         Tab newTab = mock(Tab.class);
         doReturn(newTab)
                 .when(mTabCreator)
-                .createTabWithWebContents(any(), any(), anyInt(), any(), anyBoolean());
+                .createTabWithWebContents(
+                        any(), anyBoolean(), any(), anyInt(), any(), anyBoolean());
 
         mTabWebContentsDelegateAndroid.webContentsCreated(
                 mWebContents, 0, 0, "testFrame", new GURL("https://foo.com"), newWebContents);
@@ -307,9 +309,9 @@
                 true);
 
         verify(mTabCreator, times(1))
-                .createTabWithWebContents(any(), any(), anyInt(), any(), eq(true));
+                .createTabWithWebContents(any(), anyBoolean(), any(), anyInt(), any(), eq(true));
         verify(mTabCreator, never())
-                .createTabWithWebContents(any(), any(), anyInt(), any(), eq(false));
+                .createTabWithWebContents(any(), anyBoolean(), any(), anyInt(), any(), eq(false));
     }
 
     @Test
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/customtabs/BaseCustomTabRootUiCoordinatorUnitTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/customtabs/BaseCustomTabRootUiCoordinatorUnitTest.java
index d46c9871..49d16f5 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/customtabs/BaseCustomTabRootUiCoordinatorUnitTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/customtabs/BaseCustomTabRootUiCoordinatorUnitTest.java
@@ -146,7 +146,6 @@
     @Mock private BackPressManager mBackPressManager;
     @Mock private Supplier<CustomTabActivityTabController> mTabController;
     @Mock private Supplier<CustomTabMinimizeDelegate> mMinimizeDelegateSupplier;
-    @Mock private Supplier<CustomTabFeatureOverridesManager> mFeatureOverridesManagerSupplier;
     @Mock private Profile mProfile;
     @Mock private GoogleBottomBarCoordinator mGoogleBottomBarCoordinator;
     @Mock private ShoppingService mShoppingService;
@@ -216,7 +215,6 @@
                         mBackPressManager,
                         mTabController,
                         mMinimizeDelegateSupplier,
-                        mFeatureOverridesManagerSupplier,
                         CallbackUtils.emptyRunnable(),
                         mEdgeToEdgeManager,
                         mDesktopWindowStateManager,
@@ -280,10 +278,7 @@
 
     @Test
     @Config(sdk = 30)
-    @EnableFeatures({
-        ChromeFeatureList.DRAW_KEY_NATIVE_EDGE_TO_EDGE,
-        ChromeFeatureList.EDGE_TO_EDGE_BOTTOM_CHIN
-    })
+    @EnableFeatures({ChromeFeatureList.EDGE_TO_EDGE_BOTTOM_CHIN})
     public void testEdgeToEdgeForMediaViewer() {
         doReturn(true)
                 .when(mBrowserServicesIntentDataProvider)
@@ -293,10 +288,7 @@
 
     @Test
     @Config(sdk = 30)
-    @DisableFeatures({
-        ChromeFeatureList.DRAW_KEY_NATIVE_EDGE_TO_EDGE,
-        ChromeFeatureList.EDGE_TO_EDGE_BOTTOM_CHIN
-    })
+    @DisableFeatures({ChromeFeatureList.EDGE_TO_EDGE_BOTTOM_CHIN})
     public void testEdgeToEdgeForMediaViewer_DisabledFeatures() {
         doReturn(true)
                 .when(mBrowserServicesIntentDataProvider)
@@ -308,10 +300,7 @@
 
     @Test
     @Config(sdk = 30)
-    @EnableFeatures({
-        ChromeFeatureList.DRAW_KEY_NATIVE_EDGE_TO_EDGE,
-        ChromeFeatureList.EDGE_TO_EDGE_BOTTOM_CHIN
-    })
+    @EnableFeatures({ChromeFeatureList.EDGE_TO_EDGE_BOTTOM_CHIN})
     public void testEdgeToEdgeForMediaViewer_NotMediaViewer() {
         doReturn(false)
                 .when(mBrowserServicesIntentDataProvider)
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/customtabs/CustomTabFeatureOverridesManagerUnitTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/customtabs/CustomTabFeatureOverridesManagerUnitTest.java
deleted file mode 100644
index 6bcc6fb..0000000
--- a/chrome/android/junit/src/org/chromium/chrome/browser/customtabs/CustomTabFeatureOverridesManagerUnitTest.java
+++ /dev/null
@@ -1,135 +0,0 @@
-// Copyright 2024 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-package org.chromium.chrome.browser.customtabs;
-
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.Mockito.when;
-
-import androidx.browser.customtabs.CustomTabsIntent;
-
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.junit.MockitoJUnit;
-import org.mockito.junit.MockitoRule;
-import org.robolectric.annotation.Config;
-
-import org.chromium.base.test.BaseRobolectricTestRunner;
-import org.chromium.base.test.util.Batch;
-import org.chromium.base.test.util.Features.EnableFeatures;
-import org.chromium.chrome.browser.browserservices.intents.BrowserServicesIntentDataProvider;
-import org.chromium.chrome.browser.flags.ChromeFeatureList;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-
-/** Unit tests for {@link CustomTabFeatureOverridesManager}. */
-@RunWith(BaseRobolectricTestRunner.class)
-@Batch(Batch.UNIT_TESTS)
-@Config(manifest = Config.NONE)
-@EnableFeatures(ChromeFeatureList.CCT_INTENT_FEATURE_OVERRIDES)
-public class CustomTabFeatureOverridesManagerUnitTest {
-    @Rule public MockitoRule mTestRule = MockitoJUnit.rule();
-
-    private static final Set<String> ALLOWED_FEATURES =
-            new HashSet<>(Arrays.asList("ThisFeature", "ThatFeature"));
-
-    @Mock private BrowserServicesIntentDataProvider mIntentDataProvider;
-
-    @Before
-    public void setUp() {
-        CustomTabFeatureOverridesManager.setAllowedFeaturesForTesting(ALLOWED_FEATURES);
-    }
-
-    @Test
-    public void testOverrideEnabled() {
-        var list = new ArrayList<>(List.of("ThisFeature", "ThatFeature"));
-        setUpIntentWithFeatures(list, null);
-        var manager = new CustomTabFeatureOverridesManager(mIntentDataProvider);
-
-        assertTrue(manager.isFeatureEnabled("ThisFeature"));
-        assertTrue(manager.isFeatureEnabled("ThatFeature"));
-    }
-
-    @Test
-    public void testOverrideDisabled() {
-        var list = new ArrayList<>(List.of("ThisFeature", "ThatFeature"));
-        setUpIntentWithFeatures(null, list);
-        var manager = new CustomTabFeatureOverridesManager(mIntentDataProvider);
-
-        assertFalse(manager.isFeatureEnabled("ThisFeature"));
-        assertFalse(manager.isFeatureEnabled("ThatFeature"));
-    }
-
-    @Test
-    public void testOverrideEnabledAndDisabled() {
-        var enableList = new ArrayList<>(List.of("ThisFeature"));
-        var disableList = new ArrayList<>(List.of("ThatFeature"));
-        setUpIntentWithFeatures(enableList, disableList);
-        var manager = new CustomTabFeatureOverridesManager(mIntentDataProvider);
-
-        assertTrue(manager.isFeatureEnabled("ThisFeature"));
-        assertFalse(manager.isFeatureEnabled("ThatFeature"));
-    }
-
-    @Test
-    public void testNotOverridden() {
-        var list = new ArrayList<>(List.of("ThisFeature"));
-        setUpIntentWithFeatures(list, null);
-        var manager = new CustomTabFeatureOverridesManager(mIntentDataProvider);
-
-        assertNull(manager.isFeatureEnabled("ThatFeature"));
-    }
-
-    @Test
-    public void testOverrideNotAllowed() {
-        var list = new ArrayList<>(List.of("OtherFeature"));
-        setUpIntentWithFeatures(list, null);
-        var manager = new CustomTabFeatureOverridesManager(mIntentDataProvider);
-
-        assertNull(manager.isFeatureEnabled("OtherFeature"));
-    }
-
-    @Test
-    public void testOverrideConflict() {
-        var enableList = new ArrayList<>(List.of("ThatFeature"));
-        var disableList = new ArrayList<>(List.of("ThatFeature"));
-        setUpIntentWithFeatures(enableList, disableList);
-        var manager = new CustomTabFeatureOverridesManager(mIntentDataProvider);
-
-        assertNull(manager.isFeatureEnabled("ThatFeature"));
-    }
-
-    @Test
-    public void testNotTrustedIntent() {
-        var enableList = new ArrayList<>(List.of("ThisFeature"));
-        var disableList = new ArrayList<>(List.of("ThatFeature"));
-        setUpIntentWithFeatures(enableList, disableList);
-        when(mIntentDataProvider.isTrustedIntent()).thenReturn(false);
-        var manager = new CustomTabFeatureOverridesManager(mIntentDataProvider);
-
-        assertNull(manager.isFeatureEnabled("ThisFeature"));
-        assertNull(manager.isFeatureEnabled("ThatFeature"));
-    }
-
-    private void setUpIntentWithFeatures(ArrayList<String> enabled, ArrayList<String> disabled) {
-        var intent = new CustomTabsIntent.Builder().build().intent;
-        if (enabled != null) {
-            intent.putExtra(CustomTabIntentDataProvider.EXPERIMENTS_ENABLE, enabled);
-        }
-        if (disabled != null) {
-            intent.putExtra(CustomTabIntentDataProvider.EXPERIMENTS_DISABLE, disabled);
-        }
-        when(mIntentDataProvider.getIntent()).thenReturn(intent);
-        when(mIntentDataProvider.isTrustedIntent()).thenReturn(true);
-    }
-}
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/customtabs/CustomTabsConnectionUnitTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/customtabs/CustomTabsConnectionUnitTest.java
index bc089e8..abe2bc2 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/customtabs/CustomTabsConnectionUnitTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/customtabs/CustomTabsConnectionUnitTest.java
@@ -111,7 +111,6 @@
                 });
         CustomTabsConnection.setInstanceForTesting(null);
         mConnection = CustomTabsConnection.getInstance();
-        mConnection.setIsDynamicFeaturesEnabled(true);
         mSession = spy(CustomTabsSessionToken.createMockSessionTokenForTesting());
         mSessionHolder = new SessionHolder<>(mSession);
         when(mSession.getCallback()).thenReturn(mCallback);
@@ -128,11 +127,6 @@
     }
 
     @Test
-    public void areExperimentsSupported_NullInputs() {
-        assertFalse(mConnection.areExperimentsSupported(null, null));
-    }
-
-    @Test
     public void updateVisuals_BottomBarSwipeUpGesture() {
         var bundle = new Bundle();
         var pendingIntent = mock(PendingIntent.class);
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/customtabs/content/CustomTabActivityTabControllerUnitTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/customtabs/content/CustomTabActivityTabControllerUnitTest.java
index 0dad71f..27d9aba5 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/customtabs/content/CustomTabActivityTabControllerUnitTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/customtabs/content/CustomTabActivityTabControllerUnitTest.java
@@ -15,7 +15,6 @@
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyLong;
-import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.doAnswer;
@@ -366,7 +365,6 @@
     public void setsTabObserverRegistrarOnEngagementSignalsHandler() {
         var handler = mock(EngagementSignalsHandler.class);
         when(env.connection.getEngagementSignalsHandler(eq(env.session))).thenReturn(handler);
-        when(env.connection.isDynamicFeatureEnabled(anyString())).thenReturn(true);
         when(mPrivacyPreferencesManager.isUsageAndCrashReportingPermitted()).thenReturn(true);
         mTabController.setUpInitialTab(null);
         mTabController.finishNativeInitialization();
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/customtabs/features/toolbar/CustomTabToolbarUnitTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/customtabs/features/toolbar/CustomTabToolbarUnitTest.java
index 9257fe7..6091b35a 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/customtabs/features/toolbar/CustomTabToolbarUnitTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/customtabs/features/toolbar/CustomTabToolbarUnitTest.java
@@ -14,7 +14,6 @@
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.mock;
@@ -83,7 +82,6 @@
 import org.chromium.chrome.browser.browserservices.intents.BrowserServicesIntentDataProvider;
 import org.chromium.chrome.browser.browserservices.intents.CustomButtonParams.ButtonType;
 import org.chromium.chrome.browser.customtabs.CustomButtonParamsImpl;
-import org.chromium.chrome.browser.customtabs.CustomTabFeatureOverridesManager;
 import org.chromium.chrome.browser.customtabs.CustomTabsConnection;
 import org.chromium.chrome.browser.customtabs.features.minimizedcustomtab.CustomTabMinimizeDelegate;
 import org.chromium.chrome.browser.customtabs.features.minimizedcustomtab.MinimizedFeatureUtils;
@@ -156,7 +154,6 @@
     @Mock WindowAndroid mWindowAndroid;
     @Mock AppMenuHandler mAppMenuHandler;
     private @Mock PageInfoIphController mPageInfoIphController;
-    @Mock private CustomTabFeatureOverridesManager mFeatureOverridesManager;
     @Mock private BrowserServicesIntentDataProvider mIntentDataProvider;
     @Mock private CustomTabMinimizeDelegate mMinimizeDelegate;
     @Captor ArgumentCaptor<AppMenuObserver> mAppMenuObserverCaptor;
@@ -196,7 +193,6 @@
         when(mIntentDataProvider.getActivityType()).thenReturn(CUSTOM_TAB);
         when(mIntentDataProvider.isOptionalButtonSupported())
                 .thenReturn(ChromeFeatureList.sCctAdaptiveButton.isEnabled());
-        when(mFeatureOverridesManager.isFeatureEnabled(anyString())).thenReturn(null);
 
         mActivity = Robolectric.buildActivity(TestActivity.class).get();
         var shareButtonParams = CustomButtonParamsImpl.createShareButton(mActivity, Color.WHITE);
@@ -226,7 +222,6 @@
                 /* homeButtonDisplay= */ null);
         if (!ChromeFeatureList.sCctToolbarRefactor.isEnabled()) {
             mToolbar.initVisibilityRule(mActivity, () -> mAppMenuHandler, mIntentDataProvider);
-            mToolbar.setFeatureOverridesManager(mFeatureOverridesManager);
         }
         mLocationBar =
                 (CustomTabLocationBar)
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/feed/FeedSurfaceCoordinatorTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/feed/FeedSurfaceCoordinatorTest.java
index 86e35b1..cb0580e5 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/feed/FeedSurfaceCoordinatorTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/feed/FeedSurfaceCoordinatorTest.java
@@ -466,10 +466,7 @@
     }
 
     @Test
-    @EnableFeatures({
-        ChromeFeatureList.EDGE_TO_EDGE_BOTTOM_CHIN,
-        ChromeFeatureList.DRAW_KEY_NATIVE_EDGE_TO_EDGE
-    })
+    @EnableFeatures({ChromeFeatureList.EDGE_TO_EDGE_BOTTOM_CHIN})
     public void testEdgeToEdge() {
         mEdgeToEdgeSupplier.set(mEdgeToEdgeController);
         verify(mEdgeToEdgeController).registerAdjuster(mEdgePadAdjusterCaptor.capture());
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/ntp/IncognitoNewTabPageUnitTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/ntp/IncognitoNewTabPageUnitTest.java
index d0ff45f..19f3c75 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/ntp/IncognitoNewTabPageUnitTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/ntp/IncognitoNewTabPageUnitTest.java
@@ -7,9 +7,7 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
-import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
 
 import android.graphics.Rect;
@@ -80,7 +78,6 @@
     @Test
     @EnableFeatures({
         ChromeFeatureList.EDGE_TO_EDGE_BOTTOM_CHIN,
-        ChromeFeatureList.DRAW_KEY_NATIVE_EDGE_TO_EDGE,
     })
     public void setupEdgeToEdgeWithInsets() {
         mEdgeToEdgeSupplier.set(mEdgeToEdgeController);
@@ -96,10 +93,7 @@
     }
 
     @Test
-    @EnableFeatures({
-        ChromeFeatureList.EDGE_TO_EDGE_BOTTOM_CHIN,
-        ChromeFeatureList.DRAW_KEY_NATIVE_EDGE_TO_EDGE
-    })
+    @EnableFeatures({ChromeFeatureList.EDGE_TO_EDGE_BOTTOM_CHIN})
     public void setupEdgeToEdgeWithoutInsets() {
         mEdgeToEdgeSupplier.set(mEdgeToEdgeController);
         verify(mEdgeToEdgeController).registerAdjuster(mEdgePadAdjusterCaptor.capture());
@@ -114,11 +108,4 @@
                 "ScrollView should be clip to padding where there's no bottom insets.",
                 view.getClipToPadding());
     }
-
-    @Test
-    @DisableFeatures(ChromeFeatureList.DRAW_KEY_NATIVE_EDGE_TO_EDGE)
-    public void setupEdgeToEdgeWithFeatureDisabled() {
-        mEdgeToEdgeSupplier.set(mEdgeToEdgeController);
-        verify(mEdgeToEdgeController, never()).registerAdjuster(any());
-    }
 }
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/ntp/RecentTabsPageUnitTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/ntp/RecentTabsPageUnitTest.java
index 1f083aa..41871df 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/ntp/RecentTabsPageUnitTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/ntp/RecentTabsPageUnitTest.java
@@ -68,10 +68,7 @@
     }
 
     @Test
-    @EnableFeatures({
-        ChromeFeatureList.EDGE_TO_EDGE_BOTTOM_CHIN,
-        ChromeFeatureList.DRAW_KEY_NATIVE_EDGE_TO_EDGE
-    })
+    @EnableFeatures({ChromeFeatureList.EDGE_TO_EDGE_BOTTOM_CHIN})
     public void testEdgeToEdge() {
         assertTrue("Recent tabs do support E2E.", mRecentTabsPage.supportsEdgeToEdge());
 
@@ -92,13 +89,4 @@
         mRecentTabsPage.destroy();
         verify(mEdgeToEdgeController).unregisterAdjuster(padAdjuster);
     }
-
-    @Test
-    @EnableFeatures({
-        ChromeFeatureList.EDGE_TO_EDGE_BOTTOM_CHIN,
-        ChromeFeatureList.DRAW_KEY_NATIVE_EDGE_TO_EDGE + ":disable_recent_tabs_e2e/true"
-    })
-    public void testDisableEdgeToEdge() {
-        assertFalse("Recent tabs E2E should be turned off.", mRecentTabsPage.supportsEdgeToEdge());
-    }
 }
diff --git a/chrome/android/profiles/arm.newest.txt b/chrome/android/profiles/arm.newest.txt
index de36d845b..f6f7a7fe 100644
--- a/chrome/android/profiles/arm.newest.txt
+++ b/chrome/android/profiles/arm.newest.txt
@@ -1 +1 @@
-chromeos-chrome-arm-140.0.7299.0_pre1487377_rc-r1-merged.afdo.bz2
+chromeos-chrome-arm-140.0.7304.0_pre1488695_rc-r1-merged.afdo.bz2
diff --git a/chrome/android/profiles/newest.txt b/chrome/android/profiles/newest.txt
index 47ff480a..777377e 100644
--- a/chrome/android/profiles/newest.txt
+++ b/chrome/android/profiles/newest.txt
@@ -1 +1 @@
-chromeos-chrome-amd64-140.0.7299.0_pre1487377_rc-r1-merged.afdo.bz2
+chromeos-chrome-amd64-140.0.7304.0_pre1488695_rc-r1-merged.afdo.bz2
diff --git a/chrome/app/chrome_command_ids.h b/chrome/app/chrome_command_ids.h
index 1484b0f6..b47bfb5 100644
--- a/chrome/app/chrome_command_ids.h
+++ b/chrome/app/chrome_command_ids.h
@@ -292,6 +292,7 @@
 #define IDC_OPEN_GLIC                   40294
 #define IDC_FIND_EXTENSIONS  40295
 #define IDC_SHOW_SEARCH_TOOLS  40296
+#define IDC_SHOW_COMMENTS_SIDE_PANEL  40297
 
 // Spell-check
 // Insert any additional suggestions before _LAST; these have to be consecutive.
diff --git a/chrome/app/chromium_strings.grd b/chrome/app/chromium_strings.grd
index 749825f..6c1d8c4 100644
--- a/chrome/app/chromium_strings.grd
+++ b/chrome/app/chromium_strings.grd
@@ -1710,6 +1710,11 @@
           desc="The label for the button that on click opens a feedback form for the user to send feedback about the feature.">
           Send feedback
         </message>
+        <message name="IDS_LENS_OVERLAY_CLOSE_FEEDBACK_TOAST_ACCESSIBILITY_LABEL"
+          desc="The accessibility label for the button that closes the feedback toast."
+          is_accessibility_with_no_ui="true">
+          Close feedback dialog
+        </message>
         <message name="IDS_LENS_OVERLAY_MORE_OPTIONS_BUTTON_LABEL"
           desc="Text that is shown in the tooltip of the more options button in the Lens Overlay.">
           More options
diff --git a/chrome/app/google_chrome_strings.grd b/chrome/app/google_chrome_strings.grd
index b030efe..7f6431f 100644
--- a/chrome/app/google_chrome_strings.grd
+++ b/chrome/app/google_chrome_strings.grd
@@ -1726,6 +1726,11 @@
           desc="The label for the button that on click opens a feedback form for the user to send feedback about the feature.">
           Send feedback
         </message>
+        <message name="IDS_LENS_OVERLAY_CLOSE_FEEDBACK_TOAST_ACCESSIBILITY_LABEL"
+          desc="The accessibility label for the button that closes the feedback toast."
+          is_accessibility_with_no_ui="true">
+          Close feedback dialog
+        </message>
         <message name="IDS_LENS_OVERLAY_MORE_OPTIONS_BUTTON_LABEL"
           desc="Text that is shown in the tooltip of the more options button in the Lens Overlay.">
           More options
diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn
index 04f79ec..40660c2e 100644
--- a/chrome/browser/BUILD.gn
+++ b/chrome/browser/BUILD.gn
@@ -5284,7 +5284,6 @@
       "//chrome/browser/ash/arc/accessibility",
       "//chrome/browser/ash/arc/auth",
       "//chrome/browser/ash/arc/boot_phase_monitor",
-      "//chrome/browser/ash/arc/dlc_installer",
       "//chrome/browser/ash/arc/enterprise/cert_store",
       "//chrome/browser/ash/arc/error_notification",
       "//chrome/browser/ash/arc/fileapi",
@@ -5827,7 +5826,6 @@
       "//chrome/browser/ash/arc/enterprise/cert_store",
       "//chrome/browser/ash/arc/error_notification",
       "//chrome/browser/ash/arc/fileapi",
-      "//chrome/browser/ash/arc/dlc_installer",
       "//chrome/browser/ash/arc/input_method_manager",
       "//chrome/browser/ash/arc/input_overlay",
       "//chrome/browser/ash/arc/instance_throttle",
@@ -8712,6 +8710,7 @@
       "//chrome/browser/ui/webui/privacy_sandbox/related_website_sets:mojo_bindings",
       "//chrome/browser/ui/webui/search_engine_choice:mojo_bindings",
       "//chrome/browser/ui/webui/side_panel/bookmarks:mojo_bindings",
+      "//chrome/browser/ui/webui/side_panel/comments:mojo_bindings",
       "//chrome/browser/ui/webui/side_panel/customize_chrome:mojo_bindings",
       "//chrome/browser/ui/webui/side_panel/reading_list:mojo_bindings",
       "//chrome/browser/ui/webui/tab_search:mojo_bindings",
diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc
index a08b966c..401a8e83 100644
--- a/chrome/browser/about_flags.cc
+++ b/chrome/browser/about_flags.cc
@@ -5908,10 +5908,6 @@
     {"draw-cutout-edge-to-edge", flag_descriptions::kDrawCutoutEdgeToEdgeName,
      flag_descriptions::kDrawCutoutEdgeToEdgeDescription, kOsAndroid,
      FEATURE_VALUE_TYPE(features::kDrawCutoutEdgeToEdge)},
-    {"draw-key-native-edge-to-edge",
-     flag_descriptions::kDrawKeyNativeEdgeToEdgeName,
-     flag_descriptions::kDrawKeyNativeEdgeToEdgeDescription, kOsAndroid,
-     FEATURE_VALUE_TYPE(chrome::android::kDrawKeyNativeEdgeToEdge)},
     {"edge-to-edge-bottom-chin", flag_descriptions::kEdgeToEdgeBottomChinName,
      flag_descriptions::kEdgeToEdgeBottomChinDescription, kOsAndroid,
      FEATURE_WITH_PARAMS_VALUE_TYPE(chrome::android::kEdgeToEdgeBottomChin,
@@ -12615,15 +12611,6 @@
      FEATURE_VALUE_TYPE(device::kWebAuthnLargeBlobForICloudKeychain)},
 #endif  // BUILDFLAG(IS_MAC)
 
-    {"autofill-drop-names-with-invalid-characters-for-card-upload",
-     flag_descriptions::
-         kAutofillDropNamesWithInvalidCharactersForCardUploadName,
-     flag_descriptions::
-         kAutofillDropNamesWithInvalidCharactersForCardUploadDescription,
-     kOsAll,
-     FEATURE_VALUE_TYPE(
-         autofill::features::
-             kAutofillDropNamesWithInvalidCharactersForCardUpload)},
     {"autofill-require-cvc-for-possible-card-update",
      flag_descriptions::kAutofillRequireCvcForPossibleCardUpdateName,
      flag_descriptions::kAutofillRequireCvcForPossibleCardUpdateDescription,
diff --git a/chrome/browser/actor/BUILD.gn b/chrome/browser/actor/BUILD.gn
index 3431ebc5..1e478c90 100644
--- a/chrome/browser/actor/BUILD.gn
+++ b/chrome/browser/actor/BUILD.gn
@@ -151,6 +151,7 @@
 # This avoids dependency cycles.
 source_set("types") {
   sources = [
+    "shared_types.cc",
     "shared_types.h",
     "task_id.h",
   ]
@@ -159,6 +160,7 @@
     "//chrome/common:mojo_bindings",
     "//ui/gfx/geometry:geometry",
   ]
+  deps = [ "//third_party/abseil-cpp:absl" ]
 }
 
 source_set("variant_visitor") {
@@ -186,6 +188,7 @@
   sources = [
     "actor_keyed_service_unittest.cc",
     "aggregated_journal_unittest.cc",
+    "shared_types_unittest.cc",
     "site_policy_unittest.cc",
   ]
   deps = [
diff --git a/chrome/browser/actor/execution_engine.cc b/chrome/browser/actor/execution_engine.cc
index 709c4ef..1cb1f776 100644
--- a/chrome/browser/actor/execution_engine.cc
+++ b/chrome/browser/actor/execution_engine.cc
@@ -110,8 +110,10 @@
 }
 
 void ExecutionEngine::SetState(State state) {
-  VLOG(1) << "ExecutionEngine state change: " << StateToString(state_) << " -> "
-          << StateToString(state);
+  journal_->Log(GURL(), task_->id(), "ExecutionEngine::StateChange",
+                absl::StrFormat("State %s -> %s", StateToString(state_),
+                                StateToString(state)));
+
 #if DCHECK_IS_ON()
   static const base::NoDestructor<base::StateTransitions<State>> transitions(
       base::StateTransitions<State>({
diff --git a/chrome/browser/actor/shared_types.cc b/chrome/browser/actor/shared_types.cc
new file mode 100644
index 0000000..2e83b277
--- /dev/null
+++ b/chrome/browser/actor/shared_types.cc
@@ -0,0 +1,41 @@
+// 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/shared_types.h"
+
+#include <sstream>
+
+#include "third_party/abseil-cpp/absl/strings/str_format.h"
+
+namespace actor {
+
+std::string DebugString(const MouseClickType& t) {
+  std::ostringstream ss;
+  ss << t;
+  return ss.str();
+}
+
+std::string DebugString(const MouseClickCount& c) {
+  std::ostringstream ss;
+  ss << c;
+  return ss.str();
+}
+
+std::string DebugString(const PageTarget& t) {
+  if (std::holds_alternative<gfx::Point>(t)) {
+    return std::get<gfx::Point>(t).ToString();
+  } else if (std::holds_alternative<DomNode>(t)) {
+    const DomNode& d = std::get<DomNode>(t);
+    return absl::StrFormat("DomNode[id=%d doc_id=%s]", d.node_id,
+                           d.document_identifier);
+  }
+  NOTREACHED();
+}
+
+std::ostream& operator<<(std::ostream& os, const PageTarget& t) {
+  os << DebugString(t);
+  return os;
+}
+
+}  // namespace actor
diff --git a/chrome/browser/actor/shared_types.h b/chrome/browser/actor/shared_types.h
index 63d3cf2..228b6b8 100644
--- a/chrome/browser/actor/shared_types.h
+++ b/chrome/browser/actor/shared_types.h
@@ -30,6 +30,12 @@
 
 using PageTarget = std::variant<gfx::Point, DomNode>;
 
+std::string DebugString(const MouseClickType& t);
+std::string DebugString(const MouseClickCount& c);
+std::string DebugString(const PageTarget& t);
+
+std::ostream& operator<<(std::ostream& os, const PageTarget& t);
+
 }  // namespace actor
 
 #endif  // CHROME_BROWSER_ACTOR_SHARED_TYPES_H_
diff --git a/chrome/browser/actor/shared_types_unittest.cc b/chrome/browser/actor/shared_types_unittest.cc
new file mode 100644
index 0000000..6bc167f
--- /dev/null
+++ b/chrome/browser/actor/shared_types_unittest.cc
@@ -0,0 +1,43 @@
+// Copyright 2025 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/actor/shared_types.h"
+
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace actor {
+namespace {
+
+TEST(SharedTypesTest, MouseClickType) {
+  EXPECT_EQ(DebugString(MouseClickType::kLeft), "kLeft");
+  EXPECT_EQ(DebugString(MouseClickType::kRight), "kRight");
+
+  std::ostringstream oss;
+  oss << MouseClickType::kLeft << " -- " << MouseClickType::kRight;
+  EXPECT_EQ(oss.str(), "kLeft -- kRight");
+}
+
+TEST(SharedTypesTest, MouseClickCount) {
+  EXPECT_EQ(DebugString(MouseClickCount::kDouble), "kDouble");
+  EXPECT_EQ(DebugString(MouseClickCount::kSingle), "kSingle");
+
+  std::ostringstream ss;
+  ss << MouseClickCount::kDouble << " -- " << MouseClickCount::kSingle;
+  EXPECT_EQ(ss.str(), "kDouble -- kSingle");
+}
+
+TEST(SharedTypesTest, PageTarget) {
+  auto target1 = PageTarget(gfx::Point(9, 10));
+  auto target2 =
+      PageTarget(DomNode{.node_id = 222, .document_identifier = "foo"});
+  EXPECT_EQ(DebugString(target1), "9,10");
+  EXPECT_EQ(DebugString(target2), "DomNode[id=222 doc_id=foo]");
+
+  std::ostringstream ss;
+  ss << target1 << " -- " << target2;
+  EXPECT_EQ(ss.str(), "9,10 -- DomNode[id=222 doc_id=foo]");
+}
+
+}  // namespace
+}  // namespace actor
diff --git a/chrome/browser/actor/tools/navigate_tool.cc b/chrome/browser/actor/tools/navigate_tool.cc
index 6a044e90..04f3771 100644
--- a/chrome/browser/actor/tools/navigate_tool.cc
+++ b/chrome/browser/actor/tools/navigate_tool.cc
@@ -96,10 +96,11 @@
 }
 
 void NavigateTool::DidFinishNavigation(NavigationHandle* navigation_handle) {
-  // TODO(crbug.com/411748801): We should probably handle the case where the
-  // page navigates before it's done loading. Common with client-side redirects.
   if (pending_navigation_handle_id_ &&
       navigation_handle->GetNavigationId() == *pending_navigation_handle_id_) {
+    journal().Log(
+        url_, task_id(), "NavigateTool::DidFinishNavigation",
+        absl::StrFormat("id[%d]", navigation_handle->GetNavigationId()));
     auto result =
         navigation_handle->HasCommitted() && !navigation_handle->IsErrorPage()
             ? MakeOkResult()
@@ -113,6 +114,8 @@
 }
 
 void NavigateTool::NavigationHandleCallback(NavigationHandle& handle) {
+  journal().Log(url_, task_id(), "NavigateTool::NavigationHandleCallback",
+                absl::StrFormat("id[%d]", handle.GetNavigationId()));
   pending_navigation_handle_id_ = handle.GetNavigationId();
 }
 
diff --git a/chrome/browser/actor/tools/page_tool.cc b/chrome/browser/actor/tools/page_tool.cc
index a0d4f0c..f26a194f 100644
--- a/chrome/browser/actor/tools/page_tool.cc
+++ b/chrome/browser/actor/tools/page_tool.cc
@@ -267,6 +267,9 @@
     return MakeResult(mojom::ActionResultCode::kTabWentAway);
   }
 
+  journal().Log(JournalURL(), task_id(), "TimeOfUseValidation",
+                "TabHandle:" + base::ToString(tab->GetHandle()));
+
   RenderFrameHost* frame =
       FindTargetLocalRootFrame(request_->GetTabHandle(), request_->GetTarget());
   if (!frame) {
@@ -278,6 +281,11 @@
   observed_target_node_info_ = FindLastObservedNodeForActionTarget(
       last_observation, request_->GetTarget());
 
+  if (!observed_target_node_info_) {
+    journal().Log(JournalURL(), task_id(), "TimeOfUseValidation",
+                  "No observed target found in APC.");
+  }
+
   // Perform validation for coordinate based target only.
   // TODO(bokan): We can't perform a TOCTOU check If there's no last
   // observation. Consider what to do in this case.
diff --git a/chrome/browser/actor/tools/tool_controller.cc b/chrome/browser/actor/tools/tool_controller.cc
index 1296e20..47af61f 100644
--- a/chrome/browser/actor/tools/tool_controller.cc
+++ b/chrome/browser/actor/tools/tool_controller.cc
@@ -19,6 +19,7 @@
 #include "chrome/common/actor.mojom-forward.h"
 #include "chrome/common/actor/action_result.h"
 #include "chrome/common/chrome_features.h"
+#include "third_party/abseil-cpp/absl/strings/str_format.h"
 #include "url/gurl.h"
 
 namespace actor {
@@ -47,8 +48,10 @@
 ToolController::~ToolController() = default;
 
 void ToolController::SetState(State state) {
-  VLOG(4) << "ToolController state change: " << StateToString(state_) << " -> "
-          << StateToString(state);
+  journal_->Log(active_state_ ? active_state_->tool->JournalURL() : GURL(),
+                task_->id(), "ToolControllerStateChange",
+                absl::StrFormat("State: %s -> %s", StateToString(state_),
+                                StateToString(state)));
 #if DCHECK_IS_ON()
   static const base::NoDestructor<base::StateTransitions<State>> transitions(
       base::StateTransitions<State>({
@@ -130,6 +133,8 @@
   mojom::ActionResultPtr toctou_result =
       active_state_->tool->TimeOfUseValidation(active_state_->last_observation);
   if (!IsOk(*toctou_result)) {
+    journal_->Log(active_state_->tool->JournalURL(), task_->id(),
+                  "TOCTOU Check Failed", ToDebugString(*toctou_result));
     CompleteToolRequest(std::move(toctou_result));
     return;
   }
diff --git a/chrome/browser/actor/ui/actor_ui_state_manager.cc b/chrome/browser/actor/ui/actor_ui_state_manager.cc
index 6e40c3f..b87f827 100644
--- a/chrome/browser/actor/ui/actor_ui_state_manager.cc
+++ b/chrome/browser/actor/ui/actor_ui_state_manager.cc
@@ -12,6 +12,7 @@
 #include "chrome/browser/actor/variant_visitor.h"
 #include "chrome/browser/ui/tabs/public/tab_features.h"
 #include "chrome/common/actor/action_result.h"
+#include "chrome/common/chrome_features.h"
 #include "components/tabs/public/tab_interface.h"
 
 namespace actor::ui {
@@ -56,22 +57,16 @@
   return Visitor{
       [&manager](const StartingToActOnTab& e) -> TabUiUpdate {
         auto* tab = e.tab_handle.Get();
-        manager.RunOnUiTabController(
-            tab, base::BindOnce(
-                     [](TaskId task_id,
-                        ActorUiTabControllerInterface& tab_controller) {
-                       tab_controller.SetActiveTaskId(task_id);
-                     },
-                     e.task_id));
+        if (auto* tab_controller = manager.GetUiTabController(tab)) {
+          tab_controller->SetActiveTaskId(e.task_id);
+        }
         return TabUiUpdate{tab, GetAgentControlledUiTabState()};
       },
       [&manager](const StoppedActingOnTab& e) -> TabUiUpdate {
         auto* tab = e.tab_handle.Get();
-        manager.RunOnUiTabController(
-            tab,
-            base::BindOnce([](ActorUiTabControllerInterface& tab_controller) {
-              tab_controller.ClearActiveTaskId();
-            }));
+        if (auto* tab_controller = manager.GetUiTabController(tab)) {
+          tab_controller->ClearActiveTaskId();
+        }
         return TabUiUpdate{tab, GetCompletedUiTabState()};
       },
       [](const MouseClick& e) -> TabUiUpdate {
@@ -86,12 +81,28 @@
       }};
 }
 
+// TODO(crbug.com/424495020): Bool may be converted to a map of ui
+// components:bool depending on what controller returns.
+void OnUiChangeComplete(UiCompleteCallback complete_callback, bool result) {
+  std::move(complete_callback).Run(result ? MakeOkResult() : MakeErrorResult());
+}
+
+void LogUiChangeError(bool result) {
+  if (!result) {
+    LOG(DFATAL)
+        << "Unexpected error when trying to update actor ui components.";
+  }
+}
+
 }  // namespace
 
 ActorUiStateManager::ActorUiStateManager(ActorKeyedService& actor_service)
     : actor_service_(actor_service) {}
 ActorUiStateManager::~ActorUiStateManager() = default;
 
+// TODO(crbug.com/424495020): If the tab doesn't exist we will silently
+// fail/not send a callback in the interim until these tasks are able to
+// accept a callback.
 void ActorUiStateManager::OnActorTaskStateChange(
     TaskId task_id,
     ActorTask::State new_task_state) {
@@ -119,13 +130,10 @@
       break;
   }
   for (const auto& tab : GetTabs(task_id)) {
-    RunOnUiTabController(tab,
-                         base::BindOnce(
-                             [](const UiTabState& state,
-                                ActorUiTabControllerInterface& tab_controller) {
-                               tab_controller.OnUiTabStateChange(state);
-                             },
-                             ui_tab_state));
+    if (auto* tab_controller = GetUiTabController(tab)) {
+      tab_controller->OnUiTabStateChange(ui_tab_state,
+                                         base::BindOnce(&LogUiChangeError));
+    }
   }
 
   // Update profile scoped state change.
@@ -135,19 +143,16 @@
                      weak_factory_.GetWeakPtr()));
 }
 
-// TODO(crbug.com/424495020): If the tab doesn't exist we will silently
-// fail/not send a callback in the interim until these tasks are able to
-// accept a callback.
-void ActorUiStateManager::RunOnUiTabController(
-    tabs::TabInterface* tab,
-    ActorUiTabControllerCallback callback) {
-  if (tab) {
-    auto* tab_controller = tab->GetTabFeatures()->actor_ui_tab_controller();
-    CHECK(tab_controller);
-    std::move(callback).Run(*tab_controller);
-  } else {
+ActorUiTabControllerInterface* ActorUiStateManager::GetUiTabController(
+    tabs::TabInterface* tab) {
+  if (!tab) {
     LOG(ERROR) << "Tab does not exist.";
+    return nullptr;
   }
+  auto* tab_controller = tab->GetTabFeatures()->actor_ui_tab_controller();
+  DCHECK(tab_controller)
+      << "TabController should always exist for a valid tab.";
+  return tab_controller;
 }
 
 std::vector<tabs::TabInterface*> ActorUiStateManager::GetTabs(TaskId id) {
@@ -163,29 +168,40 @@
   return {};
 }
 
+// TODO(crbug.com/424495020): In the future when a UiEvent can modify multiple
+// scoped ui components, we can look into using BarrierClosure.
 void ActorUiStateManager::OnUiEvent(AsyncUiEvent event,
                                     UiCompleteCallback callback) {
-  const TabUiUpdate new_ui_state = std::visit(GetNewUiStateFn(*this), event);
-  // TODO(crbug.com/424495020): Return a callback from the Ui state once
-  // successful.
-  RunOnUiTabController(new_ui_state.tab,
-                       base::BindOnce(
-                           [](const UiTabState& ui_tab_state,
-                              ActorUiTabControllerInterface& tab_controller) {
-                             tab_controller.OnUiTabStateChange(ui_tab_state);
-                           },
-                           new_ui_state.ui_tab_state));
-
-  base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
-      FROM_HERE, base::BindOnce(std::move(callback), MakeOkResult()));
+  if (base::FeatureList::IsEnabled(features::kGlicActorUi)) {
+    const TabUiUpdate update = std::visit(GetNewUiStateFn(*this), event);
+    if (auto* tab_controller = GetUiTabController(update.tab)) {
+      base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
+          FROM_HERE,
+          base::BindOnce(
+              &ActorUiTabControllerInterface::OnUiTabStateChange,
+              tab_controller->GetWeakPtr(), update.ui_tab_state,
+              base::BindOnce(&OnUiChangeComplete, std::move(callback))));
+    } else {
+      base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
+          FROM_HERE,
+          base::BindOnce(std::move(callback),
+                         MakeResult(mojom::ActionResultCode::kTabWentAway)));
+    }
+  } else {
+    base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
+        FROM_HERE, base::BindOnce(std::move(callback), MakeOkResult()));
+  }
 }
 
 void ActorUiStateManager::OnUiEvent(SyncUiEvent event) {
+  if (!base::FeatureList::IsEnabled(features::kGlicActorUi)) {
+    return;
+  }
   std::visit(Visitor{[this](const StartTask& e) {
                        this->MaybeUpdateProfileScopedUiState();
                      },
-                     [this](const TaskStateChanged& ret) {
-                       this->OnActorTaskStateChange(ret.task_id, ret.state);
+                     [this](const TaskStateChanged& e) {
+                       this->OnActorTaskStateChange(e.task_id, e.state);
                      }},
              event);
 }
@@ -229,6 +245,7 @@
     state_ = new_state;
     // TODO(crbug.com/424495020): Create window controller and send new state
     // via BrowserList::GetInstance()->ForEachCurrentAndNewBrowser...
+    // then wait for a callback.
   }
 }
 
diff --git a/chrome/browser/actor/ui/actor_ui_state_manager.h b/chrome/browser/actor/ui/actor_ui_state_manager.h
index 4b391bb..bbf0d0c 100644
--- a/chrome/browser/actor/ui/actor_ui_state_manager.h
+++ b/chrome/browser/actor/ui/actor_ui_state_manager.h
@@ -19,7 +19,6 @@
 }
 
 namespace actor::ui {
-
 class ActorUiStateManager : public ActorUiStateManagerInterface {
  public:
   explicit ActorUiStateManager(ActorKeyedService& actor_service);
@@ -28,6 +27,8 @@
   // ActorUiStateManagerInterface:
   void OnUiEvent(AsyncUiEvent event, UiCompleteCallback callback) override;
   void OnUiEvent(SyncUiEvent event) override;
+  ActorUiTabControllerInterface* GetUiTabController(
+      tabs::TabInterface* tab) override;
 
 // TODO(crbug.com/424495020): Post-task icon refactor, look into removing this
 // function from AUSM.
@@ -35,8 +36,6 @@
   void OnGlicUpdateFloatyState(
       glic::GlicWindowController::State floaty_state) override;
 #endif
-  void RunOnUiTabController(tabs::TabInterface* tab,
-                            ActorUiTabControllerCallback callback) override;
 
   // Returns the tabs associated with a given task id.
   std::vector<tabs::TabInterface*> GetTabs(TaskId id);
diff --git a/chrome/browser/actor/ui/actor_ui_state_manager_interface.h b/chrome/browser/actor/ui/actor_ui_state_manager_interface.h
index 6137dfe..799dd58a 100644
--- a/chrome/browser/actor/ui/actor_ui_state_manager_interface.h
+++ b/chrome/browser/actor/ui/actor_ui_state_manager_interface.h
@@ -17,8 +17,6 @@
 
 namespace actor::ui {
 using UiCompleteCallback = base::OnceCallback<void(mojom::ActionResultPtr)>;
-using ActorUiTabControllerCallback =
-    base::OnceCallback<void(ActorUiTabControllerInterface&)>;
 
 // ExpiryPeriod from when the user completes a task and when it should no longer
 // show on the ui
@@ -47,10 +45,10 @@
   // Handles a UiEvent that must be processed synchronously.
   virtual void OnUiEvent(SyncUiEvent event) = 0;
 
-  // Runs the specified function on the ActorUiTabController if the `tab`
+  // Gets the relevant UiTabController if the `tab`
   // exists. Can be stubbed out to do nothing in tests.
-  virtual void RunOnUiTabController(tabs::TabInterface* tab,
-                                    ActorUiTabControllerCallback callback) = 0;
+  virtual ActorUiTabControllerInterface* GetUiTabController(
+      tabs::TabInterface* tab) = 0;
 
 #if BUILDFLAG(ENABLE_GLIC)
   // Called on glic window (floaty) state change.
diff --git a/chrome/browser/actor/ui/actor_ui_state_manager_unittest.cc b/chrome/browser/actor/ui/actor_ui_state_manager_unittest.cc
index 89267801..b31aff0 100644
--- a/chrome/browser/actor/ui/actor_ui_state_manager_unittest.cc
+++ b/chrome/browser/actor/ui/actor_ui_state_manager_unittest.cc
@@ -1,10 +1,10 @@
 // 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/ui/actor_ui_state_manager.h"
 
 #include "base/test/bind.h"
+#include "base/test/scoped_feature_list.h"
 #include "chrome/browser/actor/actor_keyed_service.h"
 #include "chrome/browser/actor/actor_keyed_service_factory.h"
 #include "chrome/browser/actor/execution_engine.h"
@@ -14,6 +14,7 @@
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/common/actor.mojom-forward.h"
 #include "chrome/common/actor/action_result.h"
+#include "chrome/common/chrome_features.h"
 #include "chrome/test/base/testing_browser_process.h"
 #include "chrome/test/base/testing_profile.h"
 #include "components/tabs/public/mock_tab_interface.h"
@@ -38,17 +39,23 @@
   explicit ActorUiStateManagerFake(ActorKeyedService& actor_service)
       : ActorUiStateManager(actor_service) {
     mock_tab_controller_ = std::make_unique<MockActorUiTabController>();
-    ON_CALL(*mock_tab_controller_, OnUiTabStateChange(_))
-        .WillByDefault(
-            Invoke([this](UiTabState state) { this->SetUiTabState(state); }));
+    ON_CALL(*mock_tab_controller_, OnUiTabStateChange(_, _))
+        .WillByDefault(Invoke(
+            [this](UiTabState state, base::OnceCallback<void(bool)> callback) {
+              this->SetUiTabState(state, std::move(callback));
+            }));
   }
 
-  void RunOnUiTabController(tabs::TabInterface* tab,
-                            ActorUiTabControllerCallback callback) override {
-    std::move(callback).Run(*mock_tab_controller_);
+  ActorUiTabControllerInterface* GetUiTabController(
+      tabs::TabInterface* tab) override {
+    return mock_tab_controller_.get();
   }
 
-  void SetUiTabState(UiTabState ui_tab_state) { ui_tab_state_ = ui_tab_state; }
+  void SetUiTabState(UiTabState ui_tab_state,
+                     base::OnceCallback<void(bool)> callback) {
+    ui_tab_state_ = ui_tab_state;
+    std::move(callback).Run(true);
+  }
 
   UiTabState GetUiTabState() { return ui_tab_state_; }
 
@@ -84,6 +91,9 @@
 
   // testing::Test:
   void SetUp() override {
+    scoped_feature_list_.InitWithFeatures(
+        /*enabled_features=*/{features::kGlicActorUi},
+        /*disabled_features=*/{});
     profile_ = TestingProfile::Builder()
                    .AddTestingFactory(
                        ActorKeyedServiceFactory::GetInstance(),
@@ -104,6 +114,16 @@
     return std::move(actor_keyed_service);
   }
 
+  void OnUiEventComplete(AsyncUiEvent event) {
+    base::RunLoop loop;
+    actor_ui_state_manager()->OnUiEvent(
+        event, base::BindLambdaForTesting([&](mojom::ActionResultPtr result) {
+          EXPECT_TRUE(IsOk(*result));
+          loop.Quit();
+        }));
+    loop.Run();
+  }
+
   ActorUiStateManagerFake* actor_ui_state_manager() {
     return static_cast<ActorUiStateManagerFake*>(
         ActorKeyedService::Get(profile())->GetActorUiStateManager());
@@ -123,6 +143,7 @@
  private:
   content::BrowserTaskEnvironment task_environment_;
   std::unique_ptr<TestingProfile> profile_;
+  base::test::ScopedFeatureList scoped_feature_list_;
 };
 
 TEST_F(ActorUiStateManagerTest, NoTask_ReturnsInactiveUiState) {
@@ -340,13 +361,8 @@
 class ActorUiStateManagerUiEventUiTabScopedTest
     : public ActorUiStateManagerTest {
  public:
-  // TODO(crbug.com/424495020): Once a callback is added from tabcontroller, add
-  // RunLoop impl.
-  // The state setting portion of `OnUiEvent` is synchronous and the result is
-  // set immediately. The completion callback is posted and
-  // can be ignored for now.
   void VerifyUiEvent(AsyncUiEvent event, UiTabState expected_state) {
-    actor_ui_state_manager()->OnUiEvent(event, base::DoNothing());
+    OnUiEventComplete(event);
     EXPECT_EQ(actor_ui_state_manager()->GetUiTabState(), expected_state);
   }
 
diff --git a/chrome/browser/actor/ui/actor_ui_tab_controller.cc b/chrome/browser/actor/ui/actor_ui_tab_controller.cc
index 89b821f..9970f113 100644
--- a/chrome/browser/actor/ui/actor_ui_tab_controller.cc
+++ b/chrome/browser/actor/ui/actor_ui_tab_controller.cc
@@ -4,6 +4,7 @@
 
 #include "chrome/browser/actor/ui/actor_ui_tab_controller.h"
 
+#include "base/task/single_thread_task_runner.h"
 #include "components/tabs/public/tab_interface.h"
 
 namespace actor::ui {
@@ -16,17 +17,25 @@
   tab_subscriptions_.push_back(tab.RegisterWillDeactivate(
       base::BindRepeating(&ActorUiTabController::OnTabActivationChanged,
                           weak_factory_.GetWeakPtr(), /*is_activated=*/false)));
+  tab_subscriptions_.push_back(tab_->RegisterWillDetach(base::BindRepeating(
+      &ActorUiTabController::OnTabWillDetach, weak_factory_.GetWeakPtr())));
+  tab_subscriptions_.push_back(tab_->RegisterDidInsert(base::BindRepeating(
+      &ActorUiTabController::OnTabDidInsert, weak_factory_.GetWeakPtr())));
 }
 
 ActorUiTabController::~ActorUiTabController() = default;
-
-void ActorUiTabController::OnUiTabStateChange(const UiTabState& ui_tab_state) {
+void ActorUiTabController::OnUiTabStateChange(const UiTabState& ui_tab_state,
+                                              UiResultCallback callback) {
   // TODO(crbug.com/425952887): Implement this function.
   if (current_ui_tab_state_ != ui_tab_state) {
     // TODO(crbug.com/428216197): Only notify relevant UI components on change.
     current_ui_tab_state_ = ui_tab_state;
     NotifyTabScopedUiComponents(ui_tab_state, tab_->IsActivated());
   }
+  // TODO(crbug.com/425952887): Change this once ui components are implemented,
+  // for now always return true.
+  base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
+      FROM_HERE, base::BindOnce(std::move(callback), true));
 }
 
 void ActorUiTabController::SetActiveTaskId(TaskId task_id) {
@@ -47,8 +56,22 @@
 }
 
 void ActorUiTabController::OnTabActivationChanged(bool is_activated,
-                                                  tabs::TabInterface* tab) {
+                                                  TabInterface* tab) {
   NotifyTabScopedUiComponents(current_ui_tab_state_, is_activated);
 }
 
+void ActorUiTabController::OnTabWillDetach(TabInterface* tab,
+                                           TabInterface::DetachReason reason) {
+  // TODO(crbug.com/422540636): Implement.
+}
+
+void ActorUiTabController::OnTabDidInsert(TabInterface* tab) {
+  // TODO(crbug.com/422540636): Implement.
+}
+
+base::WeakPtr<ActorUiTabControllerInterface>
+ActorUiTabController::GetWeakPtr() {
+  return weak_factory_.GetWeakPtr();
+}
+
 }  // namespace actor::ui
diff --git a/chrome/browser/actor/ui/actor_ui_tab_controller.h b/chrome/browser/actor/ui/actor_ui_tab_controller.h
index 9358572..e72cbb9 100644
--- a/chrome/browser/actor/ui/actor_ui_tab_controller.h
+++ b/chrome/browser/actor/ui/actor_ui_tab_controller.h
@@ -8,10 +8,7 @@
 #include "base/callback_list.h"
 #include "base/memory/raw_ref.h"
 #include "chrome/browser/actor/ui/actor_ui_tab_controller_interface.h"
-
-namespace tabs {
-class TabInterface;
-}  // namespace tabs
+#include "components/tabs/public/tab_interface.h"
 
 namespace actor::ui {
 
@@ -21,17 +18,24 @@
   ~ActorUiTabController() override;
 
   // ActorUiTabControllerInterface:
-  void OnUiTabStateChange(const UiTabState& ui_tab_state) override;
+  void OnUiTabStateChange(const UiTabState& ui_tab_state,
+                          UiResultCallback callback) override;
   void SetActiveTaskId(TaskId task_id) override;
   void ClearActiveTaskId() override;
+  base::WeakPtr<ActorUiTabControllerInterface> GetWeakPtr() override;
 
  private:
   // Notifies tab scoped ui components that their state has changed.
   void NotifyTabScopedUiComponents(const UiTabState& ui_tab_state,
                                    bool tab_activated);
   // Tab subscriptions:
-  // Called when this `tab_`'s activation status changes.
+  // Called when this tab's activation status changes.
   void OnTabActivationChanged(bool is_activated, tabs::TabInterface* tab);
+  // Called when the tab is detached.
+  void OnTabWillDetach(tabs::TabInterface* tab,
+                       tabs::TabInterface::DetachReason reason);
+  // Called when the tab is inserted.
+  void OnTabDidInsert(tabs::TabInterface* tab);
 
   // Holds subscriptions for TabInterface callbacks.
   std::vector<base::CallbackListSubscription> tab_subscriptions_;
diff --git a/chrome/browser/actor/ui/actor_ui_tab_controller_interface.h b/chrome/browser/actor/ui/actor_ui_tab_controller_interface.h
index 589523d..48ac9d5 100644
--- a/chrome/browser/actor/ui/actor_ui_tab_controller_interface.h
+++ b/chrome/browser/actor/ui/actor_ui_tab_controller_interface.h
@@ -10,6 +10,7 @@
 #include "chrome/browser/actor/ui/states/handoff_button_state.h"
 
 namespace actor::ui {
+using UiResultCallback = base::OnceCallback<void(bool)>;
 
 struct UiTabState {
   bool operator==(const UiTabState& other) const = default;
@@ -22,7 +23,8 @@
   virtual ~ActorUiTabControllerInterface() = default;
 
   // Called whenever the UiTabState changes.
-  virtual void OnUiTabStateChange(const UiTabState& ui_tab_state) = 0;
+  virtual void OnUiTabStateChange(const UiTabState& ui_tab_state,
+                                  UiResultCallback callback) = 0;
 
   // Sets the last active task id actuating on this tab.
   // TODO(crbug.com/425952887): At most one task should be acting on a tab at
@@ -31,6 +33,7 @@
   virtual void SetActiveTaskId(TaskId task_id) = 0;
   // Clears the last active task id actuating on this tab.
   virtual void ClearActiveTaskId() = 0;
+  virtual base::WeakPtr<ActorUiTabControllerInterface> GetWeakPtr() = 0;
 };
 
 }  // namespace actor::ui
diff --git a/chrome/browser/actor/ui/mock_actor_ui_state_manager.h b/chrome/browser/actor/ui/mock_actor_ui_state_manager.h
index 35c01d4..61d2785 100644
--- a/chrome/browser/actor/ui/mock_actor_ui_state_manager.h
+++ b/chrome/browser/actor/ui/mock_actor_ui_state_manager.h
@@ -31,9 +31,9 @@
               (AsyncUiEvent event, UiCompleteCallback callback),
               (override));
   MOCK_METHOD(void, OnUiEvent, (SyncUiEvent event), (override));
-  MOCK_METHOD(void,
-              RunOnUiTabController,
-              (tabs::TabInterface * tab, ActorUiTabControllerCallback callback),
+  MOCK_METHOD(ActorUiTabControllerInterface*,
+              GetUiTabController,
+              (tabs::TabInterface * tab),
               (override));
 
 #if BUILDFLAG(ENABLE_GLIC)
diff --git a/chrome/browser/actor/ui/mock_actor_ui_tab_controller.cc b/chrome/browser/actor/ui/mock_actor_ui_tab_controller.cc
index 1639e9e5..29309cc 100644
--- a/chrome/browser/actor/ui/mock_actor_ui_tab_controller.cc
+++ b/chrome/browser/actor/ui/mock_actor_ui_tab_controller.cc
@@ -6,7 +6,11 @@
 
 namespace actor::ui {
 
-MockActorUiTabController::MockActorUiTabController() = default;
+MockActorUiTabController::MockActorUiTabController() {
+  ON_CALL(*this, GetWeakPtr())
+      .WillByDefault(testing::Return(weak_factory_.GetWeakPtr()));
+}
+
 MockActorUiTabController::~MockActorUiTabController() = default;
 
 }  // namespace actor::ui
diff --git a/chrome/browser/actor/ui/mock_actor_ui_tab_controller.h b/chrome/browser/actor/ui/mock_actor_ui_tab_controller.h
index 0e906970..247053b0 100644
--- a/chrome/browser/actor/ui/mock_actor_ui_tab_controller.h
+++ b/chrome/browser/actor/ui/mock_actor_ui_tab_controller.h
@@ -17,12 +17,20 @@
 
   MOCK_METHOD(void,
               OnUiTabStateChange,
-              (const UiTabState& ui_tab_state),
+              (const UiTabState& ui_tab_state, UiResultCallback callback),
               (override));
 
   MOCK_METHOD(void, SetActiveTaskId, (TaskId task_id), (override));
 
   MOCK_METHOD(void, ClearActiveTaskId, (), (override));
+
+  MOCK_METHOD(base::WeakPtr<ActorUiTabControllerInterface>,
+              GetWeakPtr,
+              (),
+              (override));
+
+ private:
+  base::WeakPtrFactory<MockActorUiTabController> weak_factory_{this};
 };
 
 }  // namespace actor::ui
diff --git a/chrome/browser/actor/ui/ui_event.h b/chrome/browser/actor/ui/ui_event.h
index 056af77..e05a0ab 100644
--- a/chrome/browser/actor/ui/ui_event.h
+++ b/chrome/browser/actor/ui/ui_event.h
@@ -14,8 +14,7 @@
 #include "components/tabs/public/tab_interface.h"
 
 namespace actor::ui {
-// STATUS: Dispatched on first action from a task.  Will be refactored to
-// dispatch at a different point in the actuation flow and from async to sync.
+// STATUS: Dispatched when ActorTask state changes from Created to Acting.
 struct StartTask {
   actor::TaskId task_id;
 
@@ -34,8 +33,7 @@
   ~TaskStateChanged();
 };
 
-// STATUS: Dispatched on first action from a task.  Will be refactored to
-// dispatch at a different point in the actuation flow and from async to sync.
+// STATUS: Dispatched when a tab is added to ActorTask.
 struct StartingToActOnTab {
   tabs::TabInterface::Handle tab_handle;
   actor::TaskId task_id;
@@ -45,8 +43,7 @@
   ~StartingToActOnTab();
 };
 
-// STATUS: Not yet dispatched anywhere.  Will be refactored to dispatch at a
-// different point in the actuation flow and from async to sync.
+// STATUS: Not yet dispatched anywhere.
 struct StoppedActingOnTab {
   tabs::TabInterface::Handle tab_handle;
 
diff --git a/chrome/browser/actor/ui/ui_event_debugstring.cc b/chrome/browser/actor/ui/ui_event_debugstring.cc
index 7882d3c..a03d3520 100644
--- a/chrome/browser/actor/ui/ui_event_debugstring.cc
+++ b/chrome/browser/actor/ui/ui_event_debugstring.cc
@@ -8,44 +8,12 @@
 #include <variant>
 
 #include "base/strings/strcat.h"
+#include "chrome/browser/actor/shared_types.h"
 #include "chrome/browser/actor/variant_visitor.h"
 #include "third_party/abseil-cpp/absl/strings/str_format.h"
 
 namespace actor::ui {
-
 namespace {
-std::string_view DebugString(const MouseClickType& t) {
-  switch (t) {
-    case MouseClickType::kLeft:
-      return "LeftClick";
-    case MouseClickType::kRight:
-      return "RightClick";
-    default:
-      NOTREACHED();
-  }
-}
-
-std::string_view DebugString(const MouseClickCount& c) {
-  switch (c) {
-    case MouseClickCount::kSingle:
-      return "SingleClick";
-    case MouseClickCount::kDouble:
-      return "DoubleClick";
-    default:
-      NOTREACHED();
-  }
-}
-
-std::string DebugString(const PageTarget& t) {
-  if (std::holds_alternative<gfx::Point>(t)) {
-    return std::get<gfx::Point>(t).ToString();
-  } else if (std::holds_alternative<DomNode>(t)) {
-    const DomNode& d = std::get<DomNode>(t);
-    return absl::StrFormat("DomNode[id=%d doc_id=%s]", d.node_id,
-                           d.document_identifier);
-  }
-  NOTREACHED();
-}
 
 constexpr Visitor UiEventToDebugStringFn{
     [](const StartTask& e) -> std::string {
@@ -65,8 +33,8 @@
     },
     [](const MouseClick& e) -> std::string {
       return absl::StrFormat("MouseClick[type=%s, count=%s]",
-                             DebugString(e.click_type),
-                             DebugString(e.click_count));
+                             actor::DebugString(e.click_type),
+                             actor::DebugString(e.click_count));
     },
     [](const MouseMove& e) -> std::string {
       return absl::StrFormat("MouseMove[target=%s]", DebugString(e.target));
diff --git a/chrome/browser/actor/ui/ui_event_debugstring_unittest.cc b/chrome/browser/actor/ui/ui_event_debugstring_unittest.cc
index 17df446..c18488d 100644
--- a/chrome/browser/actor/ui/ui_event_debugstring_unittest.cc
+++ b/chrome/browser/actor/ui/ui_event_debugstring_unittest.cc
@@ -68,10 +68,10 @@
 TEST_F(UiEventDebugStringTest, MouseClick) {
   EXPECT_EQ(DebugString(UiEvent(MouseClick(Handle(), MouseClickType::kLeft,
                                            MouseClickCount::kSingle))),
-            "MouseClick[type=LeftClick, count=SingleClick]");
+            "MouseClick[type=kLeft, count=kSingle]");
   EXPECT_EQ(DebugString(AsyncUiEvent(MouseClick(
                 Handle(), MouseClickType::kRight, MouseClickCount::kDouble))),
-            "MouseClick[type=RightClick, count=DoubleClick]");
+            "MouseClick[type=kRight, count=kDouble]");
 }
 
 }  // namespace
diff --git a/chrome/browser/ash/arc/BUILD.gn b/chrome/browser/ash/arc/BUILD.gn
index 9b1c20ef..f4dd2aac 100644
--- a/chrome/browser/ash/arc/BUILD.gn
+++ b/chrome/browser/ash/arc/BUILD.gn
@@ -45,7 +45,6 @@
     "//chrome/browser/ash/arc/app_shortcuts",
     "//chrome/browser/ash/arc/auth",
     "//chrome/browser/ash/arc/boot_phase_monitor",
-    "//chrome/browser/ash/arc/dlc_installer",
     "//chrome/browser/ash/arc/enterprise",
     "//chrome/browser/ash/arc/extensions",
     "//chrome/browser/ash/arc/fileapi",
diff --git a/chrome/browser/ash/arc/dlc_installer/BUILD.gn b/chrome/browser/ash/arc/dlc_installer/BUILD.gn
deleted file mode 100644
index caccf8a..0000000
--- a/chrome/browser/ash/arc/dlc_installer/BUILD.gn
+++ /dev/null
@@ -1,24 +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.
-
-assert(is_chromeos)
-
-static_library("dlc_installer") {
-  sources = [
-    "arc_dlc_install_notification_delegate_impl.cc",
-    "arc_dlc_install_notification_delegate_impl.h",
-    "arc_dlc_notification_manager_factory_impl.cc",
-    "arc_dlc_notification_manager_factory_impl.h",
-  ]
-  public_deps = [ "//chrome/browser:browser_public_dependencies" ]
-  deps = [
-    "//base",
-    "//chrome/browser/notifications",
-    "//chrome/browser/profiles:profile",
-    "//chromeos/ash/components/browser_context_helper",
-    "//chromeos/ash/experiences/arc",
-    "//components/account_id",
-    "//ui/message_center",
-  ]
-}
diff --git a/chrome/browser/ash/arc/dlc_installer/arc_dlc_install_notification_delegate_impl.cc b/chrome/browser/ash/arc/dlc_installer/arc_dlc_install_notification_delegate_impl.cc
deleted file mode 100644
index 5652abd..0000000
--- a/chrome/browser/ash/arc/dlc_installer/arc_dlc_install_notification_delegate_impl.cc
+++ /dev/null
@@ -1,33 +0,0 @@
-// Copyright 2024 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "chrome/browser/ash/arc/dlc_installer/arc_dlc_install_notification_delegate_impl.h"
-
-#include <memory>
-
-#include "base/check.h"
-#include "chrome/browser/notifications/notification_display_service.h"
-#include "chrome/browser/notifications/notification_display_service_factory.h"
-#include "chrome/browser/profiles/profile.h"
-#include "ui/message_center/public/cpp/notification.h"
-
-namespace arc {
-
-ArcDlcInstallNotificationManagerDelegateImpl::
-    ArcDlcInstallNotificationManagerDelegateImpl(Profile* profile)
-    : profile_(profile) {}
-
-ArcDlcInstallNotificationManagerDelegateImpl::
-    ~ArcDlcInstallNotificationManagerDelegateImpl() = default;
-
-void ArcDlcInstallNotificationManagerDelegateImpl::DisplayNotification(
-    const message_center::Notification& notification) {
-  auto* notification_service =
-      NotificationDisplayServiceFactory::GetForProfile(profile_);
-  CHECK(notification_service);
-  notification_service->Display(NotificationHandler::Type::TRANSIENT,
-                                notification, /*metadata=*/nullptr);
-}
-
-}  // namespace arc
diff --git a/chrome/browser/ash/arc/dlc_installer/arc_dlc_install_notification_delegate_impl.h b/chrome/browser/ash/arc/dlc_installer/arc_dlc_install_notification_delegate_impl.h
deleted file mode 100644
index c4f2ce0..0000000
--- a/chrome/browser/ash/arc/dlc_installer/arc_dlc_install_notification_delegate_impl.h
+++ /dev/null
@@ -1,39 +0,0 @@
-// Copyright 2024 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef CHROME_BROWSER_ASH_ARC_DLC_INSTALLER_ARC_DLC_INSTALL_NOTIFICATION_DELEGATE_IMPL_H_
-#define CHROME_BROWSER_ASH_ARC_DLC_INSTALLER_ARC_DLC_INSTALL_NOTIFICATION_DELEGATE_IMPL_H_
-
-#include "base/memory/raw_ptr.h"
-#include "chromeos/ash/experiences/arc/dlc_installer/arc_dlc_install_notification_manager.h"
-#include "ui/message_center/public/cpp/notification.h"
-
-class Profile;
-
-namespace arc {
-
-// Implements the delegate interface for managing notifications related
-// to ARC DLC installation. Responsible for displaying notifications
-// in the Chrome OS environment.
-class ArcDlcInstallNotificationManagerDelegateImpl
-    : public ArcDlcInstallNotificationManager::Delegate {
- public:
-  // Constructs a delegate for managing ARC DLC installation notifications.
-  explicit ArcDlcInstallNotificationManagerDelegateImpl(Profile* profile);
-
-  ~ArcDlcInstallNotificationManagerDelegateImpl() override;
-
-  // Displays a notification using the notification system.
-  void DisplayNotification(
-      const message_center::Notification& notification) override;
-
- private:
-  // The profile associated with the notifications
-  // which is also the primary profile associated with the arc session
-  const raw_ptr<Profile> profile_;
-};
-
-}  // namespace arc
-
-#endif  // CHROME_BROWSER_ASH_ARC_DLC_INSTALLER_ARC_DLC_INSTALL_NOTIFICATION_DELEGATE_IMPL_H_
diff --git a/chrome/browser/ash/arc/dlc_installer/arc_dlc_notification_manager_factory_impl.cc b/chrome/browser/ash/arc/dlc_installer/arc_dlc_notification_manager_factory_impl.cc
deleted file mode 100644
index 8f1b9e2..0000000
--- a/chrome/browser/ash/arc/dlc_installer/arc_dlc_notification_manager_factory_impl.cc
+++ /dev/null
@@ -1,38 +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 "chrome/browser/ash/arc/dlc_installer/arc_dlc_notification_manager_factory_impl.h"
-
-#include "base/logging.h"
-#include "chrome/browser/ash/arc/dlc_installer/arc_dlc_install_notification_delegate_impl.h"
-#include "chrome/browser/profiles/profile.h"
-#include "chromeos/ash/components/browser_context_helper/browser_context_helper.h"
-#include "chromeos/ash/experiences/arc/dlc_installer/arc_dlc_install_notification_manager.h"
-#include "components/account_id/account_id.h"
-
-namespace arc {
-
-ArcDlcNotificationManagerFactoryImpl::ArcDlcNotificationManagerFactoryImpl() =
-    default;
-
-ArcDlcNotificationManagerFactoryImpl::~ArcDlcNotificationManagerFactoryImpl() =
-    default;
-
-std::unique_ptr<ArcDlcInstallNotificationManager>
-ArcDlcNotificationManagerFactoryImpl::CreateNotificationManager(
-    const AccountId& account_id) {
-  Profile* profile = Profile::FromBrowserContext(
-      ash::BrowserContextHelper::Get()->GetBrowserContextByAccountId(
-          account_id));
-
-  if (!profile) {
-    LOG(ERROR) << "Profile is null for account ID: " << account_id;
-    return nullptr;
-  }
-  return std::make_unique<ArcDlcInstallNotificationManager>(
-      std::make_unique<ArcDlcInstallNotificationManagerDelegateImpl>(profile),
-      account_id);
-}
-
-}  // namespace arc
diff --git a/chrome/browser/ash/arc/dlc_installer/arc_dlc_notification_manager_factory_impl.h b/chrome/browser/ash/arc/dlc_installer/arc_dlc_notification_manager_factory_impl.h
deleted file mode 100644
index f92f152..0000000
--- a/chrome/browser/ash/arc/dlc_installer/arc_dlc_notification_manager_factory_impl.h
+++ /dev/null
@@ -1,39 +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 CHROME_BROWSER_ASH_ARC_DLC_INSTALLER_ARC_DLC_NOTIFICATION_MANAGER_FACTORY_IMPL_H_
-#define CHROME_BROWSER_ASH_ARC_DLC_INSTALLER_ARC_DLC_NOTIFICATION_MANAGER_FACTORY_IMPL_H_
-
-#include <memory>
-
-#include "chromeos/ash/experiences/arc/dlc_installer/arc_dlc_notification_manager_factory.h"
-
-class AccountId;
-
-namespace arc {
-
-class ArcDlcInstallNotificationManager;
-
-// Implementation of the ArcDlcInstallProfileDelegate interface, used to create
-// notification managers based on a Profile associated with an AccountId.
-class ArcDlcNotificationManagerFactoryImpl
-    : public ArcDlcNotificationManagerFactory {
- public:
-  ArcDlcNotificationManagerFactoryImpl();
-
-  ArcDlcNotificationManagerFactoryImpl(
-      const ArcDlcNotificationManagerFactoryImpl&) = delete;
-  ArcDlcNotificationManagerFactoryImpl& operator=(
-      const ArcDlcNotificationManagerFactoryImpl&) = delete;
-
-  ~ArcDlcNotificationManagerFactoryImpl() override;
-
-  // Creates and returns a notification manager for the given AccountId.
-  std::unique_ptr<ArcDlcInstallNotificationManager> CreateNotificationManager(
-      const AccountId& account_id) override;
-};
-
-}  // namespace arc
-
-#endif  // CHROME_BROWSER_ASH_ARC_DLC_INSTALLER_ARC_DLC_NOTIFICATION_MANAGER_FACTORY_IMPL_H_
diff --git a/chrome/browser/ash/arc/session/BUILD.gn b/chrome/browser/ash/arc/session/BUILD.gn
index bcfafbb..c3c6410 100644
--- a/chrome/browser/ash/arc/session/BUILD.gn
+++ b/chrome/browser/ash/arc/session/BUILD.gn
@@ -51,7 +51,6 @@
     "//chrome/browser/ash/arc/auth",
     "//chrome/browser/ash/arc/bluetooth",
     "//chrome/browser/ash/arc/boot_phase_monitor",
-    "//chrome/browser/ash/arc/dlc_installer",
     "//chrome/browser/ash/arc/enterprise",
     "//chrome/browser/ash/arc/enterprise/cert_store",
     "//chrome/browser/ash/arc/error_notification",
diff --git a/chrome/browser/ash/arc/session/arc_service_launcher.cc b/chrome/browser/ash/arc/session/arc_service_launcher.cc
index abbcd62..75cbd62 100644
--- a/chrome/browser/ash/arc/session/arc_service_launcher.cc
+++ b/chrome/browser/ash/arc/session/arc_service_launcher.cc
@@ -27,7 +27,6 @@
 #include "chrome/browser/ash/arc/auth/arc_auth_service.h"
 #include "chrome/browser/ash/arc/bluetooth/arc_bluetooth_bridge.h"
 #include "chrome/browser/ash/arc/boot_phase_monitor/arc_boot_phase_monitor_bridge.h"
-#include "chrome/browser/ash/arc/dlc_installer/arc_dlc_notification_manager_factory_impl.h"
 #include "chrome/browser/ash/arc/enterprise/arc_enterprise_reporting_service.h"
 #include "chrome/browser/ash/arc/enterprise/cert_store/cert_store_service_factory.h"
 #include "chrome/browser/ash/arc/error_notification/arc_error_notification_bridge.h"
@@ -186,7 +185,6 @@
                                   scheduler_configuration_manager)),
       scheduler_configuration_manager_(scheduler_configuration_manager),
       arc_dlc_installer_(std::make_unique<ArcDlcInstaller>(
-          std::make_unique<ArcDlcNotificationManagerFactoryImpl>(),
           std::make_unique<ArcDlcInstallHardwareChecker>(),
           ash::CrosSettings::Get())) {
   DCHECK(g_arc_service_launcher == nullptr);
@@ -291,25 +289,6 @@
   // Record metrics for ARC status based on device affiliation
   RecordArcStatusBasedOnDeviceAffiliationUMA(profile);
 
-  const AccountId* account_id = ash::AnnotatedAccountId::Get(profile);
-
-  // Profile set up for the test is no properly initialized, so AccountId is not
-  // annotated. If the user is a guest, OTR profile is passed, which does not
-  // have annotated account id. The OnPrimaryUserSessionStarted function must
-  // run before the profile check below. On boards with the arcvm_dlc flag
-  // (e.g., reven board), the ARCVM image installs from DLC at runtime. Before
-  // this completes, the arc_session_manager profile is nullptr, causing the
-  // profile check to fail. Moving OnPrimaryUserSessionStarted earlier ensures
-  // the notification manager can send notifications before ARCVM image
-  // installation is complete. This logic will be removed once the profile
-  // dependency for ARC DLC install notifications is eliminated.
-  // TODO(b/406349559): Remove dependency on profile for ARC DLC install
-  // notifications.
-  // TODO(b/418815526): Create a browser test for ARCVM_DLC notification.
-  if (account_id) {
-    arc_dlc_installer_->OnPrimaryUserSessionStarted(*account_id);
-  }
-
   if (arc_session_manager_->profile() != profile) {
     // Profile is not matched, so the given |profile| is not allowed to use
     // ARC.
@@ -472,7 +451,6 @@
   // Recreate arc_dlc_installer_ after shutdown because browser_test will run
   // ResetForTesting and then do the OnPrimaryUserProfilePrepared.
   arc_dlc_installer_ = std::make_unique<ArcDlcInstaller>(
-      std::make_unique<ArcDlcNotificationManagerFactoryImpl>(),
       std::make_unique<ArcDlcInstallHardwareChecker>(),
       ash::CrosSettings::Get());
 }
diff --git a/chrome/browser/chrome_browser_interface_binders_webui.cc b/chrome/browser/chrome_browser_interface_binders_webui.cc
index 684b746..9cb92ba 100644
--- a/chrome/browser/chrome_browser_interface_binders_webui.cc
+++ b/chrome/browser/chrome_browser_interface_binders_webui.cc
@@ -122,6 +122,8 @@
 #include "chrome/browser/ui/webui/search_engine_choice/search_engine_choice_ui.h"
 #include "chrome/browser/ui/webui/settings/settings_ui.h"
 #include "chrome/browser/ui/webui/side_panel/bookmarks/bookmarks_side_panel_ui.h"
+#include "chrome/browser/ui/webui/side_panel/comments/comments.mojom.h"
+#include "chrome/browser/ui/webui/side_panel/comments/comments_side_panel_ui.h"
 #include "chrome/browser/ui/webui/side_panel/customize_chrome/customize_chrome.mojom.h"
 #include "chrome/browser/ui/webui/side_panel/customize_chrome/customize_chrome_ui.h"
 #include "chrome/browser/ui/webui/side_panel/customize_chrome/wallpaper_search/wallpaper_search.mojom.h"
@@ -729,6 +731,8 @@
   RegisterWebUIControllerInterfaceBinder<
       side_panel::mojom::BookmarksPageHandlerFactory, BookmarksSidePanelUI>(
       map);
+  RegisterWebUIControllerInterfaceBinder<comments::mojom::PageHandlerFactory,
+                                         CommentsSidePanelUI>(map);
 
   RegisterWebUIControllerInterfaceBinder<
       shopping_service::mojom::ShoppingServiceHandlerFactory,
diff --git a/chrome/browser/chrome_content_browser_client.cc b/chrome/browser/chrome_content_browser_client.cc
index 145a09bb..9e944d1 100644
--- a/chrome/browser/chrome_content_browser_client.cc
+++ b/chrome/browser/chrome_content_browser_client.cc
@@ -108,7 +108,6 @@
 #include "chrome/browser/net/profile_network_context_service.h"
 #include "chrome/browser/net/profile_network_context_service_factory.h"
 #include "chrome/browser/net/system_network_context_manager.h"
-#include "chrome/browser/optimization_guide/chrome_browser_main_extra_parts_optimization_guide.h"
 #include "chrome/browser/payments/payment_request_display_manager_factory.h"
 #include "chrome/browser/performance_manager/public/chrome_browser_main_extra_parts_performance_manager.h"
 #include "chrome/browser/performance_manager/public/chrome_content_browser_client_performance_manager_part.h"
@@ -1712,9 +1711,6 @@
   main_parts->AddParts(
       std::make_unique<ChromeBrowserMainExtraPartsSegmentationPlatform>());
 
-  main_parts->AddParts(
-      std::make_unique<ChromeBrowserMainExtraPartsOptimizationGuide>());
-
   return main_parts;
 }
 
diff --git a/chrome/browser/enterprise/connectors/reporting/reporting_event_router_unittest.cc b/chrome/browser/enterprise/connectors/reporting/reporting_event_router_unittest.cc
index 69d9285a..039a0ef2d 100644
--- a/chrome/browser/enterprise/connectors/reporting/reporting_event_router_unittest.cc
+++ b/chrome/browser/enterprise/connectors/reporting/reporting_event_router_unittest.cc
@@ -769,6 +769,8 @@
       /*enabled_opt_in_events=*/{});
 
   test::EventReportValidator validator(client_.get());
+  base::RunLoop run_loop;
+  validator.SetDoneClosure(run_loop.QuitClosure());
   chrome::cros::reporting::proto::UnscannedFileEvent expected_event;
 
   if (use_proto_format()) {
@@ -818,6 +820,7 @@
       "exampleDestination", "encrypted.zip", "sha256_of_data",
       "application/zip", "FILE_UPLOAD", "FILE_PASSWORD_PROTECTED",
       "CONTENT_TRANSFER_METHOD_DRAG_AND_DROP", 12345, EventResult::ALLOWED);
+  run_loop.Run();
 }
 
 TEST_P(ReportingEventRouterTest, TestOnUnscannedFileEvent_Blocked) {
@@ -827,6 +830,8 @@
       /*enabled_opt_in_events=*/{});
 
   test::EventReportValidator validator(client_.get());
+  base::RunLoop run_loop;
+  validator.SetDoneClosure(run_loop.QuitClosure());
   chrome::cros::reporting::proto::UnscannedFileEvent expected_event;
 
   if (use_proto_format()) {
@@ -861,7 +866,7 @@
         /*expected_trigger=*/"FILE_DOWNLOAD",
         /*expected_reason=*/"FILE_PASSWORD_PROTECTED",
         /*expected_mimetypes=*/ZipMimeType(), /*expected_content_size=*/12345,
-        /* expected_result=*/"EVENT_RESULT_BLOCKED",
+        /*expected_result=*/"EVENT_RESULT_BLOCKED",
         /*expected_profile_username=*/profile_->GetProfileUserName(),
         /*expected_profile_identifier=*/GetProfileIdentifier(),
         /*expected_content_transfer_method*/ std::nullopt);
@@ -872,6 +877,194 @@
       "exampleDestination", "encrypted.zip", "sha256_of_data",
       "application/zip", "FILE_DOWNLOAD", "FILE_PASSWORD_PROTECTED", "", 12345,
       EventResult::BLOCKED);
+  run_loop.Run();
+}
+
+TEST_P(ReportingEventRouterTest, TestOnSensitiveDataEvent_Allowed) {
+  EnableEnhancedFieldsForSecOps();
+  if (use_proto_format()) {
+    return;
+  }
+
+  test::SetOnSecurityEventReporting(
+      profile_->GetPrefs(), /*enabled=*/true,
+      /*enabled_event_names=*/{kKeySensitiveDataEvent},
+      /*enabled_opt_in_events=*/{});
+
+  test::EventReportValidator validator(client_.get());
+  base::RunLoop run_loop;
+  validator.SetDoneClosure(run_loop.QuitClosure());
+
+  ContentAnalysisResponse response;
+  response.set_request_token("123");
+  auto* result = response.add_results();
+  result->set_status(ContentAnalysisResponse::Result::SUCCESS);
+  result->set_tag("dlp");
+
+  validator.ExpectSensitiveDataEvent(
+      /*url*/ "about:blank",
+      /*tab_url*/ "about:blank",
+      /*source*/ "exampleSource",
+      /*destination*/ "exampleDestination",
+      /*filename*/ "encrypted.zip",
+      /*sha256*/ "sha256_of_data",
+      /*trigger*/ "FILE_UPLOAD",
+      /*dlp_verdict*/ *result,
+      /*mimetype*/ ZipMimeType(),
+      /*size*/ 200,
+      /*result*/
+      EventResultToString(EventResult::ALLOWED),
+      /*username*/ profile_->GetProfileUserName(),
+      /*profile_identifier*/ GetProfileIdentifier(),
+      /*scan_id*/ "123",
+      /*content_transfer_method*/ "CONTENT_TRANSFER_METHOD_DRAG_AND_DROP",
+      /*user_justification*/ std::nullopt);
+  validator.ExpectActiveUser("gaia@gmail.com");
+  validator.ExpectSourceActiveUser("test@gmail.com");
+
+  ReferrerChain referrer_chain;
+  referrer_chain.Add(test::MakeReferrerChainEntry());
+  reporting_event_router_->OnSensitiveDataEvent(
+      GURL("about:blank"), GURL("about:blank"), "exampleSource",
+      "exampleDestination", "encrypted.zip", "sha256_of_data",
+      "application/zip", "FILE_UPLOAD", "123",
+      "CONTENT_TRANSFER_METHOD_DRAG_AND_DROP", "test@gmail.com",
+      "gaia@gmail.com", *result, 200, referrer_chain, EventResult::ALLOWED);
+  run_loop.Run();
+}
+
+TEST_P(ReportingEventRouterTest, TestOnSensitiveDataEvent_Blocked) {
+  EnableEnhancedFieldsForSecOps();
+  if (use_proto_format()) {
+    return;
+  }
+
+  test::SetOnSecurityEventReporting(
+      profile_->GetPrefs(), /*enabled=*/true,
+      /*enabled_event_names=*/{kKeySensitiveDataEvent},
+      /*enabled_opt_in_events=*/{});
+
+  test::EventReportValidator validator(client_.get());
+  base::RunLoop run_loop;
+  validator.SetDoneClosure(run_loop.QuitClosure());
+
+  ContentAnalysisResponse response;
+  response.set_request_token("123");
+  auto* result = response.add_results();
+  result->set_status(ContentAnalysisResponse::Result::SUCCESS);
+  result->set_tag("dlp");
+
+  validator.ExpectSensitiveDataEvent(
+      /*url*/ "about:blank",
+      /*tab_url*/ "about:blank",
+      /*source*/ "exampleSource",
+      /*destination*/ "exampleDestination",
+      /*filename*/ "encrypted.zip",
+      /*sha256*/ "sha256_of_data",
+      /*trigger*/ "FILE_DOWNLOAD",
+      /*dlp_verdict*/ *result,
+      /*mimetype*/ ZipMimeType(),
+      /*size*/ 200,
+      /*result*/
+      EventResultToString(EventResult::BLOCKED),
+      /*username*/ profile_->GetProfileUserName(),
+      /*profile_identifier*/ GetProfileIdentifier(),
+      /*scan_id*/ "123",
+      /*content_transfer_method*/ std::nullopt,
+      /*user_justification*/ std::nullopt);
+  validator.ExpectActiveUser("gaia@gmail.com");
+  validator.ExpectSourceActiveUser("test@gmail.com");
+
+  ReferrerChain referrer_chain;
+  referrer_chain.Add(test::MakeReferrerChainEntry());
+  reporting_event_router_->OnSensitiveDataEvent(
+      GURL("about:blank"), GURL("about:blank"), "exampleSource",
+      "exampleDestination", "encrypted.zip", "sha256_of_data",
+      "application/zip", "FILE_DOWNLOAD", "123", "", "test@gmail.com",
+      "gaia@gmail.com", *result, 200, referrer_chain, EventResult::BLOCKED);
+  run_loop.Run();
+}
+
+TEST_P(ReportingEventRouterTest, TestOnDangerousDownloadEvent_Warned) {
+  EnableEnhancedFieldsForSecOps();
+  if (use_proto_format()) {
+    return;
+  }
+
+  test::SetOnSecurityEventReporting(
+      profile_->GetPrefs(), /*enabled=*/true,
+      /*enabled_event_names=*/{kKeyDangerousDownloadEvent},
+      /*enabled_opt_in_events=*/{});
+
+  test::EventReportValidator validator(client_.get());
+  base::RunLoop run_loop;
+  validator.SetDoneClosure(run_loop.QuitClosure());
+
+  validator.ExpectDangerousDeepScanningResult(
+      /*url*/ "https://example.com/download.exe",
+      /*tab_url*/ "https://example.com/",
+      /*source*/ "exampleSource",
+      /*destination*/ "exampleDestination",
+      /*filename*/ "encrypted.zip",
+      /*sha256*/ "sha256_of_data",
+      /*threat_type*/ "POTENTIALLY_UNWANTED",
+      /*trigger*/ "FILE_DOWNLOAD",
+      /*mimetypes*/ ZipMimeType(),
+      /*size*/ 12345,
+      /*result*/ "EVENT_RESULT_WARNED",
+      /*username*/ profile_->GetProfileUserName(),
+      /*profile_identifier*/ GetProfileIdentifier(),
+      /*scan_id*/ "123");
+
+  ReferrerChain referrer_chain;
+  referrer_chain.Add(test::MakeReferrerChainEntry());
+  reporting_event_router_->OnDangerousDeepScanningResult(
+      GURL("https://example.com/download.exe"), GURL("https://example.com/"),
+      "exampleSource", "exampleDestination", "encrypted.zip", "sha256_of_data",
+      "POTENTIALLY_UNWANTED", "application/zip", "FILE_DOWNLOAD", 12345,
+      std::move(referrer_chain), EventResult::WARNED, "123", "");
+  run_loop.Run();
+}
+
+TEST_P(ReportingEventRouterTest, TestOnDangerousDownloadEvent_Blocked) {
+  EnableEnhancedFieldsForSecOps();
+  if (use_proto_format()) {
+    return;
+  }
+
+  test::SetOnSecurityEventReporting(
+      profile_->GetPrefs(), /*enabled=*/true,
+      /*enabled_event_names=*/{kKeyDangerousDownloadEvent},
+      /*enabled_opt_in_events=*/{});
+
+  test::EventReportValidator validator(client_.get());
+  base::RunLoop run_loop;
+  validator.SetDoneClosure(run_loop.QuitClosure());
+
+  validator.ExpectDangerousDeepScanningResult(
+      /*url*/ "https://example.com/download.exe",
+      /*tab_url*/ "https://example.com/",
+      /*source*/ "exampleSource",
+      /*destination*/ "exampleDestination",
+      /*filename*/ "encrypted.zip",
+      /*sha256*/ "sha256_of_data",
+      /*threat_type*/ "DANGEROUS",
+      /*trigger*/ "FILE_DOWNLOAD",
+      /*mimetypes*/ ZipMimeType(),
+      /*size*/ 12345,
+      /*result*/ "EVENT_RESULT_BLOCKED",
+      /*username*/ profile_->GetProfileUserName(),
+      /*profile_identifier*/ GetProfileIdentifier(),
+      /*scan_id*/ "123");
+
+  ReferrerChain referrer_chain;
+  referrer_chain.Add(test::MakeReferrerChainEntry());
+  reporting_event_router_->OnDangerousDeepScanningResult(
+      GURL("https://example.com/download.exe"), GURL("https://example.com/"),
+      "exampleSource", "exampleDestination", "encrypted.zip", "sha256_of_data",
+      "DANGEROUS", "application/zip", "FILE_DOWNLOAD", 12345,
+      std::move(referrer_chain), EventResult::BLOCKED, "123", "");
+  run_loop.Run();
 }
 #endif  // BUILDFLAG(ENTERPRISE_CONTENT_ANALYSIS)
 
diff --git a/chrome/browser/enterprise/connectors/test/deep_scanning_test_utils.cc b/chrome/browser/enterprise/connectors/test/deep_scanning_test_utils.cc
index f4337d6..051df56 100644
--- a/chrome/browser/enterprise/connectors/test/deep_scanning_test_utils.cc
+++ b/chrome/browser/enterprise/connectors/test/deep_scanning_test_utils.cc
@@ -555,11 +555,8 @@
                 is_federated_);
   ValidateField(event, SafeBrowsingPrivateEventRouter::kKeyLoginUserName,
                 login_user_name_);
-  ValidateField(event,
-                SafeBrowsingPrivateEventRouter::kKeyWebAppSignedInAccount,
-                active_content_area_user_);
-  ValidateField(event,
-                SafeBrowsingPrivateEventRouter::kKeySourceWebAppSignedInAccount,
+  ValidateField(event, kKeyWebAppSignedInAccount, active_content_area_user_);
+  ValidateField(event, kKeySourceWebAppSignedInAccount,
                 source_active_content_area_user_);
   ValidateFederatedOrigin(event);
   ValidateIdentities(event);
diff --git a/chrome/browser/extensions/api/browsing_data/browsing_data_api.cc b/chrome/browser/extensions/api/browsing_data/browsing_data_api.cc
index afd4257..c2e4bbb5 100644
--- a/chrome/browser/extensions/api/browsing_data/browsing_data_api.cc
+++ b/chrome/browser/extensions/api/browsing_data/browsing_data_api.cc
@@ -252,7 +252,10 @@
   // base::Time takes a double that represents seconds since epoch. JavaScript
   // gives developers milliseconds, so do a quick conversion before populating
   // the object.
-  remove_since_ = base::Time::FromMillisecondsSinceUnixEpoch(ms_since_epoch);
+  remove_since_ =
+      ms_since_epoch == 0
+          ? base::Time()
+          : base::Time::FromMillisecondsSinceUnixEpoch(ms_since_epoch);
 
   EXTENSION_FUNCTION_VALIDATE(GetRemovalMask(&removal_mask_));
 
diff --git a/chrome/browser/extensions/api/browsing_data/browsing_data_unittest.cc b/chrome/browser/extensions/api/browsing_data/browsing_data_unittest.cc
index ac6abba8..8b9be27 100644
--- a/chrome/browser/extensions/api/browsing_data/browsing_data_unittest.cc
+++ b/chrome/browser/extensions/api/browsing_data/browsing_data_unittest.cc
@@ -295,7 +295,7 @@
   void VerifyFilterBuilder(const std::string& options,
                            content::BrowsingDataFilterBuilder* filter_builder) {
     delegate()->ExpectCall(
-        base::Time::UnixEpoch(), base::Time::Max(),
+        base::Time(), base::Time::Max(),
         content::BrowsingDataRemover::DATA_TYPE_LOCAL_STORAGE, UNPROTECTED_WEB,
         filter_builder);
     auto function = base::MakeRefCounted<BrowsingDataRemoveFunction>();
@@ -616,7 +616,7 @@
   auto filter_builder = content::BrowsingDataFilterBuilder::Create(
       content::BrowsingDataFilterBuilder::Mode::kPreserve);
   filter_builder->AddRegisterableDomain("example.com");
-  delegate()->ExpectCall(base::Time::UnixEpoch(), base::Time::Max(),
+  delegate()->ExpectCall(base::Time(), base::Time::Max(),
                          content::BrowsingDataRemover::DATA_TYPE_COOKIES,
                          UNPROTECTED_WEB, filter_builder.get());
 
@@ -635,7 +635,7 @@
   auto filter_builder1 = content::BrowsingDataFilterBuilder::Create(
       content::BrowsingDataFilterBuilder::Mode::kDelete);
   filter_builder1->AddRegisterableDomain("example.com");
-  delegate()->ExpectCall(base::Time::UnixEpoch(), base::Time::Max(),
+  delegate()->ExpectCall(base::Time(), base::Time::Max(),
                          content::BrowsingDataRemover::DATA_TYPE_COOKIES,
                          UNPROTECTED_WEB, filter_builder1.get());
 
@@ -643,7 +643,7 @@
       content::BrowsingDataFilterBuilder::Mode::kDelete);
   filter_builder2->AddOrigin(
       url::Origin::Create(GURL("http://www.example.com")));
-  delegate()->ExpectCall(base::Time::UnixEpoch(), base::Time::Max(),
+  delegate()->ExpectCall(base::Time(), base::Time::Max(),
                          content::BrowsingDataRemover::DATA_TYPE_LOCAL_STORAGE,
                          UNPROTECTED_WEB, filter_builder2.get());
 
diff --git a/chrome/browser/extensions/api/developer_private/developer_private_functions_desktop.cc b/chrome/browser/extensions/api/developer_private/developer_private_functions_desktop.cc
index 6812a2f..c6c477c 100644
--- a/chrome/browser/extensions/api/developer_private/developer_private_functions_desktop.cc
+++ b/chrome/browser/extensions/api/developer_private/developer_private_functions_desktop.cc
@@ -40,7 +40,7 @@
 #include "chrome/browser/extensions/permissions/scripting_permissions_modifier.h"
 #include "chrome/browser/extensions/permissions/site_permissions_helper.h"
 #include "chrome/browser/extensions/shared_module_service.h"
-#include "chrome/browser/extensions/sync/extension_sync_service.h"
+#include "chrome/browser/extensions/sync/extension_sync_util.h"
 #include "chrome/browser/extensions/unpacked_installer.h"
 #include "chrome/browser/extensions/updater/extension_updater.h"
 #include "chrome/browser/platform_util.h"
@@ -642,14 +642,6 @@
   return base::ok(extension);
 }
 
-void DeveloperPrivateUploadExtensionToAccountFunction::UploadExtensionToAccount(
-    const Extension& extension) {
-  AccountExtensionTracker::Get(browser_context())
-      ->OnAccountUploadInitiatedForExtension(extension.id());
-  ExtensionSyncService::Get(browser_context())
-      ->SyncExtensionChangeIfNeeded(extension);
-}
-
 void DeveloperPrivateUploadExtensionToAccountFunction::OnDialogAccepted() {
   // We cannot proceed if the `browser_context` is not valid as the relevant
   // classes needed to upload the extension will not exist.
@@ -664,7 +656,7 @@
   }
   const Extension* extension = *result;
 
-  UploadExtensionToAccount(*extension);
+  sync_util::UploadExtensionToAccount(profile_, *extension);
   Respond(WithArguments(true));
 }
 
diff --git a/chrome/browser/extensions/api/developer_private/developer_private_functions_desktop.h b/chrome/browser/extensions/api/developer_private/developer_private_functions_desktop.h
index a6e8688..7751321b 100644
--- a/chrome/browser/extensions/api/developer_private/developer_private_functions_desktop.h
+++ b/chrome/browser/extensions/api/developer_private/developer_private_functions_desktop.h
@@ -197,9 +197,6 @@
   // in user. Returns the extension if successful, otherwise returns an error.
   base::expected<const Extension*, std::string> VerifyExtensionAndSigninState();
 
-  // Uploads the given `extension` to the user's account.
-  void UploadExtensionToAccount(const Extension& extension);
-
   // A callback function to run when the user accepts the action dialog.
   void OnDialogAccepted();
 
diff --git a/chrome/browser/extensions/api/experimental_actor/experimental_actor_api.cc b/chrome/browser/extensions/api/experimental_actor/experimental_actor_api.cc
index 4f9a90da..75c04294 100644
--- a/chrome/browser/extensions/api/experimental_actor/experimental_actor_api.cc
+++ b/chrome/browser/extensions/api/experimental_actor/experimental_actor_api.cc
@@ -9,6 +9,7 @@
 #include <vector>
 
 #include "base/check.h"
+#include "base/command_line.h"
 #include "base/functional/bind.h"
 #include "base/strings/string_number_conversions.h"
 #include "base/strings/string_split.h"
@@ -16,6 +17,7 @@
 #include "base/version_info/channel.h"
 #include "chrome/browser/actor/actor_keyed_service.h"
 #include "chrome/browser/actor/actor_keyed_service_factory.h"
+#include "chrome/browser/actor/aggregated_journal_file_serializer.h"
 #include "chrome/browser/actor/browser_action_util.h"
 #include "chrome/browser/actor/task_id.h"
 #include "chrome/browser/actor/tools/tab_management_tool_request.h"
@@ -76,6 +78,44 @@
   action_payload->set_tab_id(ConvertSessionTabIdToTabHandle(
       action_payload->tab_id(), browser_context));
 }
+
+const void* const kSerializerKey = &kSerializerKey;
+
+// File location that the actor journal should be serialized to.
+const char kExperimentalActorJournalLog[] = "experimental-actor-journal";
+
+class Serializer : public base::SupportsUserData::Data {
+ public:
+  explicit Serializer(actor::AggregatedJournal& journal) {
+    base::FilePath path =
+        base::CommandLine::ForCurrentProcess()->GetSwitchValuePath(
+            kExperimentalActorJournalLog);
+    if (!path.empty()) {
+      serializer_ =
+          std::make_unique<actor::AggregatedJournalFileSerializer>(journal);
+      serializer_->Init(
+          path, base::BindOnce(&Serializer::InitDone, base::Unretained(this)));
+    }
+  }
+
+  static void EnsureInitialized(content::BrowserContext* context,
+                                actor::AggregatedJournal& journal) {
+    if (!context->GetUserData(kSerializerKey)) {
+      context->SetUserData(kSerializerKey,
+                           std::make_unique<Serializer>(journal));
+    }
+  }
+
+ private:
+  void InitDone(bool success) {
+    if (!success) {
+      serializer_.reset();
+    }
+  }
+
+  std::unique_ptr<actor::AggregatedJournalFileSerializer> serializer_;
+};
+
 }  // namespace
 
 ExperimentalActorApiFunction::ExperimentalActorApiFunction() = default;
@@ -100,6 +140,7 @@
     return false;
   }
 
+  Serializer::EnsureInitialized(browser_context(), actor_service->GetJournal());
   return true;
 }
 
diff --git a/chrome/browser/extensions/api/experimental_actor/experimental_actor_apitest.cc b/chrome/browser/extensions/api/experimental_actor/experimental_actor_apitest.cc
index 708607e8..e1d6d1c 100644
--- a/chrome/browser/extensions/api/experimental_actor/experimental_actor_apitest.cc
+++ b/chrome/browser/extensions/api/experimental_actor/experimental_actor_apitest.cc
@@ -29,7 +29,7 @@
   "permissions": [
     "experimentalActor",
     "tabs",
-    "windows",
+    "windows"
   ]
 }
 )json";
diff --git a/chrome/browser/extensions/api/passwords_private/passwords_private_api.cc b/chrome/browser/extensions/api/passwords_private/passwords_private_api.cc
index a4f1e6b..65a203d 100644
--- a/chrome/browser/extensions/api/passwords_private/passwords_private_api.cc
+++ b/chrome/browser/extensions/api/passwords_private/passwords_private_api.cc
@@ -22,6 +22,7 @@
 #include "components/prefs/pref_service.h"
 #include "components/sync/service/sync_service.h"
 #include "components/user_prefs/user_prefs.h"
+#include "content/public/browser/browser_context.h"
 #include "content/public/browser/web_contents.h"
 #include "extensions/browser/extension_function_registry.h"
 
diff --git a/chrome/browser/extensions/api/safe_browsing_private/safe_browsing_private_event_router.cc b/chrome/browser/extensions/api/safe_browsing_private/safe_browsing_private_event_router.cc
index 5736f1c6..c23c756 100644
--- a/chrome/browser/extensions/api/safe_browsing_private/safe_browsing_private_event_router.cc
+++ b/chrome/browser/extensions/api/safe_browsing_private/safe_browsing_private_event_router.cc
@@ -56,16 +56,6 @@
 
 namespace {
 
-std::string MalwareRuleToThreatType(const std::string& rule_name) {
-  if (rule_name == "uws") {
-    return "POTENTIALLY_UNWANTED";
-  } else if (rule_name == "malware") {
-    return "DANGEROUS";
-  } else {
-    return "UNKNOWN";
-  }
-}
-
 std::string DangerTypeToThreatType(download::DownloadDangerType danger_type) {
   switch (danger_type) {
     case download::DOWNLOAD_DANGER_TYPE_DANGEROUS_FILE:
@@ -360,172 +350,6 @@
   }
 }
 
-void SafeBrowsingPrivateEventRouter::OnAnalysisConnectorResult(
-    const GURL& url,
-    const GURL& tab_url,
-    const std::string& source,
-    const std::string& destination,
-    const std::string& file_name,
-    const std::string& download_digest_sha256,
-    const std::string& mime_type,
-    const std::string& trigger,
-    const std::string& scan_id,
-    const std::string& content_transfer_method,
-    const std::string& source_email,
-    const enterprise_connectors::ContentAnalysisResponse::Result& result,
-    const int64_t content_size,
-    const safe_browsing::ReferrerChain& referrer_chain,
-    enterprise_connectors::EventResult event_result) {
-  if (result.tag() == "malware") {
-    DCHECK_EQ(1, result.triggered_rules().size());
-    OnDangerousDeepScanningResult(
-        url, tab_url, source, destination, file_name, download_digest_sha256,
-        MalwareRuleToThreatType(result.triggered_rules(0).rule_name()),
-        mime_type, trigger, content_size, referrer_chain, event_result, scan_id,
-        content_transfer_method);
-  } else if (result.tag() == "dlp") {
-    OnSensitiveDataEvent(url, tab_url, source, destination, file_name,
-                         download_digest_sha256, mime_type, trigger, scan_id,
-                         content_transfer_method, source_email, result,
-                         content_size, referrer_chain, event_result);
-  }
-}
-
-void SafeBrowsingPrivateEventRouter::OnDangerousDeepScanningResult(
-    const GURL& url,
-    const GURL& tab_url,
-    const std::string& source,
-    const std::string& destination,
-    const std::string& file_name,
-    const std::string& download_digest_sha256,
-    const std::string& threat_type,
-    const std::string& mime_type,
-    const std::string& trigger,
-    const int64_t content_size,
-    const safe_browsing::ReferrerChain& referrer_chain,
-    enterprise_connectors::EventResult event_result,
-    const std::string& scan_id,
-    const std::string& content_transfer_method) {
-#if BUILDFLAG(ENTERPRISE_CLOUD_CONTENT_ANALYSIS)
-  std::optional<enterprise_connectors::ReportingSettings> settings =
-      reporting_client_->GetReportingSettings();
-  if (!settings.has_value() ||
-      settings->enabled_event_names.count(
-          enterprise_connectors::kKeyDangerousDownloadEvent) == 0) {
-    return;
-  }
-
-  base::Value::Dict event;
-  event.Set(kKeyUrl, url.spec());
-  event.Set(kKeyTabUrl, tab_url.spec());
-  event.Set(kKeySource, source);
-  event.Set(kKeyDestination, destination);
-  event.Set(kKeyFileName,
-            GetFileName(file_name, enterprise_connectors::IncludeDeviceInfo(
-                                       Profile::FromBrowserContext(context_),
-                                       settings->per_profile)));
-  event.Set(kKeyDownloadDigestSha256, download_digest_sha256);
-  event.Set(kKeyThreatType, threat_type);
-  event.Set(kKeyContentType, mime_type);
-  // |content_size| can be set to -1 to indicate an unknown size, in
-  // which case the field is not set.
-  if (content_size >= 0) {
-    event.Set(kKeyContentSize, base::Int64ToValue(content_size));
-  }
-  event.Set(kKeyTrigger, trigger);
-  if (base::FeatureList::IsEnabled(safe_browsing::kEnhancedFieldsForSecOps)) {
-    enterprise_connectors::AddReferrerChainToEvent(referrer_chain, event);
-  }
-  event.Set(kKeyEventResult,
-            enterprise_connectors::EventResultToString(event_result));
-  event.Set(kKeyClickedThrough,
-            event_result == enterprise_connectors::EventResult::BYPASSED);
-  // The scan ID can be empty when the reported dangerous download is from a
-  // Safe Browsing verdict.
-  if (!scan_id.empty()) {
-    event.Set(kKeyScanId, scan_id);
-  }
-  if (!content_transfer_method.empty()) {
-    event.Set(kKeyContentTransferMethod, content_transfer_method);
-  }
-
-  reporting_client_->ReportRealtimeEvent(
-      enterprise_connectors::kKeyDangerousDownloadEvent,
-      std::move(settings.value()), std::move(event));
-#endif  // BUILDFLAG(ENTERPRISE_CLOUD_CONTENT_ANALYSIS)
-}
-
-void SafeBrowsingPrivateEventRouter::OnSensitiveDataEvent(
-    const GURL& url,
-    const GURL& tab_url,
-    const std::string& source,
-    const std::string& destination,
-    const std::string& file_name,
-    const std::string& download_digest_sha256,
-    const std::string& mime_type,
-    const std::string& trigger,
-    const std::string& scan_id,
-    const std::string& content_transfer_method,
-    const std::string& source_email,
-    const enterprise_connectors::ContentAnalysisResponse::Result& result,
-    const int64_t content_size,
-    const safe_browsing::ReferrerChain& referrer_chain,
-    enterprise_connectors::EventResult event_result) {
-#if BUILDFLAG(ENTERPRISE_CLOUD_CONTENT_ANALYSIS)
-  std::optional<enterprise_connectors::ReportingSettings> settings =
-      reporting_client_->GetReportingSettings();
-  if (!settings.has_value() ||
-      settings->enabled_event_names.count(
-          enterprise_connectors::kKeySensitiveDataEvent) == 0) {
-    return;
-  }
-
-  base::Value::Dict event;
-  event.Set(kKeyUrl, url.spec());
-  event.Set(kKeyTabUrl, tab_url.spec());
-  event.Set(kKeySource, source);
-  event.Set(kKeyDestination, destination);
-  event.Set(kKeyFileName,
-            GetFileName(file_name, enterprise_connectors::IncludeDeviceInfo(
-                                       Profile::FromBrowserContext(context_),
-                                       settings->per_profile)));
-  event.Set(kKeyDownloadDigestSha256, download_digest_sha256);
-  event.Set(kKeyContentType, mime_type);
-  // |content_size| can be set to -1 to indicate an unknown size, in
-  // which case the field is not set.
-  if (content_size >= 0) {
-    event.Set(kKeyContentSize, base::Int64ToValue(content_size));
-  }
-  event.Set(kKeyTrigger, trigger);
-  if (base::FeatureList::IsEnabled(safe_browsing::kEnhancedFieldsForSecOps)) {
-    enterprise_connectors::AddReferrerChainToEvent(referrer_chain, event);
-  }
-  event.Set(kKeyEventResult,
-            enterprise_connectors::EventResultToString(event_result));
-  event.Set(kKeyClickedThrough,
-            event_result == enterprise_connectors::EventResult::BYPASSED);
-  event.Set(kKeyScanId, scan_id);
-  if (!content_transfer_method.empty()) {
-    event.Set(kKeyContentTransferMethod, content_transfer_method);
-  }
-  std::string content_area_account_email =
-      enterprise_connectors::ContentAreaUserProvider::GetUser(
-          Profile::FromBrowserContext(context_), tab_url);
-  if (!content_area_account_email.empty()) {
-    event.Set(kKeyWebAppSignedInAccount, content_area_account_email);
-  }
-  if (!source_email.empty()) {
-    event.Set(kKeySourceWebAppSignedInAccount, source_email);
-  }
-
-  AddAnalysisConnectorVerdictToEvent(result, event);
-
-  reporting_client_->ReportRealtimeEvent(
-      enterprise_connectors::kKeySensitiveDataEvent,
-      std::move(settings.value()), std::move(event));
-#endif  // BUILDFLAG(ENTERPRISE_CLOUD_CONTENT_ANALYSIS)
-}
-
 void SafeBrowsingPrivateEventRouter::OnAnalysisConnectorWarningBypassed(
     const GURL& url,
     const GURL& tab_url,
@@ -584,7 +408,8 @@
       enterprise_connectors::ContentAreaUserProvider::GetUser(
           Profile::FromBrowserContext(context_), tab_url);
   if (!content_area_account_email.empty()) {
-    event.Set(kKeyWebAppSignedInAccount, content_area_account_email);
+    event.Set(enterprise_connectors::kKeyWebAppSignedInAccount,
+              content_area_account_email);
   }
 
   AddAnalysisConnectorVerdictToEvent(result, event);
@@ -773,10 +598,12 @@
       enterprise_connectors::ContentAreaUserProvider::GetUser(
           Profile::FromBrowserContext(context_), tab_url);
   if (!content_area_account_email.empty()) {
-    event.Set(kKeyWebAppSignedInAccount, content_area_account_email);
+    event.Set(enterprise_connectors::kKeyWebAppSignedInAccount,
+              content_area_account_email);
   }
   if (!source_active_user_email.empty()) {
-    event.Set(kKeySourceWebAppSignedInAccount, source_active_user_email);
+    event.Set(enterprise_connectors::kKeySourceWebAppSignedInAccount,
+              source_active_user_email);
   }
   event.Set(kKeyEventResult,
             enterprise_connectors::EventResultToString(event_result));
diff --git a/chrome/browser/extensions/api/safe_browsing_private/safe_browsing_private_event_router.h b/chrome/browser/extensions/api/safe_browsing_private/safe_browsing_private_event_router.h
index 725f595..1ed211e 100644
--- a/chrome/browser/extensions/api/safe_browsing_private/safe_browsing_private_event_router.h
+++ b/chrome/browser/extensions/api/safe_browsing_private/safe_browsing_private_event_router.h
@@ -76,9 +76,6 @@
   static const char kKeyTabUrl[];
   static constexpr char kKeyContentTransferMethod[] = "contentTransferMethod";
   static constexpr char kKeyHasWatermarking[] = "hasWatermarking";
-  static constexpr char kKeyWebAppSignedInAccount[] = "webAppSignedInAccount";
-  static constexpr char kKeySourceWebAppSignedInAccount[] =
-      "sourceWebAppSignedInAccount";
   static const char kKeyUnscannedReason[];
 
   // String constants for the "trigger" event field.  This corresponds to
@@ -135,24 +132,6 @@
                                        const std::string& reason,
                                        int net_error_code);
 
-  // Notifies listeners that the analysis connector detected a violation.
-  void OnAnalysisConnectorResult(
-      const GURL& url,
-      const GURL& tab_url,
-      const std::string& source,
-      const std::string& destination,
-      const std::string& file_name,
-      const std::string& download_digest_sha256,
-      const std::string& mime_type,
-      const std::string& trigger,
-      const std::string& scan_id,
-      const std::string& content_transfer_method,
-      const std::string& source_email,
-      const enterprise_connectors::ContentAnalysisResponse::Result& result,
-      const int64_t content_size,
-      const safe_browsing::ReferrerChain& referrer_chain,
-      enterprise_connectors::EventResult event_result);
-
   // Notifies listeners that an analysis connector violation was bypassed.
   void OnAnalysisConnectorWarningBypassed(
       const GURL& url,
@@ -252,41 +231,6 @@
   // an empty string if the profile is not signed in.
   std::string GetProfileUserName() const;
 
-  // Notifies listeners that deep scanning detected a dangerous download.
-  void OnDangerousDeepScanningResult(
-      const GURL& download_url,
-      const GURL& tab_url,
-      const std::string& source,
-      const std::string& destination,
-      const std::string& file_name,
-      const std::string& download_digest_sha256,
-      const std::string& threat_type,
-      const std::string& mime_type,
-      const std::string& trigger,
-      const int64_t content_size,
-      const safe_browsing::ReferrerChain& referrer_chain,
-      enterprise_connectors::EventResult event_result,
-      const std::string& scan_id,
-      const std::string& content_transfer_method);
-
-  // Notifies listeners that the analysis connector detected a violation.
-  void OnSensitiveDataEvent(
-      const GURL& url,
-      const GURL& tab_url,
-      const std::string& source,
-      const std::string& destination,
-      const std::string& file_name,
-      const std::string& download_digest_sha256,
-      const std::string& mime_type,
-      const std::string& trigger,
-      const std::string& scan_id,
-      const std::string& content_transfer_method,
-      const std::string& source_email,
-      const enterprise_connectors::ContentAnalysisResponse::Result& result,
-      const int64_t content_size,
-      const safe_browsing::ReferrerChain& referrer_chain,
-      enterprise_connectors::EventResult event_result);
-
   raw_ptr<content::BrowserContext> context_;
   raw_ptr<EventRouter> event_router_ = nullptr;
   raw_ptr<enterprise_connectors::RealtimeReportingClient> reporting_client_ =
diff --git a/chrome/browser/extensions/api/safe_browsing_private/safe_browsing_private_event_router_unittest.cc b/chrome/browser/extensions/api/safe_browsing_private/safe_browsing_private_event_router_unittest.cc
index 5bf8539..29273d3 100644
--- a/chrome/browser/extensions/api/safe_browsing_private/safe_browsing_private_event_router_unittest.cc
+++ b/chrome/browser/extensions/api/safe_browsing_private/safe_browsing_private_event_router_unittest.cc
@@ -201,17 +201,6 @@
                                           "PHISHING", -201, referrer_chain);
   }
 
-  void TriggerOnDangerousDownloadEvent() {
-    safe_browsing::ReferrerChain referrer_chain;
-    referrer_chain.Add(enterprise_connectors::test::MakeReferrerChainEntry());
-    SafeBrowsingPrivateEventRouterFactory::GetForProfile(profile_)
-        ->OnDangerousDownloadEvent(
-            GURL("https://maybevil.com/warning.exe"),
-            GURL("https://maybe.evil/"), "/path/to/warning.exe",
-            "sha256_of_warning_exe", "POTENTIALLY_UNWANTED", "exe", "scan_id",
-            567, referrer_chain, enterprise_connectors::EventResult::WARNED);
-  }
-
   void TriggerOnDangerousDownloadEventBypass() {
     safe_browsing::ReferrerChain referrer_chain;
     referrer_chain.Add(enterprise_connectors::test::MakeReferrerChainEntry());
@@ -223,29 +212,6 @@
             referrer_chain);
   }
 
-  void TriggerOnSensitiveDataEvent(
-      enterprise_connectors::EventResult event_result) {
-    safe_browsing::ReferrerChain referrer_chain;
-    referrer_chain.Add(enterprise_connectors::test::MakeReferrerChainEntry());
-    enterprise_connectors::ContentAnalysisResponse::Result result;
-    result.set_tag("dlp");
-    result.set_status(
-        enterprise_connectors::ContentAnalysisResponse::Result::SUCCESS);
-    auto* rule = result.add_triggered_rules();
-    rule->set_action(enterprise_connectors::TriggeredRule::BLOCK);
-    rule->set_rule_name("fake rule");
-    rule->set_rule_id("12345");
-    rule->set_url_category("test rule category");
-
-    SafeBrowsingPrivateEventRouterFactory::GetForProfile(profile_)
-        ->OnAnalysisConnectorResult(
-            GURL(kUrl), GURL(kTabUrl), kSource, kDestination,
-            "sensitive_data.txt", "sha256_of_data", "text/plain",
-            SafeBrowsingPrivateEventRouter::kTriggerFileUpload, "scan_id",
-            "content_transfer_method", "source_email", result, 12345,
-            referrer_chain, event_result);
-  }
-
 #if BUILDFLAG(ENTERPRISE_DATA_CONTROLS)
   void TriggerOnDataControlsSensitiveDataEvent(
       const data_controls::Verdict::TriggeredRules& triggered_rules) {
@@ -592,52 +558,6 @@
       *event->FindBool(SafeBrowsingPrivateEventRouter::kKeyClickedThrough));
 }
 
-TEST_F(SafeBrowsingPrivateEventRouterTest, TestOnDangerousDownloadWarning) {
-  SetUpRouters();
-  SafeBrowsingEventObserver event_observer(
-      api::safe_browsing_private::OnDangerousDownloadOpened::kEventName);
-  event_router_->AddEventObserver(&event_observer);
-
-  base::Value::Dict report;
-  EXPECT_CALL(*client_, UploadSecurityEventReport)
-      .WillOnce(CaptureArg(&report));
-
-  TriggerOnDangerousDownloadEvent();
-  base::RunLoop().RunUntilIdle();
-
-  Mock::VerifyAndClearExpectations(client_.get());
-  const base::Value::List* event_list =
-      report.FindList(policy::RealtimeReportingJobConfiguration::kEventListKey);
-  ASSERT_NE(nullptr, event_list);
-  ASSERT_EQ(1u, event_list->size());
-  const base::Value::Dict& wrapper = (*event_list)[0].GetDict();
-  const base::Value::Dict* event =
-      wrapper.FindDict(enterprise_connectors::kKeyDangerousDownloadEvent);
-  EXPECT_NE(nullptr, event);
-#if BUILDFLAG(IS_CHROMEOS)
-  // TODO(crbug.com/1163303): To fix the tests for ChromeOS.
-  EXPECT_EQ("warning.exe",
-#else
-  EXPECT_EQ("/path/to/warning.exe",
-#endif  // BUILDFLAG(IS_CHROMEOS)
-            *event->FindString(SafeBrowsingPrivateEventRouter::kKeyFileName));
-  EXPECT_EQ("exe", *event->FindString(
-                       SafeBrowsingPrivateEventRouter::kKeyContentType));
-  EXPECT_EQ("567", *event->FindString(
-                       SafeBrowsingPrivateEventRouter::kKeyContentSize));
-  EXPECT_EQ("POTENTIALLY_UNWANTED",
-            *event->FindString(SafeBrowsingPrivateEventRouter::kKeyThreatType));
-  EXPECT_EQ(
-      enterprise_connectors::EventResultToString(
-          enterprise_connectors::EventResult::WARNED),
-      *event->FindString(SafeBrowsingPrivateEventRouter::kKeyEventResult));
-  EXPECT_EQ("scan_id",
-            *event->FindString(SafeBrowsingPrivateEventRouter::kKeyScanId));
-  const base::Value::List& referrers =
-      *event->FindList(SafeBrowsingPrivateEventRouter::kKeyReferrers);
-  EXPECT_EQ(1u, referrers.size());
-}
-
 TEST_F(SafeBrowsingPrivateEventRouterTest,
        TestOnDangerousDownloadWarningBypass) {
   SetUpRouters();
@@ -811,23 +731,6 @@
 }
 
 TEST_F(SafeBrowsingPrivateEventRouterTest,
-       TestUnauthorizedOnDangerousDownloadWarning) {
-  SetUpRouters(/*authorized=*/false);
-  SafeBrowsingEventObserver event_observer(
-      api::safe_browsing_private::OnDangerousDownloadOpened::kEventName);
-  event_router_->AddEventObserver(&event_observer);
-
-  base::Value report;
-  EXPECT_CALL(*client_, UploadSecurityEventReport).Times(0);
-
-  TriggerOnDangerousDownloadEvent();
-  base::RunLoop().RunUntilIdle();
-
-  Mock::VerifyAndClearExpectations(client_.get());
-  EXPECT_EQ(base::Value::Type::NONE, report.type());
-}
-
-TEST_F(SafeBrowsingPrivateEventRouterTest,
        TestUnauthorizedOnDangerousDownloadWarningBypass) {
   SetUpRouters(/*authorized=*/false);
   SafeBrowsingEventObserver event_observer(
@@ -844,142 +747,6 @@
   EXPECT_EQ(base::Value::Type::NONE, report.type());
 }
 
-TEST_F(SafeBrowsingPrivateEventRouterTest, TestOnSensitiveDataEvent_Allowed) {
-  SetUpRouters(/*authorized=*/true);
-
-  base::Value::Dict report;
-  EXPECT_CALL(*client_, UploadSecurityEventReport)
-      .WillOnce(CaptureArg(&report));
-
-  TriggerOnSensitiveDataEvent(enterprise_connectors::EventResult::ALLOWED);
-  base::RunLoop().RunUntilIdle();
-
-  Mock::VerifyAndClearExpectations(client_.get());
-  const base::Value::List* event_list =
-      report.FindList(policy::RealtimeReportingJobConfiguration::kEventListKey);
-  ASSERT_NE(nullptr, event_list);
-  ASSERT_EQ(1u, event_list->size());
-  const base::Value::Dict& wrapper = (*event_list)[0].GetDict();
-  const base::Value::Dict* event =
-      wrapper.FindDict(enterprise_connectors::kKeySensitiveDataEvent);
-  ASSERT_NE(nullptr, event);
-
-  EXPECT_EQ(kUrl, *event->FindString(SafeBrowsingPrivateEventRouter::kKeyUrl));
-  EXPECT_EQ(kTabUrl,
-            *event->FindString(SafeBrowsingPrivateEventRouter::kKeyTabUrl));
-  EXPECT_EQ(kSource,
-            *event->FindString(SafeBrowsingPrivateEventRouter::kKeySource));
-  EXPECT_EQ(kDestination, *event->FindString(
-                              SafeBrowsingPrivateEventRouter::kKeyDestination));
-  EXPECT_EQ("12345", *event->FindString(
-                         SafeBrowsingPrivateEventRouter::kKeyContentSize));
-  EXPECT_EQ("text/plain", *event->FindString(
-                              SafeBrowsingPrivateEventRouter::kKeyContentType));
-  EXPECT_EQ("sha256_of_data",
-            *event->FindString(
-                SafeBrowsingPrivateEventRouter::kKeyDownloadDigestSha256));
-  EXPECT_EQ("sensitive_data.txt",
-            *event->FindString(SafeBrowsingPrivateEventRouter::kKeyFileName));
-  EXPECT_EQ(SafeBrowsingPrivateEventRouter::kTriggerFileUpload,
-            *event->FindString(SafeBrowsingPrivateEventRouter::kKeyTrigger));
-  EXPECT_EQ("content_transfer_method",
-            *event->FindString(
-                SafeBrowsingPrivateEventRouter::kKeyContentTransferMethod));
-  EXPECT_EQ(
-      "source_email",
-      *event->FindString(
-          SafeBrowsingPrivateEventRouter::kKeySourceWebAppSignedInAccount));
-
-  const base::Value::List* triggered_rule_info =
-      event->FindList(SafeBrowsingPrivateEventRouter::kKeyTriggeredRuleInfo);
-  ASSERT_NE(nullptr, triggered_rule_info);
-  ASSERT_EQ(1u, triggered_rule_info->size());
-  const base::Value::Dict& triggered_rule = (*triggered_rule_info)[0].GetDict();
-  EXPECT_EQ(
-      enterprise_connectors::EventResultToString(
-          enterprise_connectors::EventResult::ALLOWED),
-      *event->FindString(SafeBrowsingPrivateEventRouter::kKeyEventResult));
-  EXPECT_EQ("fake rule",
-            *triggered_rule.FindString(
-                SafeBrowsingPrivateEventRouter::kKeyTriggeredRuleName));
-  EXPECT_EQ("test rule category",
-            *triggered_rule.FindString(
-                SafeBrowsingPrivateEventRouter::kKeyUrlCategory));
-  EXPECT_EQ("scan_id",
-            *event->FindString(SafeBrowsingPrivateEventRouter::kKeyScanId));
-  const base::Value::List* referrers =
-      event->FindList(SafeBrowsingPrivateEventRouter::kKeyReferrers);
-  EXPECT_EQ(1u, referrers->size());
-}
-
-TEST_F(SafeBrowsingPrivateEventRouterTest, TestOnSensitiveDataEvent_Blocked) {
-  SetUpRouters();
-
-  base::Value::Dict report;
-  EXPECT_CALL(*client_, UploadSecurityEventReport)
-      .WillOnce(CaptureArg(&report));
-
-  TriggerOnSensitiveDataEvent(enterprise_connectors::EventResult::BLOCKED);
-  base::RunLoop().RunUntilIdle();
-
-  Mock::VerifyAndClearExpectations(client_.get());
-  const base::Value::List* event_list =
-      report.FindList(policy::RealtimeReportingJobConfiguration::kEventListKey);
-  ASSERT_NE(nullptr, event_list);
-  ASSERT_EQ(1u, event_list->size());
-  const base::Value::Dict& wrapper = (*event_list)[0].GetDict();
-  const base::Value::Dict* event =
-      wrapper.FindDict(enterprise_connectors::kKeySensitiveDataEvent);
-  ASSERT_NE(nullptr, event);
-
-  EXPECT_EQ(kUrl, *event->FindString(SafeBrowsingPrivateEventRouter::kKeyUrl));
-  EXPECT_EQ(kTabUrl,
-            *event->FindString(SafeBrowsingPrivateEventRouter::kKeyTabUrl));
-  EXPECT_EQ(kSource,
-            *event->FindString(SafeBrowsingPrivateEventRouter::kKeySource));
-  EXPECT_EQ(kDestination, *event->FindString(
-                              SafeBrowsingPrivateEventRouter::kKeyDestination));
-  EXPECT_EQ("12345", *event->FindString(
-                         SafeBrowsingPrivateEventRouter::kKeyContentSize));
-  EXPECT_EQ("text/plain", *event->FindString(
-                              SafeBrowsingPrivateEventRouter::kKeyContentType));
-  EXPECT_EQ("sha256_of_data",
-            *event->FindString(
-                SafeBrowsingPrivateEventRouter::kKeyDownloadDigestSha256));
-  EXPECT_EQ("sensitive_data.txt",
-            *event->FindString(SafeBrowsingPrivateEventRouter::kKeyFileName));
-  EXPECT_EQ(SafeBrowsingPrivateEventRouter::kTriggerFileUpload,
-            *event->FindString(SafeBrowsingPrivateEventRouter::kKeyTrigger));
-  EXPECT_EQ("content_transfer_method",
-            *event->FindString(
-                SafeBrowsingPrivateEventRouter::kKeyContentTransferMethod));
-  EXPECT_EQ(
-      "source_email",
-      *event->FindString(
-          SafeBrowsingPrivateEventRouter::kKeySourceWebAppSignedInAccount));
-
-  const base::Value::List* triggered_rule_info =
-      event->FindList(SafeBrowsingPrivateEventRouter::kKeyTriggeredRuleInfo);
-  ASSERT_NE(nullptr, triggered_rule_info);
-  ASSERT_EQ(1u, triggered_rule_info->size());
-  const base::Value::Dict& triggered_rule = (*triggered_rule_info)[0].GetDict();
-  EXPECT_EQ(
-      enterprise_connectors::EventResultToString(
-          enterprise_connectors::EventResult::BLOCKED),
-      *event->FindString(SafeBrowsingPrivateEventRouter::kKeyEventResult));
-  EXPECT_EQ("fake rule",
-            *triggered_rule.FindString(
-                SafeBrowsingPrivateEventRouter::kKeyTriggeredRuleName));
-  EXPECT_EQ("test rule category",
-            *triggered_rule.FindString(
-                SafeBrowsingPrivateEventRouter::kKeyUrlCategory));
-  EXPECT_EQ("scan_id",
-            *event->FindString(SafeBrowsingPrivateEventRouter::kKeyScanId));
-  const base::Value::List* referrers =
-      event->FindList(SafeBrowsingPrivateEventRouter::kKeyReferrers);
-  EXPECT_EQ(1u, referrers->size());
-}
-
 TEST_F(SafeBrowsingPrivateEventRouterTest, TestProfileUsername) {
   SetUpRouters();
   SafeBrowsingEventObserver event_observer(
@@ -1080,8 +847,7 @@
       api::safe_browsing_private::OnDangerousDownloadOpened::kEventName);
   event_router_->AddEventObserver(&event_observer);
 
-  EXPECT_CALL(*client_, UploadSecurityEventReport).Times(3);
-  TriggerOnDangerousDownloadEvent();
+  EXPECT_CALL(*client_, UploadSecurityEventReport).Times(2);
   TriggerOnDangerousDownloadEventBypass();
   TriggerOnDangerousDownloadOpenedEvent();
   base::RunLoop().RunUntilIdle();
@@ -1121,21 +887,6 @@
   Mock::VerifyAndClearExpectations(client_.get());
 }
 
-TEST_F(SafeBrowsingPrivateEventRouterTest, TestSensitiveDataEnabled) {
-  std::set<std::string> enabled_event_names;
-  enabled_event_names.insert(enterprise_connectors::kKeySensitiveDataEvent);
-  SetUpRouters(/*authorized=*/true, /*realtime_reporting_enable=*/true,
-               enabled_event_names);
-
-  EXPECT_CALL(*client_, UploadSecurityEventReport).Times(1);
-  TriggerOnSensitiveDataEvent(enterprise_connectors::EventResult::BLOCKED);
-  base::RunLoop().RunUntilIdle();
-
-  // Make sure UploadSecurityEventReport was called the expected number of
-  // times.
-  Mock::VerifyAndClearExpectations(client_.get());
-}
-
 #if BUILDFLAG(ENTERPRISE_DATA_CONTROLS)
 TEST_F(SafeBrowsingPrivateEventRouterTest, TestDataControlsSensitiveDataEvent) {
   SetUpRouters();
@@ -1176,10 +927,9 @@
   EXPECT_EQ(*event->FindString(SafeBrowsingPrivateEventRouter::kKeyEventResult),
             enterprise_connectors::EventResultToString(
                 enterprise_connectors::EventResult::BLOCKED));
-  EXPECT_EQ(
-      *event->FindString(
-          SafeBrowsingPrivateEventRouter::kKeySourceWebAppSignedInAccount),
-      "active_user@gmail.com");
+  EXPECT_EQ(*event->FindString(
+                enterprise_connectors::kKeySourceWebAppSignedInAccount),
+            "active_user@gmail.com");
 
   const base::Value::List* triggered_rule_info =
       event->FindList(SafeBrowsingPrivateEventRouter::kKeyTriggeredRuleInfo);
@@ -1351,9 +1101,7 @@
   TriggerOnDangerousDownloadOpenedEvent();
   TriggerOnSecurityInterstitialShownEvent();
   TriggerOnSecurityInterstitialProceededEvent();
-  TriggerOnDangerousDownloadEvent();
   TriggerOnDangerousDownloadEventBypass();
-  TriggerOnSensitiveDataEvent(enterprise_connectors::EventResult::BLOCKED);
 
   base::RunLoop().RunUntilIdle();
 
@@ -1376,8 +1124,7 @@
         testing::make_tuple(enterprise_connectors::kKeyPasswordChangedEvent, 1),
         testing::make_tuple(enterprise_connectors::kKeyPasswordReuseEvent, 1),
         testing::make_tuple(enterprise_connectors::kKeyDangerousDownloadEvent,
-                            3),
-        testing::make_tuple(enterprise_connectors::kKeyInterstitialEvent, 2),
-        testing::make_tuple(enterprise_connectors::kKeySensitiveDataEvent, 1)));
+                            2),
+        testing::make_tuple(enterprise_connectors::kKeyInterstitialEvent, 2)));
 
 }  // namespace extensions
diff --git a/chrome/browser/extensions/api/tabs/tabs_api.cc b/chrome/browser/extensions/api/tabs/tabs_api.cc
index 741bc78c..7df6d4dd 100644
--- a/chrome/browser/extensions/api/tabs/tabs_api.cc
+++ b/chrome/browser/extensions/api/tabs/tabs_api.cc
@@ -4,6 +4,7 @@
 
 #include "chrome/browser/extensions/api/tabs/tabs_api.h"
 
+#include "base/strings/string_number_conversions.h"
 #include "chrome/browser/extensions/api/tabs/tabs_constants.h"
 #include "chrome/browser/extensions/api/tabs/windows_util.h"
 #include "chrome/browser/extensions/browser_extension_window_controller.h"
@@ -11,7 +12,10 @@
 #include "chrome/browser/extensions/extension_tab_util.h"
 #include "chrome/browser/extensions/window_controller.h"
 #include "chrome/browser/extensions/window_controller_list.h"
+#include "chrome/browser/ui/browser_window/public/browser_window_interface.h"
 #include "chrome/browser/ui/browser_window/public/browser_window_interface_iterator.h"
+#include "chrome/browser/ui/tabs/tab_list_interface.h"
+#include "components/tabs/public/tab_interface.h"
 #include "extensions/common/extension.h"
 #include "extensions/common/mojom/api_permission_id.mojom-shared.h"
 #include "extensions/common/permissions/permissions_data.h"
@@ -33,6 +37,39 @@
                           mojom::APIPermissionID::kLockWindowFullscreenPrivate);
 }
 
+api::tabs::Tab CreateTabObjectHelper(content::WebContents* contents,
+                                     const Extension* extension,
+                                     mojom::ContextType context,
+                                     BrowserWindowInterface* browser,
+                                     int tab_index) {
+  ExtensionTabUtil::ScrubTabBehavior scrub_tab_behavior =
+      ExtensionTabUtil::GetScrubTabBehavior(extension, context, contents);
+  TabListInterface* tab_list =
+      browser ? TabListInterface::From(browser) : nullptr;
+  return ExtensionTabUtil::CreateTabObject(contents, scrub_tab_behavior,
+                                           extension, tab_list, tab_index);
+}
+
+bool GetTabById(int tab_id,
+                content::BrowserContext* context,
+                bool include_incognito,
+                WindowController** window_out,
+                content::WebContents** contents_out,
+                int* index_out,
+                std::string* error_out) {
+  if (ExtensionTabUtil::GetTabById(tab_id, context, include_incognito,
+                                   window_out, contents_out, index_out)) {
+    return true;
+  }
+
+  if (error_out) {
+    *error_out = ErrorUtils::FormatErrorMessage(
+        ExtensionTabUtil::kTabNotFoundError, base::NumberToString(tab_id));
+  }
+
+  return false;
+}
+
 }  // namespace tabs_internal
 
 ExtensionFunction::ResponseAction WindowsGetFunction::Run() {
@@ -112,6 +149,31 @@
   return RespondNow(WithArguments(std::move(windows)));
 }
 
+ExtensionFunction::ResponseAction WindowsGetAllFunction::Run() {
+  std::optional<windows::GetAll::Params> params =
+      windows::GetAll::Params::Create(args());
+  EXTENSION_FUNCTION_VALIDATE(params);
+
+  tabs_internal::ApiParameterExtractor<windows::GetAll::Params> extractor(
+      params);
+  base::Value::List window_list;
+  WindowController::PopulateTabBehavior populate_tab_behavior =
+      extractor.populate_tabs() ? WindowController::kPopulateTabs
+                                : WindowController::kDontPopulateTabs;
+  for (WindowController* controller : *WindowControllerList::GetInstance()) {
+    if (!controller->GetBrowserWindowInterface() ||
+        !windows_util::CanOperateOnWindow(this, controller,
+                                          extractor.type_filters())) {
+      continue;
+    }
+    window_list.Append(ExtensionTabUtil::CreateWindowValueForExtension(
+        *controller->GetBrowserWindowInterface(), extension(),
+        populate_tab_behavior, source_context_type()));
+  }
+
+  return RespondNow(WithArguments(std::move(window_list)));
+}
+
 ExtensionFunction::ResponseAction WindowsRemoveFunction::Run() {
   std::optional<windows::Remove::Params> params =
       windows::Remove::Params::Create(args());
@@ -147,6 +209,66 @@
   return RespondNow(NoArguments());
 }
 
+ExtensionFunction::ResponseAction TabsGetFunction::Run() {
+  std::optional<tabs::Get::Params> params = tabs::Get::Params::Create(args());
+  EXTENSION_FUNCTION_VALIDATE(params);
+  int tab_id = params->tab_id;
+
+  WindowController* window = nullptr;
+  content::WebContents* contents = nullptr;
+  int tab_index = -1;
+  std::string error;
+  if (!tabs_internal::GetTabById(tab_id, browser_context(),
+                                 include_incognito_information(), &window,
+                                 &contents, &tab_index, &error)) {
+    return RespondNow(Error(std::move(error)));
+  }
+
+  return RespondNow(ArgumentList(
+      tabs::Get::Results::Create(tabs_internal::CreateTabObjectHelper(
+          contents, extension(), source_context_type(),
+          window ? window->GetBrowserWindowInterface() : nullptr, tab_index))));
+}
+
+ExtensionFunction::ResponseAction TabsGetSelectedFunction::Run() {
+  // windowId defaults to "current" window.
+  int window_id = extension_misc::kCurrentWindowId;
+
+  std::optional<tabs::GetSelected::Params> params =
+      tabs::GetSelected::Params::Create(args());
+  EXTENSION_FUNCTION_VALIDATE(params);
+  if (params->window_id) {
+    window_id = *params->window_id;
+  }
+
+  std::string error;
+  WindowController* window_controller =
+      ExtensionTabUtil::GetControllerFromWindowID(
+          ChromeExtensionFunctionDetails(this), window_id, &error);
+  if (!window_controller) {
+    return RespondNow(Error(std::move(error)));
+  }
+
+  BrowserWindowInterface* browser =
+      window_controller->GetBrowserWindowInterface();
+  if (!browser) {
+    return RespondNow(Error(ExtensionTabUtil::kNoCrashBrowserError));
+  }
+  TabListInterface* tab_list = ExtensionTabUtil::GetEditableTabList(*browser);
+  if (!tab_list) {
+    return RespondNow(Error(ExtensionTabUtil::kTabStripNotEditableError));
+  }
+  ::tabs::TabInterface* tab = tab_list->GetActiveTab();
+  if (!tab) {
+    return RespondNow(Error(tabs_constants::kNoSelectedTabError));
+  }
+
+  return RespondNow(ArgumentList(
+      tabs::Get::Results::Create(tabs_internal::CreateTabObjectHelper(
+          tab->GetContents(), extension(), source_context_type(), browser,
+          tab_list->GetActiveIndex()))));
+}
+
 ExtensionFunction::ResponseAction TabsGetAllInWindowFunction::Run() {
   std::optional<tabs::GetAllInWindow::Params> params =
       tabs::GetAllInWindow::Params::Create(args());
diff --git a/chrome/browser/extensions/api/tabs/tabs_api.h b/chrome/browser/extensions/api/tabs/tabs_api.h
index ebedadd..7e01d392 100644
--- a/chrome/browser/extensions/api/tabs/tabs_api.h
+++ b/chrome/browser/extensions/api/tabs/tabs_api.h
@@ -29,6 +29,7 @@
 #include "chrome/browser/extensions/chrome_extension_function_details.h"
 #endif
 
+class BrowserWindowInterface;
 class GURL;
 class SkBitmap;
 class TabStripModel;
@@ -93,6 +94,27 @@
 // fullscreen permission.
 bool ExtensionHasLockedFullscreenPermission(const Extension* extension);
 
+// Helper method to generate a new tab object for the given `contents`,
+// appropriately scrubbed of data for the given `extension`.
+api::tabs::Tab CreateTabObjectHelper(content::WebContents* contents,
+                                     const Extension* extension,
+                                     mojom::ContextType context,
+                                     BrowserWindowInterface* browser,
+                                     int tab_index);
+
+// Retrieves the tab associated with the given `tab_id`, populating
+// `contents_out`, `window_out`, and `index_out` with the result. If the tab
+// isn't found and `error_out` is non-null, populates `error_out` with an
+// appropriate error.
+// Returns true if the tab was found.
+bool GetTabById(int tab_id,
+                content::BrowserContext* context,
+                bool include_incognito,
+                WindowController** window_out,
+                content::WebContents** contents_out,
+                int* index_out,
+                std::string* error_out);
+
 }  // namespace tabs_internal
 
 // Converts a ZoomMode to its ZoomSettings representation.
diff --git a/chrome/browser/extensions/api/tabs/tabs_api_android.cc b/chrome/browser/extensions/api/tabs/tabs_api_android.cc
index 2ea0bff..b9cc9659 100644
--- a/chrome/browser/extensions/api/tabs/tabs_api_android.cc
+++ b/chrome/browser/extensions/api/tabs/tabs_api_android.cc
@@ -53,13 +53,6 @@
 
 // Windows ---------------------------------------------------------------------
 
-ExtensionFunction::ResponseAction WindowsGetAllFunction::Run() {
-  std::optional<windows::GetAll::Params> params =
-      windows::GetAll::Params::Create(args());
-  EXTENSION_FUNCTION_VALIDATE(params);
-  return RespondNow(Error(kWindowsNotImplemented));
-}
-
 ExtensionFunction::ResponseAction WindowsCreateFunction::Run() {
   std::optional<windows::Create::Params> params =
       windows::Create::Params::Create(args());
@@ -76,13 +69,6 @@
 
 // Tabs ------------------------------------------------------------------------
 
-ExtensionFunction::ResponseAction TabsGetSelectedFunction::Run() {
-  std::optional<tabs::GetSelected::Params> params =
-      tabs::GetSelected::Params::Create(args());
-  EXTENSION_FUNCTION_VALIDATE(params);
-  return RespondNow(Error(kTabsNotImplemented));
-}
-
 ExtensionFunction::ResponseAction TabsQueryFunction::Run() {
   std::optional<tabs::Query::Params> params =
       tabs::Query::Params::Create(args());
@@ -206,12 +192,6 @@
   return RespondNow(Error(kTabsNotImplemented));
 }
 
-ExtensionFunction::ResponseAction TabsGetFunction::Run() {
-  std::optional<tabs::Get::Params> params = tabs::Get::Params::Create(args());
-  EXTENSION_FUNCTION_VALIDATE(params);
-  return RespondNow(Error(kTabsNotImplemented));
-}
-
 ExtensionFunction::ResponseAction TabsGetCurrentFunction::Run() {
   return RespondNow(Error(kTabsNotImplemented));
 }
diff --git a/chrome/browser/extensions/api/tabs/tabs_api_non_android.cc b/chrome/browser/extensions/api/tabs/tabs_api_non_android.cc
index 33d8dab..f85cc91 100644
--- a/chrome/browser/extensions/api/tabs/tabs_api_non_android.cc
+++ b/chrome/browser/extensions/api/tabs/tabs_api_non_android.cc
@@ -185,28 +185,6 @@
 constexpr char kWindowCreateCannotMoveIwaTabError[] =
     "The tab of an Isolated Web App cannot be moved to a new window.";
 
-// |error_message| can optionally be passed in and will be set with an
-// appropriate message if the tab cannot be found by id.
-bool GetTabById(int tab_id,
-                content::BrowserContext* context,
-                bool include_incognito,
-                WindowController** window,
-                content::WebContents** contents,
-                int* tab_index,
-                std::string* error_message) {
-  if (ExtensionTabUtil::GetTabById(tab_id, context, include_incognito, window,
-                                   contents, tab_index)) {
-    return true;
-  }
-
-  if (error_message) {
-    *error_message = ErrorUtils::FormatErrorMessage(
-        ExtensionTabUtil::kTabNotFoundError, base::NumberToString(tab_id));
-  }
-
-  return false;
-}
-
 // Returns the last active browser with the given `profile`. If
 // `include_incognito_information` is true, this will also return a browser
 // that crosses the incognito boundary.
@@ -235,10 +213,10 @@
   content::WebContents* web_contents = nullptr;
   if (tab_id != -1) {
     // We assume this call leaves web_contents unchanged if it is unsuccessful.
-    GetTabById(tab_id, function->browser_context(),
-               function->include_incognito_information(),
-               /*window=*/nullptr, &web_contents,
-               /*tab_index=*/nullptr, error);
+    tabs_internal::GetTabById(tab_id, function->browser_context(),
+                              function->include_incognito_information(),
+                              /*window_out=*/nullptr, &web_contents,
+                              /*index_out=*/nullptr, error);
   } else {
     WindowController* window_controller =
         ChromeExtensionFunctionDetails(function).GetCurrentWindowController();
@@ -304,19 +282,6 @@
   NOTREACHED();
 }
 
-api::tabs::Tab CreateTabObjectHelper(WebContents* contents,
-                                     const Extension* extension,
-                                     mojom::ContextType context,
-                                     BrowserWindowInterface* browser,
-                                     int tab_index) {
-  ExtensionTabUtil::ScrubTabBehavior scrub_tab_behavior =
-      ExtensionTabUtil::GetScrubTabBehavior(extension, context, contents);
-  TabListInterface* tab_list =
-      browser ? TabListInterface::From(browser) : nullptr;
-  return ExtensionTabUtil::CreateTabObject(contents, scrub_tab_behavior,
-                                           extension, tab_list, tab_index);
-}
-
 // Moves the given tab to the |target_browser|. On success, returns the
 // new index of the tab in the target tabstrip. On failure, returns -1.
 // Assumes that the caller has already checked whether the target window is
@@ -328,9 +293,10 @@
                     std::string* error) {
   WindowController* source_window = nullptr;
   int source_index = -1;
-  if (!GetTabById(tab_id, function->browser_context(),
-                  function->include_incognito_information(), &source_window,
-                  nullptr, &source_index, error) ||
+  if (!tabs_internal::GetTabById(tab_id, function->browser_context(),
+                                 function->include_incognito_information(),
+                                 &source_window, nullptr, &source_index,
+                                 error) ||
       !source_window) {
     return -1;
   }
@@ -516,31 +482,6 @@
 
 // Windows ---------------------------------------------------------------------
 
-ExtensionFunction::ResponseAction WindowsGetAllFunction::Run() {
-  std::optional<windows::GetAll::Params> params =
-      windows::GetAll::Params::Create(args());
-  EXTENSION_FUNCTION_VALIDATE(params);
-
-  tabs_internal::ApiParameterExtractor<windows::GetAll::Params> extractor(
-      params);
-  base::Value::List window_list;
-  WindowController::PopulateTabBehavior populate_tab_behavior =
-      extractor.populate_tabs() ? WindowController::kPopulateTabs
-                                : WindowController::kDontPopulateTabs;
-  for (WindowController* controller : *WindowControllerList::GetInstance()) {
-    if (!controller->GetBrowser() ||
-        !windows_util::CanOperateOnWindow(this, controller,
-                                          extractor.type_filters())) {
-      continue;
-    }
-    window_list.Append(ExtensionTabUtil::CreateWindowValueForExtension(
-        *controller->GetBrowser(), extension(), populate_tab_behavior,
-        source_context_type()));
-  }
-
-  return RespondNow(WithArguments(std::move(window_list)));
-}
-
 ExtensionFunction::ResponseAction WindowsCreateFunction::Run() {
   std::optional<windows::Create::Params> params =
       windows::Create::Params::Create(args());
@@ -619,9 +560,10 @@
     // Find the tab. `tab_index` will later be used to move the tab into the
     // created window.
     content::WebContents* web_contents = nullptr;
-    if (!GetTabById(*create_data->tab_id, calling_profile,
-                    include_incognito_information(), &source_window,
-                    &web_contents, &tab_index, &error)) {
+    if (!tabs_internal::GetTabById(*create_data->tab_id, calling_profile,
+                                   include_incognito_information(),
+                                   &source_window, &web_contents, &tab_index,
+                                   &error)) {
       return RespondNow(Error(std::move(error)));
     }
     if (!source_window) {
@@ -1086,44 +1028,6 @@
 
 // Tabs ------------------------------------------------------------------------
 
-ExtensionFunction::ResponseAction TabsGetSelectedFunction::Run() {
-  // windowId defaults to "current" window.
-  int window_id = extension_misc::kCurrentWindowId;
-
-  std::optional<tabs::GetSelected::Params> params =
-      tabs::GetSelected::Params::Create(args());
-  EXTENSION_FUNCTION_VALIDATE(params);
-  if (params->window_id) {
-    window_id = *params->window_id;
-  }
-
-  std::string error;
-  WindowController* window_controller =
-      ExtensionTabUtil::GetControllerFromWindowID(
-          ChromeExtensionFunctionDetails(this), window_id, &error);
-  if (!window_controller) {
-    return RespondNow(Error(std::move(error)));
-  }
-
-  Browser* browser = window_controller->GetBrowser();
-  if (!browser) {
-    return RespondNow(Error(ExtensionTabUtil::kNoCrashBrowserError));
-  }
-  TabStripModel* tab_strip =
-      ExtensionTabUtil::GetEditableTabStripModel(browser);
-  if (!tab_strip) {
-    return RespondNow(Error(ExtensionTabUtil::kTabStripNotEditableError));
-  }
-  WebContents* contents = tab_strip->GetActiveWebContents();
-  if (!contents) {
-    return RespondNow(Error(tabs_constants::kNoSelectedTabError));
-  }
-
-  return RespondNow(ArgumentList(tabs::Get::Results::Create(
-      CreateTabObjectHelper(contents, extension(), source_context_type(),
-                            browser, tab_strip->active_index()))));
-}
-
 ExtensionFunction::ResponseAction TabsQueryFunction::Run() {
   std::optional<tabs::Query::Params> params =
       tabs::Query::Params::Create(args());
@@ -1338,9 +1242,10 @@
         continue;
       }
 
-      result.Append(CreateTabObjectHelper(web_contents, extension(),
-                                          source_context_type(), browser, i)
-                        .ToValue());
+      result.Append(
+          tabs_internal::CreateTabObjectHelper(
+              web_contents, extension(), source_context_type(), browser, i)
+              .ToValue());
     }
   }
 
@@ -1394,8 +1299,9 @@
   WindowController* window = nullptr;
   int tab_index = -1;
   std::string error;
-  if (!GetTabById(tab_id, browser_context(), include_incognito_information(),
-                  &window, nullptr, &tab_index, &error)) {
+  if (!tabs_internal::GetTabById(tab_id, browser_context(),
+                                 include_incognito_information(), &window,
+                                 nullptr, &tab_index, &error)) {
     return RespondNow(Error(std::move(error)));
   }
   if (!window) {
@@ -1439,26 +1345,6 @@
           new_tab_index))));
 }
 
-ExtensionFunction::ResponseAction TabsGetFunction::Run() {
-  std::optional<tabs::Get::Params> params = tabs::Get::Params::Create(args());
-  EXTENSION_FUNCTION_VALIDATE(params);
-  int tab_id = params->tab_id;
-
-  WindowController* window = nullptr;
-  WebContents* contents = nullptr;
-  int tab_index = -1;
-  std::string error;
-  if (!GetTabById(tab_id, browser_context(), include_incognito_information(),
-                  &window, &contents, &tab_index, &error)) {
-    return RespondNow(Error(std::move(error)));
-  }
-
-  return RespondNow(
-      ArgumentList(tabs::Get::Results::Create(CreateTabObjectHelper(
-          contents, extension(), source_context_type(),
-          window ? window->GetBrowserWindowInterface() : nullptr, tab_index))));
-}
-
 ExtensionFunction::ResponseAction TabsGetCurrentFunction::Run() {
   DCHECK(dispatcher());
 
@@ -1466,9 +1352,10 @@
   // empty tab (hence returning true).
   WebContents* caller_contents = GetSenderWebContents();
   if (caller_contents && ExtensionTabUtil::GetTabId(caller_contents) >= 0) {
-    return RespondNow(ArgumentList(tabs::Get::Results::Create(
-        CreateTabObjectHelper(caller_contents, extension(),
-                              source_context_type(), nullptr, -1))));
+    return RespondNow(ArgumentList(
+        tabs::Get::Results::Create(tabs_internal::CreateTabObjectHelper(
+            caller_contents, extension(), source_context_type(), nullptr,
+            -1))));
   }
   return RespondNow(NoArguments());
 }
@@ -1594,8 +1481,9 @@
   int tab_index = -1;
   WindowController* window = nullptr;
   std::string error;
-  if (!GetTabById(tab_id, browser_context(), include_incognito_information(),
-                  &window, &contents, &tab_index, &error)) {
+  if (!tabs_internal::GetTabById(tab_id, browser_context(),
+                                 include_incognito_information(), &window,
+                                 &contents, &tab_index, &error)) {
     return RespondNow(Error(std::move(error)));
   }
 
@@ -1603,7 +1491,7 @@
     return RespondNow(Error(tabs_constants::kNotAllowedForDevToolsError));
   }
 
-  // GetTabById may return a null window for prerender tabs.
+  // tabs_internal::GetTabById may return a null window for prerender tabs.
   if (!window || !window->SupportsTabs()) {
     return RespondNow(Error(ExtensionTabUtil::kNoCurrentWindowError));
   }
@@ -1787,8 +1675,9 @@
     return NoArguments();
   }
 
-  return ArgumentList(tabs::Get::Results::Create(CreateTabObjectHelper(
-      web_contents_, extension(), source_context_type(), nullptr, -1)));
+  return ArgumentList(
+      tabs::Get::Results::Create(tabs_internal::CreateTabObjectHelper(
+          web_contents_, extension(), source_context_type(), nullptr, -1)));
 }
 
 ExtensionFunction::ResponseAction TabsMoveFunction::Run() {
@@ -1845,8 +1734,9 @@
   WindowController* source_window = nullptr;
   WebContents* contents = nullptr;
   int tab_index = -1;
-  if (!GetTabById(tab_id, browser_context(), include_incognito_information(),
-                  &source_window, &contents, &tab_index, error) ||
+  if (!tabs_internal::GetTabById(
+          tab_id, browser_context(), include_incognito_information(),
+          &source_window, &contents, &tab_index, error) ||
       !source_window) {
     return false;
   }
@@ -1883,9 +1773,9 @@
       content::WebContents* web_contents =
           target_controller->GetWebContentsAt(inserted_index);
 
-      tab_values.Append(CreateTabObjectHelper(web_contents, extension(),
-                                              source_context_type(),
-                                              target_browser, inserted_index)
+      tab_values.Append(tabs_internal::CreateTabObjectHelper(
+                            web_contents, extension(), source_context_type(),
+                            target_browser, inserted_index)
                             .ToValue());
     }
 
@@ -1911,11 +1801,11 @@
   }
 
   if (has_callback()) {
-    tab_values.Append(
-        CreateTabObjectHelper(contents, extension(), source_context_type(),
-                              source_window->GetBrowserWindowInterface(),
-                              *new_index)
-            .ToValue());
+    tab_values.Append(tabs_internal::CreateTabObjectHelper(
+                          contents, extension(), source_context_type(),
+                          source_window->GetBrowserWindowInterface(),
+                          *new_index)
+                          .ToValue());
   }
 
   // Insert the tabs one after another.
@@ -1951,8 +1841,9 @@
     int tab_id = *params->tab_id;
 
     std::string error;
-    if (!GetTabById(tab_id, browser_context(), include_incognito_information(),
-                    nullptr, &web_contents, nullptr, &error)) {
+    if (!tabs_internal::GetTabById(tab_id, browser_context(),
+                                   include_incognito_information(), nullptr,
+                                   &web_contents, nullptr, &error)) {
       return RespondNow(Error(std::move(error)));
     }
   }
@@ -2002,8 +1893,9 @@
 bool TabsRemoveFunction::RemoveTab(int tab_id, std::string* error) {
   WindowController* window = nullptr;
   WebContents* contents = nullptr;
-  if (!GetTabById(tab_id, browser_context(), include_incognito_information(),
-                  &window, &contents, nullptr, error) ||
+  if (!tabs_internal::GetTabById(tab_id, browser_context(),
+                                 include_incognito_information(), &window,
+                                 &contents, nullptr, error) ||
       !window) {
     return false;
   }
@@ -2133,8 +2025,9 @@
   for (int tab_id : tab_ids) {
     WindowController* tab_window = nullptr;
     content::WebContents* web_contents = nullptr;
-    if (!GetTabById(tab_id, browser_context(), include_incognito_information(),
-                    &tab_window, &web_contents, nullptr, &error)) {
+    if (!tabs_internal::GetTabById(tab_id, browser_context(),
+                                   include_incognito_information(), &tab_window,
+                                   &web_contents, nullptr, &error)) {
       return RespondNow(Error(std::move(error)));
     }
     if (tab_window) {
@@ -2164,9 +2057,10 @@
   tab_indices.reserve(tab_ids.size());
   for (int tab_id : tab_ids) {
     int tab_index = -1;
-    if (!GetTabById(tab_id, browser_context(), include_incognito_information(),
-                    /*window=*/nullptr, /*contents=*/nullptr, &tab_index,
-                    &error)) {
+    if (!tabs_internal::GetTabById(
+            tab_id, browser_context(), include_incognito_information(),
+            /*window_out=*/nullptr, /*contents_out=*/nullptr, &tab_index,
+            &error)) {
       return RespondNow(Error(std::move(error)));
     }
     tab_indices.push_back(tab_index);
@@ -2225,8 +2119,9 @@
 bool TabsUngroupFunction::UngroupTab(int tab_id, std::string* error) {
   WindowController* window = nullptr;
   int tab_index = -1;
-  if (!GetTabById(tab_id, browser_context(), include_incognito_information(),
-                  &window, nullptr, &tab_index, error) ||
+  if (!tabs_internal::GetTabById(tab_id, browser_context(),
+                                 include_incognito_information(), &window,
+                                 nullptr, &tab_index, error) ||
       !window) {
     return false;
   }
@@ -2443,9 +2338,9 @@
   if (params->tab_id) {
     WindowController* window = nullptr;
     std::string error;
-    if (!GetTabById(*params->tab_id, browser_context(),
-                    include_incognito_information(), &window, &contents,
-                    nullptr, &error)) {
+    if (!tabs_internal::GetTabById(*params->tab_id, browser_context(),
+                                   include_incognito_information(), &window,
+                                   &contents, nullptr, &error)) {
       return RespondNow(Error(std::move(error)));
     }
     // The window will be null for prerender tabs.
@@ -2615,9 +2510,9 @@
   // If |tab_id| is specified, look for the tab. Otherwise default to selected
   // tab in the current window.
   CHECK_GE(execute_tab_id_, 0);
-  if (!GetTabById(execute_tab_id_, browser_context(),
-                  include_incognito_information(), nullptr, &contents, nullptr,
-                  error)) {
+  if (!tabs_internal::GetTabById(execute_tab_id_, browser_context(),
+                                 include_incognito_information(), nullptr,
+                                 &contents, nullptr, error)) {
     return false;
   }
 
@@ -2674,10 +2569,11 @@
   WindowController* window = nullptr;
   content::WebContents* contents = nullptr;
 
-  bool success = GetTabById(execute_tab_id_, browser_context(),
-                            include_incognito_information(), &window, &contents,
-                            nullptr, error) &&
-                 contents && window;
+  bool success =
+      tabs_internal::GetTabById(execute_tab_id_, browser_context(),
+                                include_incognito_information(), &window,
+                                &contents, nullptr, error) &&
+      contents && window;
 
   if (!success) {
     return nullptr;
@@ -2850,8 +2746,9 @@
   if (params->tab_id) {
     int tab_id = *params->tab_id;
     std::string error;
-    if (!GetTabById(tab_id, browser_context(), include_incognito_information(),
-                    nullptr, &contents, nullptr, &error)) {
+    if (!tabs_internal::GetTabById(tab_id, browser_context(),
+                                   include_incognito_information(), nullptr,
+                                   &contents, nullptr, &error)) {
       return RespondNow(Error(std::move(error)));
     }
 
@@ -2874,8 +2771,8 @@
                                 : kCannotFindTabToDiscard));
   }
 
-  return RespondNow(
-      ArgumentList(tabs::Discard::Results::Create(CreateTabObjectHelper(
+  return RespondNow(ArgumentList(
+      tabs::Discard::Results::Create(tabs_internal::CreateTabObjectHelper(
           contents, extension(), source_context_type(), nullptr, -1))));
 }
 
diff --git a/chrome/browser/extensions/desktop_android/OWNERS b/chrome/browser/extensions/desktop_android/OWNERS
deleted file mode 100644
index aad0b92..0000000
--- a/chrome/browser/extensions/desktop_android/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-rdevlin.cronin@chromium.org
diff --git a/chrome/browser/extensions/desktop_android/README.md b/chrome/browser/extensions/desktop_android/README.md
deleted file mode 100644
index c85b21e1..0000000
--- a/chrome/browser/extensions/desktop_android/README.md
+++ /dev/null
@@ -1,16 +0,0 @@
-This directory represents code specific to the experimental desktop android
-extensions platform, tracked in https://crbug.com/356905053.
-
-The majority of code in this directory should be considered *temporary*.
-Desktop-android should leverage the same code as other desktop platforms in
-common cases. This code is only necessary while we continue refactoring to
-allow more pieces of the extensions system to compile on desktop-android.
-During this refactoring period, it is valuable to have functional runtime where
-we exercise and test extension capabilities; this directory provides those
-components.
-
-Code should only be added here if it is either:
-a) intentionally temporary and only meant to last during this experimental
-   phase, or
-b) code which necessarily needs to differ between desktop-android and other
-   desktop platforms.
diff --git a/chrome/browser/extensions/desktop_android/desktop_android_extensions_browsertest.cc b/chrome/browser/extensions/desktop_android_extensions_browsertest.cc
similarity index 99%
rename from chrome/browser/extensions/desktop_android/desktop_android_extensions_browsertest.cc
rename to chrome/browser/extensions/desktop_android_extensions_browsertest.cc
index 77e06a0..04b6f7e 100644
--- a/chrome/browser/extensions/desktop_android/desktop_android_extensions_browsertest.cc
+++ b/chrome/browser/extensions/desktop_android_extensions_browsertest.cc
@@ -28,6 +28,8 @@
 
 namespace extensions {
 
+// Smoke tests for the experimental desktop Android build, which supports
+// extensions. See https://crbug.com/356905053
 class DesktopAndroidExtensionsBrowserTest : public AndroidBrowserTest {
  public:
   DesktopAndroidExtensionsBrowserTest() = default;
diff --git a/chrome/browser/extensions/extension_tab_util.cc b/chrome/browser/extensions/extension_tab_util.cc
index 158578a..973da1f 100644
--- a/chrome/browser/extensions/extension_tab_util.cc
+++ b/chrome/browser/extensions/extension_tab_util.cc
@@ -1482,7 +1482,6 @@
   }));
 }
 
-#if !BUILDFLAG(IS_ANDROID)
 // static
 bool ExtensionTabUtil::IsTabStripEditable() {
   // See comments in the header for why we need to check all of them.
@@ -1494,6 +1493,15 @@
   return true;
 }
 
+TabListInterface* ExtensionTabUtil::GetEditableTabList(
+    BrowserWindowInterface& browser) {
+  if (!IsTabStripEditable()) {
+    return nullptr;
+  }
+  return TabListInterface::From(&browser);
+}
+
+#if !BUILDFLAG(IS_ANDROID)
 // static
 TabStripModel* ExtensionTabUtil::GetEditableTabStripModel(Browser* browser) {
   if (!IsTabStripEditable())
diff --git a/chrome/browser/extensions/extension_tab_util.h b/chrome/browser/extensions/extension_tab_util.h
index f6d224c..08df41a2 100644
--- a/chrome/browser/extensions/extension_tab_util.h
+++ b/chrome/browser/extensions/extension_tab_util.h
@@ -372,13 +372,20 @@
   // contexts.
   static void ClearBackForwardCache();
 
-#if !BUILDFLAG(IS_ANDROID)
   // Check TabStripModel editability in every browser because a drag session
   // could be running in another browser that reverts to the current browser. Or
   // a drag could be mid-handoff if from one browser to another.
   static bool IsTabStripEditable();
 
+  // Retrieve the corresponding TabListInterface for the specified `browser` if
+  // and only if every browser's tab list is editable. See comments above
+  // IsTabStripEditable() for details.
+  static TabListInterface* GetEditableTabList(BrowserWindowInterface& browser);
+
+#if !BUILDFLAG(IS_ANDROID)
   // Retrieve a TabStripModel only if every browser is editable.
+  // TODO(https://crbug.com/430344931): Remove this in favor of
+  // GetEditableTabList().
   static TabStripModel* GetEditableTabStripModel(Browser* browser);
 
   static bool TabIsInSavedTabGroup(content::WebContents* contents,
diff --git a/chrome/browser/extensions/sync/account_extension_tracker.cc b/chrome/browser/extensions/sync/account_extension_tracker.cc
index e6257f3a..2f80019 100644
--- a/chrome/browser/extensions/sync/account_extension_tracker.cc
+++ b/chrome/browser/extensions/sync/account_extension_tracker.cc
@@ -378,9 +378,8 @@
     AccountExtensionType type) {
   // Make sure we're actually promoting a local extension to an account
   // extension!
-  DCHECK_EQ(GetAccountExtensionType(extension_id),
-            AccountExtensionType::kLocal);
-  DCHECK_NE(type, AccountExtensionType::kLocal);
+  CHECK_EQ(GetAccountExtensionType(extension_id), AccountExtensionType::kLocal);
+  CHECK_NE(type, AccountExtensionType::kLocal);
   SetAccountExtensionType(extension_id, type);
 
   // The extension's uploadability may change when its AccountExtensionType
diff --git a/chrome/browser/extensions/sync/extension_local_data_batch_uploader.cc b/chrome/browser/extensions/sync/extension_local_data_batch_uploader.cc
index 6b79cc87..ecc2a4e 100644
--- a/chrome/browser/extensions/sync/extension_local_data_batch_uploader.cc
+++ b/chrome/browser/extensions/sync/extension_local_data_batch_uploader.cc
@@ -9,13 +9,14 @@
 #include "chrome/browser/extensions/sync/account_extension_tracker.h"
 #include "chrome/browser/extensions/sync/extension_sync_util.h"
 #include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/signin/identity_manager_factory.h"
+#include "components/signin/public/identity_manager/identity_manager.h"
 #include "components/sync/base/data_type.h"
 #include "components/sync/service/local_data_description.h"
 #include "extensions/browser/icon_util.h"
 #include "extensions/browser/image_loader.h"
 #include "extensions/common/constants.h"
 #include "extensions/common/extension.h"
-#include "extensions/common/extension_id.h"
 #include "extensions/common/icons/extension_icon_set.h"
 #include "extensions/common/manifest_handlers/icons_handler.h"
 #include "ui/gfx/image/image.h"
@@ -105,16 +106,43 @@
 }
 
 void ExtensionLocalDataBatchUploader::TriggerLocalDataMigration() {
-  std::vector<const Extension*> uploadable_extensions =
-      AccountExtensionTracker::Get(profile_)->GetUploadableLocalExtensions();
-  // TODO(crbug.com/425381293): Implement data migration for local uploadable
-  // extensions.
+  TriggerLocalDataMigrationForItemsInternal(/*ids_to_upload=*/std::nullopt);
 }
 
 void ExtensionLocalDataBatchUploader::TriggerLocalDataMigrationForItems(
     std::vector<syncer::LocalDataItemModel::DataId> items) {
-  // TODO(crbug.com/425381293): Implement data migration for local uploadable
-  // extensions.
+  ExtensionIdSet ids_to_upload;
+  for (const auto& item_id : items) {
+    const std::string* item_id_str = std::get_if<std::string>(&item_id);
+    DCHECK(item_id_str);
+    ids_to_upload.insert(*item_id_str);
+  }
+
+  TriggerLocalDataMigrationForItemsInternal(std::move(ids_to_upload));
+}
+
+void ExtensionLocalDataBatchUploader::TriggerLocalDataMigrationForItemsInternal(
+    std::optional<ExtensionIdSet> ids_to_upload) {
+  signin::IdentityManager* identity_manager =
+      IdentityManagerFactory::GetForProfile(profile_);
+  AccountInfo account_info = identity_manager->FindExtendedAccountInfo(
+      identity_manager->GetPrimaryAccountInfo(signin::ConsentLevel::kSignin));
+  CHECK(!account_info.IsEmpty());
+
+  if (!sync_util::IsSyncingExtensionsInTransportMode(profile_)) {
+    return;
+  }
+
+  std::vector<const Extension*> uploadable_extensions =
+      AccountExtensionTracker::Get(profile_)->GetUploadableLocalExtensions();
+
+  // If the extension is specified in `ids_to_upload` or if `ids_to_upload` is
+  // null (implying no filter), upload it to the user's account.
+  for (const Extension* extension : uploadable_extensions) {
+    if (!ids_to_upload || ids_to_upload->contains(extension->id())) {
+      sync_util::UploadExtensionToAccount(profile_, *extension);
+    }
+  }
 }
 
 }  // namespace extensions
diff --git a/chrome/browser/extensions/sync/extension_local_data_batch_uploader.h b/chrome/browser/extensions/sync/extension_local_data_batch_uploader.h
index 80f15f8..601e6db 100644
--- a/chrome/browser/extensions/sync/extension_local_data_batch_uploader.h
+++ b/chrome/browser/extensions/sync/extension_local_data_batch_uploader.h
@@ -5,8 +5,11 @@
 #ifndef CHROME_BROWSER_EXTENSIONS_SYNC_EXTENSION_LOCAL_DATA_BATCH_UPLOADER_H_
 #define CHROME_BROWSER_EXTENSIONS_SYNC_EXTENSION_LOCAL_DATA_BATCH_UPLOADER_H_
 
+#include <optional>
+
 #include "base/memory/raw_ptr.h"
 #include "components/sync/service/data_type_local_data_batch_uploader.h"
+#include "extensions/common/extension_id.h"
 
 class Profile;
 
@@ -32,6 +35,11 @@
       std::vector<syncer::LocalDataItemModel::DataId> items) override;
 
  private:
+  // Uploads all locat extensions, or only those specified in `items` to the
+  // current primary user's account.
+  void TriggerLocalDataMigrationForItemsInternal(
+      std::optional<ExtensionIdSet> ids_to_upload);
+
   const raw_ptr<Profile> profile_;
 };
 
diff --git a/chrome/browser/extensions/sync/extension_local_data_batch_uploader_unittest.cc b/chrome/browser/extensions/sync/extension_local_data_batch_uploader_unittest.cc
index a900ec0..2ba29c4c 100644
--- a/chrome/browser/extensions/sync/extension_local_data_batch_uploader_unittest.cc
+++ b/chrome/browser/extensions/sync/extension_local_data_batch_uploader_unittest.cc
@@ -10,12 +10,16 @@
 #include "chrome/browser/extensions/extension_service.h"
 #include "chrome/browser/extensions/extension_service_test_with_install.h"
 #include "chrome/browser/extensions/signin_test_util.h"
+#include "chrome/browser/extensions/sync/account_extension_tracker.h"
+#include "chrome/browser/extensions/sync/extension_sync_data.h"
+#include "chrome/browser/extensions/sync/extension_sync_service.h"
 #include "chrome/browser/signin/identity_test_environment_profile_adaptor.h"
 #include "components/signin/public/base/signin_pref_names.h"
 #include "components/signin/public/base/signin_switches.h"
 #include "components/signin/public/identity_manager/identity_test_environment.h"
 #include "components/signin/public/identity_manager/identity_test_utils.h"
 #include "components/sync/service/local_data_description.h"
+#include "components/sync/test/fake_sync_change_processor.h"
 #include "components/sync/test/test_matchers.h"
 #include "extensions/browser/extension_system.h"
 #include "extensions/browser/extension_util.h"
@@ -59,6 +63,20 @@
     return extension_loader.LoadExtension(data_dir().AppendASCII(path));
   }
 
+  AccountExtensionTracker::AccountExtensionType GetAccountExtensionType(
+      const ExtensionId& extension_id) {
+    return AccountExtensionTracker::Get(profile())->GetAccountExtensionType(
+        extension_id);
+  }
+
+  // Simulates an initial download of sync data (empty for this test) so the
+  // extension sync service can start syncing.
+  void SimulateInitialSync() {
+    ExtensionSyncService::Get(profile())->MergeDataAndStartSyncing(
+        syncer::EXTENSIONS, /*initial_sync_data=*/{},
+        std::make_unique<syncer::FakeSyncChangeProcessor>());
+  }
+
  private:
   std::unique_ptr<IdentityTestEnvironmentProfileAdaptor>
       identity_test_env_profile_adaptor_;
@@ -127,4 +145,93 @@
   EXPECT_TRUE(icon_url->SchemeIs(url::kDataScheme));
 }
 
+TEST_F(ExtensionLocalDataBatchUploaderTest, TriggerLocalDataMigration) {
+  base::test::ScopedFeatureList scoped_feature_list;
+  scoped_feature_list.InitAndEnableFeature(
+      switches::kEnableExtensionsExplicitBrowserSignin);
+
+  ExtensionLocalDataBatchUploader uploader(profile());
+
+  // Add an uploadable extension.
+  scoped_refptr<const Extension> extension = LoadExtension("simple_with_icon");
+  ASSERT_TRUE(extension);
+
+  // Perform an explicit sign in and spin up extensions sync.
+  signin_test_util::SimulateExplicitSignIn(profile(), identity_test_env());
+  SimulateInitialSync();
+
+  // Make sure the extension is a local extension that's not syncing.
+  EXPECT_EQ(AccountExtensionTracker::AccountExtensionType::kLocal,
+            GetAccountExtensionType(extension->id()));
+  {
+    syncer::SyncDataList list =
+        ExtensionSyncService::Get(profile())->GetAllSyncDataForTesting(
+            syncer::EXTENSIONS);
+    EXPECT_TRUE(list.empty());
+  }
+
+  // Upload the extension to the user's account.
+  uploader.TriggerLocalDataMigration();
+
+  // Now the extension should be a syncing account extension.
+  EXPECT_EQ(
+      AccountExtensionTracker::AccountExtensionType::kAccountInstalledSignedIn,
+      GetAccountExtensionType(extension->id()));
+  {
+    syncer::SyncDataList list =
+        ExtensionSyncService::Get(profile())->GetAllSyncDataForTesting(
+            syncer::EXTENSIONS);
+    ASSERT_EQ(1u, list.size());
+    std::unique_ptr<ExtensionSyncData> data =
+        ExtensionSyncData::CreateFromSyncData(list[0]);
+    ASSERT_TRUE(data.get());
+    EXPECT_EQ(extension->id(), data->id());
+    EXPECT_TRUE(data->enabled());
+  }
+}
+
+TEST_F(ExtensionLocalDataBatchUploaderTest, TriggerLocalDataMigrationForItems) {
+  base::test::ScopedFeatureList scoped_feature_list;
+  scoped_feature_list.InitAndEnableFeature(
+      switches::kEnableExtensionsExplicitBrowserSignin);
+
+  ExtensionLocalDataBatchUploader uploader(profile());
+
+  // Add two uploadable extensions, though only `extension_1` will be uploaded.
+  scoped_refptr<const Extension> extension_1 =
+      LoadExtension("simple_with_icon");
+  ASSERT_TRUE(extension_1);
+
+  scoped_refptr<const Extension> extension_2 =
+      LoadExtension("simple_with_host");
+  ASSERT_TRUE(extension_2);
+
+  // Perform an explicit sign in and spin up extensions sync.
+  signin_test_util::SimulateExplicitSignIn(profile(), identity_test_env());
+  SimulateInitialSync();
+
+  // Upload just `extension_1` to the user's account.
+  uploader.TriggerLocalDataMigrationForItems({extension_1->id()});
+
+  // Now only `extension_1` should be a syncing account extension.
+  EXPECT_EQ(
+      AccountExtensionTracker::AccountExtensionType::kAccountInstalledSignedIn,
+      GetAccountExtensionType(extension_1->id()));
+  {
+    syncer::SyncDataList list =
+        ExtensionSyncService::Get(profile())->GetAllSyncDataForTesting(
+            syncer::EXTENSIONS);
+    ASSERT_EQ(1u, list.size());
+    std::unique_ptr<ExtensionSyncData> data =
+        ExtensionSyncData::CreateFromSyncData(list[0]);
+    ASSERT_TRUE(data.get());
+    EXPECT_EQ(extension_1->id(), data->id());
+    EXPECT_TRUE(data->enabled());
+  }
+
+  // `extension_2` should still be a local extension.
+  EXPECT_EQ(AccountExtensionTracker::AccountExtensionType::kLocal,
+            GetAccountExtensionType(extension_2->id()));
+}
+
 }  // namespace extensions
diff --git a/chrome/browser/extensions/sync/extension_sync_util.cc b/chrome/browser/extensions/sync/extension_sync_util.cc
index a91289c..89718ac 100644
--- a/chrome/browser/extensions/sync/extension_sync_util.cc
+++ b/chrome/browser/extensions/sync/extension_sync_util.cc
@@ -5,6 +5,8 @@
 #include "chrome/browser/extensions/sync/extension_sync_util.h"
 
 #include "chrome/browser/extensions/extension_management.h"
+#include "chrome/browser/extensions/sync/account_extension_tracker.h"
+#include "chrome/browser/extensions/sync/extension_sync_service.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/signin/identity_manager_factory.h"
 #include "chrome/browser/sync/sync_service_factory.h"
@@ -64,4 +66,11 @@
          !identity_manager->HasPrimaryAccount(signin::ConsentLevel::kSync);
 }
 
+void UploadExtensionToAccount(content::BrowserContext* context,
+                              const Extension& extension) {
+  AccountExtensionTracker::Get(context)->OnAccountUploadInitiatedForExtension(
+      extension.id());
+  ExtensionSyncService::Get(context)->SyncExtensionChangeIfNeeded(extension);
+}
+
 }  // namespace extensions::sync_util
diff --git a/chrome/browser/extensions/sync/extension_sync_util.h b/chrome/browser/extensions/sync/extension_sync_util.h
index abf7a8df..32e09f8e 100644
--- a/chrome/browser/extensions/sync/extension_sync_util.h
+++ b/chrome/browser/extensions/sync/extension_sync_util.h
@@ -35,6 +35,10 @@
 // extensions is enabled.
 bool IsSyncingExtensionsInTransportMode(Profile* profile);
 
+// Uploads the given `extension` to the user's account in `context`.
+void UploadExtensionToAccount(content::BrowserContext* context,
+                              const Extension& extension);
+
 }  // namespace sync_util
 }  // namespace extensions
 
diff --git a/chrome/browser/facilitated_payments/ui/android/internal/java/res/layout/payment_app_item.xml b/chrome/browser/facilitated_payments/ui/android/internal/java/res/layout/payment_app_item.xml
index c10aa45..2e9ea75 100644
--- a/chrome/browser/facilitated_payments/ui/android/internal/java/res/layout/payment_app_item.xml
+++ b/chrome/browser/facilitated_payments/ui/android/internal/java/res/layout/payment_app_item.xml
@@ -5,7 +5,7 @@
 found in the LICENSE file.
 -->
 
-<LinearLayout
+<RelativeLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:background="@drawable/touch_to_fill_credential_background_modern_rounded_all"
     android:descendantFocusability="blocksDescendants"
@@ -14,24 +14,37 @@
     android:layout_width="match_parent"
     android:layout_marginBottom="2dp"
     android:layout_marginHorizontal="@dimen/facilitated_payments_horizontal_margin"
-    android:padding="16dp"
-    android:orientation="horizontal">
+    android:padding="16dp">
 
     <ImageView
         android:id="@+id/payment_app_icon"
         android:importantForAccessibility="no"
         android:layout_height="@dimen/facilitated_payments_fop_icon_height"
         android:layout_width="@dimen/facilitated_payments_fop_icon_width"
+        android:layout_centerVertical="true"
+        android:layout_centerHorizontal="true"
+        android:layout_alignParentStart="true"
         android:layout_marginEnd="16dp"
         android:background="@drawable/facilitated_payments_fop_icon_background"
-        android:scaleType="centerInside"
-        android:layout_gravity="center_vertical" />
+        android:scaleType="centerInside" />
 
-        <TextView
-            android:id="@+id/payment_app_name"
-            android:layout_height="wrap_content"
-            android:layout_width="wrap_content"
-            android:maxLines="1"
-            android:textAppearance="@style/TextAppearance.TextLarge.Primary" />
+    <TextView
+        android:id="@+id/payment_app_name"
+        android:layout_height="wrap_content"
+        android:layout_width="wrap_content"
+        android:layout_toEndOf="@id/payment_app_icon"
+        android:layout_alignParentTop="true"
+        android:maxLines="1"
+        android:ellipsize="end"
+        android:textAppearance="@style/TextAppearance.TextLarge.Primary" />
 
-</LinearLayout>
+    <TextView
+        android:text="@string/facilitated_payments_payment_app_description"
+        android:layout_height="wrap_content"
+        android:layout_width="wrap_content"
+        android:layout_toEndOf="@id/payment_app_icon"
+        android:layout_below="@id/payment_app_name"
+        android:maxLines="1"
+        android:ellipsize="end"
+        android:textAppearance="@style/TextAppearance.TextMedium.Secondary" />
+</RelativeLayout>
diff --git a/chrome/browser/facilitated_payments/ui/android/internal/java/src/org/chromium/chrome/browser/facilitated_payments/FacilitatedPaymentsPaymentMethodsProperties.java b/chrome/browser/facilitated_payments/ui/android/internal/java/src/org/chromium/chrome/browser/facilitated_payments/FacilitatedPaymentsPaymentMethodsProperties.java
index 04371c9..c1840ca 100644
--- a/chrome/browser/facilitated_payments/ui/android/internal/java/src/org/chromium/chrome/browser/facilitated_payments/FacilitatedPaymentsPaymentMethodsProperties.java
+++ b/chrome/browser/facilitated_payments/ui/android/internal/java/src/org/chromium/chrome/browser/facilitated_payments/FacilitatedPaymentsPaymentMethodsProperties.java
@@ -158,12 +158,12 @@
     static class PaymentAppProperties {
         static final ReadableObjectPropertyKey<String> PAYMENT_APP_NAME =
                 new ReadableObjectPropertyKey("payment_app_name");
-        static final ReadableIntPropertyKey PAYMENT_APP_DRAWABLE_ID =
-                new ReadableIntPropertyKey("payment_app_drawable_id");
+        static final ReadableObjectPropertyKey<Drawable> PAYMENT_APP_ICON =
+                new ReadableObjectPropertyKey<>("payment_app_icon");
         static final ReadableObjectPropertyKey<Runnable> ON_PAYMENT_APP_CLICK_ACTION =
                 new ReadableObjectPropertyKey<>("on_payment_app_click_action");
         static final PropertyKey[] NON_TRANSFORMING_KEYS = {
-            PAYMENT_APP_NAME, PAYMENT_APP_DRAWABLE_ID, ON_PAYMENT_APP_CLICK_ACTION
+            PAYMENT_APP_NAME, PAYMENT_APP_ICON, ON_PAYMENT_APP_CLICK_ACTION
         };
 
         private PaymentAppProperties() {}
diff --git a/chrome/browser/facilitated_payments/ui/android/internal/java/src/org/chromium/chrome/browser/facilitated_payments/FacilitatedPaymentsPaymentMethodsViewBinder.java b/chrome/browser/facilitated_payments/ui/android/internal/java/src/org/chromium/chrome/browser/facilitated_payments/FacilitatedPaymentsPaymentMethodsViewBinder.java
index 57a0fe9b..8254f67 100644
--- a/chrome/browser/facilitated_payments/ui/android/internal/java/src/org/chromium/chrome/browser/facilitated_payments/FacilitatedPaymentsPaymentMethodsViewBinder.java
+++ b/chrome/browser/facilitated_payments/ui/android/internal/java/src/org/chromium/chrome/browser/facilitated_payments/FacilitatedPaymentsPaymentMethodsViewBinder.java
@@ -24,7 +24,7 @@
 import static org.chromium.chrome.browser.facilitated_payments.FacilitatedPaymentsPaymentMethodsProperties.HeaderProperties.SECURITY_CHECK_DRAWABLE_ID;
 import static org.chromium.chrome.browser.facilitated_payments.FacilitatedPaymentsPaymentMethodsProperties.HeaderProperties.TITLE;
 import static org.chromium.chrome.browser.facilitated_payments.FacilitatedPaymentsPaymentMethodsProperties.PaymentAppProperties.ON_PAYMENT_APP_CLICK_ACTION;
-import static org.chromium.chrome.browser.facilitated_payments.FacilitatedPaymentsPaymentMethodsProperties.PaymentAppProperties.PAYMENT_APP_DRAWABLE_ID;
+import static org.chromium.chrome.browser.facilitated_payments.FacilitatedPaymentsPaymentMethodsProperties.PaymentAppProperties.PAYMENT_APP_ICON;
 import static org.chromium.chrome.browser.facilitated_payments.FacilitatedPaymentsPaymentMethodsProperties.PaymentAppProperties.PAYMENT_APP_NAME;
 import static org.chromium.chrome.browser.facilitated_payments.FacilitatedPaymentsPaymentMethodsProperties.SCREEN;
 import static org.chromium.chrome.browser.facilitated_payments.FacilitatedPaymentsPaymentMethodsProperties.SCREEN_VIEW_MODEL;
@@ -268,7 +268,7 @@
                 || propertyKey == EWALLET_NAME
                 || propertyKey == EWALLET_DRAWABLE_ID
                 || propertyKey == PAYMENT_APP_NAME
-                || propertyKey == PAYMENT_APP_DRAWABLE_ID) {
+                || propertyKey == PAYMENT_APP_ICON) {
             // Skip, because none of these changes affect the button
         } else {
             assert false : "Unhandled update to property:" + propertyKey;
diff --git a/chrome/browser/facilitated_payments/ui/android/internal/java/src/org/chromium/chrome/browser/facilitated_payments/PaymentAppViewBinder.java b/chrome/browser/facilitated_payments/ui/android/internal/java/src/org/chromium/chrome/browser/facilitated_payments/PaymentAppViewBinder.java
index f3c7ec0..33df15b 100644
--- a/chrome/browser/facilitated_payments/ui/android/internal/java/src/org/chromium/chrome/browser/facilitated_payments/PaymentAppViewBinder.java
+++ b/chrome/browser/facilitated_payments/ui/android/internal/java/src/org/chromium/chrome/browser/facilitated_payments/PaymentAppViewBinder.java
@@ -5,7 +5,7 @@
 package org.chromium.chrome.browser.facilitated_payments;
 
 import static org.chromium.chrome.browser.facilitated_payments.FacilitatedPaymentsPaymentMethodsProperties.PaymentAppProperties.ON_PAYMENT_APP_CLICK_ACTION;
-import static org.chromium.chrome.browser.facilitated_payments.FacilitatedPaymentsPaymentMethodsProperties.PaymentAppProperties.PAYMENT_APP_DRAWABLE_ID;
+import static org.chromium.chrome.browser.facilitated_payments.FacilitatedPaymentsPaymentMethodsProperties.PaymentAppProperties.PAYMENT_APP_ICON;
 import static org.chromium.chrome.browser.facilitated_payments.FacilitatedPaymentsPaymentMethodsProperties.PaymentAppProperties.PAYMENT_APP_NAME;
 
 import android.view.LayoutInflater;
@@ -14,8 +14,6 @@
 import android.widget.ImageView;
 import android.widget.TextView;
 
-import androidx.appcompat.content.res.AppCompatResources;
-
 import org.chromium.build.annotations.NullMarked;
 import org.chromium.chrome.browser.facilitated_payments.FacilitatedPaymentsPaymentMethodsProperties.PaymentAppProperties;
 import org.chromium.ui.modelutil.PropertyKey;
@@ -49,11 +47,9 @@
         if (propertyKey == PAYMENT_APP_NAME) {
             TextView paymentAppName = view.findViewById(R.id.payment_app_name);
             paymentAppName.setText(model.get(PAYMENT_APP_NAME));
-        } else if (propertyKey == PAYMENT_APP_DRAWABLE_ID) {
+        } else if (propertyKey == PAYMENT_APP_ICON) {
             ImageView paymentAppIcon = view.findViewById(R.id.payment_app_icon);
-            paymentAppIcon.setImageDrawable(
-                    AppCompatResources.getDrawable(
-                            view.getContext(), model.get(PAYMENT_APP_DRAWABLE_ID)));
+            paymentAppIcon.setImageDrawable(model.get(PAYMENT_APP_ICON));
         } else if (propertyKey == ON_PAYMENT_APP_CLICK_ACTION) {
             view.setOnClickListener(unusedView -> model.get(ON_PAYMENT_APP_CLICK_ACTION).run());
         } else {
diff --git a/chrome/browser/flag-metadata.json b/chrome/browser/flag-metadata.json
index 8bb381a..6e25eed 100644
--- a/chrome/browser/flag-metadata.json
+++ b/chrome/browser/flag-metadata.json
@@ -663,11 +663,6 @@
     "expiry_milestone": 141
   },
   {
-    "name": "autofill-drop-names-with-invalid-characters-for-card-upload",
-    "owners": [ "osaul@google.com", "payments-autofill-team@google.com" ],
-    "expiry_milestone": 145
-  },
-  {
     "name": "autofill-enable-allowlist-for-bmo-card-category-benefits",
     "owners": [ "ferny@google.com", "payments-autofill-team@google.com" ],
     "expiry_milestone": 145
diff --git a/chrome/browser/flag_descriptions.cc b/chrome/browser/flag_descriptions.cc
index 2d20a3ba..da4af72 100644
--- a/chrome/browser/flag_descriptions.cc
+++ b/chrome/browser/flag_descriptions.cc
@@ -599,14 +599,6 @@
     "grouped into processes based on their URL's site or origin. The default "
     "grouping when enabled is per-site.";
 
-const char kAutofillDropNamesWithInvalidCharactersForCardUploadName[] =
-    "Drop names with invalid characters for credit card upload";
-const char kAutofillDropNamesWithInvalidCharactersForCardUploadDescription[] =
-    "When enabled, cardholder and address names considered during the credit "
-    "card upload flow will be cleared out if they contain characters "
-    "considered invalid by Google Payments, such as numbers or various "
-    "punctuation marks.";
-
 const char kAutofillEnableAllowlistForBmoCardCategoryBenefitsName[] =
     "Enable allowlist for showing category benefits for BMO cards";
 const char kAutofillEnableAllowlistForBmoCardCategoryBenefitsDescription[] =
@@ -4817,11 +4809,6 @@
     "Enables the Android feature Edge-to-Edge Feature to coordinate with the "
     "Display Cutout for the notch when drawing below the Nav Bar.";
 
-const char kDrawKeyNativeEdgeToEdgeName[] = "DrawKeyNativeEdgeToEdge";
-const char kDrawKeyNativeEdgeToEdgeDescription[] =
-    "Enables the Android feature Edge-to-Edge and forces a draw ToEdge on "
-    "select native pages. No effect when EdgeToEdgeBottomChin is disabled";
-
 const char kEdgeToEdgeBottomChinName[] = "EdgeToEdgeBottomChin";
 const char kEdgeToEdgeBottomChinDescription[] =
     "Enables the scrollable bottom chin for an intermediate Edge-to-Edge "
diff --git a/chrome/browser/flag_descriptions.h b/chrome/browser/flag_descriptions.h
index af37aeb2..9b6e3132c 100644
--- a/chrome/browser/flag_descriptions.h
+++ b/chrome/browser/flag_descriptions.h
@@ -395,10 +395,6 @@
 extern const char kImageDescriptionsAlternateRoutingName[];
 extern const char kImageDescriptionsAlternateRoutingDescription[];
 
-extern const char kAutofillDropNamesWithInvalidCharactersForCardUploadName[];
-extern const char
-    kAutofillDropNamesWithInvalidCharactersForCardUploadDescription[];
-
 extern const char kAutofillEnableAllowlistForBmoCardCategoryBenefitsName[];
 extern const char
     kAutofillEnableAllowlistForBmoCardCategoryBenefitsDescription[];
@@ -2776,9 +2772,6 @@
 extern const char kDrawCutoutEdgeToEdgeName[];
 extern const char kDrawCutoutEdgeToEdgeDescription[];
 
-extern const char kDrawKeyNativeEdgeToEdgeName[];
-extern const char kDrawKeyNativeEdgeToEdgeDescription[];
-
 extern const char kDynamicSafeAreaInsetsName[];
 extern const char kDynamicSafeAreaInsetsDescription[];
 
diff --git a/chrome/browser/flags/android/chrome_feature_list.cc b/chrome/browser/flags/android/chrome_feature_list.cc
index 2ecaf7b..87be1d0 100644
--- a/chrome/browser/flags/android/chrome_feature_list.cc
+++ b/chrome/browser/flags/android/chrome_feature_list.cc
@@ -205,6 +205,7 @@
     &kAndroidBookmarkBar,
     &kAndroidBottomToolbar,
     &kAndroidElegantTextHeight,
+    &kAndroidFirstRunLaunchBounds,
     &kAndroidNativePagesInNewTab,
     &kAndroidProgressBarVisualUpdate,
     &kAndroidNoVisibleHintForDifferentTLD,
@@ -260,7 +261,6 @@
     &kCCTFixWarmup,
     &kCCTFreInSameTask,
     &kCCTIncognitoAvailableToThirdParty,
-    &kCCTIntentFeatureOverrides,
     &kCCTMinimizedEnabledByDefault,
     &kCCTNavigationalPrefetch,
     &kCCTNestedSecurityIcon,
@@ -300,7 +300,6 @@
     &kCpaSpecUpdate,
     &kCrossDeviceTabPaneAndroid,
     &kDeviceAuthenticatorAndroidx,
-    &kDrawKeyNativeEdgeToEdge,
     &kEdgeToEdgeBottomChin,
     &kEdgeToEdgeDebugging,
     &kEdgeToEdgeEverywhere,
@@ -565,6 +564,10 @@
              "AndroidElegantTextHeight",
              base::FEATURE_ENABLED_BY_DEFAULT);
 
+BASE_FEATURE(kAndroidFirstRunLaunchBounds,
+             "AndroidFirstRunLaunchBounds",
+             base::FEATURE_DISABLED_BY_DEFAULT);
+
 BASE_FEATURE(kAndroidNativePagesInNewTab,
              "AndroidNativePagesInNewTab",
              base::FEATURE_ENABLED_BY_DEFAULT);
@@ -778,10 +781,6 @@
              "CCTIncognitoAvailableToThirdParty",
              base::FEATURE_DISABLED_BY_DEFAULT);
 
-BASE_FEATURE(kCCTIntentFeatureOverrides,
-             "CCTIntentFeatureOverrides",
-             base::FEATURE_ENABLED_BY_DEFAULT);
-
 BASE_FEATURE(kCCTMinimizedEnabledByDefault,
              "CCTMinimizedEnabledByDefault",
              base::FEATURE_ENABLED_BY_DEFAULT);
@@ -952,10 +951,6 @@
              "DeviceAuthenticatorAndroidx",
              base::FEATURE_ENABLED_BY_DEFAULT);
 
-BASE_FEATURE(kDrawKeyNativeEdgeToEdge,
-             "DrawKeyNativeEdgeToEdge",
-             base::FEATURE_ENABLED_BY_DEFAULT);
-
 BASE_FEATURE(kEdgeToEdgeBottomChin,
              "EdgeToEdgeBottomChin",
              base::FEATURE_ENABLED_BY_DEFAULT);
diff --git a/chrome/browser/flags/android/chrome_feature_list.h b/chrome/browser/flags/android/chrome_feature_list.h
index a536e7e4..69a15fe5 100644
--- a/chrome/browser/flags/android/chrome_feature_list.h
+++ b/chrome/browser/flags/android/chrome_feature_list.h
@@ -31,6 +31,7 @@
 BASE_DECLARE_FEATURE(kAndroidBookmarkBar);
 BASE_DECLARE_FEATURE(kAndroidBottomToolbar);
 BASE_DECLARE_FEATURE(kAndroidElegantTextHeight);
+BASE_DECLARE_FEATURE(kAndroidFirstRunLaunchBounds);
 BASE_DECLARE_FEATURE(kAndroidNativePagesInNewTab);
 BASE_DECLARE_FEATURE(kAndroidOpenIncognitoAsWindow);
 BASE_DECLARE_FEATURE(kAndroidProgressBarVisualUpdate);
@@ -87,7 +88,6 @@
 BASE_DECLARE_FEATURE(kCCTFixWarmup);
 BASE_DECLARE_FEATURE(kCCTFreInSameTask);
 BASE_DECLARE_FEATURE(kCCTIncognitoAvailableToThirdParty);
-BASE_DECLARE_FEATURE(kCCTIntentFeatureOverrides);
 BASE_DECLARE_FEATURE(kCCTMinimized);
 BASE_DECLARE_FEATURE(kCCTMinimizedEnabledByDefault);
 BASE_DECLARE_FEATURE(kCCTNavigationalPrefetch);
@@ -132,7 +132,6 @@
 BASE_DECLARE_FEATURE(kDeviceAuthenticatorAndroidx);
 BASE_DECLARE_FEATURE(kDisableInstanceLimit);
 BASE_DECLARE_FEATURE(kDontPrefetchLibraries);
-BASE_DECLARE_FEATURE(kDrawKeyNativeEdgeToEdge);
 BASE_DECLARE_FEATURE(kEdgeToEdgeBottomChin);
 BASE_DECLARE_FEATURE(kEdgeToEdgeDebugging);
 BASE_DECLARE_FEATURE(kEdgeToEdgeEverywhere);
diff --git a/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java b/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java
index 07b8938..1e6e6c7 100644
--- a/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java
+++ b/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java
@@ -175,6 +175,7 @@
     public static final String ANDROID_BOTTOM_TOOLBAR = "AndroidBottomToolbar";
     public static final String ANDROID_COMPOSEPLATE = "AndroidComposeplate";
     public static final String ANDROID_ELEGANT_TEXT_HEIGHT = "AndroidElegantTextHeight";
+    public static final String ANDROID_FIRST_RUN_LAUNCH_BOUNDS = "AndroidFirstRunLaunchBounds";
     public static final String ANDROID_MINIMAL_UI_LARGE_SCREEN = "AndroidMinimalUiLargeScreen";
     public static final String ANDROID_NATIVE_PAGES_IN_NEW_TAB = "AndroidNativePagesInNewTab";
     public static final String ANDROID_NO_VISIBLE_HINT_FOR_DIFFERENT_TLD =
@@ -305,7 +306,6 @@
             "CCTGoogleBottomBarVariantLayouts";
     public static final String CCT_INCOGNITO_AVAILABLE_TO_THIRD_PARTY =
             "CCTIncognitoAvailableToThirdParty";
-    public static final String CCT_INTENT_FEATURE_OVERRIDES = "CCTIntentFeatureOverrides";
     public static final String CCT_MINIMIZED_ENABLED_BY_DEFAULT = "CCTMinimizedEnabledByDefault";
     public static final String CCT_NAVIGATIONAL_PREFETCH = "CCTNavigationalPrefetch";
     public static final String CCT_NESTED_SECURITY_ICON = "CCTNestedSecurityIcon";
@@ -377,7 +377,6 @@
     public static final String DISPLAY_WILDCARD_CONTENT_SETTINGS =
             "DisplayWildcardInContentSettings";
     public static final String DRAW_CUTOUT_EDGE_TO_EDGE = "DrawCutoutEdgeToEdge";
-    public static final String DRAW_KEY_NATIVE_EDGE_TO_EDGE = "DrawKeyNativeEdgeToEdge";
     public static final String DYNAMIC_SAFE_AREA_INSETS = "DynamicSafeAreaInsets";
     public static final String EDGE_TO_EDGE_BOTTOM_CHIN = "EdgeToEdgeBottomChin";
     public static final String EDGE_TO_EDGE_DEBUGGING = "EdgeToEdgeDebugging";
@@ -789,8 +788,6 @@
             newCachedFlag(CCT_GOOGLE_BOTTOM_BAR_VARIANT_LAYOUTS, false);
     public static final CachedFlag sCctIncognitoAvailableToThirdParty =
             newCachedFlag(CCT_INCOGNITO_AVAILABLE_TO_THIRD_PARTY, false);
-    public static final CachedFlag sCctIntentFeatureOverrides =
-            newCachedFlag(CCT_INTENT_FEATURE_OVERRIDES, true);
     public static final CachedFlag sCctNavigationalPrefetch =
             newCachedFlag(
                     CCT_NAVIGATIONAL_PREFETCH,
@@ -848,8 +845,6 @@
                     /* defaultValueInTests= */ true);
     public static final CachedFlag sDisplayEdgeToEdgeFullscreen =
             newCachedFlag(DISPLAY_EDGE_TO_EDGE_FULLSCREEN, false, true);
-    public static final CachedFlag sDrawKeyNativeEdgeToEdge =
-            newCachedFlag(DRAW_KEY_NATIVE_EDGE_TO_EDGE, true);
     public static final CachedFlag sEdgeToEdgeBottomChin =
             newCachedFlag(EDGE_TO_EDGE_BOTTOM_CHIN, /* defaultValue= */ true);
     public static final CachedFlag sEdgeToEdgeDebugging =
@@ -1098,7 +1093,6 @@
                     sCctGoogleBottomBar,
                     sCctGoogleBottomBarVariantLayouts,
                     sCctIncognitoAvailableToThirdParty,
-                    sCctIntentFeatureOverrides,
                     sCctNavigationalPrefetch,
                     sCctNestedSecurityIcon,
                     sCctOpenInBrowserButtonIfAllowedByEmbedder,
@@ -1119,7 +1113,6 @@
                     sCrossDeviceTabPaneAndroid,
                     sDisableInstanceLimit,
                     sDisplayEdgeToEdgeFullscreen,
-                    sDrawKeyNativeEdgeToEdge,
                     sEdgeToEdgeBottomChin,
                     sEdgeToEdgeDebugging,
                     sEdgeToEdgeEverywhere,
@@ -1525,29 +1518,6 @@
     public static final IntCachedFeatureParam sDisableInstanceLimitMaxCount =
             newIntCachedFeatureParam(DISABLE_INSTANCE_LIMIT, "max_instance_limit", 20);
 
-    /** Cached param whether we disable e2e on the recent tabs page. */
-    public static final BooleanCachedFeatureParam sDrawKeyNativeEdgeToEdgeDisableRecentTabsE2e =
-            newBooleanCachedFeatureParam(
-                    DRAW_KEY_NATIVE_EDGE_TO_EDGE, "disable_recent_tabs_e2e", false);
-
-    /** Cached param whether we disable e2e on the CCT media viewer. */
-    public static final BooleanCachedFeatureParam sDrawKeyNativeEdgeToEdgeDisableCctMediaViewerE2e =
-            newBooleanCachedFeatureParam(
-                    DRAW_KEY_NATIVE_EDGE_TO_EDGE, "disable_cct_media_viewer_e2e", false);
-
-    /** Cached param whether we disable e2e on the hub. */
-    public static final BooleanCachedFeatureParam sDrawKeyNativeEdgeToEdgeDisableHubE2e =
-            newBooleanCachedFeatureParam(DRAW_KEY_NATIVE_EDGE_TO_EDGE, "disable_hub_e2e", false);
-
-    /** Cached param whether we disable e2e on new tab page. */
-    public static final BooleanCachedFeatureParam sDrawKeyNativeEdgeToEdgeDisableNtpE2e =
-            newBooleanCachedFeatureParam(DRAW_KEY_NATIVE_EDGE_TO_EDGE, "disable_ntp_e2e", false);
-
-    /** Cached param whether we disable e2e on incognito new tab page. See crbug.com/368675202 */
-    public static final BooleanCachedFeatureParam sDrawKeyNativeEdgeToEdgeDisableIncognitoNtpE2e =
-            newBooleanCachedFeatureParam(
-                    DRAW_KEY_NATIVE_EDGE_TO_EDGE, "disable_incognito_ntp_e2e", false);
-
     /**
      * Cached param whether we disable animations for color changes to the edge-to-edge bottom chin.
      */
@@ -1769,11 +1739,6 @@
                     sDeleteMigratedLegacyTabStateFilesAfterRestore,
                     sDisableInstanceLimitMaxCount,
                     sDisableInstanceLimitMemoryThresholdMb,
-                    sDrawKeyNativeEdgeToEdgeDisableCctMediaViewerE2e,
-                    sDrawKeyNativeEdgeToEdgeDisableHubE2e,
-                    sDrawKeyNativeEdgeToEdgeDisableIncognitoNtpE2e,
-                    sDrawKeyNativeEdgeToEdgeDisableNtpE2e,
-                    sDrawKeyNativeEdgeToEdgeDisableRecentTabsE2e,
                     sEdgeToEdgeBottomChinOemList,
                     sEdgeToEdgeBottomChinOemMinVersions,
                     sEdgeToEdgeUseBackupNavbarInsetsOemMinVersions,
diff --git a/chrome/browser/glic/glic_keyed_service.cc b/chrome/browser/glic/glic_keyed_service.cc
index b0d906d..44e4330 100644
--- a/chrome/browser/glic/glic_keyed_service.cc
+++ b/chrome/browser/glic/glic_keyed_service.cc
@@ -519,7 +519,7 @@
       !base::FeatureList::IsEnabled(features::kGlicWarming)) {
     // This is to ensure the preload process completes and preload_callback_ is
     // called.
-    FinishPreload(false);
+    FinishPreload(GlicPrewarmingChecksResult::kWarmingDisabled);
     return;
   }
   GlicProfileManager* glic_profile_manager = GlicProfileManager::GetInstance();
@@ -590,17 +590,14 @@
          contents == window_controller().GetFreWebContents();
 }
 
-void GlicKeyedService::FinishPreload(bool should_preload) {
+void GlicKeyedService::FinishPreload(GlicPrewarmingChecksResult result) {
+  base::UmaHistogramEnumeration("Glic.Prewarming.ChecksResult", result);
   if (preload_callback_) {
     base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
         FROM_HERE, base::BindOnce(std::move(preload_callback_)));
   }
-  if (base::FeatureList::IsEnabled(features::kGlicWarming) && profile_ &&
-      GlicEnabling::IsEnabledAndConsentForProfile(profile_)) {
-    base::UmaHistogramBoolean("Glic.ShouldPreload", should_preload);
-  }
 
-  if (!should_preload) {
+  if (result != GlicPrewarmingChecksResult::kSuccess) {
     return;
   }
 
diff --git a/chrome/browser/glic/glic_keyed_service.h b/chrome/browser/glic/glic_keyed_service.h
index 48cc8c15..e873328 100644
--- a/chrome/browser/glic/glic_keyed_service.h
+++ b/chrome/browser/glic/glic_keyed_service.h
@@ -36,6 +36,7 @@
 }  // namespace signin
 
 namespace glic {
+
 class AuthController;
 class GlicActorController;
 class GlicEnabling;
@@ -48,6 +49,8 @@
 class GlicWindowControllerImpl;
 class Host;
 
+enum class GlicPrewarmingChecksResult;
+
 // The GlicKeyedService is created for each eligible (i.e. non-incognito,
 // non-system, etc.) browser profile if Glic flags are enabled, regardless
 // of whether the profile is enabled or disabled at runtime (currently
@@ -226,7 +229,7 @@
           GetZeroStateSuggestionsForFocusedTabCallback callback,
       std::vector<std::string> returned_suggestions);
 
-  void FinishPreload(bool should_preload);
+  void FinishPreload(GlicPrewarmingChecksResult reason);
   void FinishPreloadFre(bool should_preload);
 
   // List of callbacks to be notified when the client requests a change to the
diff --git a/chrome/browser/glic/glic_profile_manager.cc b/chrome/browser/glic/glic_profile_manager.cc
index 62fbd66..54fb4ad 100644
--- a/chrome/browser/glic/glic_profile_manager.cc
+++ b/chrome/browser/glic/glic_profile_manager.cc
@@ -160,21 +160,42 @@
     ShouldPreloadCallback callback) {
   if (!profile || IsProfileDirectoryMarkedForDeletion(profile->GetPath())) {
     base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
-        FROM_HERE, base::BindOnce(std::move(callback), false));
+        FROM_HERE, base::BindOnce(std::move(callback),
+                                  GlicPrewarmingChecksResult::kProfileGone));
     return;
   }
-  if (!base::FeatureList::IsEnabled(features::kGlicWarming) ||
-      !GlicEnabling::IsReadyForProfile(profile)) {
+  if (!base::FeatureList::IsEnabled(features::kGlicWarming)) {
     base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
-        FROM_HERE, base::BindOnce(std::move(callback), false));
+        FROM_HERE,
+        base::BindOnce(std::move(callback),
+                       GlicPrewarmingChecksResult::kWarmingDisabled));
     return;
   }
-  CanPreloadForProfile(profile, std::move(callback));
+  GlicPrewarmingChecksResult result;
+  switch (GlicEnabling::GetProfileReadyState(profile)) {
+    case mojom::ProfileReadyState::kReady:
+      CanPreloadForProfile(profile, std::move(callback));
+      return;
+    case mojom::ProfileReadyState::kUnknownError:
+      result = GlicPrewarmingChecksResult::kProfileNotReadyUnknown;
+      break;
+    case mojom::ProfileReadyState::kSignInRequired:
+      result = GlicPrewarmingChecksResult::kProfileRequiresSignIn;
+      break;
+    case mojom::ProfileReadyState::kIneligible:
+      result = GlicPrewarmingChecksResult::kProfileNotEligible;
+      break;
+    case mojom::ProfileReadyState::kDisabledByAdmin:
+      result = GlicPrewarmingChecksResult::kProfileDisallowedByAdmin;
+      break;
+  }
+  base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
+      FROM_HERE, base::BindOnce(std::move(callback), result));
 }
 
 void GlicProfileManager::ShouldPreloadFreForProfile(
     Profile* profile,
-    ShouldPreloadCallback callback) {
+    base::OnceCallback<void(bool)> callback) {
   if (!base::FeatureList::IsEnabled(features::kGlicFreWarming) ||
       // We only want to preload the FRE if it has not been completed.
       GlicEnabling::IsEnabledAndConsentForProfile(profile)) {
@@ -182,7 +203,14 @@
         FROM_HERE, base::BindOnce(std::move(callback), false));
     return;
   }
-  CanPreloadForProfile(profile, std::move(callback));
+  CanPreloadForProfile(
+      profile, base::BindOnce(
+                   [](base::OnceCallback<void(bool)> callback,
+                      GlicPrewarmingChecksResult reason) {
+                     std::move(callback).Run(
+                         reason == GlicPrewarmingChecksResult::kSuccess);
+                   },
+                   std::move(callback)));
 }
 
 GlicKeyedService* GlicProfileManager::GetLastActiveGlic() const {
@@ -292,25 +320,46 @@
 
 void GlicProfileManager::CanPreloadForProfile(Profile* profile,
                                               ShouldPreloadCallback callback) {
-  const bool is_last_active =
-      last_active_glic_ && last_active_glic_->profile() == profile;
-  const bool is_last_loaded =
-      last_loaded_glic_ && last_loaded_glic_->profile() == profile;
-  const bool blocked_by_shown_glic =
-      !base::FeatureList::IsEnabled(features::kGlicWarmMultiple) && IsShowing();
-
-  if (!profile || !GlicEnabling::IsEnabledForProfile(profile) ||
-      is_last_loaded || is_last_active || blocked_by_shown_glic ||
-      profile->ShutdownStarted() || IsUnderMemoryPressure()) {
+  auto produce_result = [&callback](GlicPrewarmingChecksResult result,
+                                    base::Location from_here =
+                                        base::Location::Current()) {
     base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
-        FROM_HERE, base::BindOnce(std::move(callback), false));
-    return;
+        from_here, base::BindOnce(std::move(callback), result));
+  };
+  if (!profile || profile->ShutdownStarted()) {
+    return produce_result(GlicPrewarmingChecksResult::kProfileGone);
+  }
+  auto enablement = GlicEnabling::EnablementForProfile(profile);
+  if (!enablement.IsProfileEligible()) {
+    return produce_result(GlicPrewarmingChecksResult::kProfileNotEligible);
+  }
+  if (enablement.DisallowedByAdmin()) {
+    return produce_result(
+        GlicPrewarmingChecksResult::kProfileDisallowedByAdmin);
+  }
+  if (!enablement.IsEnabled()) {
+    return produce_result(GlicPrewarmingChecksResult::kProfileNotEnabledOther);
+  }
+  if (last_loaded_glic_ && last_loaded_glic_->profile() == profile) {
+    return produce_result(GlicPrewarmingChecksResult::kProfileIsLastLoaded);
+  }
+  if (last_active_glic_ && last_active_glic_->profile() == profile) {
+    return produce_result(GlicPrewarmingChecksResult::kProfileIsLastActive);
+  }
+  if (!base::FeatureList::IsEnabled(features::kGlicWarmMultiple) &&
+      IsShowing()) {
+    return produce_result(GlicPrewarmingChecksResult::kBlockedByShownGlic);
+  }
+  if (IsUnderMemoryPressure()) {
+    return produce_result(GlicPrewarmingChecksResult::kUnderMemoryPressure);
   }
 
   auto on_got_connection_type = [](ShouldPreloadCallback callback,
                                    network::mojom::ConnectionType type) {
     std::move(callback).Run(
-        !network::NetworkConnectionTracker::IsConnectionCellular(type));
+        network::NetworkConnectionTracker::IsConnectionCellular(type)
+            ? GlicPrewarmingChecksResult::kCellularConnection
+            : GlicPrewarmingChecksResult::kSuccess);
   };
   auto callbacks = base::SplitOnceCallback(std::move(callback));
 
diff --git a/chrome/browser/glic/glic_profile_manager.h b/chrome/browser/glic/glic_profile_manager.h
index eaaa28f..cd342b5 100644
--- a/chrome/browser/glic/glic_profile_manager.h
+++ b/chrome/browser/glic/glic_profile_manager.h
@@ -17,6 +17,8 @@
 
 namespace glic {
 
+enum class GlicPrewarmingChecksResult;
+
 // GlicProfileManager is a GlobalFeature that manages multi-profile Glic state.
 // Among other things it is used for determining which profile to launch from an
 // OS Entry point and ensuring that just one panel is shown across all profiles.
@@ -57,17 +59,17 @@
   // respective web clients are being torn down.
   void OnUnloadingClientForService(GlicKeyedService* glic);
 
-  using ShouldPreloadCallback = base::OnceCallback<void(bool)>;
-
-  // Callback will be invoked with true if the given profile should be
+  // Callback will be invoked with kSuccess if the given profile should be
   // considered for preloading.
+  using ShouldPreloadCallback =
+      base::OnceCallback<void(GlicPrewarmingChecksResult)>;
   void ShouldPreloadForProfile(Profile* profile,
                                ShouldPreloadCallback callback);
 
   // Callback will be invoked with true if the given profile should be
   // considered for preloading the FRE.
   void ShouldPreloadFreForProfile(Profile* profile,
-                                  ShouldPreloadCallback callback);
+                                  base::OnceCallback<void(bool)> callback);
 
   // Returns the active Glic service, nullptr if there is none.
   GlicKeyedService* GetLastActiveGlic() const;
@@ -116,6 +118,58 @@
   bool did_auto_open_ = false;
   base::WeakPtrFactory<GlicProfileManager> weak_ptr_factory_{this};
 };
+
+// These values are persisted to logs. Entries should not be renumbered and
+// numeric values should never be reused. This enum should be kept in sync with
+// GlicPrewarmingChecksResult in enums.xml.
+// LINT.IfChange(GlicPrewarmingChecksResult)
+enum class GlicPrewarmingChecksResult {
+  // Preloading is happening.
+  kSuccess = 0,
+
+  // Warming was disabled by the feature configuration.
+  kWarmingDisabled = 1,
+
+  // The profile doesn't exist or is marked for deletion.
+  kProfileGone = 2,
+
+  // The profile is not ready for Glic, for an unknown reason.
+  kProfileNotReadyUnknown = 3,
+
+  // The account state is paused, and requires sign in.
+  kProfileRequiresSignIn = 4,
+
+  // The profile is not eligible for Glic.
+  kProfileNotEligible = 5,
+
+  // Glic is not rolled out to the user.
+  kProfileNotRolledOut = 6,
+
+  // The profile is disallowed by admin policy.
+  kProfileDisallowedByAdmin = 7,
+
+  // The profile is not enabled for Glic for some other reason.
+  kProfileNotEnabledOther = 8,
+
+  // The profile is already the last loaded profile.
+  kProfileIsLastLoaded = 9,
+
+  // The profile is already the last active profile.
+  kProfileIsLastActive = 10,
+
+  // Preloading is blocked because another Glic is already showing.
+  kBlockedByShownGlic = 11,
+
+  // The system is under memory pressure.
+  kUnderMemoryPressure = 12,
+
+  // The device has a cellular connection.
+  kCellularConnection = 13,
+
+  kMaxValue = kCellularConnection,
+};
+// LINT.ThenChange(//tools/metrics/histograms/metadata/glic/enums.xml:GlicPrewarmingChecksResult)
+
 }  // namespace glic
 
 #endif  // CHROME_BROWSER_GLIC_GLIC_PROFILE_MANAGER_H_
diff --git a/chrome/browser/glic/glic_profile_manager_browsertest.cc b/chrome/browser/glic/glic_profile_manager_browsertest.cc
index afddf01..558536b 100644
--- a/chrome/browser/glic/glic_profile_manager_browsertest.cc
+++ b/chrome/browser/glic/glic_profile_manager_browsertest.cc
@@ -9,6 +9,7 @@
 #include <type_traits>
 
 #include "base/memory/memory_pressure_monitor.h"
+#include "base/test/test_future.h"
 #include "chrome/browser/browser_features.h"
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/contextual_cueing/contextual_cueing_service.h"
@@ -201,7 +202,7 @@
       public testing::WithParamInterface<bool> {
  public:
   explicit GlicProfileManagerPreloadingTest(const std::string& delay_ms) {
-    if (IsPreloadingEnabled()) {
+    if (IsPrewarmingEnabled()) {
       scoped_feature_list_.InitWithFeaturesAndParameters(
           /*enabled_features=*/{{features::kGlicWarming,
                                  {{features::kGlicWarmingDelayMs.name,
@@ -224,11 +225,8 @@
   void SetUpOnMainThread() override {
     InProcessBrowserTest::SetUpOnMainThread();
     GlicProfileManager::ForceProfileForLaunchForTesting(browser()->profile());
-    run_loop_ = std::make_unique<base::RunLoop>();
   }
 
-  void TearDownOnMainThread() override { run_loop_.reset(); }
-
   void TearDown() override {
     GlicProfileManager::ForceProfileForLaunchForTesting(std::nullopt);
     GlicProfileManager::ForceMemoryPressureForTesting(std::nullopt);
@@ -236,7 +234,7 @@
     InProcessBrowserTest::TearDown();
   }
 
-  bool IsPreloadingEnabled() const { return GetParam(); }
+  bool IsPrewarmingEnabled() const { return GetParam(); }
 
   void ResetMemoryPressure() {
     GlicProfileManager::ForceMemoryPressureForTesting(
@@ -244,14 +242,11 @@
             MEMORY_PRESSURE_LEVEL_NONE);
   }
 
-  bool WaitForShouldPreload() {
-    auto* profile_manager = GlicProfileManager::GetInstance();
-    profile_manager->ShouldPreloadForProfile(
-        browser()->profile(),
-        base::BindOnce(&GlicProfileManagerPreloadingTest::OnShouldPreload,
-                       base::Unretained(this)));
-    run_loop_->Run();
-    return should_preload_;
+  GlicPrewarmingChecksResult WaitForShouldPreload() {
+    base::test::TestFuture<GlicPrewarmingChecksResult> future;
+    GlicProfileManager::GetInstance()->ShouldPreloadForProfile(
+        browser()->profile(), future.GetCallback());
+    return future.Get();
   }
 
   void SetConnectionType(network::mojom::ConnectionType connection_type) {
@@ -259,66 +254,77 @@
   }
 
  private:
-  void OnShouldPreload(bool should_preload) {
-    should_preload_ = should_preload;
-    run_loop_->Quit();
-  }
-
   GlicTestEnvironment glic_test_environment_;
-  bool should_preload_ = false;
-  std::unique_ptr<base::RunLoop> run_loop_;
   base::test::ScopedFeatureList scoped_feature_list_;
 };
 
 IN_PROC_BROWSER_TEST_P(GlicProfileManagerPreloadingTest,
                        ShouldPreloadForProfile_Success) {
   ResetMemoryPressure();
-  const bool should_preload = IsPreloadingEnabled();
-  EXPECT_EQ(should_preload, WaitForShouldPreload());
+  const bool should_preload = IsPrewarmingEnabled();
+  EXPECT_EQ(WaitForShouldPreload(),
+            should_preload ? GlicPrewarmingChecksResult::kSuccess
+                           : GlicPrewarmingChecksResult::kWarmingDisabled);
 }
 
 IN_PROC_BROWSER_TEST_P(GlicProfileManagerPreloadingTest,
                        ShouldPreloadForProfile_NotSupportedProfile) {
+  if (!IsPrewarmingEnabled()) {
+    GTEST_SKIP() << "This test only applies if prewarming is enabled.";
+  }
   ResetMemoryPressure();
   GlicProfileManager::ForceProfileForLaunchForTesting(std::nullopt);
   SetModelExecutionCapability(browser()->profile(), false);
-  EXPECT_FALSE(WaitForShouldPreload());
+  EXPECT_EQ(WaitForShouldPreload(),
+            GlicPrewarmingChecksResult::kProfileNotEligible);
 }
 
 IN_PROC_BROWSER_TEST_P(GlicProfileManagerPreloadingTest,
                        ShouldPreloadForProfile_WillBeDestroyed) {
+  if (!IsPrewarmingEnabled()) {
+    GTEST_SKIP() << "This test only applies if prewarming is enabled.";
+  }
   ResetMemoryPressure();
   browser()->profile()->NotifyWillBeDestroyed();
-  EXPECT_FALSE(WaitForShouldPreload());
+  EXPECT_EQ(WaitForShouldPreload(), GlicPrewarmingChecksResult::kProfileGone);
 }
 
 IN_PROC_BROWSER_TEST_P(GlicProfileManagerPreloadingTest,
                        ShouldPreloadForProfile_MemoryPressure) {
+  if (!IsPrewarmingEnabled()) {
+    GTEST_SKIP() << "This test only applies if prewarming is enabled.";
+  }
   // Note: we keep memory pressure at moderate here.
-  EXPECT_FALSE(WaitForShouldPreload());
+  EXPECT_EQ(WaitForShouldPreload(),
+            GlicPrewarmingChecksResult::kUnderMemoryPressure);
 }
 
 IN_PROC_BROWSER_TEST_P(GlicProfileManagerPreloadingTest,
                        ShouldPreloadForProfile_Cellular) {
+  if (!IsPrewarmingEnabled()) {
+    GTEST_SKIP() << "This test only applies if prewarming is enabled.";
+  }
   ResetMemoryPressure();
   SetConnectionType(network::mojom::ConnectionType::CONNECTION_2G);
-  EXPECT_FALSE(WaitForShouldPreload());
+  EXPECT_EQ(WaitForShouldPreload(),
+            GlicPrewarmingChecksResult::kCellularConnection);
 }
 
 // See *Deferred* below. Checks that we don't defer preloading when there's no
 // delay.
 IN_PROC_BROWSER_TEST_P(GlicProfileManagerPreloadingTest,
                        ShouldPreloadForProfile_DoNotDefer) {
+  if (!IsPrewarmingEnabled()) {
+    GTEST_SKIP() << "This test only applies if prewarming is enabled.";
+  }
   ResetMemoryPressure();
   auto* service =
       GlicKeyedServiceFactory::GetGlicKeyedService(browser()->profile());
   service->TryPreload();
-  base::RunLoop run_loop;
   // Since we have no delay, running until idle should mean that we do warm
   // (provided warming is enabled).
-  run_loop.RunUntilIdle();
-  const bool should_preload = IsPreloadingEnabled();
-  EXPECT_EQ(should_preload, service->window_controller().IsWarmed());
+  base::RunLoop().RunUntilIdle();
+  EXPECT_TRUE(service->window_controller().IsWarmed());
 }
 
 INSTANTIATE_TEST_SUITE_P(All,
@@ -342,31 +348,31 @@
 // preload immediately.
 IN_PROC_BROWSER_TEST_P(GlicProfileManagerDeferredPreloadingTest,
                        ShouldPreloadForProfile_Defer) {
+  if (!IsPrewarmingEnabled()) {
+    GTEST_SKIP() << "This test only applies if prewarming is enabled.";
+  }
   ResetMemoryPressure();
   auto* service =
       GlicKeyedServiceFactory::GetGlicKeyedService(browser()->profile());
   service->TryPreload();
-  base::RunLoop run_loop;
   // Since we shouldn't preload until after the delay, we shouldn't be warmed
   // after running until idle.
-  run_loop.RunUntilIdle();
+  base::RunLoop().RunUntilIdle();
   EXPECT_FALSE(service->window_controller().IsWarmed());
 }
 
 IN_PROC_BROWSER_TEST_P(GlicProfileManagerDeferredPreloadingTest,
                        ShouldPreloadForProfile_DeferWithProfileDeletion) {
+  if (!IsPrewarmingEnabled()) {
+    GTEST_SKIP() << "This test only applies if prewarming is enabled.";
+  }
   ResetMemoryPressure();
   auto* service =
       GlicKeyedServiceFactory::GetGlicKeyedService(browser()->profile());
   base::RunLoop run_loop;
-  service->AddPreloadCallback(base::BindOnce(
-      [](base::RunLoop* run_loop, base::OnceClosure quit_closure) {
-        std::move(quit_closure).Run();
-      },
-      &run_loop, run_loop.QuitClosure()));
+  service->AddPreloadCallback(run_loop.QuitClosure());
   service->TryPreload();
   service->reset_profile_for_test();
-
   run_loop.Run();
   EXPECT_FALSE(service->window_controller().IsWarmed());
 }
diff --git a/chrome/browser/glic/host/context/glic_pinned_tab_manager.cc b/chrome/browser/glic/host/context/glic_pinned_tab_manager.cc
index 74de3dc..d016891 100644
--- a/chrome/browser/glic/host/context/glic_pinned_tab_manager.cc
+++ b/chrome/browser/glic/host/context/glic_pinned_tab_manager.cc
@@ -19,12 +19,12 @@
 #include "chrome/browser/glic/host/context/glic_tab_data.h"
 #include "chrome/browser/glic/public/context/glic_sharing_manager.h"
 #include "chrome/browser/profiles/profile.h"
-#include "chrome/browser/resource_coordinator/tab_lifecycle_unit_external.h"
 #include "chrome/browser/ui/browser.h"
 #include "chrome/browser/ui/browser_tab_strip_tracker.h"
 #include "chrome/browser/ui/browser_window/public/browser_window_interface.h"
 #include "chrome/browser/ui/tabs/tab_strip_model.h"
 #include "components/prefs/pref_service.h"
+#include "content/public/browser/web_contents.h"
 
 namespace glic {
 
@@ -248,8 +248,7 @@
     }
     auto* tab = tab_handle.Get();
     if (!tab || IsTabPinned(tab_handle) ||
-        !sharing_manager_->IsBrowserValidForSharing(
-            tab->GetBrowserWindowInterface())) {
+        !IsBrowserValidForSharing(tab->GetBrowserWindowInterface())) {
       pinning_fully_succeeded = false;
       continue;
     }
@@ -259,11 +258,7 @@
     // its context pulled.
     // TODO(crbug.com/422767952): prevent pinned tabs from being discarded.
     if (tab->GetContents()) {
-      ::mojom::LifecycleUnitState tab_lifecycle_state =
-          resource_coordinator::TabLifecycleUnitExternal::FromWebContents(
-              tab->GetContents())
-              ->GetTabState();
-      if (tab_lifecycle_state == ::mojom::LifecycleUnitState::DISCARDED) {
+      if (tab->GetContents()->WasDiscarded()) {
         tab->GetContents()->GetController().SetNeedsReload();
       }
       tab->GetContents()->GetController().LoadIfNecessary();
@@ -295,7 +290,7 @@
     std::erase_if(pinned_tabs_, [tab_handle](const PinnedTabEntry& entry) {
       return entry.tab_handle == tab_handle;
     });
-    pinning_status_changed_callback_list_.Notify(tab_handle.Get(), true);
+    pinning_status_changed_callback_list_.Notify(tab_handle.Get(), false);
   }
   NotifyPinnedTabsChanged();
   return unpinning_fully_succeeded;
@@ -397,8 +392,7 @@
       if (IsTabPinned(tab->GetHandle())) {
         continue;
       }
-      if (!sharing_manager_->IsBrowserValidForSharing(
-              tab->GetBrowserWindowInterface())) {
+      if (!IsBrowserValidForSharing(tab->GetBrowserWindowInterface())) {
         continue;
       }
       auto* web_contents = tab->GetContents();
@@ -460,4 +454,9 @@
   NotifyPinnedTabsChanged();
 }
 
+bool GlicPinnedTabManager::IsBrowserValidForSharing(
+    BrowserWindowInterface* browser_window) {
+  return sharing_manager_->IsBrowserValidForSharing(browser_window);
+}
+
 }  // namespace glic
diff --git a/chrome/browser/glic/host/context/glic_pinned_tab_manager.h b/chrome/browser/glic/host/context/glic_pinned_tab_manager.h
index 6fb8fb8..af5d692 100644
--- a/chrome/browser/glic/host/context/glic_pinned_tab_manager.h
+++ b/chrome/browser/glic/host/context/glic_pinned_tab_manager.h
@@ -88,6 +88,9 @@
       mojom::GetPinCandidatesOptionsPtr options,
       mojo::PendingRemote<mojom::PinCandidatesObserver> observer);
 
+  // Visible for testing.
+  virtual bool IsBrowserValidForSharing(BrowserWindowInterface* browser_window);
+
  private:
   class UpdateThrottler;
 
diff --git a/chrome/browser/glic/host/context/glic_pinned_tab_manager_browsertest.cc b/chrome/browser/glic/host/context/glic_pinned_tab_manager_browsertest.cc
index d1ec3f2..42eeae4 100644
--- a/chrome/browser/glic/host/context/glic_pinned_tab_manager_browsertest.cc
+++ b/chrome/browser/glic/host/context/glic_pinned_tab_manager_browsertest.cc
@@ -96,23 +96,9 @@
   mojo::Receiver<mojom::PinCandidatesObserver> receiver_{this};
 };
 
-class MockGlicSharingManager : public GlicSharingManager {
+class GlicPinnedTabManagerWithOverrides : public GlicPinnedTabManager {
  public:
-  MOCK_METHOD(base::CallbackListSubscription,
-              AddFocusedTabChangedCallback,
-              (FocusedTabChangedCallback),
-              (override));
-  MOCK_METHOD(FocusedTabData, GetFocusedTabData, (), (override));
-  MOCK_METHOD(base::CallbackListSubscription,
-              AddTabPinningStatusChangedCallback,
-              (TabPinningStatusChangedCallback),
-              (override));
-  MOCK_METHOD(bool, PinTabs, (base::span<const tabs::TabHandle>), (override));
-  MOCK_METHOD(bool, UnpinTabs, (base::span<const tabs::TabHandle>), (override));
-  MOCK_METHOD(void, UnpinAllTabs, (), (override));
-  MOCK_METHOD(int32_t, GetMaxPinnedTabs, (), (const, override));
-  MOCK_METHOD(int32_t, GetNumPinnedTabs, (), (const, override));
-  MOCK_METHOD(bool, IsTabPinned, (tabs::TabHandle), (const, override));
+  using GlicPinnedTabManager::GlicPinnedTabManager;
   MOCK_METHOD(bool,
               IsBrowserValidForSharing,
               (BrowserWindowInterface*),
@@ -132,12 +118,10 @@
     https_server_handle_ = https_server_.StartAndReturnHandle();
     ASSERT_TRUE(https_server_handle_);
 
-    sharing_manager_ =
-        std::make_unique<testing::NiceMock<MockGlicSharingManager>>();
-    ON_CALL(*sharing_manager_, IsBrowserValidForSharing(_))
+    pinned_tab_manager_ = std::make_unique<GlicPinnedTabManagerWithOverrides>(
+        browser()->profile(), nullptr);
+    ON_CALL(*pinned_tab_manager_, IsBrowserValidForSharing(_))
         .WillByDefault(Return(true));
-    pinned_tab_manager_ = std::make_unique<GlicPinnedTabManager>(
-        browser()->profile(), sharing_manager_.get());
   }
 
   void TearDownOnMainThread() override {
@@ -173,8 +157,7 @@
 
   net::EmbeddedTestServer https_server_;
   net::test_server::EmbeddedTestServerHandle https_server_handle_;
-  std::unique_ptr<MockGlicSharingManager> sharing_manager_;
-  std::unique_ptr<GlicPinnedTabManager> pinned_tab_manager_;
+  std::unique_ptr<GlicPinnedTabManagerWithOverrides> pinned_tab_manager_;
 };
 
 IN_PROC_BROWSER_TEST_F(GlicPinnedTabManagerBrowserTest,
diff --git a/chrome/browser/glic/host/glic_actor_controller.cc b/chrome/browser/glic/host/glic_actor_controller.cc
index 03c9465..e2d11bb1 100644
--- a/chrome/browser/glic/host/glic_actor_controller.cc
+++ b/chrome/browser/glic/host/glic_actor_controller.cc
@@ -330,8 +330,9 @@
   // the same permission checks, etc. should apply here.
   if (tab) {
     const GURL& url = tab->GetContents()->GetLastCommittedURL();
-    auto journal_entry =
-        journal.CreatePendingAsyncEntry(url, task_id, "FetchPageContext", "");
+    auto journal_entry = journal.CreatePendingAsyncEntry(
+        url, task_id, "FetchPageContext",
+        "TabHandle:" + base::ToString(tab->GetHandle()));
 
     FetchPageContext(tab, *ActionableOptions(options),
                      base::BindOnce(OnFetchPageContext, url,
diff --git a/chrome/browser/glic/host/glic_annotation_manager.cc b/chrome/browser/glic/host/glic_annotation_manager.cc
index 89dc00ee..ca7bf216 100644
--- a/chrome/browser/glic/host/glic_annotation_manager.cc
+++ b/chrome/browser/glic/host/glic_annotation_manager.cc
@@ -5,6 +5,7 @@
 #include "chrome/browser/glic/host/glic_annotation_manager.h"
 
 #include <optional>
+#include <variant>
 
 #include "base/callback_list.h"
 #include "base/metrics/histogram_functions.h"
@@ -17,6 +18,7 @@
 #include "chrome/browser/glic/host/glic.mojom.h"
 #include "chrome/common/chrome_features.h"
 #include "components/optimization_guide/content/browser/page_content_proto_provider.h"
+#include "components/pdf/common/constants.h"
 #include "components/prefs/pref_service.h"
 #include "components/shared_highlighting/core/common/text_fragment.h"
 #include "content/public/browser/render_frame_host.h"
@@ -28,7 +30,6 @@
 
 #if BUILDFLAG(ENABLE_PDF)
 #include "components/pdf/browser/pdf_document_helper.h"
-#include "components/pdf/common/constants.h"
 #endif  // BUILDFLAG(ENABLE_PDF)
 
 namespace glic {
@@ -58,25 +59,69 @@
   return string;
 }
 
+std::variant<content::RenderFrameHost*, mojom::ScrollToErrorReason>
+GetVerifiedAnnotationTargetFrameForPDF(const mojom::ScrollToParams& params,
+                                       content::WebContents* focused_contents) {
 #if BUILDFLAG(ENABLE_PDF)
-content::RenderFrameHost* GetAnnotationTargetFrameForPDF(
-    std::optional<shared_highlighting::TextFragment> text_fragment,
-    content::WebContents* focused_contents) {
   if (!features::kGlicScrollToPDF.Get()) {
-    return nullptr;
+    return mojom::ScrollToErrorReason::kNotSupported;
   }
-  // PDFs don't support node selectors.
-  if (!text_fragment) {
-    return nullptr;
+
+  if (params.selector->is_node_selector()) {
+    return mojom::ScrollToErrorReason::kNotSupported;
   }
+
   auto* pdf_helper =
       pdf::PDFDocumentHelper::MaybeGetForWebContents(focused_contents);
   if (!pdf_helper || !pdf_helper->IsDocumentLoadComplete()) {
-    return nullptr;
+    return mojom::ScrollToErrorReason::kNoMatchingDocument;
   }
+
+  // TODO(crbug.com/422728758): Implement url verification for PDFs.
   return &pdf_helper->render_frame_host();
-}
+#else
+  return mojom::ScrollToErrorReason::kNotSupported;
 #endif  // BUILDFLAG(ENABLE_PDF)
+}
+
+std::variant<content::RenderFrameHost*, mojom::ScrollToErrorReason>
+GetVerifiedAnnotationTargetFrame(content::WebContents* focused_contents,
+                                 const mojom::ScrollToParams& params) {
+  content::Page& focused_primary_page = focused_contents->GetPrimaryPage();
+  content::RenderFrameHost* focused_rfh =
+      &focused_primary_page.GetMainDocument();
+
+  // TODO(crbug.com/427455182): Expand the scrollTo support to the embedded
+  // PDFs. Currently only the main-frame PDF can be scrolled and highlighted.
+  if (focused_primary_page.GetContentsMimeType() == pdf::kPDFMimeType) {
+    return GetVerifiedAnnotationTargetFrameForPDF(params, focused_contents);
+  }
+
+  // The caller currently only enforces if the documentId is set when DOMNodeId
+  // selector parameters are set. If this is configured to be true, we will
+  // always check that the documentId is set, and fail otherwise.
+  const bool fail_without_document_id =
+      features::kGlicScrollToEnforceDocumentId.Get();
+  if (fail_without_document_id && !params.document_id) {
+    return mojom::ScrollToErrorReason::kNotSupported;
+  }
+
+  // Verifies that the document_id parameter (if set) refers to the primary
+  // document in the currently focused tab.
+  if (params.document_id) {
+    // We only support scrolling the currently focused tab's main frame.
+    auto* document_identifier_user_data =
+        optimization_guide::DocumentIdentifierUserData::GetForCurrentDocument(
+            focused_rfh);
+    if (!document_identifier_user_data ||
+        document_identifier_user_data->serialized_token() !=
+            params.document_id) {
+      return mojom::ScrollToErrorReason::kNoMatchingDocument;
+    }
+  }
+
+  return focused_rfh;
+}
 }  // namespace
 
 GlicAnnotationManager::GlicAnnotationManager(GlicKeyedService* service)
@@ -174,16 +219,6 @@
     return;
   }
 
-  auto focused_tab_data = service_->sharing_manager().GetFocusedTabData();
-  if (!focused_tab_data.focus()) {
-    std::move(wrapped_callback).Run(mojom::ScrollToErrorReason::kNoFocusedTab);
-    return;
-  }
-  content::WebContents* focused_contents =
-      focused_tab_data.focus()->GetContents();
-  CHECK(focused_contents);
-  content::Page& focused_primary_page = focused_contents->GetPrimaryPage();
-
   // Note: `GlicWindowController::IsShowing()` will be false when
   // `GlicWindowController` is running the close animation.
   if (!service_->window_controller().IsShowing()) {
@@ -191,23 +226,23 @@
     return;
   }
 
-  // We only support scrolling the currently focused tab's main frame.
-  content::RenderFrameHost* focused_rfh =
-      &focused_primary_page.GetMainDocument();
-
-#if BUILDFLAG(ENABLE_PDF)
-  // TODO(crbug.com/427455182): Expand the scrollTo support to the embedded
-  // PDFs. Currently only the main-frame PDF can be scrolled and highlighted.
-  if (focused_primary_page.GetContentsMimeType() == pdf::kPDFMimeType) {
-    focused_rfh =
-        GetAnnotationTargetFrameForPDF(text_fragment, focused_contents);
-    if (!focused_rfh) {
-      std::move(wrapped_callback)
-          .Run(mojom::ScrollToErrorReason::kNotSupported);
-      return;
-    }
+  auto focused_tab_data = service_->sharing_manager().GetFocusedTabData();
+  if (!focused_tab_data.focus()) {
+    std::move(wrapped_callback).Run(mojom::ScrollToErrorReason::kNoFocusedTab);
+    return;
   }
-#endif  // BUILDFLAG(ENABLE_PDF)
+
+  content::WebContents* focused_contents =
+      focused_tab_data.focus()->GetContents();
+  CHECK(focused_contents);
+  std::variant<content::RenderFrameHost*, mojom::ScrollToErrorReason> result =
+      GetVerifiedAnnotationTargetFrame(focused_contents, *params);
+  if (auto* error_reason = std::get_if<mojom::ScrollToErrorReason>(&result)) {
+    std::move(wrapped_callback).Run(*error_reason);
+    return;
+  }
+  content::RenderFrameHost* focused_rfh =
+      std::get<content::RenderFrameHost*>(result);
 
   if (annotation_agent_container_.has_value() &&
       annotation_agent_container_->document.AsRenderFrameHostIfValid() !=
@@ -222,31 +257,6 @@
         annotation_agent_container_->remote.BindNewPipeAndPassReceiver());
   }
 
-  // The caller currently only enforces if the documentId is set when DOMNodeId
-  // selector parameters are set. If this is configured to be true, we will
-  // always check that the documentId is set, and fail otherwise.
-  const bool fail_without_document_id =
-      features::kGlicScrollToEnforceDocumentId.Get();
-  if (fail_without_document_id && !params->document_id) {
-    std::move(wrapped_callback).Run(mojom::ScrollToErrorReason::kNotSupported);
-    return;
-  }
-
-  // Verifies that the document_id parameter (if set) refers to the primary
-  // document in the currently focused tab.
-  if (params->document_id) {
-    auto* document_identifier_user_data =
-        optimization_guide::DocumentIdentifierUserData::GetForCurrentDocument(
-            focused_rfh);
-    if (!document_identifier_user_data ||
-        document_identifier_user_data->serialized_token() !=
-            params->document_id) {
-      std::move(wrapped_callback)
-          .Run(mojom::ScrollToErrorReason::kNoMatchingDocument);
-      return;
-    }
-  }
-
   blink::mojom::SelectorPtr blink_mojom_selector;
   if (text_fragment) {
     blink_mojom_selector = blink::mojom::Selector::NewSerializedSelector(
diff --git a/chrome/browser/optimization_guide/BUILD.gn b/chrome/browser/optimization_guide/BUILD.gn
index 888f88bb..5b45ae25 100644
--- a/chrome/browser/optimization_guide/BUILD.gn
+++ b/chrome/browser/optimization_guide/BUILD.gn
@@ -8,7 +8,6 @@
 
 source_set("optimization_guide") {
   sources = [
-    "chrome_browser_main_extra_parts_optimization_guide.h",
     "chrome_hints_manager.h",
     "chrome_model_quality_logs_uploader_service.h",
     "chrome_prediction_model_store.h",
@@ -48,7 +47,6 @@
 
 source_set("impl") {
   sources = [
-    "chrome_browser_main_extra_parts_optimization_guide.cc",
     "chrome_hints_manager.cc",
     "chrome_model_quality_logs_uploader_service.cc",
     "chrome_prediction_model_store.cc",
diff --git a/chrome/browser/optimization_guide/chrome_browser_main_extra_parts_optimization_guide.cc b/chrome/browser/optimization_guide/chrome_browser_main_extra_parts_optimization_guide.cc
deleted file mode 100644
index 677c71db..0000000
--- a/chrome/browser/optimization_guide/chrome_browser_main_extra_parts_optimization_guide.cc
+++ /dev/null
@@ -1,25 +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/browser/optimization_guide/chrome_browser_main_extra_parts_optimization_guide.h"
-
-#include "base/files/file_path.h"
-#include "base/path_service.h"
-#include "chrome/browser/optimization_guide/chrome_prediction_model_store.h"
-#include "chrome/browser/optimization_guide/optimization_guide_keyed_service.h"
-#include "chrome/browser/optimization_guide/optimization_guide_keyed_service_factory.h"
-#include "chrome/common/chrome_paths.h"
-#include "components/optimization_guide/core/delivery/prediction_manager.h"
-#include "components/optimization_guide/core/optimization_guide_constants.h"
-#include "components/optimization_guide/core/optimization_guide_features.h"
-
-void ChromeBrowserMainExtraPartsOptimizationGuide::PreCreateThreads() {
-  base::FilePath model_downloads_dir;
-  base::PathService::Get(chrome::DIR_USER_DATA, &model_downloads_dir);
-  model_downloads_dir = model_downloads_dir.Append(
-      optimization_guide::kOptimizationGuideModelStoreDirPrefix);
-  // Create and initialize the install-wide model store.
-  optimization_guide::ChromePredictionModelStore::GetInstance()->Initialize(
-      model_downloads_dir);
-}
diff --git a/chrome/browser/optimization_guide/chrome_browser_main_extra_parts_optimization_guide.h b/chrome/browser/optimization_guide/chrome_browser_main_extra_parts_optimization_guide.h
deleted file mode 100644
index 27063fa..0000000
--- a/chrome/browser/optimization_guide/chrome_browser_main_extra_parts_optimization_guide.h
+++ /dev/null
@@ -1,21 +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.
-
-#ifndef CHROME_BROWSER_OPTIMIZATION_GUIDE_CHROME_BROWSER_MAIN_EXTRA_PARTS_OPTIMIZATION_GUIDE_H_
-#define CHROME_BROWSER_OPTIMIZATION_GUIDE_CHROME_BROWSER_MAIN_EXTRA_PARTS_OPTIMIZATION_GUIDE_H_
-
-#include "chrome/browser/chrome_browser_main_extra_parts.h"
-
-// This class is used to initialize the optimization guide model store which is
-// install-wide, as part of Chrome browser process startup.
-class ChromeBrowserMainExtraPartsOptimizationGuide
-    : public ChromeBrowserMainExtraParts {
- public:
-  ChromeBrowserMainExtraPartsOptimizationGuide() = default;
-
-  // ChromeBrowserMainExtraParts implementation:
-  void PreCreateThreads() override;
-};
-
-#endif  // CHROME_BROWSER_OPTIMIZATION_GUIDE_CHROME_BROWSER_MAIN_EXTRA_PARTS_OPTIMIZATION_GUIDE_H_
diff --git a/chrome/browser/optimization_guide/chrome_prediction_model_store.cc b/chrome/browser/optimization_guide/chrome_prediction_model_store.cc
index e0bdd06f..5f6c0e7 100644
--- a/chrome/browser/optimization_guide/chrome_prediction_model_store.cc
+++ b/chrome/browser/optimization_guide/chrome_prediction_model_store.cc
@@ -4,17 +4,23 @@
 
 #include "chrome/browser/optimization_guide/chrome_prediction_model_store.h"
 
+#include "base/files/file_path.h"
+#include "base/path_service.h"
 #include "chrome/browser/browser_process.h"
+#include "chrome/common/chrome_paths.h"
+#include "components/optimization_guide/core/optimization_guide_constants.h"
 
 namespace optimization_guide {
 
-// static
-ChromePredictionModelStore* ChromePredictionModelStore::GetInstance() {
-  static base::NoDestructor<ChromePredictionModelStore> model_store;
-  return model_store.get();
+ChromePredictionModelStore::ChromePredictionModelStore() {
+  base::FilePath model_downloads_dir;
+  base::PathService::Get(chrome::DIR_USER_DATA, &model_downloads_dir);
+  model_downloads_dir = model_downloads_dir.Append(
+      optimization_guide::kOptimizationGuideModelStoreDirPrefix);
+  // Create and initialize the install-wide model store.
+  Initialize(model_downloads_dir);
 }
 
-ChromePredictionModelStore::ChromePredictionModelStore() = default;
 ChromePredictionModelStore::~ChromePredictionModelStore() = default;
 
 PrefService* ChromePredictionModelStore::GetLocalState() const {
diff --git a/chrome/browser/optimization_guide/chrome_prediction_model_store.h b/chrome/browser/optimization_guide/chrome_prediction_model_store.h
index 24f73fe..28ae89d 100644
--- a/chrome/browser/optimization_guide/chrome_prediction_model_store.h
+++ b/chrome/browser/optimization_guide/chrome_prediction_model_store.h
@@ -5,7 +5,6 @@
 #ifndef CHROME_BROWSER_OPTIMIZATION_GUIDE_CHROME_PREDICTION_MODEL_STORE_H_
 #define CHROME_BROWSER_OPTIMIZATION_GUIDE_CHROME_PREDICTION_MODEL_STORE_H_
 
-#include "base/no_destructor.h"
 #include "components/optimization_guide/core/delivery/prediction_model_store.h"
 
 class PrefService;
@@ -14,9 +13,6 @@
 
 class ChromePredictionModelStore : public PredictionModelStore {
  public:
-  // Returns the singleton model store.
-  static ChromePredictionModelStore* GetInstance();
-
   ChromePredictionModelStore();
   ~ChromePredictionModelStore() override;
 
@@ -26,9 +22,6 @@
 
   // optimization_guide::PredictionModelStore:
   PrefService* GetLocalState() const override;
-
- private:
-  friend base::NoDestructor<ChromePredictionModelStore>;
 };
 
 }  // namespace optimization_guide
diff --git a/chrome/browser/optimization_guide/mock_optimization_guide_keyed_service.cc b/chrome/browser/optimization_guide/mock_optimization_guide_keyed_service.cc
index 9ddc465..84cde9b 100644
--- a/chrome/browser/optimization_guide/mock_optimization_guide_keyed_service.cc
+++ b/chrome/browser/optimization_guide/mock_optimization_guide_keyed_service.cc
@@ -12,21 +12,10 @@
 
 // static
 void MockOptimizationGuideKeyedService::InitializeWithExistingTestLocalState() {
-  // Create and initialize the install-wide model store.
-  base::FilePath model_downloads_dir;
-  base::PathService::Get(chrome::DIR_USER_DATA, &model_downloads_dir);
-  model_downloads_dir = model_downloads_dir.Append(
-      optimization_guide::kOptimizationGuideModelStoreDirPrefix);
-  optimization_guide::ChromePredictionModelStore::GetInstance()->Initialize(
-      model_downloads_dir);
 }
 
 // static
 void MockOptimizationGuideKeyedService::ResetForTesting() {
-  // Reinitialize the store, so that tests do not use state from the
-  // previous test.
-  optimization_guide::ChromePredictionModelStore::GetInstance()
-      ->ResetForTesting();
 }
 
 MockOptimizationGuideKeyedService::MockOptimizationGuideKeyedService()
diff --git a/chrome/browser/optimization_guide/model_execution/chrome_model_broker_state.cc b/chrome/browser/optimization_guide/model_execution/chrome_model_broker_state.cc
index cd57d30..dc5c76f 100644
--- a/chrome/browser/optimization_guide/model_execution/chrome_model_broker_state.cc
+++ b/chrome/browser/optimization_guide/model_execution/chrome_model_broker_state.cc
@@ -7,6 +7,7 @@
 #include <memory>
 
 #include "base/functional/bind.h"
+#include "base/memory/weak_ptr.h"
 #include "base/no_destructor.h"
 #include "base/path_service.h"
 #include "base/system/sys_info.h"
@@ -58,20 +59,20 @@
   }
 
   void RegisterInstaller(
-      scoped_refptr<OnDeviceModelComponentStateManager> state_manager,
+      base::WeakPtr<OnDeviceModelComponentStateManager> state_manager,
       bool is_already_installing) override {
     if (!g_browser_process) {
       return;
     }
     component_updater::RegisterOptimizationGuideOnDeviceModelComponent(
-        g_browser_process->component_updater(), state_manager->GetWeakPtr(),
+        g_browser_process->component_updater(), std::move(state_manager),
         is_already_installing);
   }
 
-  void Uninstall(scoped_refptr<OnDeviceModelComponentStateManager>
+  void Uninstall(base::WeakPtr<OnDeviceModelComponentStateManager>
                      state_manager) override {
     component_updater::UninstallOptimizationGuideOnDeviceModelComponent(
-        state_manager->GetWeakPtr());
+        std::move(state_manager));
   }
 };
 
@@ -83,9 +84,10 @@
 }  // namespace
 
 ChromeModelBrokerState::ChromeModelBrokerState() {
-  component_state_manager_ = OnDeviceModelComponentStateManager::CreateOrGet(
-      g_browser_process->local_state(),
-      std::make_unique<OnDeviceModelComponentStateManagerDelegate>());
+  component_state_manager_ =
+      std::make_unique<OnDeviceModelComponentStateManager>(
+          g_browser_process->local_state(),
+          std::make_unique<OnDeviceModelComponentStateManagerDelegate>());
   component_state_manager_->OnStartup();
 
   service_controller_ =
diff --git a/chrome/browser/optimization_guide/model_execution/chrome_model_broker_state.h b/chrome/browser/optimization_guide/model_execution/chrome_model_broker_state.h
index f89c48b..f25ec51b 100644
--- a/chrome/browser/optimization_guide/model_execution/chrome_model_broker_state.h
+++ b/chrome/browser/optimization_guide/model_execution/chrome_model_broker_state.h
@@ -5,8 +5,11 @@
 #ifndef CHROME_BROWSER_OPTIMIZATION_GUIDE_MODEL_EXECUTION_CHROME_MODEL_BROKER_STATE_H_
 #define CHROME_BROWSER_OPTIMIZATION_GUIDE_MODEL_EXECUTION_CHROME_MODEL_BROKER_STATE_H_
 
+#include <memory>
+
 #include "base/memory/scoped_refptr.h"
 #include "base/memory/weak_ptr.h"
+#include "chrome/browser/optimization_guide/chrome_prediction_model_store.h"
 #include "components/optimization_guide/core/delivery/optimization_guide_model_provider.h"
 #include "components/optimization_guide/core/model_execution/on_device_asset_manager.h"
 #include "components/optimization_guide/core/model_execution/on_device_model_service_controller.h"
@@ -33,6 +36,10 @@
     return service_controller_;
   }
 
+  ChromePredictionModelStore& prediction_model_store() {
+    return prediction_model_store_;
+  }
+
   // Create a new asset manager to provide extra models/configs to the broker.
   std::unique_ptr<OnDeviceAssetManager> CreateAssetManager(
       OptimizationGuideModelProvider* provider);
@@ -42,9 +49,11 @@
   ChromeModelBrokerState();
   ~ChromeModelBrokerState();
 
-  scoped_refptr<OnDeviceModelComponentStateManager> component_state_manager_;
+  std::unique_ptr<OnDeviceModelComponentStateManager> component_state_manager_;
   scoped_refptr<OnDeviceModelServiceController> service_controller_;
 
+  ChromePredictionModelStore prediction_model_store_;
+
   base::WeakPtrFactory<ChromeModelBrokerState> weak_ptr_factory_{this};
 };
 
diff --git a/chrome/browser/optimization_guide/optimization_guide_keyed_service.cc b/chrome/browser/optimization_guide/optimization_guide_keyed_service.cc
index 7cd56e9..1620d74 100644
--- a/chrome/browser/optimization_guide/optimization_guide_keyed_service.cc
+++ b/chrome/browser/optimization_guide/optimization_guide_keyed_service.cc
@@ -267,6 +267,8 @@
             : nullptr;
     hint_store = hint_store_ ? hint_store_->AsWeakPtr() : nullptr;
   }
+  chrome_model_broker_state_ =
+      optimization_guide::ChromeModelBrokerState::CreateOrGet();
 
   optimization_guide_logger_ = OptimizationGuideLogger::GetInstance();
   DCHECK(optimization_guide_logger_);
@@ -278,7 +280,7 @@
       optimization_guide_logger_.get());
 
   prediction_manager_ = std::make_unique<optimization_guide::PredictionManager>(
-      optimization_guide::ChromePredictionModelStore::GetInstance(),
+      &chrome_model_broker_state_->prediction_model_store(),
       g_browser_process->shared_url_loader_factory(),
       g_browser_process->local_state(),
       g_browser_process->GetApplicationLocale(),
@@ -340,9 +342,6 @@
         "HistorySearch");
   }
 
-  chrome_model_broker_state_ =
-      optimization_guide::ChromeModelBrokerState::CreateOrGet();
-
   scoped_refptr<optimization_guide::OnDeviceModelServiceController>
       service_controller;
   if (base::FeatureList::IsEnabled(
diff --git a/chrome/browser/optimization_guide/optimization_guide_keyed_service_browsertest.cc b/chrome/browser/optimization_guide/optimization_guide_keyed_service_browsertest.cc
index f80d367..cb552ec2 100644
--- a/chrome/browser/optimization_guide/optimization_guide_keyed_service_browsertest.cc
+++ b/chrome/browser/optimization_guide/optimization_guide_keyed_service_browsertest.cc
@@ -1130,9 +1130,6 @@
 IN_PROC_BROWSER_TEST_F(OptimizationGuideKeyedServiceBrowserTest,
                        LogOnDeviceMetricsAfterStart) {
   OptimizationGuideKeyedServiceFactory::GetForProfile(browser()->profile());
-  OnDeviceModelComponentStateManager* on_device_component_state_manager =
-      OnDeviceModelComponentStateManager::GetInstanceForTesting();
-  ASSERT_TRUE(on_device_component_state_manager);
 
   EXPECT_TRUE(base::test::RunUntil([&]() {
     return histogram_tester()
@@ -1151,9 +1148,6 @@
 IN_PROC_BROWSER_TEST_F(OptimizationGuideKeyedServiceBrowserTest,
                        LogOnDeviceMetricsSingleTimeForMultipleProfiles) {
   OptimizationGuideKeyedServiceFactory::GetForProfile(browser()->profile());
-  OnDeviceModelComponentStateManager* on_device_component_state_manager =
-      OnDeviceModelComponentStateManager::GetInstanceForTesting();
-  ASSERT_TRUE(on_device_component_state_manager);
 
   // Add a second profile which should not log performance class.
   ProfileManager* profile_manager = g_browser_process->profile_manager();
diff --git a/chrome/browser/optimization_guide/prediction/chrome_profile_download_service_tracker.cc b/chrome/browser/optimization_guide/prediction/chrome_profile_download_service_tracker.cc
index 9eab2a4..f02e0dfb 100644
--- a/chrome/browser/optimization_guide/prediction/chrome_profile_download_service_tracker.cc
+++ b/chrome/browser/optimization_guide/prediction/chrome_profile_download_service_tracker.cc
@@ -41,6 +41,7 @@
 
 download::BackgroundDownloadService*
 ChromeProfileDownloadServiceTracker::GetBackgroundDownloadService() {
+  // Pick the first profile in the list of active profiles.
   return active_profile_observers_.IsObservingAnySource()
              ? BackgroundDownloadServiceFactory::GetForKey(
                    active_profile_observers_.sources().front()->GetProfileKey())
diff --git a/chrome/browser/optimization_guide/prediction/chrome_profile_download_service_tracker.h b/chrome/browser/optimization_guide/prediction/chrome_profile_download_service_tracker.h
index d5cdc96..6583f26 100644
--- a/chrome/browser/optimization_guide/prediction/chrome_profile_download_service_tracker.h
+++ b/chrome/browser/optimization_guide/prediction/chrome_profile_download_service_tracker.h
@@ -5,8 +5,6 @@
 #ifndef CHROME_BROWSER_OPTIMIZATION_GUIDE_PREDICTION_CHROME_PROFILE_DOWNLOAD_SERVICE_TRACKER_H_
 #define CHROME_BROWSER_OPTIMIZATION_GUIDE_PREDICTION_CHROME_PROFILE_DOWNLOAD_SERVICE_TRACKER_H_
 
-#include <list>
-
 #include "base/scoped_multi_source_observation.h"
 #include "base/scoped_observation.h"
 #include "chrome/browser/profiles/profile_manager_observer.h"
@@ -25,16 +23,21 @@
   ChromeProfileDownloadServiceTracker();
   ~ChromeProfileDownloadServiceTracker() override;
 
+  ChromeProfileDownloadServiceTracker(
+      const ChromeProfileDownloadServiceTracker&) = delete;
+  ChromeProfileDownloadServiceTracker& operator=(
+      const ChromeProfileDownloadServiceTracker&) = delete;
+
+  // ProfileDownloadServiceTracker:
+  download::BackgroundDownloadService* GetBackgroundDownloadService() override;
+
+ private:
   // ProfileManagerObserver:
   void OnProfileAdded(Profile* profile) override;
 
   // ProfileObserver:
   void OnProfileWillBeDestroyed(Profile* profile) override;
 
-  // ProfileDownloadServiceTracker:
-  download::BackgroundDownloadService* GetBackgroundDownloadService() override;
-
- private:
   base::ScopedObservation<ProfileManager, ProfileManagerObserver>
       profile_manager_observation_{this};
   base::ScopedMultiSourceObservation<Profile, ProfileObserver>
diff --git a/chrome/browser/optimization_guide/prediction/prediction_model_store_browsertest.cc b/chrome/browser/optimization_guide/prediction/prediction_model_store_browsertest.cc
index 71e88a2..003d9a82 100644
--- a/chrome/browser/optimization_guide/prediction/prediction_model_store_browsertest.cc
+++ b/chrome/browser/optimization_guide/prediction/prediction_model_store_browsertest.cc
@@ -172,8 +172,9 @@
   }
 
   base::FilePath GetModelStoreBaseDir() {
-    return ChromePredictionModelStore::GetInstance()
-        ->GetBaseStoreDirForTesting();
+    return optimization_guide::ChromeModelBrokerState::CreateOrGet()
+        ->prediction_model_store()
+        .GetBaseStoreDirForTesting();
   }
 
   size_t ComputeModelsInStore() {
diff --git a/chrome/browser/page_content_annotations/page_content_annotations_service_browsertest.cc b/chrome/browser/page_content_annotations/page_content_annotations_service_browsertest.cc
index 5ce5e6e..f88fc89e 100644
--- a/chrome/browser/page_content_annotations/page_content_annotations_service_browsertest.cc
+++ b/chrome/browser/page_content_annotations/page_content_annotations_service_browsertest.cc
@@ -21,6 +21,7 @@
 #include "chrome/browser/page_content_annotations/page_content_extraction_service.h"
 #include "chrome/browser/page_content_annotations/page_content_extraction_service_factory.h"
 #include "chrome/browser/page_content_annotations/page_content_extraction_types.h"
+#include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/ui/browser.h"
 #include "chrome/common/chrome_switches.h"
 #include "chrome/test/base/in_process_browser_test.h"
@@ -40,7 +41,9 @@
 #include "components/page_content_annotations/core/page_content_annotations_enums.h"
 #include "components/page_content_annotations/core/page_content_annotations_features.h"
 #include "components/page_content_annotations/core/page_content_annotations_switches.h"
+#include "components/page_content_annotations/core/test_page_content_annotations_service.h"
 #include "components/page_content_annotations/core/test_page_content_annotator.h"
+#include "components/passage_embeddings/passage_embeddings_test_util.h"
 #include "components/ukm/test_ukm_recorder.h"
 #include "content/public/test/browser_test.h"
 #include "content/public/test/browser_test_utils.h"
@@ -1156,6 +1159,186 @@
   EXPECT_FALSE(ModelAnnotationsFieldsAreSetForURL(url));
 }
 
+#if BUILDFLAG(BUILD_WITH_TFLITE_LIB)
+
+class FakeEmbedderMetadataProvider
+    : public passage_embeddings::EmbedderMetadataProvider {
+ public:
+  FakeEmbedderMetadataProvider() = default;
+  ~FakeEmbedderMetadataProvider() override = default;
+
+  // passage_embeddings::EmbedderMetadataProvider:
+  void AddObserver(
+      passage_embeddings::EmbedderMetadataObserver* observer) override {
+    observer_list_.AddObserver(observer);
+  }
+  void RemoveObserver(
+      passage_embeddings::EmbedderMetadataObserver* observer) override {
+    observer_list_.RemoveObserver(observer);
+  }
+
+  void NotifyObservers() {
+    observer_list_.Notify(
+        &passage_embeddings::EmbedderMetadataObserver::EmbedderMetadataUpdated,
+        passage_embeddings::EmbedderMetadata(1, 768));
+  }
+
+ private:
+  base::ObserverList<passage_embeddings::EmbedderMetadataObserver>
+      observer_list_;
+};
+
+class FakeEmbedder : public passage_embeddings::TestEmbedder {
+ public:
+  FakeEmbedder() = default;
+  ~FakeEmbedder() override = default;
+
+  // passage_embeddings::TestEmbedder:
+  passage_embeddings::Embedder::TaskId ComputePassagesEmbeddings(
+      passage_embeddings::PassagePriority priority,
+      std::vector<std::string> passages,
+      ComputePassagesEmbeddingsCallback callback) override {
+    if (status_ == passage_embeddings::ComputeEmbeddingsStatus::kSuccess) {
+      passage_embeddings::TestEmbedder::ComputePassagesEmbeddings(
+          priority, passages, std::move(callback));
+      return 0;
+    }
+
+    std::move(callback).Run(passages, {}, 0, status_);
+    return 0;
+  }
+
+  void set_status(passage_embeddings::ComputeEmbeddingsStatus status) {
+    status_ = status;
+  }
+
+ private:
+  passage_embeddings::ComputeEmbeddingsStatus status_ =
+      passage_embeddings::ComputeEmbeddingsStatus::kSuccess;
+};
+
+class PageContentAnnotationsServiceOnDeviceCategoryClassifierTest
+    : public InProcessBrowserTest {
+ public:
+  void SetUp() override {
+    scoped_feature_list_.InitWithFeatures(
+        {features::kPageContentAnnotations,
+         features::kOnDeviceCategoryClassifier},
+        /*disabled_features=*/{});
+    InProcessBrowserTest::SetUp();
+  }
+
+  void TearDown() override {
+    scoped_feature_list_.Reset();
+    InProcessBrowserTest::TearDown();
+  }
+
+  void SetUpBrowserContextKeyedServices(
+      content::BrowserContext* browser_context) override {
+    PageContentAnnotationsServiceFactory::GetInstance()
+        ->SetTestingFactoryAndUse(
+            browser_context,
+            base::BindRepeating(
+                [](passage_embeddings::EmbedderMetadataProvider*
+                       embedder_metadata_provider,
+                   passage_embeddings::Embedder* embedder,
+                   content::BrowserContext* context)
+                    -> std::unique_ptr<KeyedService> {
+                  Profile* profile = Profile::FromBrowserContext(context);
+                  return TestPageContentAnnotationsService::Create(
+                      OptimizationGuideKeyedServiceFactory::GetForProfile(
+                          profile),
+                      HistoryServiceFactory::GetForProfile(
+                          profile, ServiceAccessType::IMPLICIT_ACCESS),
+                      embedder_metadata_provider, embedder);
+                },
+                &embedder_metadata_provider_, &embedder_));
+  }
+
+  PageContentAnnotationsService* service() {
+    return PageContentAnnotationsServiceFactory::GetForProfile(
+        browser()->profile());
+  }
+
+  void PushClassifierModel(
+      optimization_guide::proto::OptimizationTarget optimization_target) {
+    base::FilePath test_data_dir;
+    base::PathService::Get(base::DIR_SRC_TEST_DATA_ROOT, &test_data_dir);
+    OptimizationGuideKeyedServiceFactory::GetForProfile(browser()->profile())
+        ->OverrideTargetModelForTesting(
+            optimization_target,
+            optimization_guide::TestModelInfoBuilder()
+                .SetModelFilePath(test_data_dir.AppendASCII(
+                    "components/test/data/page_content_annotations/"
+                    "edu_classifier.tflite"))
+                .Build());
+  }
+
+  void NotifyEmbedderMetadata() {
+    embedder_metadata_provider_.NotifyObservers();
+  }
+
+  void UpdateEmbedderStatus(
+      passage_embeddings::ComputeEmbeddingsStatus status) {
+    embedder_.set_status(status);
+  }
+
+ private:
+  base::test::ScopedFeatureList scoped_feature_list_;
+  FakeEmbedderMetadataProvider embedder_metadata_provider_;
+  FakeEmbedder embedder_;
+};
+
+IN_PROC_BROWSER_TEST_F(
+    PageContentAnnotationsServiceOnDeviceCategoryClassifierTest,
+    EmbedderButNoRegression) {
+  NotifyEmbedderMetadata();
+
+  base::test::TestFuture<std::vector<Category>> future;
+  service()->ClassifyCategoriesForText("some text", future.GetCallback());
+  EXPECT_TRUE(future.Get().empty());
+}
+
+IN_PROC_BROWSER_TEST_F(
+    PageContentAnnotationsServiceOnDeviceCategoryClassifierTest,
+    RegressionButNoEmbedder) {
+  PushClassifierModel(
+      optimization_guide::proto::OPTIMIZATION_TARGET_EDU_CLASSIFIER);
+
+  base::test::TestFuture<std::vector<Category>> future;
+  service()->ClassifyCategoriesForText("some text", future.GetCallback());
+  EXPECT_TRUE(future.Get().empty());
+}
+
+IN_PROC_BROWSER_TEST_F(
+    PageContentAnnotationsServiceOnDeviceCategoryClassifierTest,
+    EmbedderFailed) {
+  NotifyEmbedderMetadata();
+  UpdateEmbedderStatus(
+      passage_embeddings::ComputeEmbeddingsStatus::kExecutionFailure);
+  PushClassifierModel(
+      optimization_guide::proto::OPTIMIZATION_TARGET_EDU_CLASSIFIER);
+
+  base::test::TestFuture<std::vector<Category>> future;
+  service()->ClassifyCategoriesForText("some text", future.GetCallback());
+  EXPECT_TRUE(future.Get().empty());
+}
+
+IN_PROC_BROWSER_TEST_F(
+    PageContentAnnotationsServiceOnDeviceCategoryClassifierTest,
+    Success) {
+  NotifyEmbedderMetadata();
+  PushClassifierModel(
+      optimization_guide::proto::OPTIMIZATION_TARGET_EDU_CLASSIFIER);
+
+  base::test::TestFuture<std::vector<Category>> future;
+  service()->ClassifyCategoriesForText("some text", future.GetCallback());
+  ASSERT_EQ(1u, future.Get().size());
+  EXPECT_EQ(CategoryType::kEducation, future.Get()[0].category_type);
+}
+
+#endif
+
 class PageContentAnnotationsServiceContentExtractionTest
     : public InProcessBrowserTest {
  public:
diff --git a/chrome/browser/page_content_annotations/page_content_annotations_service_factory.cc b/chrome/browser/page_content_annotations/page_content_annotations_service_factory.cc
index 85925f5c..51ee2f3 100644
--- a/chrome/browser/page_content_annotations/page_content_annotations_service_factory.cc
+++ b/chrome/browser/page_content_annotations/page_content_annotations_service_factory.cc
@@ -17,6 +17,8 @@
 #include "chrome/browser/history/history_service_factory.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/passage_embeddings/chrome_passage_embeddings_service_controller.h"
+#include "chrome/browser/passage_embeddings/passage_embedder_model_observer_factory.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/profiles/profile_attributes_entry.h"
 #include "chrome/browser/profiles/profile_attributes_storage.h"
@@ -103,6 +105,8 @@
   DependsOn(HistoryServiceFactory::GetInstance());
   DependsOn(TemplateURLServiceFactory::GetInstance());
   DependsOn(ZeroSuggestCacheServiceFactory::GetInstance());
+  DependsOn(
+      passage_embeddings::PassageEmbedderModelObserverFactory::GetInstance());
 }
 
 PageContentAnnotationsServiceFactory::~PageContentAnnotationsServiceFactory() =
@@ -132,6 +136,18 @@
       TemplateURLServiceFactory::GetForProfile(profile);
   ZeroSuggestCacheService* zero_suggest_cache_service =
       ZeroSuggestCacheServiceFactory::GetForProfile(profile);
+
+  passage_embeddings::EmbedderMetadataProvider* embedder_metadata_provider =
+      nullptr;
+  passage_embeddings::Embedder* embedder = nullptr;
+  if (base::FeatureList::IsEnabled(
+          page_content_annotations::features::kOnDeviceCategoryClassifier)) {
+    auto* passage_embeddings_service_controller =
+        passage_embeddings::ChromePassageEmbeddingsServiceController::Get();
+    embedder_metadata_provider = passage_embeddings_service_controller;
+    embedder = passage_embeddings_service_controller->GetEmbedder();
+  }
+
   if (optimization_guide_keyed_service && history_service) {
     std::string country_code =
         GetCurrentCountryCode(g_browser_process->variations_service());
@@ -141,7 +157,7 @@
         optimization_guide_keyed_service, history_service, template_url_service,
         zero_suggest_cache_service, proto_db_provider, profile_path,
         optimization_guide_keyed_service->GetOptimizationGuideLogger(),
-        optimization_guide_keyed_service,
+        optimization_guide_keyed_service, embedder_metadata_provider, embedder,
         base::ThreadPool::CreateSequencedTaskRunner(
             {base::MayBlock(), base::TaskPriority::BEST_EFFORT}));
   }
diff --git a/chrome/browser/page_content_annotations/page_content_annotations_web_contents_observer_unittest.cc b/chrome/browser/page_content_annotations/page_content_annotations_web_contents_observer_unittest.cc
index 9cfde30..a2898db 100644
--- a/chrome/browser/page_content_annotations/page_content_annotations_web_contents_observer_unittest.cc
+++ b/chrome/browser/page_content_annotations/page_content_annotations_web_contents_observer_unittest.cc
@@ -97,18 +97,19 @@
       history::HistoryService* history_service,
       ZeroSuggestCacheService* zero_suggest_cache_service,
       TemplateURLService* template_url_service)
-      : PageContentAnnotationsService(
-            "en-US",
-            "us",
-            optimization_guide_model_provider,
-            history_service,
-            template_url_service,
-            zero_suggest_cache_service,
-            nullptr,
-            base::FilePath(),
-            nullptr,
-            nullptr,
-            nullptr) {}
+      : PageContentAnnotationsService("en-US",
+                                      "us",
+                                      optimization_guide_model_provider,
+                                      history_service,
+                                      template_url_service,
+                                      zero_suggest_cache_service,
+                                      nullptr,
+                                      base::FilePath(),
+                                      nullptr,
+                                      nullptr,
+                                      nullptr,
+                                      nullptr,
+                                      nullptr) {}
   ~FakePageContentAnnotationsService() override = default;
 
   void Annotate(const HistoryVisit& visit) override {
diff --git a/chrome/browser/picture_in_picture/video_picture_in_picture_window_controller_browsertest.cc b/chrome/browser/picture_in_picture/video_picture_in_picture_window_controller_browsertest.cc
index 519b5764..3652c05 100644
--- a/chrome/browser/picture_in_picture/video_picture_in_picture_window_controller_browsertest.cc
+++ b/chrome/browser/picture_in_picture/video_picture_in_picture_window_controller_browsertest.cc
@@ -15,6 +15,9 @@
 #include "build/build_config.h"
 #include "chrome/browser/chrome_content_browser_client.h"
 #include "chrome/browser/devtools/devtools_window_testing.h"
+#include "chrome/browser/media/media_engagement_service.h"
+#include "chrome/browser/media/media_engagement_service_factory.h"
+#include "chrome/browser/media/mock_media_engagement_service.h"
 #include "chrome/browser/picture_in_picture/picture_in_picture_window_manager.h"
 #include "chrome/browser/platform_util.h"
 #include "chrome/browser/ui/browser.h"
@@ -28,6 +31,7 @@
 #include "chrome/browser/ui/views/overlay/toggle_microphone_button.h"
 #include "chrome/test/base/in_process_browser_test.h"
 #include "chrome/test/base/ui_test_utils.h"
+#include "components/keyed_service/content/browser_context_dependency_manager.h"
 #include "components/viz/common/frame_sinks/copy_output_request.h"
 #include "components/viz/common/frame_sinks/copy_output_result.h"
 #include "content/public/browser/media_session.h"
@@ -225,7 +229,15 @@
 class VideoPictureInPictureWindowControllerBrowserTest
     : public InProcessBrowserTest {
  public:
-  VideoPictureInPictureWindowControllerBrowserTest() = default;
+  VideoPictureInPictureWindowControllerBrowserTest()
+      : dependency_manager_subscription_(
+            BrowserContextDependencyManager::GetInstance()
+                ->RegisterCreateServicesCallbackForTesting(base::BindRepeating(
+                    &VideoPictureInPictureWindowControllerBrowserTest::
+                        SetTestingFactory,
+                    // base::Unretained() is safe because `this` outlives the
+                    // dependency manager subscription.
+                    base::Unretained(this)))) {}
 
   VideoPictureInPictureWindowControllerBrowserTest(
       const VideoPictureInPictureWindowControllerBrowserTest&) = delete;
@@ -346,11 +358,33 @@
     views::test::ButtonTestApi(button).NotifyClick(event);
   }
 
+  MediaEngagementService* GetMediaEngagementService() const {
+    return MediaEngagementServiceFactory::GetForProfile(browser()->profile());
+  }
+
+  void SetExpectedHasHighEngagement(bool has_high_engagenent) const {
+    auto* mock_media_engagement_service =
+        static_cast<MockMediaEngagementService*>(GetMediaEngagementService());
+    EXPECT_CALL(*mock_media_engagement_service, HasHighEngagement(testing::_))
+        .WillRepeatedly(testing::Return(has_high_engagenent));
+  }
+
+  bool IsTrustedForMediaPlayback() {
+    return GetOverlayWindow()->IsTrustedForMediaPlayback();
+  }
+
+ protected:
+  void SetTestingFactory(content::BrowserContext* context) {
+    MediaEngagementServiceFactory::GetInstance()->SetTestingFactory(
+        context, base::BindRepeating(&BuildMockMediaEngagementService));
+  }
+
  private:
   raw_ptr<content::VideoPictureInPictureWindowController,
           AcrossTasksDanglingUntriaged>
       pip_window_controller_ = nullptr;
   MockVideoPictureInPictureWindowController mock_controller_;
+  base::CallbackListSubscription dependency_manager_subscription_;
 };
 
 // Checks the creation of the window controller, as well as basic window
@@ -2217,3 +2251,143 @@
   ClickButton(hang_up_button);
   WaitForTitle(active_web_contents, u"hangup");
 }
+
+IN_PROC_BROWSER_TEST_F(VideoPictureInPictureWindowControllerBrowserTest,
+                       IsTrustedForMediaPlayback_FileScheme) {
+  LoadTabAndEnterPictureInPicture(
+      browser(), base::FilePath(kPictureInPictureWindowSizePage));
+  content::WebContents* active_web_contents =
+      browser()->tab_strip_model()->GetActiveWebContents();
+  ASSERT_TRUE(active_web_contents->GetLastCommittedURL().SchemeIsFile());
+
+  // Set MediaSession Play action handler to ensure a MediaSession routed frame
+  // is created.
+  ASSERT_TRUE(
+      ExecJs(active_web_contents, "setMediaSessionActionHandler('play');"));
+
+  // Verify that the overlay window is trusted for media playback.
+  EXPECT_TRUE(IsTrustedForMediaPlayback());
+}
+
+IN_PROC_BROWSER_TEST_F(VideoPictureInPictureWindowControllerBrowserTest,
+                       IsTrustedForMediaPlayback_LowEngagement) {
+  ASSERT_TRUE(ui_test_utils::NavigateToURL(
+      browser(),
+      embedded_test_server()->GetURL(
+          "example.com", "/media/picture-in-picture/window-size.html")));
+
+  content::WebContents* active_web_contents =
+      browser()->tab_strip_model()->GetActiveWebContents();
+  ASSERT_TRUE(active_web_contents);
+
+  SetUpWindowController(active_web_contents);
+  ASSERT_TRUE(window_controller() != nullptr);
+
+  // Open Picture-in-Picture window.
+  ASSERT_EQ(true, EvalJs(active_web_contents, "enterPictureInPicture();"));
+  EXPECT_TRUE(window_controller()->GetWindowForTesting()->IsVisible());
+
+  ASSERT_TRUE(active_web_contents->GetLastCommittedURL().SchemeIsHTTPOrHTTPS());
+
+  // Set MediaSession Play action handler to ensure a MediaSession routed frame
+  // is created.
+  ASSERT_TRUE(
+      ExecJs(active_web_contents, "setMediaSessionActionHandler('play');"));
+
+  // Verify that the overlay window is not trusted for media playback.
+  SetExpectedHasHighEngagement(false);
+  EXPECT_FALSE(IsTrustedForMediaPlayback());
+}
+
+IN_PROC_BROWSER_TEST_F(VideoPictureInPictureWindowControllerBrowserTest,
+                       IsTrustedForMediaPlayback_HighEngagement) {
+  ASSERT_TRUE(ui_test_utils::NavigateToURL(
+      browser(),
+      embedded_test_server()->GetURL(
+          "example.com", "/media/picture-in-picture/window-size.html")));
+
+  content::WebContents* active_web_contents =
+      browser()->tab_strip_model()->GetActiveWebContents();
+  ASSERT_TRUE(active_web_contents);
+
+  SetUpWindowController(active_web_contents);
+  ASSERT_TRUE(window_controller() != nullptr);
+
+  // Open Picture-in-Picture window.
+  ASSERT_EQ(true, EvalJs(active_web_contents, "enterPictureInPicture();"));
+  EXPECT_TRUE(window_controller()->GetWindowForTesting()->IsVisible());
+
+  ASSERT_TRUE(active_web_contents->GetLastCommittedURL().SchemeIsHTTPOrHTTPS());
+
+  // Set MediaSession Play action handler to ensure a MediaSession routed frame
+  // is created.
+  ASSERT_TRUE(
+      ExecJs(active_web_contents, "setMediaSessionActionHandler('play');"));
+
+  // Verify that the overlay window is trusted for media playback.
+  SetExpectedHasHighEngagement(true);
+  EXPECT_TRUE(IsTrustedForMediaPlayback());
+}
+
+IN_PROC_BROWSER_TEST_F(VideoPictureInPictureWindowControllerBrowserTest,
+                       IsTrustedForMediaPlayback_RemoteIframe) {
+  ASSERT_TRUE(ui_test_utils::NavigateToURL(
+      browser(),
+      embedded_test_server()->GetURL(
+          "a.com", "/media/picture_in_picture/iframe-one-video.html")));
+
+  content::WebContents* active_web_contents =
+      browser()->tab_strip_model()->GetActiveWebContents();
+  ASSERT_TRUE(active_web_contents);
+  WaitForTitle(active_web_contents, u"iframe loaded");
+
+  SetUpWindowController(active_web_contents);
+  ASSERT_TRUE(window_controller() != nullptr);
+
+  // Get the render frame host for main_frame (a.com) and sub_frame (b.com).
+  auto* main_frame = active_web_contents->GetPrimaryMainFrame();
+  auto* sub_frame = ChildFrameAt(main_frame, 0);
+  ASSERT_TRUE(sub_frame);
+
+  // Set MediaSession Play action handler to ensure a MediaSession routed frame
+  // is created.
+  ASSERT_TRUE(ExecJs(sub_frame, "setMediaSessionPlayActionHandler();"));
+
+  // Add picture-in-picture event listener to sub frame.
+  ASSERT_TRUE(ExecJs(sub_frame, "addPictureInPictureEventListeners();"));
+
+  // Enter Picture-in-Picture from the iframe.
+  ASSERT_TRUE(ExecJs(main_frame, "enterPictureInPicture();"));
+  EXPECT_TRUE(window_controller()->GetWindowForTesting()->IsVisible());
+
+  // Verify that the overlay window is not trusted for media playback, even with
+  // high media engagement.
+  SetExpectedHasHighEngagement(true);
+  EXPECT_FALSE(IsTrustedForMediaPlayback());
+}
+
+IN_PROC_BROWSER_TEST_F(VideoPictureInPictureWindowControllerBrowserTest,
+                       IsTrustedForMediaPlayback_NoRoutedFrame) {
+  ASSERT_TRUE(ui_test_utils::NavigateToURL(
+      browser(),
+      embedded_test_server()->GetURL(
+          "example.com", "/media/picture-in-picture/window-size.html")));
+
+  content::WebContents* active_web_contents =
+      browser()->tab_strip_model()->GetActiveWebContents();
+  ASSERT_TRUE(active_web_contents);
+
+  SetUpWindowController(active_web_contents);
+  ASSERT_TRUE(window_controller() != nullptr);
+
+  // Open Picture-in-Picture window.
+  ASSERT_EQ(true, EvalJs(active_web_contents, "enterPictureInPicture();"));
+  EXPECT_TRUE(window_controller()->GetWindowForTesting()->IsVisible());
+
+  ASSERT_TRUE(active_web_contents->GetLastCommittedURL().SchemeIsHTTPOrHTTPS());
+
+  // Verify that the overlay window is not trusted for media playback, even with
+  // high media engagement.
+  SetExpectedHasHighEngagement(true);
+  EXPECT_FALSE(IsTrustedForMediaPlayback());
+}
diff --git a/chrome/browser/platform_experience/win b/chrome/browser/platform_experience/win
index e947bd4..47650f3 160000
--- a/chrome/browser/platform_experience/win
+++ b/chrome/browser/platform_experience/win
@@ -1 +1 @@
-Subproject commit e947bd49c411a3c31bca6d6cf08c27767ed6c8c5
+Subproject commit 47650f3a9a6f66c92e1069dfe2db3db1c48f76d3
diff --git a/chrome/browser/profiles/profile_window.cc b/chrome/browser/profiles/profile_window.cc
index fcde8b7..f88adeaab 100644
--- a/chrome/browser/profiles/profile_window.cc
+++ b/chrome/browser/profiles/profile_window.cc
@@ -7,6 +7,7 @@
 #include <stddef.h>
 
 #include "base/command_line.h"
+#include "base/debug/stack_trace.h"
 #include "base/files/file_path.h"
 #include "base/functional/bind.h"
 #include "base/functional/callback.h"
@@ -30,8 +31,12 @@
 #include "chrome/browser/signin/account_reconcilor_factory.h"
 #include "chrome/browser/signin/identity_manager_factory.h"
 #include "chrome/browser/signin/signin_ui_util.h"
+#include "chrome/browser/signin/signin_util.h"
 #include "chrome/browser/ui/browser.h"
 #include "chrome/browser/ui/browser_dialogs.h"
+#include "chrome/browser/ui/hats/hats_service.h"
+#include "chrome/browser/ui/hats/hats_service_factory.h"
+#include "chrome/browser/ui/hats/survey_config.h"
 #include "chrome/browser/ui/startup/startup_types.h"
 #include "chrome/common/pref_names.h"
 #include "chrome/common/url_constants.h"
@@ -217,6 +222,30 @@
                                            BrowserList::CloseCallback(), false);
 }
 
+void LaunchSigninHatsSurveyForBrowser(const std::string& trigger,
+                                      Browser* browser) {
+#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX)
+  if (trigger.empty() ||
+      !signin_util::IsFeatureEnabledForHatsTrigger(trigger)) {
+    return;
+  }
+
+  // A browser is required to launch the survey.
+  if (!browser) {
+    return;
+  }
+
+  HatsService* hats_service =
+      HatsServiceFactory::GetForProfile(browser->GetProfile(),
+                                        /*create_if_necessary=*/true);
+  if (hats_service) {
+    // HaTS service is not available for OTR profiles.
+    // TODO(crbug.com/427971911): add product-specific data.
+    hats_service->LaunchSurvey(trigger);
+  }
+#endif  // BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX)
+}
+
 BrowserAddedForProfileObserver::BrowserAddedForProfileObserver(
     Profile* profile,
     base::OnceCallback<void(Browser*)> callback)
diff --git a/chrome/browser/profiles/profile_window.h b/chrome/browser/profiles/profile_window.h
index 76425f9..8b9b1f2e 100644
--- a/chrome/browser/profiles/profile_window.h
+++ b/chrome/browser/profiles/profile_window.h
@@ -79,6 +79,10 @@
 // Close all the browser windows for |profile|.
 void CloseProfileWindows(Profile* profile);
 
+// Launch the HaTS survey corresponding to |trigger| for the given |browser|.
+void LaunchSigninHatsSurveyForBrowser(const std::string& trigger,
+                                      Browser* browser);
+
 // Handles running a callback when a new Browser for the given profile
 // has been completely created.  This object deletes itself once the browser
 // is created and the callback is executed.
diff --git a/chrome/browser/quick_delete/android/javatests/src/org/chromium/chrome/browser/quick_delete/QuickDeleteBridgeTest.java b/chrome/browser/quick_delete/android/javatests/src/org/chromium/chrome/browser/quick_delete/QuickDeleteBridgeTest.java
index 73dab06..3f5ae3c 100644
--- a/chrome/browser/quick_delete/android/javatests/src/org/chromium/chrome/browser/quick_delete/QuickDeleteBridgeTest.java
+++ b/chrome/browser/quick_delete/android/javatests/src/org/chromium/chrome/browser/quick_delete/QuickDeleteBridgeTest.java
@@ -26,7 +26,9 @@
 import org.chromium.chrome.browser.profiles.Profile;
 import org.chromium.chrome.browser.profiles.ProfileManager;
 import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
-import org.chromium.chrome.test.ChromeTabbedActivityTestRule;
+import org.chromium.chrome.test.transit.ChromeTransitTestRules;
+import org.chromium.chrome.test.transit.FreshCtaTransitTestRule;
+import org.chromium.chrome.test.transit.page.WebPageStation;
 
 import java.util.List;
 import java.util.concurrent.ExecutionException;
@@ -43,10 +45,12 @@
                     "https://www.example.com/",
                     "https://www.google.com/");
 
-    private QuickDeleteBridge mQuickDeleteBridge;
-
     @Rule
-    public ChromeTabbedActivityTestRule mActivityTestRule = new ChromeTabbedActivityTestRule();
+    public FreshCtaTransitTestRule mActivityTestRule =
+            ChromeTransitTestRules.freshChromeTabbedActivityRule();
+
+    private WebPageStation mPage;
+    private QuickDeleteBridge mQuickDeleteBridge;
 
     private static class DomainVisitsCallback implements QuickDeleteBridge.DomainVisitsCallback {
         private final CallbackHelper mCallbackHelper = new CallbackHelper();
@@ -64,7 +68,7 @@
 
     @Before
     public void setUp() throws ExecutionException {
-        mActivityTestRule.startMainActivityOnBlankPage();
+        mPage = mActivityTestRule.startOnBlankPage();
         ThreadUtils.runOnUiThreadBlocking(
                 () -> {
                     Profile profile =
diff --git a/chrome/browser/readaloud/android/BUILD.gn b/chrome/browser/readaloud/android/BUILD.gn
index fb5d681..25f1616c 100644
--- a/chrome/browser/readaloud/android/BUILD.gn
+++ b/chrome/browser/readaloud/android/BUILD.gn
@@ -247,7 +247,6 @@
     "//chrome/browser/ui/android/native_page:java",
     "//chrome/browser/ui/android/toolbar:java",
     "//chrome/browser/user_education:java",
-    "//chrome/test/android:chrome_java_integration_test_support",
     "//chrome/test/android:chrome_java_unit_test_support",
     "//components/browser_ui/bottomsheet/android:java",
     "//components/feature_engagement/public:public_java",
diff --git a/chrome/browser/readaloud/android/java/src/org/chromium/chrome/browser/readaloud/TapToSeekSelectionManagerUnitTest.java b/chrome/browser/readaloud/android/java/src/org/chromium/chrome/browser/readaloud/TapToSeekSelectionManagerUnitTest.java
index d06a853..952dbb6 100644
--- a/chrome/browser/readaloud/android/java/src/org/chromium/chrome/browser/readaloud/TapToSeekSelectionManagerUnitTest.java
+++ b/chrome/browser/readaloud/android/java/src/org/chromium/chrome/browser/readaloud/TapToSeekSelectionManagerUnitTest.java
@@ -24,7 +24,6 @@
 import org.chromium.chrome.browser.flags.ChromeFeatureList;
 import org.chromium.chrome.browser.profiles.Profile;
 import org.chromium.chrome.browser.tab.Tab;
-import org.chromium.chrome.test.ChromeTabbedActivityTestRule;
 import org.chromium.content_public.browser.SelectAroundCaretResult;
 import org.chromium.content_public.browser.SelectionClient;
 import org.chromium.content_public.browser.SelectionPopupController;
@@ -44,9 +43,6 @@
 public class TapToSeekSelectionManagerUnitTest {
     @Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule();
 
-    @Rule
-    public ChromeTabbedActivityTestRule mActivityTestRule = new ChromeTabbedActivityTestRule();
-
     @Mock private ReadAloudController mReadAloudController;
 
     @Mock private Profile mProfile;
diff --git a/chrome/browser/recent_tabs/internal/android/java/src/org/chromium/chrome/browser/recent_tabs/CrossDeviceListCoordinatorUnitTest.java b/chrome/browser/recent_tabs/internal/android/java/src/org/chromium/chrome/browser/recent_tabs/CrossDeviceListCoordinatorUnitTest.java
index 939792bc..b554d7e 100644
--- a/chrome/browser/recent_tabs/internal/android/java/src/org/chromium/chrome/browser/recent_tabs/CrossDeviceListCoordinatorUnitTest.java
+++ b/chrome/browser/recent_tabs/internal/android/java/src/org/chromium/chrome/browser/recent_tabs/CrossDeviceListCoordinatorUnitTest.java
@@ -72,10 +72,7 @@
     }
 
     @Test
-    @EnableFeatures({
-        ChromeFeatureList.EDGE_TO_EDGE_BOTTOM_CHIN,
-        ChromeFeatureList.DRAW_KEY_NATIVE_EDGE_TO_EDGE
-    })
+    @EnableFeatures({ChromeFeatureList.EDGE_TO_EDGE_BOTTOM_CHIN})
     public void testPadAdjuster() {
         assertTrue(mEdgeToEdgeSupplier.hasObservers());
 
diff --git a/chrome/browser/recent_tabs/internal/android/java/src/org/chromium/chrome/browser/recent_tabs/CrossDevicePaneUnitTest.java b/chrome/browser/recent_tabs/internal/android/java/src/org/chromium/chrome/browser/recent_tabs/CrossDevicePaneUnitTest.java
index 304dc8fc..3b6f752a 100644
--- a/chrome/browser/recent_tabs/internal/android/java/src/org/chromium/chrome/browser/recent_tabs/CrossDevicePaneUnitTest.java
+++ b/chrome/browser/recent_tabs/internal/android/java/src/org/chromium/chrome/browser/recent_tabs/CrossDevicePaneUnitTest.java
@@ -119,10 +119,7 @@
     }
 
     @Test
-    @EnableFeatures({
-        ChromeFeatureList.EDGE_TO_EDGE_BOTTOM_CHIN,
-        ChromeFeatureList.DRAW_KEY_NATIVE_EDGE_TO_EDGE
-    })
+    @EnableFeatures({ChromeFeatureList.EDGE_TO_EDGE_BOTTOM_CHIN})
     public void testSetEdgeToEdgeSupplier_BeforeNotifyLoadHint() {
         mEdgeToEdgeSupplier.set(mEdgeToEdgeController);
         assertFalse(mEdgeToEdgeSupplier.hasObservers());
@@ -134,10 +131,7 @@
     }
 
     @Test
-    @EnableFeatures({
-        ChromeFeatureList.EDGE_TO_EDGE_BOTTOM_CHIN,
-        ChromeFeatureList.DRAW_KEY_NATIVE_EDGE_TO_EDGE
-    })
+    @EnableFeatures({ChromeFeatureList.EDGE_TO_EDGE_BOTTOM_CHIN})
     public void testSetEdgeToEdgeSupplier_AfterNotifyLoadHint() {
         mCrossDevicePane.notifyLoadHint(LoadHint.HOT);
         assertTrue(mEdgeToEdgeSupplier.hasObservers());
diff --git a/chrome/browser/recent_tabs/internal/android/java/src/org/chromium/chrome/browser/recent_tabs/RestoreTabsTest.java b/chrome/browser/recent_tabs/internal/android/java/src/org/chromium/chrome/browser/recent_tabs/RestoreTabsTest.java
index a53ca66..eebd020 100644
--- a/chrome/browser/recent_tabs/internal/android/java/src/org/chromium/chrome/browser/recent_tabs/RestoreTabsTest.java
+++ b/chrome/browser/recent_tabs/internal/android/java/src/org/chromium/chrome/browser/recent_tabs/RestoreTabsTest.java
@@ -51,7 +51,9 @@
 import org.chromium.chrome.browser.recent_tabs.ForeignSessionHelper.ForeignSessionWindow;
 import org.chromium.chrome.browser.tasks.tab_management.TabUiTestHelper;
 import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
-import org.chromium.chrome.test.ChromeTabbedActivityTestRule;
+import org.chromium.chrome.test.transit.ChromeTransitTestRules;
+import org.chromium.chrome.test.transit.FreshCtaTransitTestRule;
+import org.chromium.chrome.test.transit.page.WebPageStation;
 import org.chromium.components.browser_ui.bottomsheet.BottomSheetController;
 import org.chromium.components.feature_engagement.FeatureConstants;
 import org.chromium.components.feature_engagement.Tracker;
@@ -71,24 +73,27 @@
     private static final String RESTORE_TABS_FEATURE = FeatureConstants.RESTORE_TABS_ON_FRE_FEATURE;
 
     @Rule
-    public ChromeTabbedActivityTestRule mActivityTestRule = new ChromeTabbedActivityTestRule();
+    public FreshCtaTransitTestRule mActivityTestRule =
+            ChromeTransitTestRules.freshChromeTabbedActivityRule();
 
     @Spy ForeignSessionHelper.Natives mForeignSessionHelperJniSpy;
     // Tell R8 not to break the ability to mock the class.
-    @Spy ForeignSessionHelperJni mUnused;
+    @Spy org.chromium.chrome.browser.recent_tabs.ForeignSessionHelperJni mUnused;
 
     @Mock private Tracker mMockTracker;
 
     private BottomSheetController mBottomSheetController;
+    private WebPageStation mPage;
 
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
-        mActivityTestRule.startMainActivityOnBlankPage();
+        mPage = mActivityTestRule.startOnBlankPage();
         TrackerFactory.setTrackerForTests(mMockTracker);
 
         mForeignSessionHelperJniSpy = Mockito.spy(ForeignSessionHelperJni.get());
-        ForeignSessionHelperJni.setInstanceForTesting(mForeignSessionHelperJniSpy);
+        org.chromium.chrome.browser.recent_tabs.ForeignSessionHelperJni.setInstanceForTesting(
+                mForeignSessionHelperJniSpy);
         doReturn(true).when(mForeignSessionHelperJniSpy).isTabSyncEnabled(anyLong());
 
         mBottomSheetController =
diff --git a/chrome/browser/renderer_host/android/BUILD.gn b/chrome/browser/renderer_host/android/BUILD.gn
index 404cdb8c..91e95ce 100644
--- a/chrome/browser/renderer_host/android/BUILD.gn
+++ b/chrome/browser/renderer_host/android/BUILD.gn
@@ -17,6 +17,7 @@
     "//chrome/browser/profiles/android:java",
     "//chrome/browser/tab:java",
     "//chrome/test/android:chrome_java_integration_test_support",
+    "//chrome/test/android:chrome_java_transit",
     "//components/browser_ui/site_settings/android:java",
     "//components/browsing_data/core:java",
     "//components/content_settings/android:content_settings_enums_java",
diff --git a/chrome/browser/renderer_host/android/java/src/org/chromium/chrome/browser/renderer_host/JavascriptOptimizerFeatureTest.java b/chrome/browser/renderer_host/android/java/src/org/chromium/chrome/browser/renderer_host/JavascriptOptimizerFeatureTest.java
index ffb9803..4ab6c39 100644
--- a/chrome/browser/renderer_host/android/java/src/org/chromium/chrome/browser/renderer_host/JavascriptOptimizerFeatureTest.java
+++ b/chrome/browser/renderer_host/android/java/src/org/chromium/chrome/browser/renderer_host/JavascriptOptimizerFeatureTest.java
@@ -21,7 +21,9 @@
 import org.chromium.chrome.browser.flags.ChromeSwitches;
 import org.chromium.chrome.browser.profiles.Profile;
 import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
-import org.chromium.chrome.test.ChromeTabbedActivityTestRule;
+import org.chromium.chrome.test.transit.ChromeTransitTestRules;
+import org.chromium.chrome.test.transit.FreshCtaTransitTestRule;
+import org.chromium.chrome.test.transit.page.WebPageStation;
 import org.chromium.chrome.test.util.AdvancedProtectionTestRule;
 import org.chromium.components.browser_ui.site_settings.WebsitePreferenceBridge;
 import org.chromium.components.content_settings.ContentSettingValues;
@@ -29,7 +31,6 @@
 import org.chromium.content_public.browser.WebContents;
 import org.chromium.content_public.common.ContentSwitches;
 import org.chromium.net.test.EmbeddedTestServer;
-import org.chromium.net.test.EmbeddedTestServerRule;
 import org.chromium.url.GURL;
 
 /** Integration test for Android OS disabling Javascript Optimizers. */
@@ -43,22 +44,22 @@
 public class JavascriptOptimizerFeatureTest {
     private static final String TEST_PAGE = "/chrome/test/data/android/test.html";
 
-    private EmbeddedTestServer mTestServer;
-
     @Rule
-    public ChromeTabbedActivityTestRule mActivityTestRule = new ChromeTabbedActivityTestRule();
+    public FreshCtaTransitTestRule mActivityTestRule =
+            ChromeTransitTestRules.freshChromeTabbedActivityRule();
 
     @ClassRule
     public static AdvancedProtectionTestRule sAdvancedProtectionRule =
             new AdvancedProtectionTestRule();
 
+    private EmbeddedTestServer mTestServer;
+    private WebPageStation mPage;
+
     @Before
     public void setUp() {
-        EmbeddedTestServerRule embeddedTestServerRule =
-                mActivityTestRule.getEmbeddedTestServerRule();
-        mTestServer = embeddedTestServerRule.getServer();
+        mTestServer = mActivityTestRule.getTestServer();
         sAdvancedProtectionRule.setIsAdvancedProtectionRequestedByOs(false);
-        mActivityTestRule.startMainActivityOnBlankPage();
+        mPage = mActivityTestRule.startOnBlankPage();
     }
 
     private boolean queryJavascriptOptimizersEnabledForActiveWebContents() {
diff --git a/chrome/browser/resources/BUILD.gn b/chrome/browser/resources/BUILD.gn
index 2b0cea8c..297aaad 100644
--- a/chrome/browser/resources/BUILD.gn
+++ b/chrome/browser/resources/BUILD.gn
@@ -337,6 +337,10 @@
   if (is_android || is_linux || is_chromeos || is_win) {
     public_deps += [ "//chrome/browser/resources/sandbox_internals:resources" ]
   }
+
+  if (enable_session_service) {
+    public_deps += [ "tab_strip_internals:resources" ]
+  }
 }
 
 repack("dev_ui_paks") {
@@ -403,5 +407,9 @@
     sources += [ "$root_gen_dir/chrome/sandbox_internals_resources.pak" ]
   }
 
+  if (enable_session_service) {
+    sources += [ "$root_gen_dir/chrome/tab_strip_internals_resources.pak" ]
+  }
+
   deps = [ ":dev_ui_resources" ]
 }
diff --git a/chrome/browser/resources/glic/glic_app_controller.ts b/chrome/browser/resources/glic/glic_app_controller.ts
index 62f9ff7..99781018 100644
--- a/chrome/browser/resources/glic/glic_app_controller.ts
+++ b/chrome/browser/resources/glic/glic_app_controller.ts
@@ -83,6 +83,19 @@
 }
 // LINT.ThenChange(//tools/metrics/histograms/metadata/glic/enums.xml:WebClientUnresponsiveState)
 
+// Enum for specific stages of loading the web client, reported if loading times
+// out.
+// LINT.IfChange(LoadingStage)
+export enum LoadingStage {
+  NOT_LOADING = 0,
+  AWAITING_PROFILE_READY = 1,
+  AWAITING_COOKIE_SYNC = 2,
+  LOADING_WEB_CLIENT = 3,
+  AWAITING_NOTIFY_PANEL_WILL_OPEN = 4,
+  MAX_VALUE = AWAITING_NOTIFY_PANEL_WILL_OPEN,
+}
+// LINT.ThenChange(//tools/metrics/histograms/metadata/glic/enums.xml:LoadingStage)
+
 export class GlicAppController implements PageInterface, WebviewDelegate,
                                           ApiHostEmbedder {
   loadingTimer: number|undefined;
@@ -108,6 +121,8 @@
   private profileReadyInitialState = Promise.withResolvers<void>();
 
   private enteredUnresponsiveTimestampMs?: number;
+  // Loading stage, affects metrics only.
+  private loadingStage: LoadingStage = LoadingStage.NOT_LOADING;
 
   state: WebUiState|undefined;
 
@@ -308,6 +323,7 @@
       WebUiState.kReady,
       {
         onEnter: () => {
+          this.loadingStage = LoadingStage.NOT_LOADING;
           $.guestPanel.classList.toggle('show-header', false);
           this.showPanel('guestPanel');
         },
@@ -361,12 +377,20 @@
     }
   }
 
-  private async beginLoad(): Promise<void> {
-    // Time to show the loading panel if the web client is not ready.
-    const showLoadingTime = performance.now() + kPreHoldLoadingTimeMs;
+  private beginLoad(): void {
+    // Wait a moment before showing the loading panel.
+    this.loadingTimer = setTimeout(() => {
+      this.setState(WebUiState.kShowLoading);
+    }, kPreHoldLoadingTimeMs);
 
+    this.load();
+  }
+
+  private async load(): Promise<void> {
     // profileReadyState isn't available right away. Wait until it's ready.
+    this.loadingStage = LoadingStage.AWAITING_PROFILE_READY;
     await this.profileReadyInitialState.promise;
+    this.loadingStage = LoadingStage.NOT_LOADING;
 
     const readyState = this.profileReadyState;
     switch (readyState) {
@@ -386,7 +410,10 @@
 
     // Blocking on cookie syncing here introduces latency, we should consider
     // ways to avoid it.
+    this.loadingStage = LoadingStage.AWAITING_COOKIE_SYNC;
     const {result} = await this.browserProxy.handler.prepareForClient();
+    this.loadingStage = LoadingStage.NOT_LOADING;
+
     switch (result) {
       case PrepareForClientResult.kSuccess:
         break;
@@ -400,6 +427,7 @@
     }
 
     // Load the web client only after cookie sync is complete.
+    this.loadingStage = LoadingStage.LOADING_WEB_CLIENT;
     this.destroyWebview();
     this.webview = new WebviewController(
         $.webviewContainer, this.browserProxy, this, this,
@@ -407,9 +435,8 @@
     this.webview.getWebClientState().subscribe(
         this.webClientStateChanged.bind(this));
 
-    this.loadingTimer = setTimeout(() => {
-      this.setState(WebUiState.kShowLoading);
-    }, Math.max(0, showLoadingTime - performance.now()));
+    // Browser is expected to call client's notifyPanelWillOpen(), and then we
+    // expect a call to webClientReady() when that finishes.
   }
 
   private showLoading(): void {
@@ -447,6 +474,19 @@
         console.warn('Exceeded timeout waiting for client to load');
         this.setState(WebUiState.kError);
       }
+
+      if (this.state !== WebUiState.kReady) {
+        let loadingStage = this.loadingStage;
+        if (loadingStage === LoadingStage.LOADING_WEB_CLIENT &&
+            this.webview?.waitingOnPanelWillOpen()) {
+          loadingStage = LoadingStage.AWAITING_NOTIFY_PANEL_WILL_OPEN;
+        }
+        chrome.metricsPrivate.recordEnumerationValue(
+            'Glic.Host.LoadingTimedOut', loadingStage,
+            LoadingStage.MAX_VALUE + 1);
+        this.webview?.onLoadTimeOut();
+      }
+      this.loadingStage = LoadingStage.NOT_LOADING;
     }, kMaxWaitTimeMs - kMinHoldLoadingTimeMs);
   }
 
diff --git a/chrome/browser/resources/glic/webview.ts b/chrome/browser/resources/glic/webview.ts
index fffb076..4ab25ec 100644
--- a/chrome/browser/resources/glic/webview.ts
+++ b/chrome/browser/resources/glic/webview.ts
@@ -186,6 +186,15 @@
     return this.host?.waitingOnPanelWillOpen() ?? false;
   }
 
+  onLoadTimeOut(): void {
+    if (this.host) {
+      chrome.metricsPrivate.recordEnumerationValue(
+          'Glic.Host.WebClientState.OnLoadTimeOut',
+          this.host.getDetailedWebClientState(),
+          DetailedWebClientState.MAX_VALUE + 1);
+    }
+  }
+
   private onLoadCommit(e: any): void {
     this.loadCommit(e.url, e.isTopLevel);
   }
diff --git a/chrome/browser/resources/lens/overlay/side_panel/feedback_toast.html.ts b/chrome/browser/resources/lens/overlay/side_panel/feedback_toast.html.ts
index 2cf1f13..952434d 100644
--- a/chrome/browser/resources/lens/overlay/side_panel/feedback_toast.html.ts
+++ b/chrome/browser/resources/lens/overlay/side_panel/feedback_toast.html.ts
@@ -21,7 +21,8 @@
       ${loadTimeData.getString('sendFeedbackButtonText')}
     </cr-button>
     <cr-icon-button id="closeFeedbackToastButton"
-        aria-label="${loadTimeData.getString('close')}"
+        aria-label="${
+      loadTimeData.getString('closeFeedbackToastAccessibilityLabel')}"
         iron-icon="cr:close" @click="${this.onHideFeedbackToastClick}">
     </cr-icon-button>
   </div>
diff --git a/chrome/browser/resources/new_tab_page/app.html b/chrome/browser/resources/new_tab_page/app.html
index b33fee4..e587ee1f 100644
--- a/chrome/browser/resources/new_tab_page/app.html
+++ b/chrome/browser/resources/new_tab_page/app.html
@@ -32,8 +32,8 @@
         ?compose-button-enabled="${this.composeButtonEnabled}"
         ?composebox-enabled="${this.composeboxEnabled}">
     </cr-searchbox>
-    ${this.composeboxEnabled ? html`
-      <ntp-composebox @toggle-composebox="${this.toggleComposebox_}" ?hidden="${!this.showComposebox_}">
+    ${this.showComposebox_ ? html`
+      <ntp-composebox @toggle-composebox="${this.toggleComposebox_}">
       </ntp-composebox>
     ` : ''}
     ${this.showLensUploadDialog_ ? html`
diff --git a/chrome/browser/resources/privacy_sandbox/internals/BUILD.gn b/chrome/browser/resources/privacy_sandbox/internals/BUILD.gn
index d3d8077..7b6888bd 100644
--- a/chrome/browser/resources/privacy_sandbox/internals/BUILD.gn
+++ b/chrome/browser/resources/privacy_sandbox/internals/BUILD.gn
@@ -15,6 +15,7 @@
     "mojo_timedelta.ts",
     "mojo_timestamp.ts",
     "pref_display.ts",
+    "text_copy_button.ts",
     "value_display.ts",
     "internals_page.ts",
   ]
diff --git a/chrome/browser/resources/privacy_sandbox/internals/content_setting_pattern_source.html b/chrome/browser/resources/privacy_sandbox/internals/content_setting_pattern_source.html
index 513be17..605cbed 100644
--- a/chrome/browser/resources/privacy_sandbox/internals/content_setting_pattern_source.html
+++ b/chrome/browser/resources/privacy_sandbox/internals/content_setting_pattern_source.html
@@ -1,108 +1,119 @@
 <style>
-table.hidden {
-  display: none;
-}
+  div.content-setting {
+    padding: 10px;
+    display: flex;
+    flex-direction: row;
+    gap: 0.25rem;
+    justify-content: space-between;
+  }
 
-td {
-  width: auto;
-  font-size: 0.7rem;
-}
+  .id-primary-pattern,
+  .id-secondary-pattern,
+  .id-source {
+    color: dimgray;
+    overflow: hidden;
+    text-overflow: ellipsis;
+    white-space: nowrap;
+  }
 
-td.label {
-  font-weight: 600;
-  text-align: right;
-  vertical-align: top;
-  padding-right: 1em;
-}
+  .pattern {
+    padding: 5px;
+    border-radius: 10px;
+  }
 
-div.content-setting {
-  padding: 10px;
-}
 
-.pattern {
-  border: 1px solid lightgray;
-  padding: 5px;
-  border-radius: 10px;
-  display: inline-block;
-  margin-left: 2rem;
-}
+  .pattern-header {
+    display: flex;
+    align-items: center;
+    gap: 0.25rem;
+  }
 
-.pattern:first-of-type {
-  margin-left: 0rem;
-}
+  .pattern-type {
+    font-weight: bold;
+  }
 
-span.block {
-  display: block;
-}
+  .patterns {
+    flex-grow: 0;
+    flex-shrink: 0;
+    font-size: 0.75rem;
+    width: 13rem;
+  }
 
-.id-primary-pattern,
-.id-secondary-pattern {
-  font-weight: bold;
-}
+  span.block {
+    display: block;
+  }
 
-.id-primary-pattern,
-.id-secondary-pattern,
-.id-source {
-  font-size: 0.75rem;
-}
+  .metadata {
+    flex-basis: 0;
+    flex-grow: 1;
+  }
 
-.pattern-type {
-  font-size: 0.5rem;
-}
+  .metadata span {
+    width: auto;
+    font-size: 0.7rem;
+  }
+
+  .metadata span.label {
+    font-weight: bold;
+    padding-right: 1ch;
+  }
 </style>
+
 <div class="content-setting">
+  <div class="patterns">
+    <div class="pattern">
+      <span class="id-primary-pattern-header pattern-header">
+        <span class="pattern-type">Primary</span>
+      </span>
+      <span class="block id-primary-pattern"></span>
+    </div>
 
-<button type="button" id="expand-button">Details</button>
+    <div class="pattern">
+      <span class="id-secondary-pattern-header pattern-header">
+        <span class="block pattern-type">Secondary</span>
+      </span>
+      <span class="block id-secondary-pattern"></span>
+    </div>
 
-<div class="pattern">
-<span class="block pattern-type">Primary</span>
-<span class="block id-primary-pattern"></span>
-</div>
+    <div class="pattern">
+      <span class="block pattern-type">Source</span>
+      <span class="block id-source"></span>
+    </div>
+  </div>
 
-<div class="pattern">
-<span class="block pattern-type">Secondary</span>
-<span class="block id-secondary-pattern"></span>
-</div>
+  <div class="metadata">
+    <div>
+      <span class="label">Value:</span>
+      <span class="value id-value"></span>
+    </div>
+    <div>
+      <span class="label">Incognito:</span>
+      <span class="value id-incognito"></span>
+    </div>
+    <div>
+      <span class="label">Last Modified:</span>
+      <span class="value id-last-modified"></span>
+    </div>
+    <div>
+      <span class="label">Last Used:</span>
+      <span class="value id-last-used"></span>
+    </div>
 
-<div class="pattern">
-<span class="block pattern-type">Source</span>
-<span class="block id-source"></span>
-</div>
-
-<table id="metadata">
-  <tbody>
-    <tr>
-      <td class="label">Value:</td>
-      <td class="value id-value"></td>
-    </tr>
-    <tr>
-      <td class="label">Incognito:</td>
-      <td class="value id-incognito"></td>
-    </tr>
-    <tr>
-      <td class="label">Last Modified:</td>
-      <td class="value id-last-modified"></td>
-    </tr>
-    <tr>
-      <td class="label">Last Used:</td>
-      <td class="value id-last-used"></td>
-    </tr>
-    <tr>
-      <td class="label">Last Visited:</td>
-      <td class="value id-last-visited"></td>
-    </tr>
-    <tr>
-      <td class="label">Expiration:</td>
-      <td class="value id-expiration"></td>
-    </tr>
-    <tr>
-      <td class="label">Session Model:</td>
-      <td class="value id-session-model"></td>
-    </tr>
-    <tr>
-      <td class="label">Lifetime:</td>
-      <td class="value id-lifetime"></td>
-    </tr>
-  </tbody>
-</table>
+    <div>
+      <span class="label">Last Visited:</span>
+      <span class="value id-last-visited"></span>
+    </div>
+    <div>
+      <span class="label">Expiration:</span>
+      <span class="value id-expiration"></span>
+    </div>
+    <div>
+      <span class="label">Session Model:</span>
+      <span class="value id-session-model"></span>
+    </div>
+    <div>
+      <span class="label">Lifetime:</span>
+      <span class="value id-lifetime"></span>
+    </div>
+  </div>
 </div>
diff --git a/chrome/browser/resources/privacy_sandbox/internals/content_setting_pattern_source.ts b/chrome/browser/resources/privacy_sandbox/internals/content_setting_pattern_source.ts
index 84620e4..80949b6a 100644
--- a/chrome/browser/resources/privacy_sandbox/internals/content_setting_pattern_source.ts
+++ b/chrome/browser/resources/privacy_sandbox/internals/content_setting_pattern_source.ts
@@ -4,6 +4,7 @@
 import './value_display.js';
 import './mojo_timestamp.js';
 import './mojo_timedelta.js';
+import './text_copy_button.js';
 
 import type {Time, TimeDelta} from '//resources/mojo/mojo/public/mojom/base/time.mojom-webui.js';
 import {CustomElement} from 'chrome://resources/js/custom_element.js';
@@ -57,8 +58,6 @@
 }
 
 export class ContentSettingPatternSourceElement extends CustomElement {
-  static observedAttributes = ['collapsed'];
-
   static override get template() {
     return getTemplate();
   }
@@ -75,6 +74,14 @@
     const elemToFill = this.getFieldElement(key);
     if (elemToFill) {
       elemToFill.textContent = value;
+      elemToFill.title = value;
+
+      const patternHeader = this.getFieldElement(key + '-header');
+      if (patternHeader) {
+        const copyButton = document.createElement('text-copy-button');
+        copyButton.textToCopy = value;
+        patternHeader.appendChild(copyButton);
+      }
     }
   }
 
@@ -143,26 +150,6 @@
     sessionModel.intValue = cs.metadata.sessionModel;
     this.setFieldValue('session-model', sessionModel, sessionModelLogicalValue);
     this.setFieldDuration('lifetime', cs.metadata.lifetime);
-
-    this.shadowRoot!.querySelector<HTMLElement>(
-                        '#expand-button')!.addEventListener('click', () => {
-      if (this.getAttribute('collapsed') === 'true') {
-        this.setAttribute('collapsed', 'false');
-      } else {
-        this.setAttribute('collapsed', 'true');
-      }
-    });
-  }
-
-  attributeChangedCallback(name: string, _oldValue: string, newValue: string) {
-    if (name === 'collapsed') {
-      const table = this.shadowRoot!.querySelector<HTMLElement>('#metadata')!;
-      if (newValue === 'true') {
-        table.classList.add('hidden');
-      } else {
-        table.classList.remove('hidden');
-      }
-    }
   }
 }
 
diff --git a/chrome/browser/resources/privacy_sandbox/internals/internals_page.html b/chrome/browser/resources/privacy_sandbox/internals/internals_page.html
index 9471d9fb..806f2ab69 100644
--- a/chrome/browser/resources/privacy_sandbox/internals/internals_page.html
+++ b/chrome/browser/resources/privacy_sandbox/internals/internals_page.html
@@ -10,20 +10,19 @@
     width: 100%;
   }
 
-  div.content-settings {
-    display: grid;
-    grid-template-columns: auto auto auto;
-  }
+div.content-settings {
+  display: grid;
+  gap: 24px;
+  grid-template-columns: repeat(auto-fit, minmax(min(100%, 28rem), 1fr));
+}
 
-  content-setting-pattern-source:nth-of-type(odd) {
-    background: rgb(239, 243, 255);
-  }
-
-  content-setting-pattern-source {
-    display: grid;
-    border: 1px solid lightgray;
-    margin: 5px;
-  }
+content-setting-pattern-source {
+  display: grid;
+  box-shadow: rgba(0, 0, 0, 0.3) 0 1px 2px 0,
+                 rgba(0, 0, 0, 0.15) 0 2px 6px 2px;
+  background: white;
+  border-radius: 8px;
+}
 </style>
 
 <cr-frame-list id="ps-page">
@@ -45,4 +44,4 @@
     <h3>Advertising Prefs</h3>
     <div id="advertising-prefs-panel"></div>
   </div>
-</cr-frame-list>
\ No newline at end of file
+</cr-frame-list>
diff --git a/chrome/browser/resources/privacy_sandbox/internals/internals_page.ts b/chrome/browser/resources/privacy_sandbox/internals/internals_page.ts
index 1d8cd2d..c1234bc 100644
--- a/chrome/browser/resources/privacy_sandbox/internals/internals_page.ts
+++ b/chrome/browser/resources/privacy_sandbox/internals/internals_page.ts
@@ -153,7 +153,7 @@
     const tabBox =
         this.shadowRoot!.querySelector<CrFrameListElement>('#ps-page')!;
     this.loadAndDisplayPrefs();
-    const csPanels = new Map<string, HTMLElement>();
+    const csPanelContainers = new Map<string, HTMLElement>();
     const handler = this.browserProxy_.handler;
     const shouldShowTpcdMetadataGrants =
         this.browserProxy_.shouldShowTpcdMetadataGrants();
@@ -173,14 +173,16 @@
 
       const panel = document.createElement('div');
       panel.setAttribute('slot', 'panel');
-      panel.setAttribute('style', 'content-settings');
       panel.setAttribute('title', ContentSettingsType[i]);
       const panelTitle = document.createElement('h2');
       panelTitle.innerText = ContentSettingsType[i];
+      const contentSettingsContainer = document.createElement('div');
+      contentSettingsContainer.classList.add('content-settings');
       panel.appendChild(panelTitle);
+      panel.appendChild(contentSettingsContainer);
       tabBox.appendChild(panel);
 
-      csPanels.set(ContentSettingsType[i], panel);
+      csPanelContainers.set(ContentSettingsType[i], contentSettingsContainer);
     }
 
     for (let i = ContentSettingsType.MIN_VALUE;
@@ -198,7 +200,7 @@
         mojoResponse = await handler.readContentSettings(i);
       }
       mojoResponse.contentSettings.forEach((cs: any) => {
-        const panel = csPanels.get(ContentSettingsType[i])!;
+        const panel = csPanelContainers.get(ContentSettingsType[i])!;
         const item = document.createElement('content-setting-pattern-source');
         panel.appendChild(item);
         item.configure(handler, cs);
diff --git a/chrome/browser/resources/privacy_sandbox/internals/text_copy_button.html b/chrome/browser/resources/privacy_sandbox/internals/text_copy_button.html
new file mode 100644
index 0000000..de2a500a
--- /dev/null
+++ b/chrome/browser/resources/privacy_sandbox/internals/text_copy_button.html
@@ -0,0 +1,47 @@
+<style>
+  .copy-icon {
+    display: block;
+  }
+
+  .tick-icon {
+    display: none;
+  }
+
+  :host([text-recently-copied]){
+    .copy-icon {
+      display: none;
+    }
+
+    .tick-icon {
+      display: block;
+    }
+  }
+
+
+  svg {
+    fill: darkgray;
+    height: 1rem;
+    width: 1rem;
+
+    &:hover {
+      fill: gray;
+    }
+
+    &:active {
+      fill: black;
+    }
+  }
+</style>
+
+<span>
+  <span class="copy-icon">
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960">
+      <path d="M360-240q-33 0-56.5-23.5T280-320v-480q0-33 23.5-56.5T360-880h360q33 0 56.5 23.5T800-800v480q0 33-23.5 56.5T720-240H360Zm0-80h360v-480H360v480ZM200-80q-33 0-56.5-23.5T120-160v-560h80v560h440v80H200Zm160-240v-480 480Z">
+    </svg>
+  </span>
+  <span class="tick-icon">
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960">
+      <path d="M382-240 154-468l57-57 171 171 367-367 57 57-424 424Z">
+    </svg>
+  </span>
+</span>
diff --git a/chrome/browser/resources/privacy_sandbox/internals/text_copy_button.ts b/chrome/browser/resources/privacy_sandbox/internals/text_copy_button.ts
new file mode 100644
index 0000000..68f892c
--- /dev/null
+++ b/chrome/browser/resources/privacy_sandbox/internals/text_copy_button.ts
@@ -0,0 +1,57 @@
+// 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 {CustomElement} from 'chrome://resources/js/custom_element.js';
+
+import {getTemplate} from './text_copy_button.html.js';
+
+export class TextCopyButton extends CustomElement {
+  textToCopy: string = '';
+  private revertIconTimeoutId_: number|null = null;
+  readonly revertIconWaitDuration = 3000;
+
+  static override get template() {
+    return getTemplate();
+  }
+
+  constructor() {
+    super();
+    this.onClickHandler = this.onClickHandler.bind(this);
+  }
+
+  connectedCallback() {
+    this.addEventListener('click', this.onClickHandler);
+  }
+
+  disconnectedCallback() {
+    this.removeEventListener('click', this.onClickHandler);
+  }
+
+  async onClickHandler() {
+    {
+      try {
+        await navigator.clipboard.writeText(this.textToCopy);
+        this.setAttribute('text-recently-copied', '');
+
+        if (this.revertIconTimeoutId_) {
+          clearTimeout(this.revertIconTimeoutId_);
+        }
+
+        this.revertIconTimeoutId_ = setTimeout(() => {
+          this.removeAttribute('text-recently-copied');
+          this.revertIconTimeoutId_ = null;
+        }, this.revertIconWaitDuration);
+      } catch (err) {
+        console.error('Failed to copy: ', err);
+      }
+    }
+  }
+}
+
+declare global {
+  interface HTMLElementTagNameMap {
+    'text-copy-button': TextCopyButton;
+  }
+}
+
+customElements.define('text-copy-button', TextCopyButton);
diff --git a/chrome/browser/resources/side_panel/comments/BUILD.gn b/chrome/browser/resources/side_panel/comments/BUILD.gn
index b4aaa28..41b5a77 100644
--- a/chrome/browser/resources/side_panel/comments/BUILD.gn
+++ b/chrome/browser/resources/side_panel/comments/BUILD.gn
@@ -13,9 +13,17 @@
   ts_files = [
     "app.html.ts",
     "app.ts",
+    "comments_api_proxy.ts",
   ]
 
-  ts_composite = true
-  ts_deps = [ "//third_party/lit/v3_0:build_ts" ]
+  ts_deps = [
+    "//third_party/lit/v3_0:build_ts",
+    "//ui/webui/resources/mojo:build_ts",
+  ]
   webui_context_type = "trusted"
+
+  mojo_files_deps = [
+    "//chrome/browser/ui/webui/side_panel/comments:mojo_bindings_ts__generator",
+  ]
+  mojo_files = [ "$root_gen_dir/chrome/browser/ui/webui/side_panel/comments/comments.mojom-webui.ts" ]
 }
diff --git a/chrome/browser/resources/side_panel/comments/app.ts b/chrome/browser/resources/side_panel/comments/app.ts
index f6c8038..36e8a93 100644
--- a/chrome/browser/resources/side_panel/comments/app.ts
+++ b/chrome/browser/resources/side_panel/comments/app.ts
@@ -5,8 +5,12 @@
 import {CrLitElement} from '//resources/lit/v3_0/lit.rollup.js';
 
 import {getHtml} from './app.html.js';
+import type {CommentsApiProxy} from './comments_api_proxy.js';
+import {CommentsApiProxyImpl} from './comments_api_proxy.js';
 
 export class CommentsApp extends CrLitElement {
+  private commentsApi_: CommentsApiProxy = CommentsApiProxyImpl.getInstance();
+
   static get is() {
     return 'comments-app';
   }
@@ -14,6 +18,11 @@
   override render() {
     return getHtml.bind(this)();
   }
+
+  override connectedCallback() {
+    super.connectedCallback();
+    this.commentsApi_.showUi();
+  }
 }
 
 declare global {
diff --git a/chrome/browser/resources/side_panel/comments/comments_api_proxy.ts b/chrome/browser/resources/side_panel/comments/comments_api_proxy.ts
new file mode 100644
index 0000000..9e0bd5f
--- /dev/null
+++ b/chrome/browser/resources/side_panel/comments/comments_api_proxy.ts
@@ -0,0 +1,42 @@
+// Copyright 2025 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import {PageCallbackRouter, PageHandlerFactory, PageHandlerRemote} from './comments.mojom-webui.js';
+import type {PageHandlerInterface} from './comments.mojom-webui.js';
+
+let instance: CommentsApiProxy|null = null;
+
+export interface CommentsApiProxy {
+  pageCallbackRouter: PageCallbackRouter;
+  handler: PageHandlerInterface;
+
+  showUi(): void;
+}
+
+export class CommentsApiProxyImpl implements CommentsApiProxy {
+  pageCallbackRouter: PageCallbackRouter;
+  handler: PageHandlerRemote;
+
+  constructor() {
+    this.pageCallbackRouter = new PageCallbackRouter();
+    this.handler = new PageHandlerRemote();
+
+    const factory = PageHandlerFactory.getRemote();
+    factory.createPageHandler(
+        this.pageCallbackRouter.$.bindNewPipeAndPassRemote(),
+        this.handler.$.bindNewPipeAndPassReceiver());
+  }
+
+  showUi() {
+    this.handler.showUI();
+  }
+
+  static getInstance(): CommentsApiProxy {
+    return instance || (instance = new CommentsApiProxyImpl());
+  }
+
+  static setInstance(obj: CommentsApiProxy) {
+    instance = obj;
+  }
+}
diff --git a/chrome/browser/resources/tab_strip_internals/BUILD.gn b/chrome/browser/resources/tab_strip_internals/BUILD.gn
new file mode 100644
index 0000000..89051bf
--- /dev/null
+++ b/chrome/browser/resources/tab_strip_internals/BUILD.gn
@@ -0,0 +1,16 @@
+# 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")
+
+# This Debug UI is intended only for desktop platforms:
+# windows, linux, mac, chromeos.
+assert(enable_session_service)
+
+build_webui("build") {
+  grd_prefix = "tab_strip_internals"
+
+  static_files = [ "tab_strip_internals.html" ]
+  ts_files = [ "tab_strip_internals.ts" ]
+}
diff --git a/chrome/browser/resources/tab_strip_internals/DIR_METADATA b/chrome/browser/resources/tab_strip_internals/DIR_METADATA
new file mode 100644
index 0000000..e72bd16
--- /dev/null
+++ b/chrome/browser/resources/tab_strip_internals/DIR_METADATA
@@ -0,0 +1,4 @@
+team_email: "top-chrome-desktop-ui@google.com"
+buganizer_public: {
+  component_id: 1456118
+}
diff --git a/chrome/browser/resources/tab_strip_internals/OWNERS b/chrome/browser/resources/tab_strip_internals/OWNERS
new file mode 100644
index 0000000..2c5d2ca
--- /dev/null
+++ b/chrome/browser/resources/tab_strip_internals/OWNERS
@@ -0,0 +1,6 @@
+# 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.
+
+dljames@google.com
+shibalik@google.com
diff --git a/chrome/browser/resources/tab_strip_internals/tab_strip_internals.html b/chrome/browser/resources/tab_strip_internals/tab_strip_internals.html
new file mode 100644
index 0000000..c73a688
--- /dev/null
+++ b/chrome/browser/resources/tab_strip_internals/tab_strip_internals.html
@@ -0,0 +1,13 @@
+<!-- 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.-->
+
+<!DOCTYPE html>
+<html>
+  <meta charset="utf-8">
+  <title>DebugUI TabStrip</title>
+  <body>
+    <h1>DebugUI TabStrip</h1>
+    <script type="module" src="tab_strip_internals.js"></script>
+  </body>
+</html>
diff --git a/chrome/browser/resources/tab_strip_internals/tab_strip_internals.ts b/chrome/browser/resources/tab_strip_internals/tab_strip_internals.ts
new file mode 100644
index 0000000..98f2f81d
--- /dev/null
+++ b/chrome/browser/resources/tab_strip_internals/tab_strip_internals.ts
@@ -0,0 +1,7 @@
+// 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.
+
+// TODO(crbug.com/427204855): Follow up with the actual implementation to
+// visualize state of the TabStrip model.
+console.info('TabStrip Internals WebUI loaded.');
diff --git a/chrome/browser/safe_browsing/android/BUILD.gn b/chrome/browser/safe_browsing/android/BUILD.gn
index 066d23e..629027d 100644
--- a/chrome/browser/safe_browsing/android/BUILD.gn
+++ b/chrome/browser/safe_browsing/android/BUILD.gn
@@ -150,6 +150,7 @@
     "//chrome/browser/settings:java",
     "//chrome/browser/settings:test_support_java",
     "//chrome/test/android:chrome_java_integration_test_support",
+    "//chrome/test/android:chrome_java_transit",
     "//components/browser_ui/settings/android:java",
     "//components/browser_ui/widget/android:java",
     "//components/permissions/android:core_java",
diff --git a/chrome/browser/safe_browsing/android/java/src/org/chromium/chrome/browser/safe_browsing/AdvancedProtectionMediatorIntegrationTest.java b/chrome/browser/safe_browsing/android/java/src/org/chromium/chrome/browser/safe_browsing/AdvancedProtectionMediatorIntegrationTest.java
index 83c70f36..66ca745b 100644
--- a/chrome/browser/safe_browsing/android/java/src/org/chromium/chrome/browser/safe_browsing/AdvancedProtectionMediatorIntegrationTest.java
+++ b/chrome/browser/safe_browsing/android/java/src/org/chromium/chrome/browser/safe_browsing/AdvancedProtectionMediatorIntegrationTest.java
@@ -18,7 +18,8 @@
 import org.chromium.base.test.util.HistogramWatcher;
 import org.chromium.chrome.browser.flags.ChromeSwitches;
 import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
-import org.chromium.chrome.test.ChromeTabbedActivityTestRule;
+import org.chromium.chrome.test.transit.ChromeTransitTestRules;
+import org.chromium.chrome.test.transit.FreshCtaTransitTestRule;
 import org.chromium.chrome.test.util.AdvancedProtectionTestRule;
 import org.chromium.components.permissions.PermissionsAndroidFeatureList;
 
@@ -33,8 +34,8 @@
             new AdvancedProtectionTestRule();
 
     @Rule
-    public final ChromeTabbedActivityTestRule mActivityTestRule =
-            new ChromeTabbedActivityTestRule();
+    public final FreshCtaTransitTestRule mActivityTestRule =
+            ChromeTransitTestRules.freshChromeTabbedActivityRule();
 
     private static final String ADVANCED_PROTECTION_UMA =
             "SafeBrowsing.Android.AdvancedProtection.Enabled";
@@ -50,7 +51,7 @@
         sAdvancedProtectionTestRule.setIsAdvancedProtectionRequestedByOs(true);
         HistogramWatcher watcher =
                 HistogramWatcher.newSingleRecordWatcher(ADVANCED_PROTECTION_UMA, true);
-        mActivityTestRule.startMainActivityOnBlankPage();
+        mActivityTestRule.startOnBlankPage();
         watcher.pollInstrumentationThreadUntilSatisfied();
     }
 
@@ -60,7 +61,7 @@
         sAdvancedProtectionTestRule.setIsAdvancedProtectionRequestedByOs(false);
         HistogramWatcher watcher =
                 HistogramWatcher.newSingleRecordWatcher(ADVANCED_PROTECTION_UMA, false);
-        mActivityTestRule.startMainActivityOnBlankPage();
+        mActivityTestRule.startOnBlankPage();
         watcher.pollInstrumentationThreadUntilSatisfied();
     }
 }
diff --git a/chrome/browser/safe_browsing/cloud_content_scanning/deep_scanning_utils.cc b/chrome/browser/safe_browsing/cloud_content_scanning/deep_scanning_utils.cc
index c2c221e1..e27efa5 100644
--- a/chrome/browser/safe_browsing/cloud_content_scanning/deep_scanning_utils.cc
+++ b/chrome/browser/safe_browsing/cloud_content_scanning/deep_scanning_utils.cc
@@ -9,6 +9,7 @@
 #include "base/metrics/histogram_functions.h"
 #include "base/strings/string_number_conversions.h"
 #include "base/strings/string_util.h"
+#include "chrome/browser/enterprise/connectors/analysis/content_analysis_info.h"
 #include "chrome/browser/enterprise/connectors/common.h"
 #include "chrome/browser/enterprise/connectors/reporting/reporting_event_router_factory.h"
 #include "chrome/browser/extensions/api/safe_browsing_private/safe_browsing_private_event_router.h"
@@ -163,6 +164,9 @@
     const enterprise_connectors::ContentAnalysisResponse& response,
     enterprise_connectors::EventResult event_result) {
   DCHECK(std::ranges::all_of(download_digest_sha256, base::IsHexDigit<char>));
+  // TODO(crbug.com/432679921): Remove unused safe browsing router and make
+  // reporting event router better at handling test cases in which the reporting
+  // set-up is missing.
   auto* safe_browsing_router =
       extensions::SafeBrowsingPrivateEventRouterFactory::GetForProfile(profile);
   auto* reporting_event_router =
@@ -197,11 +201,13 @@
           mime_type, trigger, std::move(unscanned_reason),
           content_transfer_method, content_size, event_result);
     } else if (response_result.triggered_rules_size() > 0) {
-      safe_browsing_router->OnAnalysisConnectorResult(
+      reporting_event_router->OnAnalysisConnectorResult(
           url, tab_url, source, destination, file_name, download_digest_sha256,
           mime_type, trigger, response.request_token(), content_transfer_method,
-          source_email, response_result, content_size, referrer_chain,
-          event_result);
+          source_email,
+          enterprise_connectors::ContentAreaUserProvider::GetUser(profile,
+                                                                  tab_url),
+          response_result, content_size, referrer_chain, event_result);
     }
   }
 }
diff --git a/chrome/browser/safety_hub/android/BUILD.gn b/chrome/browser/safety_hub/android/BUILD.gn
index c6cba8fe..338b886 100644
--- a/chrome/browser/safety_hub/android/BUILD.gn
+++ b/chrome/browser/safety_hub/android/BUILD.gn
@@ -255,6 +255,7 @@
     "//chrome/browser/tabmodel:java",
     "//chrome/test/android:chrome_java_integration_test_support",
     "//chrome/test/android:chrome_java_test_support_common",
+    "//chrome/test/android:chrome_java_transit",
     "//components/browser_ui/notifications/android:java",
     "//components/browser_ui/notifications/android:test_support_java",
     "//components/browser_ui/notifications/android:utils_java",
diff --git a/chrome/browser/safety_hub/android/javatests/src/org/chromium/chrome/browser/safety_hub/SafetyHubHatsHelperTest.java b/chrome/browser/safety_hub/android/javatests/src/org/chromium/chrome/browser/safety_hub/SafetyHubHatsHelperTest.java
index 4df4d7c7..c7cf49b6 100644
--- a/chrome/browser/safety_hub/android/javatests/src/org/chromium/chrome/browser/safety_hub/SafetyHubHatsHelperTest.java
+++ b/chrome/browser/safety_hub/android/javatests/src/org/chromium/chrome/browser/safety_hub/SafetyHubHatsHelperTest.java
@@ -38,7 +38,9 @@
 import org.chromium.chrome.browser.safe_browsing.SafeBrowsingState;
 import org.chromium.chrome.browser.tabmodel.TabModelSelector;
 import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
-import org.chromium.chrome.test.ChromeTabbedActivityTestRule;
+import org.chromium.chrome.test.transit.ChromeTransitTestRules;
+import org.chromium.chrome.test.transit.FreshCtaTransitTestRule;
+import org.chromium.chrome.test.transit.page.WebPageStation;
 import org.chromium.chrome.test.util.ChromeTabUtils;
 import org.chromium.content_public.browser.WebContents;
 
@@ -58,7 +60,8 @@
     @Rule public MockitoRule mMockitoRule = MockitoJUnit.rule();
 
     @Rule
-    public ChromeTabbedActivityTestRule mActivityTestRule = new ChromeTabbedActivityTestRule();
+    public FreshCtaTransitTestRule mActivityTestRule =
+            ChromeTransitTestRules.freshChromeTabbedActivityRule();
 
     @Mock private SafetyHubHatsBridge.Natives mSafetyHubHatsBridgeNatives;
     @Mock private SafetyHubFetchService mSafetyHubFetchService;
@@ -73,6 +76,7 @@
     private TabModelSelector mTabModelSelector;
     private Profile mProfile;
     private ChromeTabbedActivity mActivity;
+    private WebPageStation mPage;
 
     @Before
     public void setUp() throws ExecutionException {
@@ -101,13 +105,13 @@
                 .when(mSafeBrowsingBridgeNativeMock)
                 .getSafeBrowsingState(mProfile);
 
-        mActivityTestRule.startMainActivityOnBlankPage();
+        mPage = mActivityTestRule.startOnBlankPage();
         mActivityTestRule.waitForActivityNativeInitializationComplete();
         mActivityTestRule.waitForActivityCompletelyLoaded();
-        mTabModelSelector = mActivityTestRule.getActivity().getTabModelSelectorSupplier().get();
+        mTabModelSelector = mPage.getTabModelSelector();
         ThreadUtils.runOnUiThreadBlocking(
                 () -> mProfile = ProfileManager.getLastUsedRegularProfile());
-        mActivity = mActivityTestRule.getActivity();
+        mActivity = mPage.getActivity();
     }
 
     @Test
diff --git a/chrome/browser/safety_hub/android/javatests/src/org/chromium/chrome/browser/safety_hub/SafetyHubTest.java b/chrome/browser/safety_hub/android/javatests/src/org/chromium/chrome/browser/safety_hub/SafetyHubTest.java
index d6a98448..c3af5e21 100644
--- a/chrome/browser/safety_hub/android/javatests/src/org/chromium/chrome/browser/safety_hub/SafetyHubTest.java
+++ b/chrome/browser/safety_hub/android/javatests/src/org/chromium/chrome/browser/safety_hub/SafetyHubTest.java
@@ -93,7 +93,9 @@
 import org.chromium.chrome.browser.settings.SettingsActivityTestRule;
 import org.chromium.chrome.browser.tab.Tab;
 import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
-import org.chromium.chrome.test.ChromeTabbedActivityTestRule;
+import org.chromium.chrome.test.transit.ChromeTransitTestRules;
+import org.chromium.chrome.test.transit.FreshCtaTransitTestRule;
+import org.chromium.chrome.test.transit.page.WebPageStation;
 import org.chromium.chrome.test.util.ActivityTestUtils;
 import org.chromium.chrome.test.util.ChromeRenderTestRule;
 import org.chromium.chrome.test.util.browser.signin.SigninTestRule;
@@ -185,7 +187,8 @@
                     .build();
 
     @Rule
-    public ChromeTabbedActivityTestRule mActivityTestRule = new ChromeTabbedActivityTestRule();
+    public FreshCtaTransitTestRule mActivityTestRule =
+            ChromeTransitTestRules.freshChromeTabbedActivityRule();
 
     @Rule public final SigninTestRule mSigninTestRule = new SigninTestRule();
 
@@ -195,6 +198,7 @@
     private final FakeNotificationPermissionReviewBridge mNotificationPermissionReviewBridge =
             new FakeNotificationPermissionReviewBridge();
 
+    private WebPageStation mPage;
     private Profile mProfile;
 
     private void executeWhileCapturingIntents(Runnable func) {
@@ -223,7 +227,7 @@
         NotificationPermissionReviewBridgeJni.setInstanceForTesting(
                 mNotificationPermissionReviewBridge);
 
-        mActivityTestRule.startMainActivityOnBlankPage();
+        mPage = mActivityTestRule.startOnBlankPage();
         mProfile = mActivityTestRule.getProfile(/* incognito= */ false);
 
         // Reset state to the default of the compromised passwords count and the browsing data
diff --git a/chrome/browser/segmentation_platform/BUILD.gn b/chrome/browser/segmentation_platform/BUILD.gn
index b644bf6..2d3a085c 100644
--- a/chrome/browser/segmentation_platform/BUILD.gn
+++ b/chrome/browser/segmentation_platform/BUILD.gn
@@ -34,6 +34,7 @@
       "//chrome/browser/profiles/android:java",
       "//chrome/browser/segmentation_platform:factory_java",
       "//chrome/test/android:chrome_java_integration_test_support",
+      "//chrome/test/android:chrome_java_transit",
       "//components/segmentation_platform/public:public_java",
       "//third_party/androidx:androidx_test_runner_java",
       "//third_party/hamcrest:hamcrest_core_java",
diff --git a/chrome/browser/segmentation_platform/android/javatests/src/org/chromium/chrome/browser/segmentation_platform/SegmentationPlatformServiceFactoryTest.java b/chrome/browser/segmentation_platform/android/javatests/src/org/chromium/chrome/browser/segmentation_platform/SegmentationPlatformServiceFactoryTest.java
index 01933b3..3e60b91 100644
--- a/chrome/browser/segmentation_platform/android/javatests/src/org/chromium/chrome/browser/segmentation_platform/SegmentationPlatformServiceFactoryTest.java
+++ b/chrome/browser/segmentation_platform/android/javatests/src/org/chromium/chrome/browser/segmentation_platform/SegmentationPlatformServiceFactoryTest.java
@@ -27,7 +27,9 @@
 import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.chrome.browser.flags.ChromeSwitches;
 import org.chromium.chrome.browser.profiles.ProfileManager;
-import org.chromium.chrome.test.ChromeTabbedActivityTestRule;
+import org.chromium.chrome.test.transit.ChromeTransitTestRules;
+import org.chromium.chrome.test.transit.FreshCtaTransitTestRule;
+import org.chromium.chrome.test.transit.page.WebPageStation;
 import org.chromium.components.segmentation_platform.ClassificationResult;
 import org.chromium.components.segmentation_platform.Constants;
 import org.chromium.components.segmentation_platform.InputContext;
@@ -45,15 +47,17 @@
 public class SegmentationPlatformServiceFactoryTest {
 
     @Rule
-    public ChromeTabbedActivityTestRule mActivityTestRule = new ChromeTabbedActivityTestRule();
+    public FreshCtaTransitTestRule mActivityTestRule =
+            ChromeTransitTestRules.freshChromeTabbedActivityRule();
 
+    private WebPageStation mPage;
     private final CallbackHelper mCallbackHelper = new CallbackHelper();
 
     @Test
     @MediumTest
     public void testGetClassificationResult_withNullInputContext() throws TimeoutException {
         LibraryLoader.getInstance().ensureInitialized();
-        mActivityTestRule.startMainActivityOnBlankPage();
+        mActivityTestRule.startOnBlankPage();
 
         ThreadUtils.runOnUiThreadBlocking(
                 new Runnable() {
@@ -87,7 +91,7 @@
     @MediumTest
     public void testGetClassificationResult_withOnDemandModel() throws TimeoutException {
         LibraryLoader.getInstance().ensureInitialized();
-        mActivityTestRule.startMainActivityOnBlankPage();
+        mActivityTestRule.startOnBlankPage();
 
         ThreadUtils.runOnUiThreadBlocking(
                 new Runnable() {
diff --git a/chrome/browser/selection/android/BUILD.gn b/chrome/browser/selection/android/BUILD.gn
index 6ae58a8..3711f4bc 100644
--- a/chrome/browser/selection/android/BUILD.gn
+++ b/chrome/browser/selection/android/BUILD.gn
@@ -31,6 +31,7 @@
     "//chrome/browser/selection/android:java",
     "//chrome/browser/tab:java",
     "//chrome/test/android:chrome_java_integration_test_support",
+    "//chrome/test/android:chrome_java_transit",
     "//components/browser_ui/widget/android:java",
     "//content/public/android:content_full_java",
     "//content/public/test/android:content_java_test_support",
diff --git a/chrome/browser/selection/android/java/src/org/chromium/chrome/browser/selection/SelectionPopupBackPressTest.java b/chrome/browser/selection/android/java/src/org/chromium/chrome/browser/selection/SelectionPopupBackPressTest.java
index b026364e..53ee1ad 100644
--- a/chrome/browser/selection/android/java/src/org/chromium/chrome/browser/selection/SelectionPopupBackPressTest.java
+++ b/chrome/browser/selection/android/java/src/org/chromium/chrome/browser/selection/SelectionPopupBackPressTest.java
@@ -27,7 +27,9 @@
 import org.chromium.chrome.browser.tab.TabObserver;
 import org.chromium.chrome.browser.tab.TabTestUtils;
 import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
-import org.chromium.chrome.test.ChromeTabbedActivityTestRule;
+import org.chromium.chrome.test.transit.ChromeTransitTestRules;
+import org.chromium.chrome.test.transit.FreshCtaTransitTestRule;
+import org.chromium.chrome.test.transit.page.WebPageStation;
 import org.chromium.components.browser_ui.widget.gesture.BackPressHandler;
 import org.chromium.content_public.browser.SelectionPopupController;
 import org.chromium.content_public.browser.test.util.DOMUtils;
@@ -43,7 +45,8 @@
 @Batch(Batch.PER_CLASS)
 public class SelectionPopupBackPressTest {
     @Rule
-    public ChromeTabbedActivityTestRule mActivityTestRule = new ChromeTabbedActivityTestRule();
+    public FreshCtaTransitTestRule mActivityTestRule =
+            ChromeTransitTestRules.freshChromeTabbedActivityRule();
 
     private static final String TEST_PAGE =
             UrlUtils.encodeHtmlDataUri(
@@ -63,8 +66,8 @@
     @MediumTest
     @Feature({"TextInput", "SmartSelection"})
     public void testBackPressHandlerOnTabSwitched() {
-        mActivityTestRule.startMainActivityOnBlankPage();
-        final ChromeTabbedActivity activity = mActivityTestRule.getActivity();
+        WebPageStation page = mActivityTestRule.startOnBlankPage();
+        final ChromeTabbedActivity activity = page.getActivity();
         final BackPressHandler selectionPopupHandler =
                 activity.getBackPressManagerForTesting()
                         .getHandlersForTesting()[BackPressHandler.Type.SELECTION_POPUP];
@@ -92,8 +95,8 @@
     @MediumTest
     @Feature({"TextInput", "SmartSelection"})
     public void testBackPressHandlerOnWebContentChanged() {
-        mActivityTestRule.startMainActivityOnBlankPage();
-        final ChromeTabbedActivity activity = mActivityTestRule.getActivity();
+        WebPageStation page = mActivityTestRule.startOnBlankPage();
+        final ChromeTabbedActivity activity = page.getActivity();
         final SelectionPopupBackPressHandler selectionPopupHandler =
                 (SelectionPopupBackPressHandler)
                         activity.getBackPressManagerForTesting()
@@ -117,7 +120,7 @@
     }
 
     private void testBackPressClearSelectionInternal() throws TimeoutException {
-        mActivityTestRule.startMainActivityWithURL(TEST_PAGE);
+        mActivityTestRule.startOnUrl(TEST_PAGE);
         DOMUtils.longPressNodeByJs(
                 mActivityTestRule.getWebContents(),
                 "document.getElementById('selection_popup_text')");
diff --git a/chrome/browser/signin/chrome_signin_client.cc b/chrome/browser/signin/chrome_signin_client.cc
index d3cf5d9..9a8f277 100644
--- a/chrome/browser/signin/chrome_signin_client.cc
+++ b/chrome/browser/signin/chrome_signin_client.cc
@@ -35,8 +35,6 @@
 #include "chrome/browser/signin/identity_manager_factory.h"
 #include "chrome/browser/signin/signin_util.h"
 #include "chrome/browser/ui/browser_finder.h"
-#include "chrome/browser/ui/hats/hats_service.h"
-#include "chrome/browser/ui/hats/hats_service_factory.h"
 #include "chrome/browser/ui/hats/survey_config.h"
 #include "chrome/common/buildflags.h"
 #include "chrome/common/channel_info.h"
@@ -195,25 +193,6 @@
   return trigger;
 }
 
-// Launches a HaTS survey for the given trigger.
-void LaunchHatsSurvey(std::string trigger, Profile* profile, Browser* browser) {
-  CHECK(!trigger.empty());
-  CHECK(profile);
-
-  // A browser is required to launch the survey.
-  CHECK(browser);
-
-  HatsService* hats_service =
-      HatsServiceFactory::GetForProfile(profile,
-                                        /*create_if_necessary=*/true);
-  if (!hats_service) {
-    return;
-  }
-
-  // TODO(crbug.com/427971911): add product-specific data.
-  hats_service->LaunchSurvey(trigger);
-}
-
 #endif  // BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_WIN)
 
 }  // namespace
@@ -683,13 +662,14 @@
   if (browser) {
     // Launch the HaTS survey immediately if a browser is active for the
     // profile.
-    LaunchHatsSurvey(trigger, profile_, browser);
+    profiles::LaunchSigninHatsSurveyForBrowser(trigger, browser);
   } else {
     // If no browser is active, defer the survey launch until a browser becomes
     // available.
     // TODO(crbug.com/427971911): Fix test crashes due to the dangling pointer.
     new profiles::BrowserAddedForProfileObserver(
-        profile_, base::BindOnce(&LaunchHatsSurvey, trigger, profile_));
+        profile_,
+        base::BindOnce(&profiles::LaunchSigninHatsSurveyForBrowser, trigger));
   }
 #endif  // BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_WIN)
 }
diff --git a/chrome/browser/signin/signin_util.cc b/chrome/browser/signin/signin_util.cc
index 03cc44a..f34a292c 100644
--- a/chrome/browser/signin/signin_util.cc
+++ b/chrome/browser/signin/signin_util.cc
@@ -87,6 +87,10 @@
          &switches::kChromeIdentitySurveyProfilePickerAddProfileSignin},
         {kHatsSurveyTriggerIdentitySigninInterceptProfileSeparation,
          &switches::kChromeIdentitySurveySigninInterceptProfileSeparation},
+        {kHatsSurveyTriggerIdentitySwitchProfileFromProfileMenu,
+         &switches::kChromeIdentitySurveySwitchProfileFromProfileMenu},
+        {kHatsSurveyTriggerIdentitySwitchProfileFromProfilePicker,
+         &switches::kChromeIdentitySurveySwitchProfileFromProfilePicker},
 };
 #endif  // BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX)
 
diff --git a/chrome/browser/supervised_user/BUILD.gn b/chrome/browser/supervised_user/BUILD.gn
index e723908a..2b8bc353 100644
--- a/chrome/browser/supervised_user/BUILD.gn
+++ b/chrome/browser/supervised_user/BUILD.gn
@@ -219,6 +219,7 @@
       "//chrome/browser/ui/android/appmenu:java",
       "//chrome/browser/ui/android/appmenu/test:test_support_java",
       "//chrome/test/android:chrome_java_integration_test_support",
+      "//chrome/test/android:chrome_java_transit",
       "//chrome/test/android:pagecontroller_utils_java",
       "//components/browser_ui/bottomsheet/android:java",
       "//components/browser_ui/bottomsheet/android:manager_java",
diff --git a/chrome/browser/supervised_user/android/javatests/src/org/chromium/chrome/browser/supervised_user/IncognitoInteractionTest.java b/chrome/browser/supervised_user/android/javatests/src/org/chromium/chrome/browser/supervised_user/IncognitoInteractionTest.java
index fdead71..3c3c14fa5 100644
--- a/chrome/browser/supervised_user/android/javatests/src/org/chromium/chrome/browser/supervised_user/IncognitoInteractionTest.java
+++ b/chrome/browser/supervised_user/android/javatests/src/org/chromium/chrome/browser/supervised_user/IncognitoInteractionTest.java
@@ -22,7 +22,9 @@
 import org.chromium.chrome.browser.tab.EmptyTabObserver;
 import org.chromium.chrome.browser.tab.Tab;
 import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
-import org.chromium.chrome.test.ChromeTabbedActivityTestRule;
+import org.chromium.chrome.test.transit.ChromeTransitTestRules;
+import org.chromium.chrome.test.transit.FreshCtaTransitTestRule;
+import org.chromium.chrome.test.transit.ntp.RegularNewTabPageStation;
 import org.chromium.chrome.test.util.browser.signin.SigninTestRule;
 import org.chromium.components.signin.test.util.TestAccounts;
 
@@ -30,7 +32,8 @@
 @RunWith(ChromeJUnit4ClassRunner.class)
 @CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE})
 public class IncognitoInteractionTest {
-    public ChromeTabbedActivityTestRule mActivityTestRule = new ChromeTabbedActivityTestRule();
+    public FreshCtaTransitTestRule mActivityTestRule =
+            ChromeTransitTestRules.freshChromeTabbedActivityRule();
 
     public final SigninTestRule mSigninTestRule = new SigninTestRule();
 
@@ -38,6 +41,8 @@
     public final RuleChain mRuleChain =
             RuleChain.outerRule(mSigninTestRule).around(mActivityTestRule);
 
+    private RegularNewTabPageStation mNtp;
+
     /** Waits until the Incognito Tab is closed. */
     private static class TabClosedWaiter extends EmptyTabObserver {
         private final CallbackHelper mCallbackHelper;
@@ -58,7 +63,7 @@
 
     @Before
     public void setUp() {
-        mActivityTestRule.startMainActivityWithURL(null);
+        mNtp = mActivityTestRule.startFromLauncherAtNtp();
     }
 
     @Test
diff --git a/chrome/browser/supervised_user/android/javatests/src/org/chromium/chrome/browser/supervised_user/SupervisedUserCriticalJourneysIntegrationTest.java b/chrome/browser/supervised_user/android/javatests/src/org/chromium/chrome/browser/supervised_user/SupervisedUserCriticalJourneysIntegrationTest.java
index d837255b..1eb07a9 100644
--- a/chrome/browser/supervised_user/android/javatests/src/org/chromium/chrome/browser/supervised_user/SupervisedUserCriticalJourneysIntegrationTest.java
+++ b/chrome/browser/supervised_user/android/javatests/src/org/chromium/chrome/browser/supervised_user/SupervisedUserCriticalJourneysIntegrationTest.java
@@ -33,7 +33,9 @@
 import org.chromium.chrome.browser.tab.Tab;
 import org.chromium.chrome.browser.ui.appmenu.AppMenuTestSupport;
 import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
-import org.chromium.chrome.test.ChromeTabbedActivityTestRule;
+import org.chromium.chrome.test.transit.ChromeTransitTestRules;
+import org.chromium.chrome.test.transit.FreshCtaTransitTestRule;
+import org.chromium.chrome.test.transit.ntp.RegularNewTabPageStation;
 import org.chromium.chrome.test.util.browser.signin.SigninTestRule;
 import org.chromium.content_public.browser.WebContents;
 import org.chromium.net.test.EmbeddedTestServer;
@@ -46,7 +48,8 @@
     private static final String TEST_PAGE = "/chrome/test/data/android/test.html";
 
     @Rule
-    public ChromeTabbedActivityTestRule mActivityTestRule = new ChromeTabbedActivityTestRule();
+    public FreshCtaTransitTestRule mActivityTestRule =
+            ChromeTransitTestRules.freshChromeTabbedActivityRule();
 
     public final SigninTestRule mSigninTestRule = new SigninTestRule();
     private WebContents mWebContents;
@@ -55,9 +58,11 @@
     public final RuleChain mRuleChain =
             RuleChain.outerRule(mSigninTestRule).around(mActivityTestRule);
 
+    private RegularNewTabPageStation mNtp;
+
     @Before
     public void setUp() {
-        mActivityTestRule.startMainActivityWithURL(null);
+        mNtp = mActivityTestRule.startFromLauncherAtNtp();
         mSigninTestRule.addChildTestAccountThenWaitForSignin();
         mWebContents = mActivityTestRule.getWebContents();
     }
@@ -71,7 +76,7 @@
                             mActivityTestRule.getProfile(/* incognito= */ false), BLOCKED_SITE_URL);
                 });
 
-        EmbeddedTestServer testServer = mActivityTestRule.getEmbeddedTestServerRule().getServer();
+        EmbeddedTestServer testServer = mActivityTestRule.getTestServer();
         String blockedHost = testServer.getURLWithHostName(BLOCKED_SITE_URL, "/");
         mActivityTestRule.loadUrl(blockedHost);
 
diff --git a/chrome/browser/supervised_user/android/javatests/src/org/chromium/chrome/browser/supervised_user/WebsiteParentApprovalNativesTest.java b/chrome/browser/supervised_user/android/javatests/src/org/chromium/chrome/browser/supervised_user/WebsiteParentApprovalNativesTest.java
index 0ee1d17..391f322 100644
--- a/chrome/browser/supervised_user/android/javatests/src/org/chromium/chrome/browser/supervised_user/WebsiteParentApprovalNativesTest.java
+++ b/chrome/browser/supervised_user/android/javatests/src/org/chromium/chrome/browser/supervised_user/WebsiteParentApprovalNativesTest.java
@@ -31,7 +31,9 @@
 import org.chromium.chrome.browser.flags.ChromeSwitches;
 import org.chromium.chrome.browser.superviseduser.FilteringBehavior;
 import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
-import org.chromium.chrome.test.ChromeTabbedActivityTestRule;
+import org.chromium.chrome.test.transit.ChromeTransitTestRules;
+import org.chromium.chrome.test.transit.FreshCtaTransitTestRule;
+import org.chromium.chrome.test.transit.page.WebPageStation;
 import org.chromium.chrome.test.util.browser.signin.SigninTestRule;
 import org.chromium.components.browser_ui.bottomsheet.BottomSheetController;
 import org.chromium.components.browser_ui.bottomsheet.BottomSheetTestSupport;
@@ -59,8 +61,8 @@
     // TODO(b/243916194): Expand the test coverage beyond the completion callback, up to the page
     // refresh.
 
-    public ChromeTabbedActivityTestRule mTabbedActivityTestRule =
-            new ChromeTabbedActivityTestRule();
+    public FreshCtaTransitTestRule mTabbedActivityTestRule =
+            ChromeTransitTestRules.freshChromeTabbedActivityRule();
     public SigninTestRule mSigninTestRule = new SigninTestRule();
 
     // Destroy TabbedActivityTestRule before SigninTestRule to remove observers of
@@ -81,12 +83,13 @@
     private BottomSheetTestSupport mBottomSheetTestSupport;
 
     @Mock private ParentAuthDelegate mParentAuthDelegateMock;
+    private WebPageStation mPage;
 
     @Before
     public void setUp() throws TimeoutException {
-        mTestServer = mTabbedActivityTestRule.getEmbeddedTestServerRule().getServer();
+        mTestServer = mTabbedActivityTestRule.getTestServer();
         mBlockedUrl = mTestServer.getURL(TEST_PAGE);
-        mTabbedActivityTestRule.startMainActivityOnBlankPage();
+        mPage = mTabbedActivityTestRule.startOnBlankPage();
         ThreadUtils.runOnUiThreadBlocking(
                 () -> {
                     ChromeTabbedActivity activity = mTabbedActivityTestRule.getActivity();
diff --git a/chrome/browser/supervised_user/android/javatests/src/org/chromium/chrome/browser/supervised_user/WebsiteParentApprovalTest.java b/chrome/browser/supervised_user/android/javatests/src/org/chromium/chrome/browser/supervised_user/WebsiteParentApprovalTest.java
index d40f9a5..75b950e8 100644
--- a/chrome/browser/supervised_user/android/javatests/src/org/chromium/chrome/browser/supervised_user/WebsiteParentApprovalTest.java
+++ b/chrome/browser/supervised_user/android/javatests/src/org/chromium/chrome/browser/supervised_user/WebsiteParentApprovalTest.java
@@ -34,7 +34,9 @@
 import org.chromium.chrome.browser.supervised_user.android.AndroidLocalWebApprovalFlowOutcome;
 import org.chromium.chrome.browser.superviseduser.FilteringBehavior;
 import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
-import org.chromium.chrome.test.ChromeTabbedActivityTestRule;
+import org.chromium.chrome.test.transit.ChromeTransitTestRules;
+import org.chromium.chrome.test.transit.FreshCtaTransitTestRule;
+import org.chromium.chrome.test.transit.page.WebPageStation;
 import org.chromium.chrome.test.util.browser.signin.SigninTestRule;
 import org.chromium.components.browser_ui.bottomsheet.BottomSheetController;
 import org.chromium.components.browser_ui.bottomsheet.BottomSheetTestSupport;
@@ -61,8 +63,8 @@
                         + "which must remain unchanged for the duration of the test.")
 @CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE})
 public class WebsiteParentApprovalTest {
-    public ChromeTabbedActivityTestRule mTabbedActivityTestRule =
-            new ChromeTabbedActivityTestRule();
+    public FreshCtaTransitTestRule mTabbedActivityTestRule =
+            ChromeTransitTestRules.freshChromeTabbedActivityRule();
     public SigninTestRule mSigninTestRule = new SigninTestRule();
 
     // Destroy TabbedActivityTestRule before SigninTestRule to remove observers of
@@ -84,12 +86,13 @@
 
     @Mock private WebsiteParentApproval.Natives mWebsiteParentApprovalNativesMock;
     @Mock private ParentAuthDelegate mParentAuthDelegateMock;
+    private WebPageStation mPage;
 
     @Before
     public void setUp() throws TimeoutException {
-        mTestServer = mTabbedActivityTestRule.getEmbeddedTestServerRule().getServer();
+        mTestServer = mTabbedActivityTestRule.getTestServer();
         mBlockedUrl = mTestServer.getURL(TEST_PAGE);
-        mTabbedActivityTestRule.startMainActivityOnBlankPage();
+        mPage = mTabbedActivityTestRule.startOnBlankPage();
         ThreadUtils.runOnUiThreadBlocking(
                 () -> {
                     ChromeTabbedActivity activity = mTabbedActivityTestRule.getActivity();
diff --git a/chrome/browser/tab_ui/android/java/src/org/chromium/chrome/browser/tab_ui/TabSwitcherGroupSuggestionService.java b/chrome/browser/tab_ui/android/java/src/org/chromium/chrome/browser/tab_ui/TabSwitcherGroupSuggestionService.java
index 93754d8..5210019 100644
--- a/chrome/browser/tab_ui/android/java/src/org/chromium/chrome/browser/tab_ui/TabSwitcherGroupSuggestionService.java
+++ b/chrome/browser/tab_ui/android/java/src/org/chromium/chrome/browser/tab_ui/TabSwitcherGroupSuggestionService.java
@@ -125,14 +125,7 @@
                 }
 
                 @Override
-                public void didCreateGroup(
-                        List<Tab> tabs,
-                        List<Integer> tabOriginalIndex,
-                        List<Integer> tabOriginalRootId,
-                        List<Token> tabOriginalTabGroupId,
-                        @Nullable String destinationGroupTitle,
-                        int destinationGroupColorId,
-                        boolean destinationGroupTitleCollapsed) {
+                public void didCreateNewGroup(Tab destinationTab, TabGroupModelFilter filter) {
                     clearSuggestions();
                 }
 
diff --git a/chrome/browser/tab_ui/android/junit/src/org/chromium/chrome/browser/tab_ui/TabSwitcherGroupSuggestionServiceUnitTest.java b/chrome/browser/tab_ui/android/junit/src/org/chromium/chrome/browser/tab_ui/TabSwitcherGroupSuggestionServiceUnitTest.java
index bb0cc12..6839ef327 100644
--- a/chrome/browser/tab_ui/android/junit/src/org/chromium/chrome/browser/tab_ui/TabSwitcherGroupSuggestionServiceUnitTest.java
+++ b/chrome/browser/tab_ui/android/junit/src/org/chromium/chrome/browser/tab_ui/TabSwitcherGroupSuggestionServiceUnitTest.java
@@ -253,7 +253,6 @@
         TabGroupModelFilterObserver observer = mTabGroupModelFilterObserverCaptor.getValue();
 
         Tab mockTab = mock();
-        List<Tab> mockTabs = Collections.singletonList(mockTab);
 
         observer.willMergeTabToGroup(mockTab, 0, null);
         verify(mSuggestionLifecycleObserverHandler).onSuggestionIgnored();
@@ -267,7 +266,7 @@
         verify(mSuggestionLifecycleObserverHandler).onSuggestionIgnored();
 
         reset(mSuggestionLifecycleObserverHandler);
-        observer.didCreateGroup(mockTabs, null, null, null, null, 0, false);
+        observer.didCreateNewGroup(mockTab, mTabGroupModelFilter);
         verify(mSuggestionLifecycleObserverHandler).onSuggestionIgnored();
 
         reset(mSuggestionLifecycleObserverHandler);
diff --git a/chrome/browser/tabmodel/android/java/src/org/chromium/chrome/browser/tabmodel/TabCreator.java b/chrome/browser/tabmodel/android/java/src/org/chromium/chrome/browser/tabmodel/TabCreator.java
index 2cfb460..ce543ba 100644
--- a/chrome/browser/tabmodel/android/java/src/org/chromium/chrome/browser/tabmodel/TabCreator.java
+++ b/chrome/browser/tabmodel/android/java/src/org/chromium/chrome/browser/tabmodel/TabCreator.java
@@ -92,6 +92,7 @@
      * Creates a Tab to host the given WebContents.
      *
      * @param parent The parent Tab, if present.
+     * @param shouldPin Whether the newly created tab should be pinned.
      * @param webContents The web contents to create a Tab around.
      * @param type The TabLaunchType describing how this Tab was created.
      * @param url URL to show in the Tab. (Needed only for asynchronous tab creation.)
@@ -103,6 +104,7 @@
      */
     public abstract @Nullable Tab createTabWithWebContents(
             @Nullable Tab parent,
+            boolean shouldPin,
             WebContents webContents,
             @TabLaunchType int type,
             GURL url,
@@ -125,6 +127,29 @@
      * Creates a Tab to host the given WebContents and adds it to the TabModel.
      *
      * @param parent The parent Tab, if present.
+     * @param shouldPin Whether the newly created tab should be pinned.
+     * @param webContents The web contents to create a Tab around.
+     * @param type The TabLaunchType describing how this Tab was created.
+     * @return The new Tab or null if a Tab was not created successfully.
+     */
+    public final @Nullable Tab createTabWithWebContents(
+            @Nullable Tab parent,
+            boolean shouldPin,
+            WebContents webContents,
+            @TabLaunchType int type) {
+        return createTabWithWebContents(
+                parent,
+                shouldPin,
+                webContents,
+                type,
+                webContents.getVisibleUrl(),
+                /* addTabToModel= */ true);
+    }
+
+    /**
+     * Creates a Tab to host the given WebContents and adds it to the TabModel.
+     *
+     * @param parent The parent Tab, if present.
      * @param webContents The web contents to create a Tab around.
      * @param type The TabLaunchType describing how this Tab was created.
      * @param url URL to show in the Tab. (Needed only for asynchronous tab creation.)
@@ -132,7 +157,8 @@
      */
     public final @Nullable Tab createTabWithWebContents(
             @Nullable Tab parent, WebContents webContents, @TabLaunchType int type, GURL url) {
-        return createTabWithWebContents(parent, webContents, type, url, true);
+        return createTabWithWebContents(
+                parent, /* shouldPin= */ false, webContents, type, url, /* addTabToModel= */ true);
     }
 
     /**
diff --git a/chrome/browser/tabmodel/android/java/src/org/chromium/chrome/browser/tabmodel/TabGroupModelFilterImpl.java b/chrome/browser/tabmodel/android/java/src/org/chromium/chrome/browser/tabmodel/TabGroupModelFilterImpl.java
index d2237fe..01dbd05 100644
--- a/chrome/browser/tabmodel/android/java/src/org/chromium/chrome/browser/tabmodel/TabGroupModelFilterImpl.java
+++ b/chrome/browser/tabmodel/android/java/src/org/chromium/chrome/browser/tabmodel/TabGroupModelFilterImpl.java
@@ -349,7 +349,7 @@
                 // Since the undo group merge logic is unsupported when called from the tab strip,
                 // skip notifying the UndoGroupSnackbarController observer which shows the snackbar.
                 if (!skipUpdateTabModel) {
-                    observer.didCreateGroup(
+                    observer.showUndoGroupSnackbar(
                             tabsIncludingDestination,
                             originalIndexes,
                             originalRootIds,
@@ -477,7 +477,7 @@
 
             // Do not show a snackbar for new tab group creations as they launch a dialog.
             if (notify && !willMergingCreateNewGroup) {
-                observer.didCreateGroup(
+                observer.showUndoGroupSnackbar(
                         mergedTabs,
                         originalIndexes,
                         originalRootIds,
diff --git a/chrome/browser/tabmodel/android/java/src/org/chromium/chrome/browser/tabmodel/TabGroupModelFilterImplUnitTest.java b/chrome/browser/tabmodel/android/java/src/org/chromium/chrome/browser/tabmodel/TabGroupModelFilterImplUnitTest.java
index f830f93..886a0628 100644
--- a/chrome/browser/tabmodel/android/java/src/org/chromium/chrome/browser/tabmodel/TabGroupModelFilterImplUnitTest.java
+++ b/chrome/browser/tabmodel/android/java/src/org/chromium/chrome/browser/tabmodel/TabGroupModelFilterImplUnitTest.java
@@ -993,7 +993,7 @@
         verify(mTabGroupModelFilterObserver).didCreateNewGroup(mTab1, mTabGroupModelFilter);
         verify(mTabGroupModelFilterObserver).didMergeTabToGroup(mTab1);
         verify(mTabGroupModelFilterObserver, never())
-                .didCreateGroup(
+                .showUndoGroupSnackbar(
                         anyList(),
                         anyList(),
                         anyList(),
@@ -1317,7 +1317,7 @@
     public void mergeTabsToGroup_Collapsed() {
         mTabGroupModelFilter.mergeTabsToGroup(mTab5.getId(), mTab2.getId());
         verify(mTabGroupModelFilterObserver)
-                .didCreateGroup(any(), any(), any(), any(), any(), anyInt(), eq(true));
+                .showUndoGroupSnackbar(any(), any(), any(), any(), any(), anyInt(), eq(true));
     }
 
     @Test
@@ -1326,7 +1326,7 @@
                 .thenReturn(false);
         mTabGroupModelFilter.mergeTabsToGroup(mTab5.getId(), mTab2.getId());
         verify(mTabGroupModelFilterObserver)
-                .didCreateGroup(any(), any(), any(), any(), any(), anyInt(), eq(true));
+                .showUndoGroupSnackbar(any(), any(), any(), any(), any(), anyInt(), eq(true));
     }
 
     @Test
@@ -1335,7 +1335,7 @@
                 .thenReturn(false);
         mTabGroupModelFilter.mergeTabsToGroup(mTab5.getId(), mTab2.getId());
         verify(mTabGroupModelFilterObserver)
-                .didCreateGroup(any(), any(), any(), any(), any(), anyInt(), eq(false));
+                .showUndoGroupSnackbar(any(), any(), any(), any(), any(), anyInt(), eq(false));
     }
 
     @Test
@@ -1526,7 +1526,7 @@
         List<Tab> tabsToMerge = new ArrayList<>(Arrays.asList(mTab5, mTab6));
         mTabGroupModelFilter.mergeListOfTabsToGroup(tabsToMerge, mTab4, true);
         verify(mTabGroupModelFilterObserver)
-                .didCreateGroup(any(), any(), any(), any(), any(), anyInt(), eq(true));
+                .showUndoGroupSnackbar(any(), any(), any(), any(), any(), anyInt(), eq(true));
     }
 
     @Test
@@ -1536,7 +1536,7 @@
         List<Tab> tabsToMerge = new ArrayList<>(Arrays.asList(mTab5, mTab6));
         mTabGroupModelFilter.mergeListOfTabsToGroup(tabsToMerge, mTab4, true);
         verify(mTabGroupModelFilterObserver)
-                .didCreateGroup(any(), any(), any(), any(), any(), anyInt(), eq(true));
+                .showUndoGroupSnackbar(any(), any(), any(), any(), any(), anyInt(), eq(true));
     }
 
     @Test
@@ -1714,7 +1714,7 @@
         verify(mTabGroupModelFilterObserver).didCreateNewGroup(mTab4, mTabGroupModelFilter);
         verify(mTabGroupModelFilterObserver).didMergeTabToGroup(mTab4);
         verify(mTabGroupModelFilterObserver, never())
-                .didCreateGroup(any(), any(), any(), any(), any(), anyInt(), anyBoolean());
+                .showUndoGroupSnackbar(any(), any(), any(), any(), any(), anyInt(), anyBoolean());
 
         assertThat(mTab4.getTabGroupId(), equalTo(tabGroupId));
 
@@ -2089,7 +2089,7 @@
         verify(mTabGroupModelFilterObserver, never())
                 .didCreateNewGroup(mTab2, mTabGroupModelFilter);
         verify(mTabGroupModelFilterObserver)
-                .didCreateGroup(
+                .showUndoGroupSnackbar(
                         expectedNotifiedTabs,
                         originalIndexes,
                         originalRootIds,
@@ -2119,7 +2119,7 @@
         verify(mTabGroupModelFilterObserver, never())
                 .didCreateNewGroup(mTab2, mTabGroupModelFilter);
         verify(mTabGroupModelFilterObserver, never())
-                .didCreateGroup(
+                .showUndoGroupSnackbar(
                         anyList(),
                         anyList(),
                         anyList(),
@@ -2161,7 +2161,7 @@
         verify(mTabGroupModelFilterObserver, never())
                 .didCreateNewGroup(mTab4, mTabGroupModelFilter);
         verify(mTabGroupModelFilterObserver)
-                .didCreateGroup(
+                .showUndoGroupSnackbar(
                         expectedNotifiedTabs,
                         originalIndexes,
                         originalRootIds,
@@ -2191,7 +2191,7 @@
         mTabGroupModelFilter.mergeTabsToGroup(mTab1.getId(), mTab4.getId(), false);
         verify(mTabGroupModelFilterObserver).didCreateNewGroup(mTab4, mTabGroupModelFilter);
         verify(mTabGroupModelFilterObserver, never())
-                .didCreateGroup(
+                .showUndoGroupSnackbar(
                         anyList(),
                         anyList(),
                         anyList(),
@@ -2215,7 +2215,7 @@
         mTabGroupModelFilter.mergeTabsToGroup(mTab1.getId(), mTab4.getId(), true);
         verify(mTabGroupModelFilterObserver).didCreateNewGroup(mTab4, mTabGroupModelFilter);
         verify(mTabGroupModelFilterObserver, never())
-                .didCreateGroup(any(), any(), any(), any(), any(), anyInt(), anyBoolean());
+                .showUndoGroupSnackbar(any(), any(), any(), any(), any(), anyInt(), anyBoolean());
 
         assertEquals(mTab1.getTabGroupId(), tabGroupId);
         assertEquals(mTab4.getTabGroupId(), tabGroupId);
@@ -2702,14 +2702,14 @@
         List<Integer> expectedOriginalIndex = Arrays.asList(POSITION4, POSITION1);
         List<Integer> originalRootId = Arrays.asList(TAB4_ROOT_ID, TAB1_ROOT_ID);
         List<Token> originalTabGroupId =  Arrays.asList(TAB1_TAB_GROUP_ID, TAB4_TAB_GROUP_ID);
-        verify(mTabGroupModelFilterObserver, mode).didCreateGroup(
-            eq(expectedGroup),
-            eq(expectedOriginalIndex),
-            eq(originalRootId),
-            eq(originalTabGroupId),
-            anyString(),
-            anyInt(),
-            anyBoolean()
-        );
+        verify(mTabGroupModelFilterObserver, mode)
+                .showUndoGroupSnackbar(
+                        eq(expectedGroup),
+                        eq(expectedOriginalIndex),
+                        eq(originalRootId),
+                        eq(originalTabGroupId),
+                        anyString(),
+                        anyInt(),
+                        anyBoolean());
     }
 }
diff --git a/chrome/browser/tabmodel/android/java/src/org/chromium/chrome/browser/tabmodel/TabGroupModelFilterObserver.java b/chrome/browser/tabmodel/android/java/src/org/chromium/chrome/browser/tabmodel/TabGroupModelFilterObserver.java
index 07013e7..6943495c4a 100644
--- a/chrome/browser/tabmodel/android/java/src/org/chromium/chrome/browser/tabmodel/TabGroupModelFilterObserver.java
+++ b/chrome/browser/tabmodel/android/java/src/org/chromium/chrome/browser/tabmodel/TabGroupModelFilterObserver.java
@@ -117,7 +117,7 @@
      * @param destinationGroupColorId The original destination group color id.
      * @param destinationGroupTitleCollapsed Whether the destination group was originally collapsed.
      */
-    default void didCreateGroup(
+    default void showUndoGroupSnackbar(
             List<Tab> tabs,
             List<Integer> tabOriginalIndex,
             List<Integer> tabOriginalRootId,
diff --git a/chrome/browser/tabmodel/android/java/src/org/chromium/chrome/browser/tabmodel/TabModelOrderControllerImpl.java b/chrome/browser/tabmodel/android/java/src/org/chromium/chrome/browser/tabmodel/TabModelOrderControllerImpl.java
index 47227d8b..f592beff 100644
--- a/chrome/browser/tabmodel/android/java/src/org/chromium/chrome/browser/tabmodel/TabModelOrderControllerImpl.java
+++ b/chrome/browser/tabmodel/android/java/src/org/chromium/chrome/browser/tabmodel/TabModelOrderControllerImpl.java
@@ -7,9 +7,11 @@
 import static org.chromium.build.NullUtil.assumeNonNull;
 
 import org.chromium.build.annotations.NullMarked;
+import org.chromium.build.annotations.Nullable;
 import org.chromium.chrome.browser.tab.Tab;
 import org.chromium.chrome.browser.tab.TabAttributeKeys;
 import org.chromium.chrome.browser.tab.TabAttributes;
+import org.chromium.chrome.browser.tab.TabId;
 import org.chromium.chrome.browser.tab.TabLaunchType;
 
 /**
@@ -31,8 +33,26 @@
     @Override
     public int determineInsertionIndex(@TabLaunchType int type, int position, Tab newTab) {
         if (type == TabLaunchType.FROM_BROWSER_ACTIONS || type == TabLaunchType.FROM_RECENT_TABS) {
-            return -1;
+            return TabList.INVALID_TAB_INDEX;
         }
+
+        if (newTab.getIsPinned()) {
+            TabModel tabModel = mTabModelSelector.getCurrentModel();
+            @TabId int parentId = newTab.getParentId();
+            @Nullable Tab parentTab = tabModel.getTabById(parentId);
+            int index = tabModel.indexOf(parentTab);
+
+            if (type == TabLaunchType.FROM_TAB_LIST_INTERFACE
+                    && parentTab != null
+                    && index != TabList.INVALID_TAB_INDEX
+                    && parentTab.getIsPinned()) {
+                return index + 1;
+            }
+
+            // TabModel will handle the index.
+            return TabList.INVALID_TAB_INDEX;
+        }
+
         if (mightBeAdjacent(type)) {
             position = determineInsertionIndexIfMaybeAdjacent(type, newTab);
         }
diff --git a/chrome/browser/touch_to_fill/autofill/android/internal/BUILD.gn b/chrome/browser/touch_to_fill/autofill/android/internal/BUILD.gn
index 5e33e2f..5b2f06a 100644
--- a/chrome/browser/touch_to_fill/autofill/android/internal/BUILD.gn
+++ b/chrome/browser/touch_to_fill/autofill/android/internal/BUILD.gn
@@ -122,6 +122,7 @@
     "//chrome/browser/ui/android/night_mode:night_mode_java_test_support",
     "//chrome/browser/util:java",
     "//chrome/test/android:chrome_java_integration_test_support",
+    "//chrome/test/android:chrome_java_transit",
     "//components/autofill/android:autofill_java_resources",
     "//components/autofill/android:autofill_payments_java_resources",
     "//components/autofill/android:main_autofill_java",
diff --git a/chrome/browser/touch_to_fill/autofill/android/internal/java/src/org/chromium/chrome/browser/touch_to_fill/payments/TouchToFillPaymentMethodRenderTest.java b/chrome/browser/touch_to_fill/autofill/android/internal/java/src/org/chromium/chrome/browser/touch_to_fill/payments/TouchToFillPaymentMethodRenderTest.java
index 04df0b2..0582fc0 100644
--- a/chrome/browser/touch_to_fill/autofill/android/internal/java/src/org/chromium/chrome/browser/touch_to_fill/payments/TouchToFillPaymentMethodRenderTest.java
+++ b/chrome/browser/touch_to_fill/autofill/android/internal/java/src/org/chromium/chrome/browser/touch_to_fill/payments/TouchToFillPaymentMethodRenderTest.java
@@ -47,7 +47,9 @@
 import org.chromium.chrome.browser.night_mode.ChromeNightModeTestUtils;
 import org.chromium.chrome.browser.touch_to_fill.common.BottomSheetFocusHelper;
 import org.chromium.chrome.test.ChromeJUnit4RunnerDelegate;
-import org.chromium.chrome.test.ChromeTabbedActivityTestRule;
+import org.chromium.chrome.test.transit.ChromeTransitTestRules;
+import org.chromium.chrome.test.transit.FreshCtaTransitTestRule;
+import org.chromium.chrome.test.transit.page.WebPageStation;
 import org.chromium.chrome.test.util.ChromeRenderTestRule;
 import org.chromium.components.autofill.AutofillSuggestion;
 import org.chromium.components.autofill.LoyaltyCard;
@@ -78,7 +80,8 @@
                     new ParameterSet().value(false, true).name("RTL"),
                     new ParameterSet().value(true, false).name("NightMode"));
 
-    public ChromeTabbedActivityTestRule mActivityTestRule = new ChromeTabbedActivityTestRule();
+    public FreshCtaTransitTestRule mActivityTestRule =
+            ChromeTransitTestRules.freshChromeTabbedActivityRule();
 
     @Rule
     public final MockitoRule mMockitoRule = MockitoJUnit.rule().strictness(Strictness.STRICT_STUBS);
@@ -347,6 +350,7 @@
 
     private BottomSheetController mBottomSheetController;
     private TouchToFillPaymentMethodCoordinator mCoordinator;
+    private WebPageStation mPage;
 
     public TouchToFillPaymentMethodRenderTest(boolean nightModeEnabled, boolean useRtlLayout) {
         setRtlForTesting(useRtlLayout);
@@ -357,7 +361,7 @@
 
     @Before
     public void setUp() throws InterruptedException {
-        mActivityTestRule.startMainActivityOnBlankPage();
+        mPage = mActivityTestRule.startOnBlankPage();
         mActivityTestRule.waitForActivityCompletelyLoaded();
         mBottomSheetController =
                 mActivityTestRule
diff --git a/chrome/browser/touch_to_fill/autofill/android/internal/java/src/org/chromium/chrome/browser/touch_to_fill/payments/TouchToFillPaymentMethodViewTest.java b/chrome/browser/touch_to_fill/autofill/android/internal/java/src/org/chromium/chrome/browser/touch_to_fill/payments/TouchToFillPaymentMethodViewTest.java
index 98f4981..bddfaa4 100644
--- a/chrome/browser/touch_to_fill/autofill/android/internal/java/src/org/chromium/chrome/browser/touch_to_fill/payments/TouchToFillPaymentMethodViewTest.java
+++ b/chrome/browser/touch_to_fill/autofill/android/internal/java/src/org/chromium/chrome/browser/touch_to_fill/payments/TouchToFillPaymentMethodViewTest.java
@@ -112,7 +112,9 @@
 import org.chromium.chrome.browser.touch_to_fill.payments.TouchToFillPaymentMethodProperties.ButtonProperties;
 import org.chromium.chrome.browser.touch_to_fill.payments.TouchToFillPaymentMethodProperties.HeaderProperties;
 import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
-import org.chromium.chrome.test.ChromeTabbedActivityTestRule;
+import org.chromium.chrome.test.transit.ChromeTransitTestRules;
+import org.chromium.chrome.test.transit.FreshCtaTransitTestRule;
+import org.chromium.chrome.test.transit.page.WebPageStation;
 import org.chromium.components.autofill.AutofillSuggestion;
 import org.chromium.components.autofill.LoyaltyCard;
 import org.chromium.components.autofill.PaymentsPayload;
@@ -319,7 +321,8 @@
     @Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule();
 
     @Rule
-    public ChromeTabbedActivityTestRule mActivityTestRule = new ChromeTabbedActivityTestRule();
+    public FreshCtaTransitTestRule mActivityTestRule =
+            ChromeTransitTestRules.freshChromeTabbedActivityRule();
 
     @Mock private Callback<Integer> mDismissCallback;
     @Mock private Runnable mBackPressHandler;
@@ -330,6 +333,7 @@
     private BottomSheetTestSupport mSheetTestSupport;
     private TouchToFillPaymentMethodView mTouchToFillPaymentMethodView;
     private PropertyModel mTouchToFillPaymentMethodModel;
+    private WebPageStation mPage;
 
     @Before
     public void setupTest() throws InterruptedException {
@@ -337,7 +341,7 @@
                 TouchToFillResourceProvider.class, mResourceProvider);
         when(mResourceProvider.getLoyaltyCardHeaderDrawableId())
                 .thenReturn(R.drawable.ic_globe_24dp);
-        mActivityTestRule.startMainActivityOnBlankPage();
+        mPage = mActivityTestRule.startOnBlankPage();
         mBottomSheetController =
                 mActivityTestRule
                         .getActivity()
diff --git a/chrome/browser/touch_to_fill/password_manager/android/BUILD.gn b/chrome/browser/touch_to_fill/password_manager/android/BUILD.gn
index 7c8f59f..ebfcfde 100644
--- a/chrome/browser/touch_to_fill/password_manager/android/BUILD.gn
+++ b/chrome/browser/touch_to_fill/password_manager/android/BUILD.gn
@@ -124,6 +124,7 @@
     "//chrome/browser/ui/android/night_mode:night_mode_java_test_support",
     "//chrome/browser/util:java",
     "//chrome/test/android:chrome_java_integration_test_support",
+    "//chrome/test/android:chrome_java_transit",
     "//components/browser_ui/bottomsheet/android:java",
     "//components/browser_ui/bottomsheet/android:java_resources",
     "//components/browser_ui/bottomsheet/android:manager_java",
diff --git a/chrome/browser/touch_to_fill/password_manager/android/javatests/src/org/chromium/chrome/browser/touch_to_fill/TouchToFillIntegrationTest.java b/chrome/browser/touch_to_fill/password_manager/android/javatests/src/org/chromium/chrome/browser/touch_to_fill/TouchToFillIntegrationTest.java
index 3b50489d..c3f899ce 100644
--- a/chrome/browser/touch_to_fill/password_manager/android/javatests/src/org/chromium/chrome/browser/touch_to_fill/TouchToFillIntegrationTest.java
+++ b/chrome/browser/touch_to_fill/password_manager/android/javatests/src/org/chromium/chrome/browser/touch_to_fill/TouchToFillIntegrationTest.java
@@ -51,7 +51,9 @@
 import org.chromium.chrome.browser.touch_to_fill.data.Credential;
 import org.chromium.chrome.browser.touch_to_fill.data.WebauthnCredential;
 import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
-import org.chromium.chrome.test.ChromeTabbedActivityTestRule;
+import org.chromium.chrome.test.transit.ChromeTransitTestRules;
+import org.chromium.chrome.test.transit.FreshCtaTransitTestRule;
+import org.chromium.chrome.test.transit.page.WebPageStation;
 import org.chromium.components.browser_ui.bottomsheet.BottomSheetContent;
 import org.chromium.components.browser_ui.bottomsheet.BottomSheetController;
 import org.chromium.components.browser_ui.bottomsheet.BottomSheetController.SheetState;
@@ -86,9 +88,11 @@
     @Rule public MockitoRule mMockitoRule = MockitoJUnit.rule();
 
     @Rule
-    public ChromeTabbedActivityTestRule mActivityTestRule = new ChromeTabbedActivityTestRule();
+    public FreshCtaTransitTestRule mActivityTestRule =
+            ChromeTransitTestRules.freshChromeTabbedActivityRule();
 
     private BottomSheetController mBottomSheetController;
+    private WebPageStation mPage;
 
     @Before
     public void setUp() throws InterruptedException {
@@ -116,7 +120,7 @@
                 new WebauthnCredential(
                         "example.net", new byte[] {1}, new byte[] {2}, "cam@example.net");
 
-        mActivityTestRule.startMainActivityOnBlankPage();
+        mPage = mActivityTestRule.startOnBlankPage();
         runOnUiThreadBlocking(
                 () -> {
                     mTouchToFill = new TouchToFillCoordinator();
diff --git a/chrome/browser/touch_to_fill/password_manager/android/javatests/src/org/chromium/chrome/browser/touch_to_fill/TouchToFillRenderTest.java b/chrome/browser/touch_to_fill/password_manager/android/javatests/src/org/chromium/chrome/browser/touch_to_fill/TouchToFillRenderTest.java
index eb5ffb2..1ce0991 100644
--- a/chrome/browser/touch_to_fill/password_manager/android/javatests/src/org/chromium/chrome/browser/touch_to_fill/TouchToFillRenderTest.java
+++ b/chrome/browser/touch_to_fill/password_manager/android/javatests/src/org/chromium/chrome/browser/touch_to_fill/TouchToFillRenderTest.java
@@ -53,7 +53,9 @@
 import org.chromium.chrome.browser.touch_to_fill.data.Credential;
 import org.chromium.chrome.browser.touch_to_fill.data.WebauthnCredential;
 import org.chromium.chrome.test.ChromeJUnit4RunnerDelegate;
-import org.chromium.chrome.test.ChromeTabbedActivityTestRule;
+import org.chromium.chrome.test.transit.ChromeTransitTestRules;
+import org.chromium.chrome.test.transit.FreshCtaTransitTestRule;
+import org.chromium.chrome.test.transit.page.WebPageStation;
 import org.chromium.chrome.test.util.ChromeRenderTestRule;
 import org.chromium.components.browser_ui.bottomsheet.BottomSheetController;
 import org.chromium.components.browser_ui.bottomsheet.BottomSheetTestSupport;
@@ -103,8 +105,10 @@
     private TouchToFillView mTouchToFillView;
     private BottomSheetController mBottomSheetController;
     PasswordManagerResourceProvider mResourceProvider;
+    private WebPageStation mPage;
 
-    public ChromeTabbedActivityTestRule mActivityTestRule = new ChromeTabbedActivityTestRule();
+    public FreshCtaTransitTestRule mActivityTestRule =
+            ChromeTransitTestRules.freshChromeTabbedActivityRule();
 
     @Rule
     public final ChromeRenderTestRule mRenderTestRule =
@@ -122,7 +126,7 @@
 
     @Before
     public void setUp() throws InterruptedException {
-        mActivityTestRule.startMainActivityOnBlankPage();
+        mPage = mActivityTestRule.startOnBlankPage();
         mActivityTestRule.waitForActivityCompletelyLoaded();
         mBottomSheetController =
                 mActivityTestRule
diff --git a/chrome/browser/touch_to_fill/password_manager/android/javatests/src/org/chromium/chrome/browser/touch_to_fill/TouchToFillViewTest.java b/chrome/browser/touch_to_fill/password_manager/android/javatests/src/org/chromium/chrome/browser/touch_to_fill/TouchToFillViewTest.java
index 2c08e6c..6fda60d 100644
--- a/chrome/browser/touch_to_fill/password_manager/android/javatests/src/org/chromium/chrome/browser/touch_to_fill/TouchToFillViewTest.java
+++ b/chrome/browser/touch_to_fill/password_manager/android/javatests/src/org/chromium/chrome/browser/touch_to_fill/TouchToFillViewTest.java
@@ -74,7 +74,9 @@
 import org.chromium.chrome.browser.touch_to_fill.data.Credential;
 import org.chromium.chrome.browser.touch_to_fill.data.WebauthnCredential;
 import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
-import org.chromium.chrome.test.ChromeTabbedActivityTestRule;
+import org.chromium.chrome.test.transit.ChromeTransitTestRules;
+import org.chromium.chrome.test.transit.FreshCtaTransitTestRule;
+import org.chromium.chrome.test.transit.page.WebPageStation;
 import org.chromium.components.browser_ui.bottomsheet.BottomSheetController;
 import org.chromium.components.browser_ui.bottomsheet.BottomSheetController.SheetState;
 import org.chromium.components.browser_ui.bottomsheet.BottomSheetTestSupport;
@@ -146,13 +148,15 @@
     private TouchToFillView mTouchToFillView;
     private BottomSheetController mBottomSheetController;
     private BottomSheetTestSupport mSheetTestSupport;
+    private WebPageStation mPage;
 
     @Rule
-    public ChromeTabbedActivityTestRule mActivityTestRule = new ChromeTabbedActivityTestRule();
+    public FreshCtaTransitTestRule mActivityTestRule =
+            ChromeTransitTestRules.freshChromeTabbedActivityRule();
 
     @Before
     public void setUp() throws InterruptedException {
-        mActivityTestRule.startMainActivityOnBlankPage();
+        mPage = mActivityTestRule.startOnBlankPage();
         mBottomSheetController =
                 mActivityTestRule
                         .getActivity()
diff --git a/chrome/browser/touch_to_fill/password_manager/password_generation/android/internal/BUILD.gn b/chrome/browser/touch_to_fill/password_manager/password_generation/android/internal/BUILD.gn
index 23fcc96..f7706c8 100644
--- a/chrome/browser/touch_to_fill/password_manager/password_generation/android/internal/BUILD.gn
+++ b/chrome/browser/touch_to_fill/password_manager/password_generation/android/internal/BUILD.gn
@@ -103,6 +103,7 @@
     "//chrome/browser/sync/android:java",
     "//chrome/browser/ui/android/night_mode:night_mode_java_test_support",
     "//chrome/test/android:chrome_java_integration_test_support",
+    "//chrome/test/android:chrome_java_transit",
     "//components/browser_ui/bottomsheet/android:java",
     "//components/browser_ui/bottomsheet/android:java_resources",
     "//components/browser_ui/bottomsheet/android:manager_java",
diff --git a/chrome/browser/touch_to_fill/password_manager/password_generation/android/internal/java/src/org/chromium/chrome/browser/touch_to_fill/password_generation/TouchToFillPasswordGenerationRenderTest.java b/chrome/browser/touch_to_fill/password_manager/password_generation/android/internal/java/src/org/chromium/chrome/browser/touch_to_fill/password_generation/TouchToFillPasswordGenerationRenderTest.java
index 4b3206c9..2e61c9d5 100644
--- a/chrome/browser/touch_to_fill/password_manager/password_generation/android/internal/java/src/org/chromium/chrome/browser/touch_to_fill/password_generation/TouchToFillPasswordGenerationRenderTest.java
+++ b/chrome/browser/touch_to_fill/password_manager/password_generation/android/internal/java/src/org/chromium/chrome/browser/touch_to_fill/password_generation/TouchToFillPasswordGenerationRenderTest.java
@@ -30,7 +30,9 @@
 import org.chromium.base.test.util.Feature;
 import org.chromium.chrome.browser.flags.ChromeSwitches;
 import org.chromium.chrome.browser.night_mode.ChromeNightModeTestUtils;
-import org.chromium.chrome.test.ChromeTabbedActivityTestRule;
+import org.chromium.chrome.test.transit.ChromeTransitTestRules;
+import org.chromium.chrome.test.transit.FreshCtaTransitTestRule;
+import org.chromium.chrome.test.transit.page.WebPageStation;
 import org.chromium.chrome.test.util.ChromeRenderTestRule;
 import org.chromium.components.browser_ui.bottomsheet.BottomSheetController;
 import org.chromium.components.browser_ui.bottomsheet.BottomSheetTestSupport;
@@ -57,7 +59,8 @@
                     new ParameterSet().value(false, true).name("RTL"),
                     new ParameterSet().value(true, false).name("NightMode"));
 
-    public ChromeTabbedActivityTestRule mActivityTestRule = new ChromeTabbedActivityTestRule();
+    public FreshCtaTransitTestRule mActivityTestRule =
+            ChromeTransitTestRules.freshChromeTabbedActivityRule();
 
     @Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule();
 
@@ -71,6 +74,7 @@
     @Mock private TouchToFillPasswordGenerationCoordinator.Delegate mDelegateMock;
     @Mock private PrefService mPrefService;
 
+    private WebPageStation mPage;
     private BottomSheetController mBottomSheetController;
     private TouchToFillPasswordGenerationCoordinator mCoordinator;
     private static final String sGeneratedPassword = "Strong generated password";
@@ -85,7 +89,7 @@
 
     @Before
     public void setUp() throws InterruptedException {
-        mActivityTestRule.startMainActivityOnBlankPage();
+        mPage = mActivityTestRule.startOnBlankPage();
         mActivityTestRule.waitForActivityCompletelyLoaded();
         mBottomSheetController =
                 mActivityTestRule
diff --git a/chrome/browser/ui/BUILD.gn b/chrome/browser/ui/BUILD.gn
index c64e459..01ac822 100644
--- a/chrome/browser/ui/BUILD.gn
+++ b/chrome/browser/ui/BUILD.gn
@@ -1406,6 +1406,8 @@
       "webui/side_panel/bookmarks/bookmarks_page_handler.h",
       "webui/side_panel/bookmarks/bookmarks_side_panel_ui.cc",
       "webui/side_panel/bookmarks/bookmarks_side_panel_ui.h",
+      "webui/side_panel/comments/comments_page_handler.cc",
+      "webui/side_panel/comments/comments_page_handler.h",
       "webui/side_panel/comments/comments_side_panel_ui.cc",
       "webui/side_panel/comments/comments_side_panel_ui.h",
       "webui/side_panel/customize_chrome/customize_chrome_page_handler.cc",
@@ -5524,6 +5526,10 @@
       "//ui/webui/resources:code_cache_resources",
     ]
   }
+
+  if (enable_session_service) {
+    deps += [ "//chrome/browser/ui/webui/tab_strip_internals" ]
+  }
 }
 
 # These are the dependencies for the "ui" target that are outside of
diff --git a/chrome/browser/ui/actions/chrome_action_id.h b/chrome/browser/ui/actions/chrome_action_id.h
index 98d61b6f..96bca7d 100644
--- a/chrome/browser/ui/actions/chrome_action_id.h
+++ b/chrome/browser/ui/actions/chrome_action_id.h
@@ -532,6 +532,7 @@
   E(kActionSidePanelShowAboutThisSite) \
   E(kActionSidePanelShowAssistant) \
   E(kActionSidePanelShowBookmarks, IDC_SHOW_BOOKMARK_SIDE_PANEL) \
+  E(kActionSidePanelShowComments, IDC_SHOW_COMMENTS_SIDE_PANEL) \
   E(kActionSidePanelShowCustomizeChrome) \
   E(kActionSidePanelShowCustomizeChromeFooter) \
   E(kActionSidePanelShowCustomizeChromeToolbar) \
diff --git a/chrome/browser/ui/android/appmenu/internal/java/src/org/chromium/chrome/browser/ui/appmenu/AppMenu.java b/chrome/browser/ui/android/appmenu/internal/java/src/org/chromium/chrome/browser/ui/appmenu/AppMenu.java
index f9ed182..4c10fd6 100644
--- a/chrome/browser/ui/android/appmenu/internal/java/src/org/chromium/chrome/browser/ui/appmenu/AppMenu.java
+++ b/chrome/browser/ui/android/appmenu/internal/java/src/org/chromium/chrome/browser/ui/appmenu/AppMenu.java
@@ -357,20 +357,6 @@
         }
     }
 
-    @EnsuresNonNullIf("mPopup")
-    void setContentDescription(@Nullable String desc) {
-        if (mPopup == null) return;
-
-        View contentView = mPopup.getContentView();
-        if (contentView == null) return;
-
-        contentView.setAccessibilityLiveRegion(
-                desc != null
-                        ? View.ACCESSIBILITY_LIVE_REGION_POLITE
-                        : View.ACCESSIBILITY_LIVE_REGION_NONE);
-        contentView.setContentDescription(desc);
-    }
-
     @VisibleForTesting
     static int[] getPopupPosition(
             int[] tempLocation,
diff --git a/chrome/browser/ui/android/appmenu/internal/java/src/org/chromium/chrome/browser/ui/appmenu/AppMenuHandlerImpl.java b/chrome/browser/ui/android/appmenu/internal/java/src/org/chromium/chrome/browser/ui/appmenu/AppMenuHandlerImpl.java
index ef922b9..9bd2a96 100644
--- a/chrome/browser/ui/android/appmenu/internal/java/src/org/chromium/chrome/browser/ui/appmenu/AppMenuHandlerImpl.java
+++ b/chrome/browser/ui/android/appmenu/internal/java/src/org/chromium/chrome/browser/ui/appmenu/AppMenuHandlerImpl.java
@@ -221,12 +221,6 @@
         }
     }
 
-    @Override
-    public void setContentDescription(@Nullable String desc) {
-        assert mAppMenu != null;
-        mAppMenu.setContentDescription(desc);
-    }
-
     /**
      * Show the app menu.
      *
diff --git a/chrome/browser/ui/android/appmenu/java/src/org/chromium/chrome/browser/ui/appmenu/AppMenuHandler.java b/chrome/browser/ui/android/appmenu/java/src/org/chromium/chrome/browser/ui/appmenu/AppMenuHandler.java
index 015ad650..392a2087 100644
--- a/chrome/browser/ui/android/appmenu/java/src/org/chromium/chrome/browser/ui/appmenu/AppMenuHandler.java
+++ b/chrome/browser/ui/android/appmenu/java/src/org/chromium/chrome/browser/ui/appmenu/AppMenuHandler.java
@@ -5,7 +5,6 @@
 package org.chromium.chrome.browser.ui.appmenu;
 
 import androidx.annotation.IntDef;
-import androidx.annotation.Nullable;
 
 import org.chromium.build.annotations.NullMarked;
 
@@ -104,11 +103,4 @@
      * @return {@link AppMenuPropertiesDelegate} that builds the menu list.
      */
     AppMenuPropertiesDelegate getMenuPropertiesDelegate();
-
-    /**
-     * Sets the content description text for the app menu view.
-     *
-     * @param desc Content description.
-     */
-    void setContentDescription(@Nullable String desc);
 }
diff --git a/chrome/browser/ui/android/digital_credentials/BUILD.gn b/chrome/browser/ui/android/digital_credentials/BUILD.gn
index f75de1c..95bd1b9 100644
--- a/chrome/browser/ui/android/digital_credentials/BUILD.gn
+++ b/chrome/browser/ui/android/digital_credentials/BUILD.gn
@@ -55,6 +55,7 @@
     "//chrome/browser/tab:java",
     "//chrome/browser/webid:java",
     "//chrome/test/android:chrome_java_integration_test_support",
+    "//chrome/test/android:chrome_java_transit",
     "//content/public/android:content_java",
     "//content/public/test/android:content_java_test_support",
     "//net/android:net_java_test_support",
diff --git a/chrome/browser/ui/android/digital_credentials/java/src/org/chromium/chrome/browser/ui/android/digital_credentials/DigitalIdentitySafetyInterstitialIntegrationTest.java b/chrome/browser/ui/android/digital_credentials/java/src/org/chromium/chrome/browser/ui/android/digital_credentials/DigitalIdentitySafetyInterstitialIntegrationTest.java
index 5b36170..8613f14 100644
--- a/chrome/browser/ui/android/digital_credentials/java/src/org/chromium/chrome/browser/ui/android/digital_credentials/DigitalIdentitySafetyInterstitialIntegrationTest.java
+++ b/chrome/browser/ui/android/digital_credentials/java/src/org/chromium/chrome/browser/ui/android/digital_credentials/DigitalIdentitySafetyInterstitialIntegrationTest.java
@@ -36,7 +36,9 @@
 import org.chromium.chrome.browser.webid.DigitalIdentityProvider;
 import org.chromium.chrome.browser.webid.IdentityCredentialsDelegate;
 import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
-import org.chromium.chrome.test.ChromeTabbedActivityTestRule;
+import org.chromium.chrome.test.transit.ChromeTransitTestRules;
+import org.chromium.chrome.test.transit.FreshCtaTransitTestRule;
+import org.chromium.chrome.test.transit.page.WebPageStation;
 import org.chromium.content_public.browser.ContentFeatureList;
 import org.chromium.content_public.browser.test.util.DOMUtils;
 import org.chromium.content_public.browser.test.util.JavaScriptUtils;
@@ -110,11 +112,13 @@
     private static final String TEST_PAGE = "/chrome/test/data/android/dc_mdocs.html";
 
     @Rule
-    public ChromeTabbedActivityTestRule mActivityTestRule = new ChromeTabbedActivityTestRule();
+    public FreshCtaTransitTestRule mActivityTestRule =
+            ChromeTransitTestRules.freshChromeTabbedActivityRule();
 
     @Rule public MockitoRule mMockitoRule = MockitoJUnit.rule().strictness(Strictness.STRICT_STUBS);
 
     private EmbeddedTestServer mTestServer;
+    private WebPageStation mPage;
 
     private ModalDialogButtonPresser mModalDialogObserver;
 
@@ -128,7 +132,7 @@
         mTestServer = mActivityTestRule.getTestServer();
         DigitalIdentityProvider.setDelegateForTesting(new ReturnTokenIdentityCredentialsDelegate());
 
-        mActivityTestRule.startMainActivityWithURL(mTestServer.getURL(TEST_PAGE));
+        mPage = mActivityTestRule.startOnTestServerUrl(TEST_PAGE);
 
         mModalDialogManager = getActivity().getModalDialogManager();
     }
diff --git a/chrome/browser/ui/android/edge_to_edge/internal/junit/src/org/chromium/chrome/browser/ui/edge_to_edge/EdgeToEdgeControllerTest.java b/chrome/browser/ui/android/edge_to_edge/internal/junit/src/org/chromium/chrome/browser/ui/edge_to_edge/EdgeToEdgeControllerTest.java
index 6e3cc783..8fe8abb 100644
--- a/chrome/browser/ui/android/edge_to_edge/internal/junit/src/org/chromium/chrome/browser/ui/edge_to_edge/EdgeToEdgeControllerTest.java
+++ b/chrome/browser/ui/android/edge_to_edge/internal/junit/src/org/chromium/chrome/browser/ui/edge_to_edge/EdgeToEdgeControllerTest.java
@@ -94,10 +94,7 @@
         sdk = VERSION_CODES.R,
         manifest = Config.NONE,
         shadows = EdgeToEdgeControllerTest.ShadowEdgeToEdgeControllerFactory.class)
-@EnableFeatures({
-    ChromeFeatureList.EDGE_TO_EDGE_BOTTOM_CHIN,
-    ChromeFeatureList.DRAW_KEY_NATIVE_EDGE_TO_EDGE
-})
+@EnableFeatures({ChromeFeatureList.EDGE_TO_EDGE_BOTTOM_CHIN})
 @Features.DisableFeatures({ChromeFeatureList.EDGE_TO_EDGE_EVERYWHERE})
 public class EdgeToEdgeControllerTest {
 
@@ -566,16 +563,6 @@
         assertToEdgeExpectations();
     }
 
-    @Test
-    @EnableFeatures(
-            ChromeFeatureList.DRAW_KEY_NATIVE_EDGE_TO_EDGE + ":disable_cct_media_viewer_e2e/true")
-    public void onObservingDifferentTab_embeddedMediaExperience_DisableByParam() {
-        when(mTab.shouldEnableEmbeddedMediaExperience()).thenReturn(true);
-        mTabProvider.set(mTab);
-        verifyInteractions(mTab);
-        assertToNormalExpectations();
-    }
-
     /** Test that we update WebContentsObservers when a Tab changes WebContents. */
     @Test
     public void onTabSwitched_onWebContentsSwapped() {
diff --git a/chrome/browser/ui/android/edge_to_edge/java/src/org/chromium/chrome/browser/ui/edge_to_edge/EdgeToEdgeUtils.java b/chrome/browser/ui/android/edge_to_edge/java/src/org/chromium/chrome/browser/ui/edge_to_edge/EdgeToEdgeUtils.java
index e04e3193..a020e91 100644
--- a/chrome/browser/ui/android/edge_to_edge/java/src/org/chromium/chrome/browser/ui/edge_to_edge/EdgeToEdgeUtils.java
+++ b/chrome/browser/ui/android/edge_to_edge/java/src/org/chromium/chrome/browser/ui/edge_to_edge/EdgeToEdgeUtils.java
@@ -165,7 +165,7 @@
      * This is a sensitive check for whether all insets indicate or imply that the device is in
      * gesture navigation mode, and not tappable (3-button) navigation mode.
      *
-     * @param insetObserver The window insets to check for signals indicating gesture navigation.
+     * @param insets The window insets to check for signals indicating gesture navigation.
      * @return Whether all insets indicate the device is in gesture navigation mode.
      */
     public static boolean doAllInsetsIndicateGestureNavigation(
@@ -239,8 +239,7 @@
 
     /** Whether key native pages should draw to edge. */
     public static boolean isDrawKeyNativePageToEdgeEnabled() {
-        return isBottomChinFeatureEnabled()
-                && ChromeFeatureList.sDrawKeyNativeEdgeToEdge.isEnabled();
+        return isBottomChinFeatureEnabled();
     }
 
     /**
@@ -330,9 +329,7 @@
             boolean isPageOptedIntoEdgeToEdge, @LayoutType int layoutType, int bottomInset) {
         return isPageOptedIntoEdgeToEdge
                 || (isBottomChinFeatureEnabled() && isBottomChinAllowed(layoutType, bottomInset))
-                || (isDrawKeyNativePageToEdgeEnabled()
-                        && layoutType == LayoutType.TAB_SWITCHER
-                        && !ChromeFeatureList.sDrawKeyNativeEdgeToEdgeDisableHubE2e.getValue());
+                || (layoutType == LayoutType.TAB_SWITCHER);
     }
 
     /**
diff --git a/chrome/browser/ui/android/fast_checkout/internal/BUILD.gn b/chrome/browser/ui/android/fast_checkout/internal/BUILD.gn
index e10409af..53f0378a 100644
--- a/chrome/browser/ui/android/fast_checkout/internal/BUILD.gn
+++ b/chrome/browser/ui/android/fast_checkout/internal/BUILD.gn
@@ -169,6 +169,7 @@
     "//chrome/browser/ui/android/fast_checkout:java",
     "//chrome/browser/ui/android/night_mode:night_mode_java_test_support",
     "//chrome/test/android:chrome_java_integration_test_support",
+    "//chrome/test/android:chrome_java_transit",
     "//components/browser_ui/bottomsheet/android:java",
     "//components/browser_ui/bottomsheet/android:java_resources",
     "//components/browser_ui/bottomsheet/android:manager_java",
diff --git a/chrome/browser/ui/android/fast_checkout/internal/javatest/src/org/chromium/chrome/browser/ui/fast_checkout/FastCheckoutIntegrationTest.java b/chrome/browser/ui/android/fast_checkout/internal/javatest/src/org/chromium/chrome/browser/ui/fast_checkout/FastCheckoutIntegrationTest.java
index c800e8db..bad2431 100644
--- a/chrome/browser/ui/android/fast_checkout/internal/javatest/src/org/chromium/chrome/browser/ui/fast_checkout/FastCheckoutIntegrationTest.java
+++ b/chrome/browser/ui/android/fast_checkout/internal/javatest/src/org/chromium/chrome/browser/ui/fast_checkout/FastCheckoutIntegrationTest.java
@@ -47,7 +47,9 @@
 import org.chromium.chrome.browser.ui.fast_checkout.data.FastCheckoutCreditCard;
 import org.chromium.chrome.browser.ui.suggestion.Icon;
 import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
-import org.chromium.chrome.test.ChromeTabbedActivityTestRule;
+import org.chromium.chrome.test.transit.ChromeTransitTestRules;
+import org.chromium.chrome.test.transit.FreshCtaTransitTestRule;
+import org.chromium.chrome.test.transit.page.WebPageStation;
 import org.chromium.components.autofill.RecordType;
 import org.chromium.components.browser_ui.bottomsheet.BottomSheetContent;
 import org.chromium.components.browser_ui.bottomsheet.BottomSheetContent.ContentPriority;
@@ -124,8 +126,10 @@
     @Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule();
 
     @Rule
-    public ChromeTabbedActivityTestRule mActivityTestRule = new ChromeTabbedActivityTestRule();
+    public FreshCtaTransitTestRule mActivityTestRule =
+            ChromeTransitTestRules.freshChromeTabbedActivityRule();
 
+    private WebPageStation mPage;
     private BottomSheetController mBottomSheetController;
     private BottomSheetTestSupport mTestSupport;
 
@@ -133,7 +137,7 @@
 
     @Before
     public void setUp() throws InterruptedException {
-        mActivityTestRule.startMainActivityOnBlankPage();
+        mPage = mActivityTestRule.startOnBlankPage();
         runOnUiThreadBlocking(
                 () -> {
                     mFastCheckout = new FastCheckoutCoordinator();
diff --git a/chrome/browser/ui/android/fast_checkout/internal/javatest/src/org/chromium/chrome/browser/ui/fast_checkout/FastCheckoutRenderTest.java b/chrome/browser/ui/android/fast_checkout/internal/javatest/src/org/chromium/chrome/browser/ui/fast_checkout/FastCheckoutRenderTest.java
index a767922..d50917a 100644
--- a/chrome/browser/ui/android/fast_checkout/internal/javatest/src/org/chromium/chrome/browser/ui/fast_checkout/FastCheckoutRenderTest.java
+++ b/chrome/browser/ui/android/fast_checkout/internal/javatest/src/org/chromium/chrome/browser/ui/fast_checkout/FastCheckoutRenderTest.java
@@ -39,7 +39,9 @@
 import org.chromium.chrome.browser.ui.fast_checkout.data.FastCheckoutCreditCard;
 import org.chromium.chrome.browser.ui.suggestion.Icon;
 import org.chromium.chrome.test.ChromeJUnit4RunnerDelegate;
-import org.chromium.chrome.test.ChromeTabbedActivityTestRule;
+import org.chromium.chrome.test.transit.ChromeTransitTestRules;
+import org.chromium.chrome.test.transit.FreshCtaTransitTestRule;
+import org.chromium.chrome.test.transit.page.WebPageStation;
 import org.chromium.chrome.test.util.ChromeRenderTestRule;
 import org.chromium.components.autofill.RecordType;
 import org.chromium.components.browser_ui.bottomsheet.BottomSheetController;
@@ -99,7 +101,8 @@
                     new ParameterSet().value(false, true).name("RTL"),
                     new ParameterSet().value(true, false).name("NightMode"));
 
-    public ChromeTabbedActivityTestRule mActivityTestRule = new ChromeTabbedActivityTestRule();
+    public FreshCtaTransitTestRule mActivityTestRule =
+            ChromeTransitTestRules.freshChromeTabbedActivityRule();
 
     @Rule
     public final MockitoRule mMockitoRule = MockitoJUnit.rule().strictness(Strictness.STRICT_STUBS);
@@ -115,6 +118,7 @@
 
     @Mock private FastCheckoutComponent.Delegate mDelegateMock;
 
+    private WebPageStation mPage;
     private BottomSheetController mBottomSheetController;
     private FastCheckoutCoordinator mCoordinator;
 
@@ -127,7 +131,7 @@
 
     @Before
     public void setUp() throws InterruptedException {
-        mActivityTestRule.startMainActivityOnBlankPage();
+        mPage = mActivityTestRule.startOnBlankPage();
         mActivityTestRule.waitForActivityCompletelyLoaded();
         mBottomSheetController =
                 mActivityTestRule
diff --git a/chrome/browser/ui/android/signin/BUILD.gn b/chrome/browser/ui/android/signin/BUILD.gn
index dd5f6dd..efa12e6c 100644
--- a/chrome/browser/ui/android/signin/BUILD.gn
+++ b/chrome/browser/ui/android/signin/BUILD.gn
@@ -263,6 +263,7 @@
     "//chrome/browser/ui/android/night_mode:night_mode_java_test_support",
     "//chrome/browser/ui/android/signin:java_resources",
     "//chrome/test/android:chrome_java_integration_test_support",
+    "//chrome/test/android:chrome_java_transit",
     "//components/browser_ui/bottomsheet/android:java",
     "//components/browser_ui/bottomsheet/android:manager_java",
     "//components/browser_ui/device_lock/android:java",
diff --git a/chrome/browser/ui/android/signin/java/src/org/chromium/chrome/browser/ui/signin/account_picker/AccountPickerBottomSheetRenderTest.java b/chrome/browser/ui/android/signin/java/src/org/chromium/chrome/browser/ui/signin/account_picker/AccountPickerBottomSheetRenderTest.java
index 23e779e3..960902d 100644
--- a/chrome/browser/ui/android/signin/java/src/org/chromium/chrome/browser/ui/signin/account_picker/AccountPickerBottomSheetRenderTest.java
+++ b/chrome/browser/ui/android/signin/java/src/org/chromium/chrome/browser/ui/signin/account_picker/AccountPickerBottomSheetRenderTest.java
@@ -38,7 +38,9 @@
 import org.chromium.chrome.browser.flags.ChromeSwitches;
 import org.chromium.chrome.browser.night_mode.ChromeNightModeTestUtils;
 import org.chromium.chrome.test.ChromeJUnit4RunnerDelegate;
-import org.chromium.chrome.test.ChromeTabbedActivityTestRule;
+import org.chromium.chrome.test.transit.ChromeTransitTestRules;
+import org.chromium.chrome.test.transit.FreshCtaTransitTestRule;
+import org.chromium.chrome.test.transit.page.WebPageStation;
 import org.chromium.chrome.test.util.browser.signin.AccountManagerTestRule;
 import org.chromium.components.browser_ui.bottomsheet.BottomSheetController;
 import org.chromium.components.signin.SigninFeatures;
@@ -132,8 +134,8 @@
     public final AccountManagerTestRule mAccountManagerTestRule = new AccountManagerTestRule();
 
     @Rule
-    public final ChromeTabbedActivityTestRule mActivityTestRule =
-            new ChromeTabbedActivityTestRule();
+    public final FreshCtaTransitTestRule mActivityTestRule =
+            ChromeTransitTestRules.freshChromeTabbedActivityRule();
 
     @Rule
     public final MockitoRule mMockitoRule = MockitoJUnit.rule().strictness(Strictness.STRICT_STUBS);
@@ -141,6 +143,7 @@
     private final CustomAccountPickerDelegate mAccountPickerDelegate =
             new CustomAccountPickerDelegate();
 
+    private WebPageStation mPage;
     private @SigninAccessPoint int mSigninAccessPoint;
     private AccountPickerBottomSheetCoordinator mCoordinator;
 
@@ -161,7 +164,7 @@
     @Before
     public void setUp() {
         mSigninAccessPoint = SigninAccessPoint.WEB_SIGNIN;
-        mActivityTestRule.startMainActivityOnBlankPage();
+        mPage = mActivityTestRule.startOnBlankPage();
     }
 
     @AfterClass
diff --git a/chrome/browser/ui/android/signin/java/src/org/chromium/chrome/browser/ui/signin/account_picker/AccountPickerBottomSheetTest.java b/chrome/browser/ui/android/signin/java/src/org/chromium/chrome/browser/ui/signin/account_picker/AccountPickerBottomSheetTest.java
index 82fe0ea..25b5f77 100644
--- a/chrome/browser/ui/android/signin/java/src/org/chromium/chrome/browser/ui/signin/account_picker/AccountPickerBottomSheetTest.java
+++ b/chrome/browser/ui/android/signin/java/src/org/chromium/chrome/browser/ui/signin/account_picker/AccountPickerBottomSheetTest.java
@@ -50,7 +50,6 @@
 import org.junit.After;
 import org.junit.Assert;
 import org.junit.Before;
-import org.junit.ClassRule;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -77,9 +76,10 @@
 import org.chromium.chrome.browser.signin.services.SigninPreferencesManager;
 import org.chromium.chrome.browser.ui.signin.R;
 import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
-import org.chromium.chrome.test.ChromeTabbedActivityTestRule;
 import org.chromium.chrome.test.OverrideContextWrapperTestRule;
-import org.chromium.chrome.test.batch.BlankCTATabInitialStateRule;
+import org.chromium.chrome.test.transit.AutoResetCtaTransitTestRule;
+import org.chromium.chrome.test.transit.ChromeTransitTestRules;
+import org.chromium.chrome.test.transit.page.WebPageStation;
 import org.chromium.chrome.test.util.browser.signin.AccountManagerTestRule;
 import org.chromium.chrome.test.util.browser.signin.SigninTestUtil;
 import org.chromium.components.browser_ui.bottomsheet.BottomSheetController;
@@ -112,13 +112,9 @@
 
     private static final String DOMAIN1 = "Domain1";
 
-    @ClassRule
-    public static final ChromeTabbedActivityTestRule sActivityTestRule =
-            new ChromeTabbedActivityTestRule();
-
     @Rule
-    public final BlankCTATabInitialStateRule mInitialStateRule =
-            new BlankCTATabInitialStateRule(sActivityTestRule, false);
+    public final AutoResetCtaTransitTestRule mActivityTestRule =
+            ChromeTransitTestRules.fastAutoResetCtaActivityRule();
 
     // Use spy() instead of @Spy to immediately initialize, so the object can be injected in
     // AccountManagerTestRule below.
@@ -142,6 +138,7 @@
 
     @Captor private ArgumentCaptor<Callback<Boolean>> mUpdateCredentialsSuccessCallbackCaptor;
 
+    private WebPageStation mPage;
     private AccountPickerBottomSheetCoordinator mCoordinator;
     private SigninTestUtil.CustomDeviceLockActivityLauncher mDeviceLockActivityLauncher;
     private boolean mIsAccountManaged;
@@ -149,6 +146,7 @@
 
     @Before
     public void setUp() {
+        mPage = mActivityTestRule.startOnBlankPage();
         mAutoTestRule.setIsAutomotive(false);
         mSigninAccessPoint = SigninAccessPoint.WEB_SIGNIN;
         mAccountManagerTestRule.addAccount(TestAccounts.ACCOUNT1);
@@ -266,7 +264,7 @@
                 () -> {
                     mCoordinator =
                             new AccountPickerBottomSheetCoordinator(
-                                    sActivityTestRule.getActivity().getWindowAndroid(),
+                                    mActivityTestRule.getActivity().getWindowAndroid(),
                                     getBottomSheetController(),
                                     mAccountPickerDelegateMock,
                                     AccountPickerBottomSheetTestUtil.getBottomSheetStrings(
@@ -292,7 +290,7 @@
                 () -> {
                     mCoordinator =
                             new AccountPickerBottomSheetCoordinator(
-                                    sActivityTestRule.getActivity().getWindowAndroid(),
+                                    mActivityTestRule.getActivity().getWindowAndroid(),
                                     getBottomSheetController(),
                                     mAccountPickerDelegateMock,
                                     AccountPickerBottomSheetTestUtil.getBottomSheetStrings(
@@ -525,7 +523,7 @@
                 () -> {
                     mCoordinator =
                             new AccountPickerBottomSheetCoordinator(
-                                    sActivityTestRule.getActivity().getWindowAndroid(),
+                                    mActivityTestRule.getActivity().getWindowAndroid(),
                                     getBottomSheetController(),
                                     mAccountPickerDelegateMock,
                                     AccountPickerBottomSheetTestUtil.getBottomSheetStrings(
@@ -1180,7 +1178,7 @@
         clickContinueButtonAndClearDeviceLock(bottomSheetView);
 
         String text =
-                sActivityTestRule
+                mActivityTestRule
                         .getActivity()
                         .getString(R.string.managed_signin_with_user_policy_subtitle, DOMAIN1);
         assertTrue(text.contains(DOMAIN1));
@@ -1234,7 +1232,7 @@
         clickContinueButtonAndClearDeviceLock(bottomSheetView);
 
         String text =
-                sActivityTestRule
+                mActivityTestRule
                         .getActivity()
                         .getString(R.string.managed_signin_with_user_policy_subtitle, DOMAIN1);
         assertTrue(text.contains(DOMAIN1));
@@ -1419,7 +1417,7 @@
             onVisibleView(withText(accountInfo.getFullName())).check(matches(isDisplayed()));
         }
         String continueAsText =
-                sActivityTestRule
+                mActivityTestRule
                         .getActivity()
                         .getString(
                                 R.string.sync_promo_continue_as,
@@ -1448,7 +1446,7 @@
                 () -> {
                     mCoordinator =
                             new AccountPickerBottomSheetCoordinator(
-                                    sActivityTestRule.getActivity().getWindowAndroid(),
+                                    mActivityTestRule.getActivity().getWindowAndroid(),
                                     getBottomSheetController(),
                                     mAccountPickerDelegateMock,
                                     AccountPickerBottomSheetTestUtil.getBottomSheetStrings(
@@ -1497,7 +1495,7 @@
     }
 
     private BottomSheetController getBottomSheetController() {
-        return sActivityTestRule
+        return mActivityTestRule
                 .getActivity()
                 .getRootUiCoordinatorForTesting()
                 .getBottomSheetController();
diff --git a/chrome/browser/ui/android/strings/android_chrome_strings.grd b/chrome/browser/ui/android/strings/android_chrome_strings.grd
index c52405a..80ee9cc 100644
--- a/chrome/browser/ui/android/strings/android_chrome_strings.grd
+++ b/chrome/browser/ui/android/strings/android_chrome_strings.grd
@@ -6550,12 +6550,6 @@
       <message name="IDS_CUSTOM_TAB_CANT_PERFORM_ACTION_TOAST" desc="Toast displayed when the URL currently being displayed in the Custom Tab cannot be opened in the regular browser.">
         Something went wrong. Try again.
       </message>
-      <message name="IDS_ACCESSIBILITY_CUSTOM_TAB_MENU_WITH_DOT" desc="Content description for the menu with a dot indicating the fallback UI for contextual page action is highlighted.">
-        Customize and control Google Chrome with a highlighted item
-      </message>
-      <message name="IDS_ACCESSIBILITY_CUSTOM_TAB_MENU_ITEM_HIGHLIGHT" desc="Announcement made when the overflow menu has a highlighted item for a recommended contextual page action.">
-        <ph name="MENU_NAME">%1$s<ex>Price Tracking</ex></ph> menu is highlighted.
-      </message>
 
       <message name="IDS_ACCOUNT_SELECTION_CONTINUE" desc="Title of the button that continues filling with the only available set of credentials.">
         Continue as <ph name="NAME">%1$s<ex>Albus (or Albus Dumbledore)</ex></ph>
diff --git a/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_ACCESSIBILITY_CUSTOM_TAB_MENU_ITEM_HIGHLIGHT.png.sha1 b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_ACCESSIBILITY_CUSTOM_TAB_MENU_ITEM_HIGHLIGHT.png.sha1
deleted file mode 100644
index 9382ec6..0000000
--- a/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_ACCESSIBILITY_CUSTOM_TAB_MENU_ITEM_HIGHLIGHT.png.sha1
+++ /dev/null
@@ -1 +0,0 @@
-d7ef4d3b8761f892190b0a12ac0201c78dae7f65
\ No newline at end of file
diff --git a/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_ACCESSIBILITY_CUSTOM_TAB_MENU_WITH_DOT.png.sha1 b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_ACCESSIBILITY_CUSTOM_TAB_MENU_WITH_DOT.png.sha1
deleted file mode 100644
index f4fa6d0..0000000
--- a/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_ACCESSIBILITY_CUSTOM_TAB_MENU_WITH_DOT.png.sha1
+++ /dev/null
@@ -1 +0,0 @@
-54748f03d1c6b97b6a4e75e9b52a0249977ecc7b
\ No newline at end of file
diff --git a/chrome/browser/ui/android/tab_model/tab_model.cc b/chrome/browser/ui/android/tab_model/tab_model.cc
index 2a7c8724..cc7f50d 100644
--- a/chrome/browser/ui/android/tab_model/tab_model.cc
+++ b/chrome/browser/ui/android/tab_model/tab_model.cc
@@ -5,6 +5,7 @@
 #include "chrome/browser/ui/android/tab_model/tab_model.h"
 
 #include "base/metrics/histogram_functions.h"
+#include "base/notimplemented.h"
 #include "chrome/browser/android/tab_android.h"
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/profiles/profile.h"
@@ -127,3 +128,13 @@
   base::UmaHistogramPercentage("Android.Sync.ActualSyncedTabCountPercentage",
                                percent_synced);
 }
+
+// static
+// From //chrome/browser/ui/tabs/tab_list_interface.h
+TabListInterface* TabListInterface::From(
+    BrowserWindowInterface* browser_window_interface) {
+  // TODO(https://crbug.com/415961057): Implement this once AndroidBrowserWindow
+  // has a get-able UnownedUserDataHost.
+  NOTIMPLEMENTED();
+  return nullptr;
+}
diff --git a/chrome/browser/ui/android/tab_model/tab_model_jni_bridge.cc b/chrome/browser/ui/android/tab_model/tab_model_jni_bridge.cc
index 230cbf1..5aaf79e 100644
--- a/chrome/browser/ui/android/tab_model/tab_model_jni_bridge.cc
+++ b/chrome/browser/ui/android/tab_model/tab_model_jni_bridge.cc
@@ -355,13 +355,25 @@
 }
 
 void TabModelJniBridge::PinTab(tabs::TabHandle tab) {
-  // TODO(crbug.com/415351293): Implement.
-  NOTIMPLEMENTED();
+  TabAndroid* tab_android = TabAndroid::FromTabHandle(tab);
+  if (!tab_android) {
+    return;
+  }
+
+  JNIEnv* env = AttachCurrentThread();
+  ScopedJavaLocalRef<jobject> jobj = java_object_.get(env);
+  Java_TabModelJniBridge_pinTab(env, jobj, tab_android);
 }
 
 void TabModelJniBridge::UnpinTab(tabs::TabHandle tab) {
-  // TODO(crbug.com/415351293): Implement.
-  NOTIMPLEMENTED();
+  TabAndroid* tab_android = TabAndroid::FromTabHandle(tab);
+  if (!tab_android) {
+    return;
+  }
+
+  JNIEnv* env = AttachCurrentThread();
+  ScopedJavaLocalRef<jobject> jobj = java_object_.get(env);
+  Java_TabModelJniBridge_unpinTab(env, jobj, tab_android);
 }
 
 std::optional<tab_groups::TabGroupId> TabModelJniBridge::AddTabsToGroup(
diff --git a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/ToolbarLongPressMenuHandler.java b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/ToolbarLongPressMenuHandler.java
index 1c969e38..aafa7d4 100644
--- a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/ToolbarLongPressMenuHandler.java
+++ b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/ToolbarLongPressMenuHandler.java
@@ -71,7 +71,7 @@
     private final Context mContext;
     private final ObservableSupplier<Profile> mProfileSupplier;
     private final BooleanSupplier mSuppressLongPressSupplier;
-    private final Supplier<String> mUrlBarTextSupplier;
+    private final Supplier<GURL> mUrlSupplier;
     private final Supplier<ViewRectProvider> mUrlBarViewRectProviderSupplier;
     private final @Nullable OnLongClickListener mOnLongClickListener;
     private final SharedPreferencesManager mSharedPreferencesManager;
@@ -90,12 +90,12 @@
             BooleanSupplier suppressLongPressSupplier,
             ActivityLifecycleDispatcher lifecycleDispatcher,
             WindowAndroid windowAndroid,
-            Supplier<String> urlBarTextSupplier,
+            Supplier<GURL> urlSupplier,
             Supplier<ViewRectProvider> urlBarViewRectProviderSupplier) {
         mContext = context;
         mProfileSupplier = profileSupplier;
         mSuppressLongPressSupplier = suppressLongPressSupplier;
-        mUrlBarTextSupplier = urlBarTextSupplier;
+        mUrlSupplier = urlSupplier;
         mUrlBarViewRectProviderSupplier = urlBarViewRectProviderSupplier;
         mWindowAndroid = windowAndroid;
         mLifecycleDispatcher = lifecycleDispatcher;
@@ -248,7 +248,7 @@
     }
 
     private void handleCopyLink() {
-        Clipboard.getInstance().copyUrlToClipboard(new GURL(mUrlBarTextSupplier.get()));
+        Clipboard.getInstance().copyUrlToClipboard(mUrlSupplier.get());
     }
 
     @VisibleForTesting
diff --git a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/ToolbarLongPressMenuHandlerUnitTest.java b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/ToolbarLongPressMenuHandlerUnitTest.java
index ed2333b..935dde3 100644
--- a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/ToolbarLongPressMenuHandlerUnitTest.java
+++ b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/ToolbarLongPressMenuHandlerUnitTest.java
@@ -74,6 +74,7 @@
 import org.chromium.ui.modelutil.MVCListAdapter.ModelList;
 import org.chromium.ui.widget.UiWidgetFactory;
 import org.chromium.ui.widget.ViewRectProvider;
+import org.chromium.url.GURL;
 import org.chromium.url.JUnitTestGURLs;
 
 import java.util.function.BooleanSupplier;
@@ -113,7 +114,7 @@
     private boolean mShouldSuppress;
     private final BooleanSupplier mSuppressSupplier = () -> mShouldSuppress;
     private SharedPreferencesManager mSharedPreferencesManager;
-    private String mUrlString;
+    private GURL mUrl;
     private Configuration mConfiguration;
 
     @Before
@@ -143,7 +144,7 @@
                         mSuppressSupplier,
                         mActivityLifecycleDispatcher,
                         mWindowAndroid,
-                        () -> mUrlString,
+                        () -> mUrl,
                         () -> mViewRectProvider);
         mUrlBar.setOnLongClickListener(mToolbarLongPressMenuHandler.getOnLongClickListener());
 
@@ -279,7 +280,7 @@
         Clipboard clipboard = Clipboard.getInstance();
         ClipboardManager clipboardManager = mock(ClipboardManager.class);
         ((ClipboardImpl) clipboard).overrideClipboardManagerForTesting(clipboardManager);
-        mUrlString = JUnitTestGURLs.URL_1.getSpec();
+        mUrl = JUnitTestGURLs.URL_1;
 
         mToolbarLongPressMenuHandler.handleMenuClick(
                 ToolbarLongPressMenuHandler.MenuItemType.COPY_LINK);
@@ -287,7 +288,7 @@
         ArgumentCaptor<ClipData> clipCaptor = ArgumentCaptor.forClass(ClipData.class);
         verify(clipboardManager).setPrimaryClip(clipCaptor.capture());
         assertEquals("url", clipCaptor.getValue().getDescription().getLabel());
-        assertEquals(mUrlString, clipCaptor.getValue().getItemAt(0).getText());
+        assertEquals(mUrl.getSpec(), clipCaptor.getValue().getItemAt(0).getText());
     }
 
     @Test
diff --git a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/extensions/ExtensionActionListMediatorTest.java b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/extensions/ExtensionActionListMediatorTest.java
index efdd11b..b27c61a 100644
--- a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/extensions/ExtensionActionListMediatorTest.java
+++ b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/extensions/ExtensionActionListMediatorTest.java
@@ -6,11 +6,8 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
-import static org.mockito.Mockito.any;
-import static org.mockito.Mockito.anyLong;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
 import static org.robolectric.Shadows.shadowOf;
 
 import android.content.Context;
@@ -36,9 +33,7 @@
 import org.chromium.chrome.browser.tab.MockTab;
 import org.chromium.chrome.browser.tab.Tab;
 import org.chromium.chrome.browser.toolbar.extensions.ExtensionActionButtonProperties.ListItemType;
-import org.chromium.chrome.browser.ui.extensions.ExtensionAction;
-import org.chromium.chrome.browser.ui.extensions.ExtensionActionsBridge;
-import org.chromium.chrome.browser.ui.extensions.ExtensionActionsBridgeJni;
+import org.chromium.chrome.browser.ui.extensions.FakeExtensionActionsBridge;
 import org.chromium.ui.base.WindowAndroid;
 import org.chromium.ui.modelutil.MVCListAdapter.ListItem;
 import org.chromium.ui.modelutil.MVCListAdapter.ModelList;
@@ -48,23 +43,20 @@
 public class ExtensionActionListMediatorTest {
     private static final int TAB1_ID = 111;
     private static final int TAB2_ID = 222;
-    private static final long ACTIONS_BRIDGE_POINTER = 10000L;
 
     private static final Bitmap ICON_RED = createSimpleIcon(Color.RED);
     private static final Bitmap ICON_BLUE = createSimpleIcon(Color.BLUE);
     private static final Bitmap ICON_GREEN = createSimpleIcon(Color.GREEN);
     private static final Bitmap ICON_CYAN = createSimpleIcon(Color.CYAN);
     private static final Bitmap ICON_MAGENTA = createSimpleIcon(Color.MAGENTA);
-    private static final Bitmap ICON_YELLOW = createSimpleIcon(Color.YELLOW);
-    private static final Bitmap ICON_WHITE = createSimpleIcon(Color.YELLOW);
 
     @Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule();
     @Mock private Context mContext;
     @Mock private Profile mProfile;
-    @Mock private ExtensionActionsBridge.Natives mActionsBridgeJniMock;
     @Mock private WindowAndroid mWindowAndroid;
 
-    private ExtensionActionsBridge mActionsBridge;
+    private FakeExtensionActionsBridge mFakeExtensionActionsBridge;
+    private FakeExtensionActionsBridge.ProfileModel mProfileModel;
     private MockTab mTab1;
     private MockTab mTab2;
     private ObservableSupplierImpl<Profile> mProfileSupplier;
@@ -74,38 +66,8 @@
 
     @Before
     public void setUp() {
-        ExtensionActionsBridgeJni.setInstanceForTesting(mActionsBridgeJniMock);
-
-        // Provide good defaults for action queries via JNI.
-        mActionsBridge = new ExtensionActionsBridge(ACTIONS_BRIDGE_POINTER);
-        when(mActionsBridgeJniMock.get(mProfile)).thenReturn(mActionsBridge);
-        when(mActionsBridgeJniMock.areActionsInitialized(ACTIONS_BRIDGE_POINTER)).thenReturn(true);
-        when(mActionsBridgeJniMock.getActionIds(ACTIONS_BRIDGE_POINTER))
-                .thenReturn(new String[] {"a", "b"});
-        when(mActionsBridgeJniMock.getAction(ACTIONS_BRIDGE_POINTER, "a", TAB1_ID))
-                .thenReturn(new ExtensionAction("a", "title of a"));
-        when(mActionsBridgeJniMock.getAction(ACTIONS_BRIDGE_POINTER, "b", TAB1_ID))
-                .thenReturn(new ExtensionAction("b", "title of b"));
-        when(mActionsBridgeJniMock.getAction(ACTIONS_BRIDGE_POINTER, "c", TAB1_ID))
-                .thenReturn(new ExtensionAction("c", "title of c"));
-        when(mActionsBridgeJniMock.getAction(ACTIONS_BRIDGE_POINTER, "a", TAB2_ID))
-                .thenReturn(new ExtensionAction("a", "another title of a"));
-        when(mActionsBridgeJniMock.getAction(ACTIONS_BRIDGE_POINTER, "b", TAB2_ID))
-                .thenReturn(new ExtensionAction("b", "another title of b"));
-        when(mActionsBridgeJniMock.getAction(ACTIONS_BRIDGE_POINTER, "c", TAB2_ID))
-                .thenReturn(new ExtensionAction("c", "another title of c"));
-        when(mActionsBridgeJniMock.getActionIcon(ACTIONS_BRIDGE_POINTER, "a", TAB1_ID))
-                .thenReturn(ICON_RED);
-        when(mActionsBridgeJniMock.getActionIcon(ACTIONS_BRIDGE_POINTER, "b", TAB1_ID))
-                .thenReturn(ICON_GREEN);
-        when(mActionsBridgeJniMock.getActionIcon(ACTIONS_BRIDGE_POINTER, "c", TAB1_ID))
-                .thenReturn(ICON_BLUE);
-        when(mActionsBridgeJniMock.getActionIcon(ACTIONS_BRIDGE_POINTER, "a", TAB2_ID))
-                .thenReturn(ICON_CYAN);
-        when(mActionsBridgeJniMock.getActionIcon(ACTIONS_BRIDGE_POINTER, "b", TAB2_ID))
-                .thenReturn(ICON_MAGENTA);
-        when(mActionsBridgeJniMock.getActionIcon(ACTIONS_BRIDGE_POINTER, "c", TAB2_ID))
-                .thenReturn(ICON_YELLOW);
+        mFakeExtensionActionsBridge = new FakeExtensionActionsBridge();
+        mFakeExtensionActionsBridge.install();
 
         // Initialize common objects.
         mTab1 = new MockTab(TAB1_ID, mProfile);
@@ -124,10 +86,13 @@
     @After
     public void tearDown() {
         mMediator.destroy();
+        mFakeExtensionActionsBridge.uninstall();
     }
 
     @Test
     public void testUpdateModels() {
+        setUpProfileModel();
+
         // Set the profile and the tab.
         mProfileSupplier.set(mProfile);
         mCurrentTabSupplier.set(mTab1);
@@ -145,21 +110,24 @@
 
         // The model should have been not updated.
         assertTrue(mModels.isEmpty());
-        verify(mActionsBridgeJniMock, never()).get(any());
+        verify(mProfile, never()).getNativeBrowserContextPointer();
     }
 
     @Test
     public void testUpdateModels_noTab() {
+        setUpProfileModel();
+
         // Set the profile only.
         mProfileSupplier.set(mProfile);
 
         // The model should have been not updated.
         assertTrue(mModels.isEmpty());
-        verify(mActionsBridgeJniMock, never()).getActionIds(anyLong());
     }
 
     @Test
     public void testUpdateModels_tabChanged() {
+        setUpProfileModel();
+
         // Set the profile and the tab.
         mProfileSupplier.set(mProfile);
         mCurrentTabSupplier.set(mTab1);
@@ -178,6 +146,42 @@
         assertItemAt(1, "b", "another title of b", ICON_MAGENTA);
     }
 
+    private void setUpProfileModel() {
+        mProfileModel = mFakeExtensionActionsBridge.getOrCreateProfileModel(mProfile);
+        mProfileModel.setInitialized(true);
+
+        mProfileModel.putAction(
+                "a",
+                (tabId) -> {
+                    if (tabId == TAB1_ID) {
+                        return new FakeExtensionActionsBridge.ActionData.Builder()
+                                .setTitle("title of a")
+                                .setIcon(ICON_RED)
+                                .build();
+                    } else {
+                        return new FakeExtensionActionsBridge.ActionData.Builder()
+                                .setTitle("another title of a")
+                                .setIcon(ICON_CYAN)
+                                .build();
+                    }
+                });
+        mProfileModel.putAction(
+                "b",
+                (tabId) -> {
+                    if (tabId == TAB1_ID) {
+                        return new FakeExtensionActionsBridge.ActionData.Builder()
+                                .setTitle("title of b")
+                                .setIcon(ICON_GREEN)
+                                .build();
+                    } else {
+                        return new FakeExtensionActionsBridge.ActionData.Builder()
+                                .setTitle("another title of b")
+                                .setIcon(ICON_MAGENTA)
+                                .build();
+                    }
+                });
+    }
+
     private static Bitmap createSimpleIcon(int color) {
         Bitmap bitmap = Bitmap.createBitmap(12, 12, Bitmap.Config.ARGB_8888);
         Canvas canvas = new Canvas(bitmap);
diff --git a/chrome/browser/ui/browser_actions.cc b/chrome/browser/ui/browser_actions.cc
index b92fc58..cf4a1b5 100644
--- a/chrome/browser/ui/browser_actions.cc
+++ b/chrome/browser/ui/browser_actions.cc
@@ -55,6 +55,7 @@
 #include "chrome/browser/ui/views/media_router/cast_browser_controller.h"
 #include "chrome/browser/ui/views/page_info/page_info_view_factory.h"
 #include "chrome/browser/ui/views/send_tab_to_self/send_tab_to_self_toolbar_bubble_controller.h"
+#include "chrome/browser/ui/views/side_panel/comments/comments_side_panel_coordinator.h"
 #include "chrome/browser/ui/views/side_panel/history/history_side_panel_coordinator.h"
 #include "chrome/browser/ui/views/side_panel/history_clusters/history_clusters_side_panel_utils.h"
 #include "chrome/browser/ui/views/side_panel/side_panel_action_callback.h"
@@ -898,6 +899,16 @@
           .SetActionId(kActionSidePanelShowCustomizeChromeFooter)
           .Build());
 
+  if (CommentsSidePanelCoordinator::IsSupported()) {
+    root_action_item_->AddChild(
+        SidePanelAction(SidePanelEntryId::kComments,
+                        IDS_COLLABORATION_SHARED_TAB_GROUPS_COMMENTS_TITLE,
+                        IDS_COLLABORATION_SHARED_TAB_GROUPS_COMMENTS_TITLE,
+                        vector_icons::kChatIcon, kActionSidePanelShowComments,
+                        browser, false)
+            .Build());
+  }
+
   AddListeners();
 }
 
diff --git a/chrome/browser/ui/browser_command_controller.cc b/chrome/browser/ui/browser_command_controller.cc
index d1b9ee7..832f2b99 100644
--- a/chrome/browser/ui/browser_command_controller.cc
+++ b/chrome/browser/ui/browser_command_controller.cc
@@ -971,6 +971,10 @@
     case IDC_SHOW_DOWNLOADS:
       ShowDownloads(browser_->GetBrowserForOpeningWebUi());
       break;
+    case IDC_SHOW_COMMENTS_SIDE_PANEL:
+      browser_->GetFeatures().side_panel_ui()->Show(
+          SidePanelEntryId::kComments, SidePanelOpenTrigger::kAppMenu);
+      break;
     case IDC_MANAGE_EXTENSIONS:
     case IDC_SAFETY_HUB_MANAGE_EXTENSIONS:
       ShowExtensions(browser_->GetBrowserForOpeningWebUi());
@@ -1463,6 +1467,7 @@
       IDC_SHOW_HISTORY_CLUSTERS_SIDE_PANEL,
       (!guest_session && !profile()->IsSystemProfile()));
   command_updater_.UpdateCommandEnabled(IDC_SHOW_DOWNLOADS, true);
+  command_updater_.UpdateCommandEnabled(IDC_SHOW_COMMENTS_SIDE_PANEL, true);
   command_updater_.UpdateCommandEnabled(IDC_FIND_AND_EDIT_MENU, true);
   command_updater_.UpdateCommandEnabled(IDC_SAVE_AND_SHARE_MENU, true);
   command_updater_.UpdateCommandEnabled(IDC_SHOW_READING_MODE_SIDE_PANEL, true);
diff --git a/chrome/browser/ui/browser_window/internal/browser_window_features.cc b/chrome/browser/ui/browser_window/internal/browser_window_features.cc
index 092e66b..6d711cf 100644
--- a/chrome/browser/ui/browser_window/internal/browser_window_features.cc
+++ b/chrome/browser/ui/browser_window/internal/browser_window_features.cc
@@ -77,6 +77,7 @@
 #include "chrome/browser/ui/views/profiles/profile_menu_coordinator.h"
 #include "chrome/browser/ui/views/send_tab_to_self/send_tab_to_self_toolbar_bubble_controller.h"
 #include "chrome/browser/ui/views/side_panel/bookmarks/bookmarks_side_panel_coordinator.h"
+#include "chrome/browser/ui/views/side_panel/comments/comments_side_panel_coordinator.h"
 #include "chrome/browser/ui/views/side_panel/extensions/extension_side_panel_manager.h"
 #include "chrome/browser/ui/views/side_panel/history/history_side_panel_coordinator.h"
 #include "chrome/browser/ui/views/side_panel/history_clusters/history_clusters_side_panel_coordinator.h"
@@ -445,6 +446,11 @@
   bookmarks_side_panel_coordinator_ =
       std::make_unique<BookmarksSidePanelCoordinator>();
 
+  if (CommentsSidePanelCoordinator::IsSupported()) {
+    comments_side_panel_coordinator_ =
+        std::make_unique<CommentsSidePanelCoordinator>();
+  }
+
   side_panel_coordinator_->Init(browser_view->browser());
 
   extension_side_panel_manager_ =
diff --git a/chrome/browser/ui/browser_window/public/browser_window_features.h b/chrome/browser/ui/browser_window/public/browser_window_features.h
index de635f6..494480b 100644
--- a/chrome/browser/ui/browser_window/public/browser_window_features.h
+++ b/chrome/browser/ui/browser_window/public/browser_window_features.h
@@ -37,6 +37,7 @@
 class BrowserWindowInterface;
 class ChromeLabsCoordinator;
 class ColorProviderBrowserHelper;
+class CommentsSidePanelCoordinator;
 class CookieControlsBubbleCoordinator;
 class DataSharingBubbleController;
 class DesktopBrowserWindowCapabilities;
@@ -191,6 +192,10 @@
     return bookmarks_side_panel_coordinator_.get();
   }
 
+  CommentsSidePanelCoordinator* comments_side_panel_coordinator() {
+    return comments_side_panel_coordinator_.get();
+  }
+
 #if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC)
   pdf::infobar::PdfInfoBarController* pdf_infobar_controller() {
     return pdf_infobar_controller_.get();
@@ -437,6 +442,9 @@
   std::unique_ptr<BookmarksSidePanelCoordinator>
       bookmarks_side_panel_coordinator_;
 
+  std::unique_ptr<CommentsSidePanelCoordinator>
+      comments_side_panel_coordinator_;
+
 #if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC)
   std::unique_ptr<pdf::infobar::PdfInfoBarController> pdf_infobar_controller_;
 #endif
diff --git a/chrome/browser/ui/hats/survey_config.cc b/chrome/browser/ui/hats/survey_config.cc
index 14508db..3ea4b1b 100644
--- a/chrome/browser/ui/hats/survey_config.cc
+++ b/chrome/browser/ui/hats/survey_config.cc
@@ -56,6 +56,19 @@
     "autofill-password-users-perception";
 constexpr char kHatsSurveyTriggerAutofillCard[] = "autofill-card";
 constexpr char kHatsSurveyTriggerAutofillPassword[] = "autofill-password";
+constexpr char kHatsSurveyTriggerDownloadWarningBubbleBypass[] =
+    "download-warning-bubble-bypass";
+constexpr char kHatsSurveyTriggerDownloadWarningBubbleHeed[] =
+    "download-warning-bubble-heed";
+constexpr char kHatsSurveyTriggerDownloadWarningBubbleIgnore[] =
+    "download-warning-bubble-ignore";
+constexpr char kHatsSurveyTriggerDownloadWarningPageBypass[] =
+    "download-warning-page-bypass";
+constexpr char kHatsSurveyTriggerDownloadWarningPageHeed[] =
+    "download-warning-page-heed";
+constexpr char kHatsSurveyTriggerDownloadWarningPageIgnore[] =
+    "download-warning-page-ignore";
+constexpr char kHatsSurveyTriggerHistoryEmbeddings[] = "history-embeddings";
 constexpr char kHatsSurveyTriggerIdentityAddressBubbleSignin[] =
     "identity-address-bubble-signin";
 constexpr char kHatsSurveyTriggerIdentityDiceWebSigninAccepted[] =
@@ -74,19 +87,10 @@
     "identity-signin-intercept-profile-separation";
 constexpr char kHatsSurveyTriggerIdentitySigninPromoBubbleDismissed[] =
     "identity-signin-promo-bubble-dismissed";
-constexpr char kHatsSurveyTriggerDownloadWarningBubbleBypass[] =
-    "download-warning-bubble-bypass";
-constexpr char kHatsSurveyTriggerDownloadWarningBubbleHeed[] =
-    "download-warning-bubble-heed";
-constexpr char kHatsSurveyTriggerDownloadWarningBubbleIgnore[] =
-    "download-warning-bubble-ignore";
-constexpr char kHatsSurveyTriggerDownloadWarningPageBypass[] =
-    "download-warning-page-bypass";
-constexpr char kHatsSurveyTriggerDownloadWarningPageHeed[] =
-    "download-warning-page-heed";
-constexpr char kHatsSurveyTriggerDownloadWarningPageIgnore[] =
-    "download-warning-page-ignore";
-constexpr char kHatsSurveyTriggerHistoryEmbeddings[] = "history-embeddings";
+constexpr char kHatsSurveyTriggerIdentitySwitchProfileFromProfileMenu[] =
+    "identity-switch-profile-profile-menu";
+constexpr char kHatsSurveyTriggerIdentitySwitchProfileFromProfilePicker[] =
+    "identity-switch-profile-profile-picker";
 constexpr char kHatsSurveyTriggerLensOverlayResults[] = "lens-overlay-results";
 constexpr char kHatsSurveyTriggerNtpModules[] = "ntp-modules";
 constexpr char kHatsSurveyTriggerNtpPhotosModuleOptOut[] =
@@ -512,6 +516,12 @@
   survey_configs.emplace_back(
       &switches::kChromeIdentitySurveySigninPromoBubbleDismissed,
       kHatsSurveyTriggerIdentitySigninPromoBubbleDismissed);
+  survey_configs.emplace_back(
+      &switches::kChromeIdentitySurveySwitchProfileFromProfileMenu,
+      kHatsSurveyTriggerIdentitySwitchProfileFromProfileMenu);
+  survey_configs.emplace_back(
+      &switches::kChromeIdentitySurveySwitchProfileFromProfilePicker,
+      kHatsSurveyTriggerIdentitySwitchProfileFromProfilePicker);
 #endif  // BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX)
 
 #if BUILDFLAG(ENABLE_COMPOSE)
diff --git a/chrome/browser/ui/hats/survey_config.h b/chrome/browser/ui/hats/survey_config.h
index de96ecf..009ede1 100644
--- a/chrome/browser/ui/hats/survey_config.h
+++ b/chrome/browser/ui/hats/survey_config.h
@@ -24,6 +24,13 @@
 extern const char kHatsSurveyTriggerAutofillPasswordUserPerception[];
 extern const char kHatsSurveyTriggerAutofillCard[];
 extern const char kHatsSurveyTriggerAutofillPassword[];
+extern const char kHatsSurveyTriggerDownloadWarningBubbleBypass[];
+extern const char kHatsSurveyTriggerDownloadWarningBubbleHeed[];
+extern const char kHatsSurveyTriggerDownloadWarningBubbleIgnore[];
+extern const char kHatsSurveyTriggerDownloadWarningPageBypass[];
+extern const char kHatsSurveyTriggerDownloadWarningPageHeed[];
+extern const char kHatsSurveyTriggerDownloadWarningPageIgnore[];
+extern const char kHatsSurveyTriggerHistoryEmbeddings[];
 extern const char kHatsSurveyTriggerIdentityAddressBubbleSignin[];
 extern const char kHatsSurveyTriggerIdentityDiceWebSigninAccepted[];
 extern const char kHatsSurveyTriggerIdentityDiceWebSigninDeclined[];
@@ -33,13 +40,8 @@
 extern const char kHatsSurveyTriggerIdentityProfilePickerAddProfileSignin[];
 extern const char kHatsSurveyTriggerIdentitySigninInterceptProfileSeparation[];
 extern const char kHatsSurveyTriggerIdentitySigninPromoBubbleDismissed[];
-extern const char kHatsSurveyTriggerDownloadWarningBubbleBypass[];
-extern const char kHatsSurveyTriggerDownloadWarningBubbleHeed[];
-extern const char kHatsSurveyTriggerDownloadWarningBubbleIgnore[];
-extern const char kHatsSurveyTriggerDownloadWarningPageBypass[];
-extern const char kHatsSurveyTriggerDownloadWarningPageHeed[];
-extern const char kHatsSurveyTriggerDownloadWarningPageIgnore[];
-extern const char kHatsSurveyTriggerHistoryEmbeddings[];
+extern const char kHatsSurveyTriggerIdentitySwitchProfileFromProfileMenu[];
+extern const char kHatsSurveyTriggerIdentitySwitchProfileFromProfilePicker[];
 extern const char kHatsSurveyTriggerLensOverlayResults[];
 extern const char kHatsSurveyTriggerNtpModules[];
 extern const char kHatsSurveyTriggerNtpPhotosModuleOptOut[];
diff --git a/chrome/browser/ui/lens/lens_side_panel_untrusted_ui.cc b/chrome/browser/ui/lens/lens_side_panel_untrusted_ui.cc
index 3454fc8e..a269299 100644
--- a/chrome/browser/ui/lens/lens_side_panel_untrusted_ui.cc
+++ b/chrome/browser/ui/lens/lens_side_panel_untrusted_ui.cc
@@ -76,6 +76,9 @@
                                   IDS_LENS_OVERLAY_FEEDBACK_TOAST_MESSAGE);
   html_source->AddLocalizedString("sendFeedbackButtonText",
                                   IDS_LENS_OVERLAY_SEND_FEEDBACK_BUTTON_LABEL);
+  html_source->AddLocalizedString(
+      "closeFeedbackToastAccessibilityLabel",
+      IDS_LENS_OVERLAY_CLOSE_FEEDBACK_TOAST_ACCESSIBILITY_LABEL);
   const bool dark_mode = lens::LensOverlayShouldUseDarkMode(
       ThemeServiceFactory::GetForProfile(Profile::FromWebUI(web_ui)));
 
diff --git a/chrome/browser/ui/pdf/infobar/pdf_infobar_controller.cc b/chrome/browser/ui/pdf/infobar/pdf_infobar_controller.cc
index c304407..2817e61 100644
--- a/chrome/browser/ui/pdf/infobar/pdf_infobar_controller.cc
+++ b/chrome/browser/ui/pdf/infobar/pdf_infobar_controller.cc
@@ -192,10 +192,13 @@
       higher_priority_infobar_shown_.value()) {
     return;
   }
-
-  // Show the PDF infobar.
   content::WebContents* web_contents =
       browser_->GetTabStripModel()->GetActiveWebContents();
+  if (!web_contents) {
+    return;
+  }
+
+  // Show the PDF infobar.
   infobar_manager_ =
       infobars::ContentInfoBarManager::FromWebContents(web_contents);
   infobar_manager_->AddObserver(this);
diff --git a/chrome/browser/ui/plus_addresses/android/BUILD.gn b/chrome/browser/ui/plus_addresses/android/BUILD.gn
index 4152c5d..0fb4d77 100644
--- a/chrome/browser/ui/plus_addresses/android/BUILD.gn
+++ b/chrome/browser/ui/plus_addresses/android/BUILD.gn
@@ -149,6 +149,7 @@
     "//chrome/browser/ui/android/night_mode:night_mode_java_test_support",
     "//chrome/browser/util:java",
     "//chrome/test/android:chrome_java_integration_test_support",
+    "//chrome/test/android:chrome_java_transit",
     "//components/autofill/android:autofill_java_resources",
     "//components/autofill/android:autofill_payments_java_resources",
     "//components/autofill/android:main_autofill_java",
diff --git a/chrome/browser/ui/plus_addresses/android/javatests/src/org/chromium/chrome/browser/ui/plus_addresses/AllPlusAddressesBottomSheetRenderTest.java b/chrome/browser/ui/plus_addresses/android/javatests/src/org/chromium/chrome/browser/ui/plus_addresses/AllPlusAddressesBottomSheetRenderTest.java
index fd7b12c..a5d7f93 100644
--- a/chrome/browser/ui/plus_addresses/android/javatests/src/org/chromium/chrome/browser/ui/plus_addresses/AllPlusAddressesBottomSheetRenderTest.java
+++ b/chrome/browser/ui/plus_addresses/android/javatests/src/org/chromium/chrome/browser/ui/plus_addresses/AllPlusAddressesBottomSheetRenderTest.java
@@ -32,7 +32,9 @@
 import org.chromium.chrome.browser.night_mode.ChromeNightModeTestUtils;
 import org.chromium.chrome.browser.profiles.Profile;
 import org.chromium.chrome.test.ChromeJUnit4RunnerDelegate;
-import org.chromium.chrome.test.ChromeTabbedActivityTestRule;
+import org.chromium.chrome.test.transit.ChromeTransitTestRules;
+import org.chromium.chrome.test.transit.FreshCtaTransitTestRule;
+import org.chromium.chrome.test.transit.page.WebPageStation;
 import org.chromium.chrome.test.util.ChromeRenderTestRule;
 import org.chromium.components.browser_ui.bottomsheet.BottomSheetTestSupport;
 import org.chromium.ui.test.util.RenderTestRule.Component;
@@ -66,7 +68,8 @@
                     new ParameterSet().value(false, true).name("RTL"),
                     new ParameterSet().value(true, false).name("NightMode"));
 
-    public ChromeTabbedActivityTestRule mActivityTestRule = new ChromeTabbedActivityTestRule();
+    public FreshCtaTransitTestRule mActivityTestRule =
+            ChromeTransitTestRules.freshChromeTabbedActivityRule();
 
     @Rule
     public final MockitoRule mMockitoRule = MockitoJUnit.rule().strictness(Strictness.STRICT_STUBS);
@@ -80,6 +83,7 @@
 
     @Mock private Profile mProfile;
     @Mock private AllPlusAddressesBottomSheetCoordinator.Delegate mDelegate;
+    private WebPageStation mPage;
 
     public AllPlusAddressesBottomSheetRenderTest(boolean nightModeEnabled, boolean useRtlLayout) {
         setRtlForTesting(useRtlLayout);
@@ -90,7 +94,7 @@
 
     @Before
     public void setUp() throws InterruptedException {
-        mActivityTestRule.startMainActivityOnBlankPage();
+        mPage = mActivityTestRule.startOnBlankPage();
         mActivityTestRule.waitForActivityCompletelyLoaded();
     }
 
diff --git a/chrome/browser/ui/plus_addresses/android/javatests/src/org/chromium/chrome/browser/ui/plus_addresses/PlusAddressCreationRenderTest.java b/chrome/browser/ui/plus_addresses/android/javatests/src/org/chromium/chrome/browser/ui/plus_addresses/PlusAddressCreationRenderTest.java
index d6382356..8363712c 100644
--- a/chrome/browser/ui/plus_addresses/android/javatests/src/org/chromium/chrome/browser/ui/plus_addresses/PlusAddressCreationRenderTest.java
+++ b/chrome/browser/ui/plus_addresses/android/javatests/src/org/chromium/chrome/browser/ui/plus_addresses/PlusAddressCreationRenderTest.java
@@ -36,7 +36,9 @@
 import org.chromium.chrome.browser.layouts.LayoutStateProvider;
 import org.chromium.chrome.browser.night_mode.ChromeNightModeTestUtils;
 import org.chromium.chrome.test.ChromeJUnit4RunnerDelegate;
-import org.chromium.chrome.test.ChromeTabbedActivityTestRule;
+import org.chromium.chrome.test.transit.ChromeTransitTestRules;
+import org.chromium.chrome.test.transit.FreshCtaTransitTestRule;
+import org.chromium.chrome.test.transit.page.WebPageStation;
 import org.chromium.chrome.test.util.ChromeRenderTestRule;
 import org.chromium.components.browser_ui.bottomsheet.BottomSheetController;
 import org.chromium.components.browser_ui.bottomsheet.BottomSheetTestSupport;
@@ -79,7 +81,8 @@
                     new ParameterSet().value(false, true).name("RTL"),
                     new ParameterSet().value(true, false).name("NightMode"));
 
-    public ChromeTabbedActivityTestRule mActivityTestRule = new ChromeTabbedActivityTestRule();
+    public FreshCtaTransitTestRule mActivityTestRule =
+            ChromeTransitTestRules.freshChromeTabbedActivityRule();
 
     @Rule
     public final MockitoRule mMockitoRule = MockitoJUnit.rule().strictness(Strictness.STRICT_STUBS);
@@ -94,6 +97,7 @@
     @Mock private LayoutStateProvider mLayoutStateProvider;
     @Mock private PlusAddressCreationViewBridge mBridge;
 
+    private WebPageStation mPage;
     private BottomSheetController mBottomSheetController;
     private PlusAddressCreationCoordinator mCoordinator;
 
@@ -109,7 +113,7 @@
 
     @Before
     public void setUp() throws InterruptedException {
-        mActivityTestRule.startMainActivityOnBlankPage();
+        mPage = mActivityTestRule.startOnBlankPage();
         mActivityTestRule.waitForActivityCompletelyLoaded();
         mBottomSheetController =
                 mActivityTestRule
diff --git a/chrome/browser/ui/tabs/alert/BUILD.gn b/chrome/browser/ui/tabs/alert/BUILD.gn
index b4f6f466..75d3d28 100644
--- a/chrome/browser/ui/tabs/alert/BUILD.gn
+++ b/chrome/browser/ui/tabs/alert/BUILD.gn
@@ -53,10 +53,20 @@
   deps = [
     ":impl",
     ":tab_alert",
+    "//chrome/browser/ui:ui_features",
+    "//chrome/browser/ui/tabs:test_support",
     "//chrome/test:test_support",
     "//components/tabs:test_support",
     "//content/test:test_support",
     "//testing/gmock",
     "//testing/gtest",
   ]
+
+  if (enable_glic) {
+    deps += [
+      "//chrome/browser/glic",
+      "//chrome/browser/glic:mojo_bindings_shared_cpp_sources",
+      "//chrome/browser/glic/test_support",
+    ]
+  }
 }
diff --git a/chrome/browser/ui/tabs/alert/tab_alert_controller.cc b/chrome/browser/ui/tabs/alert/tab_alert_controller.cc
index f01c4a8d..092f662 100644
--- a/chrome/browser/ui/tabs/alert/tab_alert_controller.cc
+++ b/chrome/browser/ui/tabs/alert/tab_alert_controller.cc
@@ -19,6 +19,22 @@
 #include "content/public/browser/web_contents.h"
 #include "content/public/browser/web_contents_capability_type.h"
 
+#if BUILDFLAG(ENABLE_GLIC)
+#include "chrome/browser/glic/glic_keyed_service.h"
+#include "chrome/browser/glic/public/context/glic_sharing_manager.h"
+#endif  // BUILDFLAG(ENABLE_GLIC)
+
+namespace {
+glic::GlicKeyedService* GetGlicKeyedService(
+    BrowserWindowInterface* browser_window_interface) {
+#if BUILDFLAG(ENABLE_GLIC)
+  return glic::GlicKeyedService::Get(browser_window_interface->GetProfile());
+#else
+  return nullptr;
+#endif  // BUILDFLAG(ENABLE_GLIC)
+}
+}  // namespace
+
 namespace tabs {
 
 bool CompareAlerts::operator()(TabAlert first, TabAlert second) const {
@@ -46,6 +62,13 @@
 }
 
 TabAlertController::TabAlertController(TabInterface& tab)
+    : TabAlertController(tab,
+                         GetGlicKeyedService(tab.GetBrowserWindowInterface())) {
+}
+
+TabAlertController::TabAlertController(
+    TabInterface& tab,
+    glic::GlicKeyedService* glic_keyed_service)
     : tabs::ContentsObservingTabFeature(tab) {
   media_stream_capture_indicator_observation_.Observe(
       MediaCaptureDevicesDispatcher::GetInstance()
@@ -53,6 +76,17 @@
           .get());
   vr_tab_helper_observation_.Observe(
       vr::VrTabHelper::FromWebContents(web_contents()));
+
+#if BUILDFLAG(ENABLE_GLIC)
+  if (glic_keyed_service) {
+    glic::GlicSharingManager& glic_sharing_manager =
+        glic_keyed_service->sharing_manager();
+    glic_sharing_status_changed_subscription_ =
+        glic_sharing_manager.AddTabPinningStatusChangedCallback(
+            base::BindRepeating(&TabAlertController::OnGlicTabPinningChanged,
+                                base::Unretained(this)));
+  }
+#endif  // BUILDFLAG(ENABLE_GLIC)
 }
 
 TabAlertController::~TabAlertController() = default;
@@ -75,6 +109,10 @@
   return base::ToVector(active_alerts_);
 }
 
+bool TabAlertController::IsAlertActive(TabAlert alert) const {
+  return active_alerts_.contains(alert);
+}
+
 void TabAlertController::OnDiscardContents(TabInterface* tab_interface,
                                            content::WebContents* old_contents,
                                            content::WebContents* new_contents) {
@@ -168,6 +206,14 @@
   UpdateAlertState(TabAlert::VR_PRESENTING_IN_HEADSET, state);
 }
 
+void TabAlertController::OnGlicTabPinningChanged(
+    tabs::TabInterface* tab_interface,
+    bool is_sharing) {
+  if (tab_interface->GetContents() == web_contents()) {
+    UpdateAlertState(TabAlert::GLIC_SHARING, is_sharing);
+  }
+}
+
 void TabAlertController::UpdateAlertState(TabAlert alert, bool is_active) {
   std::optional<TabAlert> previous_alert = GetAlertToShow();
   if (is_active) {
diff --git a/chrome/browser/ui/tabs/alert/tab_alert_controller.h b/chrome/browser/ui/tabs/alert/tab_alert_controller.h
index 9fafd2b..c6cacdc2 100644
--- a/chrome/browser/ui/tabs/alert/tab_alert_controller.h
+++ b/chrome/browser/ui/tabs/alert/tab_alert_controller.h
@@ -15,12 +15,17 @@
 #include "chrome/browser/ui/tabs/alert/tab_alert.h"
 #include "chrome/browser/ui/tabs/contents_observing_tab_feature.h"
 #include "chrome/browser/vr/vr_tab_helper.h"
+#include "components/tabs/public/tab_interface.h"
 
 namespace content {
 enum class WebContentsCapabilityType;
 class WebContents;
 }  // namespace content
 
+namespace glic {
+class GlicKeyedService;
+}  // namespace glic
+
 namespace tabs {
 class TabInterface;
 
@@ -38,6 +43,9 @@
                            public vr::VrTabHelper::Observer {
  public:
   explicit TabAlertController(TabInterface& tab);
+
+  TabAlertController(TabInterface& tab,
+                     glic::GlicKeyedService* glic_keyed_service);
   TabAlertController(const TabAlertController&) = delete;
   TabAlertController& operator=(const TabAlertController&) = delete;
   ~TabAlertController() override;
@@ -52,6 +60,10 @@
   // to lowest priority to be shown.
   std::vector<TabAlert> GetAllActiveAlerts();
 
+  // Returns true if `alert` is currently active for this tab and false
+  // otherwise.
+  bool IsAlertActive(TabAlert alert) const;
+
   // WebContentsObserver:
   void OnDiscardContents(TabInterface* tab_interface,
                          content::WebContents* old_contents,
@@ -79,6 +91,10 @@
   void OnIsContentDisplayedInHeadsetChanged(bool state) override;
 
  private:
+  void OnGlicTabPinningChanged(tabs::TabInterface* tab_interface,
+                               bool is_sharing);
+  void ObserveAlerts();
+
   // Adds `alert` to the set of already active alerts for this tab if it isn't
   // currently active. Otherwise, removes `alert` from the set and is considered
   // inactive.
@@ -102,6 +118,9 @@
   // is displaying content to a headset.
   base::ScopedObservation<vr::VrTabHelper, vr::VrTabHelper::Observer>
       vr_tab_helper_observation_{this};
+
+  // Subscription to be notified when glic sharing status has changed.
+  base::CallbackListSubscription glic_sharing_status_changed_subscription_;
 };
 }  // namespace tabs
 
diff --git a/chrome/browser/ui/tabs/alert/tab_alert_controller_unittest.cc b/chrome/browser/ui/tabs/alert/tab_alert_controller_unittest.cc
index 19adb99..4a61ad1e 100644
--- a/chrome/browser/ui/tabs/alert/tab_alert_controller_unittest.cc
+++ b/chrome/browser/ui/tabs/alert/tab_alert_controller_unittest.cc
@@ -8,10 +8,18 @@
 #include <optional>
 
 #include "base/functional/bind.h"
+#include "base/test/scoped_feature_list.h"
+#include "chrome/browser/global_features.h"
+#include "chrome/browser/ui/browser_window/test/mock_browser_window_interface.h"
 #include "chrome/browser/ui/tabs/alert/tab_alert.h"
+#include "chrome/browser/ui/tabs/tab_strip_model.h"
+#include "chrome/browser/ui/tabs/test_tab_strip_model_delegate.h"
+#include "chrome/browser/ui/ui_features.h"
 #include "chrome/browser/vr/vr_tab_helper.h"
-#include "chrome/test/base/testing_profile.h"
-#include "components/tabs/public/mock_tab_interface.h"
+#include "chrome/common/chrome_features.h"
+#include "chrome/test/base/testing_browser_process.h"
+#include "chrome/test/base/testing_profile_manager.h"
+#include "components/signin/public/identity_manager/identity_test_environment.h"
 #include "content/public/browser/web_contents.h"
 #include "content/public/test/browser_task_environment.h"
 #include "content/public/test/test_renderer_host.h"
@@ -19,17 +27,24 @@
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
+#if BUILDFLAG(ENABLE_GLIC)
+#include "chrome/browser/glic/glic_keyed_service.h"
+#include "chrome/browser/glic/glic_profile_manager.h"
+#include "chrome/browser/glic/host/glic_features.mojom-features.h"
+#include "chrome/browser/glic/public/context/glic_sharing_manager.h"
+#include "chrome/browser/glic/test_support/glic_test_util.h"
+#endif  // BUILDFLAG(ENABLE_GLIC)
+
 namespace tabs {
 
-class FakeTabInterface : public tabs::MockTabInterface {
+class FakeBrowserWindowInterface : public MockBrowserWindowInterface {
  public:
-  ~FakeTabInterface() override = default;
-  explicit FakeTabInterface(std::unique_ptr<content::WebContents> contents)
-      : contents_(std::move(contents)) {}
-  content::WebContents* GetContents() const override { return contents_.get(); }
+  ~FakeBrowserWindowInterface() override = default;
+  explicit FakeBrowserWindowInterface(Profile* profile) : profile_(profile) {}
+  Profile* GetProfile() override { return profile_; }
 
  private:
-  std::unique_ptr<content::WebContents> contents_;
+  raw_ptr<Profile> profile_ = nullptr;
 };
 
 class MockTabAlertControllerSubscriber {
@@ -40,29 +55,114 @@
                void(std::optional<TabAlert> new_alert));
 };
 
+#if BUILDFLAG(ENABLE_GLIC)
+class TestGlicKeyedService : public glic::GlicKeyedService {
+ public:
+  TestGlicKeyedService(
+      content::BrowserContext* browser_context,
+      signin::IdentityManager* identity_manager,
+      ProfileManager* profile_manager,
+      glic::GlicProfileManager* glic_profile_manager,
+      contextual_cueing::ContextualCueingService* contextual_cueing_service)
+      : GlicKeyedService(Profile::FromBrowserContext(browser_context),
+                         identity_manager,
+                         profile_manager,
+                         glic_profile_manager,
+                         contextual_cueing_service) {}
+};
+#endif  // BUILDFLAG(ENABLE_GLIC)
+
 class TabAlertControllerTest : public testing::Test {
  public:
-  TabAlertControllerTest() {
+  void SetUp() override {
+#if BUILDFLAG(ENABLE_GLIC)
+    scoped_feature_list_.InitWithFeatures(
+        {features::kGlic, features::kTabstripComboButton,
+         glic::mojom::features::kGlicMultiTab},
+        {});
+#endif  // BUILDFLAG(ENABLE_GLIC)
+
+    testing_profile_manager_ = std::make_unique<TestingProfileManager>(
+        TestingBrowserProcess::GetGlobal());
+    ASSERT_TRUE(testing_profile_manager_->SetUp());
+    TestingBrowserProcess::GetGlobal()->CreateGlobalFeaturesForTesting();
+    profile_ = testing_profile_manager_->CreateTestingProfile("profile");
+
+#if BUILDFLAG(ENABLE_GLIC)
+    test_glic_keyed_service_ = std::make_unique<TestGlicKeyedService>(
+        profile_, identity_test_environment.identity_manager(),
+        testing_profile_manager_->profile_manager(), &glic_profile_manager_,
+        /*contextual_cueing_service=*/nullptr);
+    glic::ForceSigninAndModelExecutionCapability(profile_);
+#endif  // BUILDFLAG(ENABLE_GLIC)
+
+    browser_window_interface_ =
+        std::make_unique<FakeBrowserWindowInterface>(profile_);
+    tab_strip_model_delegate_ = std::make_unique<TestTabStripModelDelegate>();
+    tab_strip_model_delegate_->SetBrowserWindowInterface(
+        browser_window_interface_.get());
+    tab_strip_model_ = std::make_unique<TabStripModel>(
+        tab_strip_model_delegate_.get(), profile_);
+
     std::unique_ptr<content::WebContents> web_contents =
-        content::WebContentsTester::CreateTestWebContents(&profile_, nullptr);
-    tab_interface_ =
-        std::make_unique<FakeTabInterface>(std::move(web_contents));
-    vr::VrTabHelper::CreateForWebContents(tab_interface_->GetContents());
+        content::WebContentsTester::CreateTestWebContents(profile_, nullptr);
+    tab_model_ = std::make_unique<TabModel>(std::move(web_contents),
+                                            tab_strip_model_.get());
+
+#if BUILDFLAG(ENABLE_GLIC)
+    tab_alert_controller_ = std::make_unique<TabAlertController>(
+        *tab_model_.get(), test_glic_keyed_service_.get());
+#else
     tab_alert_controller_ =
-        std::make_unique<TabAlertController>(*tab_interface_.get());
+        std::make_unique<TabAlertController>(*tab_model_.get());
+#endif  // BUILDFLAG(ENABLE_GLIC)
+  }
+
+  void TearDown() override {
+    // Explicitly reset the pointers to prevent them from causing the
+    // BrowserTaskEnvironment to time out on destruction.
+    tab_alert_controller_.reset();
+    tab_model_.reset();
+    tab_strip_model_.reset();
+    tab_strip_model_delegate_.reset();
+    browser_window_interface_.reset();
+#if BUILDFLAG(ENABLE_GLIC)
+    test_glic_keyed_service_.reset();
+#endif  // BUILDFLAG(ENABLE_GLIC)
+    profile_ = nullptr;
+    TestingBrowserProcess::GetGlobal()->GetFeatures()->Shutdown();
+    testing_profile_manager_.reset();
   }
 
   TabAlertController* tab_alert_controller() {
     return tab_alert_controller_.get();
   }
 
-  FakeTabInterface* tab_interface() { return tab_interface_.get(); }
+  TabInterface* tab_interface() { return tab_model_.get(); }
+
+#if BUILDFLAG(ENABLE_GLIC)
+  TestGlicKeyedService* test_glic_keyed_service() {
+    return test_glic_keyed_service_.get();
+  }
+#endif  // BUILDFLAG(ENABLE_GLIC)
 
  private:
+  base::test::ScopedFeatureList scoped_feature_list_;
   content::BrowserTaskEnvironment task_environment_;
   content::RenderViewHostTestEnabler test_enabler_;
-  TestingProfile profile_;
-  std::unique_ptr<FakeTabInterface> tab_interface_;
+  std::unique_ptr<TestingProfileManager> testing_profile_manager_;
+  raw_ptr<Profile> profile_ = nullptr;
+  signin::IdentityTestEnvironment identity_test_environment;
+
+#if BUILDFLAG(ENABLE_GLIC)
+  glic::GlicProfileManager glic_profile_manager_;
+  std::unique_ptr<TestGlicKeyedService> test_glic_keyed_service_;
+#endif  // BUILDFLAG(ENABLE_GLIC)
+
+  std::unique_ptr<FakeBrowserWindowInterface> browser_window_interface_;
+  std::unique_ptr<TestTabStripModelDelegate> tab_strip_model_delegate_;
+  std::unique_ptr<TabStripModel> tab_strip_model_;
+  std::unique_ptr<TabModel> tab_model_;
   std::unique_ptr<TabAlertController> tab_alert_controller_;
 };
 
@@ -125,6 +225,23 @@
   EXPECT_EQ(active_alerts[3], TabAlert::AUDIO_PLAYING);
 }
 
+TEST_F(TabAlertControllerTest, AlertIsActive) {
+  tab_alert_controller()->OnAudioStateChanged(true);
+  tab_alert_controller()->OnCapabilityTypesChanged(
+      content::WebContentsCapabilityType::kBluetoothConnected, true);
+  tab_alert_controller()->MediaPictureInPictureChanged(true);
+
+  EXPECT_TRUE(tab_alert_controller()->IsAlertActive(TabAlert::AUDIO_PLAYING));
+  EXPECT_TRUE(
+      tab_alert_controller()->IsAlertActive(TabAlert::BLUETOOTH_CONNECTED));
+  EXPECT_TRUE(tab_alert_controller()->IsAlertActive(TabAlert::PIP_PLAYING));
+
+  // When the non-prioritized alert is no longer active, the alert controller
+  // should be updated to reflect that.
+  tab_alert_controller()->MediaPictureInPictureChanged(false);
+  EXPECT_FALSE(tab_alert_controller()->IsAlertActive(TabAlert::PIP_PLAYING));
+}
+
 TEST_F(TabAlertControllerTest, VrStateUpdatesAlertController) {
   EXPECT_FALSE(tab_alert_controller()->GetAlertToShow().has_value());
   vr::VrTabHelper* const vr_tab_helper =
@@ -136,4 +253,18 @@
   vr_tab_helper->SetIsContentDisplayedInHeadset(false);
   EXPECT_FALSE(tab_alert_controller()->GetAlertToShow().has_value());
 }
+
+#if BUILDFLAG(ENABLE_GLIC)
+TEST_F(TabAlertControllerTest, GlicSharingUpdatesAlertController) {
+  EXPECT_FALSE(tab_alert_controller()->GetAlertToShow().has_value());
+  glic::GlicSharingManager& glic_sharing_manager =
+      test_glic_keyed_service()->sharing_manager();
+  glic_sharing_manager.PinTabs({tab_interface()->GetHandle()});
+  EXPECT_TRUE(tab_alert_controller()->GetAlertToShow().has_value());
+  EXPECT_EQ(tab_alert_controller()->GetAlertToShow().value(),
+            TabAlert::GLIC_SHARING);
+  glic_sharing_manager.UnpinAllTabs();
+  EXPECT_FALSE(tab_alert_controller()->GetAlertToShow().has_value());
+}
+#endif  // BUILDFLAG(ENABLE_GLIC)
 }  // namespace tabs
diff --git a/chrome/browser/ui/tabs/tab_features.cc b/chrome/browser/ui/tabs/tab_features.cc
index 7fd09542..8c58c45a1 100644
--- a/chrome/browser/ui/tabs/tab_features.cc
+++ b/chrome/browser/ui/tabs/tab_features.cc
@@ -72,6 +72,7 @@
 #include "chrome/browser/ui/webui/webui_embedding_context.h"
 #include "chrome/browser/web_applications/web_app_tab_helper.h"
 #include "chrome/browser/web_applications/web_app_utils.h"
+#include "chrome/common/chrome_features.h"
 #include "components/browsing_topics/browsing_topics_service.h"
 #include "components/favicon/content/content_favicon_driver.h"
 #include "components/fingerprinting_protection_filter/common/fingerprinting_protection_filter_features.h"
@@ -340,9 +341,10 @@
 
   task_manager::WebContentsTags::CreateForTabContents(tab.GetContents());
 
-  // TODO(crbug.com/425952887): Gate behind feature flag.
-  actor_ui_tab_controller_ =
-      std::make_unique<actor::ui::ActorUiTabController>(tab);
+  if (base::FeatureList::IsEnabled(features::kGlicActorUi)) {
+    actor_ui_tab_controller_ =
+        std::make_unique<actor::ui::ActorUiTabController>(tab);
+  }
 
 #if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX) || \
     BUILDFLAG(IS_CHROMEOS)
diff --git a/chrome/browser/ui/tabs/tab_list_interface.h b/chrome/browser/ui/tabs/tab_list_interface.h
index 1d8c1c2..ee3a1d8 100644
--- a/chrome/browser/ui/tabs/tab_list_interface.h
+++ b/chrome/browser/ui/tabs/tab_list_interface.h
@@ -26,12 +26,8 @@
   TabListInterface(const TabListInterface& other) = delete;
   void operator=(const TabListInterface& other) = delete;
 
-  // TODO(https://crbug.com/427503497): Include this on Android when the
-  // implementation is get-able from a BrowserWindowInterface.
-#if !BUILDFLAG(IS_ANDROID)
   // Returns the TabListInterface associated with the given `browser`.
   static TabListInterface* From(BrowserWindowInterface* browser);
-#endif
 
   // Returns the count of tabs within the tab list.
   virtual int GetTabCount() const = 0;
diff --git a/chrome/browser/ui/views/overlay/video_overlay_window_views.cc b/chrome/browser/ui/views/overlay/video_overlay_window_views.cc
index 4e1a0bb..6996fd4 100644
--- a/chrome/browser/ui/views/overlay/video_overlay_window_views.cc
+++ b/chrome/browser/ui/views/overlay/video_overlay_window_views.cc
@@ -18,6 +18,7 @@
 #include "base/time/time.h"
 #include "base/timer/timer.h"
 #include "build/build_config.h"
+#include "chrome/browser/media/media_engagement_service.h"
 #include "chrome/browser/picture_in_picture/picture_in_picture_occlusion_tracker.h"
 #include "chrome/browser/picture_in_picture/picture_in_picture_window_manager.h"
 #include "chrome/browser/profiles/profile.h"
@@ -42,7 +43,9 @@
 #include "chrome/grit/generated_resources.h"
 #include "components/global_media_controls/public/format_duration.h"
 #include "components/vector_icons/vector_icons.h"
+#include "content/public/browser/media_session.h"
 #include "content/public/browser/picture_in_picture_window_controller.h"
+#include "content/public/browser/render_frame_host.h"
 #include "content/public/browser/web_contents.h"
 #include "media/base/media_switches.h"
 #include "media/base/video_util.h"
@@ -64,6 +67,7 @@
 #include "ui/views/controls/label.h"
 #include "ui/views/widget/widget_delegate.h"
 #include "ui/views/window/non_client_view.h"
+#include "url/origin.h"
 
 #if BUILDFLAG(IS_CHROMEOS)
 #include "ash/public/cpp/window_properties.h"  // nogncheck
@@ -2521,6 +2525,42 @@
   return live_caption_dialog_->GetMirroredBounds();
 }
 
+bool VideoOverlayWindowViews::HasHighMediaEngagement(
+    const url::Origin& origin) const {
+  MediaEngagementService* service =
+      MediaEngagementService::Get(Profile::FromBrowserContext(
+          GetController()->GetWebContents()->GetBrowserContext()));
+  if (!service) {
+    return false;
+  }
+
+  return service->HasHighEngagement(origin);
+}
+
+bool VideoOverlayWindowViews::IsTrustedForMediaPlayback() const {
+  content::MediaSession* media_session =
+      content::MediaSession::GetIfExists(GetController()->GetWebContents());
+  if (!media_session) {
+    return false;
+  }
+
+  content::RenderFrameHost* rfh = media_session->GetRoutedFrame();
+  if (rfh == nullptr) {
+    return false;
+  }
+
+  if (!rfh->IsInPrimaryMainFrame()) {
+    return false;
+  }
+
+  const url::Origin origin = rfh->GetLastCommittedOrigin();
+  if (origin.GetURL().SchemeIsFile()) {
+    return true;
+  }
+
+  return HasHighMediaEngagement(origin);
+}
+
 #if BUILDFLAG(IS_CHROMEOS)
 int VideoOverlayWindowViews::GetResizeHTComponent() const {
   return resize_handle_view_->GetHTComponent();
diff --git a/chrome/browser/ui/views/overlay/video_overlay_window_views.h b/chrome/browser/ui/views/overlay/video_overlay_window_views.h
index 6a212596..070fd23 100644
--- a/chrome/browser/ui/views/overlay/video_overlay_window_views.h
+++ b/chrome/browser/ui/views/overlay/video_overlay_window_views.h
@@ -259,6 +259,8 @@
       content::VideoPictureInPictureWindowController* controller);
 
  private:
+  friend class VideoPictureInPictureWindowControllerBrowserTest;
+
   // Return the work area for the nearest display the widget is on.
   gfx::Rect GetWorkAreaForWindow() const;
 
@@ -360,6 +362,19 @@
   // Returns true if the title and top scrim are visible.
   bool AreTitleAndScrimVisible() const;
 
+  bool HasHighMediaEngagement(const url::Origin& origin) const;
+
+  // Returns true if the current Picture-in-Picture window is trusted for media
+  // playback.
+  //
+  // A Picture-in-Picture window is trusted for media playback if all of the
+  // following conditions are met:
+  //    * `MediaSession` exists
+  //    * `MediaSession` routed frame exists
+  //    * The `MediaSession` routed frame is the primary main frame
+  //    * The origin URL has high media engagement or is a file
+  bool IsTrustedForMediaPlayback() const;
+
   // Not owned; |controller_| owns |this|.
   raw_ptr<content::VideoPictureInPictureWindowController> controller_;
 
diff --git a/chrome/browser/ui/views/profiles/profile_menu_view.cc b/chrome/browser/ui/views/profiles/profile_menu_view.cc
index 950c6d3..56e5be5bbd 100644
--- a/chrome/browser/ui/views/profiles/profile_menu_view.cc
+++ b/chrome/browser/ui/views/profiles/profile_menu_view.cc
@@ -51,6 +51,7 @@
 #include "chrome/browser/ui/browser_navigator_params.h"
 #include "chrome/browser/ui/browser_window/public/browser_window_features.h"
 #include "chrome/browser/ui/color/chrome_color_id.h"
+#include "chrome/browser/ui/hats/survey_config.h"
 #include "chrome/browser/ui/managed_ui.h"
 #include "chrome/browser/ui/passwords/ui_utils.h"
 #include "chrome/browser/ui/profiles/profile_colors_util.h"
@@ -451,7 +452,12 @@
 
   if (!web_app::AppBrowserController::IsWebApp(&browser())) {
     GetWidget()->CloseWithReason(views::Widget::ClosedReason::kUnspecified);
-    profiles::SwitchToProfile(profile_path, /*always_create=*/false);
+    // Switch to the selected profile and launch a HaTS survey for the
+    // associated non-webapp browser.
+    profiles::SwitchToProfile(
+        profile_path, /*always_create=*/false,
+        base::BindOnce(&profiles::LaunchSigninHatsSurveyForBrowser,
+                       kHatsSurveyTriggerIdentitySwitchProfileFromProfileMenu));
   } else {
     // Open the same web app for another profile.
     // On non-macOS the only allowlisted case is PasswordManager WebApp, which
diff --git a/chrome/browser/ui/views/profiles/profile_menu_view_browsertest.cc b/chrome/browser/ui/views/profiles/profile_menu_view_browsertest.cc
index be9c93f..51f74b9 100644
--- a/chrome/browser/ui/views/profiles/profile_menu_view_browsertest.cc
+++ b/chrome/browser/ui/views/profiles/profile_menu_view_browsertest.cc
@@ -55,6 +55,9 @@
 #include "chrome/browser/ui/browser_finder.h"
 #include "chrome/browser/ui/browser_list.h"
 #include "chrome/browser/ui/browser_window/public/browser_window_features.h"
+#include "chrome/browser/ui/hats/hats_service_factory.h"
+#include "chrome/browser/ui/hats/mock_hats_service.h"
+#include "chrome/browser/ui/hats/survey_config.h"
 #include "chrome/browser/ui/signin/signin_view_controller.h"
 #include "chrome/browser/ui/tabs/tab_enums.h"
 #include "chrome/browser/ui/test/test_browser_dialog.h"
@@ -126,6 +129,7 @@
 #endif  // BUILDFLAG(ENABLE_DICE_SUPPORT)
 
 namespace {
+using testing::_;
 using ::testing::StrictMock;
 
 constexpr char kTestEmail[] = "foo@example.com";
@@ -1616,6 +1620,65 @@
 }
 #endif
 
+#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX)
+class ProfileMenuHatsSurveyTest : public ProfileMenuViewTestBase,
+                                  public InProcessBrowserTest {
+ private:
+  base::test::ScopedFeatureList feature_list_{
+      switches::kChromeIdentitySurveySwitchProfileFromProfileMenu};
+};
+
+// List of actionable items in the correct order as they appear in the menu. If
+// a new button is added to the menu, it should also be added to this list.
+// This list is not for setting up a click test, and rather for anchoring the
+// "Other Profile" item for selection.
+constexpr std::array kActionableItems_WithAnotherProfile = {
+    ProfileMenuViewBase::ActionableItem::kSigninButton,
+    ProfileMenuViewBase::ActionableItem::kAutofillSettingsButton,
+    ProfileMenuViewBase::ActionableItem::kEditProfileButton,
+    ProfileMenuViewBase::ActionableItem::kExitProfileButton,
+    ProfileMenuViewBase::ActionableItem::kOtherProfileButton,
+    ProfileMenuViewBase::ActionableItem::kAddNewProfileButton,
+    ProfileMenuViewBase::ActionableItem::kGuestProfileButton,
+    ProfileMenuViewBase::ActionableItem::kManageProfilesButton,
+    // The first button is added again to finish the cycle and test that
+    // there are no other buttons at the end.
+    ProfileMenuViewBase::ActionableItem::kSigninButton};
+
+IN_PROC_BROWSER_TEST_F(ProfileMenuHatsSurveyTest,
+                       SurveyLaunchedOnSwitchingProfile) {
+  // Setup a new profile and its mock HatsService.
+  Profile* other_profile = CreateAdditionalProfile();
+  MockHatsService* hats_service = static_cast<MockHatsService*>(
+      HatsServiceFactory::GetInstance()->SetTestingFactoryAndUse(
+          other_profile, base::BindRepeating(&BuildMockHatsService)));
+  Browser::Create(Browser::CreateParams(other_profile, /*user_gesture=*/true));
+
+  // The survey should be launched for the other profile after switching.
+  EXPECT_CALL(
+      *hats_service,
+      LaunchSurvey(kHatsSurveyTriggerIdentitySwitchProfileFromProfileMenu, _, _,
+                   _, _, _, _));
+
+  // Open the profile menu and select the other profile.
+  SetTargetBrowser(browser());
+  OpenProfileMenu();
+  ASSERT_TRUE(profile_menu_view());
+  for (const auto& item : kActionableItems_WithAnotherProfile) {
+    if (item == ProfileMenuViewBase::ActionableItem::kOtherProfileButton) {
+      break;
+    }
+    profile_menu_view()->GetFocusManager()->AdvanceFocus(/*reverse=*/false);
+  }
+  views::View* focused_item =
+      profile_menu_view()->GetFocusManager()->GetFocusedView();
+  ASSERT_TRUE(focused_item);
+  Click(focused_item);
+  base::RunLoop().RunUntilIdle();
+}
+
+#endif  // BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX)
+
 #if !BUILDFLAG(IS_CHROMEOS)
 class ProfileMenuViewWebAppTest : public ProfileMenuViewTestBase,
                                   public web_app::WebAppBrowserTestBase {
diff --git a/chrome/browser/ui/views/profiles/profile_picker_flow_controller.cc b/chrome/browser/ui/views/profiles/profile_picker_flow_controller.cc
index 4664df64..9e59154 100644
--- a/chrome/browser/ui/views/profiles/profile_picker_flow_controller.cc
+++ b/chrome/browser/ui/views/profiles/profile_picker_flow_controller.cc
@@ -735,6 +735,13 @@
   if (profile->IsGuestSession()) {
     RecordProfilePickerAction(ProfilePickerAction::kLaunchGuestProfile);
   } else {
+    // Launch a HaTS survey if the user intentionally switched profiles via the
+    // profile picker accessed from the profile menu. This excludes first-run or
+    // startup scenarios.
+    if (entry_point_ == ProfilePicker::EntryPoint::kProfileMenuManageProfiles) {
+      profiles::LaunchSigninHatsSurveyForBrowser(
+          kHatsSurveyTriggerIdentitySwitchProfileFromProfilePicker, browser);
+    }
     RecordProfilePickerAction(
         open_settings
             ? ProfilePickerAction::kLaunchExistingProfileCustomizeSettings
diff --git a/chrome/browser/ui/views/profiles/profile_picker_view_browsertest.cc b/chrome/browser/ui/views/profiles/profile_picker_view_browsertest.cc
index 90f5d83..754b3743 100644
--- a/chrome/browser/ui/views/profiles/profile_picker_view_browsertest.cc
+++ b/chrome/browser/ui/views/profiles/profile_picker_view_browsertest.cc
@@ -63,6 +63,9 @@
 #include "chrome/browser/ui/browser_finder.h"
 #include "chrome/browser/ui/browser_list.h"
 #include "chrome/browser/ui/browser_window/public/browser_window_features.h"
+#include "chrome/browser/ui/hats/hats_service_factory.h"
+#include "chrome/browser/ui/hats/mock_hats_service.h"
+#include "chrome/browser/ui/hats/survey_config.h"
 #include "chrome/browser/ui/profiles/profile_customization_util.h"
 #include "chrome/browser/ui/profiles/profile_ui_test_utils.h"
 #include "chrome/browser/ui/signin/signin_view_controller.h"
@@ -105,6 +108,7 @@
 #include "components/signin/public/base/signin_buildflags.h"
 #include "components/signin/public/base/signin_metrics.h"
 #include "components/signin/public/base/signin_pref_names.h"
+#include "components/signin/public/base/signin_switches.h"
 #include "components/signin/public/identity_manager/account_capabilities.h"
 #include "components/signin/public/identity_manager/account_capabilities_test_mutator.h"
 #include "components/signin/public/identity_manager/account_info.h"
@@ -156,6 +160,7 @@
 #endif
 
 using signin::constants::kNoHostedDomainFound;
+using testing::_;
 
 namespace {
 const SkColor kProfileColor = SK_ColorRED;
@@ -667,6 +672,8 @@
   std::unique_ptr<policy::ScopedManagementServiceOverrideForTesting>
       platform_management_;
 #endif
+  base::test::ScopedFeatureList feature_list_{
+      switches::kChromeIdentitySurveySwitchProfileFromProfilePicker};
 };
 
 IN_PROC_BROWSER_TEST_F(ProfilePickerCreationFlowBrowserTest, ShowPicker) {
@@ -1150,7 +1157,7 @@
       ProfilePicker::Params::ForGlicManager(mock_callback.Get()));
   WaitForPickerClosedAndReopenedImmediately();
 
-  EXPECT_CALL(mock_callback, Run(testing::_)).Times(0);
+  EXPECT_CALL(mock_callback, Run(_)).Times(0);
   ASSERT_TRUE(ProfilePicker::IsOpen());
   ASSERT_FALSE(IsForceSigninErrorDialogShown());
 
@@ -2032,6 +2039,19 @@
   ASSERT_EQ(1u, BrowserList::GetInstance()->size());
   // Create a second profile.
   base::FilePath other_path = CreateNewProfileWithoutBrowser();
+
+  ProfileManager* profile_manager = g_browser_process->profile_manager();
+  Profile* other_profile = profile_manager->GetProfile(other_path);
+  MockHatsService* hats_service = static_cast<MockHatsService*>(
+      HatsServiceFactory::GetInstance()->SetTestingFactoryAndUse(
+          other_profile, base::BindRepeating(&BuildMockHatsService)));
+
+  EXPECT_CALL(
+      *hats_service,
+      LaunchSurvey(kHatsSurveyTriggerIdentitySwitchProfileFromProfilePicker, _,
+                   _, _, _, _, _))
+      .Times(2);
+
   // Open the picker.
   ProfilePicker::Show(ProfilePicker::Params::FromEntryPoint(
       ProfilePicker::EntryPoint::kProfileMenuManageProfiles));
@@ -3227,7 +3247,7 @@
 
   // Open the picker with Glic version.
   base::MockCallback<base::OnceCallback<void(Profile*)>> picked_profile_mock;
-  EXPECT_CALL(picked_profile_mock, Run(testing::_))
+  EXPECT_CALL(picked_profile_mock, Run(_))
       .WillOnce([&new_profile_path](Profile* profile) {
         EXPECT_TRUE(profile);
         EXPECT_EQ(profile->GetPath(), new_profile_path);
diff --git a/chrome/browser/ui/views/side_panel/BUILD.gn b/chrome/browser/ui/views/side_panel/BUILD.gn
index d8f35173..f423e44 100644
--- a/chrome/browser/ui/views/side_panel/BUILD.gn
+++ b/chrome/browser/ui/views/side_panel/BUILD.gn
@@ -14,6 +14,8 @@
   sources = [
     "bookmarks/bookmarks_side_panel_coordinator.cc",
     "bookmarks/bookmarks_side_panel_coordinator.h",
+    "comments/comments_side_panel_coordinator.cc",
+    "comments/comments_side_panel_coordinator.h",
     "customize_chrome/customize_chrome_utils.cc",
     "customize_chrome/customize_chrome_utils.h",
     "customize_chrome/side_panel_controller_views.cc",
diff --git a/chrome/browser/ui/views/side_panel/comments/comments_side_panel_coordinator.cc b/chrome/browser/ui/views/side_panel/comments/comments_side_panel_coordinator.cc
new file mode 100644
index 0000000..1363441
--- /dev/null
+++ b/chrome/browser/ui/views/side_panel/comments/comments_side_panel_coordinator.cc
@@ -0,0 +1,50 @@
+// Copyright 2025 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ui/views/side_panel/comments/comments_side_panel_coordinator.h"
+
+#include "base/functional/callback.h"
+#include "chrome/browser/ui/browser_window/public/browser_window_interface.h"
+#include "chrome/browser/ui/views/side_panel/side_panel_coordinator.h"
+#include "chrome/browser/ui/views/side_panel/side_panel_entry.h"
+#include "chrome/browser/ui/views/side_panel/side_panel_registry.h"
+#include "chrome/browser/ui/views/side_panel/side_panel_web_ui_view.h"
+#include "chrome/browser/ui/webui/side_panel/comments/comments_side_panel_ui.h"
+#include "chrome/common/webui_url_constants.h"
+#include "components/collaboration/public/features.h"
+#include "components/strings/grit/components_strings.h"
+#include "ui/base/metadata/metadata_impl_macros.h"
+
+using SidePanelWebUIViewT_CommentsSidePanelUI =
+    SidePanelWebUIViewT<CommentsSidePanelUI>;
+BEGIN_TEMPLATE_METADATA(SidePanelWebUIViewT_CommentsSidePanelUI,
+                        SidePanelWebUIViewT)
+END_METADATA
+
+// static
+bool CommentsSidePanelCoordinator::IsSupported() {
+  return base::FeatureList::IsEnabled(
+      collaboration::features::kCollaborationComments);
+}
+
+void CommentsSidePanelCoordinator::CreateAndRegisterEntry(
+    SidePanelRegistry* global_registry) {
+  global_registry->Register(std::make_unique<SidePanelEntry>(
+      SidePanelEntry::Key(SidePanelEntry::Id::kComments),
+      base::BindRepeating(&CommentsSidePanelCoordinator::CreateCommentsWebView,
+                          base::Unretained(this)),
+      SidePanelEntry::kSidePanelDefaultContentWidth));
+}
+
+std::unique_ptr<views::View>
+CommentsSidePanelCoordinator::CreateCommentsWebView(
+    SidePanelEntryScope& scope) {
+  return std::make_unique<SidePanelWebUIViewT<CommentsSidePanelUI>>(
+      scope, base::RepeatingClosure(), base::RepeatingClosure(),
+      std::make_unique<WebUIContentsWrapperT<CommentsSidePanelUI>>(
+          GURL(chrome::kChromeUICommentsSidePanelURL),
+          scope.GetBrowserWindowInterface().GetProfile(),
+          IDS_COLLABORATION_SHARED_TAB_GROUPS_COMMENTS_TITLE,
+          /*esc_closes_ui=*/false));
+}
diff --git a/chrome/browser/ui/views/side_panel/comments/comments_side_panel_coordinator.h b/chrome/browser/ui/views/side_panel/comments/comments_side_panel_coordinator.h
new file mode 100644
index 0000000..5aae9911
--- /dev/null
+++ b/chrome/browser/ui/views/side_panel/comments/comments_side_panel_coordinator.h
@@ -0,0 +1,41 @@
+// 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_VIEWS_SIDE_PANEL_COMMENTS_COMMENTS_SIDE_PANEL_COORDINATOR_H_
+#define CHROME_BROWSER_UI_VIEWS_SIDE_PANEL_COMMENTS_COMMENTS_SIDE_PANEL_COORDINATOR_H_
+
+#include <memory>
+
+#include "base/memory/raw_ptr.h"
+
+class SidePanelEntryScope;
+class SidePanelRegistry;
+
+namespace views {
+class View;
+}
+
+// CommentsSidePanelCoordinator handles the creation and registration of
+// the comments SidePanelEntry.
+class CommentsSidePanelCoordinator {
+ public:
+  CommentsSidePanelCoordinator() = default;
+  ~CommentsSidePanelCoordinator() = default;
+
+  // Returns whether CommentsSidePanelCoordinator is supported.
+  // If this returns false, it should not be registered with the side
+  // panel registry.
+  static bool IsSupported();
+
+  // Creates and registers the comments side panel entry in the global registry.
+  void CreateAndRegisterEntry(SidePanelRegistry* global_registry);
+
+ private:
+  // Creates the comments web view that will be used as the content of the
+  // comments side panel entry.
+  std::unique_ptr<views::View> CreateCommentsWebView(
+      SidePanelEntryScope& scope);
+};
+
+#endif  // CHROME_BROWSER_UI_VIEWS_SIDE_PANEL_COMMENTS_COMMENTS_SIDE_PANEL_COORDINATOR_H_
diff --git a/chrome/browser/ui/views/side_panel/side_panel_entry_id.h b/chrome/browser/ui/views/side_panel/side_panel_entry_id.h
index a9232ea..329c3cf 100644
--- a/chrome/browser/ui/views/side_panel/side_panel_entry_id.h
+++ b/chrome/browser/ui/views/side_panel/side_panel_entry_id.h
@@ -39,6 +39,7 @@
   V(kLensOverlayResults, kActionSidePanelShowLensOverlayResults,              \
     "LensOverlayResults")                                                     \
   V(kMerchantTrust, kActionSidePanelShowMerchantTrust, "MerchantTrust")       \
+  V(kComments, kActionSidePanelShowComments, "Comments")                      \
   /* Extensions (nothing more should be added below here) */                  \
   V(kExtension, std::nullopt, "Extension")
 
diff --git a/chrome/browser/ui/views/side_panel/side_panel_util.cc b/chrome/browser/ui/views/side_panel/side_panel_util.cc
index 1c5984f..bd22b52 100644
--- a/chrome/browser/ui/views/side_panel/side_panel_util.cc
+++ b/chrome/browser/ui/views/side_panel/side_panel_util.cc
@@ -15,6 +15,7 @@
 #include "chrome/browser/ui/browser_window/public/browser_window_features.h"
 #include "chrome/browser/ui/ui_features.h"
 #include "chrome/browser/ui/views/side_panel/bookmarks/bookmarks_side_panel_coordinator.h"
+#include "chrome/browser/ui/views/side_panel/comments/comments_side_panel_coordinator.h"
 #include "chrome/browser/ui/views/side_panel/extensions/extension_side_panel_manager.h"
 #include "chrome/browser/ui/views/side_panel/history/history_side_panel_coordinator.h"
 #include "chrome/browser/ui/views/side_panel/history_clusters/history_clusters_side_panel_coordinator.h"
@@ -56,6 +57,13 @@
         ->history_side_panel_coordinator()
         ->CreateAndRegisterEntry(window_registry);
   }
+
+  // Add comments.
+  if (CommentsSidePanelCoordinator::IsSupported()) {
+    browser->browser_window_features()
+        ->comments_side_panel_coordinator()
+        ->CreateAndRegisterEntry(window_registry);
+  }
 }
 
 SidePanelContentProxy* SidePanelUtil::GetSidePanelContentProxy(
diff --git a/chrome/browser/ui/webui/BUILD.gn b/chrome/browser/ui/webui/BUILD.gn
index d768851..468dbf1 100644
--- a/chrome/browser/ui/webui/BUILD.gn
+++ b/chrome/browser/ui/webui/BUILD.gn
@@ -112,6 +112,10 @@
   if (enable_glic) {
     deps += [ "//chrome/browser/glic" ]
   }
+
+  if (enable_session_service) {
+    deps += [ "//chrome/browser/ui/webui/tab_strip_internals" ]
+  }
 }
 
 source_set("webui") {
diff --git a/chrome/browser/ui/webui/chrome_web_ui_configs.cc b/chrome/browser/ui/webui/chrome_web_ui_configs.cc
index 5de663c..245a4c9a 100644
--- a/chrome/browser/ui/webui/chrome_web_ui_configs.cc
+++ b/chrome/browser/ui/webui/chrome_web_ui_configs.cc
@@ -90,6 +90,7 @@
 #include "chrome/browser/ui/webui/inspect/inspect_ui.h"
 #if BUILDFLAG(ENABLE_SESSION_SERVICE)
 #include "chrome/browser/ui/webui/internals/internals_ui.h"
+#include "chrome/browser/ui/webui/tab_strip_internals/tab_strip_internals_ui.h"
 #endif  // BUILDFLAG(ENABLE_SESSION_SERVICE)
 #include "chrome/browser/ui/webui/management/management_ui.h"
 #include "chrome/browser/ui/webui/media_router/media_router_internals_ui.h"
@@ -309,6 +310,7 @@
   map.AddWebUIConfig(std::make_unique<InspectUIConfig>());
 #if BUILDFLAG(ENABLE_SESSION_SERVICE)
   map.AddWebUIConfig(std::make_unique<InternalsUIConfig>());
+  map.AddWebUIConfig(std::make_unique<TabStripInternalsUIConfig>());
 #endif  // BUILDFLAG(ENABLE_SESSION_SERVICE)
   map.AddWebUIConfig(std::make_unique<ManagementUIConfig>());
   map.AddWebUIConfig(
diff --git a/chrome/browser/ui/webui/new_tab_page/composebox/composebox_handler.cc b/chrome/browser/ui/webui/new_tab_page/composebox/composebox_handler.cc
index c66e895..c4f6374 100644
--- a/chrome/browser/ui/webui/new_tab_page/composebox/composebox_handler.cc
+++ b/chrome/browser/ui/webui/new_tab_page/composebox/composebox_handler.cc
@@ -30,6 +30,7 @@
 }
 
 void ComposeboxHandler::NotifySessionAbandoned() {
+  query_controller_->ClearFiles();
   query_controller_->NotifySessionAbandoned();
 }
 
diff --git a/chrome/browser/ui/webui/on_device_internals/on_device_internals_page_handler.cc b/chrome/browser/ui/webui/on_device_internals/on_device_internals_page_handler.cc
index 2d8014d..dafe8efd 100644
--- a/chrome/browser/ui/webui/on_device_internals/on_device_internals_page_handler.cc
+++ b/chrome/browser/ui/webui/on_device_internals/on_device_internals_page_handler.cc
@@ -30,6 +30,7 @@
 #include "services/data_decoder/public/cpp/decode_image.h"
 #include "services/on_device_model/ml/performance_class.h"
 #include "services/on_device_model/public/cpp/buildflags.h"
+#include "services/on_device_model/public/cpp/features.h"
 #include "services/on_device_model/public/cpp/model_assets.h"
 #include "services/preferences/public/cpp/dictionary_value_update.h"
 #include "services/preferences/public/cpp/scoped_pref_update.h"
@@ -60,7 +61,8 @@
     model_paths.weights = model_path;
   }
 
-  if (optimization_guide::features::ForceCpuBackendForOnDeviceModel()) {
+  if (base::FeatureList::IsEnabled(
+          on_device_model::features::kOnDeviceModelForceCpuBackend)) {
     model_paths.cache =
         model_paths.weights.AddExtension(FILE_PATH_LITERAL("cache"));
   }
@@ -220,7 +222,8 @@
   auto params = on_device_model::mojom::LoadModelParams::New();
   params->assets = std::move(assets);
   params->backend_type =
-      optimization_guide::features::ForceCpuBackendForOnDeviceModel()
+      base::FeatureList::IsEnabled(
+          on_device_model::features::kOnDeviceModelForceCpuBackend)
           ? ml::ModelBackendType::kCpuBackend
           : ml::ModelBackendType::kGpuBackend;
   params->max_tokens = 4096;
diff --git a/chrome/browser/ui/webui/side_panel/comments/BUILD.gn b/chrome/browser/ui/webui/side_panel/comments/BUILD.gn
new file mode 100644
index 0000000..e93df73
--- /dev/null
+++ b/chrome/browser/ui/webui/side_panel/comments/BUILD.gn
@@ -0,0 +1,16 @@
+# 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")
+
+assert(!is_android)
+
+mojom("mojo_bindings") {
+  sources = [ "comments.mojom" ]
+  webui_module_path = "/"
+  public_deps = [
+    "//mojo/public/mojom/base",
+    "//ui/base/mojom",
+  ]
+}
diff --git a/chrome/browser/ui/webui/side_panel/comments/comments.mojom b/chrome/browser/ui/webui/side_panel/comments/comments.mojom
new file mode 100644
index 0000000..b5713afd
--- /dev/null
+++ b/chrome/browser/ui/webui/side_panel/comments/comments.mojom
@@ -0,0 +1,21 @@
+// 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 comments.mojom;
+
+// Used by the WebUI page to bootstrap bidirectional communication.
+interface PageHandlerFactory {
+  // The WebUI calls this method when the page is first initialized.
+  CreatePageHandler(
+      pending_remote<Page> page, pending_receiver<PageHandler> handler);
+};
+
+// Browser-side handler for requests from WebUI page.
+interface PageHandler {
+  // Notify the backend that the UI is ready to be shown.
+  ShowUI();
+};
+
+// WebUI-side handler for requests from the browser.
+interface Page {};
diff --git a/chrome/browser/ui/webui/side_panel/comments/comments_page_handler.cc b/chrome/browser/ui/webui/side_panel/comments/comments_page_handler.cc
new file mode 100644
index 0000000..a1e5431
--- /dev/null
+++ b/chrome/browser/ui/webui/side_panel/comments/comments_page_handler.cc
@@ -0,0 +1,28 @@
+// Copyright 2025 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ui/webui/side_panel/comments/comments_page_handler.h"
+
+#include "chrome/browser/ui/webui/side_panel/comments/comments.mojom.h"
+#include "chrome/browser/ui/webui/side_panel/comments/comments_side_panel_ui.h"
+#include "chrome/browser/ui/webui/webui_embedding_context.h"
+
+CommentsPageHandler::CommentsPageHandler(
+    mojo::PendingReceiver<comments::mojom::PageHandler> receiver,
+    mojo::PendingRemote<comments::mojom::Page> page,
+    CommentsSidePanelUI& comments_ui,
+    content::WebUI& web_ui)
+    : receiver_(this, std::move(receiver)),
+      page_(std::move(page)),
+      web_ui_(web_ui),
+      comments_ui_(comments_ui) {}
+
+CommentsPageHandler::~CommentsPageHandler() = default;
+
+void CommentsPageHandler::ShowUI() {
+  auto embedder = comments_ui_->embedder();
+  if (embedder) {
+    embedder->ShowUI();
+  }
+}
diff --git a/chrome/browser/ui/webui/side_panel/comments/comments_page_handler.h b/chrome/browser/ui/webui/side_panel/comments/comments_page_handler.h
new file mode 100644
index 0000000..8848f0d
--- /dev/null
+++ b/chrome/browser/ui/webui/side_panel/comments/comments_page_handler.h
@@ -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.
+
+#ifndef CHROME_BROWSER_UI_WEBUI_SIDE_PANEL_COMMENTS_COMMENTS_PAGE_HANDLER_H_
+#define CHROME_BROWSER_UI_WEBUI_SIDE_PANEL_COMMENTS_COMMENTS_PAGE_HANDLER_H_
+
+#include "base/memory/raw_ref.h"
+#include "chrome/browser/ui/webui/side_panel/comments/comments.mojom.h"
+#include "mojo/public/cpp/bindings/pending_receiver.h"
+#include "mojo/public/cpp/bindings/receiver.h"
+#include "mojo/public/cpp/bindings/remote.h"
+
+class CommentsSidePanelUI;
+class BrowserWindowInterface;
+
+namespace content {
+class WebUI;
+}
+
+// Page handler for the comments side panel WebUI.
+class CommentsPageHandler : public comments::mojom::PageHandler {
+ public:
+  explicit CommentsPageHandler(
+      mojo::PendingReceiver<comments::mojom::PageHandler> receiver,
+      mojo::PendingRemote<comments::mojom::Page> page,
+      CommentsSidePanelUI& comments_ui,
+      content::WebUI& web_ui);
+  CommentsPageHandler(const CommentsPageHandler&) = delete;
+  CommentsPageHandler& operator=(const PageHandler&) = delete;
+  ~CommentsPageHandler() override;
+
+  // comments::mojom::PageHandler:
+  // Shows the comments side panel UI.
+  void ShowUI() override;
+
+ private:
+  mojo::Receiver<comments::mojom::PageHandler> receiver_;
+  mojo::Remote<comments::mojom::Page> page_;
+  const raw_ref<content::WebUI> web_ui_;
+  const raw_ref<CommentsSidePanelUI> comments_ui_;
+};
+
+#endif  // CHROME_BROWSER_UI_WEBUI_SIDE_PANEL_COMMENTS_COMMENTS_PAGE_HANDLER_H_
diff --git a/chrome/browser/ui/webui/side_panel/comments/comments_side_panel_ui.cc b/chrome/browser/ui/webui/side_panel/comments/comments_side_panel_ui.cc
index 547abe4..206b460 100644
--- a/chrome/browser/ui/webui/side_panel/comments/comments_side_panel_ui.cc
+++ b/chrome/browser/ui/webui/side_panel/comments/comments_side_panel_ui.cc
@@ -5,7 +5,10 @@
 #include "chrome/browser/ui/webui/side_panel/comments/comments_side_panel_ui.h"
 
 #include "base/feature_list.h"
+#include "chrome/app/chrome_command_ids.h"
 #include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/ui/webui/side_panel/comments/comments.mojom.h"
+#include "chrome/browser/ui/webui/side_panel/comments/comments_page_handler.h"
 #include "chrome/common/webui_url_constants.h"
 #include "chrome/grit/side_panel_comments_resources.h"
 #include "chrome/grit/side_panel_comments_resources_map.h"
@@ -25,6 +28,10 @@
       collaboration::features::kCollaborationComments);
 }
 
+std::optional<int> CommentsSidePanelUIConfig::GetCommandIdForTesting() {
+  return IDC_SHOW_COMMENTS_SIDE_PANEL;
+}
+
 CommentsSidePanelUI::CommentsSidePanelUI(content::WebUI* web_ui)
     : TopChromeWebUIController(web_ui, true) {
   Profile* const profile = Profile::FromWebUI(web_ui);
@@ -37,3 +44,16 @@
 CommentsSidePanelUI::~CommentsSidePanelUI() = default;
 
 WEB_UI_CONTROLLER_TYPE_IMPL(CommentsSidePanelUI)
+
+void CommentsSidePanelUI::BindInterface(
+    mojo::PendingReceiver<comments::mojom::PageHandlerFactory> receiver) {
+  comments_page_factory_receiver_.reset();
+  comments_page_factory_receiver_.Bind(std::move(receiver));
+}
+
+void CommentsSidePanelUI::CreatePageHandler(
+    mojo::PendingRemote<comments::mojom::Page> page,
+    mojo::PendingReceiver<comments::mojom::PageHandler> receiver) {
+  comments_page_handler_ = std::make_unique<CommentsPageHandler>(
+      std::move(receiver), std::move(page), *this, *web_ui());
+}
diff --git a/chrome/browser/ui/webui/side_panel/comments/comments_side_panel_ui.h b/chrome/browser/ui/webui/side_panel/comments/comments_side_panel_ui.h
index c5d0dbe2..1494e3e 100644
--- a/chrome/browser/ui/webui/side_panel/comments/comments_side_panel_ui.h
+++ b/chrome/browser/ui/webui/side_panel/comments/comments_side_panel_ui.h
@@ -5,10 +5,15 @@
 #ifndef CHROME_BROWSER_UI_WEBUI_SIDE_PANEL_COMMENTS_COMMENTS_SIDE_PANEL_UI_H_
 #define CHROME_BROWSER_UI_WEBUI_SIDE_PANEL_COMMENTS_COMMENTS_SIDE_PANEL_UI_H_
 
+#include "chrome/browser/ui/webui/side_panel/comments/comments.mojom.h"
 #include "chrome/browser/ui/webui/top_chrome/top_chrome_web_ui_controller.h"
 #include "chrome/browser/ui/webui/top_chrome/top_chrome_webui_config.h"
 #include "content/public/browser/webui_config.h"
+#include "mojo/public/cpp/bindings/pending_receiver.h"
+#include "mojo/public/cpp/bindings/pending_remote.h"
+#include "mojo/public/cpp/bindings/receiver.h"
 
+class CommentsPageHandler;
 class CommentsSidePanelUI;
 
 class CommentsSidePanelUIConfig
@@ -16,9 +21,11 @@
  public:
   CommentsSidePanelUIConfig();
   bool IsWebUIEnabled(content::BrowserContext* browser_context) override;
+  std::optional<int> GetCommandIdForTesting() override;
 };
 
-class CommentsSidePanelUI : public TopChromeWebUIController {
+class CommentsSidePanelUI : public TopChromeWebUIController,
+                            public comments::mojom::PageHandlerFactory {
  public:
   explicit CommentsSidePanelUI(content::WebUI* web_ui);
   CommentsSidePanelUI(const CommentsSidePanelUI&) = delete;
@@ -29,8 +36,20 @@
     return "CommentsSidePanel";
   }
 
+  void BindInterface(
+      mojo::PendingReceiver<comments::mojom::PageHandlerFactory> receiver);
+
  private:
   WEB_UI_CONTROLLER_TYPE_DECL();
+
+  // comments::mojom::PageHandlerFactory:
+  void CreatePageHandler(
+      mojo::PendingRemote<comments::mojom::Page> page,
+      mojo::PendingReceiver<comments::mojom::PageHandler> receiver) override;
+
+  std::unique_ptr<CommentsPageHandler> comments_page_handler_;
+  mojo::Receiver<comments::mojom::PageHandlerFactory>
+      comments_page_factory_receiver_{this};
 };
 
 #endif  // CHROME_BROWSER_UI_WEBUI_SIDE_PANEL_COMMENTS_COMMENTS_SIDE_PANEL_UI_H_
diff --git a/chrome/browser/ui/webui/tab_strip_internals/BUILD.gn b/chrome/browser/ui/webui/tab_strip_internals/BUILD.gn
new file mode 100644
index 0000000..90175336
--- /dev/null
+++ b/chrome/browser/ui/webui/tab_strip_internals/BUILD.gn
@@ -0,0 +1,22 @@
+# Copyright 2025 The Chromium Authors
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import("//chrome/common/features.gni")
+
+assert(enable_session_service)
+
+source_set("tab_strip_internals") {
+  sources = [
+    "tab_strip_internals_ui.cc",
+    "tab_strip_internals_ui.h",
+  ]
+
+  deps = [
+    "//chrome/browser/resources/tab_strip_internals:resources_grit",
+    "//chrome/browser/ui/tabs",
+    "//chrome/common",
+    "//content/public/browser",
+    "//ui/webui",
+  ]
+}
diff --git a/chrome/browser/ui/webui/tab_strip_internals/DIR_METADATA b/chrome/browser/ui/webui/tab_strip_internals/DIR_METADATA
new file mode 100644
index 0000000..e72bd16
--- /dev/null
+++ b/chrome/browser/ui/webui/tab_strip_internals/DIR_METADATA
@@ -0,0 +1,4 @@
+team_email: "top-chrome-desktop-ui@google.com"
+buganizer_public: {
+  component_id: 1456118
+}
diff --git a/chrome/browser/ui/webui/tab_strip_internals/OWNERS b/chrome/browser/ui/webui/tab_strip_internals/OWNERS
new file mode 100644
index 0000000..f70d2eae
--- /dev/null
+++ b/chrome/browser/ui/webui/tab_strip_internals/OWNERS
@@ -0,0 +1,6 @@
+# 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.
+
+dljames@google.com
+shibalik@google.com
\ No newline at end of file
diff --git a/chrome/browser/ui/webui/tab_strip_internals/tab_strip_internals_ui.cc b/chrome/browser/ui/webui/tab_strip_internals/tab_strip_internals_ui.cc
new file mode 100644
index 0000000..5a6deb2f
--- /dev/null
+++ b/chrome/browser/ui/webui/tab_strip_internals/tab_strip_internals_ui.cc
@@ -0,0 +1,34 @@
+// Copyright 2025 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ui/webui/tab_strip_internals/tab_strip_internals_ui.h"
+
+#include "chrome/browser/ui/tabs/features.h"
+#include "chrome/common/webui_url_constants.h"
+#include "chrome/grit/tab_strip_internals_resources.h"
+#include "chrome/grit/tab_strip_internals_resources_map.h"
+#include "content/public/browser/web_contents.h"
+#include "content/public/browser/web_ui.h"
+#include "content/public/browser/web_ui_data_source.h"
+#include "ui/webui/webui_util.h"
+
+TabStripInternalsUI::TabStripInternalsUI(content::WebUI* web_ui)
+    : content::WebUIController(web_ui) {
+  // Setup up the chrome://tab-strip-internals source
+  content::WebUIDataSource* source = content::WebUIDataSource::CreateAndAdd(
+      web_ui->GetWebContents()->GetBrowserContext(),
+      chrome::kChromeUITabStripInternalsHost);
+
+  // Add required resources
+  webui::SetupWebUIDataSource(source, kTabStripInternalsResources,
+                              IDR_TAB_STRIP_INTERNALS_TAB_STRIP_INTERNALS_HTML);
+}
+
+TabStripInternalsUI::~TabStripInternalsUI() = default;
+
+bool TabStripInternalsUIConfig::IsWebUIEnabled(
+    content::BrowserContext* browser_context) {
+  return DefaultInternalWebUIConfig::IsWebUIEnabled(browser_context) &&
+         base::FeatureList::IsEnabled(tabs::kDebugUITabStrip);
+}
diff --git a/chrome/browser/ui/webui/tab_strip_internals/tab_strip_internals_ui.h b/chrome/browser/ui/webui/tab_strip_internals/tab_strip_internals_ui.h
new file mode 100644
index 0000000..737bc5a
--- /dev/null
+++ b/chrome/browser/ui/webui/tab_strip_internals/tab_strip_internals_ui.h
@@ -0,0 +1,32 @@
+// 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_TAB_STRIP_INTERNALS_TAB_STRIP_INTERNALS_UI_H_
+#define CHROME_BROWSER_UI_WEBUI_TAB_STRIP_INTERNALS_TAB_STRIP_INTERNALS_UI_H_
+
+#include "chrome/common/webui_url_constants.h"
+#include "content/public/browser/internal_webui_config.h"
+#include "content/public/browser/web_ui_controller.h"
+
+class TabStripInternalsUI;
+
+// Registers chrome://tab-strip-internals as a debug-only WebUI that
+// is conditionally enabled via the `kInternalOnlyUisEnabled` pref.
+class TabStripInternalsUIConfig
+    : public content::DefaultInternalWebUIConfig<TabStripInternalsUI> {
+ public:
+  TabStripInternalsUIConfig()
+      : DefaultInternalWebUIConfig(chrome::kChromeUITabStripInternalsHost) {}
+
+  bool IsWebUIEnabled(content::BrowserContext* browser_context) override;
+};
+
+// The Web UI controller for the chrome://tab-strip-internals page.
+class TabStripInternalsUI : public content::WebUIController {
+ public:
+  explicit TabStripInternalsUI(content::WebUI* web_ui);
+  ~TabStripInternalsUI() override;
+};
+
+#endif  // CHROME_BROWSER_UI_WEBUI_TAB_STRIP_INTERNALS_TAB_STRIP_INTERNALS_UI_H_
diff --git a/chrome/browser/web_applications/BUILD.gn b/chrome/browser/web_applications/BUILD.gn
index 79b587cd..a195efd 100644
--- a/chrome/browser/web_applications/BUILD.gn
+++ b/chrome/browser/web_applications/BUILD.gn
@@ -90,8 +90,8 @@
     "generated_icon_fix_manager.h",
     "generated_icon_fix_util.cc",
     "generated_icon_fix_util.h",
-    "icons/primary_icon_filter.cc",
-    "icons/primary_icon_filter.h",
+    "icons/trusted_icon_filter.cc",
+    "icons/trusted_icon_filter.h",
     "install_bounce_metric.cc",
     "install_bounce_metric.h",
     "isolated_web_apps/chrome_content_browser_client_isolated_web_apps_part.cc",
diff --git a/chrome/browser/web_applications/icons/primary_icon_filter.cc b/chrome/browser/web_applications/icons/trusted_icon_filter.cc
similarity index 97%
rename from chrome/browser/web_applications/icons/primary_icon_filter.cc
rename to chrome/browser/web_applications/icons/trusted_icon_filter.cc
index 71e97902..2ff42f3f 100644
--- a/chrome/browser/web_applications/icons/primary_icon_filter.cc
+++ b/chrome/browser/web_applications/icons/trusted_icon_filter.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "chrome/browser/web_applications/icons/primary_icon_filter.h"
+#include "chrome/browser/web_applications/icons/trusted_icon_filter.h"
 
 #include <optional>
 #include <vector>
@@ -31,7 +31,7 @@
 
 }  // namespace
 
-std::optional<apps::IconInfo> GetPrimaryIconsFromManifest(
+std::optional<apps::IconInfo> GetTrustedIconsFromManifest(
     const std::vector<blink::Manifest::ImageResource>& icons) {
   // Keep track of largest icon per purpose based on all the entries in the
   // manifest.
diff --git a/chrome/browser/web_applications/icons/primary_icon_filter.h b/chrome/browser/web_applications/icons/trusted_icon_filter.h
similarity index 71%
rename from chrome/browser/web_applications/icons/primary_icon_filter.h
rename to chrome/browser/web_applications/icons/trusted_icon_filter.h
index f445180..c5b0c3d 100644
--- a/chrome/browser/web_applications/icons/primary_icon_filter.h
+++ b/chrome/browser/web_applications/icons/trusted_icon_filter.h
@@ -2,12 +2,13 @@
 // 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_ICONS_PRIMARY_ICON_FILTER_H_
-#define CHROME_BROWSER_WEB_APPLICATIONS_ICONS_PRIMARY_ICON_FILTER_H_
+#ifndef CHROME_BROWSER_WEB_APPLICATIONS_ICONS_TRUSTED_ICON_FILTER_H_
+#define CHROME_BROWSER_WEB_APPLICATIONS_ICONS_TRUSTED_ICON_FILTER_H_
 
 #include <optional>
 #include <vector>
 
+#include "chrome/browser/web_applications/web_app_install_info.h"
 #include "components/services/app_service/public/cpp/icon_info.h"
 #include "third_party/blink/public/mojom/manifest/manifest.mojom.h"
 
@@ -18,9 +19,9 @@
 // while on other platforms, the icon of largest size is preferred. If no icons
 // are found, and only SVG icons of no size are available in the manifest, that
 // is the last fallback option, of size 512.
-std::optional<apps::IconInfo> GetPrimaryIconsFromManifest(
+std::optional<apps::IconInfo> GetTrustedIconsFromManifest(
     const std::vector<blink::Manifest::ImageResource>& icons);
 
 }  // namespace web_app
 
-#endif  // CHROME_BROWSER_WEB_APPLICATIONS_ICONS_PRIMARY_ICON_FILTER_H_
+#endif  // CHROME_BROWSER_WEB_APPLICATIONS_ICONS_TRUSTED_ICON_FILTER_H_
diff --git a/chrome/browser/web_applications/jobs/manifest_to_web_app_install_info_job.cc b/chrome/browser/web_applications/jobs/manifest_to_web_app_install_info_job.cc
index da4307a0..b969097 100644
--- a/chrome/browser/web_applications/jobs/manifest_to_web_app_install_info_job.cc
+++ b/chrome/browser/web_applications/jobs/manifest_to_web_app_install_info_job.cc
@@ -17,7 +17,7 @@
 #include "base/metrics/histogram_functions.h"
 #include "base/strings/utf_string_conversions.h"
 #include "base/synchronization/lock.h"
-#include "chrome/browser/web_applications/icons/primary_icon_filter.h"
+#include "chrome/browser/web_applications/icons/trusted_icon_filter.h"
 #include "chrome/browser/web_applications/scope_extension_info.h"
 #include "chrome/browser/web_applications/web_app_constants.h"
 #include "chrome/browser/web_applications/web_app_icon_generator.h"
@@ -572,7 +572,7 @@
   if (!options_.skip_primary_icon_download) {
     if (base::FeatureList::IsEnabled(features::kWebAppUsePrimaryIcon)) {
       std::optional<apps::IconInfo> primary_icon_metadata =
-          GetPrimaryIconsFromManifest(manifest_->icons);
+          GetTrustedIconsFromManifest(manifest_->icons);
       if (primary_icon_metadata) {
         install_info_->manifest_icons = {*primary_icon_metadata};
       }
diff --git a/chrome/browser_exposed_mojom_targets.gni b/chrome/browser_exposed_mojom_targets.gni
index 04811ba..6b8758d 100644
--- a/chrome/browser_exposed_mojom_targets.gni
+++ b/chrome/browser_exposed_mojom_targets.gni
@@ -53,6 +53,7 @@
   "//chrome/browser/ui/webui/search_engine_choice:mojo_bindings",
   "//chrome/browser/ui/webui/segmentation_internals:mojo_bindings",
   "//chrome/browser/ui/webui/side_panel/bookmarks:mojo_bindings",
+  "//chrome/browser/ui/webui/side_panel/comments:mojo_bindings",
   "//chrome/browser/ui/webui/side_panel/customize_chrome:mojo_bindings",
   "//chrome/browser/ui/webui/side_panel/reading_list:mojo_bindings",
   "//chrome/browser/ui/webui/signin/batch_upload:mojo_bindings",
diff --git a/chrome/build/android-arm32.pgo.txt b/chrome/build/android-arm32.pgo.txt
index e3c6301..383a114f 100644
--- a/chrome/build/android-arm32.pgo.txt
+++ b/chrome/build/android-arm32.pgo.txt
@@ -1 +1 @@
-chrome-android32-main-1752839987-b12be1bf19a2c73fec95ee88aa73f2ac61ec6de1-17e3614108b597e205f22d3486ada914e4dd6249.profdata
+chrome-android32-main-1752904355-968aafeb46520af5577f40ed75db9463368cda65-cd4b8a4a77117b7f5fb935e2a07873a5452df1ea.profdata
diff --git a/chrome/build/android-arm64.pgo.txt b/chrome/build/android-arm64.pgo.txt
index 6dfae8f..061b34f 100644
--- a/chrome/build/android-arm64.pgo.txt
+++ b/chrome/build/android-arm64.pgo.txt
@@ -1 +1 @@
-chrome-android64-main-1752844874-bfbc32ab4fc57fe5f6d5c83eb1ec60fe0f8e5fa4-b3fe782468942192770cc24d26d34fe9c396c60d.profdata
+chrome-android64-main-1752907699-856b74264775af0b267aa03b5aaeb822d6f48ea9-2d3f6c29b6c632a23b52b90c501fe31b018c5fc0.profdata
diff --git a/chrome/build/android-desktop-x64.pgo.txt b/chrome/build/android-desktop-x64.pgo.txt
index 58531c2c..3eafee4c 100644
--- a/chrome/build/android-desktop-x64.pgo.txt
+++ b/chrome/build/android-desktop-x64.pgo.txt
@@ -1 +1 @@
-chrome-android-desktop-x64-main-1752839987-7cb2019d4cc61f98f233bc79702698c80aea5a63-17e3614108b597e205f22d3486ada914e4dd6249.profdata
+chrome-android-desktop-x64-main-1752904355-bd39df39b1c5025e97ab729d01646f06dc5a6f92-cd4b8a4a77117b7f5fb935e2a07873a5452df1ea.profdata
diff --git a/chrome/build/linux.pgo.txt b/chrome/build/linux.pgo.txt
index 95f2caa..b506df71 100644
--- a/chrome/build/linux.pgo.txt
+++ b/chrome/build/linux.pgo.txt
@@ -1 +1 @@
-chrome-linux-main-1752839987-f08a4553320f668b919dd7d166e0cbc425d59e9a-17e3614108b597e205f22d3486ada914e4dd6249.profdata
+chrome-linux-main-1752883023-00a527de401f877af46a63189185bb11f3dcdc9f-1b06ea2fcf56be2f42e2f314c7ee02e8ce9dc654.profdata
diff --git a/chrome/build/mac-arm.pgo.txt b/chrome/build/mac-arm.pgo.txt
index 3e401d8..6384ade4 100644
--- a/chrome/build/mac-arm.pgo.txt
+++ b/chrome/build/mac-arm.pgo.txt
@@ -1 +1 @@
-chrome-mac-arm-main-1752846993-79bf438d232a85f3fdea30a2ed19ce063fc3b350-bd1ca80bfe4fa78364cec5901854820ba53f0fc5.profdata
+chrome-mac-arm-main-1752904355-9203e78d34d5233c80a5cf726835613f61f10a92-cd4b8a4a77117b7f5fb935e2a07873a5452df1ea.profdata
diff --git a/chrome/build/mac.pgo.txt b/chrome/build/mac.pgo.txt
index b22a9d4..3498891 100644
--- a/chrome/build/mac.pgo.txt
+++ b/chrome/build/mac.pgo.txt
@@ -1 +1 @@
-chrome-mac-main-1752839987-0cbeb37d639cff1dba503f13cfaeaec1901118ad-17e3614108b597e205f22d3486ada914e4dd6249.profdata
+chrome-mac-main-1752904355-b52db8708614622dcf385c52fe9d26030a2da650-cd4b8a4a77117b7f5fb935e2a07873a5452df1ea.profdata
diff --git a/chrome/build/win32.pgo.txt b/chrome/build/win32.pgo.txt
index e891479..cf7c3bb2 100644
--- a/chrome/build/win32.pgo.txt
+++ b/chrome/build/win32.pgo.txt
@@ -1 +1 @@
-chrome-win32-main-1752829088-7a79e053e4786a17808708003997ba307614b41e-d76c1a67de342f51716b06708ebf162182d02ef0.profdata
+chrome-win32-main-1752883023-57e31469be417d4d991928320054c86f40d508cc-1b06ea2fcf56be2f42e2f314c7ee02e8ce9dc654.profdata
diff --git a/chrome/build/win64.pgo.txt b/chrome/build/win64.pgo.txt
index 314a5efd..e56a045 100644
--- a/chrome/build/win64.pgo.txt
+++ b/chrome/build/win64.pgo.txt
@@ -1 +1 @@
-chrome-win64-main-1752829088-4886a7ebc26eeb23fbf73d8a42e54b2020e2056b-d76c1a67de342f51716b06708ebf162182d02ef0.profdata
+chrome-win64-main-1752883023-4213786099ad5058e6b792928bcf966e932f5f71-1b06ea2fcf56be2f42e2f314c7ee02e8ce9dc654.profdata
diff --git a/chrome/common/chrome_features.cc b/chrome/common/chrome_features.cc
index 06f782c..6b03fc5 100644
--- a/chrome/common/chrome_features.cc
+++ b/chrome/common/chrome_features.cc
@@ -346,7 +346,7 @@
 BASE_FEATURE(kGlicActor, "GlicActor", base::FEATURE_ENABLED_BY_DEFAULT);
 
 // Controls whether the Actor UI components are enabled.
-BASE_FEATURE(kGlicActorUi, "GlicActorUi", base::FEATURE_ENABLED_BY_DEFAULT);
+BASE_FEATURE(kGlicActorUi, "GlicActorUi", base::FEATURE_DISABLED_BY_DEFAULT);
 
 const char kGlicActorUiTaskIconName[] = "glic-actor-ui-task-icon";
 const char kGlicActorUiOverlayName[] = "glic-actor-ui-overlay";
@@ -376,6 +376,13 @@
 const base::FeatureParam<base::TimeDelta> kGlicActorPageStabilityTimeout{
     &kGlicActor, "glic-actor-page-stability-timeout", base::Seconds(10)};
 
+// An artificial delay before signalling the tools that the page has become
+// stable.
+const base::FeatureParam<base::TimeDelta>
+    kGlicActorPageStabilityInvokeCallbackDelay{
+        &kGlicActor, "glic-actor-page-stability-invoke-callback-delay",
+        base::Milliseconds(200)};
+
 // Controls whether typing happens incrementally.
 BASE_FEATURE(kGlicActorIncrementalTyping,
              "GlicActorIncrementalTyping",
diff --git a/chrome/common/chrome_features.h b/chrome/common/chrome_features.h
index 32ef974..cd5fe9b 100644
--- a/chrome/common/chrome_features.h
+++ b/chrome/common/chrome_features.h
@@ -224,6 +224,9 @@
 COMPONENT_EXPORT(CHROME_FEATURES)
 extern const base::FeatureParam<base::TimeDelta>(
     kGlicActorPageStabilityTimeout);
+COMPONENT_EXPORT(CHROME_FEATURES)
+extern const base::FeatureParam<base::TimeDelta>(
+    kGlicActorPageStabilityInvokeCallbackDelay);
 
 COMPONENT_EXPORT(CHROME_FEATURES)
 BASE_DECLARE_FEATURE(kGlicActorIncrementalTyping);
diff --git a/chrome/common/webui_url_constants.h b/chrome/common/webui_url_constants.h
index 766cc7fc..9ac8aa7 100644
--- a/chrome/common/webui_url_constants.h
+++ b/chrome/common/webui_url_constants.h
@@ -627,6 +627,12 @@
 inline constexpr char kChromeUIWatermarkURL[] = "chrome://watermark/";
 #endif
 
+#if BUILDFLAG(ENABLE_SESSION_SERVICE)
+inline constexpr char kChromeUITabStripInternalsHost[] = "tab-strip-internals";
+inline constexpr char kChromeUITabStripInternalsURL[] =
+    "chrome://tab-strip-internals";
+#endif
+
 // Settings sub-pages.
 //
 // NOTE: Add sub page paths to |kChromeSettingsSubPages| in
diff --git a/chrome/renderer/actor/page_stability_monitor.cc b/chrome/renderer/actor/page_stability_monitor.cc
index 9c6d6f6..cfa839c3 100644
--- a/chrome/renderer/actor/page_stability_monitor.cc
+++ b/chrome/renderer/actor/page_stability_monitor.cc
@@ -11,6 +11,7 @@
 #include "base/state_transitions.h"
 #include "base/task/sequenced_task_runner.h"
 #include "base/time/time.h"
+#include "base/types/cxx23_to_underlying.h"
 #include "chrome/common/actor/actor_logging.h"
 #include "chrome/common/chrome_features.h"
 #include "chrome/renderer/actor/tool_base.h"
@@ -30,7 +31,7 @@
 
 std::ostream& operator<<(std::ostream& o,
                          const PageStabilityMonitor::State& state) {
-  return o << static_cast<int>(state);
+  return o << base::to_underlying(state);
 }
 
 namespace {
@@ -87,7 +88,7 @@
       "DidCommitProvisionalLoad",
       absl::StrFormat("transition[%s]",
                       PageTransitionGetCoreTransitionString(transition)));
-  MoveToState(State::kInvokeCallback);
+  MoveToState(State::kMaybeDelayCallback);
 }
 
 void PageStabilityMonitor::DidFailProvisionalLoad() {
@@ -167,7 +168,7 @@
     case State::kWaitForVisualStateRequest: {
       WebFrameWidget* widget = render_frame()->GetWebFrame()->FrameWidget();
       if (!widget->InsertVisualStateRequest(
-              PostMoveToStateClosure(State::kInvokeCallback))) {
+              PostMoveToStateClosure(State::kMaybeDelayCallback))) {
         journal_entry_->EndEntry(
             "Failed to wait for new frame presentation due to no "
             "compositor.");
@@ -187,6 +188,17 @@
       MoveToState(State::kInvokeCallback);
       break;
     }
+    case State::kMaybeDelayCallback: {
+      base::TimeDelta callback_invoke_delay =
+          features::kGlicActorPageStabilityInvokeCallbackDelay.Get();
+      if (callback_invoke_delay.is_zero()) {
+        MoveToState(State::kInvokeCallback);
+      } else {
+        PostMoveToStateClosure(State::kInvokeCallback, callback_invoke_delay)
+            .Run();
+      }
+      break;
+    }
     case State::kInvokeCallback: {
       // Ensure we release the network idle callback slot.
       network_idle_callback_.Cancel();
@@ -240,6 +252,7 @@
               State::kWaitForNetworkIdle,
               State::kWaitForMainThreadIdle}},
           {State::kWaitForNavigation, {
+              State::kMaybeDelayCallback,
               State::kInvokeCallback,
               State::kTimeoutGlobal}},
           {State::kWaitForNetworkIdle, {
@@ -250,6 +263,7 @@
               State::kTimeoutMainThread,
               State::kTimeoutGlobal}},
           {State::kWaitForVisualStateRequest, {
+              State::kMaybeDelayCallback,
               State::kInvokeCallback,
               State::kTimeoutMainThread,
               State::kTimeoutGlobal}},
@@ -257,6 +271,10 @@
               State::kInvokeCallback}},
           {State::kTimeoutGlobal, {
               State::kInvokeCallback}},
+          {State::kMaybeDelayCallback, {
+              State::kInvokeCallback,
+              State::kTimeoutMainThread,
+              State::kTimeoutGlobal}},
           {State::kInvokeCallback, {
               State::kDone}}
 
diff --git a/chrome/renderer/actor/page_stability_monitor.h b/chrome/renderer/actor/page_stability_monitor.h
index 4b588d8..aee6a483 100644
--- a/chrome/renderer/actor/page_stability_monitor.h
+++ b/chrome/renderer/actor/page_stability_monitor.h
@@ -70,6 +70,10 @@
     kTimeoutGlobal,
     kTimeoutMainThread,
 
+    // If `kGlicActorPageStabilityInvokeCallbackDelay` is set, the callback
+    // passed to WaitForStable() will be delayed by said amount of time.
+    kMaybeDelayCallback,
+
     // Invoke the callback passed to WaitForStable and cleanup.
     kInvokeCallback,
 
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn
index fc45c75d..9248bb5d 100644
--- a/chrome/test/BUILD.gn
+++ b/chrome/test/BUILD.gn
@@ -2071,7 +2071,7 @@
         "../browser/extensions/api/notifications/notifications_apitest.cc",
         "../browser/extensions/api/omnibox/omnibox_api_interactive_test.cc",
         "../browser/extensions/api/tabs/tabs_apitest_android.cc",
-        "../browser/extensions/desktop_android/desktop_android_extensions_browsertest.cc",
+        "../browser/extensions/desktop_android_extensions_browsertest.cc",
       ]
 
       deps += [
@@ -3017,6 +3017,7 @@
       "//components/test/data/autofill/",
       "//components/test/data/custom_handlers",
       "//components/test/data/optimization_guide/",
+      "//components/test/data/page_content_annotations/",
       "//components/test/data/payments/",
       "//components/test/data/subresource_filter/",
       "//components/test/data/translate/",
@@ -11510,7 +11511,6 @@
       deps += [
         "//chrome/browser/ui/aura/accessibility:interactive_ui_tests",
         "//components/eye_dropper",
-        "//ui/aura:aura_interactive_ui_tests",
         "//ui/aura:test_support",
         "//ui/base/dragdrop:types",
         "//ui/base/dragdrop/mojom",
diff --git a/chrome/test/android/javatests/src/org/chromium/chrome/browser/tab/MockTab.java b/chrome/test/android/javatests/src/org/chromium/chrome/browser/tab/MockTab.java
index 8bb9ff93..da1095f6 100644
--- a/chrome/test/android/javatests/src/org/chromium/chrome/browser/tab/MockTab.java
+++ b/chrome/test/android/javatests/src/org/chromium/chrome/browser/tab/MockTab.java
@@ -35,7 +35,7 @@
     /** Create a new Tab for testing and initializes Tab UserData objects. */
     public static MockTab createAndInitialize(int id, Profile profile) {
         MockTab tab = new MockTab(id, profile);
-        tab.initialize(null, null, null, null, null, null, false, null, false);
+        tab.initialize(null, null, null, null, null, null, false, null, false, false);
         return tab;
     }
 
@@ -43,7 +43,7 @@
     public static MockTab createAndInitialize(
             int id, Profile profile, @TabLaunchType int tabLaunchType) {
         MockTab tab = new MockTab(id, profile, tabLaunchType);
-        tab.initialize(null, null, null, null, null, null, false, null, false);
+        tab.initialize(null, null, null, null, null, null, false, null, false, false);
         return tab;
     }
 
@@ -65,7 +65,8 @@
             @Nullable TabDelegateFactory delegateFactory,
             boolean initiallyHidden,
             TabState tabState,
-            boolean initializeRenderer) {
+            boolean initializeRenderer,
+            boolean isPinned) {
         if (loadUrlParams != null) {
             mGurlOverride = new GURL(loadUrlParams.getUrl());
         }
diff --git a/chrome/test/android/javatests/src/org/chromium/chrome/browser/tab/TabTestUtils.java b/chrome/test/android/javatests/src/org/chromium/chrome/browser/tab/TabTestUtils.java
index a135b6c..d1f05d1 100644
--- a/chrome/test/android/javatests/src/org/chromium/chrome/browser/tab/TabTestUtils.java
+++ b/chrome/test/android/javatests/src/org/chromium/chrome/browser/tab/TabTestUtils.java
@@ -45,7 +45,8 @@
             @Nullable TabDelegateFactory delegateFactory,
             boolean initiallyHidden,
             TabState tabState,
-            boolean initializeRenderer) {
+            boolean initializeRenderer,
+            boolean isPinned) {
         ((TabImpl) tab)
                 .initialize(
                         parent,
@@ -56,7 +57,8 @@
                         delegateFactory,
                         initiallyHidden,
                         tabState,
-                        initializeRenderer);
+                        initializeRenderer,
+                        isPinned);
     }
 
     /** Set the last hidden timestamp. */
diff --git a/chrome/test/android/javatests/src/org/chromium/chrome/test/util/browser/tabmodel/MockTabCreator.java b/chrome/test/android/javatests/src/org/chromium/chrome/test/util/browser/tabmodel/MockTabCreator.java
index b79b4db..6af36ef98 100644
--- a/chrome/test/android/javatests/src/org/chromium/chrome/test/util/browser/tabmodel/MockTabCreator.java
+++ b/chrome/test/android/javatests/src/org/chromium/chrome/test/util/browser/tabmodel/MockTabCreator.java
@@ -64,7 +64,7 @@
                         TabLaunchType.FROM_LINK);
         tab.getUserDataHost().setUserData(MockTabAttributes.class, new MockTabAttributes(false));
         TabTestUtils.initialize(
-                tab, null, null, loadUrlParams, title, null, null, false, null, false);
+                tab, null, null, loadUrlParams, title, null, null, false, null, false, false);
         tab.setIsInitialized(true);
         mSelector
                 .getModel(mIsIncognito)
@@ -82,7 +82,7 @@
                         TabLaunchType.FROM_RESTORE);
         tab.getUserDataHost().setUserData(MockTabAttributes.class, new MockTabAttributes(true));
         if (state != null) TabTestUtils.restoreFieldsFromState(tab, state);
-        TabTestUtils.initialize(tab, null, null, null, null, null, null, false, null, false);
+        TabTestUtils.initialize(tab, null, null, null, null, null, null, false, null, false, false);
         tab.setIsInitialized(true);
         mSelector
                 .getModel(mIsIncognito)
@@ -94,6 +94,7 @@
     @Override
     public Tab createTabWithWebContents(
             Tab parent,
+            boolean shouldPin,
             WebContents webContents,
             @TabLaunchType int type,
             GURL url,
diff --git a/chrome/test/data/webui/new_tab_page/app_test.ts b/chrome/test/data/webui/new_tab_page/app_test.ts
index 9a84f82..60e9f6b 100644
--- a/chrome/test/data/webui/new_tab_page/app_test.ts
+++ b/chrome/test/data/webui/new_tab_page/app_test.ts
@@ -1163,6 +1163,24 @@
       assertStyle($$(app, '#searchbox')!, 'visibility', 'hidden');
     });
     test(
+        'Clicking the searchbox composebox button displays the composebox',
+        async () => {
+          composeboxHandler.reset();
+          const composeButton = getComposeButton();
+          assertTrue(!!composeButton);
+
+          // Simulate entry point click.
+          composeButton.dispatchEvent(new CustomEvent(
+              'compose-click', DEFAULT_COMPOSE_CLICK_EVENT_OPTIONS));
+          await microtasksFinished();
+
+          // Assert.
+          const composebox = app.shadowRoot.querySelector('ntp-composebox');
+          assertTrue(!!composebox);
+          assertEquals(
+              composeboxHandler.getCallCount('notifySessionStarted'), 1);
+        });
+    test(
         'Clicking the searchbox composebox button with text navigates',
         async () => {
           composeboxHandler.reset();
diff --git a/chrome/test/data/webui/privacy_sandbox/internals/privacy_sandbox_internals_test.ts b/chrome/test/data/webui/privacy_sandbox/internals/privacy_sandbox_internals_test.ts
index 2f3fa0a..5662f36f 100644
--- a/chrome/test/data/webui/privacy_sandbox/internals/privacy_sandbox_internals_test.ts
+++ b/chrome/test/data/webui/privacy_sandbox/internals/privacy_sandbox_internals_test.ts
@@ -2,12 +2,13 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-import 'chrome://privacy-sandbox-internals/mojo_timestamp.js';
-import 'chrome://privacy-sandbox-internals/mojo_timedelta.js';
-import 'chrome://privacy-sandbox-internals/value_display.js';
-import 'chrome://privacy-sandbox-internals/pref_display.js';
 import 'chrome://privacy-sandbox-internals/expandable_json_viewer.js';
 import 'chrome://privacy-sandbox-internals/internals_page.js';
+import 'chrome://privacy-sandbox-internals/mojo_timestamp.js';
+import 'chrome://privacy-sandbox-internals/mojo_timedelta.js';
+import 'chrome://privacy-sandbox-internals/pref_display.js';
+import 'chrome://privacy-sandbox-internals/text_copy_button.js';
+import 'chrome://privacy-sandbox-internals/value_display.js';
 
 import type {CrFrameListElement} from 'chrome://privacy-sandbox-internals/cr_frame_list.js';
 import type {ExpandableJsonViewerElement} from 'chrome://privacy-sandbox-internals/expandable_json_viewer.js';
@@ -16,10 +17,12 @@
 import type {PrivacySandboxInternalsPref} from 'chrome://privacy-sandbox-internals/privacy_sandbox_internals.mojom-webui.js';
 import {PrivacySandboxInternalsBrowserProxy} from 'chrome://privacy-sandbox-internals/privacy_sandbox_internals_browser_proxy.js';
 import {Router} from 'chrome://privacy-sandbox-internals/router.js';
+import type {TextCopyButton} from 'chrome://privacy-sandbox-internals/text_copy_button.js';
 import type {ValueDisplayElement} from 'chrome://privacy-sandbox-internals/value_display.js';
 import {defaultLogicalFn, timestampLogicalFn} from 'chrome://privacy-sandbox-internals/value_display.js';
 import type {DictionaryValue, ListValue, Value} from 'chrome://resources/mojo/mojo/public/mojom/base/values.mojom-webui.js';
 import {assertEquals, assertFalse, assertNull, assertTrue} from 'chrome://webui-test/chai_assert.js';
+import {MockTimer} from 'chrome://webui-test/mock_timer.js';
 import {microtasksFinished} from 'chrome://webui-test/test_util.js';
 
 import {TestPrivacySandboxInternalsBrowserProxy} from './test_privacy_sandbox_internals_browser_proxy.js';
@@ -782,3 +785,118 @@
     assertEquals(jsonViewer.getTitleTextForTesting(), kJsonViewerTitle);
   });
 });
+
+// Test the <text-copy-button> element.
+suite('TextCopyButton', function() {
+  let clipboardData = '';
+  let textCopyButton: TextCopyButton;
+  const kTextToCopy = 'Sample text';
+  const textRecentlyCopiedAttribute = 'text-recently-copied';
+
+  suiteSetup(async function() {
+    await customElements.whenDefined('text-copy-button');
+
+    const mockClipboard = {
+      writeText: async (data: string) => {
+        clipboardData = data;
+        return Promise.resolve();
+      },
+      readText: async () => {
+        return Promise.resolve(clipboardData);
+      },
+    };
+
+    Object.defineProperty(navigator, 'clipboard', {
+      configurable: true,
+      get: () => mockClipboard,
+    });
+  });
+
+  const getCopyIconElementOrFail = () => {
+    const span = textCopyButton.$('.copy-icon');
+    assertTrue(!!span);
+    return span;
+  };
+
+  const getTickIconElementOrFail = () => {
+    const span = textCopyButton.$('.tick-icon');
+    assertTrue(!!span);
+    return span;
+  };
+
+  suiteTeardown(function() {
+    delete (navigator as any).clipboard;
+  });
+
+  setup(function() {
+    document.body.innerHTML = window.trustedTypes!.emptyHTML;
+    textCopyButton = document.createElement('text-copy-button');
+    textCopyButton.textToCopy = kTextToCopy;
+    document.body.appendChild(textCopyButton);
+  });
+
+  test('clickingButtonCopiesText', async () => {
+    textCopyButton.click();
+    const clipboardText = await navigator.clipboard.readText();
+    assertEquals(clipboardText, kTextToCopy);
+  });
+
+  test('clickingButtonSetsRecentlyTextCopiedAttribute', async () => {
+    assertFalse(textCopyButton.hasAttribute(textRecentlyCopiedAttribute));
+    textCopyButton.click();
+    await waitForCondition(
+        () => textCopyButton.hasAttribute(textRecentlyCopiedAttribute));
+    assertTrue(textCopyButton.hasAttribute(textRecentlyCopiedAttribute));
+  });
+
+  test('textCopiedAttributeGetsReverted', async () => {
+    const mockTimer = new MockTimer();
+    mockTimer.install();
+
+    textCopyButton.click();
+    // Awaiting navigator.clipboard.readText() allows us to make sure that the
+    // writeText() call is completed. await waitForCondition() would have been
+    // more ideal here, but MockTimer mocks setTimeout and prevents us from
+    // being able to rely on waitForCondition.
+    await navigator.clipboard.readText();
+    await Promise.resolve();
+    assertTrue(textCopyButton.hasAttribute(textRecentlyCopiedAttribute));
+    mockTimer.tick(textCopyButton.revertIconWaitDuration);
+    await Promise.resolve();
+    assertFalse(textCopyButton.hasAttribute(textRecentlyCopiedAttribute));
+    mockTimer.uninstall();
+  });
+
+  test('onClickIconVisibilityIsSetAndReverted', async () => {
+    const mockTimer = new MockTimer();
+    mockTimer.install();
+    const copyIcon = getCopyIconElementOrFail();
+    const tickIcon = getTickIconElementOrFail();
+
+    // Just the copy icon should be shown by default
+    assertEquals(
+        window.getComputedStyle(copyIcon).getPropertyValue('display'), 'block');
+    assertEquals(
+        window.getComputedStyle(tickIcon).getPropertyValue('display'), 'none');
+
+    // Just tick icon should be shown after the icon is clicked
+    textCopyButton.click();
+    await navigator.clipboard.readText();
+    await Promise.resolve();
+    assertEquals(
+        window.getComputedStyle(copyIcon).getPropertyValue('display'), 'none');
+    assertEquals(
+        window.getComputedStyle(tickIcon).getPropertyValue('display'), 'block');
+
+    // The check icon should be shown after the timeout
+    mockTimer.tick(textCopyButton.revertIconWaitDuration);
+    await Promise.resolve();
+    assertEquals(
+        window.getComputedStyle(copyIcon).getPropertyValue('display'), 'block');
+    assertEquals(
+        window.getComputedStyle(tickIcon).getPropertyValue('display'), 'none');
+
+
+    mockTimer.uninstall();
+  });
+});
diff --git a/chromeos/CHROMEOS_LKGM b/chromeos/CHROMEOS_LKGM
index 8f47259..ffaec9c 100644
--- a/chromeos/CHROMEOS_LKGM
+++ b/chromeos/CHROMEOS_LKGM
@@ -1 +1 @@
-16353.0.0-1070339
\ No newline at end of file
+16354.0.0-1070368
\ No newline at end of file
diff --git a/chromeos/ash/experiences/arc/BUILD.gn b/chromeos/ash/experiences/arc/BUILD.gn
index 010eecb0..664063f 100644
--- a/chromeos/ash/experiences/arc/BUILD.gn
+++ b/chromeos/ash/experiences/arc/BUILD.gn
@@ -56,7 +56,6 @@
     "dlc_installer/arc_dlc_install_notification_manager.h",
     "dlc_installer/arc_dlc_installer.cc",
     "dlc_installer/arc_dlc_installer.h",
-    "dlc_installer/arc_dlc_notification_manager_factory.h",
     "ime/arc_ime_bridge.h",
     "ime/arc_ime_bridge_impl.cc",
     "ime/arc_ime_bridge_impl.h",
@@ -350,10 +349,6 @@
     "test/fake_arc_bridge_host.h",
     "test/fake_arc_dlc_install_hardware_checker.cc",
     "test/fake_arc_dlc_install_hardware_checker.h",
-    "test/fake_arc_dlc_install_notification_delegate.cc",
-    "test/fake_arc_dlc_install_notification_delegate.h",
-    "test/fake_arc_dlc_notification_manager_factory_impl.cc",
-    "test/fake_arc_dlc_notification_manager_factory_impl.h",
     "test/fake_arc_icon_cache.cc",
     "test/fake_arc_icon_cache.h",
     "test/fake_arc_intent_helper_mojo.cc",
@@ -581,6 +576,7 @@
     "//ui/events",
     "//ui/events:dom_keycode_converter",
     "//ui/events:test_support",
+    "//ui/message_center:test_support",
     "//ui/views:test_support",
     "//url:url",
   ]
diff --git a/chromeos/ash/experiences/arc/dlc_installer/arc_dlc_install_notification_manager.cc b/chromeos/ash/experiences/arc/dlc_installer/arc_dlc_install_notification_manager.cc
index f34d366..aa2c9cf8 100644
--- a/chromeos/ash/experiences/arc/dlc_installer/arc_dlc_install_notification_manager.cc
+++ b/chromeos/ash/experiences/arc/dlc_installer/arc_dlc_install_notification_manager.cc
@@ -7,26 +7,24 @@
 #include <memory>
 #include <optional>
 #include <string>
+#include <string_view>
 
 #include "ash/constants/notifier_catalogs.h"
 #include "ash/public/cpp/notification_utils.h"
 #include "base/check.h"
 #include "base/functional/bind.h"
-#include "components/account_id/account_id.h"
 #include "components/strings/grit/components_strings.h"
 #include "components/vector_icons/vector_icons.h"
 #include "ui/base/l10n/l10n_util.h"
+#include "ui/message_center/message_center.h"
 #include "ui/message_center/public/cpp/notification.h"
 #include "ui/message_center/public/cpp/notification_delegate.h"
 #include "url/gurl.h"
 
-namespace arc {
+namespace arc::arc_dlc_install_notification_manager {
 
 namespace {
 
-constexpr char kNotifierId[] = "arc_dlc_install";
-
-// Retrieves the localized message string for the specified notification type.
 std::u16string GetMessage(NotificationType type) {
   switch (type) {
     case NotificationType::kArcVmPreloadStarted:
@@ -38,49 +36,39 @@
   }
 }
 
-std::string GenerateNotificationId(NotificationType type) {
+std::string_view GetNotificationId(NotificationType type) {
   switch (type) {
     case NotificationType::kArcVmPreloadStarted:
-      return std::string(kNotifierId) + "/started";
+      return kArcVmPreloadStartedId;
     case NotificationType::kArcVmPreloadSucceeded:
-      return std::string(kNotifierId) + "/succeeded";
+      return kArcVmPreloadSucceededId;
     case NotificationType::kArcVmPreloadFailed:
-      return std::string(kNotifierId) + "/failed";
+      return kArcVmPreloadFailedId;
   }
 }
-
 }  // namespace
 
-ArcDlcInstallNotificationManager::ArcDlcInstallNotificationManager(
-    std::unique_ptr<ArcDlcInstallNotificationManager::Delegate> delegate,
-    const AccountId& account_id)
-    : delegate_(std::move(delegate)), account_id_(account_id) {}
-
-ArcDlcInstallNotificationManager::~ArcDlcInstallNotificationManager() = default;
-
-void ArcDlcInstallNotificationManager::Show(
-    NotificationType notification_type) {
+void Show(NotificationType notification_type) {
   message_center::NotifierId notifier_id(
-      message_center::NotifierType::SYSTEM_COMPONENT, kNotifierId,
-      ash::NotificationCatalogName::kArcMigrationGuide);
-  notifier_id.profile_id = account_id_.GetUserEmail();
+      message_center::NotifierType::SYSTEM_COMPONENT, "arc_dlc_install",
+      ash::NotificationCatalogName::kArcDlcInstall);
 
   auto click_delegate =
       base::MakeRefCounted<message_center::HandleNotificationClickDelegate>(
           base::BindRepeating([]() {}));
 
-  message_center::Notification notification = ash::CreateSystemNotification(
-      message_center::NOTIFICATION_TYPE_SIMPLE,
-      GenerateNotificationId(notification_type),
-      l10n_util::GetStringUTF16(IDS_ARC_VM_PRELOAD_NOTIFICATION_TITLE),
-      GetMessage(notification_type), std::u16string(), GURL(), notifier_id,
-      message_center::RichNotificationData(), std::move(click_delegate),
-      vector_icons::kSettingsIcon,
-      message_center::SystemNotificationWarningLevel::NORMAL);
+  std::unique_ptr<message_center::Notification> notification =
+      ash::CreateSystemNotificationPtr(
+          message_center::NOTIFICATION_TYPE_SIMPLE,
+          std::string(GetNotificationId(notification_type)),
+          l10n_util::GetStringUTF16(IDS_ARC_VM_PRELOAD_NOTIFICATION_TITLE),
+          GetMessage(notification_type), std::u16string(), GURL(), notifier_id,
+          message_center::RichNotificationData(), std::move(click_delegate),
+          vector_icons::kSettingsIcon,
+          message_center::SystemNotificationWarningLevel::NORMAL);
 
-  notification.set_renotify(true);
-
-  delegate_->DisplayNotification(std::move(notification));
+  message_center::MessageCenter::Get()->AddNotification(
+      std::move(notification));
 }
 
-}  // namespace arc
+}  // namespace arc::arc_dlc_install_notification_manager
diff --git a/chromeos/ash/experiences/arc/dlc_installer/arc_dlc_install_notification_manager.h b/chromeos/ash/experiences/arc/dlc_installer/arc_dlc_install_notification_manager.h
index a34e33c..ed80f520 100644
--- a/chromeos/ash/experiences/arc/dlc_installer/arc_dlc_install_notification_manager.h
+++ b/chromeos/ash/experiences/arc/dlc_installer/arc_dlc_install_notification_manager.h
@@ -5,14 +5,20 @@
 #ifndef CHROMEOS_ASH_EXPERIENCES_ARC_DLC_INSTALLER_ARC_DLC_INSTALL_NOTIFICATION_MANAGER_H_
 #define CHROMEOS_ASH_EXPERIENCES_ARC_DLC_INSTALLER_ARC_DLC_INSTALL_NOTIFICATION_MANAGER_H_
 
-#include <memory>
-
-#include "base/memory/raw_ref.h"
-#include "components/account_id/account_id.h"
-#include "ui/message_center/public/cpp/notification.h"
+#include <string_view>
 
 namespace arc {
 
+// Manages notifications for ARC DLC installation processes.
+namespace arc_dlc_install_notification_manager {
+
+inline constexpr std::string_view kArcVmPreloadStartedId =
+    "arc_dlc_install/started";
+inline constexpr std::string_view kArcVmPreloadSucceededId =
+    "arc_dlc_install/succeeded";
+inline constexpr std::string_view kArcVmPreloadFailedId =
+    "arc_dlc_install/failed";
+
 // Represents the type of notification to be displayed.
 enum class NotificationType {
   kArcVmPreloadStarted,
@@ -20,32 +26,10 @@
   kArcVmPreloadFailed
 };
 
-// Manages notifications for ARC DLC installation processes.
-class ArcDlcInstallNotificationManager {
- public:
-  // Interface for handling notification display logic.
-  class Delegate {
-   public:
-    virtual void DisplayNotification(
-        const message_center::Notification& notification) = 0;
+// Displays a notification of the specified type.
+void Show(NotificationType notification_type);
 
-    virtual ~Delegate() = default;
-  };
-
-  ArcDlcInstallNotificationManager(std::unique_ptr<Delegate> delegate,
-                                   const AccountId& account_id);
-  ~ArcDlcInstallNotificationManager();
-
-  // Displays a notification of the specified type.
-  void Show(NotificationType notification_type);
-
- private:
-  // Delegate for managing notification display.
-  std::unique_ptr<Delegate> delegate_;
-
-  // Account id associated with notifications.
-  const AccountId account_id_;
-};
+}  // namespace arc_dlc_install_notification_manager
 
 }  // namespace arc
 
diff --git a/chromeos/ash/experiences/arc/dlc_installer/arc_dlc_install_notification_manager_unittest.cc b/chromeos/ash/experiences/arc/dlc_installer/arc_dlc_install_notification_manager_unittest.cc
index a8b8ba6e..7c52f80a 100644
--- a/chromeos/ash/experiences/arc/dlc_installer/arc_dlc_install_notification_manager_unittest.cc
+++ b/chromeos/ash/experiences/arc/dlc_installer/arc_dlc_install_notification_manager_unittest.cc
@@ -4,16 +4,15 @@
 
 #include "chromeos/ash/experiences/arc/dlc_installer/arc_dlc_install_notification_manager.h"
 
-#include <vector>
+#include <memory>
 
 #include "base/memory/raw_ptr.h"
 #include "chromeos/ash/experiences/arc/session/arc_service_manager.h"
-#include "chromeos/ash/experiences/arc/test/fake_arc_dlc_install_notification_delegate.h"
-#include "components/account_id/account_id.h"
 #include "components/strings/grit/components_strings.h"
 #include "google_apis/gaia/gaia_id.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "ui/base/l10n/l10n_util.h"
+#include "ui/message_center/fake_message_center.h"
 
 namespace arc {
 
@@ -23,67 +22,75 @@
   ~ArcDlcInstallNotificationManagerTest() override = default;
 
   void SetUp() override {
-    auto account_id =
-        AccountId::FromUserEmailGaiaId("example.com", GaiaId("123123"));
-    auto delegate = std::make_unique<FakeArcDlcInstallNotificationDelegate>();
-    delegate_ = delegate.get();
-    manager_ = std::make_unique<ArcDlcInstallNotificationManager>(
-        std::move(delegate), account_id);
+    auto fake_message_center =
+        std::make_unique<message_center::FakeMessageCenter>();
+    fake_message_center_ = fake_message_center.get();
+    message_center::MessageCenter::InitializeForTesting(
+        std::move(fake_message_center));
   }
 
   void TearDown() override {
-    delegate_ = nullptr;
-    manager_.reset();
+    fake_message_center_ = nullptr;
+    message_center::MessageCenter::Shutdown();
   }
 
-  ArcDlcInstallNotificationManager* manager() { return manager_.get(); }
-
-  FakeArcDlcInstallNotificationDelegate* delegate() { return delegate_; }
-
- private:
-  raw_ptr<content::BrowserContext> test_profile_ = nullptr;
-  std::unique_ptr<ArcDlcInstallNotificationManager> manager_;
-  raw_ptr<FakeArcDlcInstallNotificationDelegate> delegate_ = nullptr;
+  raw_ptr<message_center::FakeMessageCenter> fake_message_center_ = nullptr;
 };
 
 TEST_F(ArcDlcInstallNotificationManagerTest, DisplayNotification_Success) {
-  manager()->Show(NotificationType::kArcVmPreloadSucceeded);
+  arc_dlc_install_notification_manager::Show(
+      arc_dlc_install_notification_manager::NotificationType::
+          kArcVmPreloadSucceeded);
 
-  const auto& notifications = delegate()->displayed_notifications();
+  const auto& notifications = fake_message_center_->GetNotifications();
+  ASSERT_FALSE(notifications.empty()) << "No notifications found.";
+
+  const auto& notification = *notifications.begin();
 
   ASSERT_EQ(1u, notifications.size());
-  EXPECT_EQ(notifications[0].title(),
+  EXPECT_EQ(notification->title(),
             l10n_util::GetStringUTF16(IDS_ARC_VM_PRELOAD_NOTIFICATION_TITLE));
-  EXPECT_EQ(notifications[0].message(),
+  EXPECT_EQ(notification->message(),
             l10n_util::GetStringUTF16(IDS_ARC_VM_PRELOAD_SUCCEEDED_MESSAGE));
-  EXPECT_EQ(notifications[0].id(), "arc_dlc_install/succeeded");
+  EXPECT_EQ(notification->id(),
+            arc_dlc_install_notification_manager::kArcVmPreloadSucceededId);
 }
 
 TEST_F(ArcDlcInstallNotificationManagerTest, DisplayNotification_Failure) {
-  manager()->Show(NotificationType::kArcVmPreloadFailed);
+  arc_dlc_install_notification_manager::Show(
+      arc_dlc_install_notification_manager::NotificationType::
+          kArcVmPreloadFailed);
 
-  const auto& notifications = delegate()->displayed_notifications();
+  const auto& notifications = fake_message_center_->GetNotifications();
+  ASSERT_FALSE(notifications.empty()) << "No notifications found.";
+  const auto& notification = *notifications.begin();
 
   ASSERT_EQ(1u, notifications.size());
-  EXPECT_EQ(notifications[0].title(),
+  EXPECT_EQ(notification->title(),
             l10n_util::GetStringUTF16(IDS_ARC_VM_PRELOAD_NOTIFICATION_TITLE));
-  EXPECT_EQ(notifications[0].message(),
+  EXPECT_EQ(notification->message(),
             l10n_util::GetStringUTF16(IDS_ARC_VM_PRELOAD_FAILED_MESSAGE));
-  EXPECT_EQ(notifications[0].id(), "arc_dlc_install/failed");
+  EXPECT_EQ(notification->id(),
+            arc_dlc_install_notification_manager::kArcVmPreloadFailedId);
 }
 
 TEST_F(ArcDlcInstallNotificationManagerTest,
        DisplayNotification_PreloadStarted) {
-  manager()->Show(NotificationType::kArcVmPreloadStarted);
+  arc_dlc_install_notification_manager::Show(
+      arc_dlc_install_notification_manager::NotificationType::
+          kArcVmPreloadStarted);
 
-  const auto& notifications = delegate()->displayed_notifications();
+  const auto& notifications = fake_message_center_->GetNotifications();
+  ASSERT_FALSE(notifications.empty()) << "No notifications found.";
+  const auto& notification = *notifications.begin();
 
   ASSERT_EQ(1u, notifications.size());
-  EXPECT_EQ(notifications[0].title(),
+  EXPECT_EQ(notification->title(),
             l10n_util::GetStringUTF16(IDS_ARC_VM_PRELOAD_NOTIFICATION_TITLE));
-  EXPECT_EQ(notifications[0].message(),
+  EXPECT_EQ(notification->message(),
             l10n_util::GetStringUTF16(IDS_ARC_VM_PRELOAD_STARTED_MESSAGE));
-  EXPECT_EQ(notifications[0].id(), "arc_dlc_install/started");
+  EXPECT_EQ(notification->id(),
+            arc_dlc_install_notification_manager::kArcVmPreloadStartedId);
 }
 
 }  // namespace arc
diff --git a/chromeos/ash/experiences/arc/dlc_installer/arc_dlc_installer.cc b/chromeos/ash/experiences/arc/dlc_installer/arc_dlc_installer.cc
index 6db9570b..3fe6cfb 100644
--- a/chromeos/ash/experiences/arc/dlc_installer/arc_dlc_installer.cc
+++ b/chromeos/ash/experiences/arc/dlc_installer/arc_dlc_installer.cc
@@ -18,7 +18,6 @@
 #include "chromeos/ash/experiences/arc/arc_util.h"
 #include "chromeos/ash/experiences/arc/dlc_installer/arc_dlc_install_hardware_checker.h"
 #include "chromeos/ash/experiences/arc/dlc_installer/arc_dlc_install_notification_manager.h"
-#include "chromeos/ash/experiences/arc/dlc_installer/arc_dlc_notification_manager_factory.h"
 
 namespace arc {
 
@@ -31,12 +30,9 @@
 }  // namespace
 
 ArcDlcInstaller::ArcDlcInstaller(
-    std::unique_ptr<ArcDlcNotificationManagerFactory>
-        notification_manager_factory,
     std::unique_ptr<ArcDlcInstallHardwareChecker> hardware_checker,
     ash::CrosSettings* cros_settings)
-    : notification_manager_factory_(std::move(notification_manager_factory)),
-      hardware_checker_(std::move(hardware_checker)),
+    : hardware_checker_(std::move(hardware_checker)),
       cros_settings_(std::move(cros_settings)) {}
 
 ArcDlcInstaller::~ArcDlcInstaller() = default;
@@ -105,25 +101,6 @@
   return true;
 }
 
-void ArcDlcInstaller::MaybeShowDlcInstallNotification(NotificationType type) {
-  if (!arc_dlc_install_notification_manager_) {
-    VLOG(1) << "Notification manager not initialized. Queueing notification.";
-    dlc_install_pending_notifications_.push_back(type);
-    return;
-  }
-  arc_dlc_install_notification_manager_->Show(type);
-}
-
-void ArcDlcInstaller::OnPrimaryUserSessionStarted(const AccountId& account_id) {
-  arc_dlc_install_notification_manager_ =
-      notification_manager_factory_->CreateNotificationManager(account_id);
-
-  for (const auto& notification : dlc_install_pending_notifications_) {
-    arc_dlc_install_notification_manager_->Show(notification);
-  }
-  dlc_install_pending_notifications_.clear();
-}
-
 void ArcDlcInstaller::OnDlcProgress(bool* installation_triggered_ptr,
                                     double progress) {
   CHECK(installation_triggered_ptr);
@@ -131,7 +108,9 @@
     return;
   }
 
-  MaybeShowDlcInstallNotification(NotificationType::kArcVmPreloadStarted);
+  arc_dlc_install_notification_manager::Show(
+      arc_dlc_install_notification_manager::NotificationType::
+          kArcVmPreloadStarted);
   *installation_triggered_ptr = true;
 }
 
@@ -142,7 +121,9 @@
     const ash::DlcserviceClient::InstallResult& install_result) {
   if (install_result.error != dlcservice::kErrorNone) {
     VLOG(1) << "Failed to install ARCVM DLC: " << install_result.error;
-    MaybeShowDlcInstallNotification(NotificationType::kArcVmPreloadFailed);
+    arc_dlc_install_notification_manager::Show(
+        arc_dlc_install_notification_manager::NotificationType::
+            kArcVmPreloadFailed);
     base::UmaHistogramBoolean("Arc.DlcInstaller.Install", false);
     std::move(callback).Run(false);
     return;
@@ -159,17 +140,14 @@
   // installation succeeds.
   CHECK(installation_triggered);
   if (*installation_triggered) {
-    MaybeShowDlcInstallNotification(NotificationType::kArcVmPreloadSucceeded);
+    arc_dlc_install_notification_manager::Show(
+        arc_dlc_install_notification_manager::NotificationType::
+            kArcVmPreloadSucceeded);
   }
 
   OnPrepareArcDlc(std::move(callback), true);
 }
 
-const std::vector<NotificationType>&
-ArcDlcInstaller::GetDlcInstallPendingNotificationsForTesting() const {
-  return dlc_install_pending_notifications_;
-}
-
 void ArcDlcInstaller::OnPrepareArcDlc(base::OnceCallback<void(bool)> callback,
                                       bool result) {
   if (!result) {
diff --git a/chromeos/ash/experiences/arc/dlc_installer/arc_dlc_installer.h b/chromeos/ash/experiences/arc/dlc_installer/arc_dlc_installer.h
index a81764e1..e8084e9 100644
--- a/chromeos/ash/experiences/arc/dlc_installer/arc_dlc_installer.h
+++ b/chromeos/ash/experiences/arc/dlc_installer/arc_dlc_installer.h
@@ -12,8 +12,7 @@
 #include "base/memory/raw_ptr.h"
 #include "base/memory/weak_ptr.h"
 #include "chromeos/ash/components/dbus/dlcservice/dlcservice_client.h"
-
-class AccountId;
+#include "chromeos/ash/experiences/arc/dlc_installer/arc_dlc_install_notification_manager.h"
 
 namespace ash {
 class CrosSettings;
@@ -22,10 +21,6 @@
 namespace arc {
 
 class ArcDlcInstallHardwareChecker;
-class ArcDlcInstallNotificationManager;
-class ArcDlcNotificationManagerFactory;
-
-enum class NotificationType;
 
 // The ArcDlcInstaller class manages the installation process for ARC DLC
 // (Android Runtime for Chrome). It handles hardware compatibility checks,
@@ -34,8 +29,6 @@
 class ArcDlcInstaller {
  public:
   ArcDlcInstaller(
-      std::unique_ptr<ArcDlcNotificationManagerFactory>
-          notification_manager_factory,
       std::unique_ptr<ArcDlcInstallHardwareChecker> hardware_checker,
       ash::CrosSettings* cros_settings);
 
@@ -48,19 +41,11 @@
   // DLC, it performs a hardware compatibility check.
   void PrepareArc(base::OnceCallback<void(bool)> callback);
 
-  // ArcServiceLauncher will invoke this function when the profile is set up,
-  // and it will create the notification manager and process any pending DLC
-  // installation notifications that need to be shown.
-  void OnPrimaryUserSessionStarted(const AccountId& account_id);
 
   // Determines if the DLC installation is necessary based on
   // board, management, and feature flag conditions.
   bool IsDlcRequired();
 
-  // Returns dlc_install_pending_notifications_ for testing.
-  const std::vector<NotificationType>&
-  GetDlcInstallPendingNotificationsForTesting() const;
-
  private:
   // Callback invoked after ARC DLC preparation is complete.
   // This function is triggered when the DLC required for
@@ -78,11 +63,6 @@
   void OnHardwareCheckComplete(base::OnceCallback<void(bool)> callback,
                                bool is_compatible);
 
-  // Displays a DLC installation notification of the specified type if the
-  // Notification Manager is initialized. If not initialized, queues the
-  // notification to be shown later once the manager is ready.
-  void MaybeShowDlcInstallNotification(NotificationType type);
-
   // Handles the result of the ARCVM DLC installation. If successful, shows a
   // success notification, configures and starts necessary Upstart jobs, and
   // invokes the callback. If installation fails, logs the error, shows a
@@ -99,13 +79,8 @@
   // determine whether the DLC image was installed.
   void OnDlcProgress(bool* installation_triggered, double progress);
 
-  std::unique_ptr<ArcDlcNotificationManagerFactory>
-      notification_manager_factory_;
-  std::unique_ptr<ArcDlcInstallNotificationManager>
-      arc_dlc_install_notification_manager_;
   std::unique_ptr<ArcDlcInstallHardwareChecker> hardware_checker_;
   raw_ptr<ash::CrosSettings> cros_settings_;
-  std::vector<NotificationType> dlc_install_pending_notifications_;
   base::WeakPtrFactory<ArcDlcInstaller> weak_ptr_factory_{this};
 };
 
diff --git a/chromeos/ash/experiences/arc/dlc_installer/arc_dlc_installer_unittest.cc b/chromeos/ash/experiences/arc/dlc_installer/arc_dlc_installer_unittest.cc
index 276ef76..1ccc9c4 100644
--- a/chromeos/ash/experiences/arc/dlc_installer/arc_dlc_installer_unittest.cc
+++ b/chromeos/ash/experiences/arc/dlc_installer/arc_dlc_installer_unittest.cc
@@ -4,8 +4,12 @@
 
 #include "chromeos/ash/experiences/arc/dlc_installer/arc_dlc_installer.h"
 
+#include <algorithm>
 #include <memory>
+#include <string>
+#include <string_view>
 #include <utility>
+#include <vector>
 
 #include "ash/constants/ash_switches.h"
 #include "base/command_line.h"
@@ -18,9 +22,8 @@
 #include "chromeos/ash/components/settings/fake_cros_settings_provider.h"
 #include "chromeos/ash/experiences/arc/dlc_installer/arc_dlc_install_notification_manager.h"
 #include "chromeos/ash/experiences/arc/test/fake_arc_dlc_install_hardware_checker.h"
-#include "chromeos/ash/experiences/arc/test/fake_arc_dlc_notification_manager_factory_impl.h"
-#include "components/account_id/account_id.h"
 #include "testing/gtest/include/gtest/gtest.h"
+#include "ui/message_center/fake_message_center.h"
 
 namespace arc {
 
@@ -29,8 +32,13 @@
   void SetUp() override {
     ash::DlcserviceClient::InitializeFake();
     ash::UpstartClient::InitializeFake();
-    std::unique_ptr<FakeArcDlcNotificationManagerFactoryImpl> fake_factory =
-        std::make_unique<FakeArcDlcNotificationManagerFactoryImpl>();
+
+    auto fake_message_center =
+        std::make_unique<message_center::FakeMessageCenter>();
+    fake_message_center_ = fake_message_center.get();
+    message_center::MessageCenter::InitializeForTesting(
+        std::move(fake_message_center));
+
     std::unique_ptr<FakeArcDlcInstallHardwareChecker> fake_hardware_checker =
         std::make_unique<FakeArcDlcInstallHardwareChecker>(true);
     cros_settings_ = std::make_unique<ash::CrosSettings>();
@@ -42,8 +50,7 @@
     // specific path.
     fake_provider_->Set(ash::kDeviceFlexArcPreloadEnabled, base::Value());
     arc_dlc_installer_ = std::make_unique<ArcDlcInstaller>(
-        std::move(fake_factory), std::move(fake_hardware_checker),
-        cros_settings_.get());
+        std::move(fake_hardware_checker), cros_settings_.get());
   }
 
   void TearDown() override {
@@ -52,6 +59,8 @@
     cros_settings_.reset();
     ash::UpstartClient::Shutdown();
     ash::DlcserviceClient::Shutdown();
+    fake_message_center_ = nullptr;
+    message_center::MessageCenter::Shutdown();
   }
 
   void SetFlexArcPreloadEnabled(bool enabled) {
@@ -71,14 +80,13 @@
     run_loop.Run();
   }
 
-  void VerifyPendingNotifications(
-      const std::vector<NotificationType>& expected_notifications) {
-    const auto& pending_notifications =
-        arc_dlc_installer_->GetDlcInstallPendingNotificationsForTesting();
-
-    ASSERT_EQ(pending_notifications.size(), expected_notifications.size());
-    for (size_t i = 0; i < pending_notifications.size(); ++i) {
-      EXPECT_EQ(pending_notifications[i], expected_notifications[i]);
+  void VerifyNotifications(base::span<const std::string_view> expected_ids) {
+    const auto& notifications = fake_message_center_->GetNotifications();
+    ASSERT_EQ(notifications.size(), expected_ids.size());
+    size_t index = 0;
+    for (const auto& notification : notifications) {
+      ASSERT_TRUE(notification);
+      EXPECT_EQ(notification->id(), expected_ids[index++]);
     }
   }
 
@@ -89,6 +97,7 @@
 
   base::test::TaskEnvironment task_environment_;
   ash::ScopedStubInstallAttributes test_install_attributes_;
+  raw_ptr<message_center::FakeMessageCenter> fake_message_center_ = nullptr;
   std::unique_ptr<ash::CrosSettings> cros_settings_;
   raw_ptr<ash::FakeCrosSettingsProvider> fake_provider_ = nullptr;
   std::unique_ptr<ArcDlcInstaller> arc_dlc_installer_;
@@ -149,7 +158,7 @@
       base::BindOnce([](bool result) { EXPECT_FALSE(result); }));
 }
 
-TEST_F(ArcDlcInstallerTest, VerifyPendingNotifications_InstallSuccess) {
+TEST_F(ArcDlcInstallerTest, VerifyNotifications_InstallSuccess) {
   test_install_attributes_.Get()->SetCloudManaged("example.com",
                                                   "fake-device-id");
   base::CommandLine::ForCurrentProcess()->AppendSwitch(
@@ -160,17 +169,12 @@
   SetFlexArcPreloadEnabled(true);
 
   InstallArcImageFromDlc(dlcservice::kErrorNone);
-  VerifyPendingNotifications({NotificationType::kArcVmPreloadStarted,
-                              NotificationType::kArcVmPreloadSucceeded});
-
-  arc_dlc_installer_->OnPrimaryUserSessionStarted(
-      AccountId::FromUserEmail("test@example.com"));
-  // With the session properly initialized, the pending notifications will be
-  // sent and removed from the pending queue.
-  VerifyPendingNotifications({});
+  VerifyNotifications(
+      {arc_dlc_install_notification_manager::kArcVmPreloadSucceededId,
+       arc_dlc_install_notification_manager::kArcVmPreloadStartedId});
 }
 
-TEST_F(ArcDlcInstallerTest, VerifyPendingNotifications_InstallFail) {
+TEST_F(ArcDlcInstallerTest, VerifyNotifications_InstallFail) {
   test_install_attributes_.Get()->SetCloudManaged("example.com",
                                                   "fake-device-id");
   base::CommandLine::ForCurrentProcess()->AppendSwitch(
@@ -181,14 +185,9 @@
   SetFlexArcPreloadEnabled(true);
 
   InstallArcImageFromDlc(dlcservice::kErrorInternal);
-  VerifyPendingNotifications({NotificationType::kArcVmPreloadStarted,
-                              NotificationType::kArcVmPreloadFailed});
-
-  arc_dlc_installer_->OnPrimaryUserSessionStarted(
-      AccountId::FromUserEmail("test@example.com"));
-  // With the session properly initialized, the pending notifications will be
-  // sent and removed from the pending queue.
-  VerifyPendingNotifications({});
+  VerifyNotifications(
+      {arc_dlc_install_notification_manager::kArcVmPreloadFailedId,
+       arc_dlc_install_notification_manager::kArcVmPreloadStartedId});
 }
 
 // Verifies that installation completion notifications are triggered only once
@@ -211,8 +210,9 @@
 
   // Expect two notifications: one for the preload start and one for the
   // success, even after triggering the installation twice.
-  VerifyPendingNotifications({NotificationType::kArcVmPreloadStarted,
-                              NotificationType::kArcVmPreloadSucceeded});
+  VerifyNotifications(
+      {arc_dlc_install_notification_manager::kArcVmPreloadSucceededId,
+       arc_dlc_install_notification_manager::kArcVmPreloadStartedId});
 }
 
 }  // namespace arc
diff --git a/chromeos/ash/experiences/arc/dlc_installer/arc_dlc_notification_manager_factory.h b/chromeos/ash/experiences/arc/dlc_installer/arc_dlc_notification_manager_factory.h
deleted file mode 100644
index 5998439..0000000
--- a/chromeos/ash/experiences/arc/dlc_installer/arc_dlc_notification_manager_factory.h
+++ /dev/null
@@ -1,30 +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 CHROMEOS_ASH_EXPERIENCES_ARC_DLC_INSTALLER_ARC_DLC_NOTIFICATION_MANAGER_FACTORY_H_
-#define CHROMEOS_ASH_EXPERIENCES_ARC_DLC_INSTALLER_ARC_DLC_NOTIFICATION_MANAGER_FACTORY_H_
-
-#include <memory>
-
-class AccountId;
-
-namespace arc {
-
-class ArcDlcInstallNotificationManager;
-
-// Interface for handling the creation of notification managers.
-// This interface is used to abstract the creation of notification managers
-// based on the AccountId for the ARC DLC installer.
-class ArcDlcNotificationManagerFactory {
- public:
-  virtual ~ArcDlcNotificationManagerFactory() = default;
-
-  // Initializes and returns a notification manager.
-  virtual std::unique_ptr<ArcDlcInstallNotificationManager>
-  CreateNotificationManager(const AccountId& account_id) = 0;
-};
-
-}  // namespace arc
-
-#endif  // CHROMEOS_ASH_EXPERIENCES_ARC_DLC_INSTALLER_ARC_DLC_NOTIFICATION_MANAGER_FACTORY_H_
diff --git a/chromeos/ash/experiences/arc/test/fake_arc_dlc_install_notification_delegate.cc b/chromeos/ash/experiences/arc/test/fake_arc_dlc_install_notification_delegate.cc
deleted file mode 100644
index 5873defd..0000000
--- a/chromeos/ash/experiences/arc/test/fake_arc_dlc_install_notification_delegate.cc
+++ /dev/null
@@ -1,19 +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 "chromeos/ash/experiences/arc/test/fake_arc_dlc_install_notification_delegate.h"
-
-namespace arc {
-
-FakeArcDlcInstallNotificationDelegate::FakeArcDlcInstallNotificationDelegate() =
-    default;
-
-FakeArcDlcInstallNotificationDelegate::
-    ~FakeArcDlcInstallNotificationDelegate() = default;
-
-void FakeArcDlcInstallNotificationDelegate::DisplayNotification(
-    const message_center::Notification& notification) {
-  displayed_notifications_.push_back(notification);
-}
-}  // namespace arc
diff --git a/chromeos/ash/experiences/arc/test/fake_arc_dlc_install_notification_delegate.h b/chromeos/ash/experiences/arc/test/fake_arc_dlc_install_notification_delegate.h
deleted file mode 100644
index f3a9f23..0000000
--- a/chromeos/ash/experiences/arc/test/fake_arc_dlc_install_notification_delegate.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 CHROMEOS_ASH_EXPERIENCES_ARC_TEST_FAKE_ARC_DLC_INSTALL_NOTIFICATION_DELEGATE_H_
-#define CHROMEOS_ASH_EXPERIENCES_ARC_TEST_FAKE_ARC_DLC_INSTALL_NOTIFICATION_DELEGATE_H_
-
-#include <vector>
-
-#include "chromeos/ash/experiences/arc/dlc_installer/arc_dlc_install_notification_manager.h"
-#include "ui/message_center/public/cpp/notification.h"
-
-namespace arc {
-
-class FakeArcDlcInstallNotificationDelegate
-    : public ArcDlcInstallNotificationManager::Delegate {
- public:
-  FakeArcDlcInstallNotificationDelegate();
-
-  ~FakeArcDlcInstallNotificationDelegate() override;
-
-  void DisplayNotification(
-      const message_center::Notification& notification) override;
-
-  const std::vector<message_center::Notification>& displayed_notifications()
-      const {
-    return displayed_notifications_;
-  }
-
- private:
-  std::vector<message_center::Notification> displayed_notifications_;
-};
-
-}  // namespace arc
-
-#endif  // CHROMEOS_ASH_EXPERIENCES_ARC_TEST_FAKE_ARC_DLC_INSTALL_NOTIFICATION_DELEGATE_H_
diff --git a/chromeos/ash/experiences/arc/test/fake_arc_dlc_notification_manager_factory_impl.cc b/chromeos/ash/experiences/arc/test/fake_arc_dlc_notification_manager_factory_impl.cc
deleted file mode 100644
index 471906a..0000000
--- a/chromeos/ash/experiences/arc/test/fake_arc_dlc_notification_manager_factory_impl.cc
+++ /dev/null
@@ -1,28 +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 "chromeos/ash/experiences/arc/test/fake_arc_dlc_notification_manager_factory_impl.h"
-
-#include <utility>
-
-#include "chromeos/ash/experiences/arc/dlc_installer/arc_dlc_install_notification_manager.h"
-#include "chromeos/ash/experiences/arc/test/fake_arc_dlc_install_notification_delegate.h"
-
-namespace arc {
-
-FakeArcDlcNotificationManagerFactoryImpl::
-    FakeArcDlcNotificationManagerFactoryImpl() = default;
-
-FakeArcDlcNotificationManagerFactoryImpl::
-    ~FakeArcDlcNotificationManagerFactoryImpl() = default;
-
-std::unique_ptr<ArcDlcInstallNotificationManager>
-FakeArcDlcNotificationManagerFactoryImpl::CreateNotificationManager(
-    const AccountId& account_id) {
-  auto delegate = std::make_unique<FakeArcDlcInstallNotificationDelegate>();
-  return std::make_unique<ArcDlcInstallNotificationManager>(std::move(delegate),
-                                                            account_id);
-}
-
-}  // namespace arc
diff --git a/chromeos/ash/experiences/arc/test/fake_arc_dlc_notification_manager_factory_impl.h b/chromeos/ash/experiences/arc/test/fake_arc_dlc_notification_manager_factory_impl.h
deleted file mode 100644
index a6ebbf5c..0000000
--- a/chromeos/ash/experiences/arc/test/fake_arc_dlc_notification_manager_factory_impl.h
+++ /dev/null
@@ -1,37 +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 CHROMEOS_ASH_EXPERIENCES_ARC_TEST_FAKE_ARC_DLC_NOTIFICATION_MANAGER_FACTORY_IMPL_H_
-#define CHROMEOS_ASH_EXPERIENCES_ARC_TEST_FAKE_ARC_DLC_NOTIFICATION_MANAGER_FACTORY_IMPL_H_
-
-#include <memory>
-
-#include "chromeos/ash/experiences/arc/dlc_installer/arc_dlc_notification_manager_factory.h"
-
-class AccountId;
-
-namespace arc {
-
-// This class is a test implementation of ArcDlcNotificationManagerFactory that
-// provides a fake notification manager for ARC DLC installation. It allows
-// tests to simulate notifications without requiring real system interactions.
-class ArcDlcInstallNotificationManager;
-class FakeArcDlcNotificationManagerFactoryImpl
-    : public ArcDlcNotificationManagerFactory {
- public:
-  FakeArcDlcNotificationManagerFactoryImpl();
-
-  FakeArcDlcNotificationManagerFactoryImpl(
-      const FakeArcDlcNotificationManagerFactoryImpl&) = delete;
-  FakeArcDlcNotificationManagerFactoryImpl& operator=(
-      const FakeArcDlcNotificationManagerFactoryImpl&) = delete;
-
-  ~FakeArcDlcNotificationManagerFactoryImpl() override;
-  std::unique_ptr<ArcDlcInstallNotificationManager> CreateNotificationManager(
-      const AccountId& account_id) override;
-};
-
-}  // namespace arc
-
-#endif  // CHROMEOS_ASH_EXPERIENCES_ARC_TEST_FAKE_ARC_DLC_NOTIFICATION_MANAGER_FACTORY_IMPL_H_
diff --git a/chromeos/profiles/arm.afdo.newest.txt b/chromeos/profiles/arm.afdo.newest.txt
index 497d82f..ebdd44f 100644
--- a/chromeos/profiles/arm.afdo.newest.txt
+++ b/chromeos/profiles/arm.afdo.newest.txt
@@ -1 +1 @@
-chromeos-chrome-arm-none-140-7258.8-1752459642-benchmark-140.0.7299.0_pre1487377-r1-redacted.afdo.xz
+chromeos-chrome-arm-none-140-7258.8-1752459642-benchmark-140.0.7304.0_pre1488695-r1-redacted.afdo.xz
diff --git a/chromeos/profiles/atom.afdo.newest.txt b/chromeos/profiles/atom.afdo.newest.txt
index f1d030e..7ec73a8c 100644
--- a/chromeos/profiles/atom.afdo.newest.txt
+++ b/chromeos/profiles/atom.afdo.newest.txt
@@ -1 +1 @@
-chromeos-chrome-amd64-atom-140-7258.33-1752463459-benchmark-140.0.7299.0_pre1487377-r1-redacted.afdo.xz
+chromeos-chrome-amd64-atom-140-7258.33-1752463459-benchmark-140.0.7304.0_pre1488695-r1-redacted.afdo.xz
diff --git a/chromeos/profiles/bigcore.afdo.newest.txt b/chromeos/profiles/bigcore.afdo.newest.txt
index 2bdf9f7..d140bf9 100644
--- a/chromeos/profiles/bigcore.afdo.newest.txt
+++ b/chromeos/profiles/bigcore.afdo.newest.txt
@@ -1 +1 @@
-chromeos-chrome-amd64-bigcore-140-7258.8-1752457593-benchmark-140.0.7299.0_pre1487377-r1-redacted.afdo.xz
+chromeos-chrome-amd64-bigcore-140-7258.8-1752457593-benchmark-140.0.7304.0_pre1488695-r1-redacted.afdo.xz
diff --git a/clank b/clank
index 6d479618..2f10656 160000
--- a/clank
+++ b/clank
@@ -1 +1 @@
-Subproject commit 6d479618f334071254f22d7006cc1328c317f36b
+Subproject commit 2f10656fc4a8dbf6a3f39d2c279a6f882b81d376
diff --git a/components/BUILD.gn b/components/BUILD.gn
index fc3bbe71..333709f 100644
--- a/components/BUILD.gn
+++ b/components/BUILD.gn
@@ -964,6 +964,7 @@
     "$root_gen_dir/components/arc/input_overlay_resources.pak",
     "$root_gen_dir/components/autofill/core/browser/geo/autofill_address_rewriter_resources.pak",
     "$root_gen_dir/components/components_resources.pak",
+    "$root_gen_dir/components/components_resources_100_percent.pak",
     "$root_gen_dir/components/metrics/metrics_server_urls.pak",
     "$root_gen_dir/components/omnibox/resources/omnibox_pedal_synonyms_en-US.pak",
     "$root_gen_dir/components/plus_addresses/resources/strings/plus_addresses_strings_en-US.pak",
diff --git a/components/autofill/core/browser/payments/iban_manager_unittest.cc b/components/autofill/core/browser/payments/iban_manager_unittest.cc
index 6114f3e..eac584eb 100644
--- a/components/autofill/core/browser/payments/iban_manager_unittest.cc
+++ b/components/autofill/core/browser/payments/iban_manager_unittest.cc
@@ -30,7 +30,6 @@
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "ui/base/l10n/l10n_util.h"
-#include "ui/base/resource/mock_resource_bundle_delegate.h"
 #include "ui/gfx/image/image_unittest_util.h"
 
 using testing::_;
@@ -83,17 +82,6 @@
     test_api(*form_structure_).SetFieldTypes({IBAN_VALUE});
     autofill_field_ = form_structure_->field(0);
 
-    ui::ResourceBundle::InitSharedInstanceWithLocale(
-        "en-US", &mock_resource_delegate_,
-        ui::ResourceBundle::DO_NOT_LOAD_COMMON_RESOURCES);
-    if (IsNewFopDisplayEnabled()) {
-      ON_CALL(mock_resource_delegate_, GetImageNamed(IDR_AUTOFILL_IBAN))
-          .WillByDefault(testing::Return(gfx::test::CreateImage(100, 50)));
-    } else {
-      ON_CALL(mock_resource_delegate_, GetImageNamed(IDR_AUTOFILL_IBAN_OLD))
-          .WillByDefault(testing::Return(gfx::test::CreateImage(100, 50)));
-    }
-
     ON_CALL(*autofill_client_.GetAutofillOptimizationGuide(),
             ShouldBlockSingleFieldSuggestions)
         .WillByDefault(testing::Return(false));
@@ -107,10 +95,6 @@
 #endif
   }
 
-  void TearDown() override {
-    ui::ResourceBundle::CleanupSharedInstance();
-  }
-
   // Sets up the TestPersonalDataManager with a local IBAN.
   Iban SetUpLocalIban(std::string_view value, std::string_view nickname) {
     Iban iban;
@@ -185,8 +169,6 @@
   raw_ptr<AutofillField> autofill_field_;
   IbanManager iban_manager_{
       &autofill_client_.GetPersonalDataManager().payments_data_manager()};
-  testing::NiceMock<ui::MockResourceBundleDelegate> mock_resource_delegate_;
-  ui::ResourceBundle::SharedInstanceSwapperForTesting resource_bundle_swapper_;
   base::test::ScopedFeatureList feature_list_metadata_;
 };
 
diff --git a/components/autofill/core/browser/suggestions/payments/iban_suggestion_generator_unittest.cc b/components/autofill/core/browser/suggestions/payments/iban_suggestion_generator_unittest.cc
index a579ed0..5cbccb4 100644
--- a/components/autofill/core/browser/suggestions/payments/iban_suggestion_generator_unittest.cc
+++ b/components/autofill/core/browser/suggestions/payments/iban_suggestion_generator_unittest.cc
@@ -21,7 +21,6 @@
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "ui/base/l10n/l10n_util.h"
-#include "ui/base/resource/mock_resource_bundle_delegate.h"
 #include "ui/gfx/image/image_unittest_util.h"
 
 namespace autofill {
@@ -49,26 +48,11 @@
         test::CreateTestIbanFormData(/*value=*/""));
     test_api(*form_structure_).SetFieldTypes({IBAN_VALUE});
 
-    ui::ResourceBundle::InitSharedInstanceWithLocale(
-        "en-US", &mock_resource_delegate_,
-        ui::ResourceBundle::DO_NOT_LOAD_COMMON_RESOURCES);
-    if (IsNewFopDisplayEnabled()) {
-      ON_CALL(mock_resource_delegate_, GetImageNamed(IDR_AUTOFILL_IBAN))
-          .WillByDefault(testing::Return(gfx::test::CreateImage(100, 50)));
-    } else {
-      ON_CALL(mock_resource_delegate_, GetImageNamed(IDR_AUTOFILL_IBAN_OLD))
-          .WillByDefault(testing::Return(gfx::test::CreateImage(100, 50)));
-    }
-
     ON_CALL(*autofill_client_.GetAutofillOptimizationGuide(),
             ShouldBlockSingleFieldSuggestions)
         .WillByDefault(testing::Return(false));
   }
 
-  ~IbanSuggestionGeneratorTest() override {
-    ui::ResourceBundle::CleanupSharedInstance();
-  }
-
   bool IsNewFopDisplayEnabled() const {
 #if BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_IOS)
     return false;
@@ -156,8 +140,6 @@
   TestAutofillClient autofill_client_;
   std::unique_ptr<PrefService> prefs_;
   std::unique_ptr<FormStructure> form_structure_;
-  testing::NiceMock<ui::MockResourceBundleDelegate> mock_resource_delegate_;
-  ui::ResourceBundle::SharedInstanceSwapperForTesting resource_bundle_swapper_;
   base::test::ScopedFeatureList feature_list_;
 };
 
diff --git a/components/autofill/core/browser/suggestions/payments/payments_suggestion_generator_unittest.cc b/components/autofill/core/browser/suggestions/payments/payments_suggestion_generator_unittest.cc
index cd8da48..658b5744 100644
--- a/components/autofill/core/browser/suggestions/payments/payments_suggestion_generator_unittest.cc
+++ b/components/autofill/core/browser/suggestions/payments/payments_suggestion_generator_unittest.cc
@@ -54,8 +54,6 @@
 #include "components/sync/test/test_sync_service.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "ui/base/l10n/l10n_util.h"
-#include "ui/base/resource/mock_resource_bundle_delegate.h"
-#include "ui/base/resource/resource_bundle.h"
 #include "ui/gfx/image/image.h"
 #include "ui/gfx/image/image_unittest_util.h"
 #include "ui/resources/grit/ui_resources.h"
@@ -226,10 +224,6 @@
   }
 
   void TearDown() override {
-    if (did_set_up_image_resource_for_test_) {
-      CleanUpIbanImageResources();
-      did_set_up_image_resource_for_test_ = false;
-    }
     credit_card_form_event_logger_->OnDestroyed();
   }
 
@@ -259,11 +253,6 @@
 
   gfx::Image CustomIconForTest() { return gfx::test::CreateImage(32, 32); }
 
-  void CleanUpIbanImageResources() {
-    ui::ResourceBundle::CleanupSharedInstance();
-    resource_bundle_swapper_.reset();
-  }
-
   bool VerifyCardArtImageExpectation(Suggestion& suggestion,
                                      const GURL& expected_url,
                                      const gfx::Image& expected_image) {
@@ -298,12 +287,6 @@
   TestBrowserAutofillManager autofill_manager_{&autofill_driver_};
 
  protected:
-  testing::NiceMock<ui::MockResourceBundleDelegate> mock_resource_delegate_;
-  std::unique_ptr<ui::ResourceBundle::SharedInstanceSwapperForTesting>
-      resource_bundle_swapper_;
-  // Tracks whether SetUpIbanImageResources() has been called, so that the
-  // created images can be cleaned up when the test has finished.
-  bool did_set_up_image_resource_for_test_ = false;
   std::unique_ptr<MockCreditCardFormEventLogger> credit_card_form_event_logger_;
 };
 
@@ -2238,22 +2221,6 @@
 
   ~AutofillIbanSuggestionContentTest() override = default;
 
-  void SetUpIbanImageResources() {
-    resource_bundle_swapper_ =
-        std::make_unique<ui::ResourceBundle::SharedInstanceSwapperForTesting>();
-    ui::ResourceBundle::InitSharedInstanceWithLocale(
-        "en-US", &mock_resource_delegate_,
-        ui::ResourceBundle::DO_NOT_LOAD_COMMON_RESOURCES);
-    if (IsNewFopDisplayEnabled()) {
-      ON_CALL(mock_resource_delegate_, GetImageNamed(IDR_AUTOFILL_IBAN))
-          .WillByDefault(testing::Return(CustomIconForTest()));
-    } else {
-      ON_CALL(mock_resource_delegate_, GetImageNamed(IDR_AUTOFILL_IBAN_OLD))
-          .WillByDefault(testing::Return(CustomIconForTest()));
-    }
-    did_set_up_image_resource_for_test_ = true;
-  }
-
   bool IsNewFopDisplayEnabled() const {
 #if BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_IOS)
     return false;
@@ -2271,8 +2238,6 @@
                          ::testing::Bool());
 
 TEST_P(AutofillIbanSuggestionContentTest, GetLocalIbanSuggestions) {
-  SetUpIbanImageResources();
-
   auto MakeLocalIban = [](const std::u16string& value,
                           const std::u16string& nickname) {
     Iban iban(Iban::Guid(base::Uuid::GenerateRandomV4().AsLowercaseString()));
@@ -2326,8 +2291,6 @@
 }
 
 TEST_P(AutofillIbanSuggestionContentTest, GetServerIbanSuggestions) {
-  SetUpIbanImageResources();
-
   Iban server_iban1 = test::GetServerIban();
   Iban server_iban2 = test::GetServerIban2();
   Iban server_iban3 = test::GetServerIban3();
@@ -2366,8 +2329,6 @@
 }
 
 TEST_P(AutofillIbanSuggestionContentTest, GetLocalAndServerIbanSuggestions) {
-  SetUpIbanImageResources();
-
   Iban server_iban1 = test::GetServerIban();
   Iban server_iban2 = test::GetServerIban2();
   Iban local_iban1 = test::GetLocalIban();
diff --git a/components/autofill/core/common/autofill_payments_features.cc b/components/autofill/core/common/autofill_payments_features.cc
index b235cf31..b377752 100644
--- a/components/autofill/core/common/autofill_payments_features.cc
+++ b/components/autofill/core/common/autofill_payments_features.cc
@@ -14,13 +14,6 @@
              base::FEATURE_ENABLED_BY_DEFAULT);
 #endif
 
-// When enabled, cardholder and address names considered during the credit card
-// upload flow will be cleared out if they contain characters considered invalid
-// by Google Payments, such as numbers or various punctuation marks.
-BASE_FEATURE(kAutofillDropNamesWithInvalidCharactersForCardUpload,
-             "AutofillDropNamesWithInvalidCharactersForCardUpload",
-             base::FEATURE_DISABLED_BY_DEFAULT);
-
 // When enabled, card category benefits offered by BMO will be shown in Autofill
 // suggestions on the allowlisted merchant websites.
 BASE_FEATURE(kAutofillEnableAllowlistForBmoCardCategoryBenefits,
diff --git a/components/autofill/core/common/autofill_payments_features.h b/components/autofill/core/common/autofill_payments_features.h
index 0c1d66a..baca278 100644
--- a/components/autofill/core/common/autofill_payments_features.h
+++ b/components/autofill/core/common/autofill_payments_features.h
@@ -18,8 +18,6 @@
 BASE_DECLARE_FEATURE(kAutofillDisableDefaultSaveCardFixFlowDetection);
 #endif
 COMPONENT_EXPORT(AUTOFILL)
-BASE_DECLARE_FEATURE(kAutofillDropNamesWithInvalidCharactersForCardUpload);
-COMPONENT_EXPORT(AUTOFILL)
 BASE_DECLARE_FEATURE(kAutofillEnableAllowlistForBmoCardCategoryBenefits);
 COMPONENT_EXPORT(AUTOFILL)
 BASE_DECLARE_FEATURE(kAutofillEnableAmountExtractionAllowlistDesktop);
diff --git a/components/autofill/ios/form_util/autofill_test_with_web_state.mm b/components/autofill/ios/form_util/autofill_test_with_web_state.mm
index 5c110c5..daf382c 100644
--- a/components/autofill/ios/form_util/autofill_test_with_web_state.mm
+++ b/components/autofill/ios/form_util/autofill_test_with_web_state.mm
@@ -47,7 +47,7 @@
   // although `FormHandlersJavaScriptFeature` is specified, all autofill
   // features must live in the same content world so any one of them could be
   // used here.
-  return web::test::ExecuteJavaScriptForFeature(
+  return web::test::ExecuteJavaScriptForFeatureAndReturnResult(
       web_state(), script,
       autofill::FormHandlersJavaScriptFeature::GetInstance());
 }
diff --git a/components/autofill/ios/form_util/fill_js_unittest.mm b/components/autofill/ios/form_util/fill_js_unittest.mm
index c33189ed..bea90d0 100644
--- a/components/autofill/ios/form_util/fill_js_unittest.mm
+++ b/components/autofill/ios/form_util/fill_js_unittest.mm
@@ -114,14 +114,14 @@
             @"__gCrWeb.fill.getUniqueID(document.getElementById('%@'))",
             element_id];
 
-    id result_id = web::test::ExecuteJavaScriptForFeature(
+    id result_id = web::test::ExecuteJavaScriptForFeatureAndReturnResult(
         web_state(), script, GetDummyFeatureForContentWorld(content_world));
     return base::apple::ObjCCastStrict<NSString>(result_id);
   }
 
   // Runs `script` in the main content world for Autofill features.
   id ExecuteJavaScriptInAutofillContentWorld(NSString* script) {
-    return web::test::ExecuteJavaScriptForFeature(
+    return web::test::ExecuteJavaScriptForFeatureAndReturnResult(
         web_state(), script,
         GetDummyFeatureForContentWorld(
             ContentWorldForAutofillJavascriptFeatures()));
diff --git a/components/certificate_transparency/chrome_require_ct_delegate.cc b/components/certificate_transparency/chrome_require_ct_delegate.cc
index a2599ee..055e3738 100644
--- a/components/certificate_transparency/chrome_require_ct_delegate.cc
+++ b/components/certificate_transparency/chrome_require_ct_delegate.cc
@@ -21,6 +21,7 @@
 #include "base/memory/ref_counted.h"
 #include "base/strings/string_util.h"
 #include "base/task/sequenced_task_runner.h"
+#include "base/types/zip.h"
 #include "base/values.h"
 #include "components/url_formatter/url_fixer.h"
 #include "components/url_matcher/url_matcher.h"
@@ -225,46 +226,26 @@
   if (spkis_.empty())
     return false;
 
-  // TODO(crbug.com/41286522): simplify this to require the input hashes match
-  // the order of the certs in the chain, so that we don't have to jump through
-  // hoops to figure out which certificate's SPKI hash actually matched.
-
-  // Scan the constrained SPKIs via |hashes| first, as an optimization. If
-  // there are matches, the SPKI hash will have to be recomputed anyways to
-  // find the matching certificate, but avoid recomputing all the hashes for
-  // the case where there is no match.
-  std::vector<net::SHA256HashValue> matches;
-  for (const auto& hash : hashes) {
-    if (std::binary_search(spkis_.begin(), spkis_.end(), hash)) {
-      matches.push_back(hash);
-    }
-  }
-  if (matches.empty())
-    return false;
-
-  CRYPTO_BUFFER* leaf_cert = chain->cert_buffer();
-
-  // As an optimization, since the leaf is allowed to be listed as an SPKI,
-  // a match on the leaf's SPKI hash can return early, without comparing
-  // the organization information to itself.
-  net::SHA256HashValue hash;
-  if (net::x509_util::CalculateSha256SpkiHash(leaf_cert, &hash) &&
-      base::Contains(matches, hash)) {
+  if (spkis_.contains(hashes.front())) {
+    // As an optimization, since the leaf is allowed to be listed as an
+    // SPKI, a match on the leaf's SPKI hash can return early, without
+    // comparing the organization information to itself.
     return true;
   }
 
-  // If there was a match (or multiple matches), it's necessary to recompute
-  // the hashes to find the associated certificate.
   std::vector<CRYPTO_BUFFER*> candidates;
-  for (const auto& buffer : chain->intermediate_buffers()) {
-    if (net::x509_util::CalculateSha256SpkiHash(buffer.get(), &hash) &&
-        base::Contains(matches, hash)) {
-      candidates.push_back(buffer.get());
+  auto intermediate_hashes = base::span(hashes).subspan(1u);
+  for (auto [hash, cert_buffer] :
+       base::zip(intermediate_hashes, chain->intermediate_buffers())) {
+    if (spkis_.contains(hash)) {
+      candidates.push_back(cert_buffer.get());
     }
   }
-
-  if (candidates.empty())
+  if (candidates.empty()) {
     return false;
+  }
+
+  CRYPTO_BUFFER* leaf_cert = chain->cert_buffer();
 
   std::shared_ptr<const bssl::ParsedCertificate> parsed_leaf =
       bssl::ParsedCertificate::Create(bssl::UpRef(leaf_cert),
@@ -348,7 +329,7 @@
 
 void ChromeRequireCTDelegate::ParseSpkiHashes(
     const std::vector<std::string> spki_list,
-    std::vector<net::SHA256HashValue>* hashes) const {
+    absl::flat_hash_set<net::SHA256HashValue>* hashes) const {
   hashes->clear();
   for (const auto& value : spki_list) {
     net::HashValue hash;
@@ -358,9 +339,8 @@
     if (hash.tag() != net::HASH_VALUE_SHA256) {
       continue;
     }
-    hashes->push_back(hash.sha256hashvalue());
+    hashes->insert(hash.sha256hashvalue());
   }
-  std::sort(hashes->begin(), hashes->end());
 }
 
 }  // namespace certificate_transparency
diff --git a/components/certificate_transparency/chrome_require_ct_delegate.h b/components/certificate_transparency/chrome_require_ct_delegate.h
index df071ee..3936f61f 100644
--- a/components/certificate_transparency/chrome_require_ct_delegate.h
+++ b/components/certificate_transparency/chrome_require_ct_delegate.h
@@ -14,6 +14,7 @@
 #include "components/url_matcher/url_matcher.h"
 #include "net/base/hash_value.h"
 #include "net/cert/require_ct_delegate.h"
+#include "third_party/abseil-cpp/absl/container/flat_hash_set.h"
 
 namespace net {
 class X509Certificate;
@@ -79,17 +80,16 @@
   void AddFilters(const std::vector<std::string>& host_patterns,
                   url_matcher::URLMatcherConditionSet::Vector* conditions);
 
-  // Parses the SPKIs from |spki_list|, setting |*hashes| to the sorted set of
-  // all valid SPKIs.
+  // Parses the SPKIs from |spki_list|, setting |*hashes| to the set of all
+  // valid SPKIs.
   void ParseSpkiHashes(const std::vector<std::string> spki_list,
-                       std::vector<net::SHA256HashValue>* hashes) const;
+                       absl::flat_hash_set<net::SHA256HashValue>* hashes) const;
 
   std::unique_ptr<url_matcher::URLMatcher> url_matcher_;
   base::MatcherStringPattern::ID next_id_;
   std::map<base::MatcherStringPattern::ID, Filter> filters_;
 
-  // SPKI list is sorted.
-  std::vector<net::SHA256HashValue> spkis_;
+  absl::flat_hash_set<net::SHA256HashValue> spkis_;
 };
 
 }  // namespace certificate_transparency
diff --git a/components/collaboration/internal/collaboration_controller.cc b/components/collaboration/internal/collaboration_controller.cc
index 4924138..ac2738d 100644
--- a/components/collaboration/internal/collaboration_controller.cc
+++ b/components/collaboration/internal/collaboration_controller.cc
@@ -302,6 +302,17 @@
       return;
     }
 
+    // If neither sign in nor sync has been disabled by the enterprise and the
+    // user is not trying to join, allow it.
+    bool signin_enabled = status.signin_status != SigninStatus::kSigninDisabled;
+    bool sync_enabled =
+        status.sync_status != SyncStatus::kSyncDisabledByEnterprise;
+    bool is_join_flow = controller_->flow().type == FlowType::kJoin;
+    if (signin_enabled && sync_enabled && !is_join_flow) {
+      OnProcessingFinishedWithSuccess();
+      return;
+    }
+
     HandleError();
   }
 
@@ -309,16 +320,32 @@
     DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
     ServiceStatus status =
         controller_->collaboration_service()->GetServiceStatus();
-    if (status.signin_status != SigninStatus::kSigninDisabled) {
+
+    if (status.signin_status == SigninStatus::kSigninDisabled) {
       RecordJoinOrShareOrManageEvent(
           GetLogger(), controller_->flow().type,
-          CollaborationServiceJoinEvent::kManagedAccountSignin,
-          CollaborationServiceShareOrManageEvent::kManagedAccountSignin);
+          CollaborationServiceJoinEvent::kDevicePolicyDisableSignin,
+          CollaborationServiceShareOrManageEvent::kDevicePolicyDisableSignin);
       RecordCollaborationFlowEvent(
           GetLogger(), controller_->flow().type,
-          CollaborationServiceFlowEvent::kManagedAccountSignin);
+          CollaborationServiceFlowEvent::kDevicePolicyDisableSignin);
+      HandleErrorWithType(ErrorInfo::Type::kSigninDisabledByPolicy);
+      return;
     }
-    controller_->TransitionForEnterprisePolicy(status);
+
+    RecordJoinOrShareOrManageEvent(
+        GetLogger(), controller_->flow().type,
+        CollaborationServiceJoinEvent::kManagedAccountSignin,
+        CollaborationServiceShareOrManageEvent::kManagedAccountSignin);
+    RecordCollaborationFlowEvent(
+        GetLogger(), controller_->flow().type,
+        CollaborationServiceFlowEvent::kManagedAccountSignin);
+
+    if (status.sync_status == SyncStatus::kSyncDisabledByEnterprise) {
+      HandleErrorWithType(ErrorInfo::Type::kSyncDisabledByPolicy);
+    } else if (controller_->flow().type == FlowType::kJoin) {
+      HandleErrorWithType(ErrorInfo::Type::kSharingDisabledByPolicy);
+    }
   }
 
   void OnProcessingFinishedWithSuccess() override {
@@ -1505,27 +1532,17 @@
           CollaborationStatus::kDisabledForPolicy &&
       update.new_status.collaboration_status ==
           CollaborationStatus::kDisabledForPolicy) {
-    TransitionForEnterprisePolicy(update.new_status);
-  }
-}
-
-void CollaborationController::TransitionForEnterprisePolicy(
-    ServiceStatus status) {
-  if (status.signin_status == SigninStatus::kSigninDisabled) {
-    RecordJoinOrShareOrManageEvent(
-        data_sharing_service()->GetLogger(), flow().type,
-        CollaborationServiceJoinEvent::kDevicePolicyDisableSignin,
-        CollaborationServiceShareOrManageEvent::kDevicePolicyDisableSignin);
-    RecordCollaborationFlowEvent(
-        data_sharing_service()->GetLogger(), flow().type,
-        CollaborationServiceFlowEvent::kDevicePolicyDisableSignin);
-    current_state_->HandleErrorWithType(
-        ErrorInfo::Type::kSigninDisabledByPolicy);
-  } else if (status.sync_status == SyncStatus::kSyncDisabledByEnterprise) {
-    current_state_->HandleErrorWithType(ErrorInfo::Type::kSyncDisabledByPolicy);
-  } else {
-    current_state_->HandleErrorWithType(
-        ErrorInfo::Type::kSharingDisabledByPolicy);
+    if (update.new_status.signin_status == SigninStatus::kSigninDisabled) {
+      current_state_->HandleErrorWithType(
+          ErrorInfo::Type::kSigninDisabledByPolicy);
+    } else if (update.new_status.sync_status ==
+               SyncStatus::kSyncDisabledByEnterprise) {
+      current_state_->HandleErrorWithType(
+          ErrorInfo::Type::kSyncDisabledByPolicy);
+    } else {
+      current_state_->HandleErrorWithType(
+          ErrorInfo::Type::kSharingDisabledByPolicy);
+    }
   }
 }
 
diff --git a/components/collaboration/internal/collaboration_controller.h b/components/collaboration/internal/collaboration_controller.h
index c70fb1a..4ffa8770 100644
--- a/components/collaboration/internal/collaboration_controller.h
+++ b/components/collaboration/internal/collaboration_controller.h
@@ -202,9 +202,6 @@
       const CollaborationService::Observer::ServiceStatusUpdate& update)
       override;
 
-  // Called when enterprise policy disabled the feature.
-  void TransitionForEnterprisePolicy(ServiceStatus status);
-
  private:
   static constexpr std::array<std::pair<StateId, StateId>, 41>
       kValidTransitions = {{
diff --git a/components/collaboration/internal/collaboration_controller_unittest.cc b/components/collaboration/internal/collaboration_controller_unittest.cc
index 5a33eab..d54f097 100644
--- a/components/collaboration/internal/collaboration_controller_unittest.cc
+++ b/components/collaboration/internal/collaboration_controller_unittest.cc
@@ -385,7 +385,7 @@
   EXPECT_EQ(controller_->GetStateForTesting(), StateId::kError);
 }
 
-TEST_F(CollaborationControllerTest, JoinFlowManagedAccount) {
+TEST_F(CollaborationControllerTest, JoinFlowManagedAccountSharingDisabled) {
   // Start Join flow.
   InitializeJoinController(base::DoNothing());
 
@@ -426,6 +426,38 @@
   EXPECT_EQ(controller_->GetStateForTesting(), StateId::kError);
 }
 
+TEST_F(CollaborationControllerTest, ManageFlowManagedAccountSharingDisabled) {
+  // Start manage flow with a local shared tab group.
+  tab_groups::LocalTabGroupID local_id =
+      tab_groups::test::GenerateRandomTabGroupID();
+  tab_groups::EitherGroupID either_id = local_id;
+  SavedTabGroup tab_group(std::u16string(u"title"),
+                          tab_groups::TabGroupColorId::kGrey, {});
+  tab_group.SetLocalGroupId(local_id);
+  tab_group.SetCollaborationId(tab_groups::CollaborationId(kGroupId.value()));
+  EXPECT_CALL(*tab_group_sync_service_, GetGroup(either_id))
+      .WillRepeatedly(Return(tab_group));
+
+  InitializeController(base::DoNothing(),
+                       Flow(FlowType::kShareOrManage, local_id));
+
+  // 1. Pending state.
+  EXPECT_EQ(controller_->GetStateForTesting(), StateId::kPending);
+
+  // Simulate managed device sharing disabled.
+  ServiceStatus status;
+  status.signin_status = SigninStatus::kSignedIn;
+  status.sync_status = SyncStatus::kSyncEnabled;
+  status.collaboration_status = CollaborationStatus::kDisabledForPolicy;
+  EXPECT_CALL(*collaboration_service_, GetServiceStatus())
+      .WillRepeatedly(Return(status));
+
+  // The share flow is allowed to proceed to share screen.
+  // 2. Pending -> Showing share screen state.
+  std::move(prepare_ui_callback_).Run(Outcome::kSuccess);
+  EXPECT_EQ(controller_->GetStateForTesting(), StateId::kShowingManageScreen);
+}
+
 TEST_F(CollaborationControllerTest, UrlHandlingError) {
   RunLoop run_loop;
   // Start Join flow.
diff --git a/components/collaboration/public/android/java/src/org/chromium/components/collaboration/ServiceStatus.java b/components/collaboration/public/android/java/src/org/chromium/components/collaboration/ServiceStatus.java
index 87d35f2..110948d 100644
--- a/components/collaboration/public/android/java/src/org/chromium/components/collaboration/ServiceStatus.java
+++ b/components/collaboration/public/android/java/src/org/chromium/components/collaboration/ServiceStatus.java
@@ -43,8 +43,8 @@
         switch (collaborationStatus) {
             case CollaborationStatus.DISABLED:
             case CollaborationStatus.DISABLED_PENDING:
-            case CollaborationStatus.DISABLED_FOR_POLICY:
                 return false;
+            case CollaborationStatus.DISABLED_FOR_POLICY:
             case CollaborationStatus.ALLOWED_TO_JOIN:
             case CollaborationStatus.ENABLED_JOIN_ONLY:
             case CollaborationStatus.ENABLED_CREATE_AND_JOIN:
diff --git a/components/collaboration_strings.grdp b/components/collaboration_strings.grdp
index ce63014..8ee0f666 100644
--- a/components/collaboration_strings.grdp
+++ b/components/collaboration_strings.grdp
@@ -229,4 +229,13 @@
   </message>
   <!-- Instant Messages -->
 
+  <!-- Comments -->
+  <!-- Excluded from Android to pass LINT check until it needs to be used there. -->
+  <if expr="not is_android">
+    <message name="IDS_COLLABORATION_SHARED_TAB_GROUPS_COMMENTS_TITLE" desc="Title of the comments side panel." formatter_data="android_java">
+      Comments
+    </message>
+  </if>
+  <!-- Comments -->
+
 </grit-part>
diff --git a/components/collaboration_strings_grdp/IDS_COLLABORATION_SHARED_TAB_GROUPS_COMMENTS_TITLE.png.sha1 b/components/collaboration_strings_grdp/IDS_COLLABORATION_SHARED_TAB_GROUPS_COMMENTS_TITLE.png.sha1
new file mode 100644
index 0000000..e863745
--- /dev/null
+++ b/components/collaboration_strings_grdp/IDS_COLLABORATION_SHARED_TAB_GROUPS_COMMENTS_TITLE.png.sha1
@@ -0,0 +1 @@
+6c4cc7d7d64cdbd2559c36c3520f0b1664fcae9e
\ No newline at end of file
diff --git a/components/enterprise/connectors/core/reporting_constants.h b/components/enterprise/connectors/core/reporting_constants.h
index afc7c41a..1f158ff 100644
--- a/components/enterprise/connectors/core/reporting_constants.h
+++ b/components/enterprise/connectors/core/reporting_constants.h
@@ -37,6 +37,11 @@
 inline constexpr char kEnterpriseBlockedSeenThreatType[] =
     "ENTERPRISE_BLOCKED_SEEN";
 
+inline constexpr char kDangerousDownloadThreatType[] = "DANGEROUS";
+inline constexpr char kPotentiallyUnwantedDownloadThreatType[] =
+    "POTENTIALLY_UNWANTED";
+inline constexpr char kUnknownDownloadThreatType[] = "UNKNOWN";
+
 // TODO(crbug.com/432065125): Use these constants for event reporting and delete
 // the duplicates.
 inline constexpr char kFilePasswordProtectedUnscannedReason[] =
@@ -176,7 +181,6 @@
 inline constexpr char kKeyDestination[] = "destination";
 inline constexpr char kKeyDownloadDigestSha256[] = "downloadDigestSha256";
 inline constexpr char kKeyFileName[] = "fileName";
-inline constexpr char kKeyWebAppSignedInAccount[] = "webAppSignedInAccount";
 inline constexpr char kKeyContentType[] = "contentType";
 inline constexpr char kKeyUnscannedReason[] = "unscannedReason";
 inline constexpr char kKeyContentSize[] = "contentSize";
@@ -197,10 +201,14 @@
 inline constexpr char kKeyAction[] = "action";
 inline constexpr char kKeyHasWatermarking[] = "hasWatermarking";
 inline constexpr char kKeyReason[] = "reason";
+inline constexpr char kKeyScanId[] = "scanId";
 inline constexpr char kKeyNetErrorCode[] = "netErrorCode";
 inline constexpr char kKeyUserName[] = "userName";
 inline constexpr char kKeyIsPhishingUrl[] = "isPhishingUrl";
 inline constexpr char kKeyReferrers[] = "referrers";
+inline constexpr char kKeySourceWebAppSignedInAccount[] =
+    "sourceWebAppSignedInAccount";
+inline constexpr char kKeyWebAppSignedInAccount[] = "webAppSignedInAccount";
 
 enum EnterpriseRealTimeUrlCheckMode {
   REAL_TIME_CHECK_DISABLED = 0,
diff --git a/components/enterprise/connectors/core/reporting_event_router.cc b/components/enterprise/connectors/core/reporting_event_router.cc
index 9225e898d..20215d12 100644
--- a/components/enterprise/connectors/core/reporting_event_router.cc
+++ b/components/enterprise/connectors/core/reporting_event_router.cc
@@ -38,6 +38,34 @@
   return false;
 }
 
+void AddAnalysisConnectorVerdictToEvent(
+    const enterprise_connectors::ContentAnalysisResponse::Result& result,
+    base::Value::Dict& event) {
+  base::Value::List triggered_rule_info;
+  for (const enterprise_connectors::TriggeredRule& trigger :
+       result.triggered_rules()) {
+    base::Value::Dict triggered_rule;
+    triggered_rule.Set(kKeyTriggeredRuleName, trigger.rule_name());
+    int rule_id_int = 0;
+    if (base::StringToInt(trigger.rule_id(), &rule_id_int)) {
+      triggered_rule.Set(kKeyTriggeredRuleId, rule_id_int);
+    }
+    triggered_rule.Set(kKeyUrlCategory, trigger.url_category());
+
+    triggered_rule_info.Append(std::move(triggered_rule));
+  }
+  event.Set(kKeyTriggeredRuleInfo, std::move(triggered_rule_info));
+}
+
+std::string MalwareRuleToThreatType(const std::string& rule_name) {
+  if (rule_name == "uws") {
+    return kPotentiallyUnwantedDownloadThreatType;
+  } else if (rule_name == "malware") {
+    return kDangerousDownloadThreatType;
+  } else {
+    return kUnknownDownloadThreatType;
+  }
+}
 }  // namespace
 
 ReportingEventRouter::ReportingEventRouter(
@@ -443,6 +471,171 @@
   }
 }
 
+void ReportingEventRouter::OnSensitiveDataEvent(
+    const GURL& url,
+    const GURL& tab_url,
+    const std::string& source,
+    const std::string& destination,
+    const std::string& file_name,
+    const std::string& download_digest_sha256,
+    const std::string& mime_type,
+    const std::string& trigger,
+    const std::string& scan_id,
+    const std::string& content_transfer_method,
+    const std::string& source_email,
+    const std::string& content_area_account_email,
+    const enterprise_connectors::ContentAnalysisResponse::Result& result,
+    const int64_t content_size,
+    const ReferrerChain& referrer_chain,
+    enterprise_connectors::EventResult event_result) {
+  std::optional<enterprise_connectors::ReportingSettings> settings =
+      reporting_client_->GetReportingSettings();
+  if (!settings.has_value() ||
+      settings->enabled_event_names.count(
+          enterprise_connectors::kKeySensitiveDataEvent) == 0) {
+    return;
+  }
+
+  base::Value::Dict event;
+  event.Set(kKeyUrl, url.spec());
+  event.Set(kKeyTabUrl, tab_url.spec());
+  event.Set(kKeySource, source);
+  event.Set(kKeyDestination, destination);
+  event.Set(kKeyFileName,
+            GetFileName(file_name, reporting_client_->ShouldIncludeDeviceInfo(
+                                       settings->per_profile)));
+  event.Set(kKeyDownloadDigestSha256, download_digest_sha256);
+  event.Set(kKeyContentType, mime_type);
+  // |content_size| can be set to -1 to indicate an unknown size, in
+  // which case the field is not set.
+  if (content_size >= 0) {
+    event.Set(kKeyContentSize, base::Int64ToValue(content_size));
+  }
+  event.Set(kKeyTrigger, trigger);
+
+  if (base::FeatureList::IsEnabled(safe_browsing::kEnhancedFieldsForSecOps)) {
+    enterprise_connectors::AddReferrerChainToEvent(referrer_chain, event);
+  }
+
+  event.Set(kKeyEventResult,
+            enterprise_connectors::EventResultToString(event_result));
+  event.Set(kKeyClickedThrough,
+            event_result == enterprise_connectors::EventResult::BYPASSED);
+  event.Set(kKeyScanId, scan_id);
+
+  if (!content_transfer_method.empty()) {
+    event.Set(kKeyContentTransferMethod, content_transfer_method);
+  }
+  if (!content_area_account_email.empty()) {
+    event.Set(kKeyWebAppSignedInAccount, content_area_account_email);
+  }
+  if (!source_email.empty()) {
+    event.Set(kKeySourceWebAppSignedInAccount, source_email);
+  }
+
+  AddAnalysisConnectorVerdictToEvent(result, event);
+
+  reporting_client_->ReportEventWithTimestampDeprecated(
+      enterprise_connectors::kKeySensitiveDataEvent,
+      std::move(settings.value()), std::move(event), base::Time::Now(),
+      /*include_profile_user_name=*/true);
+}
+
+void ReportingEventRouter::OnDangerousDeepScanningResult(
+    const GURL& url,
+    const GURL& tab_url,
+    const std::string& source,
+    const std::string& destination,
+    const std::string& file_name,
+    const std::string& download_digest_sha256,
+    const std::string& threat_type,
+    const std::string& mime_type,
+    const std::string& trigger,
+    const int64_t content_size,
+    const ReferrerChain& referrer_chain,
+    enterprise_connectors::EventResult event_result,
+    const std::string& scan_id,
+    const std::string& content_transfer_method) {
+  std::optional<enterprise_connectors::ReportingSettings> settings =
+      reporting_client_->GetReportingSettings();
+  if (!settings.has_value() ||
+      settings->enabled_event_names.count(
+          enterprise_connectors::kKeyDangerousDownloadEvent) == 0) {
+    return;
+  }
+
+  base::Value::Dict event;
+  event.Set(kKeyUrl, url.spec());
+  event.Set(kKeyTabUrl, tab_url.spec());
+  event.Set(kKeySource, source);
+  event.Set(kKeyDestination, destination);
+  event.Set(kKeyFileName,
+            GetFileName(file_name, reporting_client_->ShouldIncludeDeviceInfo(
+                                       settings->per_profile)));
+  event.Set(kKeyDownloadDigestSha256, download_digest_sha256);
+  event.Set(kKeyThreatType, threat_type);
+  event.Set(kKeyContentType, mime_type);
+  // |content_size| can be set to -1 to indicate an unknown size, in
+  // which case the field is not set.
+  if (content_size >= 0) {
+    event.Set(kKeyContentSize, base::Int64ToValue(content_size));
+  }
+  event.Set(kKeyTrigger, trigger);
+  if (base::FeatureList::IsEnabled(safe_browsing::kEnhancedFieldsForSecOps)) {
+    enterprise_connectors::AddReferrerChainToEvent(referrer_chain, event);
+  }
+  event.Set(kKeyEventResult,
+            enterprise_connectors::EventResultToString(event_result));
+  event.Set(kKeyClickedThrough,
+            event_result == enterprise_connectors::EventResult::BYPASSED);
+  // The scan ID can be empty when the reported dangerous download is from a
+  // Safe Browsing verdict.
+  if (!scan_id.empty()) {
+    event.Set(kKeyScanId, scan_id);
+  }
+  if (!content_transfer_method.empty()) {
+    event.Set(kKeyContentTransferMethod, content_transfer_method);
+  }
+
+  reporting_client_->ReportEventWithTimestampDeprecated(
+      enterprise_connectors::kKeyDangerousDownloadEvent,
+      std::move(settings.value()), std::move(event), base::Time::Now(),
+      /*include_profile_user_name=*/true);
+}
+
+void ReportingEventRouter::OnAnalysisConnectorResult(
+    const GURL& url,
+    const GURL& tab_url,
+    const std::string& source,
+    const std::string& destination,
+    const std::string& file_name,
+    const std::string& download_digest_sha256,
+    const std::string& mime_type,
+    const std::string& trigger,
+    const std::string& scan_id,
+    const std::string& content_transfer_method,
+    const std::string& source_email,
+    const std::string& content_area_account_email,
+    const enterprise_connectors::ContentAnalysisResponse::Result& result,
+    const int64_t content_size,
+    const ReferrerChain& referrer_chain,
+    enterprise_connectors::EventResult event_result) {
+  if (result.tag() == "malware") {
+    DCHECK_EQ(1, result.triggered_rules().size());
+    OnDangerousDeepScanningResult(
+        url, tab_url, source, destination, file_name, download_digest_sha256,
+        MalwareRuleToThreatType(result.triggered_rules(0).rule_name()),
+        mime_type, trigger, content_size, referrer_chain, event_result, scan_id,
+        content_transfer_method);
+  } else if (result.tag() == "dlp") {
+    OnSensitiveDataEvent(url, tab_url, source, destination, file_name,
+                         download_digest_sha256, mime_type, trigger, scan_id,
+                         content_transfer_method, source_email,
+                         content_area_account_email, result, content_size,
+                         referrer_chain, event_result);
+  }
+}
+
 // static
 std::string ReportingEventRouter::GetFileName(const std::string& filename,
                                               const bool include_full_path) {
diff --git a/components/enterprise/connectors/core/reporting_event_router.h b/components/enterprise/connectors/core/reporting_event_router.h
index d0c6a02..c28ed5a 100644
--- a/components/enterprise/connectors/core/reporting_event_router.h
+++ b/components/enterprise/connectors/core/reporting_event_router.h
@@ -90,6 +90,61 @@
                             const int64_t content_size,
                             EventResult event_result);
 
+  // Notifies listeners that the analysis connector detected a violation.
+  void OnSensitiveDataEvent(
+      const GURL& url,
+      const GURL& tab_url,
+      const std::string& source,
+      const std::string& destination,
+      const std::string& file_name,
+      const std::string& download_digest_sha256,
+      const std::string& mime_type,
+      const std::string& trigger,
+      const std::string& scan_id,
+      const std::string& content_transfer_method,
+      const std::string& source_email,
+      const std::string& content_area_account_email,
+      const enterprise_connectors::ContentAnalysisResponse::Result& result,
+      const int64_t content_size,
+      const ReferrerChain& referrer_chain,
+      enterprise_connectors::EventResult event_result);
+
+  // Notifies listeners that deep scanning detected a dangerous download.
+  void OnDangerousDeepScanningResult(
+      const GURL& download_url,
+      const GURL& tab_url,
+      const std::string& source,
+      const std::string& destination,
+      const std::string& file_name,
+      const std::string& download_digest_sha256,
+      const std::string& threat_type,
+      const std::string& mime_type,
+      const std::string& trigger,
+      const int64_t content_size,
+      const ReferrerChain& referrer_chain,
+      enterprise_connectors::EventResult event_result,
+      const std::string& scan_id,
+      const std::string& content_transfer_method);
+
+  // Notifies listeners that the analysis connector detected a violation.
+  void OnAnalysisConnectorResult(
+      const GURL& url,
+      const GURL& tab_url,
+      const std::string& source,
+      const std::string& destination,
+      const std::string& file_name,
+      const std::string& download_digest_sha256,
+      const std::string& mime_type,
+      const std::string& trigger,
+      const std::string& scan_id,
+      const std::string& content_transfer_method,
+      const std::string& source_email,
+      const std::string& content_area_account_email,
+      const enterprise_connectors::ContentAnalysisResponse::Result& result,
+      const int64_t content_size,
+      const ReferrerChain& referrer_chain,
+      enterprise_connectors::EventResult event_result);
+
  private:
   // Returns filename with full path if full path is required;
   // Otherwise returns only the basename without full path.
diff --git a/components/facilitated_payments/android/device_delegate_android.cc b/components/facilitated_payments/android/device_delegate_android.cc
index b1a72693..b89db177 100644
--- a/components/facilitated_payments/android/device_delegate_android.cc
+++ b/components/facilitated_payments/android/device_delegate_android.cc
@@ -58,6 +58,10 @@
 
 void DeviceDelegateAndroid::OnApplicationStateChanged(
     base::android::ApplicationState state) {
+  if (on_application_state_changed_callback_for_testing_) {
+    std::move(on_application_state_changed_callback_for_testing_).Run();
+  }
+
   // The observer is initialized only after setting the callback, and is reset
   // after the callback is run.
   CHECK(on_return_to_chrome_callback_);
diff --git a/components/facilitated_payments/android/device_delegate_android.h b/components/facilitated_payments/android/device_delegate_android.h
index d7e3a93f..379a37c 100644
--- a/components/facilitated_payments/android/device_delegate_android.h
+++ b/components/facilitated_payments/android/device_delegate_android.h
@@ -57,6 +57,9 @@
   bool is_chrome_in_background_ = false;
   // Callback to be called when Chrome comes back to the foreground.
   base::OnceClosure on_return_to_chrome_callback_;
+  // A test-only callback that is run when `OnApplicationStateChanged` is
+  // called.
+  base::OnceClosure on_application_state_changed_callback_for_testing_;
 
   base::WeakPtrFactory<DeviceDelegateAndroid> weak_ptr_factory_{this};
 };
diff --git a/components/facilitated_payments/android/device_delegate_android_test_api.h b/components/facilitated_payments/android/device_delegate_android_test_api.h
index a76ec9f..f691ad18 100644
--- a/components/facilitated_payments/android/device_delegate_android_test_api.h
+++ b/components/facilitated_payments/android/device_delegate_android_test_api.h
@@ -7,6 +7,7 @@
 
 #include "base/android/application_status_listener.h"
 #include "base/check_deref.h"
+#include "base/functional/callback.h"
 #include "base/memory/raw_ref.h"
 #include "components/facilitated_payments/android/device_delegate_android.h"
 
@@ -21,10 +22,12 @@
       delete;
   ~DeviceDelegateAndroidTestApi() = default;
 
-  // Calls the underlying DeviceDelegateAndroid's private methods.
-  void OnApplicationStateChanged(base::android::ApplicationState state) {
-    delegate_->OnApplicationStateChanged(state);
+  void SetOnApplicationStateChangedCallbackForTesting(
+      base::OnceClosure callback) {
+    delegate_->on_application_state_changed_callback_for_testing_ =
+        std::move(callback);
   }
+
   base::android::ApplicationStatusListener* app_status_listener() {
     return delegate_->app_status_listener_.get();
   }
diff --git a/components/facilitated_payments/android/device_delegate_android_unittest.cc b/components/facilitated_payments/android/device_delegate_android_unittest.cc
index 07716479..6449cb2 100644
--- a/components/facilitated_payments/android/device_delegate_android_unittest.cc
+++ b/components/facilitated_payments/android/device_delegate_android_unittest.cc
@@ -6,6 +6,7 @@
 
 #include "base/android/application_status_listener.h"
 #include "base/functional/callback_helpers.h"
+#include "base/run_loop.h"
 #include "base/test/mock_callback.h"
 #include "components/facilitated_payments/android/device_delegate_android_test_api.h"
 #include "content/public/test/test_renderer_host.h"
@@ -58,9 +59,13 @@
 
   EXPECT_CALL(mock_callback, Run).Times(0);
 
-  test_api().OnApplicationStateChanged(
+  base::RunLoop run_loop;
+  test_api().SetOnApplicationStateChangedCallbackForTesting(
+      run_loop.QuitClosure());
+  base::android::ApplicationStatusListener::NotifyApplicationStateChange(
       base::android::ApplicationState::
           APPLICATION_STATE_HAS_RUNNING_ACTIVITIES);
+  run_loop.Run();
 }
 
 // The current activity can be paused by actions like opening the Settings page.
@@ -74,11 +79,15 @@
 
   EXPECT_CALL(mock_callback, Run).Times(0);
 
-  test_api().OnApplicationStateChanged(
+  base::RunLoop run_loop;
+  test_api().SetOnApplicationStateChangedCallbackForTesting(
+      run_loop.QuitWhenIdleClosure());
+  base::android::ApplicationStatusListener::NotifyApplicationStateChange(
       base::android::ApplicationState::APPLICATION_STATE_HAS_PAUSED_ACTIVITIES);
-  test_api().OnApplicationStateChanged(
+  base::android::ApplicationStatusListener::NotifyApplicationStateChange(
       base::android::ApplicationState::
           APPLICATION_STATE_HAS_RUNNING_ACTIVITIES);
+  run_loop.Run();
 }
 
 TEST_F(DeviceDelegateAndroidTest,
@@ -89,12 +98,16 @@
 
   EXPECT_CALL(mock_callback, Run);
 
-  test_api().OnApplicationStateChanged(
+  base::RunLoop run_loop;
+  test_api().SetOnApplicationStateChangedCallbackForTesting(
+      run_loop.QuitWhenIdleClosure());
+  base::android::ApplicationStatusListener::NotifyApplicationStateChange(
       base::android::ApplicationState::
           APPLICATION_STATE_HAS_STOPPED_ACTIVITIES);
-  test_api().OnApplicationStateChanged(
+  base::android::ApplicationStatusListener::NotifyApplicationStateChange(
       base::android::ApplicationState::
           APPLICATION_STATE_HAS_RUNNING_ACTIVITIES);
+  run_loop.Run();
 }
 
 TEST_F(DeviceDelegateAndroidTest,
@@ -102,12 +115,16 @@
   delegate()->SetOnReturnToChromeCallbackAndObserveAppState(base::DoNothing());
   ASSERT_TRUE(test_api().app_status_listener());
 
-  test_api().OnApplicationStateChanged(
+  base::RunLoop run_loop;
+  test_api().SetOnApplicationStateChangedCallbackForTesting(
+      run_loop.QuitWhenIdleClosure());
+  base::android::ApplicationStatusListener::NotifyApplicationStateChange(
       base::android::ApplicationState::
           APPLICATION_STATE_HAS_STOPPED_ACTIVITIES);
-  test_api().OnApplicationStateChanged(
+  base::android::ApplicationStatusListener::NotifyApplicationStateChange(
       base::android::ApplicationState::
           APPLICATION_STATE_HAS_RUNNING_ACTIVITIES);
+  run_loop.Run();
 
   EXPECT_FALSE(test_api().app_status_listener());
 }
diff --git a/components/facilitated_payments/core/browser/pix_account_linking_manager.cc b/components/facilitated_payments/core/browser/pix_account_linking_manager.cc
index 1c5375b2..023ff5a 100644
--- a/components/facilitated_payments/core/browser/pix_account_linking_manager.cc
+++ b/components/facilitated_payments/core/browser/pix_account_linking_manager.cc
@@ -140,7 +140,8 @@
 }
 
 void PixAccountLinkingManager::OnDeclined() {
-  // TODO(crbug.com/419108993): Add metrics.
+  LogPixAccountLinkingFlowExitedReason(
+      PixAccountLinkingFlowExitedReason::kUserDeclined);
   DismissPrompt();
   client_->GetPaymentsDataManager()
       ->SetFacilitatedPaymentsPixAccountLinkingUserPref(/* enabled= */ false);
@@ -155,24 +156,23 @@
     }
     case UiEvent::kScreenCouldNotBeShown: {
       CHECK(is_prompt_showing_);
-      // TODO(crbug.com/419108993): Log that the prompt show failed.
+      LogPixAccountLinkingFlowExitedReason(
+          PixAccountLinkingFlowExitedReason::kScreenNotShown);
       is_prompt_showing_ = false;
       break;
     }
     case UiEvent::kScreenClosedNotByUser: {
       if (is_prompt_showing_) {
-        // TODO(crbug.com/419108993): Log that the prompt was closed
-        // unexpectedly.
+        LogPixAccountLinkingFlowExitedReason(
+            PixAccountLinkingFlowExitedReason::kScreenClosedNotByUser);
       }
-      // TODO(crbug.com/419108993): Add specific logging for Pix Account Linking
-      // prompt closed not by user.
       is_prompt_showing_ = false;
       break;
     }
     case UiEvent::kScreenClosedByUser: {
       CHECK(is_prompt_showing_);
-      // TODO(crbug.com/419108993): Add specific logging for Pix Account Linking
-      // prompt closed by user.
+      LogPixAccountLinkingFlowExitedReason(
+          PixAccountLinkingFlowExitedReason::kScreenClosedByUser);
       is_prompt_showing_ = false;
       break;
     }
diff --git a/components/facilitated_payments/core/browser/pix_account_linking_manager_unittest.cc b/components/facilitated_payments/core/browser/pix_account_linking_manager_unittest.cc
index a57198a4..8b26d889 100644
--- a/components/facilitated_payments/core/browser/pix_account_linking_manager_unittest.cc
+++ b/components/facilitated_payments/core/browser/pix_account_linking_manager_unittest.cc
@@ -17,6 +17,7 @@
 #include "components/facilitated_payments/core/browser/mock_facilitated_payments_client.h"
 #include "components/facilitated_payments/core/browser/network_api/mock_facilitated_payments_network_interface.h"
 #include "components/facilitated_payments/core/browser/pix_account_linking_manager_test_api.h"
+#include "components/facilitated_payments/core/metrics/facilitated_payments_metrics.h"
 #include "components/signin/public/identity_manager/identity_manager.h"
 #include "components/signin/public/identity_manager/identity_test_environment.h"
 #include "components/sync/test/test_sync_service.h"
@@ -321,7 +322,7 @@
   test_api().OnUiScreenEvent(UiEvent::kNewScreenShown);
 
   histogram_tester.ExpectUniqueSample(
-      "FacilitatedPayments.Pix.AccountLinkingPromptShown",
+      "FacilitatedPayments.Pix.AccountLinking.PromptShown",
       /*sample=*/true,
       /*expected_bucket_count=*/1);
 }
@@ -333,7 +334,7 @@
   test_api().OnUiScreenEvent(UiEvent::kScreenCouldNotBeShown);
 
   histogram_tester.ExpectUniqueSample(
-      "FacilitatedPayments.Pix.AccountLinkingPromptShown",
+      "FacilitatedPayments.Pix.AccountLinking.PromptShown",
       /*sample=*/true,
       /*expected_bucket_count=*/0);
 }
@@ -376,4 +377,62 @@
                          PixAccountLinkingManagerParameterizedTest,
                          testing::Bool());
 
+TEST_F(PixAccountLinkingManagerTest, FlowExitedReason_UserDeclinedLogged) {
+  base::HistogramTester histogram_tester;
+
+  manager()->MaybeShowPixAccountLinkingPrompt(kPixPaymentPageOrigin);
+  test_api().OnDeclined();
+
+  histogram_tester.ExpectUniqueSample(
+      "FacilitatedPayments.Pix.AccountLinking.FlowExitedReason",
+      /*sample=*/PixAccountLinkingFlowExitedReason::kUserDeclined,
+      /*expected_bucket_count=*/1);
+  histogram_tester.ExpectBucketCount(
+      "FacilitatedPayments.Pix.AccountLinking.FlowExitedReason",
+      /*sample=*/PixAccountLinkingFlowExitedReason::kScreenClosedNotByUser,
+      /*expected_count=*/0);
+  histogram_tester.ExpectBucketCount(
+      "FacilitatedPayments.Pix.AccountLinking.FlowExitedReason",
+      /*sample=*/PixAccountLinkingFlowExitedReason::kScreenClosedByUser,
+      /*expected_count=*/0);
+}
+
+class PixAccountLinkingManagerTestForExitedReasons
+    : public PixAccountLinkingManagerTest,
+      public testing::WithParamInterface<
+          std::tuple<UiEvent, PixAccountLinkingFlowExitedReason>> {
+ public:
+  UiEvent ui_event() const { return std::get<0>(GetParam()); }
+
+  PixAccountLinkingFlowExitedReason pix_account_linking_flow_exited_reason()
+      const {
+    return std::get<1>(GetParam());
+  }
+};
+
+TEST_P(PixAccountLinkingManagerTestForExitedReasons, FlowExitedReasonLogged) {
+  base::HistogramTester histogram_tester;
+
+  manager()->MaybeShowPixAccountLinkingPrompt(kPixPaymentPageOrigin);
+  test_api().OnUiScreenEvent(ui_event());
+
+  histogram_tester.ExpectUniqueSample(
+      "FacilitatedPayments.Pix.AccountLinking.FlowExitedReason",
+      /*sample=*/pix_account_linking_flow_exited_reason(),
+      /*expected_bucket_count=*/1);
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    PixAccountLinkingManagerTestSuite,
+    PixAccountLinkingManagerTestForExitedReasons,
+    testing::ValuesIn({
+        std::make_tuple(UiEvent::kScreenCouldNotBeShown,
+                        PixAccountLinkingFlowExitedReason::kScreenNotShown),
+        std::make_tuple(
+            UiEvent::kScreenClosedNotByUser,
+            PixAccountLinkingFlowExitedReason::kScreenClosedNotByUser),
+        std::make_tuple(UiEvent::kScreenClosedByUser,
+                        PixAccountLinkingFlowExitedReason::kScreenClosedByUser),
+    }));
+
 }  // namespace payments::facilitated
diff --git a/components/facilitated_payments/core/metrics/facilitated_payments_metrics.cc b/components/facilitated_payments/core/metrics/facilitated_payments_metrics.cc
index efb32ac..0bc75a8 100644
--- a/components/facilitated_payments/core/metrics/facilitated_payments_metrics.cc
+++ b/components/facilitated_payments/core/metrics/facilitated_payments_metrics.cc
@@ -16,6 +16,9 @@
 namespace payments::facilitated {
 namespace {
 
+static constexpr std::string_view kPixAccountLinkingHistogramPrefix =
+    "FacilitatedPayments.Pix.AccountLinking.";
+
 // Helper to convert `PurchaseActionResult` to a string for logging.
 std::string GetPurchaseActionResultString(PurchaseActionResult result) {
   switch (result) {
@@ -392,21 +395,29 @@
 }
 
 void LogPixAccountLinkingPromptShown() {
-  base::UmaHistogramBoolean("FacilitatedPayments.Pix.AccountLinkingPromptShown",
-                            /*sample=*/true);
+  base::UmaHistogramBoolean(
+      base::StrCat({kPixAccountLinkingHistogramPrefix, "PromptShown"}),
+      /*sample=*/true);
 }
 
 void LogGetDetailsForCreatePaymentInstrumentResultAndLatency(
     bool is_eligible,
     base::TimeDelta latency) {
   base::UmaHistogramBoolean(
-      "FacilitatedPayments.Pix.AccountLinking."
-      "GetDetailsForCreatePaymentInstrument.Result",
+      base::StrCat({kPixAccountLinkingHistogramPrefix,
+                    "GetDetailsForCreatePaymentInstrument.Result"}),
       is_eligible);
   base::UmaHistogramLongTimes(
-      "FacilitatedPayments.Pix.AccountLinking."
-      "GetDetailsForCreatePaymentInstrument.Latency",
+      base::StrCat({kPixAccountLinkingHistogramPrefix,
+                    "GetDetailsForCreatePaymentInstrument.Latency"}),
       latency);
 }
 
+void LogPixAccountLinkingFlowExitedReason(
+    PixAccountLinkingFlowExitedReason reason) {
+  base::UmaHistogramEnumeration(
+      base::StrCat({kPixAccountLinkingHistogramPrefix, "FlowExitedReason"}),
+      reason);
+}
+
 }  // namespace payments::facilitated
diff --git a/components/facilitated_payments/core/metrics/facilitated_payments_metrics.h b/components/facilitated_payments/core/metrics/facilitated_payments_metrics.h
index ca49b73..f4d9bc6e 100644
--- a/components/facilitated_payments/core/metrics/facilitated_payments_metrics.h
+++ b/components/facilitated_payments/core/metrics/facilitated_payments_metrics.h
@@ -19,9 +19,6 @@
 
 namespace payments::facilitated {
 
-static constexpr std::string_view kPixAccountLinkingHistogramPrefix =
-    "FacilitatedPayments.Pix.AccountLinking.";
-
 // A payment system that is currently running.
 enum class FacilitatedPaymentsType {
   kEwallet = 0,
@@ -127,6 +124,16 @@
 };
 // LINT.ThenChange(/tools/metrics/histograms/metadata/facilitated_payments/enums.xml:FacilitatedPayments.PixFlowExitedReason)
 
+// LINT.IfChange(PixAccountLinkingFlowExitedReason)
+enum class PixAccountLinkingFlowExitedReason {
+  kScreenNotShown = 0,
+  kScreenClosedNotByUser = 1,
+  kScreenClosedByUser = 2,
+  kUserDeclined = 3,
+  kMaxValue = kUserDeclined
+};
+// LINT.ThenChange(/tools/metrics/histograms/metadata/facilitated_payments/enums.xml:FacilitatedPayments.Pix.AccountLinking.FlowExitedReason)
+
 // Log when a Pix code is copied to the clippboard on an allowlisted merchant
 // website.
 void LogPixCodeCopied(ukm::SourceId ukm_source_id);
@@ -292,6 +299,10 @@
     bool is_eligible,
     base::TimeDelta latency);
 
+// Log the reason for the Pix account linking flow was exited early.
+void LogPixAccountLinkingFlowExitedReason(
+    PixAccountLinkingFlowExitedReason reason);
+
 }  // namespace payments::facilitated
 
 #endif  // COMPONENTS_FACILITATED_PAYMENTS_CORE_METRICS_FACILITATED_PAYMENTS_METRICS_H_
diff --git a/components/facilitated_payments/core/metrics/facilitated_payments_metrics_unittest.cc b/components/facilitated_payments/core/metrics/facilitated_payments_metrics_unittest.cc
index 38465e7..ffe87a2 100644
--- a/components/facilitated_payments/core/metrics/facilitated_payments_metrics_unittest.cc
+++ b/components/facilitated_payments/core/metrics/facilitated_payments_metrics_unittest.cc
@@ -193,7 +193,8 @@
   LogPixAccountLinkingPromptShown();
 
   histogram_tester.ExpectUniqueSample(
-      "FacilitatedPayments.Pix.AccountLinkingPromptShown", /*sample=*/true,
+      "FacilitatedPayments.Pix.AccountLinking.PromptShown",
+      /*sample=*/true,
       /*expected_bucket_count=*/1);
 }
 
@@ -208,6 +209,29 @@
       /*expected_bucket_count=*/1);
 }
 
+class FacilitatedPaymentsMetricsPixAccountLinkingFlowExitedReasonTest
+    : public testing::TestWithParam<PixAccountLinkingFlowExitedReason> {};
+
+TEST_P(FacilitatedPaymentsMetricsPixAccountLinkingFlowExitedReasonTest,
+       LogPixAccountLinkingFlowExitedReason) {
+  base::HistogramTester histogram_tester;
+
+  LogPixAccountLinkingFlowExitedReason(GetParam());
+
+  histogram_tester.ExpectUniqueSample(
+      "FacilitatedPayments.Pix.AccountLinking.FlowExitedReason",
+      /*sample=*/GetParam(),
+      /*expected_bucket_count=*/1);
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    FacilitatedPaymentsMetricsTest,
+    FacilitatedPaymentsMetricsPixAccountLinkingFlowExitedReasonTest,
+    testing::Values(PixAccountLinkingFlowExitedReason::kScreenNotShown,
+                    PixAccountLinkingFlowExitedReason::kScreenClosedNotByUser,
+                    PixAccountLinkingFlowExitedReason::kScreenClosedByUser,
+                    PixAccountLinkingFlowExitedReason::kUserDeclined));
+
 TEST(FacilitatedPaymentsMetricsTest,
      LogGetDetailsForCreatePaymentInstrumentResultAndLatency) {
   base::HistogramTester histogram_tester;
diff --git a/components/facilitated_payments_strings.grdp b/components/facilitated_payments_strings.grdp
index c6c6103..9a6f28a6 100644
--- a/components/facilitated_payments_strings.grdp
+++ b/components/facilitated_payments_strings.grdp
@@ -65,7 +65,7 @@
   <message name="IDS_BANK_ACCOUNT_TYPE_TRANSACTING" desc="A transacting account is a basic account used for regular expenses. It often has limits on the withdrawal amount. Text to describe a transacting bank account." formatter_data="android_java">
     Account
   </message>
-  <!-- Ewallet and Pix bottom sheet related strings -->
+  <!-- Facilitated payments bottom sheet related strings -->
   <message name="IDS_FACILITATED_PAYMENTS_PAYMENT_METHODS_BOTTOM_SHEET_DETAILED_TITLE" desc="Title for the bottom sheet displaying user's accounts linked to their GPay wallet. It is shown to prompt the user to pay directly on Chrome when it receives signal of intention to pay, and the user has eligible payment accounts. The title string contains either the payment type or the name of the payment provider." formatter_data="android_java">
     Pay with <ph name="PAYMENT_METHOD">%1$s<ex>ChiliPay</ex></ph> without switching apps
   </message>
@@ -108,6 +108,9 @@
   <message name="IDS_FACILITATED_PAYMENTS_OK" desc="Generic OK string to be used in any Facilitated Payments UI." formatter_data="android_java">
     OK
   </message>
+  <message name="IDS_FACILITATED_PAYMENTS_PAYMENT_APP_DESCRIPTION" desc="A message shown within a payment app item in the fop selector to indicate the click will open the app." formatter_data="android_java">
+    Open app
+  </message>
 
   <!-- Pix account linking prompt strings -->
   <message name="IDS_PIX_ACCOUNT_LINKING_PROMPT_TITLE" desc="Title for the bottom sheet showing Pix account linking prompt. It asks users to link their Pix accounts to Google Wallet so they can directly pay on Chrome." formatter_data="android_java">
diff --git a/components/facilitated_payments_strings_grdp/IDS_FACILITATED_PAYMENTS_PAYMENT_APP_DESCRIPTION.png.sha1 b/components/facilitated_payments_strings_grdp/IDS_FACILITATED_PAYMENTS_PAYMENT_APP_DESCRIPTION.png.sha1
new file mode 100644
index 0000000..849ea57
--- /dev/null
+++ b/components/facilitated_payments_strings_grdp/IDS_FACILITATED_PAYMENTS_PAYMENT_APP_DESCRIPTION.png.sha1
@@ -0,0 +1 @@
+d5c926ee8a14f699f6b50b550707e819177e71a8
\ No newline at end of file
diff --git a/components/input/web_input_event_builders_android_unittest.cc b/components/input/web_input_event_builders_android_unittest.cc
index b01e78a4..9f1f98b 100644
--- a/components/input/web_input_event_builders_android_unittest.cc
+++ b/components/input/web_input_event_builders_android_unittest.cc
@@ -12,6 +12,7 @@
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/blink/public/common/input/web_input_event.h"
 #include "ui/events/android/key_event_utils.h"
+#include "ui/events/android/motion_event_android_factory.h"
 #include "ui/events/android/motion_event_android_java.h"
 #include "ui/events/keycodes/dom/dom_code.h"
 #include "ui/events/keycodes/dom/dom_key.h"
@@ -216,14 +217,28 @@
       JNI_MotionEvent::Java_MotionEvent_obtain(
           env, /*downTime=*/0, /*eventTime=*/0, /*action=*/0, /*x=*/0, /*y=*/0,
           /*metaState=*/AMETA_ALT_ON);
-  ui::MotionEventAndroidJava motion_event(
-      env, obj, kPixToDip, 0.f, 0.f, 0.f,
+  auto motion_event = ui::MotionEventAndroidFactory::CreateFromJava(
+      env, obj, kPixToDip,
+      /*ticks_x=*/0.f,
+      /*ticks_y=*/0.f,
+      /*tick_multiplier=*/0.f,
+      /*oldest_event_time=*/
       base::TimeTicks() + base::Nanoseconds(kEventTimeNs),
-      AMOTION_EVENT_ACTION_DOWN, 1, 0, -1, 0, 0, 1, raw_offset_x, raw_offset_y,
-      false, &p0, nullptr);
+      /*android_action=*/AMOTION_EVENT_ACTION_DOWN,
+      /*pointer_count=*/1,
+      /*history_size=*/0,
+      /*action_index=*/-1,
+      /*android_action_button=*/0,
+      /*android_gesture_classification=*/0,
+      /*android_button_state=*/1,
+      /*raw_offset_x_pixels=*/raw_offset_x,
+      /*raw_offset_y_pixels=*/raw_offset_y,
+      /*for_touch_handle=*/false,
+      /*pointer0=*/&p0,
+      /*pointer1=*/nullptr);
 
   WebMouseEvent web_event = input::WebMouseEventBuilder::Build(
-      motion_event, blink::WebInputEvent::Type::kMouseDown, 1,
+      *motion_event, blink::WebInputEvent::Type::kMouseDown, 1,
       ui::MotionEvent::BUTTON_PRIMARY);
   EXPECT_EQ(web_event.PositionInWidget().x(), p0.pos_x_pixels * kPixToDip);
   EXPECT_EQ(web_event.PositionInWidget().y(), p0.pos_y_pixels * kPixToDip);
diff --git a/components/media_router/common/providers/cast/channel/cast_socket_service.h b/components/media_router/common/providers/cast/channel/cast_socket_service.h
index 1a4e4cd..d6c5373 100644
--- a/components/media_router/common/providers/cast/channel/cast_socket_service.h
+++ b/components/media_router/common/providers/cast/channel/cast_socket_service.h
@@ -13,7 +13,6 @@
 #include "base/task/single_thread_task_runner.h"
 #include "components/media_router/common/providers/cast/channel/cast_socket.h"
 #include "services/network/public/cpp/network_context_getter.h"
-#include "services/network/public/mojom/network_context.mojom.h"
 
 namespace cast_channel {
 
diff --git a/components/omnibox/browser/omnibox_edit_model.cc b/components/omnibox/browser/omnibox_edit_model.cc
index 4e4a088..7f9ddfd 100644
--- a/components/omnibox/browser/omnibox_edit_model.cc
+++ b/components/omnibox/browser/omnibox_edit_model.cc
@@ -2012,11 +2012,16 @@
           ask_keyword ? IDS_ACC_ASK_KEYWORD_MODE : IDS_ACC_KEYWORD_MODE;
       return l10n_util::GetStringFUTF16(message_id, replacement_string);
     }
-    case OmniboxPopupSelection::FOCUSED_BUTTON_ACTION:
+    case OmniboxPopupSelection::FOCUSED_BUTTON_ACTION: {
       // When pedal button is focused, the autocomplete suggestion isn't
       // read because it's not relevant to the button's action.
-      DCHECK(match.GetActionAt(0u));
-      return match.GetActionAt(0u)->GetLabelStrings().accessibility_hint;
+      // When dealing with toolbelt actions, we need to ensure that the proper
+      // action a11y label is announced based on the action index.
+      DCHECK(match.GetActionAt(popup_selection_.action_index));
+      return match.GetActionAt(popup_selection_.action_index)
+          ->GetLabelStrings()
+          .accessibility_hint;
+    }
     case OmniboxPopupSelection::FOCUSED_BUTTON_THUMBS_UP:
       additional_message_id = IDS_ACC_THUMBS_UP_SUGGESTION_FOCUSED_PREFIX;
       break;
diff --git a/components/omnibox/browser/omnibox_edit_model_unittest.cc b/components/omnibox/browser/omnibox_edit_model_unittest.cc
index 09f8856..a90d869 100644
--- a/components/omnibox/browser/omnibox_edit_model_unittest.cc
+++ b/components/omnibox/browser/omnibox_edit_model_unittest.cc
@@ -35,6 +35,7 @@
 #include "components/omnibox/browser/test_omnibox_view.h"
 #include "components/omnibox/browser/test_scheme_classifier.h"
 #include "components/omnibox/browser/unscoped_extension_provider.h"
+#include "components/omnibox/common/omnibox_feature_configs.h"
 #include "components/omnibox/common/omnibox_features.h"
 #include "components/omnibox/common/omnibox_focus_state.h"
 #include "components/prefs/testing_pref_service.h"
@@ -871,6 +872,11 @@
 // Actions are not part of the selection stepping in Android and iOS at all.
 #if !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS)
 TEST_F(OmniboxEditModelPopupTest, PopupStepSelectionWithActions) {
+  omnibox_feature_configs::ScopedConfigForTesting<
+      omnibox_feature_configs::Toolbelt>
+      scoped_config;
+  scoped_config.Get().enabled = true;
+
   ACMatches matches;
   for (size_t i = 0; i < 4; ++i) {
     AutocompleteMatch match(nullptr, 1000, false,
@@ -879,6 +885,18 @@
     match.allowed_to_be_default_match = true;
     matches.push_back(match);
   }
+
+  // The toolbelt match has three normal actions.
+  AutocompleteMatch toolbelt_match(nullptr, 1000, false,
+                                   AutocompleteMatchType::NULL_RESULT_MESSAGE);
+  toolbelt_match.actions.push_back(base::MakeRefCounted<OmniboxAction>(
+      OmniboxAction::LabelStrings(u"", u"", u"", u"foo"), GURL()));
+  toolbelt_match.actions.push_back(base::MakeRefCounted<OmniboxAction>(
+      OmniboxAction::LabelStrings(u"", u"", u"", u"bar"), GURL()));
+  toolbelt_match.actions.push_back(base::MakeRefCounted<OmniboxAction>(
+      OmniboxAction::LabelStrings(u"", u"", u"", u"spam"), GURL()));
+  matches.push_back(toolbelt_match);
+
   // The second match has a normal action.
   matches[1].actions.push_back(base::MakeRefCounted<OmniboxAction>(
       OmniboxAction::LabelStrings(), GURL()));
@@ -909,19 +927,37 @@
     EXPECT_EQ(n, model()->GetPopupSelection().line);
   }
 
+  std::vector<std::u16string> expected_labels = {u"foo", u"bar", u"spam"};
+  auto test_a11y_label = [&](size_t action_index, std::u16string a11y_label) {
+    DCHECK(action_index < expected_labels.size());
+    EXPECT_EQ(expected_labels[action_index], a11y_label);
+  };
+
   // Step by states forward.
   for (auto selection : {
            Selection(1, Selection::NORMAL),
            Selection(1, Selection::FOCUSED_BUTTON_ACTION),
            Selection(2, Selection::NORMAL),
            Selection(3, Selection::NORMAL),
+           Selection(4, Selection::FOCUSED_BUTTON_ACTION, /*action_index=*/0),
+           Selection(4, Selection::FOCUSED_BUTTON_ACTION, /*action_index=*/1),
+           Selection(4, Selection::FOCUSED_BUTTON_ACTION, /*action_index=*/2),
            Selection(0, Selection::NORMAL),
        }) {
     model()->OnTabPressed(false);
-    EXPECT_EQ(selection, model()->GetPopupSelection());
+    auto popup_selection = model()->GetPopupSelection();
+    EXPECT_EQ(selection, popup_selection);
+    if (matches[popup_selection.line].IsToolbelt()) {
+      test_a11y_label(popup_selection.action_index,
+                      model()->GetPopupAccessibilityLabelForCurrentSelection(
+                          u"", false, nullptr));
+    }
   }
   // Step by states backward.
   for (auto selection : {
+           Selection(4, Selection::FOCUSED_BUTTON_ACTION, /*action_index=*/2),
+           Selection(4, Selection::FOCUSED_BUTTON_ACTION, /*action_index=*/1),
+           Selection(4, Selection::FOCUSED_BUTTON_ACTION, /*action_index=*/0),
            Selection(3, Selection::NORMAL),
            Selection(2, Selection::NORMAL),
            Selection(1, Selection::FOCUSED_BUTTON_ACTION),
@@ -929,7 +965,13 @@
            Selection(0, Selection::NORMAL),
        }) {
     model()->OnTabPressed(true);
-    EXPECT_EQ(selection, model()->GetPopupSelection());
+    auto popup_selection = model()->GetPopupSelection();
+    EXPECT_EQ(selection, popup_selection);
+    if (matches[popup_selection.line].IsToolbelt()) {
+      test_a11y_label(popup_selection.action_index,
+                      model()->GetPopupAccessibilityLabelForCurrentSelection(
+                          u"", false, nullptr));
+    }
   }
 
   // Try the `kAllLines` step behavior.
diff --git a/components/omnibox/browser/omnibox_popup_selection.h b/components/omnibox/browser/omnibox_popup_selection.h
index 527258e..e1bdd6d 100644
--- a/components/omnibox/browser/omnibox_popup_selection.h
+++ b/components/omnibox/browser/omnibox_popup_selection.h
@@ -88,7 +88,7 @@
   // When `state` is `FOCUSED_BUTTON_ACTION`, this indicates which action
   // is selected by index into `AutocompleteMatch::actions`. Other states
   // keep an unused zero index.
-  size_t action_index;
+  size_t action_index = 0u;
 
   explicit OmniboxPopupSelection(size_t line,
                                  LineState state = NORMAL,
diff --git a/components/omnibox/common/omnibox_feature_configs.cc b/components/omnibox/common/omnibox_feature_configs.cc
index 70ab232..53da05e 100644
--- a/components/omnibox/common/omnibox_feature_configs.cc
+++ b/components/omnibox/common/omnibox_feature_configs.cc
@@ -260,6 +260,10 @@
           .Get();
 }
 
+Toolbelt::Toolbelt(const Toolbelt&) = default;
+Toolbelt& Toolbelt::operator=(const Toolbelt&) = default;
+Toolbelt::~Toolbelt() = default;
+
 DocumentProvider::DocumentProvider() {
   enabled = base::FeatureList::IsEnabled(omnibox::kDocumentProvider);
   min_query_length =
diff --git a/components/omnibox/common/omnibox_feature_configs.h b/components/omnibox/common/omnibox_feature_configs.h
index e738936b..71fcd9d2f 100644
--- a/components/omnibox/common/omnibox_feature_configs.h
+++ b/components/omnibox/common/omnibox_feature_configs.h
@@ -230,6 +230,9 @@
   DECLARE_FEATURE(kOmniboxToolbelt);
 
   Toolbelt();
+  Toolbelt(const Toolbelt&);
+  Toolbelt& operator=(const Toolbelt&);
+  ~Toolbelt();
 
   // Whether the toolbelt is to be included in the omnibox.
   bool enabled;
diff --git a/components/omnibox/composebox/composebox_query_controller.cc b/components/omnibox/composebox/composebox_query_controller.cc
index 4704553b..b99be47 100644
--- a/components/omnibox/composebox/composebox_query_controller.cc
+++ b/components/omnibox/composebox/composebox_query_controller.cc
@@ -155,6 +155,15 @@
 }
 #endif  // !BUILDFLAG(IS_IOS)
 
+// Returns true if the file upload status is valid to include in the multimodal
+// request.
+bool IsValidFileUploadStatusForMultimodalRequest(
+    FileUploadStatus upload_status) {
+  return upload_status == FileUploadStatus::kProcessing ||
+         upload_status == FileUploadStatus::kUploadStarted ||
+         upload_status == FileUploadStatus::kUploadSuccessful;
+}
+
 }  // namespace
 
 ComposeboxQueryController::ComposeboxQueryController(
@@ -203,22 +212,28 @@
 
 GURL ComposeboxQueryController::CreateAimUrl(const std::string& query_text,
                                              base::Time query_start_time) {
-  CHECK(cluster_info_.has_value());
   session_state_ = SessionState::kQuerySubmitted;
-  if (!active_files_.empty()) {
+  if (!active_files_.empty() && cluster_info_.has_value()) {
     // Since multiple file upload isn't supported right now, use the last file
     // uploaded to determine `vit` param.
     // TODO(crbug.com/428967670): Support multiple file upload.
     const std::unique_ptr<FileInfo>& last_file = active_files_.rbegin()->second;
-    return GetUrlForMultimodalAim(
-        template_url_service_, kEntrypointParameterValue, query_start_time,
-        cluster_info_->search_session_id(),
-        request_id_generator_.GetNextRequestId(
-            lens::RequestIdUpdateMode::kSearchUrl),
-        last_file->mime_type_,
-        send_lns_surface_ ? kLnsSurfaceParameterValue : std::string(),
-        base::UTF8ToUTF16(query_text));
+    if (IsValidFileUploadStatusForMultimodalRequest(
+            last_file->upload_status_)) {
+      return GetUrlForMultimodalAim(
+          template_url_service_, kEntrypointParameterValue, query_start_time,
+          cluster_info_->search_session_id(),
+          request_id_generator_.GetNextRequestId(
+              lens::RequestIdUpdateMode::kSearchUrl),
+          last_file->mime_type_,
+          send_lns_surface_ ? kLnsSurfaceParameterValue : std::string(),
+          base::UTF8ToUTF16(query_text));
+    }
   }
+  // Treat queries in which the cluster info has expired, or the last file is
+  // not valid, as unimodal text queries.
+  // TODO(crbug.com/432125987): Handle file reupload after cluster info
+  // expiration.
   return GetUrlForAim(template_url_service_, kEntrypointParameterValue,
                       query_start_time, base::UTF8ToUTF16(query_text));
 }
diff --git a/components/omnibox/composebox/composebox_query_controller_unittest.cc b/components/omnibox/composebox/composebox_query_controller_unittest.cc
index 441a0f6..3d4d11c 100644
--- a/components/omnibox/composebox/composebox_query_controller_unittest.cc
+++ b/components/omnibox/composebox/composebox_query_controller_unittest.cc
@@ -683,6 +683,49 @@
   EXPECT_EQ(client_context.locale_context().time_zone(), kTimeZone);
 }
 
+TEST_F(ComposeboxQueryControllerTest,
+       UnimodalTextQuerySubmittedWithInvalidClusterInfoSuccess) {
+  controller().set_next_cluster_info_request_should_return_error(true);
+
+  // Act: Start the session.
+  controller().NotifySessionStarted();
+
+  // Assert: Validate cluster info request and state changes.
+  WaitForClusterInfo(QueryControllerState::kClusterInfoInvalid);
+
+  // Act: Generate the destination URL for the query.
+  GURL aim_url = controller().CreateAimUrl("test", kTestQueryStartTime);
+
+  // Assert: Validate the state change.
+  EXPECT_EQ(SessionState::kQuerySubmitted, controller().session_state());
+
+  // Assert: Lens request id is NOT added to unimodal text queries.
+  std::string vsrid_value;
+  EXPECT_FALSE(net::GetValueForKeyInQuery(aim_url, kRequestIdParameterKey,
+                                          &vsrid_value));
+
+  // Assert: Visual input type is NOT added to unimodal text queries.
+  std::string vit_value;
+  EXPECT_FALSE(net::GetValueForKeyInQuery(aim_url, kVisualInputTypeParameterKey,
+                                          &vit_value));
+
+  // Assert: Gsession id is NOT added to unimodal text queries.
+  std::string gsession_id_value;
+  EXPECT_FALSE(net::GetValueForKeyInQuery(aim_url, kSessionIdQueryParameterKey,
+                                          &gsession_id_value));
+
+  // Check that the timestamps are attached to the url.
+  std::string qsubts_value;
+  EXPECT_TRUE(net::GetValueForKeyInQuery(
+      aim_url, kQuerySubmissionTimeQueryParameter, &qsubts_value));
+
+  std::string pqsubts_value;
+  EXPECT_TRUE(net::GetValueForKeyInQuery(
+      aim_url, kUserPerceivedQuerySubmissionTimeQueryParameter,
+      &pqsubts_value));
+  EXPECT_EQ(pqsubts_value, "1000");
+}
+
 TEST_F(ComposeboxQueryControllerTest, QuerySubmitted) {
   // Act: Start the session.
   controller().NotifySessionStarted();
@@ -690,8 +733,7 @@
   // Assert: Validate cluster info request and state changes.
   WaitForClusterInfo();
 
-  // Act: Generate the destination URL for the query. The destination URL can
-  // only be created after the cluster info is received.
+  // Act: Generate the destination URL for the query.
   GURL aim_url = controller().CreateAimUrl("test", kTestQueryStartTime);
 
   // Assert: Validate the state change.
@@ -777,6 +819,58 @@
   EXPECT_EQ(pqsubts_value, "1000");
 }
 
+TEST_F(ComposeboxQueryControllerTest,
+       QuerySubmittedWithUploadedPdfButInvalidClusterInfoIsUnimodal) {
+  // Enable cluster info TTL.
+  controller().set_enable_cluster_info_ttl(true);
+
+  // Act: Start the session.
+  controller().NotifySessionStarted();
+
+  // Assert: Validate cluster info request and state changes.
+  WaitForClusterInfo();
+
+  // Ensure that future cluster info requests fail.
+  controller().set_next_cluster_info_request_should_return_error(true);
+
+  // Act: Start the file upload flow.
+  const base::UnguessableToken file_token = base::UnguessableToken::Create();
+  StartPdfFileUploadFlow(
+      file_token,
+      /*file_data=*/base::MakeRefCounted<base::RefCountedBytes>());
+
+  // Assert: Validate file upload request and status changes.
+  WaitForFileUpload(file_token);
+
+  // Wait 1 hour.
+  task_environment().FastForwardBy(base::Hours(1));
+
+  // Assert: Validate cluster info request and state changes.
+  EXPECT_EQ(QueryControllerState::kClusterInfoInvalid,
+            controller().query_controller_state());
+
+  // Act: Create the destination URL for the query.
+  GURL aim_url = controller().CreateAimUrl("hello", kTestQueryStartTime);
+
+  // Assert: Validate the state change.
+  EXPECT_EQ(SessionState::kQuerySubmitted, controller().session_state());
+
+  // Assert: Lens request id is NOT added to unimodal text queries.
+  std::string vsrid_value;
+  EXPECT_FALSE(net::GetValueForKeyInQuery(aim_url, kRequestIdParameterKey,
+                                          &vsrid_value));
+
+  // Assert: Visual input type is NOT added to unimodal text queries.
+  std::string vit_value;
+  EXPECT_FALSE(net::GetValueForKeyInQuery(aim_url, kVisualInputTypeParameterKey,
+                                          &vit_value));
+
+  // Assert: Gsession id is NOT added to unimodal text queries.
+  std::string gsession_id_value;
+  EXPECT_FALSE(net::GetValueForKeyInQuery(aim_url, kSessionIdQueryParameterKey,
+                                          &gsession_id_value));
+}
+
 TEST_F(ComposeboxQueryControllerTest, DeleteFile_Success) {
   // Act: Start the session.
   controller().NotifySessionStarted();
diff --git a/components/optimization_guide/core/hints/optimization_metadata.cc b/components/optimization_guide/core/hints/optimization_metadata.cc
index 55ebda6..68d33a5 100644
--- a/components/optimization_guide/core/hints/optimization_metadata.cc
+++ b/components/optimization_guide/core/hints/optimization_metadata.cc
@@ -11,12 +11,4 @@
 OptimizationMetadata::OptimizationMetadata(const OptimizationMetadata&) =
     default;
 
-void OptimizationMetadata::SetAnyMetadataForTesting(
-    const google::protobuf::MessageLite& metadata) {
-  proto::Any any;
-  any.set_type_url(metadata.GetTypeName());
-  metadata.SerializeToString(any.mutable_value());
-  any_metadata_ = any;
-}
-
 }  // namespace optimization_guide
diff --git a/components/optimization_guide/core/hints/optimization_metadata.h b/components/optimization_guide/core/hints/optimization_metadata.h
index ee374d27c..caf79b48 100644
--- a/components/optimization_guide/core/hints/optimization_metadata.h
+++ b/components/optimization_guide/core/hints/optimization_metadata.h
@@ -37,9 +37,6 @@
   void set_any_metadata(const proto::Any& any_metadata) {
     any_metadata_ = any_metadata;
   }
-  // Sets |any_metadata_| to be validly parsed as |metadata|. Should only be
-  // used for testing purposes.
-  void SetAnyMetadataForTesting(const google::protobuf::MessageLite& metadata);
 
   const std::optional<proto::LoadingPredictorMetadata>&
   loading_predictor_metadata() const {
diff --git a/components/optimization_guide/core/model_execution/on_device_model_component.cc b/components/optimization_guide/core/model_execution/on_device_model_component.cc
index 9577663..a9ad1d77 100644
--- a/components/optimization_guide/core/model_execution/on_device_model_component.cc
+++ b/components/optimization_guide/core/model_execution/on_device_model_component.cc
@@ -28,16 +28,12 @@
 #include "components/optimization_guide/core/optimization_guide_features.h"
 #include "components/prefs/pref_service.h"
 #include "components/version_info/version_info.h"
+#include "services/on_device_model/public/cpp/cpu.h"
+#include "third_party/abseil-cpp/absl/container/flat_hash_set.h"
 
 namespace optimization_guide {
 namespace {
 
-base::WeakPtr<OnDeviceModelComponentStateManager>& GetInstance() {
-  static base::NoDestructor<base::WeakPtr<OnDeviceModelComponentStateManager>>
-      state_manager_instance;
-  return *state_manager_instance.get();
-}
-
 bool WasAnyOnDeviceEligibleFeatureRecentlyUsed(const PrefService& local_state) {
   for (const ModelBasedCapabilityKey key : kAllModelBasedCapabilityKeys) {
     if (!features::internal::GetOptimizationTargetForCapability(key)) {
@@ -50,11 +46,14 @@
   return false;
 }
 
-bool IsDeviceCapable(const PrefService& local_state) {
+bool IsDeviceGPUCapable(const PrefService& local_state) {
   return IsPerformanceClassCompatible(
-             features::kPerformanceClassListForOnDeviceModel.Get(),
-             PerformanceClassFromPref(local_state)) ||
-         features::ForceCpuBackendForOnDeviceModel();
+      features::kPerformanceClassListForOnDeviceModel.Get(),
+      PerformanceClassFromPref(local_state));
+}
+
+bool IsDeviceCapable(const PrefService& local_state) {
+  return IsDeviceGPUCapable(local_state) || on_device_model::IsCpuCapable();
 }
 
 void LogInstallCriteria(std::string_view event_name,
@@ -315,10 +314,10 @@
     // Don't allow UpdateRegistration to do anything until after
     // UninstallComplete.
     component_installer_registered_ = true;
-    delegate_->Uninstall(this);
+    delegate_->Uninstall(GetWeakPtr());
   } else if (criteria.should_install() || criteria.is_already_installing) {
     component_installer_registered_ = true;
-    delegate_->RegisterInstaller(this, criteria.is_already_installing);
+    delegate_->RegisterInstaller(GetWeakPtr(), criteria.is_already_installing);
   }
 
   // Log metrics only for first registration attempt.
@@ -379,21 +378,6 @@
   return is_model_allowed_ ? state_.get() : nullptr;
 }
 
-scoped_refptr<OnDeviceModelComponentStateManager>
-OnDeviceModelComponentStateManager::CreateOrGet(
-    PrefService* local_state,
-    std::unique_ptr<Delegate> delegate) {
-  base::WeakPtr<OnDeviceModelComponentStateManager>& instance = GetInstance();
-  if (!instance) {
-    auto state_manager =
-        base::WrapRefCounted(new OnDeviceModelComponentStateManager(
-            local_state, std::move(delegate)));
-    instance = state_manager->GetWeakPtr();
-    return state_manager;
-  }
-  return scoped_refptr<OnDeviceModelComponentStateManager>(instance.get());
-}
-
 void OnDeviceModelComponentStateManager::AddObserver(Observer* observer) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   observers_.AddObserver(observer);
@@ -405,12 +389,6 @@
 }
 
 // static
-OnDeviceModelComponentStateManager*
-OnDeviceModelComponentStateManager::GetInstanceForTesting() {
-  return GetInstance().get();
-}
-
-// static
 bool OnDeviceModelComponentStateManager::VerifyInstallation(
     const base::FilePath& install_dir,
     const base::Value::Dict& manifest) {
@@ -496,27 +474,42 @@
     return std::nullopt;
   }
 
+  absl::flat_hash_set<int> supported_hints;
   for (const auto& supported_performance_hint_val :
        *manifest_performance_hints) {
     std::optional<int> supported_performance_hint_int =
         supported_performance_hint_val.GetIfInt();
-
-    if (IsLowTierDevice() &&
-        *supported_performance_hint_int ==
-            proto::ON_DEVICE_MODEL_PERFORMANCE_HINT_FASTEST_INFERENCE) {
-      return proto::ON_DEVICE_MODEL_PERFORMANCE_HINT_FASTEST_INFERENCE;
+    if (supported_performance_hint_int) {
+      supported_hints.insert(*supported_performance_hint_int);
     }
-    // `IsDeviceCapable` is a superset of `IsLowTierDevice`, so assume that
-    // !`IsLowTier` = highest quality capable.
-    if (IsDeviceCapable(*local_state_) && !IsLowTierDevice() &&
-        *supported_performance_hint_int ==
-            proto::ON_DEVICE_MODEL_PERFORMANCE_HINT_HIGHEST_QUALITY) {
-      return proto::ON_DEVICE_MODEL_PERFORMANCE_HINT_HIGHEST_QUALITY;
+  }
+  for (auto hint : GetPossibleHints()) {
+    if (supported_hints.contains(hint)) {
+      return hint;
     }
   }
   return std::nullopt;
 }
 
+std::vector<proto::OnDeviceModelPerformanceHint>
+OnDeviceModelComponentStateManager::GetPossibleHints() const {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  std::vector<proto::OnDeviceModelPerformanceHint> hints;
+  if (IsDeviceGPUCapable(*local_state_)) {
+    // Best option is highest quality for GPU device that is not low tier.
+    if (!IsLowTierDevice()) {
+      hints.push_back(proto::ON_DEVICE_MODEL_PERFORMANCE_HINT_HIGHEST_QUALITY);
+    }
+    // Other GPU capable devices get fastest inference.
+    hints.push_back(proto::ON_DEVICE_MODEL_PERFORMANCE_HINT_FASTEST_INFERENCE);
+  }
+  if (on_device_model::IsCpuCapable()) {
+    // Last option is CPU if the device is capable but not GPU capable.
+    hints.push_back(proto::ON_DEVICE_MODEL_PERFORMANCE_HINT_CPU);
+  }
+  return hints;
+}
+
 OnDeviceModelComponentState::OnDeviceModelComponentState() = default;
 OnDeviceModelComponentState::~OnDeviceModelComponentState() = default;
 
diff --git a/components/optimization_guide/core/model_execution/on_device_model_component.h b/components/optimization_guide/core/model_execution/on_device_model_component.h
index 7f550e1c..43f7b48 100644
--- a/components/optimization_guide/core/model_execution/on_device_model_component.h
+++ b/components/optimization_guide/core/model_execution/on_device_model_component.h
@@ -13,6 +13,7 @@
 #include "base/functional/callback.h"
 #include "base/memory/raw_ptr.h"
 #include "base/memory/ref_counted.h"
+#include "base/memory/weak_ptr.h"
 #include "base/observer_list.h"
 #include "base/observer_list_types.h"
 #include "base/sequence_checker.h"
@@ -97,11 +98,9 @@
 };
 
 // Manages the state of the on-device component.
-// This object needs to have lifetime equal to the browser process. This is
-// achieved by holding a scoped_refptr on KeyedServices which need it, and on
-// the installer (which is owned by ComponentUpdaterService).
-class OnDeviceModelComponentStateManager
-    : public base::RefCounted<OnDeviceModelComponentStateManager> {
+// This object needs to have lifetime equal to the browser process, and outside
+// of tests is created by a static NoDestructor initializer.
+class OnDeviceModelComponentStateManager final {
  public:
   class Delegate {
    public:
@@ -120,14 +119,14 @@
     // `OnDeviceModelComponentStateManager::SetReady` when the component is
     // ready to use.
     virtual void RegisterInstaller(
-        scoped_refptr<OnDeviceModelComponentStateManager> state_manager,
+        base::WeakPtr<OnDeviceModelComponentStateManager> state_manager,
         bool is_already_installing) = 0;
 
     // Uninstall the component. Calls
     // `OnDeviceModelComponentStateManager::UninstallComplete()` when uninstall
     // completes.
     virtual void Uninstall(
-        scoped_refptr<OnDeviceModelComponentStateManager> state_manager) = 0;
+        base::WeakPtr<OnDeviceModelComponentStateManager> state_manager) = 0;
   };
 
   class Observer : public base::CheckedObserver {
@@ -181,11 +180,9 @@
     }
   };
 
-  // Creates the instance if one does not already exist. Returns an existing
-  // instance otherwise.
-  static scoped_refptr<OnDeviceModelComponentStateManager> CreateOrGet(
-      PrefService* local_state,
-      std::unique_ptr<Delegate> delegate);
+  OnDeviceModelComponentStateManager(PrefService* local_state,
+                                     std::unique_ptr<Delegate> delegate);
+  ~OnDeviceModelComponentStateManager();
 
   // Returns whether the component installation is valid.
   static bool VerifyInstallation(const base::FilePath& install_dir,
@@ -264,12 +261,7 @@
     return weak_ptr_factory_.GetWeakPtr();
   }
 
-  // Testing functionality:
-  static OnDeviceModelComponentStateManager* GetInstanceForTesting();
-
  private:
-  friend class base::RefCounted<OnDeviceModelComponentStateManager>;
-
   enum class OnDeviceRegistrationDecision {
     // The component should be installed.
     kInstall,
@@ -279,10 +271,6 @@
     kDoNotInstall,
   };
 
-  OnDeviceModelComponentStateManager(PrefService* local_state,
-                                     std::unique_ptr<Delegate> delegate);
-  ~OnDeviceModelComponentStateManager();
-
   RegistrationCriteria ComputeRegistrationCriteria(
       int64_t disk_space_free_bytes);
 
@@ -303,6 +291,10 @@
   const std::optional<OnDeviceBaseModelSpec> ProcessBaseModelSpecFromManifest(
       const base::Value::Dict& manifest);
 
+  // Returns a list of performance hints this device supports in priority order,
+  // with highest priority first.
+  std::vector<proto::OnDeviceModelPerformanceHint> GetPossibleHints() const;
+
   raw_ptr<PrefService> local_state_ GUARDED_BY_CONTEXT(sequence_checker_);
   std::unique_ptr<Delegate> delegate_ GUARDED_BY_CONTEXT(sequence_checker_);
   base::ObserverList<Observer> observers_ GUARDED_BY_CONTEXT(sequence_checker_);
diff --git a/components/optimization_guide/core/model_execution/on_device_model_component_unittest.cc b/components/optimization_guide/core/model_execution/on_device_model_component_unittest.cc
index 207ff9a3..85b8f47 100644
--- a/components/optimization_guide/core/model_execution/on_device_model_component_unittest.cc
+++ b/components/optimization_guide/core/model_execution/on_device_model_component_unittest.cc
@@ -6,6 +6,7 @@
 
 #include <memory>
 
+#include "base/memory/weak_ptr.h"
 #include "base/scoped_add_feature_flags.h"
 #include "base/test/metrics/histogram_tester.h"
 #include "base/test/scoped_feature_list.h"
@@ -18,6 +19,7 @@
 #include "components/optimization_guide/core/optimization_guide_features.h"
 #include "components/optimization_guide/core/optimization_guide_switches.h"
 #include "components/prefs/testing_pref_service.h"
+#include "services/on_device_model/public/cpp/features.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
@@ -34,8 +36,24 @@
     base::Value::Dict()
         .Set("version", "0.0.1")
         .Set("name", "Test")
+        .Set(
+            "supported_performance_hints",
+            base::Value::List()
+                .Append(
+                    proto::ON_DEVICE_MODEL_PERFORMANCE_HINT_FASTEST_INFERENCE)
+                .Append(proto::ON_DEVICE_MODEL_PERFORMANCE_HINT_HIGHEST_QUALITY)
+                .Append(
+                    proto::ON_DEVICE_MODEL_PERFORMANCE_HINT_FASTEST_INFERENCE)
+                .Append(proto::ON_DEVICE_MODEL_PERFORMANCE_HINT_UNSPECIFIED)
+                .Append(proto::ON_DEVICE_MODEL_PERFORMANCE_HINT_CPU)));
+const base::Value::Dict kTestManifestWithCPUPerfHints = base::Value::Dict().Set(
+    "BaseModelSpec",
+    base::Value::Dict()
+        .Set("version", "0.0.1")
+        .Set("name", "Test")
         .Set("supported_performance_hints",
-             base::Value::List().Append(1).Append(2).Append(1).Append(0)));
+             base::Value::List().Append(
+                 proto::ON_DEVICE_MODEL_PERFORMANCE_HINT_CPU)));
 
 class StubObserver : public OnDeviceModelComponentStateManager::Observer {
  public:
@@ -83,8 +101,8 @@
               on_device_component_state_manager_.IsInstallerRegistered());
   }
 
-  scoped_refptr<OnDeviceModelComponentStateManager> manager() {
-    return on_device_component_state_manager_.get();
+  base::WeakPtr<OnDeviceModelComponentStateManager> manager() {
+    return on_device_component_state_manager_.get()->GetWeakPtr();
   }
 
   void WaitForStartup() {
@@ -611,5 +629,70 @@
           proto::ON_DEVICE_MODEL_PERFORMANCE_HINT_FASTEST_INFERENCE));
 }
 
+TEST_F(OnDeviceModelComponentTest,
+       SetReadyManifestContainsPerformanceHintsCPUNotEnabled) {
+  manager()->OnStartup();
+  WaitForStartup();
+
+  manager()->DevicePerformanceClassChanged(
+      base::DoNothing(), OnDeviceModelPerformanceClass::kVeryLow);
+
+  manager()->SetReady(base::Version("0.1.1"),
+                      base::FilePath(FILE_PATH_LITERAL("/some/path")),
+                      kTestManifestWithPerfHints);
+  EXPECT_EQ(manager()->GetState()->GetBaseModelSpec().model_name, "Test");
+  EXPECT_EQ(manager()->GetState()->GetBaseModelSpec().model_version, "0.0.1");
+  EXPECT_TRUE(manager()
+                  ->GetState()
+                  ->GetBaseModelSpec()
+                  .supported_performance_hints.empty());
+}
+
+TEST_F(OnDeviceModelComponentTest,
+       SetReadyManifestContainsPerformanceHintsCPU) {
+  base::test::ScopedFeatureList feature_list;
+  feature_list.InitAndEnableFeatureWithParameters(
+      on_device_model::features::kOnDeviceModelCpuBackend,
+      {{"on_device_cpu_ram_threshold_mb", "0"},
+       {"on_device_cpu_processor_count_threshold", "0"}});
+  manager()->OnStartup();
+  WaitForStartup();
+
+  manager()->DevicePerformanceClassChanged(
+      base::DoNothing(), OnDeviceModelPerformanceClass::kVeryLow);
+
+  manager()->SetReady(base::Version("0.1.1"),
+                      base::FilePath(FILE_PATH_LITERAL("/some/path")),
+                      kTestManifestWithPerfHints);
+  EXPECT_EQ(manager()->GetState()->GetBaseModelSpec().model_name, "Test");
+  EXPECT_EQ(manager()->GetState()->GetBaseModelSpec().model_version, "0.0.1");
+  EXPECT_THAT(
+      manager()->GetState()->GetBaseModelSpec().supported_performance_hints,
+      UnorderedElementsAre(proto::ON_DEVICE_MODEL_PERFORMANCE_HINT_CPU));
+}
+
+TEST_F(OnDeviceModelComponentTest,
+       SetReadyManifestContainsPerformanceHintsCPUOnly) {
+  base::test::ScopedFeatureList feature_list;
+  feature_list.InitAndEnableFeatureWithParameters(
+      on_device_model::features::kOnDeviceModelCpuBackend,
+      {{"on_device_cpu_ram_threshold_mb", "0"},
+       {"on_device_cpu_processor_count_threshold", "0"}});
+  manager()->OnStartup();
+  WaitForStartup();
+
+  manager()->DevicePerformanceClassChanged(
+      base::DoNothing(), OnDeviceModelPerformanceClass::kHigh);
+
+  manager()->SetReady(base::Version("0.1.1"),
+                      base::FilePath(FILE_PATH_LITERAL("/some/path")),
+                      kTestManifestWithCPUPerfHints);
+  EXPECT_EQ(manager()->GetState()->GetBaseModelSpec().model_name, "Test");
+  EXPECT_EQ(manager()->GetState()->GetBaseModelSpec().model_version, "0.0.1");
+  EXPECT_THAT(
+      manager()->GetState()->GetBaseModelSpec().supported_performance_hints,
+      UnorderedElementsAre(proto::ON_DEVICE_MODEL_PERFORMANCE_HINT_CPU));
+}
+
 }  // namespace
 }  // namespace optimization_guide
diff --git a/components/optimization_guide/core/model_execution/on_device_model_service_controller.cc b/components/optimization_guide/core/model_execution/on_device_model_service_controller.cc
index f40e586..5891d96 100644
--- a/components/optimization_guide/core/model_execution/on_device_model_service_controller.cc
+++ b/components/optimization_guide/core/model_execution/on_device_model_service_controller.cc
@@ -47,6 +47,7 @@
 #include "mojo/public/cpp/bindings/pending_receiver.h"
 #include "mojo/public/cpp/bindings/receiver.h"
 #include "mojo/public/cpp/bindings/receiver_set.h"
+#include "services/on_device_model/public/cpp/features.h"
 #include "services/on_device_model/public/cpp/model_assets.h"
 #include "services/on_device_model/public/cpp/service_client.h"
 #include "services/on_device_model/public/cpp/text_safety_assets.h"
@@ -563,7 +564,8 @@
   model_paths.weights = model_metadata_->model_path().Append(kWeightsFile);
 
   // TODO(crbug.com/400998489): Cache files are experimental for now.
-  if (features::ForceCpuBackendForOnDeviceModel()) {
+  if (base::FeatureList::IsEnabled(
+          on_device_model::features::kOnDeviceModelForceCpuBackend)) {
     model_paths.cache =
         model_metadata_->model_path().Append(kExperimentalCacheFile);
   }
@@ -575,9 +577,11 @@
     mojo::PendingReceiver<on_device_model::mojom::OnDeviceModel> model,
     on_device_model::ModelAssets assets) {
   auto params = on_device_model::mojom::LoadModelParams::New();
-  params->backend_type = features::ForceCpuBackendForOnDeviceModel()
-                             ? ml::ModelBackendType::kCpuBackend
-                             : ml::ModelBackendType::kGpuBackend;
+  params->backend_type =
+      base::FeatureList::IsEnabled(
+          on_device_model::features::kOnDeviceModelForceCpuBackend)
+          ? ml::ModelBackendType::kCpuBackend
+          : ml::ModelBackendType::kGpuBackend;
   params->assets = std::move(assets);
   // TODO(crbug.com/302402959): Choose max_tokens based on device.
   params->max_tokens = features::GetOnDeviceModelMaxTokens();
diff --git a/components/optimization_guide/core/model_execution/on_device_model_service_controller_unittest.cc b/components/optimization_guide/core/model_execution/on_device_model_service_controller_unittest.cc
index 5a7e69c2..fd99043 100644
--- a/components/optimization_guide/core/model_execution/on_device_model_service_controller_unittest.cc
+++ b/components/optimization_guide/core/model_execution/on_device_model_service_controller_unittest.cc
@@ -63,6 +63,7 @@
 #include "components/prefs/testing_pref_service.h"
 #include "mojo/public/cpp/bindings/receiver.h"
 #include "services/on_device_model/public/cpp/capabilities.h"
+#include "services/on_device_model/public/cpp/features.h"
 #include "services/on_device_model/public/cpp/service_client.h"
 #include "services/on_device_model/public/cpp/test_support/fake_service.h"
 #include "services/on_device_model/public/mojom/on_device_model.mojom.h"
@@ -431,11 +432,11 @@
   // TODO(crbug.com/400998489): Cache files are experimental for now. Stop
   // setting this feature flag once that's no longer the case.
   base::test::ScopedFeatureList feature_list;
-  feature_list.InitAndEnableFeatureWithParameters(
-      features::kOptimizationGuideOnDeviceModel,
-      {{"on_device_model_force_cpu_backend", "true"},
-       {"on_device_model_topk", "1"},
-       {"on_device_model_temperature", "0"}});
+  feature_list.InitWithFeaturesAndParameters(
+      {{features::kOptimizationGuideOnDeviceModel,
+        {{"on_device_model_topk", "1"}, {"on_device_model_temperature", "0"}}},
+       {on_device_model::features::kOnDeviceModelForceCpuBackend, {}}},
+      {});
 
   FakeBaseModelAsset base_model_with_cache({
       .cache_weight = 1015,
diff --git a/components/optimization_guide/core/model_execution/test/test_on_device_model_component_state_manager.cc b/components/optimization_guide/core/model_execution/test/test_on_device_model_component_state_manager.cc
index 39d4d4cca..dcd21d91 100644
--- a/components/optimization_guide/core/model_execution/test/test_on_device_model_component_state_manager.cc
+++ b/components/optimization_guide/core/model_execution/test/test_on_device_model_component_state_manager.cc
@@ -25,13 +25,13 @@
 
   // OnDeviceModelComponentStateManager::Delegate.
   void RegisterInstaller(
-      scoped_refptr<OnDeviceModelComponentStateManager> state_manager,
+      base::WeakPtr<OnDeviceModelComponentStateManager> state_manager,
       bool is_already_installing) override {
     if (state_) {
       state_->installer_registered_ = true;
     }
   }
-  void Uninstall(scoped_refptr<OnDeviceModelComponentStateManager>
+  void Uninstall(base::WeakPtr<OnDeviceModelComponentStateManager>
                      state_manager) override {
     if (state_) {
       state_->uninstall_called_ = true;
@@ -70,18 +70,15 @@
   Reset();
 }
 
-scoped_refptr<OnDeviceModelComponentStateManager>
+base::WeakPtr<OnDeviceModelComponentStateManager>
 TestOnDeviceModelComponentStateManager::get() {
   // Note that we create the instance lazily to allow tests time to register
   // prefs.
   if (!manager_) {
-    CHECK(!OnDeviceModelComponentStateManager::GetInstanceForTesting())
-        << "Instance already exists";
-    manager_ = OnDeviceModelComponentStateManager::CreateOrGet(
+    manager_ = std::make_unique<OnDeviceModelComponentStateManager>(
         local_state_, state_->CreateDelegate());
-    CHECK(manager_);
   }
-  return manager_;
+  return manager_->GetWeakPtr();
 }
 
 void TestOnDeviceModelComponentStateManager::Reset() {
diff --git a/components/optimization_guide/core/model_execution/test/test_on_device_model_component_state_manager.h b/components/optimization_guide/core/model_execution/test/test_on_device_model_component_state_manager.h
index ee2e984..b9264f2f1 100644
--- a/components/optimization_guide/core/model_execution/test/test_on_device_model_component_state_manager.h
+++ b/components/optimization_guide/core/model_execution/test/test_on_device_model_component_state_manager.h
@@ -49,7 +49,7 @@
   explicit TestOnDeviceModelComponentStateManager(PrefService* local_state);
   ~TestOnDeviceModelComponentStateManager();
 
-  scoped_refptr<OnDeviceModelComponentStateManager> get();
+  base::WeakPtr<OnDeviceModelComponentStateManager> get();
 
   void Reset();
 
@@ -62,7 +62,7 @@
 
  private:
   raw_ptr<PrefService> local_state_;
-  scoped_refptr<OnDeviceModelComponentStateManager> manager_;
+  std::unique_ptr<OnDeviceModelComponentStateManager> manager_;
   std::unique_ptr<TestComponentState> state_;
 };
 
diff --git a/components/optimization_guide/core/optimization_guide_features.cc b/components/optimization_guide/core/optimization_guide_features.cc
index 649453c..b03af531 100644
--- a/components/optimization_guide/core/optimization_guide_features.cc
+++ b/components/optimization_guide/core/optimization_guide_features.cc
@@ -680,13 +680,6 @@
   return ranks;
 }
 
-bool ForceCpuBackendForOnDeviceModel() {
-  static const base::FeatureParam<bool> kForceCpuBackend{
-      &kOptimizationGuideOnDeviceModel, "on_device_model_force_cpu_backend",
-      false};
-  return kForceCpuBackend.Get();
-}
-
 bool ShouldEnableOptimizationGuideIconView() {
   return base::FeatureList::IsEnabled(kOptimizationGuideIconView);
 }
diff --git a/components/optimization_guide/core/optimization_guide_features.h b/components/optimization_guide/core/optimization_guide_features.h
index fa55206..650fcb3b 100644
--- a/components/optimization_guide/core/optimization_guide_features.h
+++ b/components/optimization_guide/core/optimization_guide_features.h
@@ -394,10 +394,6 @@
 COMPONENT_EXPORT(OPTIMIZATION_GUIDE_FEATURES)
 std::vector<uint32_t> GetOnDeviceModelAllowedAdaptationRanks();
 
-// Whether the on-device model should be limited to running only on the CPU.
-COMPONENT_EXPORT(OPTIMIZATION_GUIDE_FEATURES)
-bool ForceCpuBackendForOnDeviceModel();
-
 // Returns whether the icon view should be enabled.
 COMPONENT_EXPORT(OPTIMIZATION_GUIDE_FEATURES)
 bool ShouldEnableOptimizationGuideIconView();
diff --git a/components/optimization_guide/internal b/components/optimization_guide/internal
index 2a21ca6..a1b6a0b 160000
--- a/components/optimization_guide/internal
+++ b/components/optimization_guide/internal
@@ -1 +1 @@
-Subproject commit 2a21ca633ac74e58197d23619bab4d2e4997e318
+Subproject commit a1b6a0bb7276cee3d161aba543b0a24f8a087d5c
diff --git a/components/optimization_guide/proto/on_device_base_model_metadata.proto b/components/optimization_guide/proto/on_device_base_model_metadata.proto
index 70b7d57d..d43b596f 100644
--- a/components/optimization_guide/proto/on_device_base_model_metadata.proto
+++ b/components/optimization_guide/proto/on_device_base_model_metadata.proto
@@ -32,11 +32,14 @@
 
   ON_DEVICE_MODEL_PERFORMANCE_HINT_UNSPECIFIED = 0;
 
-  // Allows for use of the highest quality model for the feature, which is only
-  // supported for "high-end" devices.
+  // Allows for use of the highest quality GPU model for the feature, which is
+  // only supported for "high-end" devices.
   ON_DEVICE_MODEL_PERFORMANCE_HINT_HIGHEST_QUALITY = 1;
 
-  // Allows for use of the smaller model for the feature, which can result in
-  // degraded quality but results in higher user reach.
+  // Allows for use of the smaller GPU model for the feature, which can result
+  // in degraded quality but results in higher user reach.
   ON_DEVICE_MODEL_PERFORMANCE_HINT_FASTEST_INFERENCE = 2;
+
+  // Allows for use of a model that can run on CPU.
+  ON_DEVICE_MODEL_PERFORMANCE_HINT_CPU = 3;
 }
diff --git a/components/page_content_annotations/core/BUILD.gn b/components/page_content_annotations/core/BUILD.gn
index ac78e55..36d3942 100644
--- a/components/page_content_annotations/core/BUILD.gn
+++ b/components/page_content_annotations/core/BUILD.gn
@@ -38,6 +38,8 @@
       "edu_classifier_model_executor.h",
       "edu_classifier_model_handler.cc",
       "edu_classifier_model_handler.h",
+      "on_device_category_classifier.cc",
+      "on_device_category_classifier.h",
       "page_content_annotation_job_executor.cc",
       "page_content_annotation_job_executor.h",
       "page_content_annotations_model_manager.cc",
@@ -60,6 +62,7 @@
     "//components/omnibox/common",
     "//components/optimization_guide/core",
     "//components/optimization_guide/core:features",
+    "//components/passage_embeddings:passage_embeddings_types",
     "//components/search",
     "//components/search_engines",
     "//services/metrics/public/cpp:metrics_cpp",
diff --git a/components/page_content_annotations/core/DEPS b/components/page_content_annotations/core/DEPS
index db0a7714..9890b67 100644
--- a/components/page_content_annotations/core/DEPS
+++ b/components/page_content_annotations/core/DEPS
@@ -11,6 +11,7 @@
   "+components/optimization_guide",
   "+components/optimization_guide/core",
   "+components/optimization_guide/proto",
+  "+components/passage_embeddings",
   "+components/search",
   "+components/search_engines",
   "+net/base",
diff --git a/components/page_content_annotations/core/on_device_category_classifier.cc b/components/page_content_annotations/core/on_device_category_classifier.cc
new file mode 100644
index 0000000..9ef9bb10
--- /dev/null
+++ b/components/page_content_annotations/core/on_device_category_classifier.cc
@@ -0,0 +1,115 @@
+// 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/page_content_annotations/core/on_device_category_classifier.h"
+
+#include "base/barrier_callback.h"
+#include "base/task/sequenced_task_runner.h"
+#include "base/task/task_traits.h"
+#include "base/task/thread_pool.h"
+#include "components/optimization_guide/core/delivery/optimization_guide_model_provider.h"
+#include "components/page_content_annotations/core/edu_classifier_model_handler.h"
+#include "components/page_content_annotations/core/page_content_annotation_type.h"
+
+namespace page_content_annotations {
+
+namespace {
+
+// Callback invoked when a single category classifier has completed.
+void OnSingleCategoryClassifierComplete(
+    CategoryType category_type,
+    base::OnceCallback<void(std::pair<CategoryType, std::optional<float>>)>
+        callback,
+    const std::optional<float>& maybe_score) {
+  std::move(callback).Run(std::make_pair(category_type, maybe_score));
+}
+
+}  // namespace
+
+OnDeviceCategoryClassifier::OnDeviceCategoryClassifier(
+    optimization_guide::OptimizationGuideModelProvider*
+        optimization_guide_model_provider,
+    passage_embeddings::EmbedderMetadataProvider* embedder_metadata_provider,
+    passage_embeddings::Embedder* embedder)
+    : optimization_guide_model_provider_(optimization_guide_model_provider),
+      embedder_metadata_provider_(embedder_metadata_provider),
+      embedder_(embedder) {
+  scoped_observation_.Observe(embedder_metadata_provider_);
+  category_classifier_model_handlers_[CategoryType::kEducation] =
+      std::make_unique<EduClassifierModelHandler>(
+          optimization_guide_model_provider_,
+          base::ThreadPool::CreateSequencedTaskRunner(
+              {base::MayBlock(), base::TaskPriority::BEST_EFFORT}));
+}
+
+OnDeviceCategoryClassifier::~OnDeviceCategoryClassifier() = default;
+
+void OnDeviceCategoryClassifier::ClassifyText(
+    const std::string& text,
+    base::OnceCallback<void(std::vector<Category>)> callback) {
+  if (!is_embedder_available_) {
+    std::move(callback).Run({});
+    return;
+  }
+
+  embedder_->ComputePassagesEmbeddings(
+      passage_embeddings::PassagePriority::kUrgent, {text},
+      base::BindOnce(&OnDeviceCategoryClassifier::OnEmbeddingComputed,
+                     weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
+}
+
+void OnDeviceCategoryClassifier::EmbedderMetadataUpdated(
+    passage_embeddings::EmbedderMetadata metadata) {
+  is_embedder_available_ = metadata.IsValid();
+}
+
+void OnDeviceCategoryClassifier::OnEmbeddingComputed(
+    base::OnceCallback<void(std::vector<Category>)> callback,
+    std::vector<std::string> passages,
+    std::vector<passage_embeddings::Embedding> embeddings,
+    passage_embeddings::Embedder::TaskId task_id,
+    passage_embeddings::ComputeEmbeddingsStatus status) {
+  // Embedding was not successfully generated.
+  if (status != passage_embeddings::ComputeEmbeddingsStatus::kSuccess) {
+    std::move(callback).Run({});
+    return;
+  }
+
+  // Unexpected output size. Just return.
+  if (embeddings.size() != 1u) {
+    std::move(callback).Run({});
+    return;
+  }
+
+  // Run each of the category classifiers on the returned embedding.
+  auto barrier_callback =
+      base::BarrierCallback<std::pair<CategoryType, std::optional<float>>>(
+          category_classifier_model_handlers_.size(),
+          base::BindOnce(
+              &OnDeviceCategoryClassifier::OnCategoryClassifiersCompleted,
+              weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
+  for (const auto& [category_type, model_handler] :
+       category_classifier_model_handlers_) {
+    model_handler->ExecuteModelWithInput(
+        base::BindOnce(&OnSingleCategoryClassifierComplete, category_type,
+                       barrier_callback),
+        embeddings[0].GetData());
+  }
+}
+
+void OnDeviceCategoryClassifier::OnCategoryClassifiersCompleted(
+    base::OnceCallback<void(std::vector<Category>)> callback,
+    const std::vector<std::pair<CategoryType, std::optional<float>>>&
+        classifier_outputs) {
+  std::vector<Category> outputs;
+  for (const auto& [category_type, maybe_score] : classifier_outputs) {
+    if (maybe_score) {
+      outputs.push_back(
+          {.category_type = category_type, .score = *maybe_score});
+    }
+  }
+  std::move(callback).Run(outputs);
+}
+
+}  // namespace page_content_annotations
diff --git a/components/page_content_annotations/core/on_device_category_classifier.h b/components/page_content_annotations/core/on_device_category_classifier.h
new file mode 100644
index 0000000..e7d3e64
--- /dev/null
+++ b/components/page_content_annotations/core/on_device_category_classifier.h
@@ -0,0 +1,100 @@
+// 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_PAGE_CONTENT_ANNOTATIONS_CORE_ON_DEVICE_CATEGORY_CLASSIFIER_H_
+#define COMPONENTS_PAGE_CONTENT_ANNOTATIONS_CORE_ON_DEVICE_CATEGORY_CLASSIFIER_H_
+
+#include <memory>
+#include <optional>
+#include <string>
+#include <vector>
+
+#include "base/containers/flat_map.h"
+#include "base/functional/callback.h"
+#include "base/memory/raw_ptr.h"
+#include "base/scoped_observation.h"
+#include "components/optimization_guide/core/inference/model_handler.h"
+#include "components/passage_embeddings/passage_embeddings_types.h"
+
+namespace optimization_guide {
+class OptimizationGuideModelProvider;
+}  // namespace optimization_guide
+
+namespace page_content_annotations {
+
+struct Category;
+enum class CategoryType;
+
+// Manages the loading and execution of models used to classify the category
+// represented by the text.
+class OnDeviceCategoryClassifier
+    : public passage_embeddings::EmbedderMetadataObserver {
+ public:
+  OnDeviceCategoryClassifier(
+      optimization_guide::OptimizationGuideModelProvider*
+          optimization_guide_model_provider,
+      passage_embeddings::EmbedderMetadataProvider* embedder_metadata_provider,
+      passage_embeddings::Embedder* embedder);
+  ~OnDeviceCategoryClassifier() override;
+  OnDeviceCategoryClassifier(const OnDeviceCategoryClassifier&) = delete;
+  OnDeviceCategoryClassifier& operator=(const OnDeviceCategoryClassifier&) =
+      delete;
+
+  // Classifies `text` and invokes `callback` with the classifications of all
+  // supported categories when complete.
+  //
+  // Note that it is possible that this returns empty if the underlying models
+  // are not available yet.
+  void ClassifyText(const std::string& text,
+                    base::OnceCallback<void(std::vector<Category>)> callback);
+
+ private:
+  // EmbedderMetadataObserver:
+  void EmbedderMetadataUpdated(
+      passage_embeddings::EmbedderMetadata metadata) override;
+
+  // Callback invoked when the embedding has been computed.
+  void OnEmbeddingComputed(
+      base::OnceCallback<void(std::vector<Category>)> callback,
+      std::vector<std::string> passages,
+      std::vector<passage_embeddings::Embedding> embeddings,
+      passage_embeddings::Embedder::TaskId task_id,
+      passage_embeddings::ComputeEmbeddingsStatus status);
+
+  // Callback invoked when all category classifiers have completed execution.
+  void OnCategoryClassifiersCompleted(
+      base::OnceCallback<void(std::vector<Category>)> callback,
+      const std::vector<std::pair<CategoryType, std::optional<float>>>&
+          classifier_outputs);
+
+  // The model handlers for the category classifiers to run.
+  base::flat_map<
+      CategoryType,
+      std::unique_ptr<
+          optimization_guide::ModelHandler<float, const std::vector<float>&>>>
+      category_classifier_model_handlers_;
+
+  // Whether the embedder is currently available.
+  bool is_embedder_available_ = false;
+
+  // The model provider for category regression layers. Not owned.
+  raw_ptr<optimization_guide::OptimizationGuideModelProvider>
+      optimization_guide_model_provider_;
+  // The provider of embedder metadata to determine whether `embedder_` is
+  // currently available to submit jobs to. Not owned.
+  raw_ptr<passage_embeddings::EmbedderMetadataProvider>
+      embedder_metadata_provider_;
+  // The text embedder to use. Not owned.
+  raw_ptr<passage_embeddings::Embedder> embedder_;
+
+  base::ScopedObservation<passage_embeddings::EmbedderMetadataProvider,
+                          passage_embeddings::EmbedderMetadataObserver>
+      scoped_observation_{this};
+
+  base::WeakPtrFactory<OnDeviceCategoryClassifier> weak_ptr_factory_{this};
+};
+
+}  // namespace page_content_annotations
+
+#endif  // COMPONENTS_PAGE_CONTENT_ANNOTATIONS_CORE_ON_DEVICE_CATEGORY_CLASSIFIER_H_
diff --git a/components/page_content_annotations/core/page_content_annotation_type.h b/components/page_content_annotations/core/page_content_annotation_type.h
index fdf5c7a6..8bc50775 100644
--- a/components/page_content_annotations/core/page_content_annotation_type.h
+++ b/components/page_content_annotations/core/page_content_annotation_type.h
@@ -35,6 +35,22 @@
 
 std::string AnnotationTypeToString(AnnotationType type);
 
+enum class CategoryType {
+  kEducation = 0,
+
+  // Add new types above this line.
+  kMaxValue = kEducation,
+};
+
+struct Category {
+  // The classified category.
+  CategoryType category_type;
+
+  // A score from 0 to 1, inclusive. The higher the more likely a piece of text
+  // is to be classified as the category type.
+  float score;
+};
+
 }  // namespace page_content_annotations
 
 #endif  // COMPONENTS_PAGE_CONTENT_ANNOTATIONS_CORE_PAGE_CONTENT_ANNOTATION_TYPE_H_
diff --git a/components/page_content_annotations/core/page_content_annotations_service.cc b/components/page_content_annotations/core/page_content_annotations_service.cc
index 1f4c240..dc81de3 100644
--- a/components/page_content_annotations/core/page_content_annotations_service.cc
+++ b/components/page_content_annotations/core/page_content_annotations_service.cc
@@ -34,6 +34,7 @@
 #include "components/page_content_annotations/core/page_content_annotations_features.h"
 #include "components/page_content_annotations/core/page_content_annotations_switches.h"
 #include "components/page_content_annotations/core/page_content_annotations_validator.h"
+#include "components/passage_embeddings/passage_embeddings_types.h"
 #include "components/search/search.h"
 #include "services/metrics/public/cpp/metrics_utils.h"
 #include "services/metrics/public/cpp/ukm_builders.h"
@@ -43,6 +44,7 @@
 #include "third_party/omnibox_proto/types.pb.h"
 
 #if BUILDFLAG(BUILD_WITH_TFLITE_LIB)
+#include "components/page_content_annotations/core/on_device_category_classifier.h"
 #include "components/page_content_annotations/core/page_content_annotations_model_manager.h"
 #endif
 
@@ -184,6 +186,8 @@
     const base::FilePath& database_dir,
     OptimizationGuideLogger* optimization_guide_logger,
     optimization_guide::OptimizationGuideDecider* optimization_guide_decider,
+    passage_embeddings::EmbedderMetadataProvider* embedder_metadata_provider,
+    passage_embeddings::Embedder* embedder,
     scoped_refptr<base::SequencedTaskRunner> background_task_runner)
     : min_page_category_score_to_persist_(
           features::GetMinimumPageCategoryScoreToPersist()),
@@ -216,6 +220,14 @@
         AnnotationType::kContentVisibility, base::DoNothing());
     annotation_types_to_execute_.push_back(AnnotationType::kContentVisibility);
   }
+
+  if (base::FeatureList::IsEnabled(features::kOnDeviceCategoryClassifier) &&
+      embedder_metadata_provider && embedder) {
+    on_device_category_classifier_ =
+        std::make_unique<OnDeviceCategoryClassifier>(
+            optimization_guide_model_provider, embedder_metadata_provider,
+            embedder);
+  }
 #endif
 
   is_remote_page_metadata_fetching_enabled_ =
@@ -919,6 +931,21 @@
   }
 }
 
+void PageContentAnnotationsService::ClassifyCategoriesForText(
+    const std::string& text,
+    base::OnceCallback<void(std::vector<Category>)> callback) {
+#if BUILDFLAG(BUILD_WITH_TFLITE_LIB)
+  if (!on_device_category_classifier_) {
+    std::move(callback).Run({});
+    return;
+  }
+
+  on_device_category_classifier_->ClassifyText(text, std::move(callback));
+#else
+  std::move(callback).Run({});
+#endif
+}
+
 HistoryVisit::HistoryVisit() = default;
 
 HistoryVisit::HistoryVisit(base::Time nav_entry_timestamp, GURL url) {
diff --git a/components/page_content_annotations/core/page_content_annotations_service.h b/components/page_content_annotations/core/page_content_annotations_service.h
index e2a8a06..9cc160ee 100644
--- a/components/page_content_annotations/core/page_content_annotations_service.h
+++ b/components/page_content_annotations/core/page_content_annotations_service.h
@@ -56,8 +56,14 @@
 class OptimizationMetadata;
 }  // namespace optimization_guide
 
+namespace passage_embeddings {
+class Embedder;
+class EmbedderMetadataProvider;
+}  // namespace passage_embeddings
+
 namespace page_content_annotations {
 
+class OnDeviceCategoryClassifier;
 class PageContentAnnotationsModelManager;
 class PageContentAnnotationsServiceBrowserTest;
 class PageContentAnnotationsValidator;
@@ -145,6 +151,8 @@
       const base::FilePath& database_dir,
       OptimizationGuideLogger* optimization_guide_logger,
       optimization_guide::OptimizationGuideDecider* optimization_guide_decider,
+      passage_embeddings::EmbedderMetadataProvider* embedder_metadata_provider,
+      passage_embeddings::Embedder* embedder,
       scoped_refptr<base::SequencedTaskRunner> background_task_runner);
   ~PageContentAnnotationsService() override;
   PageContentAnnotationsService(const PageContentAnnotationsService&) = delete;
@@ -216,6 +224,13 @@
     return optimization_guide_logger_;
   }
 
+  // Classifies categories for a piece of text.
+  //
+  // DO NOT USE. This is temporary until the rest of the API is hooked up.
+  void ClassifyCategoriesForText(
+      const std::string& text,
+      base::OnceCallback<void(std::vector<Category>)> callback);
+
  private:
 #if BUILDFLAG(BUILD_WITH_TFLITE_LIB)
   // Callback invoked when a single |visit| has been annotated.
@@ -253,6 +268,7 @@
 
   std::unique_ptr<PageContentAnnotationsModelManager> model_manager_;
 
+  std::unique_ptr<OnDeviceCategoryClassifier> on_device_category_classifier_;
 #endif
 
   // The annotator to use for requests to |BatchAnnotate| and |Annotate|. In
diff --git a/components/page_content_annotations/core/page_content_annotations_service_unittest.cc b/components/page_content_annotations/core/page_content_annotations_service_unittest.cc
index 91a30bb..d488162d 100644
--- a/components/page_content_annotations/core/page_content_annotations_service_unittest.cc
+++ b/components/page_content_annotations/core/page_content_annotations_service_unittest.cc
@@ -171,6 +171,8 @@
         /*database_dir=*/base::FilePath(),
         /*optimization_guide_logger=*/nullptr,
         optimization_guide_decider_.get(),
+        /*embedder_metadata_provider=*/nullptr,
+        /*embedder_=*/nullptr,
         /*background_task_runner=*/nullptr);
 
 #if BUILDFLAG(BUILD_WITH_TFLITE_LIB)
diff --git a/components/page_content_annotations/core/test_page_content_annotations_service.cc b/components/page_content_annotations/core/test_page_content_annotations_service.cc
index 0d8a4ec1..9dda7c19 100644
--- a/components/page_content_annotations/core/test_page_content_annotations_service.cc
+++ b/components/page_content_annotations/core/test_page_content_annotations_service.cc
@@ -18,7 +18,9 @@
 TestPageContentAnnotationsService::Create(
     optimization_guide::OptimizationGuideModelProvider*
         optimization_guide_model_provider,
-    history::HistoryService* history_service) {
+    history::HistoryService* history_service,
+    passage_embeddings::EmbedderMetadataProvider* embedder_metadata_provider,
+    passage_embeddings::Embedder* embedder) {
   std::unique_ptr<optimization_guide::TestOptimizationGuideModelProvider>
       test_model_provider;
   optimization_guide::OptimizationGuideModelProvider* model_provider_to_use =
@@ -42,7 +44,8 @@
   }
 
   auto test_service = base::WrapUnique(new TestPageContentAnnotationsService(
-      model_provider_to_use, history_service_to_use));
+      model_provider_to_use, history_service_to_use, embedder_metadata_provider,
+      embedder));
   test_service->temp_dir_ = std::move(temp_dir);
   test_service->test_model_provider_ = std::move(test_model_provider);
   test_service->test_history_service_ = std::move(test_history_service);
@@ -68,7 +71,9 @@
 TestPageContentAnnotationsService::TestPageContentAnnotationsService(
     optimization_guide::OptimizationGuideModelProvider*
         optimization_guide_model_provider,
-    history::HistoryService* history_service)
+    history::HistoryService* history_service,
+    passage_embeddings::EmbedderMetadataProvider* embedder_metadata_provider,
+    passage_embeddings::Embedder* embedder)
     : PageContentAnnotationsService(
           /*application_locale=*/"en-US",
           /*country_code=*/"US",
@@ -80,6 +85,8 @@
           /*database_dir=*/base::FilePath(),
           /*optimization_guide_logger=*/nullptr,
           /*optimization_guide_decider=*/nullptr,
+          embedder_metadata_provider,
+          embedder,
           /*background_task_runner=*/nullptr) {}
 
 }  // namespace page_content_annotations
diff --git a/components/page_content_annotations/core/test_page_content_annotations_service.h b/components/page_content_annotations/core/test_page_content_annotations_service.h
index c13dde9..a2901121 100644
--- a/components/page_content_annotations/core/test_page_content_annotations_service.h
+++ b/components/page_content_annotations/core/test_page_content_annotations_service.h
@@ -22,7 +22,10 @@
   static std::unique_ptr<TestPageContentAnnotationsService> Create(
       optimization_guide::OptimizationGuideModelProvider*
           optimization_guide_model_provider,
-      history::HistoryService* history_service);
+      history::HistoryService* history_service,
+      passage_embeddings::EmbedderMetadataProvider* embedder_metadata_provider =
+          nullptr,
+      passage_embeddings::Embedder* embedder = nullptr);
 
   ~TestPageContentAnnotationsService() override;
   TestPageContentAnnotationsService(const TestPageContentAnnotationsService&) =
@@ -34,7 +37,9 @@
   TestPageContentAnnotationsService(
       optimization_guide::OptimizationGuideModelProvider*
           optimization_guide_model_provider,
-      history::HistoryService* history_service);
+      history::HistoryService* history_service,
+      passage_embeddings::EmbedderMetadataProvider* embedder_metadata_provider,
+      passage_embeddings::Embedder* embedder);
 
   std::unique_ptr<base::ScopedTempDir> temp_dir_;
   std::unique_ptr<optimization_guide::TestOptimizationGuideModelProvider>
diff --git a/components/privacy_sandbox/privacy_sandbox_attestations/preload/manifest.json b/components/privacy_sandbox/privacy_sandbox_attestations/preload/manifest.json
index 4213a69..86a40e2 100644
--- a/components/privacy_sandbox/privacy_sandbox_attestations/preload/manifest.json
+++ b/components/privacy_sandbox/privacy_sandbox_attestations/preload/manifest.json
@@ -1,6 +1,6 @@
 {
   "manifest_version": 2,
   "name": "Privacy Sandbox Attestations",
-  "version": "2025.7.9.0",
+  "version": "2025.7.18.0",
   "pre_installed": true
 }
\ No newline at end of file
diff --git a/components/privacy_sandbox/privacy_sandbox_attestations/preload/privacy-sandbox-attestations.dat b/components/privacy_sandbox/privacy_sandbox_attestations/preload/privacy-sandbox-attestations.dat
index dcebc62..11661b8 100644
--- a/components/privacy_sandbox/privacy_sandbox_attestations/preload/privacy-sandbox-attestations.dat
+++ b/components/privacy_sandbox/privacy_sandbox_attestations/preload/privacy-sandbox-attestations.dat
Binary files differ
diff --git a/components/search_engines/keyword_table.cc b/components/search_engines/keyword_table.cc
index ad6b7bd0..b6fa442e6 100644
--- a/components/search_engines/keyword_table.cc
+++ b/components/search_engines/keyword_table.cc
@@ -37,25 +37,6 @@
 using ::base::Time;
 using ::country_codes::CountryId;
 
-namespace features {
-BASE_FEATURE(kKeywordTableHashVerification,
-             "KeywordTableHashVerification",
-// Only enable this hash checking feature on Windows. This because the value of
-// OSCrypt::IsEncryptionAvailable can vary and is platform specific. E.g.
-// os_crypt_posix.cc historically returned 'false' for IsEncryptionAvailable. On
-// Linux, OSCrypt::IsEncryptionAvailable can return `false` if v11 encryption is
-// not available, but data could still be encrypted with v10 encryption, and the
-// backend can change for various reasons including command line options or
-// desktop window manager.
-#if BUILDFLAG(IS_WIN)
-             base::FEATURE_ENABLED_BY_DEFAULT
-#else
-             base::FEATURE_DISABLED_BY_DEFAULT
-#endif  // BUILDFLAG(IS_WIN)
-);
-
-}  // namespace features
-
 namespace {
 
 // These values are persisted to logs. Entries should not be renumbered and
@@ -524,10 +505,12 @@
                               all_rows_migrated);
   };
 
+  // See the comment in `GetKeywordDataFromStatement` as to why this code is
+  // only enabled for Windows.
+#if BUILDFLAG(IS_WIN)
   // If there is no platform encryption, nothing left to do, since the
   // `url_hash` column will just be NULL.
-  if (!base::FeatureList::IsEnabled(features::kKeywordTableHashVerification) ||
-      !encryptor()->IsEncryptionAvailable()) {
+  if (!encryptor()->IsEncryptionAvailable()) {
     return transaction.Commit();
   }
 
@@ -570,7 +553,7 @@
       continue;
     }
   }
-
+#endif  // BUILDFLAG(IS_WIN)
   return transaction.Commit();
 }
 
@@ -634,9 +617,16 @@
                                   status);
   };
 
-  if (!base::FeatureList::IsEnabled(features::kKeywordTableHashVerification)) {
-    status = HashValidationStatus::kNotVerifiedFeatureDisabled;
-  } else if (!encryptor()->IsDecryptionAvailable()) {
+// Only enable this hash checking feature on Windows. This because the value of
+// `OSCrypt::IsEncryptionAvailable` (exposed via the `Encryptor`
+// `IsDecryptionAvailable` API) can vary and is platform specific. E.g.
+// os_crypt_posix.cc historically returned 'false' for `IsEncryptionAvailable`.
+// On Linux, `IsEncryptionAvailable` can return `false` if v11 encryption is
+// not available, but data could still be encrypted with v10 encryption, and the
+// backend can change for various reasons including command line options or
+// desktop window manager.
+#if BUILDFLAG(IS_WIN)
+  if (!encryptor()->IsDecryptionAvailable()) {
     status = HashValidationStatus::kNotVerifiedNoCrypto;
   } else {
     const auto hash = encryptor()->DecryptData(s.ColumnBlob(27));
@@ -659,7 +649,9 @@
       return std::nullopt;
     }
   }
-
+#else
+  status = HashValidationStatus::kNotVerifiedFeatureDisabled;
+#endif  // BUILDFLAG(IS_WIN)
   return data;
 }
 
diff --git a/components/search_engines/keyword_table.h b/components/search_engines/keyword_table.h
index 12e73f0..a6008b0e 100644
--- a/components/search_engines/keyword_table.h
+++ b/components/search_engines/keyword_table.h
@@ -13,7 +13,6 @@
 #include <vector>
 
 #include "base/compiler_specific.h"
-#include "base/feature_list.h"
 #include "base/gtest_prod_util.h"
 #include "components/country_codes/country_codes.h"
 #include "components/search_engines/template_url_id.h"
@@ -26,12 +25,6 @@
 class Statement;
 }  // namespace sql
 
-namespace features {
-// An emergency 'off switch' to disable hash verification.
-// TODO(crbug.com/376303929): Remove in M134.
-BASE_DECLARE_FEATURE(kKeywordTableHashVerification);
-}  // namespace features
-
 // This class manages the |keywords| MetaTable within the SQLite database
 // passed to the constructor. It expects the following schema:
 //
diff --git a/components/search_engines/keyword_table_unittest.cc b/components/search_engines/keyword_table_unittest.cc
index 8254c4d0..e1649dac 100644
--- a/components/search_engines/keyword_table_unittest.cc
+++ b/components/search_engines/keyword_table_unittest.cc
@@ -127,18 +127,20 @@
 
 
 TEST_F(KeywordTableTest, Keywords) {
-  // The feature is tested elsewhere, force enable to make sure expectations
-  // match.
-  base::test::ScopedFeatureList enable_verification(
-      features::kKeywordTableHashVerification);
-
   TemplateURLData keyword(CreateAndAddKeyword());
 
   base::HistogramTester histograms;
 
   KeywordTable::Keywords keywords(GetKeywords());
+  constexpr base::HistogramBase::Sample32 expected_bucket =
+#if BUILDFLAG(IS_WIN)
+      0;  // HashValidationStatus::kSuccess;
+#else
+      5;  // HashValidationStatus::kNotVerifiedFeatureDisabled;
+#endif  // BUILDFLAG(IS_WIN)
   histograms.ExpectUniqueSample("Search.KeywordTable.HashValidationStatus",
-                                /*HashValidationStatus::kSuccess*/ 0, 1);
+                                expected_bucket, 1);
+
   EXPECT_EQ(1U, keywords.size());
   const TemplateURLData& restored_keyword = keywords.front();
 
@@ -285,18 +287,17 @@
   }
 }
 
+#if BUILDFLAG(IS_WIN)
 namespace {
 
 struct TestCase {
   bool encryption_enabled;
-  bool feature_enabled;
   bool tamper;
   base::HistogramBase::Sample32 expected_histogram_sample;
   size_t expected_keyword_count;
 
   std::string Name() const {
     return base::StrCat({encryption_enabled ? "Encryption" : "NoEncryption",
-                         feature_enabled ? "FeatureEnabled" : "FeatureDisabled",
                          tamper ? "Tamper" : "NoTamper"});
   }
 };
@@ -306,14 +307,6 @@
 class KeywordTableTestEncryption
     : public KeywordTableTest,
       public ::testing::WithParamInterface<::TestCase> {
- public:
-  KeywordTableTestEncryption() {
-    feature_.InitWithFeatureState(features::kKeywordTableHashVerification,
-                                  GetParam().feature_enabled);
-  }
-
- private:
-  base::test::ScopedFeatureList feature_;
 };
 
 TEST_P(KeywordTableTestEncryption, KeywordBadHash) {
@@ -345,55 +338,25 @@
     /*empty*/,
     KeywordTableTestEncryption,
     ::testing::Values(
-        ::TestCase{
-            .encryption_enabled = false,
-            .feature_enabled = false,
-            .tamper = true,
-            .expected_histogram_sample = /*kNotVerifiedFeatureDisabled*/ 5,
-            .expected_keyword_count = 1u},
         ::TestCase{.encryption_enabled = false,
-                   .feature_enabled = true,
                    .tamper = true,
                    .expected_histogram_sample = /*kNotVerifiedNoCrypto*/ 4,
                    .expected_keyword_count = 1u},
-        ::TestCase{
-            .encryption_enabled = true,
-            .feature_enabled = false,
-            .tamper = true,
-            .expected_histogram_sample = /*kNotVerifiedFeatureDisabled*/ 5,
-            .expected_keyword_count = 1u},
         ::TestCase{.encryption_enabled = true,
-                   .feature_enabled = true,
                    .tamper = true,
                    .expected_histogram_sample = /*kIncorrectHash*/ 3,
                    .expected_keyword_count = 0},
-        ::TestCase{
-            .encryption_enabled = false,
-            .feature_enabled = false,
-            .tamper = false,
-            .expected_histogram_sample = /*kNotVerifiedFeatureDisabled*/ 5,
-            .expected_keyword_count = 1u},
         ::TestCase{.encryption_enabled = false,
-                   .feature_enabled = true,
                    .tamper = false,
                    .expected_histogram_sample = /*kNotVerifiedNoCrypto*/ 4,
                    .expected_keyword_count = 1u},
-        ::TestCase{
-            .encryption_enabled = true,
-            .feature_enabled = false,
-            .tamper = false,
-            .expected_histogram_sample = /*kNotVerifiedFeatureDisabled*/ 5,
-            .expected_keyword_count = 1u},
         ::TestCase{.encryption_enabled = true,
-                   .feature_enabled = true,
                    .tamper = false,
                    .expected_histogram_sample = /*kSuccess*/ 0,
                    .expected_keyword_count = 1u}),
     [](const auto& info) { return info.param.Name(); });
 
 TEST_F(KeywordTableTest, KeywordBadCrypto) {
-  base::test::ScopedFeatureList enable_verification(
-      features::kKeywordTableHashVerification);
   TemplateURLData keyword(CreateAndAddKeyword());
   {
     KeywordTable::Keywords keywords(GetKeywords());
@@ -416,11 +379,9 @@
                                   1);
   }
 }
+#endif  // BUILDFLAG(IS_WIN)
 
 TEST_F(KeywordTableTest, KeywordBadUrl) {
-  base::test::ScopedFeatureList enable_verification(
-      features::kKeywordTableHashVerification);
-
   TemplateURLData keyword(CreateAndAddKeyword());
   {
     KeywordTable::Keywords keywords(GetKeywords());
diff --git a/components/signin/public/base/signin_switches.cc b/components/signin/public/base/signin_switches.cc
index 16381c84..f47c372d 100644
--- a/components/signin/public/base/signin_switches.cc
+++ b/components/signin/public/base/signin_switches.cc
@@ -298,6 +298,14 @@
 BASE_FEATURE(kChromeIdentitySurveySigninPromoBubbleDismissed,
              "ChromeIdentitySurveyBubbleSigninPromoDismissed",
              base::FEATURE_DISABLED_BY_DEFAULT);
+
+BASE_FEATURE(kChromeIdentitySurveySwitchProfileFromProfileMenu,
+             "ChromeIdentitySurveySwitchProfileFromProfileMenus",
+             base::FEATURE_DISABLED_BY_DEFAULT);
+
+BASE_FEATURE(kChromeIdentitySurveySwitchProfileFromProfilePicker,
+             "ChromeIdentitySurveySwitchProfileFromProfilePicker",
+             base::FEATURE_DISABLED_BY_DEFAULT);
 #endif  // BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX)
 
 #if BUILDFLAG(ENABLE_DICE_SUPPORT)
diff --git a/components/signin/public/base/signin_switches.h b/components/signin/public/base/signin_switches.h
index eb1f65c..3ad9b3c 100644
--- a/components/signin/public/base/signin_switches.h
+++ b/components/signin/public/base/signin_switches.h
@@ -226,6 +226,10 @@
 BASE_DECLARE_FEATURE(kChromeIdentitySurveySigninInterceptProfileSeparation);
 COMPONENT_EXPORT(SIGNIN_SWITCHES)
 BASE_DECLARE_FEATURE(kChromeIdentitySurveySigninPromoBubbleDismissed);
+COMPONENT_EXPORT(SIGNIN_SWITCHES)
+BASE_DECLARE_FEATURE(kChromeIdentitySurveySwitchProfileFromProfileMenu);
+COMPONENT_EXPORT(SIGNIN_SWITCHES)
+BASE_DECLARE_FEATURE(kChromeIdentitySurveySwitchProfileFromProfilePicker);
 // LINT.ThenChange(//chrome/browser/signin/signin_util.cc)
 
 #endif  // BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX)
diff --git a/components/sync/protocol/autofill_specifics.proto b/components/sync/protocol/autofill_specifics.proto
index 5f76a4d..84ef712 100644
--- a/components/sync/protocol/autofill_specifics.proto
+++ b/components/sync/protocol/autofill_specifics.proto
@@ -418,6 +418,12 @@
     SOURCE_CURINOS = 3;
   }
 
+  enum CardCreationSource {
+    CREATION_SOURCE_UNSPECIFIED = 0;
+    CREATION_SOURCE_CHROME_PAYMENTS = 1;
+    CREATION_SOURCE_NON_CHROME_PAYMENTS = 2;
+  }
+
   // Server-generated unique ID string. This is opaque to the client.
   // This is the legacy version of `instrument_id`.
   optional string id = 1;
@@ -490,6 +496,11 @@
   // The source of the card's benefits. eg: Curinos. Each card can only have one
   // single benefit source, even if there are multiple benefits.
   optional CardBenefitSource card_benefit_source = 23;
+
+  // The source of the card creation, indicating whether the card was added
+  // through a Chrome-related service, or through an external service (which
+  // includes Android Autofill).
+  optional CardCreationSource card_creation_source = 24;
 }
 
 // Unused by the client since M121.
diff --git a/components/sync/protocol/proto_enum_conversions.cc b/components/sync/protocol/proto_enum_conversions.cc
index afa59d03..bf848805 100644
--- a/components/sync/protocol/proto_enum_conversions.cc
+++ b/components/sync/protocol/proto_enum_conversions.cc
@@ -633,6 +633,20 @@
 }
 
 const char* ProtoEnumToString(
+    sync_pb::WalletMaskedCreditCard::CardCreationSource card_creation_source) {
+  ASSERT_ENUM_BOUNDS(sync_pb::WalletMaskedCreditCard, CardCreationSource,
+                     CREATION_SOURCE_UNSPECIFIED,
+                     CREATION_SOURCE_NON_CHROME_PAYMENTS);
+  switch (card_creation_source) {
+    ENUM_CASE(sync_pb::WalletMaskedCreditCard, CREATION_SOURCE_UNSPECIFIED);
+    ENUM_CASE(sync_pb::WalletMaskedCreditCard, CREATION_SOURCE_CHROME_PAYMENTS);
+    ENUM_CASE(sync_pb::WalletMaskedCreditCard,
+              CREATION_SOURCE_NON_CHROME_PAYMENTS);
+  }
+  NOTREACHED();
+}
+
+const char* ProtoEnumToString(
     sync_pb::WalletMaskedCreditCard::VirtualCardEnrollmentState
         virtual_card_enrollment_state) {
   ASSERT_ENUM_BOUNDS(sync_pb::WalletMaskedCreditCard,
diff --git a/components/sync/protocol/proto_enum_conversions.h b/components/sync/protocol/proto_enum_conversions.h
index 51a53484..f86cd7006 100644
--- a/components/sync/protocol/proto_enum_conversions.h
+++ b/components/sync/protocol/proto_enum_conversions.h
@@ -153,6 +153,8 @@
     sync_pb::WalletMaskedCreditCard::CardBenefitSource
         card_benefit_source);
 
+const char* ProtoEnumToString(
+    sync_pb::WalletMaskedCreditCard::CardCreationSource card_creation_source);
 
 const char* ProtoEnumToString(
     sync_pb::WalletMaskedCreditCard::VirtualCardEnrollmentState
diff --git a/components/sync/protocol/proto_enum_conversions_unittest.cc b/components/sync/protocol/proto_enum_conversions_unittest.cc
index 9b10949..854dda8 100644
--- a/components/sync/protocol/proto_enum_conversions_unittest.cc
+++ b/components/sync/protocol/proto_enum_conversions_unittest.cc
@@ -147,6 +147,10 @@
   TestEnumStringsNonEmpty(sync_pb::WalletMaskedCreditCard::CardBenefitSource);
 }
 
+TEST(ProtoEnumConversionsTest, GetCardCreationSourceString) {
+  TestEnumStringsNonEmpty(sync_pb::WalletMaskedCreditCard::CardCreationSource);
+}
+
 TEST(ProtoEnumConversionsTest, GetActionRequiredString) {
   TestEnumStringsNonEmpty(sync_pb::PaymentInstrument::ActionRequired);
 }
diff --git a/components/sync/protocol/proto_visitors.h b/components/sync/protocol/proto_visitors.h
index b26d89b..a9f10945 100644
--- a/components/sync/protocol/proto_visitors.h
+++ b/components/sync/protocol/proto_visitors.h
@@ -1799,6 +1799,7 @@
   VISIT(product_terms_url);
   VISIT_ENUM(card_info_retrieval_enrollment_state);
   VISIT_ENUM(card_benefit_source);
+  VISIT_ENUM(card_creation_source);
 }
 
 VISIT_PROTO_FIELDS(const sync_pb::WalletMetadataSpecifics& proto) {
diff --git a/components/tabs/impl/tab_handle_factory.cc b/components/tabs/impl/tab_handle_factory.cc
index db8aebf..e9658f2 100644
--- a/components/tabs/impl/tab_handle_factory.cc
+++ b/components/tabs/impl/tab_handle_factory.cc
@@ -44,6 +44,17 @@
   handle_value_to_session_id_.emplace(handle_value, session_id);
 }
 
+void SessionMappedTabHandleFactory::ClearHandleMappings(
+    base::PassKey<SupportsTabHandles>,
+    int32_t handle_value) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence());
+  if (auto it = handle_value_to_session_id_.find(handle_value);
+      it != handle_value_to_session_id_.end()) {
+    session_id_to_handle_value_.erase(it->second);
+    handle_value_to_session_id_.erase(it);
+  }
+}
+
 int32_t SessionMappedTabHandleFactory::GetHandleForSessionId(
     int32_t session_id) const {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence());
@@ -77,4 +88,9 @@
       base::PassKey<SupportsTabHandles>(), GetHandle().raw_value(), session_id);
 }
 
+void SupportsTabHandles::ClearSessionId() {
+  SessionMappedTabHandleFactory::GetInstance().ClearHandleMappings(
+      base::PassKey<SupportsTabHandles>(), GetHandle().raw_value());
+}
+
 }  // namespace tabs
diff --git a/components/tabs/public/tab_handle_factory.h b/components/tabs/public/tab_handle_factory.h
index b05151ad..a760b85 100644
--- a/components/tabs/public/tab_handle_factory.h
+++ b/components/tabs/public/tab_handle_factory.h
@@ -35,6 +35,8 @@
   void SetSessionIdForHandle(base::PassKey<SupportsTabHandles>,
                              int32_t handle,
                              int32_t session_id);
+  // Clears all mappings associated with the handle.
+  void ClearHandleMappings(base::PassKey<SupportsTabHandles>, int32_t handle);
 
  private:
   friend class ::base::NoDestructor<SessionMappedTabHandleFactory>;
@@ -57,6 +59,11 @@
 
  protected:
   void SetSessionId(int32_t sesion_id);
+
+  // Resets the mapped session IDs.
+  // This is necessary as long as a tab may have multiple web contents
+  // throughout its lifetime.
+  void ClearSessionId();
 };
 
 }  // namespace tabs
diff --git a/components/tabs/public/tab_handle_factory_unittest.cc b/components/tabs/public/tab_handle_factory_unittest.cc
index c19af7ae..01c9188 100644
--- a/components/tabs/public/tab_handle_factory_unittest.cc
+++ b/components/tabs/public/tab_handle_factory_unittest.cc
@@ -24,6 +24,9 @@
   void SetSessionId(int32_t session_id) {
     SupportsTabHandles::SetSessionId(session_id);
   }
+
+  // Expose the protected ClearSessionId for testing.
+  void ClearSessionId() { SupportsTabHandles::ClearSessionId(); }
 };
 
 using SessionMappedTabHandleFactoryTest = testing::Test;
@@ -103,5 +106,23 @@
   EXPECT_EQ(session_id, factory->GetSessionIdForHandle(handle.raw_value()));
 }
 
+TEST_F(SessionMappedTabHandleFactoryTest, ClearSessionId) {
+  TestSupportsTabHandles tab;
+  const int32_t session_id = 1;
+  tab.SetSessionId(session_id);
+
+  auto* const factory = &SessionMappedTabHandleFactory::GetInstance();
+  TestHandle handle = tab.GetHandle();
+
+  // Verify the mappings exist before we clear them.
+  EXPECT_EQ(handle.raw_value(), factory->GetHandleForSessionId(session_id));
+  EXPECT_EQ(session_id, factory->GetSessionIdForHandle(handle.raw_value()));
+
+  // Clear the session ID and verify the mappings are gone.
+  tab.ClearSessionId();
+  EXPECT_EQ(TestHandle::NullValue, factory->GetHandleForSessionId(session_id));
+  EXPECT_FALSE(factory->GetSessionIdForHandle(handle.raw_value()).has_value());
+}
+
 }  // namespace
 }  // namespace tabs
diff --git a/components/viz/service/input/android_state_transfer_handler.cc b/components/viz/service/input/android_state_transfer_handler.cc
index 52af075..7f72eb2 100644
--- a/components/viz/service/input/android_state_transfer_handler.cc
+++ b/components/viz/service/input/android_state_transfer_handler.cc
@@ -11,6 +11,7 @@
 #include "base/notreached.h"
 #include "base/strings/string_number_conversions.h"
 #include "ui/events/android/events_android_utils.h"
+#include "ui/events/android/motion_event_android_factory.h"
 #include "ui/events/android/motion_event_android_native.h"
 
 namespace viz {
@@ -272,16 +273,15 @@
     return;
   }
 
-  std::optional<ui::MotionEventAndroidNative::EventTimes> event_times =
-      std::nullopt;
+  std::optional<ui::MotionEventAndroid::EventTimes> event_times = std::nullopt;
   if (action == AMOTION_EVENT_ACTION_DOWN) {
-    event_times = ui::MotionEventAndroidNative::EventTimes();
+    event_times = ui::MotionEventAndroid::EventTimes();
     // AMotionEvent_getDownTime returns down time in nanoseconds precision.
     event_times->latest = base::TimeTicks::FromJavaNanoTime(
         AMotionEvent_getDownTime(input_event.a_input_event()));
     event_times->oldest = event_times->latest;
   }
-  auto event = ui::MotionEventAndroidNative::Create(
+  auto event = ui::MotionEventAndroidFactory::CreateFromNative(
       std::move(input_event),
       1.f / state_for_curr_sequence_->transfer_state->dip_scale,
       state_for_curr_sequence_->transfer_state->web_contents_y_offset_pix,
diff --git a/components/webdata/common/web_database_migration_unittest.cc b/components/webdata/common/web_database_migration_unittest.cc
index 621d88c..d0b807ac 100644
--- a/components/webdata/common/web_database_migration_unittest.cc
+++ b/components/webdata/common/web_database_migration_unittest.cc
@@ -1516,6 +1516,7 @@
   }
 }
 
+#if BUILDFLAG(IS_WIN)
 class WebDatabaseMigrationTestEncryption
     : public WebDatabaseMigrationTest,
       public ::testing::WithParamInterface<bool> {
@@ -1525,11 +1526,6 @@
 
 // Tests addition of the url_hash column to the keywords table.
 TEST_P(WebDatabaseMigrationTestEncryption, MigrateVersion136ToCurrent) {
-  // The feature is tested elsewhere, force enable to make sure expectations
-  // match.
-  base::test::ScopedFeatureList enable_verification(
-      features::kKeywordTableHashVerification);
-
   encryptor_.set_encryption_available_for_testing(IsEncryptionAvailable());
   encryptor_.set_decryption_available_for_testing(IsEncryptionAvailable());
 
@@ -1594,13 +1590,10 @@
 // Tests migration of a keywords table with an empty url, which is invalid. The
 // entry should not be migrated, and the test should not crash. The dropping of
 // the invalid entry takes place upon the first GetKeywords call, and this is
-// tested elsewhere in KeywordTableTest.KeywordBadUrl.
+// tested elsewhere in KeywordTableTest.KeywordBadUrl. This test is only valid
+// on Windows because the bad url detection only happens if encrypted hashing is
+// enabled.
 TEST_F(WebDatabaseMigrationTest, MigrateVersion136ToCurrentBadUrl) {
-  // The feature is tested elsewhere, force enable to make sure expectations
-  // match.
-  base::test::ScopedFeatureList enable_verification(
-      features::kKeywordTableHashVerification);
-
   ASSERT_NO_FATAL_FAILURE(LoadDatabase(FILE_PATH_LITERAL("version_136.sql")));
   const TemplateURLID kTestId = 99;
   {
@@ -1622,6 +1615,27 @@
                                   false, 1);
   }
 }
+#else
+// On non-Windows the 136 to 137 migration does nothing except update add the
+// `url_hash` column and update the database version.
+TEST_F(WebDatabaseMigrationTest, MigrateVersion136ToCurrent) {
+  ASSERT_NO_FATAL_FAILURE(LoadDatabase(FILE_PATH_LITERAL("version_136.sql")));
+  {
+    sql::Database connection(sql::test::kTestTag);
+    ASSERT_TRUE(connection.Open(GetDatabasePath()));
+    EXPECT_EQ(136, VersionFromConnection(&connection));
+    EXPECT_FALSE(connection.DoesColumnExist("keywords", "url_hash"));
+  }
+  DoMigration();
+  {
+    sql::Database connection(sql::test::kTestTag);
+    ASSERT_TRUE(connection.Open(GetDatabasePath()));
+    EXPECT_EQ(WebDatabase::kCurrentVersionNumber,
+              VersionFromConnection(&connection));
+    EXPECT_TRUE(connection.DoesColumnExist("keywords", "url_hash"));
+  }
+}
+#endif  // BUILDFLAG(IS_WIN)
 
 TEST_F(WebDatabaseMigrationTest, MigrateVersion137ToCurrent) {
   ASSERT_NO_FATAL_FAILURE(LoadDatabase(FILE_PATH_LITERAL("version_137.sql")));
diff --git a/content/browser/accessibility/browser_accessibility_state_impl.cc b/content/browser/accessibility/browser_accessibility_state_impl.cc
index 6798112..23e48e1 100644
--- a/content/browser/accessibility/browser_accessibility_state_impl.cc
+++ b/content/browser/accessibility/browser_accessibility_state_impl.cc
@@ -24,6 +24,7 @@
 #include "content/browser/accessibility/render_accessibility_host.h"
 #include "content/browser/web_contents/web_contents_impl.h"
 #include "content/common/features.h"
+#include "content/public/browser/browser_context.h"
 #include "content/public/browser/browser_task_traits.h"
 #include "content/public/browser/scoped_accessibility_mode.h"
 #include "content/public/browser/web_contents_user_data.h"
diff --git a/content/browser/android/content_ui_event_handler.cc b/content/browser/android/content_ui_event_handler.cc
index 2c166f9..ddbdf2f 100644
--- a/content/browser/android/content_ui_event_handler.cc
+++ b/content/browser/android/content_ui_event_handler.cc
@@ -13,6 +13,7 @@
 #include "ui/events/android/gesture_event_android.h"
 #include "ui/events/android/gesture_event_type.h"
 #include "ui/events/android/key_event_android.h"
+#include "ui/events/android/motion_event_android_factory.h"
 #include "ui/events/android/motion_event_android_java.h"
 #include "ui/events/base_event_utils.h"
 #include "ui/events/event_utils.h"
@@ -111,14 +112,24 @@
   float pixels_per_tick =
       window ? window->mouse_wheel_scroll_factor()
              : ui::kDefaultMouseWheelTickMultiplier * view->GetDipScale();
-  ui::MotionEventAndroidJava event(
-      env, motion_event, 1.f / view->GetDipScale(), ticks_x, ticks_y,
-      pixels_per_tick, base::TimeTicks::FromJavaNanoTime(time_ns),
-      0 /*action=*/, 1 /*pointer_count=*/, 0 /*history_size=*/,
-      0 /*action_index=*/, 0, 0, 0, 0 /*raw_offset_x_pixels=*/,
-      0 /*raw_offset_y_pixels=*/, false /*for_touch_handle=*/, &pointer,
-      nullptr);
-  event_handler->OnMouseWheelEvent(event);
+  auto event = ui::MotionEventAndroidFactory::CreateFromJava(
+      env, motion_event,
+      /*pix_to_dip=*/1.f / view->GetDipScale(), ticks_x, ticks_y,
+      /*tick_multiplier=*/pixels_per_tick,
+      /*oldest_event_time=*/base::TimeTicks::FromJavaNanoTime(time_ns),
+      /*android_action=*/0,
+      /*pointer_count=*/1,
+      /*history_size=*/0,
+      /*action_index=*/0,
+      /*android_action_button=*/0,
+      /*android_gesture_classification=*/0,
+      /*android_button_state=*/0,
+      /*raw_offset_x_pixels=*/0,
+      /*raw_offset_y_pixels=*/0,
+      /*for_touch_handle=*/false,
+      /*pointer0=*/&pointer,
+      /*pointer1=*/nullptr);
+  event_handler->OnMouseWheelEvent(*event);
 }
 
 void ContentUiEventHandler::SendMouseEvent(
@@ -148,15 +159,24 @@
       /*touch_minor_pixels=*/0.0f, /*pressure=*/pressure,
       /*orientation_rad=*/orientation,
       /*tilt_rad=*/tilt, /*tool_type=*/android_tool_type);
-  ui::MotionEventAndroidJava event(
+  auto event = ui::MotionEventAndroidFactory::CreateFromJava(
       env, /*event=*/motion_event,
-      1.f / web_contents_->GetNativeView()->GetDipScale(), 0.f, 0.f, 0.f,
-      base::TimeTicks::FromJavaNanoTime(time_ns), android_action,
-      /*pointer_count=*/1, /*history_size=*/0, /*action_index=*/0,
-      android_action_button, /*android_gesture_classification=*/0,
-      android_button_state, /*raw_offset_x_pixels=*/0,
-      /*raw_offset_y_pixels=*/0, /*for_touch_handle=*/false, &pointer, nullptr);
-  event_handler->OnMouseEvent(event);
+      /*pix_to_dip=*/1.f / web_contents_->GetNativeView()->GetDipScale(),
+      /*ticks_x=*/0.f,
+      /*ticks_y=*/0.f,
+      /*tick_multiplier=*/0.f,
+      /*oldest_event_time=*/base::TimeTicks::FromJavaNanoTime(time_ns),
+      android_action,
+      /*pointer_count=*/1,
+      /*history_size=*/0,
+      /*action_index=*/0, android_action_button,
+      /*android_gesture_classification=*/0, android_button_state,
+      /*raw_offset_x_pixels=*/0,
+      /*raw_offset_y_pixels=*/0,
+      /*for_touch_handle=*/false,
+      /*pointer0=*/&pointer,
+      /*pointer1=*/nullptr);
+  event_handler->OnMouseEvent(*event);
 }
 
 void ContentUiEventHandler::SendScrollEvent(JNIEnv* env,
diff --git a/content/browser/attribution_reporting/attribution_input_event_tracker_android_unittest.cc b/content/browser/attribution_reporting/attribution_input_event_tracker_android_unittest.cc
index df6db045..b0ab4f1 100644
--- a/content/browser/attribution_reporting/attribution_input_event_tracker_android_unittest.cc
+++ b/content/browser/attribution_reporting/attribution_input_event_tracker_android_unittest.cc
@@ -18,6 +18,7 @@
 #include "content/public/test/browser_task_environment.h"
 #include "content/public/test/test_renderer_host.h"
 #include "testing/gtest/include/gtest/gtest.h"
+#include "ui/events/android/motion_event_android_factory.h"
 #include "ui/events/android/motion_event_android_java.h"
 #include "ui/events/motionevent_jni_headers/MotionEvent_jni.h"
 
@@ -31,9 +32,25 @@
     base::TimeTicks event_time = base::TimeTicks()) {
   JNIEnv* env = jni_zero::AttachCurrentThread();
   ui::MotionEventAndroid::Pointer pointer0(0, x, y, 0, 0, 0, 0, 0, 0);
-  return std::unique_ptr<ui::MotionEventAndroid>(new ui::MotionEventAndroidJava(
-      env, event, 1.f, 0, 0, 0, event_time, 0, 1, 0, 0, 0, 0, 0, 0, 0, false,
-      &pointer0, nullptr));
+  return ui::MotionEventAndroidFactory::CreateFromJava(
+      env, event,
+      /*pix_to_dip=*/1.f,
+      /*ticks_x=*/0,
+      /*ticks_y=*/0,
+      /*tick_multiplier=*/0,
+      /*oldest_event_time=*/event_time,
+      /*android_action=*/0,
+      /*pointer_count=*/1,
+      /*history_size=*/0,
+      /*action_index=*/0,
+      /*android_action_button=*/0,
+      /*android_gesture_classification=*/0,
+      /*android_button_state=*/0,
+      /*raw_offset_x_pixels=*/0,
+      /*raw_offset_y_pixels=*/0,
+      /*for_touch_handle=*/false,
+      /*pointer0=*/&pointer0,
+      /*pointer1=*/nullptr);
 }
 
 }  // namespace
diff --git a/content/browser/attribution_reporting/attribution_src_browsertest.cc b/content/browser/attribution_reporting/attribution_src_browsertest.cc
index 6c5bbe2..68c891ef 100644
--- a/content/browser/attribution_reporting/attribution_src_browsertest.cc
+++ b/content/browser/attribution_reporting/attribution_src_browsertest.cc
@@ -42,6 +42,7 @@
 #include "content/browser/renderer_host/render_frame_host_impl.h"
 #include "content/browser/storage_partition_impl.h"
 #include "content/browser/web_contents/web_contents_impl.h"
+#include "content/public/browser/browser_context.h"
 #include "content/public/browser/navigation_handle.h"
 #include "content/public/browser/navigation_throttle.h"
 #include "content/public/common/content_features.h"
diff --git a/content/browser/attribution_reporting/trigger_registration_browsertest.cc b/content/browser/attribution_reporting/trigger_registration_browsertest.cc
index 947cd4de..84a4afb9 100644
--- a/content/browser/attribution_reporting/trigger_registration_browsertest.cc
+++ b/content/browser/attribution_reporting/trigger_registration_browsertest.cc
@@ -17,6 +17,7 @@
 #include "content/browser/attribution_reporting/attribution_test_utils.h"
 #include "content/browser/attribution_reporting/test/mock_attribution_manager.h"
 #include "content/browser/web_contents/web_contents_impl.h"
+#include "content/public/browser/browser_context.h"
 #include "content/public/common/content_switches.h"
 #include "content/public/test/browser_test.h"
 #include "content/public/test/browser_test_utils.h"
diff --git a/content/browser/back_forward_cache_no_store_browsertest.cc b/content/browser/back_forward_cache_no_store_browsertest.cc
index 480baf6..195cbce 100644
--- a/content/browser/back_forward_cache_no_store_browsertest.cc
+++ b/content/browser/back_forward_cache_no_store_browsertest.cc
@@ -13,6 +13,7 @@
 #include "content/browser/renderer_host/navigation_request.h"
 #include "content/browser/renderer_host/render_frame_host_impl.h"
 #include "content/browser/web_contents/web_contents_impl.h"
+#include "content/public/browser/browser_context.h"
 #include "content/public/browser/content_browser_client.h"
 #include "content/public/browser/web_contents.h"
 #include "content/public/common/content_switches.h"
diff --git a/content/browser/blob_storage/blob_url_browsertest.cc b/content/browser/blob_storage/blob_url_browsertest.cc
index 793ad7da..c36e80d 100644
--- a/content/browser/blob_storage/blob_url_browsertest.cc
+++ b/content/browser/blob_storage/blob_url_browsertest.cc
@@ -14,6 +14,7 @@
 #include "content/browser/permissions/permission_controller_impl.h"
 #include "content/browser/renderer_host/render_frame_host_impl.h"
 #include "content/browser/web_contents/web_contents_impl.h"
+#include "content/public/browser/browser_context.h"
 #include "content/public/common/content_switches.h"
 #include "content/public/common/url_constants.h"
 #include "content/public/test/browser_test.h"
diff --git a/content/browser/devtools/protocol/fedcm_handler.cc b/content/browser/devtools/protocol/fedcm_handler.cc
index 266537c..26475c1 100644
--- a/content/browser/devtools/protocol/fedcm_handler.cc
+++ b/content/browser/devtools/protocol/fedcm_handler.cc
@@ -11,6 +11,7 @@
 #include "content/browser/renderer_host/render_frame_host_impl.h"
 #include "content/browser/webid/federated_auth_request_impl.h"
 #include "content/browser/webid/federated_auth_request_page_data.h"
+#include "content/public/browser/browser_context.h"
 #include "content/public/browser/webid/federated_identity_api_permission_context_delegate.h"
 #include "content/public/browser/webid/identity_request_dialog_controller.h"
 
diff --git a/content/browser/loader/keep_alive_attribution_request_helper_unittest.cc b/content/browser/loader/keep_alive_attribution_request_helper_unittest.cc
index bd22661..deb1c54 100644
--- a/content/browser/loader/keep_alive_attribution_request_helper_unittest.cc
+++ b/content/browser/loader/keep_alive_attribution_request_helper_unittest.cc
@@ -30,6 +30,7 @@
 #include "content/browser/attribution_reporting/attribution_test_utils.h"
 #include "content/browser/attribution_reporting/test/mock_attribution_manager.h"
 #include "content/browser/storage_partition_impl.h"
+#include "content/public/browser/browser_context.h"
 #include "content/public/browser/content_browser_client.h"
 #include "content/public/test/test_renderer_host.h"
 #include "content/test/test_web_contents.h"
diff --git a/content/browser/loader/keep_alive_request_browsertest_util.cc b/content/browser/loader/keep_alive_request_browsertest_util.cc
index e0f1c467..88ccd44 100644
--- a/content/browser/loader/keep_alive_request_browsertest_util.cc
+++ b/content/browser/loader/keep_alive_request_browsertest_util.cc
@@ -6,6 +6,7 @@
 #include "base/strings/stringprintf.h"
 #include "content/browser/renderer_host/render_frame_host_impl.h"
 #include "content/browser/web_contents/web_contents_impl.h"
+#include "content/public/browser/browser_context.h"
 #include "content/public/test/back_forward_cache_util.h"
 #include "content/public/test/browser_test_utils.h"
 #include "content/shell/browser/shell.h"
diff --git a/content/browser/loader/keep_alive_url_loader_service_unittest.cc b/content/browser/loader/keep_alive_url_loader_service_unittest.cc
index 47e50568..de6149d 100644
--- a/content/browser/loader/keep_alive_url_loader_service_unittest.cc
+++ b/content/browser/loader/keep_alive_url_loader_service_unittest.cc
@@ -19,6 +19,7 @@
 #include "content/browser/attribution_reporting/test/mock_attribution_data_host_manager.h"
 #include "content/browser/attribution_reporting/test/mock_attribution_manager.h"
 #include "content/browser/storage_partition_impl.h"
+#include "content/public/browser/browser_context.h"
 #include "content/public/browser/navigation_entry.h"
 #include "content/public/test/test_utils.h"
 #include "content/test/navigation_simulator_impl.h"
diff --git a/content/browser/media/captured_surface_controller_permission_manager_unittest.cc b/content/browser/media/captured_surface_controller_permission_manager_unittest.cc
index 0c4af89..adb85b3 100644
--- a/content/browser/media/captured_surface_controller_permission_manager_unittest.cc
+++ b/content/browser/media/captured_surface_controller_permission_manager_unittest.cc
@@ -8,6 +8,7 @@
 
 #include "base/functional/bind.h"
 #include "base/run_loop.h"
+#include "content/public/browser/browser_context.h"
 #include "content/public/browser/global_routing_id.h"
 #include "content/public/browser/permission_request_description.h"
 #include "content/public/browser/render_frame_host.h"
diff --git a/content/browser/preloading/prefetch/prefetch_container.cc b/content/browser/preloading/prefetch/prefetch_container.cc
index 14bb7e4..e00298d 100644
--- a/content/browser/preloading/prefetch/prefetch_container.cc
+++ b/content/browser/preloading/prefetch/prefetch_container.cc
@@ -290,9 +290,6 @@
 
   const bool is_isolated_network_context_required_;
 
-  // Whether this |url_| is eligible to be prefetched
-  std::optional<PreloadingEligibility> eligibility_;
-
   // This tracks whether the cookies associated with |url_| have changed at
   // some point after the initial eligibility check.
   std::unique_ptr<PrefetchCookieListener> cookie_listener_;
@@ -777,8 +774,17 @@
   // only be called once prefetching has actually started, and not for
   // ineligible or eligibled but not started triggers (e.g., holdback triggers,
   // triggers waiting on a queue).
-  if (GetLoadState() == LoadState::kStarted) {
-    SetTriggeringOutcomeAndFailureReasonFromStatus(prefetch_status);
+  switch (GetLoadState()) {
+    case LoadState::kStarted:
+    case LoadState::kDeterminedHead:
+    case LoadState::kCompletedOrFailed:
+      SetTriggeringOutcomeAndFailureReasonFromStatus(prefetch_status);
+      break;
+    case LoadState::kNotStarted:
+    case LoadState::kEligible:
+    case LoadState::kFailedIneligible:
+    case LoadState::kFailedHeldback:
+      break;
   }
   SetPrefetchStatusWithoutUpdatingTriggeringOutcome(prefetch_status);
 }
@@ -860,6 +866,14 @@
     case LoadState::kFailedHeldback:
       CHECK_EQ(load_state_, LoadState::kEligible);
       break;
+
+    case LoadState::kDeterminedHead:
+      CHECK_EQ(load_state_, LoadState::kStarted);
+      break;
+
+    case LoadState::kCompletedOrFailed:
+      CHECK_EQ(load_state_, LoadState::kDeterminedHead);
+      break;
   }
   DVLOG(1) << (*this) << " LoadState " << load_state_ << " -> "
            << new_load_state;
@@ -876,8 +890,6 @@
 
 void PrefetchContainer::OnEligibilityCheckComplete(
     PreloadingEligibility eligibility) {
-  SinglePrefetch& this_prefetch = GetCurrentSinglePrefetchToPrefetch();
-  this_prefetch.eligibility_ = eligibility;
   preload_pipeline_info_->SetPrefetchEligibility(eligibility);
   for (auto& preload_pipeline_info : inherited_preload_pipeline_infos_) {
     preload_pipeline_info->SetPrefetchEligibility(eligibility);
@@ -927,13 +939,6 @@
   }
 }
 
-bool PrefetchContainer::IsInitialPrefetchEligible() const {
-  DCHECK(redirect_chain_.size() > 0);
-  return redirect_chain_[0]->eligibility_ &&
-         redirect_chain_[0]->eligibility_.value() ==
-             PreloadingEligibility::kEligible;
-}
-
 void PrefetchContainer::AddRedirectHop(const net::RedirectInfo& redirect_info) {
   CHECK(resource_request_);
 
@@ -1277,6 +1282,8 @@
 }
 
 void PrefetchContainer::OnDeterminedHead() {
+  SetLoadState(LoadState::kDeterminedHead);
+
   if (GetNonRedirectHead()) {
     time_header_determined_successfully_ = base::TimeTicks::Now();
   }
@@ -1400,6 +1407,7 @@
 
 void PrefetchContainer::OnPrefetchComplete(
     const network::URLLoaderCompletionStatus& completion_status) {
+  SetLoadState(LoadState::kCompletedOrFailed);
   OnPrefetchCompleteInternal(completion_status);
 
   std::optional<int> response_code = std::nullopt;
@@ -1455,6 +1463,8 @@
         return ServableState::kShouldBlockUntilEligibilityGot;
       case LoadState::kFailedIneligible:
       case LoadState::kStarted:
+      case LoadState::kDeterminedHead:
+      case LoadState::kCompletedOrFailed:
       case LoadState::kFailedHeldback:
         // nop
         break;
@@ -1880,6 +1890,10 @@
       return ostream << "FailedIneligible";
     case PrefetchContainer::LoadState::kStarted:
       return ostream << "Started";
+    case PrefetchContainer::LoadState::kDeterminedHead:
+      return ostream << "DeterminedHead";
+    case PrefetchContainer::LoadState::kCompletedOrFailed:
+      return ostream << "CompletedOrFailed";
     case PrefetchContainer::LoadState::kFailedHeldback:
       return ostream << "FailedHeldback";
   }
diff --git a/content/browser/preloading/prefetch/prefetch_container.h b/content/browser/preloading/prefetch/prefetch_container.h
index 921cb9e..bf6a69f3 100644
--- a/content/browser/preloading/prefetch/prefetch_container.h
+++ b/content/browser/preloading/prefetch/prefetch_container.h
@@ -373,11 +373,31 @@
     // --- Phase 3. PrefetchService::StartSinglePrefetch() has been called and
     // the holdback check has completed.
 
-    // [Final state] Not heldback.
+    // Not heldback:
     //
-    // On this state, refer to `PrefetchResponseReader`s for detailed
+    // On these states, refer to `PrefetchResponseReader`s for detailed
     // prefetching state and servability.
     //
+    // - `kStarted`: Prefetch is started.
+    // - `kDeterminedHead`: `PrefetchContainer::OnDeterminedHead()` is called.
+    //   `Observer::OnDeterminedHead()` is called after transitioning to this
+    //   state.
+    // - [Final state] `kCompletedOrFailed`:
+    //   `PrefetchContainer::OnPrefetchComplete()` is called.
+    //   `Observer::OnPrefetchCompletedOrFailed()` is called after transitioning
+    //   to this state.
+    //
+    // Currently the distinction between these three states is introduced for
+    // CHECK()ing the calling order of `OnDeterminedHead()` and
+    // `OnPrefetchComplete()` (for https://crbug.com/400761083) and shouldn't be
+    // used for
+    // other purposes (i.e. these three enum values should behave in the same
+    // way).
+    //
+    // TODO(https://crbug.com/432518638): Make more strict association with
+    // `PrefetchContainer::LoadState` and `PrefetchResponseReader::LoadState`
+    // and verify it by adding CHECK()s.
+    //
     // Also, refer to `attempt_` for triggering outcome and failure reasons for
     // metrics.
     // `PreloadingAttempt::SetFailureReason()` can be only called on this state.
@@ -386,6 +406,8 @@
     // (e.g. `PrefetchResponseReader::GetServableState()` can be still
     // `kServable` even if `attempt_` has a failure).
     kStarted,
+    kDeterminedHead,
+    kCompletedOrFailed,
 
     // [Final state] Heldback due to `PreloadingAttempt::ShouldHoldback()`.
     kFailedHeldback,
@@ -404,7 +426,6 @@
 
   // Whether or not the prefetch was determined to be eligibile.
   void OnEligibilityCheckComplete(PreloadingEligibility eligibility);
-  bool IsInitialPrefetchEligible() const;
 
   // Adds a the new URL to |redirect_chain_|.
   void AddRedirectHop(const net::RedirectInfo& redirect_info);
diff --git a/content/browser/preloading/prefetch/prefetch_container_unittest.cc b/content/browser/preloading/prefetch/prefetch_container_unittest.cc
index 45bd658..249f7036 100644
--- a/content/browser/preloading/prefetch/prefetch_container_unittest.cc
+++ b/content/browser/preloading/prefetch/prefetch_container_unittest.cc
@@ -1208,11 +1208,12 @@
       CreateSpeculationRulesPrefetchContainer(GURL("https://test.com"));
   prefetch_container->MakeResourceRequest({});
 
-  prefetch_container->SetPrefetchStatus(
-      PrefetchStatus::kPrefetchNotFinishedInTime);
+  prefetch_container->SimulatePrefetchEligibleForTest();
+  prefetch_container->SimulatePrefetchStartedForTest();
 
   AddRedirectHop(prefetch_container.get(), GURL("https://redirect1.com"));
   AddRedirectHop(prefetch_container.get(), GURL("https://redirect2.com"));
+  prefetch_container->OnDeterminedHead();
   prefetch_container->OnPrefetchComplete(network::URLLoaderCompletionStatus());
 
   histogram_tester.ExpectUniqueSample(
diff --git a/content/browser/preloading/prefetch/prefetch_handle_impl.cc b/content/browser/preloading/prefetch/prefetch_handle_impl.cc
index 1583aa69..6142277 100644
--- a/content/browser/preloading/prefetch/prefetch_handle_impl.cc
+++ b/content/browser/preloading/prefetch/prefetch_handle_impl.cc
@@ -103,6 +103,8 @@
         case PrefetchContainer::LoadState::kFailedHeldback:
           break;
         case PrefetchContainer::LoadState::kStarted:
+        case PrefetchContainer::LoadState::kDeterminedHead:
+        case PrefetchContainer::LoadState::kCompletedOrFailed:
           prefetch_container_->SetPrefetchStatus(
               *prefetch_status_on_release_started_prefetch_);
           break;
diff --git a/content/browser/preloading/prefetch/prefetch_response_reader.h b/content/browser/preloading/prefetch/prefetch_response_reader.h
index bd189c1f..ebfd146c 100644
--- a/content/browser/preloading/prefetch/prefetch_response_reader.h
+++ b/content/browser/preloading/prefetch/prefetch_response_reader.h
@@ -120,6 +120,46 @@
       base::PassKey<PrefetchContainer>,
       ukm::builders::PrefetchProxy_PrefetchedResource& builder) const;
 
+  // Valid state transitions (which imply valid event sequences) are:
+  // - Redirect: `kStarted` -> `kRedirectHandled`
+  // - Non-redirect: `kStarted` -> `kResponseReceived` -> `kCompleted`
+  // - Failure: `kStarted` -> `kFailed`
+  //            `kStarted` -> `kFailedRedirect`
+  //            `kStarted` -> `kFailedResponseReceived` -> `kFailed`
+  //            `kStarted` -> `kResponseReceived` -> `kFailed`
+  // Optional `OnReceiveEarlyHints()` and `OnTransferSizeUpdated()` events can
+  // be received in any non-final states.
+  enum class LoadState {
+    // Initial state, not yet receiving a redirect nor non-redirect response.
+    kStarted,
+
+    // [Final] A redirect response is received (`HandleRedirect()` is called).
+    // This is a final state because we always switch to a new
+    // `PrefetchResponseReader` on redirects.
+    kRedirectHandled,
+
+    // [servable] A non-redirect successful response is received
+    // (`OnReceiveResponse()` is called with `servable` = true).
+    kResponseReceived,
+
+    // A non-redirect failed response is received (`OnReceiveResponse()` is
+    // called with `servable` = false).
+    kFailedResponseReceived,
+
+    // [Final, servable] Successful completion (`OnComplete(net::OK)` is called
+    // after `kResponseReceived`.
+    kCompleted,
+
+    // [Final] Failed completion (`OnComplete()` is called, either with
+    // non-`net::OK`, or after `kFailedResponseReceived`).
+    kFailed,
+
+    // [Final] Failed redirects.
+    kFailedRedirect
+  };
+
+  LoadState load_state() const { return load_state_; }
+
   base::WeakPtr<PrefetchResponseReader> GetWeakPtr() {
     return weak_ptr_factory_.GetWeakPtr();
   }
@@ -192,50 +232,11 @@
   };
   EventQueueStatus event_queue_status_{EventQueueStatus::kNotRunning};
 
-  // Valid state transitions (which imply valid event sequences) are:
-  // - Redirect: `kStarted` -> `kRedirectHandled`
-  // - Non-redirect: `kStarted` -> `kResponseReceived` -> `kCompleted`
-  // - Failure: `kStarted` -> `kFailed`
-  //            `kStarted` -> `kFailedRedirect`
-  //            `kStarted` -> `kFailedResponseReceived` -> `kFailed`
-  //            `kStarted` -> `kResponseReceived` -> `kFailed`
-  // Optional `OnReceiveEarlyHints()` and `OnTransferSizeUpdated()` events can
-  // be received in any non-final states.
-  enum class LoadState {
-    // Initial state, not yet receiving a redirect nor non-redirect response.
-    kStarted,
-
-    // [Final] A redirect response is received (`HandleRedirect()` is called).
-    // This is a final state because we always switch to a new
-    // `PrefetchResponseReader` on redirects.
-    kRedirectHandled,
-
-    // [servable] A non-redirect successful response is received
-    // (`OnReceiveResponse()` is called with `servable` = true).
-    kResponseReceived,
-
-    // A non-redirect failed response is received (`OnReceiveResponse()` is
-    // called with `servable` = false).
-    kFailedResponseReceived,
-
-    // [Final, servable] Successful completion (`OnComplete(net::OK)` is called
-    // after `kResponseReceived`.
-    kCompleted,
-
-    // [Final] Failed completion (`OnComplete()` is called, either with
-    // non-`net::OK`, or after `kFailedResponseReceived`).
-    kFailed,
-
-    // [Final] Failed redirects.
-    kFailedRedirect
-  };
-
   // Always access/update through `load_state()` and
   // `SetLoadStateAndAddEventToQueue()` below, to avoid unintentional state
   // changes and missing related callbacks on state changes.
   LoadState load_state_{LoadState::kStarted};
 
-  LoadState load_state() const { return load_state_; }
   void SetLoadStateAndAddEventToQueue(LoadState new_load_state,
                                       EventCallback callback);
 
diff --git a/content/browser/preloading/prefetch/prefetch_service.cc b/content/browser/preloading/prefetch/prefetch_service.cc
index a5308d4..e3e2a597 100644
--- a/content/browser/preloading/prefetch/prefetch_service.cc
+++ b/content/browser/preloading/prefetch/prefetch_service.cc
@@ -607,10 +607,16 @@
   }
 
   // `PrefetchContainer::LoadState` check.
-  PrefetchContainer::LoadState load_state = prefetch_container->GetLoadState();
-  if (load_state == PrefetchContainer::LoadState::kFailedIneligible ||
-      load_state == PrefetchContainer::LoadState::kFailedHeldback) {
-    return true;
+  switch (prefetch_container->GetLoadState()) {
+    case PrefetchContainer::LoadState::kFailedIneligible:
+    case PrefetchContainer::LoadState::kFailedHeldback:
+      return true;
+    case PrefetchContainer::LoadState::kNotStarted:
+    case PrefetchContainer::LoadState::kEligible:
+    case PrefetchContainer::LoadState::kStarted:
+    case PrefetchContainer::LoadState::kDeterminedHead:
+    case PrefetchContainer::LoadState::kCompletedOrFailed:
+      break;
   }
 
   // `PrefetchContainer::ServableState` check.
@@ -622,6 +628,39 @@
   return false;
 }
 
+// Parameter class used during eligibility check and `OnGotEligibility*` methods
+// (`callback`).
+struct PrefetchService::CheckEligibilityParams final {
+  void Finish(PreloadingEligibility eligibility) && {
+    // `callback_local` is needed to avoid use-after-move.
+    auto callback_local = std::move(callback);
+    std::move(callback_local).Run(std::move(*this), eligibility);
+  }
+
+  // Returns if proxy is required for the next request.
+  bool IsProxyRequired() const {
+    CHECK(prefetch_container);
+    return prefetch_container->IsProxyRequiredForURL(url) &&
+           !ShouldPrefetchBypassProxyForTestHost(url.host());
+  }
+
+  base::WeakPtr<PrefetchContainer> prefetch_container;
+
+  // The URL of the next request.
+  GURL url;
+
+  // Whether this is eligibility check for a redirect, or for an initial
+  // request.
+  bool is_redirect;
+
+  // TODO(crbug.com/432783906): Add a `CHECK()` to ensure `callback` is always
+  // called. However, there are some cases where `callback` is not called (e.g.
+  // when `PrefetchService` is destroyed during eligibility check, which a valid
+  // exception, as well as other suspicious cases).
+  base::OnceCallback<void(CheckEligibilityParams, PreloadingEligibility)>
+      callback;
+};
+
 std::unique_ptr<PrefetchHandle> PrefetchService::AddPrefetchContainerWithHandle(
     std::unique_ptr<PrefetchContainer> owned_prefetch_container) {
   base::WeakPtr<PrefetchContainer> prefetch_container =
@@ -647,25 +686,27 @@
   TRACE_EVENT1("loading", "PrefetchService::PrefetchUrl", "prefetch_url",
                prefetch_container->GetURL());
 
+  auto params = CheckEligibilityParams(
+      {.prefetch_container = prefetch_container,
+       .url = prefetch_container->GetURL(),
+       .is_redirect = false,
+       .callback =
+           base::BindOnce(&PrefetchService::OnGotEligibilityForNonRedirect,
+                          weak_method_factory_.GetWeakPtr())});
+
   if (delegate_) {
     // If pre* actions are disabled then don't prefetch.
     switch (delegate_->IsSomePreloadingEnabled()) {
       case PreloadingEligibility::kEligible:
         break;
       case PreloadingEligibility::kDataSaverEnabled:
-        OnGotEligibilityForNonRedirect(
-            std::move(prefetch_container),
-            PreloadingEligibility::kDataSaverEnabled);
+        std::move(params).Finish(PreloadingEligibility::kDataSaverEnabled);
         return;
       case PreloadingEligibility::kBatterySaverEnabled:
-        OnGotEligibilityForNonRedirect(
-            std::move(prefetch_container),
-            PreloadingEligibility::kBatterySaverEnabled);
+        std::move(params).Finish(PreloadingEligibility::kBatterySaverEnabled);
         return;
       case PreloadingEligibility::kPreloadingDisabled:
-        OnGotEligibilityForNonRedirect(
-            std::move(prefetch_container),
-            PreloadingEligibility::kPreloadingDisabled);
+        std::move(params).Finish(PreloadingEligibility::kPreloadingDisabled);
         return;
       default:
         DVLOG(1) << *prefetch_container
@@ -701,39 +742,28 @@
     }
   }
 
-  // Note that this is initial URL of the prefetch. So, this is immutable over
-  // `PrefetchContainer`'s lifetime. And it is alive until the call of
-  // `CheckHasServiceWorker()`.
-  const GURL& url = prefetch_container->GetURL();
-
   if (GetDelayEligibilityCheckForTesting()) {
     GetDelayEligibilityCheckForTesting().Run(  // IN-TEST
         base::BindOnce(&PrefetchService::CheckEligibilityOfPrefetch,
-                       weak_method_factory_.GetWeakPtr(),
-                       std::move(prefetch_container), url,
-                       /*redirect_data=*/std::nullopt));
+                       weak_method_factory_.GetWeakPtr(), std::move(params)));
     return;
   }
 
-  CheckEligibilityOfPrefetch(std::move(prefetch_container), url,
-                             /*redirect_data=*/std::nullopt);
+  CheckEligibilityOfPrefetch(std::move(params));
 }
 
 void PrefetchService::CheckEligibilityOfPrefetch(
-    base::WeakPtr<PrefetchContainer> prefetch_container,
-    const GURL& url,
-    std::optional<
-        std::pair<net::RedirectInfo, network::mojom::URLResponseHeadPtr>>
-        redirect_data) {
+    CheckEligibilityParams params) {
+  const auto prefetch_container = params.prefetch_container;
+
   CHECK(prefetch_container);
   TRACE_EVENT_NESTABLE_ASYNC_BEGIN0("loading",
                                     "PrefetchService::CheckEligibility", this);
 
   // Inject failure in tests.
   if (GetForceIneligibilityForTesting().has_value()) {
-    OnGotEligibility(std::move(prefetch_container), std::move(redirect_data),
-                     GetForceIneligibilityForTesting().value()  // IN-TEST
-    );
+    std::move(params).Finish(
+        GetForceIneligibilityForTesting().value());  // IN-TEST
     return;
   }
 
@@ -750,15 +780,13 @@
   // matters. Also, we bypass the check for the test hosts, since we run the
   // test web servers on the localhost or private networks, where the check
   // fails.
-  if (prefetch_container->IsProxyRequiredForURL(url) &&
-      !ShouldPrefetchBypassProxyForTestHost(url.host())) {
+  if (params.IsProxyRequired()) {
     bool is_host_non_unique =
         g_host_non_unique_filter
-            ? g_host_non_unique_filter(url.HostNoBrackets())
-            : net::IsHostnameNonUnique(url.HostNoBrackets());
+            ? g_host_non_unique_filter(params.url.HostNoBrackets())
+            : net::IsHostnameNonUnique(params.url.HostNoBrackets());
     if (is_host_non_unique) {
-      OnGotEligibility(std::move(prefetch_container), std::move(redirect_data),
-                       PreloadingEligibility::kHostIsNonUnique);
+      std::move(params).Finish(PreloadingEligibility::kHostIsNonUnique);
       return;
     }
   }
@@ -767,25 +795,23 @@
   // For proxied prefetches, we only want HTTPS URLs.
   // For non-proxied prefetches, other URLs (notably localhost HTTP) is also
   // acceptable. This is common during development.
-  const bool is_secure_http = prefetch_container->IsProxyRequiredForURL(url)
-                                  ? url.SchemeIs(url::kHttpsScheme)
-                                  : (url.SchemeIsHTTPOrHTTPS() &&
-                                     network::IsUrlPotentiallyTrustworthy(url));
+  const bool is_secure_http =
+      params.IsProxyRequired()
+          ? params.url.SchemeIs(url::kHttpsScheme)
+          : (params.url.SchemeIsHTTPOrHTTPS() &&
+             network::IsUrlPotentiallyTrustworthy(params.url));
   if (!is_secure_http) {
-    OnGotEligibility(std::move(prefetch_container), std::move(redirect_data),
-                     PreloadingEligibility::kSchemeIsNotHttps);
+    std::move(params).Finish(PreloadingEligibility::kSchemeIsNotHttps);
     return;
   }
 
   // Fail the prefetch (or more precisely, PrefetchContainer::SinglePrefetch)
   // early if it is going to go through a proxy, and we know that it is not
   // available.
-  if (prefetch_container->IsProxyRequiredForURL(url) &&
-      !ShouldPrefetchBypassProxyForTestHost(url.host()) &&
+  if (params.IsProxyRequired() &&
       (!prefetch_proxy_configurator_ ||
        !prefetch_proxy_configurator_->IsPrefetchProxyAvailable())) {
-    OnGotEligibility(std::move(prefetch_container), std::move(redirect_data),
-                     PreloadingEligibility::kPrefetchProxyNotAvailable);
+    std::move(params).Finish(PreloadingEligibility::kPrefetchProxyNotAvailable);
     return;
   }
 
@@ -794,36 +820,30 @@
   StoragePartition* default_storage_partition =
       browser_context_->GetDefaultStoragePartition();
   if (default_storage_partition !=
-      browser_context_->GetStoragePartitionForUrl(url,
+      browser_context_->GetStoragePartitionForUrl(params.url,
                                                   /*can_create=*/false)) {
-    OnGotEligibility(std::move(prefetch_container), std::move(redirect_data),
-                     PreloadingEligibility::kNonDefaultStoragePartition);
+    std::move(params).Finish(
+        PreloadingEligibility::kNonDefaultStoragePartition);
     return;
   }
 
   // If we have recently received a "retry-after" for the origin, then don't
   // send new prefetches.
-  if (delegate_ && !delegate_->IsOriginOutsideRetryAfterWindow(url)) {
-    OnGotEligibility(std::move(prefetch_container), std::move(redirect_data),
-                     PreloadingEligibility::kRetryAfter);
+  if (delegate_ && !delegate_->IsOriginOutsideRetryAfterWindow(params.url)) {
+    std::move(params).Finish(PreloadingEligibility::kRetryAfter);
     return;
   }
 
-  CheckHasServiceWorker(std::move(prefetch_container), url,
-                        std::move(redirect_data));
+  CheckHasServiceWorker(std::move(params));
 }
 
-void PrefetchService::CheckHasServiceWorker(
-    base::WeakPtr<PrefetchContainer> prefetch_container,
-    const GURL& url,
-    std::optional<
-        std::pair<net::RedirectInfo, network::mojom::URLResponseHeadPtr>>
-        redirect_data) {
+void PrefetchService::CheckHasServiceWorker(CheckEligibilityParams params) {
+  const auto prefetch_container = params.prefetch_container;
   CHECK(prefetch_container);
   TRACE_EVENT_NESTABLE_ASYNC_BEGIN0(
       "loading", "PrefetchService::CheckHasServiceWorker", this);
 
-  if (redirect_data) {
+  if (params.is_redirect) {
     switch (prefetch_container->service_worker_state()) {
       case PrefetchServiceWorkerState::kDisallowed:
         break;
@@ -835,9 +855,8 @@
       case PrefetchServiceWorkerState::kControlled:
         // Currently we disallow redirects from ServiceWorker-controlled
         // prefetches.
-        OnGotEligibility(std::move(prefetch_container),
-                         std::move(redirect_data),
-                         PreloadingEligibility::kRedirectFromServiceWorker);
+        std::move(params).Finish(
+            PreloadingEligibility::kRedirectFromServiceWorker);
         return;
     }
   } else {
@@ -849,8 +868,7 @@
         // The controlling ServiceWorker will be checked by
         // `ServiceWorkerMainResourceLoaderInterceptor` from
         // `PrefetchStreamingURLLoader`, not here during eligibility check.
-        OnGotServiceWorkerResult(std::move(prefetch_container), url,
-                                 std::move(redirect_data), base::Time::Now(),
+        OnGotServiceWorkerResult(std::move(params), base::Time::Now(),
                                  ServiceWorkerCapability::NO_SERVICE_WORKER);
         return;
 
@@ -869,7 +887,8 @@
           : browser_context_->GetDefaultStoragePartition()
                 ->GetServiceWorkerContext();
   CHECK(service_worker_context);
-  auto key = blink::StorageKey::CreateFirstParty(url::Origin::Create(url));
+  auto key =
+      blink::StorageKey::CreateFirstParty(url::Origin::Create(params.url));
   // Check `MaybeHasRegistrationForStorageKey` first as it is much faster than
   // calling `CheckHasServiceWorker`.
   auto has_registration_for_storage_key =
@@ -884,36 +903,34 @@
             : PreloadingAttemptImpl::ServiceWorkerRegisteredCheck::kOriginOnly);
   }
   if (!has_registration_for_storage_key) {
-    OnGotServiceWorkerResult(std::move(prefetch_container), url,
-                             std::move(redirect_data), base::Time::Now(),
+    OnGotServiceWorkerResult(std::move(params), base::Time::Now(),
                              ServiceWorkerCapability::NO_SERVICE_WORKER);
     return;
   }
   // Start recording here the start of the check for Service Worker registration
   // for url.
+  // `url` is needed to avoid use-after-move.
+  const GURL url = params.url;
   service_worker_context->CheckHasServiceWorker(
       url, key,
       base::BindOnce(&PrefetchService::OnGotServiceWorkerResult,
-                     weak_method_factory_.GetWeakPtr(),
-                     std::move(prefetch_container), url,
-                     std::move(redirect_data), base::Time::Now()));
+                     weak_method_factory_.GetWeakPtr(), std::move(params),
+                     base::Time::Now()));
 }
 
 void PrefetchService::OnGotServiceWorkerResult(
-    base::WeakPtr<PrefetchContainer> prefetch_container,
-    const GURL& url,
-    std::optional<std::pair<net::RedirectInfo,
-                            network::mojom::URLResponseHeadPtr>> redirect_data,
+    CheckEligibilityParams params,
     base::Time check_has_service_worker_start_time,
     ServiceWorkerCapability service_worker_capability) {
+  const auto prefetch_container = params.prefetch_container;
+
   TRACE_EVENT_NESTABLE_ASYNC_END0(
       "loading", "PrefetchService::CheckHasServiceWorker", this);
   TRACE_EVENT1("loading", "PrefetchService::OnGotServiceWorkerResult",
                "prefetch_url",
                prefetch_container ? prefetch_container->GetURL().spec() : "");
   if (!prefetch_container) {
-    OnGotEligibility(std::move(prefetch_container), std::move(redirect_data),
-                     PreloadingEligibility::kEligible);
+    std::move(params).Finish(PreloadingEligibility::kEligible);
     return;
   }
   CHECK(prefetch_container);
@@ -938,18 +955,15 @@
     case ServiceWorkerCapability::SERVICE_WORKER_NO_FETCH_HANDLER:
       if (base::FeatureList::IsEnabled(
               features::kPrefetchServiceWorkerNoFetchHandlerFix)) {
-        OnGotEligibility(
-            std::move(prefetch_container), std::move(redirect_data),
+        std::move(params).Finish(
             PreloadingEligibility::kUserHasServiceWorkerNoFetchHandler);
         return;
       }
       break;
     case ServiceWorkerCapability::SERVICE_WORKER_WITH_FETCH_HANDLER: {
-      auto eligibility = redirect_data
-                             ? PreloadingEligibility::kRedirectToServiceWorker
-                             : PreloadingEligibility::kUserHasServiceWorker;
-      OnGotEligibility(std::move(prefetch_container), std::move(redirect_data),
-                       eligibility);
+      std::move(params).Finish(
+          params.is_redirect ? PreloadingEligibility::kRedirectToServiceWorker
+                             : PreloadingEligibility::kUserHasServiceWorker);
       return;
     }
   }
@@ -959,11 +973,10 @@
   // context.
   // TODO(crbug.com/40265797): Allow same-site cross-origin prefetches
   // that require the prefetch proxy to be made.
-  if (prefetch_container->IsProxyRequiredForURL(url) &&
+  if (params.IsProxyRequired() &&
       !prefetch_container
            ->IsIsolatedNetworkContextRequiredForCurrentPrefetch()) {
-    OnGotEligibility(
-        std::move(prefetch_container), std::move(redirect_data),
+    std::move(params).Finish(
         PreloadingEligibility::kSameSiteCrossOriginPrefetchRequiredProxy);
     return;
   }
@@ -971,8 +984,7 @@
   // isolated network context.
   if (!prefetch_container
            ->IsIsolatedNetworkContextRequiredForCurrentPrefetch()) {
-    OnGotEligibility(std::move(prefetch_container), std::move(redirect_data),
-                     PreloadingEligibility::kEligible);
+    std::move(params).Finish(PreloadingEligibility::kEligible);
     return;
   }
 
@@ -983,35 +995,32 @@
                                     this);
   net::CookieOptions options = net::CookieOptions::MakeAllInclusive();
   options.set_return_excluded_cookies();
+  // `url` is needed to avoid use-after-move.
+  const GURL url = params.url;
   default_storage_partition->GetCookieManagerForBrowserProcess()->GetCookieList(
       url, options, net::CookiePartitionKeyCollection::Todo(),
       base::BindOnce(&PrefetchService::OnGotCookiesForEligibilityCheck,
-                     weak_method_factory_.GetWeakPtr(),
-                     std::move(prefetch_container), url,
-                     std::move(redirect_data)));
+                     weak_method_factory_.GetWeakPtr(), std::move(params)));
 }
 
 void PrefetchService::OnGotCookiesForEligibilityCheck(
-    base::WeakPtr<PrefetchContainer> prefetch_container,
-    const GURL& url,
-    std::optional<std::pair<net::RedirectInfo,
-                            network::mojom::URLResponseHeadPtr>> redirect_data,
+    CheckEligibilityParams params,
     const net::CookieAccessResultList& cookie_list,
     const net::CookieAccessResultList& excluded_cookies) {
+  const auto prefetch_container = params.prefetch_container;
+
   TRACE_EVENT_NESTABLE_ASYNC_END0("loading", "PrefetchService::CheckCookies",
                                   this);
   TRACE_EVENT1("loading", "PrefetchService::OnGotCookiesForEligibilityCheck",
                "prefetch_url",
                prefetch_container ? prefetch_container->GetURL().spec() : "");
   if (!prefetch_container) {
-    OnGotEligibility(std::move(prefetch_container), std::move(redirect_data),
-                     PreloadingEligibility::kEligible);
+    std::move(params).Finish(PreloadingEligibility::kEligible);
     return;
   }
 
   if (!cookie_list.empty()) {
-    OnGotEligibility(std::move(prefetch_container), std::move(redirect_data),
-                     PreloadingEligibility::kUserHasCookies);
+    std::move(params).Finish(PreloadingEligibility::kUserHasCookies);
     return;
   }
 
@@ -1057,28 +1066,22 @@
       continue;
     }
 
-    if (url.DomainIs(cookie_result.cookie.DomainWithoutDot())) {
+    if (params.url.DomainIs(cookie_result.cookie.DomainWithoutDot())) {
       excluded_cookie_has_tld = true;
       break;
     }
   }
 
   if (excluded_cookie_has_tld) {
-    OnGotEligibility(std::move(prefetch_container), std::move(redirect_data),
-                     PreloadingEligibility::kUserHasCookies);
+    std::move(params).Finish(PreloadingEligibility::kUserHasCookies);
     return;
   }
 
-  StartProxyLookupCheck(std::move(prefetch_container), url,
-                        std::move(redirect_data));
+  StartProxyLookupCheck(std::move(params));
 }
 
-void PrefetchService::StartProxyLookupCheck(
-    base::WeakPtr<PrefetchContainer> prefetch_container,
-    const GURL& url,
-    std::optional<
-        std::pair<net::RedirectInfo, network::mojom::URLResponseHeadPtr>>
-        redirect_data) {
+void PrefetchService::StartProxyLookupCheck(CheckEligibilityParams params) {
+  const auto prefetch_container = params.prefetch_container;
   // Same origin prefetches (which use the default network context and cannot
   // use the prefetch proxy) can use the existing proxy settings.
   // TODO(crbug.com/40231580): Copy proxy settings over to the isolated
@@ -1086,8 +1089,7 @@
   // prefetches to be made using the existing proxy settings.
   if (!prefetch_container
            ->IsIsolatedNetworkContextRequiredForCurrentPrefetch()) {
-    OnGotEligibility(std::move(prefetch_container), std::move(redirect_data),
-                     PreloadingEligibility::kEligible);
+    std::move(params).Finish(PreloadingEligibility::kEligible);
     return;
   }
 
@@ -1095,69 +1097,47 @@
                                     this);
   // Start proxy check for this prefetch, and give ownership of the
   // |ProxyLookupClientImpl| to |prefetch_container|.
+  // `url` is needed to avoid use-after-move.
+  const GURL url = params.url;
   prefetch_container->TakeProxyLookupClient(
       std::make_unique<ProxyLookupClientImpl>(
           url,
           base::BindOnce(&PrefetchService::OnGotProxyLookupResult,
-                         weak_method_factory_.GetWeakPtr(),
-                         std::move(prefetch_container),
-                         std::move(redirect_data)),
+                         weak_method_factory_.GetWeakPtr(), std::move(params)),
           g_network_context_for_proxy_lookup_for_testing
               ? g_network_context_for_proxy_lookup_for_testing
               : browser_context_->GetDefaultStoragePartition()
                     ->GetNetworkContext()));
 }
 
-void PrefetchService::OnGotProxyLookupResult(
-    base::WeakPtr<PrefetchContainer> prefetch_container,
-    std::optional<std::pair<net::RedirectInfo,
-                            network::mojom::URLResponseHeadPtr>> redirect_data,
-    bool has_proxy) {
+void PrefetchService::OnGotProxyLookupResult(CheckEligibilityParams params,
+                                             bool has_proxy) {
+  const auto prefetch_container = params.prefetch_container;
   TRACE_EVENT_NESTABLE_ASYNC_END0("loading", "PrefetchService::ProxyCheck",
                                   this);
   TRACE_EVENT1("loading", "PrefetchService::OnGotProxyLookupResult",
                "prefetch_url",
                prefetch_container ? prefetch_container->GetURL().spec() : "");
   if (!prefetch_container) {
-    OnGotEligibility(std::move(prefetch_container), std::move(redirect_data),
-                     PreloadingEligibility::kEligible);
+    std::move(params).Finish(PreloadingEligibility::kEligible);
     return;
   }
 
   prefetch_container->ReleaseProxyLookupClient();
   if (has_proxy) {
-    OnGotEligibility(std::move(prefetch_container), std::move(redirect_data),
-                     PreloadingEligibility::kExistingProxy);
+    std::move(params).Finish(PreloadingEligibility::kExistingProxy);
     return;
   }
 
-  OnGotEligibility(std::move(prefetch_container), std::move(redirect_data),
-                   PreloadingEligibility::kEligible);
-}
-
-void PrefetchService::OnGotEligibility(
-    base::WeakPtr<PrefetchContainer> prefetch_container,
-    std::optional<std::pair<net::RedirectInfo,
-                            network::mojom::URLResponseHeadPtr>> redirect_data,
-    PreloadingEligibility eligibility) {
-  TRACE_EVENT_NESTABLE_ASYNC_END0("loading",
-                                  "PrefetchService::CheckEligibility", this);
-  TRACE_EVENT2("loading", "PrefetchService::OnGotEligibility", "prefetch_url",
-               prefetch_container ? prefetch_container->GetURL().spec() : "",
-               "eligibility", eligibility);
-  if (redirect_data.has_value()) {
-    OnGotEligibilityForRedirect(std::move(prefetch_container),
-                                std::move(std::get<0>(redirect_data.value())),
-                                std::move(std::get<1>(redirect_data.value())),
-                                eligibility);
-  } else {
-    OnGotEligibilityForNonRedirect(std::move(prefetch_container), eligibility);
-  }
+  std::move(params).Finish(PreloadingEligibility::kEligible);
 }
 
 void PrefetchService::OnGotEligibilityForNonRedirect(
-    base::WeakPtr<PrefetchContainer> prefetch_container,
+    CheckEligibilityParams params,
     PreloadingEligibility eligibility) {
+  const auto prefetch_container = params.prefetch_container;
+  TRACE_EVENT_NESTABLE_ASYNC_END0("loading",
+                                  "PrefetchService::CheckEligibility", this);
   TRACE_EVENT1("loading", "PrefetchService::OnGotEligibilityForNonRedirect",
                "prefetch_url",
                prefetch_container ? prefetch_container->GetURL().spec() : "");
@@ -1169,8 +1149,7 @@
   bool is_decoy = false;
   if (!eligible) {
     is_decoy =
-        prefetch_container->IsProxyRequiredForURL(
-            prefetch_container->GetURL()) &&
+        params.IsProxyRequired() &&
         ShouldConsiderDecoyRequestForStatus(eligibility) &&
         PrefetchServiceSendDecoyRequestForIneligblePrefetch(
             delegate_ ? delegate_->DisableDecoysBasedOnUserSettings() : false);
@@ -1222,10 +1201,13 @@
 }
 
 void PrefetchService::OnGotEligibilityForRedirect(
-    base::WeakPtr<PrefetchContainer> prefetch_container,
     net::RedirectInfo redirect_info,
     network::mojom::URLResponseHeadPtr redirect_head,
+    CheckEligibilityParams params,
     PreloadingEligibility eligibility) {
+  const auto prefetch_container = params.prefetch_container;
+  TRACE_EVENT_NESTABLE_ASYNC_END0("loading",
+                                  "PrefetchService::CheckEligibility", this);
   TRACE_EVENT1("loading", "PrefetchService::OnGotEligibilityForRedirect",
                "prefetch_url",
                prefetch_container ? prefetch_container->GetURL().spec() : "");
@@ -1242,7 +1224,7 @@
   bool is_decoy = false;
   if (!eligible) {
     is_decoy =
-        prefetch_container->IsProxyRequiredForURL(redirect_info.new_url) &&
+        params.IsProxyRequired() &&
         ShouldConsiderDecoyRequestForStatus(eligibility) &&
         PrefetchServiceSendDecoyRequestForIneligblePrefetch(
             delegate_ ? delegate_->DisableDecoysBasedOnUserSettings() : false);
@@ -1798,21 +1780,22 @@
           ->IsIsolatedNetworkContextRequiredForPreviousRedirectHop(),
       prefetch_container->IsIsolatedNetworkContextRequiredForCurrentPrefetch());
 
-  auto redirect_data = std::make_optional<
-      std::pair<net::RedirectInfo, network::mojom::URLResponseHeadPtr>>(
-      {redirect_info, std::move(redirect_head)});
+  auto params = CheckEligibilityParams(
+      {.prefetch_container = prefetch_container,
+       .url = redirect_info.new_url,
+       .is_redirect = true,
+       .callback = base::BindOnce(&PrefetchService::OnGotEligibilityForRedirect,
+                                  weak_method_factory_.GetWeakPtr(),
+                                  redirect_info, std::move(redirect_head))});
 
   if (GetDelayEligibilityCheckForTesting()) {
     GetDelayEligibilityCheckForTesting().Run(  // IN-TEST
         base::BindOnce(&PrefetchService::CheckEligibilityOfPrefetch,
-                       weak_method_factory_.GetWeakPtr(),
-                       std::move(prefetch_container), redirect_info.new_url,
-                       std::move(redirect_data)));
+                       weak_method_factory_.GetWeakPtr(), std::move(params)));
     return;
   }
 
-  CheckEligibilityOfPrefetch(std::move(prefetch_container),
-                             redirect_info.new_url, std::move(redirect_data));
+  CheckEligibilityOfPrefetch(std::move(params));
 }
 
 std::optional<PrefetchErrorOnResponseReceived>
@@ -2066,8 +2049,17 @@
       matching_prefetch = true;
       num_matching_prefetches++;
 
-      if (prefetch_iter.second->IsInitialPrefetchEligible()) {
-        num_matching_eligible_prefetch++;
+      switch (prefetch_iter.second->GetLoadState()) {
+        case PrefetchContainer::LoadState::kNotStarted:
+        case PrefetchContainer::LoadState::kFailedIneligible:
+          break;
+        case PrefetchContainer::LoadState::kEligible:
+        case PrefetchContainer::LoadState::kFailedHeldback:
+        case PrefetchContainer::LoadState::kStarted:
+        case PrefetchContainer::LoadState::kDeterminedHead:
+        case PrefetchContainer::LoadState::kCompletedOrFailed:
+          num_matching_eligible_prefetch++;
+          break;
       }
 
       switch (
diff --git a/content/browser/preloading/prefetch/prefetch_service.h b/content/browser/preloading/prefetch/prefetch_service.h
index 6c6b6c5e..64e59aa 100644
--- a/content/browser/preloading/prefetch/prefetch_service.h
+++ b/content/browser/preloading/prefetch/prefetch_service.h
@@ -229,29 +229,17 @@
  private:
   friend class PrefetchURLLoaderInterceptorTestBase;
 
+  struct CheckEligibilityParams;
+
   // Checks whether the given |prefetch_container| is eligible for prefetch.
   // Once the eligibility is determined then |OnGotEligibility()| will be
   // called.
-  void CheckEligibilityOfPrefetch(
-      base::WeakPtr<PrefetchContainer> prefetch_container,
-      const GURL& url,
-      std::optional<
-          std::pair<net::RedirectInfo, network::mojom::URLResponseHeadPtr>>
-          redirect_data);
+  void CheckEligibilityOfPrefetch(CheckEligibilityParams params);
 
-  void CheckHasServiceWorker(
-      base::WeakPtr<PrefetchContainer> prefetch_container,
-      const GURL& url,
-      std::optional<
-          std::pair<net::RedirectInfo, network::mojom::URLResponseHeadPtr>>
-          redirect_data);
+  void CheckHasServiceWorker(CheckEligibilityParams params);
 
   void OnGotServiceWorkerResult(
-      base::WeakPtr<PrefetchContainer> prefetch_container,
-      const GURL& url,
-      std::optional<
-          std::pair<net::RedirectInfo, network::mojom::URLResponseHeadPtr>>
-          redirect_data,
+      CheckEligibilityParams params,
       base::Time check_has_service_worker_start_time,
       ServiceWorkerCapability service_worker_capability);
 
@@ -259,33 +247,19 @@
   // |prefetch_container|. If there are any cookies, then the prefetch is not
   // eligible.
   void OnGotCookiesForEligibilityCheck(
-      base::WeakPtr<PrefetchContainer> prefetch_container,
-      const GURL& url,
-      std::optional<
-          std::pair<net::RedirectInfo, network::mojom::URLResponseHeadPtr>>
-          redirect_data,
+      CheckEligibilityParams params,
       const net::CookieAccessResultList& cookie_list,
       const net::CookieAccessResultList& excluded_cookies);
 
   // Starts the check for whether or not there is a proxy configured for the URL
   // of |prefetch_container|. If there is an existing proxy, then the prefetch
   // is not eligible.
-  void StartProxyLookupCheck(
-      base::WeakPtr<PrefetchContainer> prefetch_container,
-      const GURL& url,
-      std::optional<
-          std::pair<net::RedirectInfo, network::mojom::URLResponseHeadPtr>>
-          redirect_data);
+  void StartProxyLookupCheck(CheckEligibilityParams params);
 
   // Called after looking up the proxy configuration for the URL of
   // |prefetch_container|. If there is an existing proxy, then the prefetch is
   // not eligible.
-  void OnGotProxyLookupResult(
-      base::WeakPtr<PrefetchContainer> prefetch_container,
-      std::optional<
-          std::pair<net::RedirectInfo, network::mojom::URLResponseHeadPtr>>
-          redirect_data,
-      bool has_proxy);
+  void OnGotProxyLookupResult(CheckEligibilityParams params, bool has_proxy);
 
   // Called when the eligibility is determined for each fetch of prefetch, i.e.
   // initial fetch and redirects.
@@ -295,19 +269,12 @@
   // If the initial fetch (respectively, the redirect) is eligible or the
   // prefetch is decoy, the prefetch is added to `prefetch_queue_`
   // (respectively, is retained in the queue) and proceeds to the next fetch.
-  void OnGotEligibility(
-      base::WeakPtr<PrefetchContainer> prefetch_container,
-      std::optional<
-          std::pair<net::RedirectInfo, network::mojom::URLResponseHeadPtr>>
-          redirect_data,
-      PreloadingEligibility eligibility);
-  void OnGotEligibilityForNonRedirect(
-      base::WeakPtr<PrefetchContainer> prefetch_container,
-      PreloadingEligibility eligibility);
+  void OnGotEligibilityForNonRedirect(CheckEligibilityParams params,
+                                      PreloadingEligibility eligibility);
   void OnGotEligibilityForRedirect(
-      base::WeakPtr<PrefetchContainer> prefetch_container,
       net::RedirectInfo redirect_info,
       network::mojom::URLResponseHeadPtr redirect_head,
+      CheckEligibilityParams params,
       PreloadingEligibility eligibility);
 
   // Adds `prefetch_container` to the cache but doesn't initiate prefetching.
diff --git a/content/browser/preloading/prefetch/prefetch_url_loader_helper.cc b/content/browser/preloading/prefetch/prefetch_url_loader_helper.cc
index 431ea52..6406cb3 100644
--- a/content/browser/preloading/prefetch/prefetch_url_loader_helper.cc
+++ b/content/browser/preloading/prefetch/prefetch_url_loader_helper.cc
@@ -16,6 +16,7 @@
 #include "content/browser/preloading/prefetch/prefetch_status.h"
 #include "content/browser/renderer_host/frame_tree_node.h"
 #include "content/browser/renderer_host/navigation_request.h"
+#include "content/public/browser/browser_context.h"
 #include "content/public/browser/prefetch_metrics.h"
 #include "content/public/browser/web_contents.h"
 #include "net/cookies/canonical_cookie.h"
diff --git a/content/browser/renderer_host/input/input_transfer_handler_android_unittest.cc b/content/browser/renderer_host/input/input_transfer_handler_android_unittest.cc
index 32e4ad5..b74ebd8f 100644
--- a/content/browser/renderer_host/input/input_transfer_handler_android_unittest.cc
+++ b/content/browser/renderer_host/input/input_transfer_handler_android_unittest.cc
@@ -15,6 +15,7 @@
 #include "components/input/utils.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
+#include "ui/events/android/motion_event_android_factory.h"
 #include "ui/events/android/motion_event_android_java.h"
 #include "ui/events/motionevent_jni_headers/MotionEvent_jni.h"
 #include "ui/events/velocity_tracker/motion_event_generic.h"
@@ -54,7 +55,7 @@
   MOCK_METHOD((int), TransferInputToViz, (int), (override));
 };
 
-ui::MotionEventAndroidJava GetMotionEventAndroid(
+std::unique_ptr<ui::MotionEventAndroid> GetMotionEventAndroid(
     ui::MotionEvent::Action action,
     base::TimeTicks event_time,
     base::TimeTicks down_time,
@@ -67,10 +68,27 @@
           env, /*downTime=*/0, /*eventTime=*/0, /*action=*/0, /*x=*/0, /*y=*/0,
           /*metaState=*/0);
 
-  return ui::MotionEventAndroidJava(
-      env, obj, pix_to_dip, 0.f, 0.f, 0.f, event_time, event_time, down_time,
-      ui::MotionEventAndroid::GetAndroidAction(action), 1, 0, 0, 0, 0, 0, 0, 0,
-      false, &pointer, nullptr, false);
+  return ui::MotionEventAndroidFactory::CreateFromJava(
+      env, obj, pix_to_dip,
+      /*ticks_x=*/0.f,
+      /*ticks_y=*/0.f,
+      /*tick_multiplier=*/0.f,
+      /*oldest_event_time=*/event_time,
+      /*latest_event_time=*/event_time,
+      /*down_time_ms=*/down_time,
+      /*android_action=*/ui::MotionEventAndroid::GetAndroidAction(action),
+      /*pointer_count=*/1,
+      /*history_size=*/0,
+      /*action_index=*/0,
+      /*android_action_button=*/0,
+      /*android_gesture_classification=*/0,
+      /*android_button_state=*/0,
+      /*raw_offset_x_pixels=*/0,
+      /*raw_offset_y_pixels=*/0,
+      /*for_touch_handle=*/false,
+      /*pointer0=*/&pointer,
+      /*pointer1=*/nullptr,
+      /*is_latest_event_time_resampled=*/false);
 }
 
 }  // namespace
@@ -121,7 +139,7 @@
 TEST_F(InputTransferHandlerTest, ConsumeEventsIfSequenceTransferred) {
   base::TimeTicks event_time = base::TimeTicks::Now();
 
-  ui::MotionEventAndroidJava down_event = GetMotionEventAndroid(
+  auto down_event = GetMotionEventAndroid(
       ui::MotionEvent::Action::DOWN, event_time, event_time, finger_pointer_);
 
   EXPECT_CALL(*mock_, MaybeTransferInputToViz(_))
@@ -129,44 +147,44 @@
   EXPECT_CALL(*input_transfer_handler_client_,
               SendStateOnTouchTransfer(_, /*browser_would_have_handled=*/false))
       .Times(1);
-  EXPECT_TRUE(transfer_handler_->OnTouchEvent(down_event));
+  EXPECT_TRUE(transfer_handler_->OnTouchEvent(*down_event));
 
-  ui::MotionEventAndroidJava move_event = GetMotionEventAndroid(
+  auto move_event = GetMotionEventAndroid(
       ui::MotionEvent::Action::MOVE, event_time, event_time, finger_pointer_);
-  EXPECT_TRUE(transfer_handler_->OnTouchEvent(move_event));
-  EXPECT_TRUE(transfer_handler_->OnTouchEvent(move_event));
+  EXPECT_TRUE(transfer_handler_->OnTouchEvent(*move_event));
+  EXPECT_TRUE(transfer_handler_->OnTouchEvent(*move_event));
 
-  ui::MotionEventAndroidJava cancel_event = GetMotionEventAndroid(
+  auto cancel_event = GetMotionEventAndroid(
       ui::MotionEvent::Action::CANCEL, event_time, event_time, finger_pointer_);
-  EXPECT_TRUE(transfer_handler_->OnTouchEvent(cancel_event));
+  EXPECT_TRUE(transfer_handler_->OnTouchEvent(*cancel_event));
 
   transfer_handler_->OnTouchEnd(event_time + base::Milliseconds(10));
 
   EXPECT_CALL(*mock_, MaybeTransferInputToViz(_))
       .WillOnce(Return(kFailureTransferring));
   // New events shouldn't be consumed due the expectation set in line above.
-  EXPECT_FALSE(transfer_handler_->OnTouchEvent(down_event));
-  EXPECT_FALSE(transfer_handler_->OnTouchEvent(move_event));
+  EXPECT_FALSE(transfer_handler_->OnTouchEvent(*down_event));
+  EXPECT_FALSE(transfer_handler_->OnTouchEvent(*move_event));
 }
 
 TEST_F(InputTransferHandlerTest, EmitsTouchMovesSeenAfterTransferHistogram) {
   base::TimeTicks event_time = base::TimeTicks::Now();
-  ui::MotionEventAndroidJava down_event = GetMotionEventAndroid(
+  auto down_event = GetMotionEventAndroid(
       ui::MotionEvent::Action::DOWN, event_time, event_time, finger_pointer_);
-  ui::MotionEventAndroidJava move_event = GetMotionEventAndroid(
+  auto move_event = GetMotionEventAndroid(
       ui::MotionEvent::Action::MOVE, event_time, event_time, finger_pointer_);
-  ui::MotionEventAndroidJava cancel_event = GetMotionEventAndroid(
+  auto cancel_event = GetMotionEventAndroid(
       ui::MotionEvent::Action::CANCEL, event_time, event_time, finger_pointer_);
 
   for (int touch_moves_seen = 0; touch_moves_seen <= 2; touch_moves_seen++) {
     base::HistogramTester histogram_tester;
     EXPECT_CALL(*mock_, MaybeTransferInputToViz(_))
         .WillOnce(Return(kSuccessfullyTransferred));
-    EXPECT_TRUE(transfer_handler_->OnTouchEvent(down_event));
+    EXPECT_TRUE(transfer_handler_->OnTouchEvent(*down_event));
     for (int ind = 1; ind <= touch_moves_seen; ind++) {
-      EXPECT_TRUE(transfer_handler_->OnTouchEvent(move_event));
+      EXPECT_TRUE(transfer_handler_->OnTouchEvent(*move_event));
     }
-    EXPECT_TRUE(transfer_handler_->OnTouchEvent(cancel_event));
+    EXPECT_TRUE(transfer_handler_->OnTouchEvent(*cancel_event));
     histogram_tester.ExpectUniqueSample(
         InputTransferHandlerAndroid::kTouchMovesSeenHistogram, touch_moves_seen,
         1);
@@ -175,11 +193,11 @@
 
 TEST_F(InputTransferHandlerTest, EmitsEventsAfterTransferHistogram) {
   base::TimeTicks event_time = base::TimeTicks::Now();
-  ui::MotionEventAndroidJava down_event = GetMotionEventAndroid(
+  auto down_event = GetMotionEventAndroid(
       ui::MotionEvent::Action::DOWN, event_time, event_time, finger_pointer_);
-  ui::MotionEventAndroidJava move_event = GetMotionEventAndroid(
+  auto move_event = GetMotionEventAndroid(
       ui::MotionEvent::Action::MOVE, event_time, event_time, finger_pointer_);
-  ui::MotionEventAndroidJava cancel_event = GetMotionEventAndroid(
+  auto cancel_event = GetMotionEventAndroid(
       ui::MotionEvent::Action::CANCEL, event_time, event_time, finger_pointer_);
 
   const std::vector<std::pair<ui::MotionEvent::Action, int>>
@@ -197,36 +215,36 @@
     base::HistogramTester histogram_tester;
     EXPECT_CALL(*mock_, MaybeTransferInputToViz(_))
         .WillOnce(Return(kSuccessfullyTransferred));
-    EXPECT_TRUE(transfer_handler_->OnTouchEvent(down_event));
+    EXPECT_TRUE(transfer_handler_->OnTouchEvent(*down_event));
 
-    ui::MotionEventAndroidJava event = GetMotionEventAndroid(
-        event_action, event_time, event_time, finger_pointer_);
-    EXPECT_TRUE(transfer_handler_->OnTouchEvent(event));
+    auto event = GetMotionEventAndroid(event_action, event_time, event_time,
+                                       finger_pointer_);
+    EXPECT_TRUE(transfer_handler_->OnTouchEvent(*event));
     histogram_tester.ExpectUniqueSample(
         InputTransferHandlerAndroid::kEventsAfterTransferHistogram,
         expected_histogram_sample, 1);
 
-    EXPECT_TRUE(transfer_handler_->OnTouchEvent(cancel_event));
+    EXPECT_TRUE(transfer_handler_->OnTouchEvent(*cancel_event));
   }
 }
 
 TEST_F(InputTransferHandlerTest, DoNotConsumeEventsIfSequenceNotTransferred) {
   base::TimeTicks event_time = base::TimeTicks::Now();
-  ui::MotionEventAndroidJava down_event = GetMotionEventAndroid(
+  auto down_event = GetMotionEventAndroid(
       ui::MotionEvent::Action::DOWN, event_time, event_time, finger_pointer_);
 
   EXPECT_CALL(*mock_, MaybeTransferInputToViz(_))
       .WillOnce(Return(kFailureTransferring));
-  EXPECT_FALSE(transfer_handler_->OnTouchEvent(down_event));
+  EXPECT_FALSE(transfer_handler_->OnTouchEvent(*down_event));
 
-  ui::MotionEventAndroidJava move_event = GetMotionEventAndroid(
+  auto move_event = GetMotionEventAndroid(
       ui::MotionEvent::Action::MOVE, event_time, event_time, finger_pointer_);
-  EXPECT_FALSE(transfer_handler_->OnTouchEvent(move_event));
-  EXPECT_FALSE(transfer_handler_->OnTouchEvent(move_event));
+  EXPECT_FALSE(transfer_handler_->OnTouchEvent(*move_event));
+  EXPECT_FALSE(transfer_handler_->OnTouchEvent(*move_event));
 
-  ui::MotionEventAndroidJava cancel_event = GetMotionEventAndroid(
+  auto cancel_event = GetMotionEventAndroid(
       ui::MotionEvent::Action::CANCEL, event_time, event_time, finger_pointer_);
-  EXPECT_FALSE(transfer_handler_->OnTouchEvent(cancel_event));
+  EXPECT_FALSE(transfer_handler_->OnTouchEvent(*cancel_event));
 }
 
 TEST_F(InputTransferHandlerTest, DoNotConsumeNonFingerEvents) {
@@ -241,15 +259,15 @@
     ui::MotionEventAndroid::Pointer non_finger_pointer(0, 0, 0, 0, 0, 0, 0, 0,
                                                        0);
     non_finger_pointer.tool_type = tool_type;
-    ui::MotionEventAndroidJava down_event =
+    auto down_event =
         GetMotionEventAndroid(ui::MotionEvent::Action::DOWN, event_time,
                               event_time, non_finger_pointer);
-    ui::MotionEventAndroidJava up_event =
+    auto up_event =
         GetMotionEventAndroid(ui::MotionEvent::Action::UP, event_time,
                               event_time, non_finger_pointer);
     EXPECT_CALL(*mock_, MaybeTransferInputToViz(_)).Times(0);
-    EXPECT_FALSE(transfer_handler_->OnTouchEvent(down_event));
-    EXPECT_FALSE(transfer_handler_->OnTouchEvent(up_event));
+    EXPECT_FALSE(transfer_handler_->OnTouchEvent(*down_event));
+    EXPECT_FALSE(transfer_handler_->OnTouchEvent(*up_event));
   }
 }
 
@@ -265,17 +283,17 @@
       // Arbitrary non-finger tooltype.
       pointer.tool_type = static_cast<int>(ui::MotionEvent::ToolType::STYLUS);
     }
-    ui::MotionEventAndroidJava down_event = GetMotionEventAndroid(
-        ui::MotionEvent::Action::DOWN, event_time, event_time, pointer);
-    ui::MotionEventAndroidJava up_event = GetMotionEventAndroid(
-        ui::MotionEvent::Action::CANCEL, event_time, event_time, pointer);
+    auto down_event = GetMotionEventAndroid(ui::MotionEvent::Action::DOWN,
+                                            event_time, event_time, pointer);
+    auto up_event = GetMotionEventAndroid(ui::MotionEvent::Action::CANCEL,
+                                          event_time, event_time, pointer);
     if (transfer_result !=
         static_cast<int>(TransferInputToVizResult::kNonFingerToolType)) {
       EXPECT_CALL(*mock_, MaybeTransferInputToViz(_))
           .WillOnce(Return(transfer_result));
     }
-    transfer_handler_->OnTouchEvent(down_event);
-    transfer_handler_->OnTouchEvent(up_event);
+    transfer_handler_->OnTouchEvent(*down_event);
+    transfer_handler_->OnTouchEvent(*up_event);
 
     histogram_tester.ExpectUniqueSample(
         InputTransferHandlerAndroid::kTransferInputToVizResultHistogram,
@@ -297,10 +315,10 @@
        transfer_result++) {
     event_time += base::Milliseconds(8);
     base::TimeTicks down_time = event_time;
-    ui::MotionEventAndroidJava down_event = GetMotionEventAndroid(
+    auto down_event = GetMotionEventAndroid(
         ui::MotionEvent::Action::DOWN, event_time, down_time, finger_pointer_);
     event_time += base::Milliseconds(8);
-    ui::MotionEventAndroidJava cancel_event =
+    auto cancel_event =
         GetMotionEventAndroid(ui::MotionEvent::Action::CANCEL, event_time,
                               down_time, finger_pointer_);
 
@@ -310,8 +328,8 @@
         *input_transfer_handler_client_,
         SendStateOnTouchTransfer(_, /*browser_would_have_handled=*/false))
         .Times(1);
-    EXPECT_TRUE(transfer_handler_->OnTouchEvent(down_event));
-    EXPECT_TRUE(transfer_handler_->OnTouchEvent(cancel_event));
+    EXPECT_TRUE(transfer_handler_->OnTouchEvent(*down_event));
+    EXPECT_TRUE(transfer_handler_->OnTouchEvent(*cancel_event));
 
     testing::Mock::VerifyAndClearExpectations(mock_);
     testing::Mock::VerifyAndClearExpectations(
@@ -319,10 +337,10 @@
 
     event_time += base::Milliseconds(20);
     down_time = event_time;
-    ui::MotionEventAndroidJava down_event_2 = GetMotionEventAndroid(
+    auto down_event_2 = GetMotionEventAndroid(
         ui::MotionEvent::Action::DOWN, event_time, down_time, finger_pointer_);
     event_time += base::Milliseconds(8);
-    ui::MotionEventAndroidJava cancel_event_2 =
+    auto cancel_event_2 =
         GetMotionEventAndroid(ui::MotionEvent::Action::CANCEL, event_time,
                               down_time, finger_pointer_);
 
@@ -342,8 +360,8 @@
           .Times(1);
     }
 
-    EXPECT_TRUE(transfer_handler_->OnTouchEvent(down_event_2));
-    EXPECT_TRUE(transfer_handler_->OnTouchEvent(cancel_event_2));
+    EXPECT_TRUE(transfer_handler_->OnTouchEvent(*down_event_2));
+    EXPECT_TRUE(transfer_handler_->OnTouchEvent(*cancel_event_2));
 
     event_time += base::Milliseconds(20);
     transfer_handler_->OnTouchEnd(event_time);
@@ -358,26 +376,26 @@
        DropFailedTransferSequenceWhileVizHandlesInput) {
   base::TimeTicks event_time = base::TimeTicks::Now() - base::Milliseconds(60);
   base::TimeTicks down_time = event_time;
-  ui::MotionEventAndroidJava down_event_1 = GetMotionEventAndroid(
+  auto down_event_1 = GetMotionEventAndroid(
       ui::MotionEvent::Action::DOWN, event_time, down_time, finger_pointer_);
   event_time += base::Milliseconds(8);
-  ui::MotionEventAndroidJava cancel_event_1 = GetMotionEventAndroid(
+  auto cancel_event_1 = GetMotionEventAndroid(
       ui::MotionEvent::Action::CANCEL, event_time, down_time, finger_pointer_);
 
   EXPECT_CALL(*mock_, MaybeTransferInputToViz(_))
       .WillOnce(Return(kSuccessfullyTransferred));
-  EXPECT_TRUE(transfer_handler_->OnTouchEvent(down_event_1));
-  EXPECT_TRUE(transfer_handler_->OnTouchEvent(cancel_event_1));
+  EXPECT_TRUE(transfer_handler_->OnTouchEvent(*down_event_1));
+  EXPECT_TRUE(transfer_handler_->OnTouchEvent(*cancel_event_1));
 
   event_time += base::Milliseconds(20);
   down_time = event_time;
-  ui::MotionEventAndroidJava down_event_2 = GetMotionEventAndroid(
+  auto down_event_2 = GetMotionEventAndroid(
       ui::MotionEvent::Action::DOWN, event_time, down_time, finger_pointer_);
   event_time += base::Milliseconds(8);
-  ui::MotionEventAndroidJava move_event_2 = GetMotionEventAndroid(
+  auto move_event_2 = GetMotionEventAndroid(
       ui::MotionEvent::Action::MOVE, event_time, down_time, finger_pointer_);
   event_time += base::Milliseconds(8);
-  ui::MotionEventAndroidJava cancel_event_2 = GetMotionEventAndroid(
+  auto cancel_event_2 = GetMotionEventAndroid(
       ui::MotionEvent::Action::CANCEL, event_time, down_time, finger_pointer_);
   // The next sequence fails to transfer.
   EXPECT_CALL(*mock_, MaybeTransferInputToViz(_))
@@ -387,9 +405,9 @@
   // The new sequence should be dropped, instead of Browser and Viz
   // potentially handling the seequence at same time.
   base::HistogramTester histogram_tester;
-  EXPECT_TRUE(transfer_handler_->OnTouchEvent(down_event_2));
-  EXPECT_TRUE(transfer_handler_->OnTouchEvent(move_event_2));
-  EXPECT_TRUE(transfer_handler_->OnTouchEvent(cancel_event_2));
+  EXPECT_TRUE(transfer_handler_->OnTouchEvent(*down_event_2));
+  EXPECT_TRUE(transfer_handler_->OnTouchEvent(*move_event_2));
+  EXPECT_TRUE(transfer_handler_->OnTouchEvent(*cancel_event_2));
   const int num_dropped_events = 3;
   histogram_tester.ExpectUniqueSample(
       InputTransferHandlerAndroid::kEventsInDroppedSequenceHistogram,
@@ -409,37 +427,37 @@
        BrowserHandlesSequenceAfterTouchEndNotification) {
   base::TimeTicks event_time = base::TimeTicks::Now() - base::Milliseconds(60);
   base::TimeTicks down_time = event_time;
-  ui::MotionEventAndroidJava down_event_1 = GetMotionEventAndroid(
+  auto down_event_1 = GetMotionEventAndroid(
       ui::MotionEvent::Action::DOWN, event_time, down_time, finger_pointer_);
   event_time += base::Milliseconds(8);
-  ui::MotionEventAndroidJava cancel_event_1 = GetMotionEventAndroid(
+  auto cancel_event_1 = GetMotionEventAndroid(
       ui::MotionEvent::Action::CANCEL, event_time, down_time, finger_pointer_);
 
   EXPECT_CALL(*mock_, MaybeTransferInputToViz(_))
       .WillOnce(Return(kSuccessfullyTransferred));
-  EXPECT_TRUE(transfer_handler_->OnTouchEvent(down_event_1));
-  EXPECT_TRUE(transfer_handler_->OnTouchEvent(cancel_event_1));
+  EXPECT_TRUE(transfer_handler_->OnTouchEvent(*down_event_1));
+  EXPECT_TRUE(transfer_handler_->OnTouchEvent(*cancel_event_1));
 
   event_time += base::Milliseconds(8);
   transfer_handler_->OnTouchEnd(event_time);
 
   event_time += base::Milliseconds(20);
   down_time = event_time;
-  ui::MotionEventAndroidJava down_event_2 = GetMotionEventAndroid(
+  auto down_event_2 = GetMotionEventAndroid(
       ui::MotionEvent::Action::DOWN, event_time, down_time, finger_pointer_);
   event_time += base::Milliseconds(8);
-  ui::MotionEventAndroidJava move_event_2 = GetMotionEventAndroid(
+  auto move_event_2 = GetMotionEventAndroid(
       ui::MotionEvent::Action::MOVE, event_time, down_time, finger_pointer_);
   event_time += base::Milliseconds(8);
-  ui::MotionEventAndroidJava cancel_event_2 = GetMotionEventAndroid(
+  auto cancel_event_2 = GetMotionEventAndroid(
       ui::MotionEvent::Action::CANCEL, event_time, down_time, finger_pointer_);
   // The next sequence fails to transfer.
   EXPECT_CALL(*mock_, MaybeTransferInputToViz(_))
       .WillOnce(Return(kFailureTransferring));
 
-  EXPECT_FALSE(transfer_handler_->OnTouchEvent(down_event_2));
-  EXPECT_FALSE(transfer_handler_->OnTouchEvent(move_event_2));
-  EXPECT_FALSE(transfer_handler_->OnTouchEvent(cancel_event_2));
+  EXPECT_FALSE(transfer_handler_->OnTouchEvent(*down_event_2));
+  EXPECT_FALSE(transfer_handler_->OnTouchEvent(*move_event_2));
+  EXPECT_FALSE(transfer_handler_->OnTouchEvent(*cancel_event_2));
 }
 
 TEST_F(InputTransferHandlerTest, DoNotRetryTransferIfNoActiveSequence) {
@@ -456,10 +474,10 @@
        transfer_result++) {
     event_time += base::Milliseconds(8);
     base::TimeTicks down_time = event_time;
-    ui::MotionEventAndroidJava down_event = GetMotionEventAndroid(
+    auto down_event = GetMotionEventAndroid(
         ui::MotionEvent::Action::DOWN, event_time, down_time, finger_pointer_);
     event_time += base::Milliseconds(8);
-    ui::MotionEventAndroidJava cancel_event =
+    auto cancel_event =
         GetMotionEventAndroid(ui::MotionEvent::Action::CANCEL, event_time,
                               down_time, finger_pointer_);
 
@@ -469,8 +487,8 @@
         *input_transfer_handler_client_,
         SendStateOnTouchTransfer(_, /*browser_would_have_handled=*/false))
         .Times(1);
-    EXPECT_TRUE(transfer_handler_->OnTouchEvent(down_event));
-    EXPECT_TRUE(transfer_handler_->OnTouchEvent(cancel_event));
+    EXPECT_TRUE(transfer_handler_->OnTouchEvent(*down_event));
+    EXPECT_TRUE(transfer_handler_->OnTouchEvent(*cancel_event));
 
     testing::Mock::VerifyAndClearExpectations(mock_);
     testing::Mock::VerifyAndClearExpectations(
@@ -481,10 +499,10 @@
 
     event_time += base::Milliseconds(20);
     down_time = event_time;
-    ui::MotionEventAndroidJava down_event_2 = GetMotionEventAndroid(
+    auto down_event_2 = GetMotionEventAndroid(
         ui::MotionEvent::Action::DOWN, event_time, down_time, finger_pointer_);
     event_time += base::Milliseconds(8);
-    ui::MotionEventAndroidJava cancel_event_2 =
+    auto cancel_event_2 =
         GetMotionEventAndroid(ui::MotionEvent::Action::CANCEL, event_time,
                               down_time, finger_pointer_);
 
@@ -504,8 +522,8 @@
     }
 
     const bool consume_sequence = transfer_result == kSuccessfullyTransferred;
-    EXPECT_EQ(transfer_handler_->OnTouchEvent(down_event_2), consume_sequence);
-    EXPECT_EQ(transfer_handler_->OnTouchEvent(cancel_event_2),
+    EXPECT_EQ(transfer_handler_->OnTouchEvent(*down_event_2), consume_sequence);
+    EXPECT_EQ(transfer_handler_->OnTouchEvent(*cancel_event_2),
               consume_sequence);
 
     event_time += base::Milliseconds(20);
@@ -521,11 +539,11 @@
   base::HistogramTester histogram_tester;
   base::TimeTicks event_time = base::TimeTicks::Now() - base::Milliseconds(60);
   base::TimeTicks down_time = event_time + base::Milliseconds(8);
-  ui::MotionEventAndroidJava down_event = GetMotionEventAndroid(
+  auto down_event = GetMotionEventAndroid(
       ui::MotionEvent::Action::DOWN, event_time, down_time, finger_pointer_);
 
   EXPECT_CALL(*mock_, MaybeTransferInputToViz(_)).Times(0);
-  EXPECT_FALSE(transfer_handler_->OnTouchEvent(down_event));
+  EXPECT_FALSE(transfer_handler_->OnTouchEvent(*down_event));
   histogram_tester.ExpectUniqueSample(
       InputTransferHandlerAndroid::kTransferInputToVizResultHistogram,
       TransferInputToVizResult::kDownTimeAfterEventTime, 1);
@@ -535,24 +553,24 @@
        DownTimeAfterEventTimeButActiveTouchSequenceOnViz) {
   base::TimeTicks event_time = base::TimeTicks::Now() - base::Milliseconds(60);
   base::TimeTicks down_time = event_time;
-  ui::MotionEventAndroidJava down_event_1 = GetMotionEventAndroid(
+  auto down_event_1 = GetMotionEventAndroid(
       ui::MotionEvent::Action::DOWN, event_time, down_time, finger_pointer_);
   event_time += base::Milliseconds(8);
-  ui::MotionEventAndroidJava cancel_event_1 = GetMotionEventAndroid(
+  auto cancel_event_1 = GetMotionEventAndroid(
       ui::MotionEvent::Action::CANCEL, event_time, down_time, finger_pointer_);
 
   EXPECT_CALL(*mock_, MaybeTransferInputToViz(_))
       .WillOnce(Return(kSuccessfullyTransferred));
-  EXPECT_TRUE(transfer_handler_->OnTouchEvent(down_event_1));
-  EXPECT_TRUE(transfer_handler_->OnTouchEvent(cancel_event_1));
+  EXPECT_TRUE(transfer_handler_->OnTouchEvent(*down_event_1));
+  EXPECT_TRUE(transfer_handler_->OnTouchEvent(*cancel_event_1));
 
   // Touch end hasn't been received from Viz.
   event_time += base::Milliseconds(20);
   // Down time is later than the event time of input event.
   down_time = event_time + base::Milliseconds(8);
-  ui::MotionEventAndroidJava down_event_2 = GetMotionEventAndroid(
+  auto down_event_2 = GetMotionEventAndroid(
       ui::MotionEvent::Action::DOWN, event_time, down_time, finger_pointer_);
-  EXPECT_TRUE(transfer_handler_->OnTouchEvent(down_event_2));
+  EXPECT_TRUE(transfer_handler_->OnTouchEvent(*down_event_2));
 }
 
 }  // namespace content
diff --git a/content/browser/renderer_host/navigation_state_keep_alive.cc b/content/browser/renderer_host/navigation_state_keep_alive.cc
index 45cb25d7..9aad43a 100644
--- a/content/browser/renderer_host/navigation_state_keep_alive.cc
+++ b/content/browser/renderer_host/navigation_state_keep_alive.cc
@@ -11,6 +11,7 @@
 #include "content/browser/site_instance_group.h"
 #include "content/browser/site_instance_impl.h"
 #include "content/browser/storage_partition_impl.h"
+#include "content/public/browser/browser_context.h"
 
 namespace content {
 
diff --git a/content/browser/renderer_host/render_frame_host_impl.cc b/content/browser/renderer_host/render_frame_host_impl.cc
index e287148e..2406cb3a 100644
--- a/content/browser/renderer_host/render_frame_host_impl.cc
+++ b/content/browser/renderer_host/render_frame_host_impl.cc
@@ -6429,8 +6429,6 @@
     unload_event_monitor_timeout_->Start(kUnloadTimeout);
   }
 
-  // TODO(nasko): If the frame is not live, the RFH should just be deleted by
-  // simulating the receipt of unload ack.
   is_waiting_for_unload_ack_ = true;
 
   if (proxy) {
diff --git a/content/browser/renderer_host/render_frame_metadata_provider_impl.cc b/content/browser/renderer_host/render_frame_metadata_provider_impl.cc
index f8d89932..09ff06f7 100644
--- a/content/browser/renderer_host/render_frame_metadata_provider_impl.cc
+++ b/content/browser/renderer_host/render_frame_metadata_provider_impl.cc
@@ -42,7 +42,7 @@
   // Reset on disconnect so that pending state will be correctly stored and
   // later forwarded in the case of a renderer crash.
   render_frame_metadata_observer_remote_.reset_on_disconnect();
-#if BUILDFLAG(IS_ANDROID)
+#if BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_IOS)
   if (pending_root_scroll_offset_update_frequency_.has_value()) {
     UpdateRootScrollOffsetUpdateFrequency(
         *pending_root_scroll_offset_update_frequency_);
@@ -56,7 +56,7 @@
   }
 }
 
-#if BUILDFLAG(IS_ANDROID)
+#if BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_IOS)
 void RenderFrameMetadataProviderImpl::UpdateRootScrollOffsetUpdateFrequency(
     cc::mojom::RootScrollOffsetUpdateFrequency frequency) {
   if (!render_frame_metadata_observer_remote_) {
@@ -151,7 +151,7 @@
                                   weak_factory_.GetWeakPtr()));
 }
 
-#if BUILDFLAG(IS_ANDROID)
+#if BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_IOS)
 void RenderFrameMetadataProviderImpl::OnRootScrollOffsetChanged(
     const gfx::PointF& root_scroll_offset) {
   for (Observer& observer : observers_)
diff --git a/content/browser/renderer_host/render_frame_metadata_provider_impl.h b/content/browser/renderer_host/render_frame_metadata_provider_impl.h
index 8e06afe8..694b99d3 100644
--- a/content/browser/renderer_host/render_frame_metadata_provider_impl.h
+++ b/content/browser/renderer_host/render_frame_metadata_provider_impl.h
@@ -55,7 +55,7 @@
 
   const cc::RenderFrameMetadata& LastRenderFrameMetadata() override;
 
-#if BUILDFLAG(IS_ANDROID)
+#if BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_IOS)
   // Notifies the renderer of the changes in the notification frequency of the
   // root scroll updates, which is needed for accessibility and
   // GestureListenerManager on Android.
@@ -90,7 +90,7 @@
       uint32_t frame_token,
       const cc::RenderFrameMetadata& metadata) override;
   void OnFrameSubmissionForTesting(uint32_t frame_token) override;
-#if BUILDFLAG(IS_ANDROID)
+#if BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_IOS)
   void OnRootScrollOffsetChanged(
       const gfx::PointF& root_scroll_offset) override;
 #endif
@@ -111,7 +111,7 @@
   mojo::Remote<cc::mojom::RenderFrameMetadataObserver>
       render_frame_metadata_observer_remote_;
 
-#if BUILDFLAG(IS_ANDROID)
+#if BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_IOS)
   std::optional<cc::mojom::RootScrollOffsetUpdateFrequency>
       pending_root_scroll_offset_update_frequency_;
 #endif
diff --git a/content/browser/renderer_host/render_widget_host_browsertest.cc b/content/browser/renderer_host/render_widget_host_browsertest.cc
index 1b47d43..bab7b504 100644
--- a/content/browser/renderer_host/render_widget_host_browsertest.cc
+++ b/content/browser/renderer_host/render_widget_host_browsertest.cc
@@ -26,6 +26,7 @@
 #include "content/browser/web_contents/web_contents_impl.h"
 #include "content/common/content_navigation_policy.h"
 #include "content/common/input/synthetic_smooth_drag_gesture.h"
+#include "content/public/browser/browser_context.h"
 #include "content/public/browser/render_widget_host_observer.h"
 #include "content/public/browser/web_contents.h"
 #include "content/public/browser/web_contents_observer.h"
diff --git a/content/browser/renderer_host/render_widget_host_unittest.cc b/content/browser/renderer_host/render_widget_host_unittest.cc
index f4915c40..2344a1b 100644
--- a/content/browser/renderer_host/render_widget_host_unittest.cc
+++ b/content/browser/renderer_host/render_widget_host_unittest.cc
@@ -323,7 +323,7 @@
 
   ~FakeRenderFrameMetadataObserver() override {}
 
-#if BUILDFLAG(IS_ANDROID)
+#if BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_IOS)
   void UpdateRootScrollOffsetUpdateFrequency(
       cc::mojom::RootScrollOffsetUpdateFrequency frequency) override {}
 #endif
diff --git a/content/browser/renderer_host/render_widget_host_view_android_browsertest.cc b/content/browser/renderer_host/render_widget_host_view_android_browsertest.cc
index 14c6192..7c1aec3 100644
--- a/content/browser/renderer_host/render_widget_host_view_android_browsertest.cc
+++ b/content/browser/renderer_host/render_widget_host_view_android_browsertest.cc
@@ -16,6 +16,7 @@
 #include "content/public/test/content_browser_test_utils.h"
 #include "content/shell/browser/shell.h"
 #include "testing/gmock/include/gmock/gmock.h"
+#include "ui/events/android/motion_event_android_factory.h"
 #include "ui/events/android/motion_event_android_java.h"
 #include "ui/events/base_event_utils.h"
 #include "ui/events/motionevent_jni_headers/MotionEvent_jni.h"
@@ -90,16 +91,31 @@
       JNI_MotionEvent::Java_MotionEvent_obtain(
           env, /*downTime=*/0, /*eventTime=*/0, /*action=*/0, /*x=*/0, /*y=*/0,
           /*metaState=*/0);
-  ui::MotionEventAndroidJava touch(
-      env, obj, 1.f, 0, 0, 0, base::TimeTicks::FromJavaNanoTime(time_ns),
-      ui::MotionEventAndroid::GetAndroidAction(action), 1, 0, 0, 0, 0, 0, 0, 0,
-      false, &p, nullptr);
+  auto touch = ui::MotionEventAndroidFactory::CreateFromJava(
+      env, obj,
+      /*pix_to_dip=*/1.f,
+      /*ticks_x=*/0,
+      /*ticks_y=*/0,
+      /*tick_multiplier=*/0,
+      /*oldest_event_time=*/base::TimeTicks::FromJavaNanoTime(time_ns),
+      /*android_action=*/ui::MotionEventAndroid::GetAndroidAction(action),
+      /*pointer_count=*/1,
+      /*history_size=*/0,
+      /*action_index=*/0,
+      /*android_action_button=*/0,
+      /*android_gesture_classification=*/0,
+      /*android_button_state=*/0,
+      /*raw_offset_x_pixels=*/0,
+      /*raw_offset_y_pixels=*/0,
+      /*for_touch_handle=*/false,
+      /*pointer0=*/&p,
+      /*pointer1=*/nullptr);
 
   int successfully_transferred =
       static_cast<int>(TransferInputToVizResult::kSuccessfullyTransferred);
   EXPECT_CALL(*mock_jni, MaybeTransferInputToViz(_))
       .WillOnce(Return(successfully_transferred));
-  view->OnTouchEvent(touch);
+  view->OnTouchEvent(*touch);
 
   absl::Status status = ttp.StopAndParseTrace();
   EXPECT_TRUE(status.ok()) << status.message();
diff --git a/content/browser/renderer_host/render_widget_host_view_android_unittest.cc b/content/browser/renderer_host/render_widget_host_view_android_unittest.cc
index 820ca851..4a243d7 100644
--- a/content/browser/renderer_host/render_widget_host_view_android_unittest.cc
+++ b/content/browser/renderer_host/render_widget_host_view_android_unittest.cc
@@ -28,6 +28,7 @@
 #include "ui/android/test_view_android_delegate.h"
 #include "ui/android/view_android.h"
 #include "ui/android/window_android.h"
+#include "ui/events/android/motion_event_android_factory.h"
 #include "ui/events/android/motion_event_android_java.h"
 #include "ui/events/base_event_utils.h"
 #include "ui/events/motionevent_jni_headers/MotionEvent_jni.h"
@@ -482,18 +483,33 @@
       JNI_MotionEvent::Java_MotionEvent_obtain(
           env, /*downTime=*/0, /*eventTime=*/0, /*action=*/0, /*x=*/0, /*y=*/0,
           /*metaState=*/0);
-  ui::MotionEventAndroidJava touch_down(
-      env, obj, 1.f, 0, 0, 0, base::TimeTicks::FromJavaNanoTime(time_ns),
-      ui::MotionEventAndroid::GetAndroidAction(action), 1, 0, 0, 0, 0, 0, 0, 0,
-      false, &p, nullptr);
+  auto touch_down = ui::MotionEventAndroidFactory::CreateFromJava(
+      env, obj,
+      /*pix_to_dip=*/1.f,
+      /*ticks_x=*/0,
+      /*ticks_y=*/0,
+      /*tick_multiplier=*/0,
+      /*oldest_event_time=*/base::TimeTicks::FromJavaNanoTime(time_ns),
+      /*android_action=*/ui::MotionEventAndroid::GetAndroidAction(action),
+      /*pointer_count=*/1,
+      /*history_size=*/0,
+      /*action_index=*/0,
+      /*android_action_button=*/0,
+      /*android_gesture_classification=*/0,
+      /*android_button_state=*/0,
+      /*raw_offset_x_pixels=*/0,
+      /*raw_offset_y_pixels=*/0,
+      /*for_touch_handle=*/false,
+      /*pointer0=*/&p,
+      /*pointer1=*/nullptr);
 
   EXPECT_CALL(*handler, OnTouchEventImpl(_, _)).WillOnce(Return(true));
   EXPECT_EQ(gesture_provider.GetCurrentDownEvent(), nullptr);
-  rwhva->OnTouchEvent(touch_down);
+  rwhva->OnTouchEvent(*touch_down);
   EXPECT_EQ(gesture_provider.GetCurrentDownEvent(), nullptr);
 
   EXPECT_CALL(*handler, OnTouchEventImpl(_, _)).WillOnce(Return(false));
-  rwhva->OnTouchEvent(touch_down);
+  rwhva->OnTouchEvent(*touch_down);
   EXPECT_NE(gesture_provider.GetCurrentDownEvent(), nullptr);
 }
 
@@ -510,11 +526,26 @@
       JNI_MotionEvent::Java_MotionEvent_obtain(
           env, /*downTime=*/0, /*eventTime=*/0, /*action=*/0, /*x=*/0, /*y=*/0,
           /*metaState=*/0);
-  ui::MotionEventAndroidJava touch_down(
-      env, obj, 1.f, 0, 0, 0, base::TimeTicks::FromJavaNanoTime(time_ns),
-      ui::MotionEventAndroid::GetAndroidAction(action), 1, 0, 0, 0, 0, 0, 0, 0,
-      false, &p, nullptr);
-  rwhva->OnTouchEvent(touch_down);
+  auto touch_down = ui::MotionEventAndroidFactory::CreateFromJava(
+      env, obj,
+      /*pix_to_dip=*/1.f,
+      /*ticks_x=*/0,
+      /*ticks_y=*/0,
+      /*tick_multiplier=*/0,
+      /*oldest_event_time=*/base::TimeTicks::FromJavaNanoTime(time_ns),
+      /*android_action=*/ui::MotionEventAndroid::GetAndroidAction(action),
+      /*pointer_count=*/1,
+      /*history_size=*/0,
+      /*action_index=*/0,
+      /*android_action_button=*/0,
+      /*android_gesture_classification=*/0,
+      /*android_button_state=*/0,
+      /*raw_offset_x_pixels=*/0,
+      /*raw_offset_y_pixels=*/0,
+      /*for_touch_handle=*/false,
+      /*pointer0=*/&p,
+      /*pointer1=*/nullptr);
+  rwhva->OnTouchEvent(*touch_down);
 
   auto& gesture_provider = rwhva->GetGestureProvider();
   EXPECT_NE(gesture_provider.GetCurrentDownEvent(), nullptr);
@@ -579,13 +610,28 @@
       JNI_MotionEvent::Java_MotionEvent_obtain(
           env, /*downTime=*/0, /*eventTime=*/0, /*action=*/0, /*x=*/0, /*y=*/0,
           /*metaState=*/0);
-  ui::MotionEventAndroidJava touch_down1(
-      env, obj1, 1.f, 0, 0, 0, base::TimeTicks::FromJavaNanoTime(time_ns),
-      ui::MotionEventAndroid::GetAndroidAction(action), 1, 0, 0, 0, 0, 0, 0, 0,
-      false, &p, nullptr);
+  auto touch_down1 = ui::MotionEventAndroidFactory::CreateFromJava(
+      env, obj1,
+      /*pix_to_dip=*/1.f,
+      /*ticks_x=*/0,
+      /*ticks_y=*/0,
+      /*tick_multiplier=*/0,
+      /*oldest_event_time=*/base::TimeTicks::FromJavaNanoTime(time_ns),
+      /*android_action=*/ui::MotionEventAndroid::GetAndroidAction(action),
+      /*pointer_count=*/1,
+      /*history_size=*/0,
+      /*action_index=*/0,
+      /*android_action_button=*/0,
+      /*android_gesture_classification=*/0,
+      /*android_button_state=*/0,
+      /*raw_offset_x_pixels=*/0,
+      /*raw_offset_y_pixels=*/0,
+      /*for_touch_handle=*/false,
+      /*pointer0=*/&p,
+      /*pointer1=*/nullptr);
 
   EXPECT_CALL(*handler, OnTouchEventImpl(_, _)).WillOnce(Return(true));
-  rwhva->OnTouchEvent(touch_down1);
+  rwhva->OnTouchEvent(*touch_down1);
 
   time_ns = (ui::EventTimeForNow() - base::TimeTicks()).InNanoseconds();
 
@@ -593,13 +639,28 @@
       JNI_MotionEvent::Java_MotionEvent_obtain(
           env, /*downTime=*/0, /*eventTime=*/0, /*action=*/0, /*x=*/0, /*y=*/0,
           /*metaState=*/0);
-  ui::MotionEventAndroidJava touch_down2(
-      env, obj2, 1.f, 0, 0, 0, base::TimeTicks::FromJavaNanoTime(time_ns),
-      ui::MotionEventAndroid::GetAndroidAction(action), 1, 0, 0, 0, 0, 0, 0, 0,
-      false, &p, nullptr);
+  auto touch_down2 = ui::MotionEventAndroidFactory::CreateFromJava(
+      env, obj2,
+      /*pix_to_dip=*/1.f,
+      /*ticks_x=*/0,
+      /*ticks_y=*/0,
+      /*tick_multiplier=*/0,
+      /*oldest_event_time=*/base::TimeTicks::FromJavaNanoTime(time_ns),
+      /*android_action=*/ui::MotionEventAndroid::GetAndroidAction(action),
+      /*pointer_count=*/1,
+      /*history_size=*/0,
+      /*action_index=*/0,
+      /*android_action_button=*/0,
+      /*android_gesture_classification=*/0,
+      /*android_button_state=*/0,
+      /*raw_offset_x_pixels=*/0,
+      /*raw_offset_y_pixels=*/0,
+      /*for_touch_handle=*/false,
+      /*pointer0=*/&p,
+      /*pointer1=*/nullptr);
 
   EXPECT_CALL(*handler, OnTouchEventImpl(_, _)).WillOnce(Return(false));
-  rwhva->OnTouchEvent(touch_down2);
+  rwhva->OnTouchEvent(*touch_down2);
   // Expect a call to StopFlingingOnViz mojo method if the input sequence hasn't
   // been transferred to VizCompositorThread for handling.
   EXPECT_CALL(rir_delegate, StopFlingingOnViz).Times(1);
diff --git a/content/browser/renderer_host/render_widget_host_view_ios.h b/content/browser/renderer_host/render_widget_host_view_ios.h
index ec6e89c..0113cb2 100644
--- a/content/browser/renderer_host/render_widget_host_view_ios.h
+++ b/content/browser/renderer_host/render_widget_host_view_ios.h
@@ -178,6 +178,8 @@
   // RenderFrameMetadataProvider::Observer implementation.
   void OnRenderFrameMetadataChangedBeforeActivation(
       const cc::RenderFrameMetadata& metadata) override;
+  void OnRootScrollOffsetChanged(
+      const gfx::PointF& root_scroll_offset) override;
   void OnRenderFrameMetadataChangedAfterActivation(
       base::TimeTicks activation_time) override {}
   void OnRenderFrameSubmission() override {}
diff --git a/content/browser/renderer_host/render_widget_host_view_ios.mm b/content/browser/renderer_host/render_widget_host_view_ios.mm
index e40441a2..a2e3ebe 100644
--- a/content/browser/renderer_host/render_widget_host_view_ios.mm
+++ b/content/browser/renderer_host/render_widget_host_view_ios.mm
@@ -10,6 +10,7 @@
 
 #include "base/command_line.h"
 #include "build/ios_buildflags.h"
+#include "cc/mojom/render_frame_metadata.mojom-shared.h"
 #include "components/input/events_helper.h"
 #include "components/input/render_widget_host_input_event_router.h"
 #include "components/input/switches.h"
@@ -125,6 +126,10 @@
   }
 
   host()->render_frame_metadata_provider()->AddObserver(this);
+  host()
+      ->render_frame_metadata_provider()
+      ->UpdateRootScrollOffsetUpdateFrequency(
+          cc::mojom::RootScrollOffsetUpdateFrequency::kAllUpdates);
   host()->SetView(this);
 }
 
@@ -877,17 +882,13 @@
 }
 
 void RenderWidgetHostViewIOS::UpdateFrameBounds() {
-  // UIScrollView* scrollView = (UIScrollView*)[ui_view_->view_ superview];
-  gfx::PointF scrollOffset;
-  if (last_root_scroll_offset_) {
-    scrollOffset = *last_root_scroll_offset_;
-  }
-  CGRect parentBounds = [[ui_view_->view_ superview] bounds];
-  gfx::SizeF viewportSize(parentBounds.size);
+  const gfx::PointF scrollOffset =
+      last_root_scroll_offset_.value_or(gfx::PointF());
+  const CGRect parentBounds = [[ui_view_->view_ superview] bounds];
 
   CGRect frameBounds;
   frameBounds.origin = scrollOffset.ToCGPoint();
-  frameBounds.size = viewportSize.ToCGSize();
+  frameBounds.size = parentBounds.size;
 
   // If we are scrolling we don't resize the WebView immediately.
   if (!is_scrolling_ && !IsTesting()) {
@@ -922,6 +923,11 @@
   }
 }
 
+void RenderWidgetHostViewIOS::OnRootScrollOffsetChanged(
+    const gfx::PointF& root_scroll_offset) {
+  ApplyRootScrollOffsetChanged(root_scroll_offset, /*force=*/false);
+}
+
 void RenderWidgetHostViewIOS::ContentInsetChanged() {
   if (last_root_scroll_offset_) {
     ApplyRootScrollOffsetChanged(*last_root_scroll_offset_, /*force=*/true);
diff --git a/content/browser/renderer_host/view_transition_browsertest.cc b/content/browser/renderer_host/view_transition_browsertest.cc
index 972192f1..7e635422 100644
--- a/content/browser/renderer_host/view_transition_browsertest.cc
+++ b/content/browser/renderer_host/view_transition_browsertest.cc
@@ -13,6 +13,7 @@
 #include "content/browser/renderer_host/frame_tree_node.h"
 #include "content/browser/renderer_host/view_transition_opt_in_state.h"
 #include "content/browser/web_contents/web_contents_impl.h"
+#include "content/public/browser/browser_context.h"
 #include "content/public/browser/web_contents_observer.h"
 #include "content/public/test/back_forward_cache_util.h"
 #include "content/public/test/browser_test.h"
diff --git a/content/browser/shared_storage/shared_storage_header_observer_unittest.cc b/content/browser/shared_storage/shared_storage_header_observer_unittest.cc
index cdb24089..382da4d 100644
--- a/content/browser/shared_storage/shared_storage_header_observer_unittest.cc
+++ b/content/browser/shared_storage/shared_storage_header_observer_unittest.cc
@@ -20,6 +20,7 @@
 #include "base/test/test_future.h"
 #include "content/browser/navigation_or_document_handle.h"
 #include "content/browser/storage_partition_impl.h"
+#include "content/public/browser/browser_context.h"
 #include "content/public/browser/content_browser_client.h"
 #include "content/public/browser/global_routing_id.h"
 #include "content/public/common/content_client.h"
diff --git a/content/browser/site_instance_impl.cc b/content/browser/site_instance_impl.cc
index 1eea1bb..4645ecf 100644
--- a/content/browser/site_instance_impl.cc
+++ b/content/browser/site_instance_impl.cc
@@ -24,6 +24,7 @@
 #include "content/browser/storage_partition_impl.h"
 #include "content/common/content_navigation_policy.h"
 #include "content/common/features.h"
+#include "content/public/browser/browser_context.h"
 #include "content/public/browser/browser_or_resource_context.h"
 #include "content/public/browser/content_browser_client.h"
 #include "content/public/browser/site_isolation_policy.h"
diff --git a/content/browser/site_per_process_browsertest.cc b/content/browser/site_per_process_browsertest.cc
index 9d8869d..4aa2466 100644
--- a/content/browser/site_per_process_browsertest.cc
+++ b/content/browser/site_per_process_browsertest.cc
@@ -202,6 +202,7 @@
 #include "ui/android/view_android.h"
 #include "ui/android/window_android.h"
 #include "ui/events/android/event_handler_android.h"
+#include "ui/events/android/motion_event_android_factory.h"
 #include "ui/events/android/motion_event_android_java.h"
 #include "ui/events/motionevent_jni_headers/MotionEvent_jni.h"
 #include "ui/gfx/geometry/point_f.h"
@@ -9503,10 +9504,26 @@
       JNI_MotionEvent::Java_MotionEvent_obtain(
           env, /*downTime=*/0, /*eventTime=*/0, /*action=*/0, /*x=*/0, /*y=*/0,
           /*metaState=*/0);
-  ui::MotionEventAndroidJava event(env, obj, 1.f / root_view->GetDipScale(),
-                                   0.f, 0.f, 0.f, base::TimeTicks(), 0, 1, 0, 0,
-                                   0, 0, 0, 0, 0, false, &pointer0, nullptr);
-  root_view->OnTouchEventForTesting(event);
+  auto event = ui::MotionEventAndroidFactory::CreateFromJava(
+      env, obj,
+      /*pix_to_dip=*/1.f / root_view->GetDipScale(),
+      /*ticks_x=*/0.f,
+      /*ticks_y=*/0.f,
+      /*tick_multiplier=*/0.f,
+      /*oldest_event_time=*/base::TimeTicks(),
+      /*android_action=*/0,
+      /*pointer_count=*/1,
+      /*history_size=*/0,
+      /*action_index=*/0,
+      /*android_action_button=*/0,
+      /*android_gesture_classification=*/0,
+      /*android_button_state=*/0,
+      /*raw_offset_x_pixels=*/0,
+      /*raw_offset_y_pixels=*/0,
+      /*for_touch_handle=*/false,
+      /*pointer0=*/&pointer0,
+      /*pointer1=*/nullptr);
+  root_view->OnTouchEventForTesting(*event);
 
   EXPECT_TRUE(mock_handler.did_receive_event());
   EXPECT_FALSE(mock_handler_speculative.did_receive_event());
@@ -9847,11 +9864,26 @@
         JNI_MotionEvent::Java_MotionEvent_obtain(
             env, /*downTime=*/0, /*eventTime=*/0, /*action=*/0, /*x=*/0,
             /*y=*/0, /*metaState=*/0);
-    ui::MotionEventAndroidJava touch(
-        env, obj, 1.f, 0, 0, 0, base::TimeTicks::FromJavaNanoTime(time_ns),
-        ui::MotionEventAndroid::GetAndroidAction(action), 1, 0, 0, 0, 0, 0, 0,
-        0, false, &p, nullptr);
-    view->OnTouchEvent(touch);
+    auto touch = ui::MotionEventAndroidFactory::CreateFromJava(
+        env, obj,
+        /*pix_to_dip=*/1.f,
+        /*ticks_x=*/0,
+        /*ticks_y=*/0,
+        /*tick_multiplier=*/0,
+        /*oldest_event_time=*/base::TimeTicks::FromJavaNanoTime(time_ns),
+        /*android_action=*/ui::MotionEventAndroid::GetAndroidAction(action),
+        /*pointer_count=*/1,
+        /*history_size=*/0,
+        /*action_index=*/0,
+        /*android_action_button=*/0,
+        /*android_gesture_classification=*/0,
+        /*android_button_state=*/0,
+        /*raw_offset_x_pixels=*/0,
+        /*raw_offset_y_pixels=*/0,
+        /*for_touch_handle=*/false,
+        /*pointer0=*/&p,
+        /*pointer1=*/nullptr);
+    view->OnTouchEvent(*touch);
   }
 
   raw_ptr<RenderWidgetHostViewAndroid, DanglingUntriaged> root_rwhv_;
diff --git a/content/browser/site_per_process_hit_test_browsertest.cc b/content/browser/site_per_process_hit_test_browsertest.cc
index 8fae347ab..73f037d 100644
--- a/content/browser/site_per_process_hit_test_browsertest.cc
+++ b/content/browser/site_per_process_hit_test_browsertest.cc
@@ -78,6 +78,7 @@
 #include "base/android/jni_android.h"
 #include "content/browser/renderer_host/render_widget_host_view_android.h"
 #include "content/test/mock_overscroll_refresh_handler_android.h"
+#include "ui/events/android/motion_event_android_factory.h"
 #include "ui/events/android/motion_event_android_java.h"
 #include "ui/events/motionevent_jni_headers/MotionEvent_jni.h"
 #endif
@@ -806,7 +807,7 @@
 #endif
 
 #if BUILDFLAG(IS_ANDROID)
-ui::MotionEventAndroidJava GetMotionEventAndroid(
+std::unique_ptr<ui::MotionEventAndroid> GetMotionEventAndroid(
     ui::MotionEvent::Action action,
     base::TimeTicks event_time,
     base::TimeTicks down_time,
@@ -818,10 +819,27 @@
       JNI_MotionEvent::Java_MotionEvent_obtain(
           env, /*downTime=*/0, /*eventTime=*/0, /*action=*/0, /*x=*/0, /*y=*/0,
           /*metaState=*/0);
-  return ui::MotionEventAndroidJava(
-      env, obj, pix_to_dip, 0.f, 0.f, 0.f, event_time, event_time, down_time,
-      ui::MotionEventAndroid::GetAndroidAction(action), 1, 0, 0, 0, 0, 0, 0, 0,
-      false, &pointer, nullptr, false);
+  return ui::MotionEventAndroidFactory::CreateFromJava(
+      env, obj, pix_to_dip,
+      /*ticks_x=*/0.f,
+      /*ticks_y=*/0.f,
+      /*tick_multiplier=*/0.f,
+      /*oldest_event_time=*/event_time,
+      /*latest_event_time=*/event_time,
+      /*down_time_ms=*/down_time,
+      /*android_action=*/ui::MotionEventAndroid::GetAndroidAction(action),
+      /*pointer_count=*/1,
+      /*history_size=*/0,
+      /*action_index=*/0,
+      /*android_action_button=*/0,
+      /*android_gesture_classification=*/0,
+      /*android_button_state=*/0,
+      /*raw_offset_x_pixels=*/0,
+      /*raw_offset_y_pixels=*/0,
+      /*for_touch_handle=*/false,
+      /*pointer0=*/&pointer,
+      /*pointer1=*/nullptr,
+      /*is_latest_event_time_resampled=*/false);
 }
 #endif
 }  // namespace
@@ -2429,20 +2447,20 @@
   base::TimeTicks event_time = base::TimeTicks::Now();
   // Input event is being generated by moving in the same direction.
   ui::MotionEventAndroid::Pointer finger_pointer(0, 10.0f, 0, 0, 0, 0, 0, 0, 1);
-  ui::MotionEventAndroidJava event1 = GetMotionEventAndroid(
-      ui::MotionEvent::Action::MOVE, event_time, event_time, finger_pointer);
+  auto event1 = GetMotionEventAndroid(ui::MotionEvent::Action::MOVE, event_time,
+                                      event_time, finger_pointer);
 
   // Now we reverse direction.
   finger_pointer =
       ui::MotionEventAndroid::Pointer(0, -5.0f, 0, 0, 0, 0, 0, 0, 1);
-  ui::MotionEventAndroidJava event2 = GetMotionEventAndroid(
-      ui::MotionEvent::Action::MOVE, event_time, event_time, finger_pointer);
+  auto event2 = GetMotionEventAndroid(ui::MotionEvent::Action::MOVE, event_time,
+                                      event_time, finger_pointer);
 
-  ui::MotionEventAndroidJava end_event = GetMotionEventAndroid(
+  auto end_event = GetMotionEventAndroid(
       ui::MotionEvent::Action::UP, event_time, event_time, finger_pointer);
 
-  auto route_event = [&](ui::MotionEventAndroidJava& event) {
-    rwhv_android->OnTouchEvent(event);
+  auto route_event = [&](const std::unique_ptr<ui::MotionEventAndroid>& event) {
+    rwhv_android->OnTouchEvent(*event);
   };
 
 #endif
diff --git a/content/browser/webid/idp_network_request_manager.cc b/content/browser/webid/idp_network_request_manager.cc
index 0e310b0..159f5d1 100644
--- a/content/browser/webid/idp_network_request_manager.cc
+++ b/content/browser/webid/idp_network_request_manager.cc
@@ -23,6 +23,7 @@
 #include "content/browser/webid/flags.h"
 #include "content/browser/webid/identity_provider_info.h"
 #include "content/browser/webid/webid_utils.h"
+#include "content/public/browser/browser_context.h"
 #include "content/public/browser/render_frame_host.h"
 #include "content/public/browser/storage_partition.h"
 #include "content/public/browser/webid/constants.h"
diff --git a/content/common/gpu_pre_sandbox_hook_linux.cc b/content/common/gpu_pre_sandbox_hook_linux.cc
index 97f7221..2e53794 100644
--- a/content/common/gpu_pre_sandbox_hook_linux.cc
+++ b/content/common/gpu_pre_sandbox_hook_linux.cc
@@ -489,57 +489,6 @@
   }
 }
 
-std::vector<BrokerFilePermission> FilePermissionsForGpu(
-    const sandbox::policy::SandboxSeccompBPF::Options& options) {
-  // All GPU process policies need this file brokered out.
-  static const char kDriRcPath[] = "/etc/drirc";
-  std::vector<BrokerFilePermission> permissions = {
-      BrokerFilePermission::ReadOnly(kDriRcPath)};
-
-  AddVulkanICDPermissions(&permissions);
-
-  if (IsChromeOS()) {
-    // Permissions are additive, there can be multiple GPUs in the system.
-    AddStandardChromeOsPermissions(&permissions);
-    if (UseV4L2Codec(options))
-      AddV4L2GpuPermissions(&permissions, options);
-    if (IsArchitectureArm()) {
-      AddImgPvrGpuPermissions(&permissions);
-      AddArmGpuPermissions(&permissions);
-      // Add standard DRM permissions for snapdragon:
-      AddDrmGpuPermissions(&permissions);
-      // Following discrete GPUs can be plugged in via USB4 on ARM systems.
-    }
-    if (options.use_amd_specific_policies) {
-      AddAmdGpuPermissions(&permissions);
-    }
-    if (options.use_intel_specific_policies) {
-      AddIntelGpuPermissions(&permissions);
-    }
-    if (options.use_nvidia_specific_policies) {
-      AddStandardGpuPermissions(&permissions);
-      AddNvidiaGpuPermissions(&permissions);
-    }
-    if (options.use_virtio_specific_policies) {
-      AddVirtIOGpuPermissions(&permissions);
-    }
-    return permissions;
-  }
-
-  if (UseChromecastSandboxAllowlist()) {
-    if (UseV4L2Codec(options))
-      AddV4L2GpuPermissions(&permissions, options);
-
-    if (IsArchitectureArm()) {
-      AddChromecastArmGpuPermissions(&permissions);
-      return permissions;
-    }
-  }
-
-  AddStandardGpuPermissions(&permissions);
-  return permissions;
-}
-
 void LoadArmGpuLibraries() {
 #if BUILDFLAG(IS_CHROMEOS) && defined(ARCH_CPU_ARM_FAMILY)
   // This environmental variable needs to be set before we load libMali if we
@@ -652,6 +601,77 @@
   }
 }
 
+}  // namespace
+
+sandbox::syscall_broker::BrokerCommandSet CommandSetForGPU(
+    const sandbox::policy::SandboxLinux::Options& options) {
+  sandbox::syscall_broker::BrokerCommandSet command_set;
+  command_set.set(sandbox::syscall_broker::COMMAND_ACCESS);
+  command_set.set(sandbox::syscall_broker::COMMAND_OPEN);
+  command_set.set(sandbox::syscall_broker::COMMAND_STAT);
+  if (IsChromeOS() &&
+      (options.use_amd_specific_policies ||
+       options.use_intel_specific_policies ||
+       options.use_nvidia_specific_policies ||
+       options.use_virtio_specific_policies || IsArchitectureArm())) {
+    command_set.set(sandbox::syscall_broker::COMMAND_READLINK);
+  }
+  return command_set;
+}
+
+std::vector<BrokerFilePermission> FilePermissionsForGpu(
+    const sandbox::policy::SandboxSeccompBPF::Options& options) {
+  // All GPU process policies need this file brokered out.
+  static const char kDriRcPath[] = "/etc/drirc";
+  std::vector<BrokerFilePermission> permissions = {
+      BrokerFilePermission::ReadOnly(kDriRcPath)};
+
+  AddVulkanICDPermissions(&permissions);
+
+  if (IsChromeOS()) {
+    // Permissions are additive, there can be multiple GPUs in the system.
+    AddStandardChromeOsPermissions(&permissions);
+    if (UseV4L2Codec(options)) {
+      AddV4L2GpuPermissions(&permissions, options);
+    }
+    if (IsArchitectureArm()) {
+      AddImgPvrGpuPermissions(&permissions);
+      AddArmGpuPermissions(&permissions);
+      // Add standard DRM permissions for snapdragon:
+      AddDrmGpuPermissions(&permissions);
+      // Following discrete GPUs can be plugged in via USB4 on ARM systems.
+    }
+    if (options.use_amd_specific_policies) {
+      AddAmdGpuPermissions(&permissions);
+    }
+    if (options.use_intel_specific_policies) {
+      AddIntelGpuPermissions(&permissions);
+    }
+    if (options.use_nvidia_specific_policies) {
+      AddStandardGpuPermissions(&permissions);
+      AddNvidiaGpuPermissions(&permissions);
+    }
+    if (options.use_virtio_specific_policies) {
+      AddVirtIOGpuPermissions(&permissions);
+    }
+    return permissions;
+  }
+
+  if (UseChromecastSandboxAllowlist()) {
+    if (UseV4L2Codec(options)) {
+      AddV4L2GpuPermissions(&permissions, options);
+    }
+
+    if (IsArchitectureArm()) {
+      AddChromecastArmGpuPermissions(&permissions);
+      return permissions;
+    }
+  }
+
+  AddStandardGpuPermissions(&permissions);
+  return permissions;
+}
+
 bool LoadLibrariesForGpu(
     const sandbox::policy::SandboxSeccompBPF::Options& options) {
   LoadVulkanLibraries();
@@ -675,24 +695,6 @@
   return true;
 }
 
-sandbox::syscall_broker::BrokerCommandSet CommandSetForGPU(
-    const sandbox::policy::SandboxLinux::Options& options) {
-  sandbox::syscall_broker::BrokerCommandSet command_set;
-  command_set.set(sandbox::syscall_broker::COMMAND_ACCESS);
-  command_set.set(sandbox::syscall_broker::COMMAND_OPEN);
-  command_set.set(sandbox::syscall_broker::COMMAND_STAT);
-  if (IsChromeOS() &&
-      (options.use_amd_specific_policies ||
-       options.use_intel_specific_policies ||
-       options.use_nvidia_specific_policies ||
-       options.use_virtio_specific_policies || IsArchitectureArm())) {
-    command_set.set(sandbox::syscall_broker::COMMAND_READLINK);
-  }
-  return command_set;
-}
-
-}  // namespace
-
 bool GpuPreSandboxHook(sandbox::policy::SandboxLinux::Options options) {
   sandbox::policy::SandboxLinux::GetInstance()->StartBrokerProcess(
       CommandSetForGPU(options), FilePermissionsForGpu(options), options);
diff --git a/content/common/gpu_pre_sandbox_hook_linux.h b/content/common/gpu_pre_sandbox_hook_linux.h
index 153c463..764a5502 100644
--- a/content/common/gpu_pre_sandbox_hook_linux.h
+++ b/content/common/gpu_pre_sandbox_hook_linux.h
@@ -5,16 +5,38 @@
 #ifndef CONTENT_COMMON_GPU_PRE_SANDBOX_HOOK_LINUX_H_
 #define CONTENT_COMMON_GPU_PRE_SANDBOX_HOOK_LINUX_H_
 
-#include "base/component_export.h"
+#include <vector>
+
 #include "sandbox/policy/linux/sandbox_linux.h"
 
+namespace sandbox::syscall_broker {
+class BrokerFilePermission;
+}  // namespace sandbox::syscall_broker
+
 namespace content {
 
 // A pre-sandbox hook to use on Linux-based systems in sandboxed processes that
 // require general GPU usage.
-COMPONENT_EXPORT(GPU_PRE_SANDBOX_HOOK)
 bool GpuPreSandboxHook(sandbox::policy::SandboxLinux::Options options);
 
+// These functions can be used by other pre-sandbox hooks that need GPU access
+// in addition to their other permissions.
+
+// Returns the set of commands (open, stat, unlink, rename, etc...) that are
+// needed for a process to use the GPU.
+sandbox::syscall_broker::BrokerCommandSet CommandSetForGPU(
+    const sandbox::policy::SandboxLinux::Options& options);
+
+// Returns a list of file permissions that are needed for a process to use
+// the GPU. These will include the libraries that make up the graphics driver.
+std::vector<sandbox::syscall_broker::BrokerFilePermission>
+FilePermissionsForGpu(
+    const sandbox::policy::SandboxSeccompBPF::Options& options);
+
+// Loads the libraries needed for a process to use the GPU.
+bool LoadLibrariesForGpu(
+    const sandbox::policy::SandboxSeccompBPF::Options& options);
+
 }  // namespace content
 
 #endif  // CONTENT_COMMON_GPU_PRE_SANDBOX_HOOK_LINUX_H_
diff --git a/content/public/browser/render_frame_metadata_provider.h b/content/public/browser/render_frame_metadata_provider.h
index ed82504..df57551 100644
--- a/content/public/browser/render_frame_metadata_provider.h
+++ b/content/public/browser/render_frame_metadata_provider.h
@@ -42,7 +42,7 @@
     // to pass in Viz.
     virtual void OnLocalSurfaceIdChanged(
         const cc::RenderFrameMetadata& metadata) = 0;
-#if BUILDFLAG(IS_ANDROID)
+#if BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_IOS)
     virtual void OnRootScrollOffsetChanged(
         const gfx::PointF& root_scroll_offset) {}
 #endif
diff --git a/content/test/content_test_bundle_data.filelist b/content/test/content_test_bundle_data.filelist
index 38c5f49..aeda6ecc 100644
--- a/content/test/content_test_bundle_data.filelist
+++ b/content/test/content_test_bundle_data.filelist
@@ -7843,6 +7843,7 @@
 data/media/peerconnection-source-feed-switch.html
 data/media/picture_in_picture/audio-and-canvas.html
 data/media/picture_in_picture/canvas-in-pip.html
+data/media/picture_in_picture/iframe-one-video.html
 data/media/picture_in_picture/one-video.html
 data/media/picture_in_picture/two-videos.html
 data/media/session/audio-1second.ogg
diff --git a/content/test/data/gpu/pixel_destroyed_webgpu_canvas.html b/content/test/data/gpu/pixel_destroyed_webgpu_canvas.html
index df549de..8f8e6009 100644
--- a/content/test/data/gpu/pixel_destroyed_webgpu_canvas.html
+++ b/content/test/data/gpu/pixel_destroyed_webgpu_canvas.html
@@ -25,9 +25,17 @@
 If there are three images, they are the source canvas, and two readbacks.
 If there are two images, they are the two readbacks.
 
+The only exception are CaptureStream related tests. CaptureStream tests have
+two images, the first one is source canvas, the second one is the readback after
+device.destroy(). There is no readback after device.lost promise resolves.
+
 The source canvas including onscreen canvas, offscreen canvas and
 onscreen canvas that transferred control to OffScreenCanvas object.
 
+All tests include three types of source canvas except for the following:
+- ToDataURL and CaptureStream are only tested for onscreen canvas.
+- TransferToImageBitmap is only tested for offscreen canvas.
+
 The source canvas should be either green/red or blank.
 Both readbacks should always be blank.
 
diff --git a/content/test/data/media/picture_in_picture/iframe-one-video.html b/content/test/data/media/picture_in_picture/iframe-one-video.html
new file mode 100644
index 0000000..4096f99
--- /dev/null
+++ b/content/test/data/media/picture_in_picture/iframe-one-video.html
@@ -0,0 +1,33 @@
+<html>
+
+<head>
+    <title>Iframe - One Video</title>
+</head>
+
+<body>
+    <iframe width="100%" height="100%"
+        src="/cross-site/b.com/media/picture_in_picture/one-video.html"
+        id="iframe" onload="document.title='iframe loaded'"></iframe>
+
+    <script>
+        const iframe = document.getElementById('iframe');
+
+        async function enterPictureInPicture() {
+            const promise = new Promise(resolve => {
+                const listener = (event) => {
+                    if (event.source === iframe.contentWindow && event.data.event === 'enterpictureinpicture') {
+                        window.removeEventListener('message', listener);
+                        resolve(true);
+                    }
+                };
+                window.addEventListener('message', listener);
+            });
+            // Send a `postMessage` to b.com to enter picture in picture.
+            iframe.contentWindow.postMessage({ action: 'enterpictureinpicture' }, '*');
+            return promise;
+        }
+
+    </script>
+</body>
+
+</html>
diff --git a/content/test/data/media/picture_in_picture/one-video.html b/content/test/data/media/picture_in_picture/one-video.html
index 1c52ad4e..bf8830f7 100644
--- a/content/test/data/media/picture_in_picture/one-video.html
+++ b/content/test/data/media/picture_in_picture/one-video.html
@@ -10,9 +10,19 @@
   <script>
     const video = document.querySelector('video');
 
+    window.addEventListener('message', event => {
+      if (event.data.action === 'enterpictureinpicture') {
+        enterPictureInPicture();
+      }
+    });
+
     function addPictureInPictureEventListeners() {
       video.addEventListener('enterpictureinpicture', _ => {
         document.title = 'enterpictureinpicture';
+
+        if (window.parent !== window) {
+          window.parent.postMessage({ event: 'enterpictureinpicture' }, '*');
+        }
       });
       video.addEventListener('leavepictureinpicture', _ => {
         document.title = 'leavepictureinpicture';
diff --git a/content/utility/BUILD.gn b/content/utility/BUILD.gn
index 5ff547b6..b1d9395 100644
--- a/content/utility/BUILD.gn
+++ b/content/utility/BUILD.gn
@@ -52,6 +52,7 @@
     "//content/public/common:common_sources",
     "//content/public/common:content_descriptor_keys",
     "//content/services/auction_worklet",
+    "//content/utility/on_device_model:on_device_model_sandbox_init",
     "//device/vr/buildflags",
     "//media:media_buildflags",
     "//mojo/public/cpp/bindings",
diff --git a/content/utility/on_device_model/BUILD.gn b/content/utility/on_device_model/BUILD.gn
new file mode 100644
index 0000000..1b55e53f3
--- /dev/null
+++ b/content/utility/on_device_model/BUILD.gn
@@ -0,0 +1,35 @@
+# Copyright 2025 The Chromium Authors
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import("//services/on_device_model/on_device_model.gni")
+
+source_set("on_device_model_sandbox_init") {
+  sources = [
+    "on_device_model_sandbox_init.cc",
+    "on_device_model_sandbox_init.h",
+  ]
+  deps = [
+    "//base",
+    "//services/on_device_model:on_device_model_service",
+  ]
+  defines = []
+  if (enable_ml_internal) {
+    deps += [ "//services/on_device_model/ml" ]
+    defines += [ "ENABLE_ML_INTERNAL" ]
+  }
+  if (is_linux || is_chromeos) {
+    deps += [
+      "//content/common",
+      "//gpu/config",
+      "//sandbox/policy",
+    ]
+  }
+  if (!is_fuchsia) {
+    deps += [
+      "//third_party/dawn/include/dawn:cpp_headers",
+      "//third_party/dawn/src/dawn:proc",
+      "//third_party/dawn/src/dawn/native",
+    ]
+  }
+}
diff --git a/content/utility/on_device_model/DEPS b/content/utility/on_device_model/DEPS
new file mode 100644
index 0000000..161f9ca
--- /dev/null
+++ b/content/utility/on_device_model/DEPS
@@ -0,0 +1,4 @@
+include_rules = [
+  "+services/on_device_model/ml",
+  "+third_party/dawn/include",
+]
diff --git a/content/utility/on_device_model/DIR_METADATA b/content/utility/on_device_model/DIR_METADATA
new file mode 100644
index 0000000..7db664b
--- /dev/null
+++ b/content/utility/on_device_model/DIR_METADATA
@@ -0,0 +1,3 @@
+buganizer {
+    component_id: 1430831
+}
diff --git a/content/utility/on_device_model/OWNERS b/content/utility/on_device_model/OWNERS
new file mode 100644
index 0000000..2fe07dd
--- /dev/null
+++ b/content/utility/on_device_model/OWNERS
@@ -0,0 +1,2 @@
+per-file on_device_model_sandbox_init.*=set noparent
+per-file on_device_model_sandbox_init.*=file://sandbox/linux/OWNERS
diff --git a/services/on_device_model/pre_sandbox_init.cc b/content/utility/on_device_model/on_device_model_sandbox_init.cc
similarity index 80%
rename from services/on_device_model/pre_sandbox_init.cc
rename to content/utility/on_device_model/on_device_model_sandbox_init.cc
index 88daa90b..b7f6e706 100644
--- a/services/on_device_model/pre_sandbox_init.cc
+++ b/content/utility/on_device_model/on_device_model_sandbox_init.cc
@@ -2,24 +2,30 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#include "content/utility/on_device_model/on_device_model_sandbox_init.h"
+
 #include "base/base_paths.h"
 #include "base/files/file_path.h"
 #include "base/logging.h"
 #include "base/native_library.h"
 #include "base/path_service.h"
 #include "build/build_config.h"
-#include "services/on_device_model/on_device_model_service.h"
 
 #if defined(ENABLE_ML_INTERNAL)
-#include "services/on_device_model/ml/chrome_ml.h"  // nogncheck
+#include "services/on_device_model/ml/chrome_ml.h"      // nogncheck
 #include "services/on_device_model/ml/gpu_blocklist.h"  // nogncheck
 #endif
 
 #if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
-#include "gpu/config/gpu_info_collector.h"                    // nogncheck
+#include <errno.h>
+
+#include "content/common/gpu_pre_sandbox_hook_linux.h"
+#include "gpu/config/gpu_info_collector.h"  // nogncheck
+#include "sandbox/policy/linux/sandbox_linux.h"
 #endif
 
 #if !BUILDFLAG(IS_FUCHSIA)
+#include "base/feature_list.h"
 #include "third_party/dawn/include/dawn/dawn_proc.h"          // nogncheck
 #include "third_party/dawn/include/dawn/native/DawnNative.h"  // nogncheck
 #include "third_party/dawn/include/dawn/webgpu_cpp.h"         // nogncheck
@@ -73,8 +79,7 @@
 
 }  // namespace
 
-// static
-bool OnDeviceModelService::PreSandboxInit() {
+bool PreSandboxInit() {
 #if defined(ENABLE_ML_INTERNAL)
   // Ensure the library is loaded before the sandbox is initialized.
   if (!ml::ChromeML::Get()) {
@@ -133,9 +138,7 @@
 }
 
 #if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
-// static
-void OnDeviceModelService::AddSandboxLinuxOptions(
-    sandbox::policy::SandboxLinux::Options& options) {
+void AddSandboxLinuxOptions(sandbox::policy::SandboxLinux::Options& options) {
   // Make sure any necessary vendor-specific options are set.
   gpu::GPUInfo info;
   gpu::CollectBasicGraphicsInfo(&info);
@@ -144,10 +147,27 @@
     UpdateSandboxOptionsForGpu(gpu, options);
   }
 }
+
+bool PreSandboxHook(sandbox::policy::SandboxLinux::Options options) {
+  std::vector<sandbox::syscall_broker::BrokerFilePermission> file_permissions =
+      content::FilePermissionsForGpu(options);
+  file_permissions.push_back(
+      sandbox::syscall_broker::BrokerFilePermission::ReadOnly(
+          "/sys/devices/system/cpu/online"));
+
+  sandbox::policy::SandboxLinux::GetInstance()->StartBrokerProcess(
+      content::CommandSetForGPU(options), file_permissions, options);
+
+  if (!content::LoadLibrariesForGpu(options)) {
+    return false;
+  }
+
+  errno = 0;
+  return true;
+}
 #endif
 
-// static
-bool OnDeviceModelService::Shutdown() {
+bool Shutdown() {
   return true;
 }
 
diff --git a/content/utility/on_device_model/on_device_model_sandbox_init.h b/content/utility/on_device_model/on_device_model_sandbox_init.h
new file mode 100644
index 0000000..3d2b1d38
--- /dev/null
+++ b/content/utility/on_device_model/on_device_model_sandbox_init.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 CONTENT_UTILITY_ON_DEVICE_MODEL_ON_DEVICE_MODEL_SANDBOX_INIT_H_
+#define CONTENT_UTILITY_ON_DEVICE_MODEL_ON_DEVICE_MODEL_SANDBOX_INIT_H_
+
+#include "build/build_config.h"
+
+#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
+#include "sandbox/policy/linux/sandbox_linux.h"
+#endif
+
+namespace on_device_model {
+
+// Must be called in the service's process before sandbox initialization.
+// These are defined separately in pre_sandbox_init.cc for explicit security
+// review coverage.
+[[nodiscard]] bool PreSandboxInit();
+
+// Must be called in the service's process after the run loop finished.
+[[nodiscard]] bool Shutdown();
+
+#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
+void AddSandboxLinuxOptions(sandbox::policy::SandboxLinux::Options& options);
+
+[[nodiscard]] bool PreSandboxHook(
+    sandbox::policy::SandboxLinux::Options options);
+#endif
+
+}  // namespace on_device_model
+
+#endif  // CONTENT_UTILITY_ON_DEVICE_MODEL_ON_DEVICE_MODEL_SANDBOX_INIT_H_
diff --git a/content/utility/services.cc b/content/utility/services.cc
index 921dab3..e95871d 100644
--- a/content/utility/services.cc
+++ b/content/utility/services.cc
@@ -32,6 +32,7 @@
 #include "services/data_decoder/data_decoder_service.h"
 #include "services/network/network_service.h"
 #include "services/on_device_model/on_device_model_service.h"
+#include "services/on_device_model/public/mojom/on_device_model_service.mojom.h"
 #include "services/tracing/public/mojom/tracing_service.mojom.h"
 #include "services/tracing/tracing_service.h"
 #include "services/video_capture/public/mojom/video_capture_service.mojom.h"
diff --git a/content/utility/speech/OWNERS b/content/utility/speech/OWNERS
index 6ee8a6f..014c2e32 100644
--- a/content/utility/speech/OWNERS
+++ b/content/utility/speech/OWNERS
@@ -1 +1,2 @@
+per-file speech_recognition_sandbox_hook_linux.*=set noparent
 per-file speech_recognition_sandbox_hook_linux.*=file://sandbox/linux/OWNERS
diff --git a/content/utility/utility_main.cc b/content/utility/utility_main.cc
index 490e66c..9cc954c9 100644
--- a/content/utility/utility_main.cc
+++ b/content/utility/utility_main.cc
@@ -29,12 +29,13 @@
 #include "content/public/common/content_switches.h"
 #include "content/public/common/main_function_params.h"
 #include "content/public/utility/content_utility_client.h"
+#include "content/utility/on_device_model/on_device_model_sandbox_init.h"
 #include "content/utility/utility_thread_impl.h"
 #include "printing/buildflags/buildflags.h"
 #include "sandbox/policy/mojom/sandbox.mojom.h"
 #include "sandbox/policy/sandbox.h"
 #include "sandbox/policy/sandbox_type.h"
-#include "services/on_device_model/on_device_model_service.h"
+#include "services/on_device_model/public/mojom/on_device_model_service.mojom.h"
 #include "services/tracing/public/cpp/trace_startup.h"
 #include "services/video_effects/public/cpp/buildflags.h"
 
@@ -281,7 +282,7 @@
   }
 
   if (utility_sub_type == on_device_model::mojom::OnDeviceModelService::Name_) {
-    CHECK(on_device_model::OnDeviceModelService::PreSandboxInit());
+    CHECK(on_device_model::PreSandboxInit());
   }
 
 #if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
@@ -324,9 +325,8 @@
       pre_sandbox_hook = base::BindOnce(&audio::AudioPreSandboxHook);
       break;
     case sandbox::mojom::Sandbox::kOnDeviceModelExecution:
-      on_device_model::OnDeviceModelService::AddSandboxLinuxOptions(
-          sandbox_options);
-      pre_sandbox_hook = base::BindOnce(&GpuPreSandboxHook);
+      on_device_model::AddSandboxLinuxOptions(sandbox_options);
+      pre_sandbox_hook = base::BindOnce(&on_device_model::PreSandboxHook);
       break;
     case sandbox::mojom::Sandbox::kSpeechRecognition:
       pre_sandbox_hook =
@@ -507,7 +507,7 @@
   run_loop.Run();
 
   if (utility_sub_type == on_device_model::mojom::OnDeviceModelService::Name_) {
-    CHECK(on_device_model::OnDeviceModelService::Shutdown());
+    CHECK(on_device_model::Shutdown());
   }
 
 #if defined(LEAK_SANITIZER)
diff --git a/extensions/browser/api/sockets_tcp_server/tcp_server_socket_event_dispatcher.cc b/extensions/browser/api/sockets_tcp_server/tcp_server_socket_event_dispatcher.cc
index 8e86126..b0053622 100644
--- a/extensions/browser/api/sockets_tcp_server/tcp_server_socket_event_dispatcher.cc
+++ b/extensions/browser/api/sockets_tcp_server/tcp_server_socket_event_dispatcher.cc
@@ -8,6 +8,7 @@
 
 #include "base/functional/bind.h"
 #include "base/lazy_instance.h"
+#include "content/public/browser/browser_context.h"
 #include "content/public/browser/browser_thread.h"
 #include "extensions/browser/api/socket/tcp_socket.h"
 #include "extensions/browser/event_router.h"
diff --git a/extensions/browser/api/sockets_udp/sockets_udp_apitest.cc b/extensions/browser/api/sockets_udp/sockets_udp_apitest.cc
index b73d761b..17e49ab 100644
--- a/extensions/browser/api/sockets_udp/sockets_udp_apitest.cc
+++ b/extensions/browser/api/sockets_udp/sockets_udp_apitest.cc
@@ -5,6 +5,7 @@
 #include "base/memory/ref_counted.h"
 #include "base/strings/stringprintf.h"
 #include "build/build_config.h"
+#include "content/public/browser/browser_context.h"
 #include "content/public/browser/storage_partition.h"
 #include "extensions/browser/api/socket/write_quota_checker.h"
 #include "extensions/browser/api/sockets_udp/sockets_udp_api.h"
diff --git a/extensions/common/api/alarms.webidl b/extensions/common/api/alarms.webidl
new file mode 100644
index 0000000..0437499
--- /dev/null
+++ b/extensions/common/api/alarms.webidl
@@ -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.
+
+dictionary Alarm {
+  // Name of this alarm.
+  DOMString name;
+
+  // Time at which this alarm was scheduled to fire, in milliseconds past the
+  // epoch (e.g. <code>Date.now() + n</code>).  For performance reasons, the
+  // alarm may have been delayed an arbitrary amount beyond this.
+  double scheduledTime;
+
+  // If not null, the alarm is a repeating alarm and will fire again in
+  // <var>periodInMinutes</var> minutes.
+  double? periodInMinutes;
+};
+
+// TODO(mpcomplete): rename to CreateInfo when http://crbug.com/123073 is
+// fixed.
+dictionary AlarmCreateInfo {
+  // Time at which the alarm should fire, in milliseconds past the epoch
+  // (e.g. <code>Date.now() + n</code>).
+  double? when;
+
+  // Length of time in minutes after which the <code>onAlarm</code> event
+  // should fire.
+  //
+  // <!-- TODO: need minimum=0 -->
+  double? delayInMinutes;
+
+  // If set, the onAlarm event should fire every <var>periodInMinutes</var>
+  // minutes after the initial event specified by <var>when</var> or
+  // <var>delayInMinutes</var>.  If not set, the alarm will only fire once.
+  //
+  // <!-- TODO: need minimum=0 -->
+  double? periodInMinutes;
+};
+
+// Listener callback for the onAlarm event.
+// |alarm|: The alarm that has elapsed.
+callback OnAlarmListener = undefined (Alarm alarm);
+
+interface OnAlarmEvent : ExtensionEvent {
+  static undefined addListener(OnAlarmListener listener);
+  static undefined removeListener(OnAlarmListener listener);
+  static boolean hasListener(OnAlarmListener listener);
+};
+
+// Use the <code>chrome.alarms</code> API to schedule code to run
+// periodically or at a specified time in the future.
+interface Alarms {
+  // Creates an alarm.  Near the time(s) specified by <var>alarmInfo</var>,
+  // the <code>onAlarm</code> event is fired. If there is another alarm with
+  // the same name (or no name if none is specified), it will be cancelled and
+  // replaced by this alarm.
+  //
+  // In order to reduce the load on the user's machine, Chrome limits alarms
+  // to at most once every 30 seconds but may delay them an arbitrary amount
+  // more.  That is, setting <code>delayInMinutes</code> or
+  // <code>periodInMinutes</code> to less than <code>0.5</code> will not be
+  // honored and will cause a warning.  <code>when</code> can be set to less
+  // than 30 seconds after "now" without warning but won't actually cause the
+  // alarm to fire for at least 30 seconds.
+  //
+  // To help you debug your app or extension, when you've loaded it unpacked,
+  // there's no limit to how often the alarm can fire.
+  //
+  // |name|: Optional name to identify this alarm. Defaults to the empty
+  // string.
+  // |alarmInfo|: Describes when the alarm should fire.  The initial time must
+  // be specified by either <var>when</var> or <var>delayInMinutes</var> (but
+  // not both).  If <var>periodInMinutes</var> is set, the alarm will repeat
+  // every <var>periodInMinutes</var> minutes after the initial event.  If
+  // neither <var>when</var> or <var>delayInMinutes</var> is set for a
+  // repeating alarm, <var>periodInMinutes</var> is used as the default for
+  // <var>delayInMinutes</var>.
+  // |Returns|: Invoked when the alarm has been created.
+  static Promise<undefined> create(
+      optional DOMString name,
+      AlarmCreateInfo alarmInfo);
+
+  // Retrieves details about the specified alarm.
+  // |name|: The name of the alarm to get. Defaults to the empty string.
+  // |PromiseValue|: alarm
+  [requiredCallback] static Promise<Alarm?> get(optional DOMString name);
+
+  // Gets an array of all the alarms.
+  // |PromiseValue|: alarms
+  [requiredCallback] static Promise<sequence<Alarm>> getAll();
+
+  // Clears the alarm with the given name.
+  // |name|: The name of the alarm to clear. Defaults to the empty string.
+  // |PromiseValue|: wasCleared
+  static Promise<boolean> clear(optional DOMString name);
+
+  // Clears all alarms.
+  // |PromiseValue|: wasCleared
+  static Promise<boolean> clearAll();
+
+  // Fired when an alarm has elapsed. Useful for event pages.
+  static attribute OnAlarmEvent onAlarm;
+};
+
+partial interface Browser {
+  static attribute Alarms alarms;
+};
diff --git a/extensions/common/api/schema.gni b/extensions/common/api/schema.gni
index b38e3d3..ef7750e 100644
--- a/extensions/common/api/schema.gni
+++ b/extensions/common/api/schema.gni
@@ -9,7 +9,7 @@
 # desktop-android build.
 # TODO(https://crbug.com/356905053): Continue moving more here.
 extensions_api_schema_files_ = [
-  "alarms.idl",
+  "alarms.webidl",
   "declarative_net_request.idl",
   "dns.idl",
   "events.json",
diff --git a/extensions/common/mojom/event_dispatcher.mojom b/extensions/common/mojom/event_dispatcher.mojom
index 97d873ce..82ef0da4e 100644
--- a/extensions/common/mojom/event_dispatcher.mojom
+++ b/extensions/common/mojom/event_dispatcher.mojom
@@ -18,6 +18,9 @@
 // script. It is should always be false for service workers.
 interface EventDispatcher {
     // Sent to the renderer to dispatch an event to a listener.
+    // TODO(crbug.com/380898785): remove the UnlimitedSize tag once
+    // the responsible extensions are identified.
+    [UnlimitedSize]
     DispatchEvent(DispatchEventParams params,
                   mojo_base.mojom.ListValue event_args) => (
                     // TODO(crbug.com/40277737): Remove this variable once
diff --git a/gpu/command_buffer/service/graphite_shared_context_unittest.cc b/gpu/command_buffer/service/graphite_shared_context_unittest.cc
index 939376a..59e0089 100644
--- a/gpu/command_buffer/service/graphite_shared_context_unittest.cc
+++ b/gpu/command_buffer/service/graphite_shared_context_unittest.cc
@@ -66,7 +66,7 @@
     dawn::native::Instance dawn_instance(&instance_desc);
 
     wgpu::RequestAdapterOptions options = {};
-#if BUILDFLAG(IS_MAC)
+#if BUILDFLAG(IS_APPLE)
     options.backendType = wgpu::BackendType::Metal;
 #elif BUILDFLAG(IS_WIN)
     options.backendType = wgpu::BackendType::D3D11;
diff --git a/gpu/command_buffer/service/webgpu_decoder_impl.cc b/gpu/command_buffer/service/webgpu_decoder_impl.cc
index 664493de..c9b5b591 100644
--- a/gpu/command_buffer/service/webgpu_decoder_impl.cc
+++ b/gpu/command_buffer/service/webgpu_decoder_impl.cc
@@ -2275,6 +2275,8 @@
     return error::kInvalidArguments;
   }
 
+  bool is_device_lost = dawn::native::IsDeviceLost(device.Get());
+
   Mailbox mailbox = it->second->mailbox();
   wgpu::Texture texture = it->second->texture();
   DCHECK(texture);
@@ -2283,8 +2285,10 @@
 
   associated_shared_image_map_.erase(it);
   // The compositor renders uninitialized textures as red. If the texture is
-  // not initialized, we need to explicitly clear its contents to black.
-  if (!is_initialized && !ClearSharedImageWithSkia(mailbox)) {
+  // not initialized or gpu device is lost, we need to explicitly clear its
+  // contents to black.
+  bool clear_shared_image = !is_initialized || is_device_lost;
+  if (clear_shared_image && !ClearSharedImageWithSkia(mailbox)) {
     return error::kInvalidArguments;
   }
   return error::kNoError;
diff --git a/infra/config/generated/builders/ci/ios18-sdk-simulator/targets/chromium.fyi.json b/infra/config/generated/builders/ci/ios18-sdk-simulator/targets/chromium.fyi.json
index 1b35b0b7..e89a078 100644
--- a/infra/config/generated/builders/ci/ios18-sdk-simulator/targets/chromium.fyi.json
+++ b/infra/config/generated/builders/ci/ios18-sdk-simulator/targets/chromium.fyi.json
@@ -1129,6 +1129,150 @@
         "merge": {
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
+        "name": "ios_chrome_unittests iPad Air (5th generation) 26.0",
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
+        "swarming": {
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/mac_toolchain/${platform}",
+              "location": ".",
+              "revision": "git_revision:4c7290150d1c360cecc6a93c0214dc531585c3ab"
+            }
+          ],
+          "dimensions": {
+            "cpu": "arm64",
+            "os": "Mac-15.5"
+          },
+          "named_caches": [
+            {
+              "name": "runtime_ios_26_0",
+              "path": "Runtime-ios-26.0"
+            },
+            {
+              "name": "xcode_ios_17a5241o",
+              "path": "Xcode.app"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "ios_chrome_unittests",
+        "test_id_prefix": "ninja://ios/chrome/test:ios_chrome_unittests/",
+        "variant_id": "iPad Air (5th generation) 26.0"
+      },
+      {
+        "args": [
+          "--platform",
+          "iPhone 14",
+          "--version",
+          "26.0",
+          "--out-dir",
+          "${ISOLATED_OUTDIR}",
+          "--xcode-build-version",
+          "17a5241o",
+          "--xctest"
+        ],
+        "merge": {
+          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+        },
+        "name": "ios_chrome_unittests iPhone 14 26.0",
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
+        "swarming": {
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/mac_toolchain/${platform}",
+              "location": ".",
+              "revision": "git_revision:4c7290150d1c360cecc6a93c0214dc531585c3ab"
+            }
+          ],
+          "dimensions": {
+            "cpu": "arm64",
+            "os": "Mac-15.5"
+          },
+          "named_caches": [
+            {
+              "name": "runtime_ios_26_0",
+              "path": "Runtime-ios-26.0"
+            },
+            {
+              "name": "xcode_ios_17a5241o",
+              "path": "Xcode.app"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "ios_chrome_unittests",
+        "test_id_prefix": "ninja://ios/chrome/test:ios_chrome_unittests/",
+        "variant_id": "iPhone 14 26.0"
+      },
+      {
+        "args": [
+          "--platform",
+          "iPhone SE (3rd generation)",
+          "--version",
+          "26.0",
+          "--out-dir",
+          "${ISOLATED_OUTDIR}",
+          "--xcode-build-version",
+          "17a5241o",
+          "--xctest"
+        ],
+        "merge": {
+          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+        },
+        "name": "ios_chrome_unittests iPhone SE (3rd generation) 26.0",
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
+        "swarming": {
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/mac_toolchain/${platform}",
+              "location": ".",
+              "revision": "git_revision:4c7290150d1c360cecc6a93c0214dc531585c3ab"
+            }
+          ],
+          "dimensions": {
+            "cpu": "arm64",
+            "os": "Mac-15.5"
+          },
+          "named_caches": [
+            {
+              "name": "runtime_ios_26_0",
+              "path": "Runtime-ios-26.0"
+            },
+            {
+              "name": "xcode_ios_17a5241o",
+              "path": "Xcode.app"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "ios_chrome_unittests",
+        "test_id_prefix": "ninja://ios/chrome/test:ios_chrome_unittests/",
+        "variant_id": "iPhone SE (3rd generation) 26.0"
+      },
+      {
+        "args": [
+          "--platform",
+          "iPad Air (5th generation)",
+          "--version",
+          "26.0",
+          "--out-dir",
+          "${ISOLATED_OUTDIR}",
+          "--xcode-build-version",
+          "17a5241o",
+          "--xctest"
+        ],
+        "merge": {
+          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+        },
         "name": "ios_components_unittests iPad Air (5th generation) 26.0",
         "resultdb": {
           "enable": true,
diff --git a/infra/config/generated/builders/ci/ios26-sdk-simulator/targets/chromium.fyi.json b/infra/config/generated/builders/ci/ios26-sdk-simulator/targets/chromium.fyi.json
index 5181bd4..933e91f 100644
--- a/infra/config/generated/builders/ci/ios26-sdk-simulator/targets/chromium.fyi.json
+++ b/infra/config/generated/builders/ci/ios26-sdk-simulator/targets/chromium.fyi.json
@@ -522,150 +522,6 @@
           "iPad Air (5th generation)",
           "--version",
           "26.0",
-          "--out-dir",
-          "${ISOLATED_OUTDIR}",
-          "--xcode-build-version",
-          "17a5241o",
-          "--xctest"
-        ],
-        "merge": {
-          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
-        },
-        "name": "ios_chrome_unittests iPad Air (5th generation) 26.0",
-        "resultdb": {
-          "enable": true,
-          "has_native_resultdb_integration": true
-        },
-        "swarming": {
-          "cipd_packages": [
-            {
-              "cipd_package": "infra/tools/mac_toolchain/${platform}",
-              "location": ".",
-              "revision": "git_revision:4c7290150d1c360cecc6a93c0214dc531585c3ab"
-            }
-          ],
-          "dimensions": {
-            "cpu": "arm64",
-            "os": "Mac-15.5"
-          },
-          "named_caches": [
-            {
-              "name": "runtime_ios_26_0",
-              "path": "Runtime-ios-26.0"
-            },
-            {
-              "name": "xcode_ios_17a5241o",
-              "path": "Xcode.app"
-            }
-          ],
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "ios_chrome_unittests",
-        "test_id_prefix": "ninja://ios/chrome/test:ios_chrome_unittests/",
-        "variant_id": "iPad Air (5th generation) 26.0"
-      },
-      {
-        "args": [
-          "--platform",
-          "iPhone 14",
-          "--version",
-          "26.0",
-          "--out-dir",
-          "${ISOLATED_OUTDIR}",
-          "--xcode-build-version",
-          "17a5241o",
-          "--xctest"
-        ],
-        "merge": {
-          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
-        },
-        "name": "ios_chrome_unittests iPhone 14 26.0",
-        "resultdb": {
-          "enable": true,
-          "has_native_resultdb_integration": true
-        },
-        "swarming": {
-          "cipd_packages": [
-            {
-              "cipd_package": "infra/tools/mac_toolchain/${platform}",
-              "location": ".",
-              "revision": "git_revision:4c7290150d1c360cecc6a93c0214dc531585c3ab"
-            }
-          ],
-          "dimensions": {
-            "cpu": "arm64",
-            "os": "Mac-15.5"
-          },
-          "named_caches": [
-            {
-              "name": "runtime_ios_26_0",
-              "path": "Runtime-ios-26.0"
-            },
-            {
-              "name": "xcode_ios_17a5241o",
-              "path": "Xcode.app"
-            }
-          ],
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "ios_chrome_unittests",
-        "test_id_prefix": "ninja://ios/chrome/test:ios_chrome_unittests/",
-        "variant_id": "iPhone 14 26.0"
-      },
-      {
-        "args": [
-          "--platform",
-          "iPhone SE (3rd generation)",
-          "--version",
-          "26.0",
-          "--out-dir",
-          "${ISOLATED_OUTDIR}",
-          "--xcode-build-version",
-          "17a5241o",
-          "--xctest"
-        ],
-        "merge": {
-          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
-        },
-        "name": "ios_chrome_unittests iPhone SE (3rd generation) 26.0",
-        "resultdb": {
-          "enable": true,
-          "has_native_resultdb_integration": true
-        },
-        "swarming": {
-          "cipd_packages": [
-            {
-              "cipd_package": "infra/tools/mac_toolchain/${platform}",
-              "location": ".",
-              "revision": "git_revision:4c7290150d1c360cecc6a93c0214dc531585c3ab"
-            }
-          ],
-          "dimensions": {
-            "cpu": "arm64",
-            "os": "Mac-15.5"
-          },
-          "named_caches": [
-            {
-              "name": "runtime_ios_26_0",
-              "path": "Runtime-ios-26.0"
-            },
-            {
-              "name": "xcode_ios_17a5241o",
-              "path": "Xcode.app"
-            }
-          ],
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "ios_chrome_unittests",
-        "test_id_prefix": "ninja://ios/chrome/test:ios_chrome_unittests/",
-        "variant_id": "iPhone SE (3rd generation) 26.0"
-      },
-      {
-        "args": [
-          "--platform",
-          "iPad Air (5th generation)",
-          "--version",
-          "26.0",
           "--xcodebuild-sim-runner",
           "--out-dir",
           "${ISOLATED_OUTDIR}",
diff --git a/infra/config/generated/builders/try/ios18-sdk-simulator/targets/chromium.fyi.json b/infra/config/generated/builders/try/ios18-sdk-simulator/targets/chromium.fyi.json
index 1b35b0b7..e89a078 100644
--- a/infra/config/generated/builders/try/ios18-sdk-simulator/targets/chromium.fyi.json
+++ b/infra/config/generated/builders/try/ios18-sdk-simulator/targets/chromium.fyi.json
@@ -1129,6 +1129,150 @@
         "merge": {
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
+        "name": "ios_chrome_unittests iPad Air (5th generation) 26.0",
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
+        "swarming": {
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/mac_toolchain/${platform}",
+              "location": ".",
+              "revision": "git_revision:4c7290150d1c360cecc6a93c0214dc531585c3ab"
+            }
+          ],
+          "dimensions": {
+            "cpu": "arm64",
+            "os": "Mac-15.5"
+          },
+          "named_caches": [
+            {
+              "name": "runtime_ios_26_0",
+              "path": "Runtime-ios-26.0"
+            },
+            {
+              "name": "xcode_ios_17a5241o",
+              "path": "Xcode.app"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "ios_chrome_unittests",
+        "test_id_prefix": "ninja://ios/chrome/test:ios_chrome_unittests/",
+        "variant_id": "iPad Air (5th generation) 26.0"
+      },
+      {
+        "args": [
+          "--platform",
+          "iPhone 14",
+          "--version",
+          "26.0",
+          "--out-dir",
+          "${ISOLATED_OUTDIR}",
+          "--xcode-build-version",
+          "17a5241o",
+          "--xctest"
+        ],
+        "merge": {
+          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+        },
+        "name": "ios_chrome_unittests iPhone 14 26.0",
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
+        "swarming": {
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/mac_toolchain/${platform}",
+              "location": ".",
+              "revision": "git_revision:4c7290150d1c360cecc6a93c0214dc531585c3ab"
+            }
+          ],
+          "dimensions": {
+            "cpu": "arm64",
+            "os": "Mac-15.5"
+          },
+          "named_caches": [
+            {
+              "name": "runtime_ios_26_0",
+              "path": "Runtime-ios-26.0"
+            },
+            {
+              "name": "xcode_ios_17a5241o",
+              "path": "Xcode.app"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "ios_chrome_unittests",
+        "test_id_prefix": "ninja://ios/chrome/test:ios_chrome_unittests/",
+        "variant_id": "iPhone 14 26.0"
+      },
+      {
+        "args": [
+          "--platform",
+          "iPhone SE (3rd generation)",
+          "--version",
+          "26.0",
+          "--out-dir",
+          "${ISOLATED_OUTDIR}",
+          "--xcode-build-version",
+          "17a5241o",
+          "--xctest"
+        ],
+        "merge": {
+          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+        },
+        "name": "ios_chrome_unittests iPhone SE (3rd generation) 26.0",
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
+        "swarming": {
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/mac_toolchain/${platform}",
+              "location": ".",
+              "revision": "git_revision:4c7290150d1c360cecc6a93c0214dc531585c3ab"
+            }
+          ],
+          "dimensions": {
+            "cpu": "arm64",
+            "os": "Mac-15.5"
+          },
+          "named_caches": [
+            {
+              "name": "runtime_ios_26_0",
+              "path": "Runtime-ios-26.0"
+            },
+            {
+              "name": "xcode_ios_17a5241o",
+              "path": "Xcode.app"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "ios_chrome_unittests",
+        "test_id_prefix": "ninja://ios/chrome/test:ios_chrome_unittests/",
+        "variant_id": "iPhone SE (3rd generation) 26.0"
+      },
+      {
+        "args": [
+          "--platform",
+          "iPad Air (5th generation)",
+          "--version",
+          "26.0",
+          "--out-dir",
+          "${ISOLATED_OUTDIR}",
+          "--xcode-build-version",
+          "17a5241o",
+          "--xctest"
+        ],
+        "merge": {
+          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+        },
         "name": "ios_components_unittests iPad Air (5th generation) 26.0",
         "resultdb": {
           "enable": true,
diff --git a/infra/config/generated/builders/try/ios26-sdk-simulator/targets/chromium.fyi.json b/infra/config/generated/builders/try/ios26-sdk-simulator/targets/chromium.fyi.json
index 5181bd4..933e91f 100644
--- a/infra/config/generated/builders/try/ios26-sdk-simulator/targets/chromium.fyi.json
+++ b/infra/config/generated/builders/try/ios26-sdk-simulator/targets/chromium.fyi.json
@@ -522,150 +522,6 @@
           "iPad Air (5th generation)",
           "--version",
           "26.0",
-          "--out-dir",
-          "${ISOLATED_OUTDIR}",
-          "--xcode-build-version",
-          "17a5241o",
-          "--xctest"
-        ],
-        "merge": {
-          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
-        },
-        "name": "ios_chrome_unittests iPad Air (5th generation) 26.0",
-        "resultdb": {
-          "enable": true,
-          "has_native_resultdb_integration": true
-        },
-        "swarming": {
-          "cipd_packages": [
-            {
-              "cipd_package": "infra/tools/mac_toolchain/${platform}",
-              "location": ".",
-              "revision": "git_revision:4c7290150d1c360cecc6a93c0214dc531585c3ab"
-            }
-          ],
-          "dimensions": {
-            "cpu": "arm64",
-            "os": "Mac-15.5"
-          },
-          "named_caches": [
-            {
-              "name": "runtime_ios_26_0",
-              "path": "Runtime-ios-26.0"
-            },
-            {
-              "name": "xcode_ios_17a5241o",
-              "path": "Xcode.app"
-            }
-          ],
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "ios_chrome_unittests",
-        "test_id_prefix": "ninja://ios/chrome/test:ios_chrome_unittests/",
-        "variant_id": "iPad Air (5th generation) 26.0"
-      },
-      {
-        "args": [
-          "--platform",
-          "iPhone 14",
-          "--version",
-          "26.0",
-          "--out-dir",
-          "${ISOLATED_OUTDIR}",
-          "--xcode-build-version",
-          "17a5241o",
-          "--xctest"
-        ],
-        "merge": {
-          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
-        },
-        "name": "ios_chrome_unittests iPhone 14 26.0",
-        "resultdb": {
-          "enable": true,
-          "has_native_resultdb_integration": true
-        },
-        "swarming": {
-          "cipd_packages": [
-            {
-              "cipd_package": "infra/tools/mac_toolchain/${platform}",
-              "location": ".",
-              "revision": "git_revision:4c7290150d1c360cecc6a93c0214dc531585c3ab"
-            }
-          ],
-          "dimensions": {
-            "cpu": "arm64",
-            "os": "Mac-15.5"
-          },
-          "named_caches": [
-            {
-              "name": "runtime_ios_26_0",
-              "path": "Runtime-ios-26.0"
-            },
-            {
-              "name": "xcode_ios_17a5241o",
-              "path": "Xcode.app"
-            }
-          ],
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "ios_chrome_unittests",
-        "test_id_prefix": "ninja://ios/chrome/test:ios_chrome_unittests/",
-        "variant_id": "iPhone 14 26.0"
-      },
-      {
-        "args": [
-          "--platform",
-          "iPhone SE (3rd generation)",
-          "--version",
-          "26.0",
-          "--out-dir",
-          "${ISOLATED_OUTDIR}",
-          "--xcode-build-version",
-          "17a5241o",
-          "--xctest"
-        ],
-        "merge": {
-          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
-        },
-        "name": "ios_chrome_unittests iPhone SE (3rd generation) 26.0",
-        "resultdb": {
-          "enable": true,
-          "has_native_resultdb_integration": true
-        },
-        "swarming": {
-          "cipd_packages": [
-            {
-              "cipd_package": "infra/tools/mac_toolchain/${platform}",
-              "location": ".",
-              "revision": "git_revision:4c7290150d1c360cecc6a93c0214dc531585c3ab"
-            }
-          ],
-          "dimensions": {
-            "cpu": "arm64",
-            "os": "Mac-15.5"
-          },
-          "named_caches": [
-            {
-              "name": "runtime_ios_26_0",
-              "path": "Runtime-ios-26.0"
-            },
-            {
-              "name": "xcode_ios_17a5241o",
-              "path": "Xcode.app"
-            }
-          ],
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "ios_chrome_unittests",
-        "test_id_prefix": "ninja://ios/chrome/test:ios_chrome_unittests/",
-        "variant_id": "iPhone SE (3rd generation) 26.0"
-      },
-      {
-        "args": [
-          "--platform",
-          "iPad Air (5th generation)",
-          "--version",
-          "26.0",
           "--xcodebuild-sim-runner",
           "--out-dir",
           "${ISOLATED_OUTDIR}",
diff --git a/infra/config/targets/bundles.star b/infra/config/targets/bundles.star
index 5586cef..f7ceb73 100644
--- a/infra/config/targets/bundles.star
+++ b/infra/config/targets/bundles.star
@@ -5502,7 +5502,6 @@
 targets.bundle(
     name = "ios_failing_screen_size_dependent_tests",
     targets = [
-        "ios_chrome_unittests",
         "ios_web_inttests",
         "ios_web_unittests",
     ],
@@ -5571,6 +5570,7 @@
         "base_unittests",
         "components_unittests",
         "gfx_unittests",
+        "ios_chrome_unittests",
         "ios_web_view_inttests",
         "ios_web_view_unittests",
         "skia_unittests",
diff --git a/internal b/internal
index 49594467..d7516c7 160000
--- a/internal
+++ b/internal
@@ -1 +1 @@
-Subproject commit 495944671d1f0f6b653bcf7b09c4b21b4b43aa78
+Subproject commit d7516c780898c612c1ff9c1034fb2d79f39949f0
diff --git a/ios/chrome/browser/authentication/ui_bundled/signin_earl_grey_ui_test_util.mm b/ios/chrome/browser/authentication/ui_bundled/signin_earl_grey_ui_test_util.mm
index 4f0f94a..69acc9d 100644
--- a/ios/chrome/browser/authentication/ui_bundled/signin_earl_grey_ui_test_util.mm
+++ b/ios/chrome/browser/authentication/ui_bundled/signin_earl_grey_ui_test_util.mm
@@ -64,7 +64,7 @@
   // Verify whether there is a confirmation dialog and interact with it to
   // complete the sign-in flow if present.
   id<GREYMatcher> acceptButton = [ChromeMatchersAppInterface
-      buttonWithAccessibilityLabelID:
+      actionSheetItemWithAccessibilityLabelID:
           IDS_IOS_SIGNOUT_AND_DELETE_DIALOG_SIGN_OUT_BUTTON];
   [ChromeEarlGrey waitForUIElementToAppearWithMatcher:acceptButton];
   [[EarlGrey selectElementWithMatcher:acceptButton] performAction:grey_tap()];
diff --git a/ios/chrome/browser/autofill/model/autofill_controller_js_unittest.mm b/ios/chrome/browser/autofill/model/autofill_controller_js_unittest.mm
index 33b566e2..2a803a7 100644
--- a/ios/chrome/browser/autofill/model/autofill_controller_js_unittest.mm
+++ b/ios/chrome/browser/autofill/model/autofill_controller_js_unittest.mm
@@ -971,7 +971,7 @@
 }
 
 id AutofillControllerJsTest::ExecuteJavaScript(NSString* java_script) {
-  return web::test::ExecuteJavaScriptForFeature(
+  return web::test::ExecuteJavaScriptForFeatureAndReturnResult(
       web_state(), java_script,
       autofill::AutofillJavaScriptFeature::GetInstance());
 }
diff --git a/ios/chrome/browser/autofill/model/autofill_java_script_feature_unittest.mm b/ios/chrome/browser/autofill/model/autofill_java_script_feature_unittest.mm
index 06e5f4f..4cc3a2f 100644
--- a/ios/chrome/browser/autofill/model/autofill_java_script_feature_unittest.mm
+++ b/ios/chrome/browser/autofill/model/autofill_java_script_feature_unittest.mm
@@ -124,8 +124,8 @@
   }
 
   id ExecuteJavaScript(NSString* java_script) {
-    return web::test::ExecuteJavaScriptForFeature(web_state(), java_script,
-                                                  feature());
+    return web::test::ExecuteJavaScriptForFeatureAndReturnResult(
+        web_state(), java_script, feature());
   }
 
   autofill::AutofillJavaScriptFeature* feature() {
diff --git a/ios/chrome/browser/autofill/model/suggestion_controller_java_script_feature_unittest.mm b/ios/chrome/browser/autofill/model/suggestion_controller_java_script_feature_unittest.mm
index f0153116..d57aa14 100644
--- a/ios/chrome/browser/autofill/model/suggestion_controller_java_script_feature_unittest.mm
+++ b/ios/chrome/browser/autofill/model/suggestion_controller_java_script_feature_unittest.mm
@@ -47,8 +47,8 @@
   id ExecuteJavaScript(NSString* java_script) {
     autofill::SuggestionControllerJavaScriptFeature* feature =
         autofill::SuggestionControllerJavaScriptFeature::GetInstance();
-    return web::test::ExecuteJavaScriptForFeature(web_state(), java_script,
-                                                  feature);
+    return web::test::ExecuteJavaScriptForFeatureAndReturnResult(
+        web_state(), java_script, feature);
   }
   // Returns the active element name from the JS side.
   NSString* GetActiveElementName() {
diff --git a/ios/chrome/browser/autofill/ui_bundled/manual_fill/manual_fill_password_mediator.mm b/ios/chrome/browser/autofill/ui_bundled/manual_fill/manual_fill_password_mediator.mm
index 5aa6ca97..13bdb24 100644
--- a/ios/chrome/browser/autofill/ui_bundled/manual_fill/manual_fill_password_mediator.mm
+++ b/ios/chrome/browser/autofill/ui_bundled/manual_fill/manual_fill_password_mediator.mm
@@ -302,8 +302,12 @@
             ? NO
             : AreCredentialsAtIndicesConnected(credentials, i, i + 1);
 
+    ManualFillCredential* manualFillCredential =
+        credentials[i].manual_fill_credential;
+
     NSArray<UIAction*>* menuActions =
-        IsKeyboardAccessoryUpgradeEnabled()
+        IsKeyboardAccessoryUpgradeEnabled() &&
+                !manualFillCredential.isBackupCredential
             ? @[ [self createMenuEditActionForPassword:credentials[i]
                                                            .password_form] ]
             : @[];
@@ -316,7 +320,7 @@
             base::checked_cast<int>(i + 1)));
 
     ManualFillCredentialItem* item = [[ManualFillCredentialItem alloc]
-                 initWithCredential:credentials[i].manual_fill_credential
+                 initWithCredential:manualFillCredential
           isConnectedToPreviousItem:isConnectedToPreviousItem
               isConnectedToNextItem:isConnectedToNextItem
                     contentInjector:self
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 df0bbfc6..e60f480 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
@@ -106,10 +106,11 @@
   return grey_accessibilityID(kPasswordDetailsViewControllerID);
 }
 
-// Matcher for a cell displaying a backup password.
-id<GREYMatcher> BackupCredentialCellMatcher(NSString* host,
-                                            int cell_index,
-                                            int password_count) {
+// Matcher for a cell displaying a backup password. A backup password cell
+// should come with a backup credential icon and no overflow menu.
+id<GREYMatcher> BackupCredentialCell(NSString* host,
+                                     int cell_index,
+                                     int password_count) {
   NSString* cell_position = base::SysUTF16ToNSString(
       base::i18n::MessageFormatter::FormatWithNamedArgs(
           l10n_util::GetStringUTF16(
@@ -130,17 +131,19 @@
       kRecoveryPasswordSuggestionIconAccessibilityIdentifier);
 
   return grey_allOf(grey_accessibilityLabel(accessibility_label),
-                    grey_descendant(backup_icon), nullptr);
+                    grey_descendant(backup_icon),
+                    grey_not(grey_descendant(OverflowMenuButton(cell_index))),
+                    nullptr);
 }
 
 // Matcher for the "Autofill form" button shown in a backup password cell.
 id<GREYMatcher> BackupCredentialAutofillFormButton(NSString* host,
                                                    int cell_index,
                                                    int password_count) {
-  return grey_allOf(AutofillFormButton(),
-                    grey_ancestor(BackupCredentialCellMatcher(host, cell_index,
-                                                              password_count)),
-                    nullptr);
+  return grey_allOf(
+      AutofillFormButton(),
+      grey_ancestor(BackupCredentialCell(host, cell_index, password_count)),
+      nullptr);
 }
 
 // Opens the password manual fill view and verifies that the password view
diff --git a/ios/chrome/browser/content_notification/model/content_notification_client.h b/ios/chrome/browser/content_notification/model/content_notification_client.h
index ef3bd76..6afd9ea 100644
--- a/ios/chrome/browser/content_notification/model/content_notification_client.h
+++ b/ios/chrome/browser/content_notification/model/content_notification_client.h
@@ -24,6 +24,8 @@
   ~ContentNotificationClient() override;
 
   // Override PushNotificationClient::
+  std::optional<NotificationType> GetNotificationType(
+      UNNotification* notification) override;
   bool CanHandleNotification(UNNotification* notification) override;
   bool HandleNotificationInteraction(UNNotificationResponse* response) override;
   std::optional<UIBackgroundFetchResult> HandleNotificationReception(
diff --git a/ios/chrome/browser/content_notification/model/content_notification_client.mm b/ios/chrome/browser/content_notification/model/content_notification_client.mm
index cc9d8087..5c2a2c3 100644
--- a/ios/chrome/browser/content_notification/model/content_notification_client.mm
+++ b/ios/chrome/browser/content_notification/model/content_notification_client.mm
@@ -30,6 +30,14 @@
 
 ContentNotificationClient::~ContentNotificationClient() = default;
 
+std::optional<NotificationType> ContentNotificationClient::GetNotificationType(
+    UNNotification* notification) {
+  if (CanHandleNotification(notification)) {
+    return NotificationType::kContent;
+  }
+  return std::nullopt;
+}
+
 bool ContentNotificationClient::CanHandleNotification(
     UNNotification* notification) {
   return [notification.request.content.categoryIdentifier
diff --git a/ios/chrome/browser/flags/about_flags.mm b/ios/chrome/browser/flags/about_flags.mm
index 5fe5f8b..bfaec32 100644
--- a/ios/chrome/browser/flags/about_flags.mm
+++ b/ios/chrome/browser/flags/about_flags.mm
@@ -2670,15 +2670,6 @@
      flag_descriptions::kIOSMiniMapUniversalLinkName,
      flag_descriptions::kIOSMiniMapUniversalLinkDescription, flags_ui::kOsIos,
      FEATURE_VALUE_TYPE(kIOSMiniMapUniversalLink)},
-    {"autofill-drop-names-with-invalid-characters-for-card-upload",
-     flag_descriptions::
-         kAutofillDropNamesWithInvalidCharactersForCardUploadName,
-     flag_descriptions::
-         kAutofillDropNamesWithInvalidCharactersForCardUploadDescription,
-     flags_ui::kOsIos,
-     FEATURE_VALUE_TYPE(
-         autofill::features::
-             kAutofillDropNamesWithInvalidCharactersForCardUpload)},
     {"autofill-require-cvc-for-possible-card-update",
      flag_descriptions::kAutofillRequireCvcForPossibleCardUpdateName,
      flag_descriptions::kAutofillRequireCvcForPossibleCardUpdateDescription,
diff --git a/ios/chrome/browser/flags/ios_chrome_flag_descriptions.cc b/ios/chrome/browser/flags/ios_chrome_flag_descriptions.cc
index ec9e4b66..5d15067 100644
--- a/ios/chrome/browser/flags/ios_chrome_flag_descriptions.cc
+++ b/ios/chrome/browser/flags/ios_chrome_flag_descriptions.cc
@@ -57,14 +57,6 @@
     "When enabled, Autofill will not apply silent updates to address profiles. "
     "For testing purposes.";
 
-const char kAutofillDropNamesWithInvalidCharactersForCardUploadName[] =
-    "Drop names with invalid characters for credit card upload";
-const char kAutofillDropNamesWithInvalidCharactersForCardUploadDescription[] =
-    "When enabled, cardholder and address names considered during the credit "
-    "card upload flow will be cleared out if they contain characters "
-    "considered invalid by Google Payments, such as numbers or various "
-    "punctuation marks.";
-
 const char kAutofillEnableAllowlistForBmoCardCategoryBenefitsName[] =
     "Enable allowlist for showing category benefits for BMO cards";
 const char kAutofillEnableAllowlistForBmoCardCategoryBenefitsDescription[] =
diff --git a/ios/chrome/browser/flags/ios_chrome_flag_descriptions.h b/ios/chrome/browser/flags/ios_chrome_flag_descriptions.h
index 99937ac..9103efb 100644
--- a/ios/chrome/browser/flags/ios_chrome_flag_descriptions.h
+++ b/ios/chrome/browser/flags/ios_chrome_flag_descriptions.h
@@ -47,10 +47,6 @@
 extern const char kAutofillDisableSilentProfileUpdatesName[];
 extern const char kAutofillDisableSilentProfileUpdatesDescription[];
 
-extern const char kAutofillDropNamesWithInvalidCharactersForCardUploadName[];
-extern const char
-    kAutofillDropNamesWithInvalidCharactersForCardUploadDescription[];
-
 extern const char kAutofillEnableAllowlistForBmoCardCategoryBenefitsName[];
 extern const char
     kAutofillEnableAllowlistForBmoCardCategoryBenefitsDescription[];
diff --git a/ios/chrome/browser/ntp/ui_bundled/feed_top_section/feed_top_section_coordinator.mm b/ios/chrome/browser/ntp/ui_bundled/feed_top_section/feed_top_section_coordinator.mm
index c443bc5..2189e8a 100644
--- a/ios/chrome/browser/ntp/ui_bundled/feed_top_section/feed_top_section_coordinator.mm
+++ b/ios/chrome/browser/ntp/ui_bundled/feed_top_section/feed_top_section_coordinator.mm
@@ -185,6 +185,10 @@
 - (void)showSignin:(SigninPromoViewMediator*)mediator
            command:(ShowSigninCommand*)command {
   CHECK_EQ(self.signinPromoMediator, mediator);
+  if (_signinCoordinator) {
+    // This can occur in case of double tap.
+    return;
+  }
   __weak __typeof(self) weakSelf = self;
   [command addSigninCompletion:^(SigninCoordinatorResult result,
                                  id<SystemIdentity>) {
diff --git a/ios/chrome/browser/optimization_guide/model/BUILD.gn b/ios/chrome/browser/optimization_guide/model/BUILD.gn
index 1a40391..6c6d197d 100644
--- a/ios/chrome/browser/optimization_guide/model/BUILD.gn
+++ b/ios/chrome/browser/optimization_guide/model/BUILD.gn
@@ -6,6 +6,8 @@
 
 source_set("model") {
   sources = [
+    "chrome_profile_download_service_tracker.h",
+    "chrome_profile_download_service_tracker.mm",
     "ios_chrome_hints_manager.h",
     "ios_chrome_hints_manager.mm",
     "ios_chrome_prediction_model_store.h",
@@ -75,6 +77,7 @@
 source_set("unit_tests") {
   testonly = true
   sources = [
+    "chrome_profile_download_service_tracker_unittest.mm",
     "optimization_guide_service_factory_unittest.mm",
     "optimization_guide_service_unittest.mm",
     "optimization_guide_tab_helper_unittest.mm",
@@ -101,6 +104,7 @@
     "//components/unified_consent",
     "//components/variations",
     "//components/variations:test_support",
+    "//ios/chrome/browser/download/model/background_service",
     "//ios/chrome/browser/push_notification/model:push_notification_client",
     "//ios/chrome/browser/saved_tab_groups/model",
     "//ios/chrome/browser/shared/model/application_context",
diff --git a/ios/chrome/browser/optimization_guide/model/chrome_profile_download_service_tracker.h b/ios/chrome/browser/optimization_guide/model/chrome_profile_download_service_tracker.h
new file mode 100644
index 0000000..4ffc4d4d
--- /dev/null
+++ b/ios/chrome/browser/optimization_guide/model/chrome_profile_download_service_tracker.h
@@ -0,0 +1,58 @@
+// 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_OPTIMIZATION_GUIDE_MODEL_CHROME_PROFILE_DOWNLOAD_SERVICE_TRACKER_H_
+#define IOS_CHROME_BROWSER_OPTIMIZATION_GUIDE_MODEL_CHROME_PROFILE_DOWNLOAD_SERVICE_TRACKER_H_
+
+#include <vector>
+
+#import "base/scoped_observation.h"
+#import "components/optimization_guide/core/delivery/profile_download_service_tracker.h"
+#import "ios/chrome/browser/shared/model/profile/profile_manager_ios.h"
+#import "ios/chrome/browser/shared/model/profile/profile_manager_observer_ios.h"
+
+namespace optimization_guide {
+
+class ChromeProfileDownloadServiceTrackerIOSTest;
+
+class ChromeProfileDownloadServiceTracker
+    : public ProfileDownloadServiceTracker,
+      public ProfileManagerObserverIOS {
+ public:
+  ChromeProfileDownloadServiceTracker();
+  ~ChromeProfileDownloadServiceTracker() override;
+
+  ChromeProfileDownloadServiceTracker(
+      const ChromeProfileDownloadServiceTracker&) = delete;
+  ChromeProfileDownloadServiceTracker& operator=(
+      const ChromeProfileDownloadServiceTracker&) = delete;
+
+  // ProfileDownloadServiceTracker:
+  download::BackgroundDownloadService* GetBackgroundDownloadService() override;
+
+ private:
+  friend class ChromeProfileDownloadServiceTrackerIOSTest;
+
+  // ProfileManagerObserverIOS:
+  void OnProfileManagerWillBeDestroyed(ProfileManagerIOS* manager) override;
+  void OnProfileManagerDestroyed(ProfileManagerIOS* manager) override;
+  void OnProfileCreated(ProfileManagerIOS* manager, ProfileIOS* profile) override;
+  void OnProfileLoaded(ProfileManagerIOS* manager, ProfileIOS* profile) override;
+  void OnProfileUnloaded(ProfileManagerIOS* manager,
+                         ProfileIOS* profile) override;
+  void OnProfileMarkedForPermanentDeletion(ProfileManagerIOS* manager,
+                                           ProfileIOS* profile) override;
+
+  base::ScopedObservation<ProfileManagerIOS, ProfileManagerObserverIOS>
+      profile_manager_observation_{this};
+
+  // The list of regular profiles that are currently active. This list is used
+  // to determine which profile to use for model downloads. Kept in the order
+  // that the profiles were added.
+  std::vector<ProfileIOS*> active_profiles_;
+};
+
+}  // namespace optimization_guide
+
+#endif  // IOS_CHROME_BROWSER_OPTIMIZATION_GUIDE_MODEL_CHROME_PROFILE_DOWNLOAD_SERVICE_TRACKER_H_
diff --git a/ios/chrome/browser/optimization_guide/model/chrome_profile_download_service_tracker.mm b/ios/chrome/browser/optimization_guide/model/chrome_profile_download_service_tracker.mm
new file mode 100644
index 0000000..67385a1
--- /dev/null
+++ b/ios/chrome/browser/optimization_guide/model/chrome_profile_download_service_tracker.mm
@@ -0,0 +1,64 @@
+// Copyright 2025 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#import "ios/chrome/browser/optimization_guide/model/chrome_profile_download_service_tracker.h"
+
+#import "ios/chrome/browser/download/model/background_service/background_download_service_factory.h"
+#import "ios/chrome/browser/shared/model/application_context/application_context.h"
+#import "ios/chrome/browser/shared/model/profile/profile_ios.h"
+#import "ios/chrome/browser/shared/model/profile/profile_manager_ios.h"
+
+namespace optimization_guide {
+
+ChromeProfileDownloadServiceTracker::ChromeProfileDownloadServiceTracker() {
+  profile_manager_observation_.Observe(
+      GetApplicationContext()->GetProfileManager());
+}
+
+ChromeProfileDownloadServiceTracker::~ChromeProfileDownloadServiceTracker() =
+    default;
+
+void ChromeProfileDownloadServiceTracker::OnProfileManagerWillBeDestroyed(
+    ProfileManagerIOS* manager) {
+  active_profiles_.clear();
+}
+
+void ChromeProfileDownloadServiceTracker::OnProfileManagerDestroyed(
+    ProfileManagerIOS* manager) {}
+
+void ChromeProfileDownloadServiceTracker::OnProfileCreated(
+    ProfileManagerIOS* manager,
+    ProfileIOS* profile) {}
+
+void ChromeProfileDownloadServiceTracker::OnProfileLoaded(
+    ProfileManagerIOS* manager,
+    ProfileIOS* profile) {
+  if (profile->IsOffTheRecord()) {
+    return;
+  }
+  active_profiles_.push_back(profile);
+}
+
+void ChromeProfileDownloadServiceTracker::OnProfileUnloaded(
+    ProfileManagerIOS* manager,
+    ProfileIOS* profile) {
+  std::erase(active_profiles_, profile);
+}
+
+void ChromeProfileDownloadServiceTracker::OnProfileMarkedForPermanentDeletion(
+    ProfileManagerIOS* manager,
+    ProfileIOS* profile) {
+  std::erase(active_profiles_, profile);
+}
+
+download::BackgroundDownloadService*
+ChromeProfileDownloadServiceTracker::GetBackgroundDownloadService() {
+  if (active_profiles_.empty()) {
+    return nullptr;
+  }
+  return BackgroundDownloadServiceFactory::GetForProfile(
+      active_profiles_.front());
+}
+
+}  // namespace optimization_guide
diff --git a/ios/chrome/browser/optimization_guide/model/chrome_profile_download_service_tracker_unittest.mm b/ios/chrome/browser/optimization_guide/model/chrome_profile_download_service_tracker_unittest.mm
new file mode 100644
index 0000000..74c99e5
--- /dev/null
+++ b/ios/chrome/browser/optimization_guide/model/chrome_profile_download_service_tracker_unittest.mm
@@ -0,0 +1,102 @@
+// 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/optimization_guide/model/chrome_profile_download_service_tracker.h"
+
+#import "ios/chrome/browser/download/model/background_service/background_download_service_factory.h"
+#import "ios/chrome/browser/shared/model/profile/profile_ios.h"
+#import "ios/chrome/browser/shared/model/profile/test/test_profile_ios.h"
+#import "ios/chrome/browser/shared/model/profile/test/test_profile_manager_ios.h"
+#import "ios/chrome/test/ios_chrome_scoped_testing_local_state.h"
+#import "ios/web/public/test/web_task_environment.h"
+#import "testing/gtest/include/gtest/gtest.h"
+#import "testing/platform_test.h"
+
+namespace {
+
+constexpr char kProfileFoo[] = "foo";
+constexpr char kProfileBar[] = "bar";
+constexpr char kProfileBaz[] = "baz";
+
+download::BackgroundDownloadService* GetBackgroundDownloadServiceForProfile(
+    ProfileIOS* profile) {
+  return BackgroundDownloadServiceFactory::GetForProfile(profile);
+}
+
+}  // namespace
+
+namespace optimization_guide {
+
+class ChromeProfileDownloadServiceTrackerIOSTest : public PlatformTest {
+ public:
+  ChromeProfileDownloadServiceTrackerIOSTest() = default;
+
+  ChromeProfileDownloadServiceTrackerIOSTest(
+      const ChromeProfileDownloadServiceTrackerIOSTest&) = delete;
+  ChromeProfileDownloadServiceTrackerIOSTest& operator=(
+      const ChromeProfileDownloadServiceTrackerIOSTest&) = delete;
+
+ protected:
+  ProfileIOS* CreateTestingProfile(const std::string& name) {
+    TestProfileIOS::Builder builder;
+    builder.SetName(name);
+    return profile_manager_.AddProfileWithBuilder(std::move(builder));
+  }
+
+  void DeleteProfile(ChromeProfileDownloadServiceTracker* service_tracker,
+                     ProfileIOS* profile) {
+    service_tracker->OnProfileUnloaded(&profile_manager_, profile);
+    service_tracker->OnProfileMarkedForPermanentDeletion(&profile_manager_,
+                                                         profile);
+  }
+
+  web::WebTaskEnvironment task_environment_;
+  IOSChromeScopedTestingLocalState scoped_testing_local_state_;
+  TestProfileManagerIOS profile_manager_;
+};
+
+TEST_F(ChromeProfileDownloadServiceTrackerIOSTest, OneProfile) {
+  ChromeProfileDownloadServiceTracker service_tracker;
+  ProfileIOS* foo_profile = CreateTestingProfile(kProfileFoo);
+  task_environment_.RunUntilIdle();
+
+  EXPECT_EQ(service_tracker.GetBackgroundDownloadService(),
+            GetBackgroundDownloadServiceForProfile(foo_profile));
+}
+
+TEST_F(ChromeProfileDownloadServiceTrackerIOSTest, TwoProfiles) {
+  ChromeProfileDownloadServiceTracker service_tracker;
+  ProfileIOS* foo_profile = CreateTestingProfile(kProfileFoo);
+  ProfileIOS* bar_profile = CreateTestingProfile(kProfileBar);
+  task_environment_.RunUntilIdle();
+
+  EXPECT_EQ(service_tracker.GetBackgroundDownloadService(),
+            GetBackgroundDownloadServiceForProfile(foo_profile));
+
+  // Simulate foo profile deletion, the download manager should be picked from
+  // bar profile.
+  DeleteProfile(&service_tracker, foo_profile);
+  task_environment_.RunUntilIdle();
+  EXPECT_EQ(service_tracker.GetBackgroundDownloadService(),
+            GetBackgroundDownloadServiceForProfile(bar_profile));
+
+  // When another profile is created, it should still pick the bar profile.
+  CreateTestingProfile(kProfileBaz);
+  task_environment_.RunUntilIdle();
+  EXPECT_EQ(service_tracker.GetBackgroundDownloadService(),
+            GetBackgroundDownloadServiceForProfile(bar_profile));
+}
+
+TEST_F(ChromeProfileDownloadServiceTrackerIOSTest,
+       ServiceTrackerCreatedAfterProfile) {
+  ProfileIOS* foo_profile = CreateTestingProfile(kProfileFoo);
+  task_environment_.RunUntilIdle();
+
+  ChromeProfileDownloadServiceTracker service_tracker;
+
+  EXPECT_EQ(service_tracker.GetBackgroundDownloadService(),
+            GetBackgroundDownloadServiceForProfile(foo_profile));
+}
+
+}  // namespace optimization_guide
diff --git a/ios/chrome/browser/page_content_annotations/model/page_content_annotations_service_factory.mm b/ios/chrome/browser/page_content_annotations/model/page_content_annotations_service_factory.mm
index e6576a2..f79ac4b 100644
--- a/ios/chrome/browser/page_content_annotations/model/page_content_annotations_service_factory.mm
+++ b/ios/chrome/browser/page_content_annotations/model/page_content_annotations_service_factory.mm
@@ -62,6 +62,8 @@
       proto_db_provider, profile_path,
       optimization_guide_keyed_service->GetOptimizationGuideLogger(),
       optimization_guide_keyed_service,
+      /*embedder_metadata_provider=*/nullptr,
+      /*embedder=*/nullptr,
       base::ThreadPool::CreateSequencedTaskRunner(
           {base::MayBlock(), base::TaskPriority::BEST_EFFORT}));
 }
diff --git a/ios/chrome/browser/passwords/model/password_controller_js_unittest.mm b/ios/chrome/browser/passwords/model/password_controller_js_unittest.mm
index 1cf45473..58a065c 100644
--- a/ios/chrome/browser/passwords/model/password_controller_js_unittest.mm
+++ b/ios/chrome/browser/passwords/model/password_controller_js_unittest.mm
@@ -243,7 +243,8 @@
   id ExecuteJavaScript(NSString* script) {
     password_manager::PasswordManagerJavaScriptFeature* feature =
         password_manager::PasswordManagerJavaScriptFeature::GetInstance();
-    return web::test::ExecuteJavaScriptForFeature(web_state(), script, feature);
+    return web::test::ExecuteJavaScriptForFeatureAndReturnResult(
+        web_state(), script, feature);
   }
 
   web::WebState* web_state() { return web_state_.get(); }
diff --git a/ios/chrome/browser/passwords/model/password_controller_unittest.mm b/ios/chrome/browser/passwords/model/password_controller_unittest.mm
index 8bd18927..d6c40d0e 100644
--- a/ios/chrome/browser/passwords/model/password_controller_unittest.mm
+++ b/ios/chrome/browser/passwords/model/password_controller_unittest.mm
@@ -487,8 +487,8 @@
   id ExecuteJavaScriptInFeatureWorld(NSString* java_script) {
     password_manager::PasswordManagerJavaScriptFeature* feature =
         password_manager::PasswordManagerJavaScriptFeature::GetInstance();
-    return web::test::ExecuteJavaScriptForFeature(web_state(), java_script,
-                                                  feature);
+    return web::test::ExecuteJavaScriptForFeatureAndReturnResult(
+        web_state(), java_script, feature);
   }
 
   web::ScopedTestingWebClient web_client_;
diff --git a/ios/chrome/browser/push_notification/model/constants.h b/ios/chrome/browser/push_notification/model/constants.h
index d66b8e73..ebdfe17 100644
--- a/ios/chrome/browser/push_notification/model/constants.h
+++ b/ios/chrome/browser/push_notification/model/constants.h
@@ -34,7 +34,8 @@
   kTipsTrustedVaultKeyRetrieval = 16,
   kReminder = 17,
   kCommerce = 18,
-  kMaxValue = kCommerce,
+  kContent = 19,
+  kMaxValue = kContent,
 };
 // LINT.ThenChange(/tools/metrics/histograms/metadata/ios/enums.xml)
 
diff --git a/ios/chrome/browser/web/model/image_fetch/image_fetch_tab_helper_unittest.mm b/ios/chrome/browser/web/model/image_fetch/image_fetch_tab_helper_unittest.mm
index f56c95ac..0e302e4 100644
--- a/ios/chrome/browser/web/model/image_fetch/image_fetch_tab_helper_unittest.mm
+++ b/ios/chrome/browser/web/model/image_fetch/image_fetch_tab_helper_unittest.mm
@@ -113,7 +113,7 @@
 TEST_F(ImageFetchTabHelperTest, GetImageDataWithJsSucceedFromCanvas) {
   // Inject fake `__gCrWeb.imageFetch.getImageData` that returns `kImageData`
   // in base64 format.
-  id script_result = web::test::ExecuteJavaScriptForFeature(
+  id script_result = web::test::ExecuteJavaScriptForFeatureAndReturnResult(
       web_state(),
       [NSString
           stringWithFormat:
@@ -147,7 +147,7 @@
 TEST_F(ImageFetchTabHelperTest, GetImageDataWithJsSucceedFromXmlHttpRequest) {
   // Inject fake `__gCrWeb.imageFetch.getImageData` that returns `kImageData`
   // in base64 format.
-  id script_result = web::test::ExecuteJavaScriptForFeature(
+  id script_result = web::test::ExecuteJavaScriptForFeatureAndReturnResult(
       web_state(),
       [NSString
           stringWithFormat:
@@ -180,7 +180,7 @@
 // Tests that ImageFetchTabHelper::GetImageData gets image data from server when
 // Js fails.
 TEST_F(ImageFetchTabHelperTest, GetImageDataWithJsFail) {
-  id script_result = web::test::ExecuteJavaScriptForFeature(
+  id script_result = web::test::ExecuteJavaScriptForFeatureAndReturnResult(
       web_state(),
       @"__gCrWeb.imageFetch = {}; __gCrWeb.imageFetch.getImageData = "
        "function(id, url) { "
@@ -209,7 +209,7 @@
 // Js does not send a message back.
 TEST_F(ImageFetchTabHelperTest, GetImageDataWithJsTimeout) {
   // Inject fake `__gCrWeb.imageFetch.getImageData` that does not do anything.
-  id script_result = web::test::ExecuteJavaScriptForFeature(
+  id script_result = web::test::ExecuteJavaScriptForFeatureAndReturnResult(
       web_state(),
       @"__gCrWeb.imageFetch = {}; __gCrWeb.imageFetch.getImageData = "
       @"function(id, url) {}; true;",
@@ -237,7 +237,7 @@
 // WebState is destroyed.
 TEST_F(ImageFetchTabHelperTest, GetImageDataWithWebStateDestroy) {
   // Inject fake `__gCrWeb.imageFetch.getImageData` that does not do anything.
-  id script_result = web::test::ExecuteJavaScriptForFeature(
+  id script_result = web::test::ExecuteJavaScriptForFeatureAndReturnResult(
       web_state(),
       @"__gCrWeb.imageFetch = {}; __gCrWeb.imageFetch.getImageData = "
       @"function(id, url) {}; true;",
@@ -265,7 +265,7 @@
 // WebState navigates to a new web page.
 TEST_F(ImageFetchTabHelperTest, GetImageDataWithWebStateNavigate) {
   // Inject fake `__gCrWeb.imageFetch.getImageData` that does not do anything.
-  id script_result = web::test::ExecuteJavaScriptForFeature(
+  id script_result = web::test::ExecuteJavaScriptForFeatureAndReturnResult(
       web_state(),
       @"__gCrWeb.imageFetch = {}; __gCrWeb.imageFetch.getImageData = "
       @"function(id, url) {}; true;",
diff --git a/ios/chrome/common/credential_provider/memory_credential_store.mm b/ios/chrome/common/credential_provider/memory_credential_store.mm
index 8b0bf2a..863e5f5 100644
--- a/ios/chrome/common/credential_provider/memory_credential_store.mm
+++ b/ios/chrome/common/credential_provider/memory_credential_store.mm
@@ -41,7 +41,8 @@
   __block NSArray<id<Credential>>* credentials;
   __weak __typeof(self) weakSelf = self;
   dispatch_sync(self.workingQueue, ^{
-    credentials = [weakSelf allMemoryStorageValues];
+    __typeof(self) strongSelf = weakSelf;
+    credentials = [strongSelf allMemoryStorageValues];
   });
   return credentials;
 }
@@ -51,7 +52,8 @@
   CHECK(completion);
   __weak __typeof(self) weakSelf = self;
   dispatch_async(self.workingQueue, ^{
-    completion([weakSelf allMemoryStorageValues]);
+    __typeof(self) strongSelf = weakSelf;
+    completion([strongSelf allMemoryStorageValues]);
   });
 }
 
@@ -65,7 +67,8 @@
 - (void)removeAllCredentials {
   __weak __typeof(self) weakSelf = self;
   dispatch_barrier_async(self.workingQueue, ^{
-    [weakSelf.memoryStorage removeAllObjects];
+    __typeof(self) strongSelf = weakSelf;
+    [strongSelf.memoryStorage removeAllObjects];
   });
 }
 
@@ -74,7 +77,8 @@
       << "credential must have a record identifier";
   __weak __typeof(self) weakSelf = self;
   dispatch_barrier_async(self.workingQueue, ^{
-    weakSelf.memoryStorage[credential.recordIdentifier] =
+    __typeof(self) strongSelf = weakSelf;
+    strongSelf.memoryStorage[credential.recordIdentifier] =
         base::apple::ObjCCastStrict<ArchivableCredential>(credential);
   });
 }
@@ -88,7 +92,8 @@
   DCHECK(recordIdentifier.length) << "Invalid `recordIdentifier` was passed.";
   __weak __typeof(self) weakSelf = self;
   dispatch_barrier_async(self.workingQueue, ^{
-    weakSelf.memoryStorage[recordIdentifier] = nil;
+    __typeof(self) strongSelf = weakSelf;
+    strongSelf.memoryStorage[recordIdentifier] = nil;
   });
 }
 
@@ -97,7 +102,8 @@
   __block id<Credential> credential;
   __weak __typeof(self) weakSelf = self;
   dispatch_sync(self.workingQueue, ^{
-    credential = weakSelf.memoryStorage[recordIdentifier];
+    __typeof(self) strongSelf = weakSelf;
+    credential = strongSelf.memoryStorage[recordIdentifier];
   });
   return credential;
 }
diff --git a/ios/chrome/common/ui/promo_style/promo_style_view_controller.mm b/ios/chrome/common/ui/promo_style/promo_style_view_controller.mm
index a92943b..77aea96 100644
--- a/ios/chrome/common/ui/promo_style/promo_style_view_controller.mm
+++ b/ios/chrome/common/ui/promo_style/promo_style_view_controller.mm
@@ -1671,4 +1671,12 @@
   textView.selectedTextRange = nil;
 }
 
+#pragma mark - UIResponder
+
+// To always be able to register key commands via -keyCommands, the VC must be
+// able to become first responder.
+- (BOOL)canBecomeFirstResponder {
+  return YES;
+}
+
 @end
diff --git a/ios/google_internal/frameworks/ChromeExtensionKeychainInternal.framework.dSYM.ios.zip.sha1 b/ios/google_internal/frameworks/ChromeExtensionKeychainInternal.framework.dSYM.ios.zip.sha1
index d38caf1..0ff3a37b 100644
--- a/ios/google_internal/frameworks/ChromeExtensionKeychainInternal.framework.dSYM.ios.zip.sha1
+++ b/ios/google_internal/frameworks/ChromeExtensionKeychainInternal.framework.dSYM.ios.zip.sha1
@@ -1 +1 @@
-32ccc3f93d00d8ac14cd09cdaa607e7823d3dacd
\ No newline at end of file
+a28aa5759f7c379a8090ac0054d59e6a44b1160a
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/ChromeExtensionKeychainInternal.framework.dSYM.ios_asan.zip.sha1 b/ios/google_internal/frameworks/ChromeExtensionKeychainInternal.framework.dSYM.ios_asan.zip.sha1
index 97841eca..d4bd262 100644
--- a/ios/google_internal/frameworks/ChromeExtensionKeychainInternal.framework.dSYM.ios_asan.zip.sha1
+++ b/ios/google_internal/frameworks/ChromeExtensionKeychainInternal.framework.dSYM.ios_asan.zip.sha1
@@ -1 +1 @@
-26976b5c546328149e878bc15f62ff9dbc863709
\ No newline at end of file
+b566139bd99d64d6a8c23a963ee250dca7226d6d
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/ChromeInternal.framework.dSYM.ios.zip.sha1 b/ios/google_internal/frameworks/ChromeInternal.framework.dSYM.ios.zip.sha1
index aa5c6ed..0ecb929 100644
--- a/ios/google_internal/frameworks/ChromeInternal.framework.dSYM.ios.zip.sha1
+++ b/ios/google_internal/frameworks/ChromeInternal.framework.dSYM.ios.zip.sha1
@@ -1 +1 @@
-8f9f7a1e2620866765728ee2868d915b1680e6d4
\ No newline at end of file
+f2465cf03f3797f92a28368285dd57daf07771a5
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/ChromeInternal.framework.dSYM.ios_asan.zip.sha1 b/ios/google_internal/frameworks/ChromeInternal.framework.dSYM.ios_asan.zip.sha1
index 42ba8f2..8a62707 100644
--- a/ios/google_internal/frameworks/ChromeInternal.framework.dSYM.ios_asan.zip.sha1
+++ b/ios/google_internal/frameworks/ChromeInternal.framework.dSYM.ios_asan.zip.sha1
@@ -1 +1 @@
-0ba53d450972ade5d1dea14ae67aaf28e6d8d771
\ No newline at end of file
+772bcf33738b8b262033cf7fe811af80fcf7311c
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/ChromeSSOInternal.framework.dSYM.ios.zip.sha1 b/ios/google_internal/frameworks/ChromeSSOInternal.framework.dSYM.ios.zip.sha1
index f464180..0a73e2d 100644
--- a/ios/google_internal/frameworks/ChromeSSOInternal.framework.dSYM.ios.zip.sha1
+++ b/ios/google_internal/frameworks/ChromeSSOInternal.framework.dSYM.ios.zip.sha1
@@ -1 +1 @@
-43da453ca1756caa44c9be17a9171d9f545b77dd
\ No newline at end of file
+6eb9a67d2961b1acdcff2a33fea50577a27872b7
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/ChromeSSOInternal.framework.dSYM.ios_asan.zip.sha1 b/ios/google_internal/frameworks/ChromeSSOInternal.framework.dSYM.ios_asan.zip.sha1
index 8a6a7fb..2a3f33d1 100644
--- a/ios/google_internal/frameworks/ChromeSSOInternal.framework.dSYM.ios_asan.zip.sha1
+++ b/ios/google_internal/frameworks/ChromeSSOInternal.framework.dSYM.ios_asan.zip.sha1
@@ -1 +1 @@
-f09c17c3153b807ca569b34055729e69565d064f
\ No newline at end of file
+93337102a9b412a81e2a6364ae808992d830191a
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/chrome_extension_keychain_internal_dynamic_framework.ios.zip.sha1 b/ios/google_internal/frameworks/chrome_extension_keychain_internal_dynamic_framework.ios.zip.sha1
index cb444ab..088414fd 100644
--- a/ios/google_internal/frameworks/chrome_extension_keychain_internal_dynamic_framework.ios.zip.sha1
+++ b/ios/google_internal/frameworks/chrome_extension_keychain_internal_dynamic_framework.ios.zip.sha1
@@ -1 +1 @@
-bea6bda527a157f26f293d3cd5ae8417b2a09df1
\ No newline at end of file
+555fa32e40ad873dbb38871cbfcb5cffd632bf37
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/chrome_extension_keychain_internal_dynamic_framework.ios_asan.zip.sha1 b/ios/google_internal/frameworks/chrome_extension_keychain_internal_dynamic_framework.ios_asan.zip.sha1
index a54903d4..d1c087d 100644
--- a/ios/google_internal/frameworks/chrome_extension_keychain_internal_dynamic_framework.ios_asan.zip.sha1
+++ b/ios/google_internal/frameworks/chrome_extension_keychain_internal_dynamic_framework.ios_asan.zip.sha1
@@ -1 +1 @@
-ef2353505614bc005764c06101403a6d2053d06b
\ No newline at end of file
+4fcd878779a0fd0786076e27b69f4d4d1df0b190
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/chrome_extension_keychain_internal_dynamic_framework.iossimulator.zip.sha1 b/ios/google_internal/frameworks/chrome_extension_keychain_internal_dynamic_framework.iossimulator.zip.sha1
index 169b3ace..61fbf4e 100644
--- a/ios/google_internal/frameworks/chrome_extension_keychain_internal_dynamic_framework.iossimulator.zip.sha1
+++ b/ios/google_internal/frameworks/chrome_extension_keychain_internal_dynamic_framework.iossimulator.zip.sha1
@@ -1 +1 @@
-e62250c30af3541ff14733a376186639cee8aa4a
\ No newline at end of file
+6edef7e600d57cdd4cbcc1e4e91964c6d1e1605e
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/chrome_extension_keychain_internal_dynamic_framework.iossimulator_asan.zip.sha1 b/ios/google_internal/frameworks/chrome_extension_keychain_internal_dynamic_framework.iossimulator_asan.zip.sha1
index 38806de8..6a305f46 100644
--- a/ios/google_internal/frameworks/chrome_extension_keychain_internal_dynamic_framework.iossimulator_asan.zip.sha1
+++ b/ios/google_internal/frameworks/chrome_extension_keychain_internal_dynamic_framework.iossimulator_asan.zip.sha1
@@ -1 +1 @@
-300e578fcbc95d0dfdc34aac824be4852729a8c8
\ No newline at end of file
+1e0f73377e6e49d66a8e8d598c3a7736ead1b39f
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/chrome_internal_dynamic_framework.ios.zip.sha1 b/ios/google_internal/frameworks/chrome_internal_dynamic_framework.ios.zip.sha1
index 9d76933..5fe5438 100644
--- a/ios/google_internal/frameworks/chrome_internal_dynamic_framework.ios.zip.sha1
+++ b/ios/google_internal/frameworks/chrome_internal_dynamic_framework.ios.zip.sha1
@@ -1 +1 @@
-ec116b21484253a6d681927bdcd5f11a6a054bd0
\ No newline at end of file
+3fb217964a346f412323189c437887b6aa647d0c
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/chrome_internal_dynamic_framework.ios_asan.zip.sha1 b/ios/google_internal/frameworks/chrome_internal_dynamic_framework.ios_asan.zip.sha1
index 6bf95f44..83576bda 100644
--- a/ios/google_internal/frameworks/chrome_internal_dynamic_framework.ios_asan.zip.sha1
+++ b/ios/google_internal/frameworks/chrome_internal_dynamic_framework.ios_asan.zip.sha1
@@ -1 +1 @@
-57a9bead890d6b84c41c7321d6d22aaaed012072
\ No newline at end of file
+5bb07d3e4c12b404477b3811debddf4bc1b00438
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/chrome_internal_dynamic_framework.iossimulator.zip.sha1 b/ios/google_internal/frameworks/chrome_internal_dynamic_framework.iossimulator.zip.sha1
index 322899f..665ebdc 100644
--- a/ios/google_internal/frameworks/chrome_internal_dynamic_framework.iossimulator.zip.sha1
+++ b/ios/google_internal/frameworks/chrome_internal_dynamic_framework.iossimulator.zip.sha1
@@ -1 +1 @@
-72b805f7f3094f5aba745db5233abf1ac30aa56e
\ No newline at end of file
+b7679d66d068e8712b0d0ef223953176dcd8b6cb
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/chrome_internal_dynamic_framework.iossimulator_asan.zip.sha1 b/ios/google_internal/frameworks/chrome_internal_dynamic_framework.iossimulator_asan.zip.sha1
index 965534de..1c658d52 100644
--- a/ios/google_internal/frameworks/chrome_internal_dynamic_framework.iossimulator_asan.zip.sha1
+++ b/ios/google_internal/frameworks/chrome_internal_dynamic_framework.iossimulator_asan.zip.sha1
@@ -1 +1 @@
-865645a67576c54bc195bd9633bbd6274c9e8916
\ No newline at end of file
+dc0433eea1abb9ec2ab1ec714c37d2c47ef7a43e
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.ios.zip.sha1 b/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.ios.zip.sha1
index 71d4baea..73881e60 100644
--- a/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.ios.zip.sha1
+++ b/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.ios.zip.sha1
@@ -1 +1 @@
-04fe86691415dcc8a3a6fb4c5a5064ff64013577
\ No newline at end of file
+601a8187dc7b887bc861946edfb7ce96579520ac
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.ios_asan.zip.sha1 b/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.ios_asan.zip.sha1
index 0b55576..a09bf8c 100644
--- a/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.ios_asan.zip.sha1
+++ b/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.ios_asan.zip.sha1
@@ -1 +1 @@
-95df631faab9f118bdad5a18517576ffe51e9a9f
\ No newline at end of file
+f787ae0df7e7775f12d7197391238ce23b5bf2c9
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.iossimulator.zip.sha1 b/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.iossimulator.zip.sha1
index 2009267..fa06934b 100644
--- a/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.iossimulator.zip.sha1
+++ b/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.iossimulator.zip.sha1
@@ -1 +1 @@
-2e17777dd56d3d1695687546cfb77e4d361d8bd3
\ No newline at end of file
+4820af256a49cd236476b23c828ffab7721ce6e7
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.iossimulator_asan.zip.sha1 b/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.iossimulator_asan.zip.sha1
index 19983df5..aa94223 100644
--- a/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.iossimulator_asan.zip.sha1
+++ b/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.iossimulator_asan.zip.sha1
@@ -1 +1 @@
-44ab110812e5ea95ee581b5b643858ef438a0c0c
\ No newline at end of file
+a20581643fddd6517580042fdcb6f8d5d756953a
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/chrome_test_internal_dynamic_framework.ios.zip.sha1 b/ios/google_internal/frameworks/chrome_test_internal_dynamic_framework.ios.zip.sha1
index 87936cf..ef1d2fe 100644
--- a/ios/google_internal/frameworks/chrome_test_internal_dynamic_framework.ios.zip.sha1
+++ b/ios/google_internal/frameworks/chrome_test_internal_dynamic_framework.ios.zip.sha1
@@ -1 +1 @@
-524826bbaba7ceabe940250bbc8daceb08d9626e
\ No newline at end of file
+58639708d5608950a741fadc768735e98201e7b5
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/chrome_test_internal_dynamic_framework.iossimulator.zip.sha1 b/ios/google_internal/frameworks/chrome_test_internal_dynamic_framework.iossimulator.zip.sha1
index 5b4fe26e..85252ec 100644
--- a/ios/google_internal/frameworks/chrome_test_internal_dynamic_framework.iossimulator.zip.sha1
+++ b/ios/google_internal/frameworks/chrome_test_internal_dynamic_framework.iossimulator.zip.sha1
@@ -1 +1 @@
-5a509060d05fe558bdc847142cf2dc86cd5d69a3
\ No newline at end of file
+808568b4a11ee3deea3371241ffd6fb06ed2bb8a
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/remoting_internal_dynamic_framework.ios.zip.sha1 b/ios/google_internal/frameworks/remoting_internal_dynamic_framework.ios.zip.sha1
index 5bccf1f..a1773af9 100644
--- a/ios/google_internal/frameworks/remoting_internal_dynamic_framework.ios.zip.sha1
+++ b/ios/google_internal/frameworks/remoting_internal_dynamic_framework.ios.zip.sha1
@@ -1 +1 @@
-621efc5af58f26ae392ae4c857b8ac943c46c843
\ No newline at end of file
+31a46210c10aeeab966f91ec1c92ec6b149d79f0
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/remoting_internal_dynamic_framework.ios_asan.zip.sha1 b/ios/google_internal/frameworks/remoting_internal_dynamic_framework.ios_asan.zip.sha1
index b1bfb7c..7781c8ab1 100644
--- a/ios/google_internal/frameworks/remoting_internal_dynamic_framework.ios_asan.zip.sha1
+++ b/ios/google_internal/frameworks/remoting_internal_dynamic_framework.ios_asan.zip.sha1
@@ -1 +1 @@
-5de976c38a87787ab2bf8de9fb25ec0a36856f35
\ No newline at end of file
+bb7811d757c0fc080f9d593fafe6a33c1a0ff8e3
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/remoting_internal_dynamic_framework.iossimulator.zip.sha1 b/ios/google_internal/frameworks/remoting_internal_dynamic_framework.iossimulator.zip.sha1
index 7838fc8..ae57487 100644
--- a/ios/google_internal/frameworks/remoting_internal_dynamic_framework.iossimulator.zip.sha1
+++ b/ios/google_internal/frameworks/remoting_internal_dynamic_framework.iossimulator.zip.sha1
@@ -1 +1 @@
-54fac699d29b9092e67fe3239bd77b4e283d1a50
\ No newline at end of file
+5ef463f797d58ba67506673424df372d07fecd01
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/remoting_internal_dynamic_framework.iossimulator_asan.zip.sha1 b/ios/google_internal/frameworks/remoting_internal_dynamic_framework.iossimulator_asan.zip.sha1
index 81959ec..5571c5e2 100644
--- a/ios/google_internal/frameworks/remoting_internal_dynamic_framework.iossimulator_asan.zip.sha1
+++ b/ios/google_internal/frameworks/remoting_internal_dynamic_framework.iossimulator_asan.zip.sha1
@@ -1 +1 @@
-3cf80b38e815e3735b3c0d3a0a4a49e44929699b
\ No newline at end of file
+dee8f9e4a49c77020e60caa501f5681dbbff70b7
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.ios.zip.sha1 b/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.ios.zip.sha1
index c59dd61..a9b59e62 100644
--- a/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.ios.zip.sha1
+++ b/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.ios.zip.sha1
@@ -1 +1 @@
-7f82b0dd8e6479d15b8125ca6f7299bf473d55a7
\ No newline at end of file
+167e22d0116d8277dc7ed4bed69a17ccf537f746
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.ios_asan.zip.sha1 b/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.ios_asan.zip.sha1
index d3407414..7e3214d8 100644
--- a/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.ios_asan.zip.sha1
+++ b/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.ios_asan.zip.sha1
@@ -1 +1 @@
-56dd8e228c172a8d913757709a8e53538fdecad0
\ No newline at end of file
+5d6a40c46d724082f4681442f6975286134c5e3c
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.iossimulator.zip.sha1 b/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.iossimulator.zip.sha1
index a59c48e..515d132 100644
--- a/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.iossimulator.zip.sha1
+++ b/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.iossimulator.zip.sha1
@@ -1 +1 @@
-7c7f88585f8d40471e7f1b7612edcbdb0018196b
\ No newline at end of file
+9af94ebf9da33ddd8ddc6f877ee967c46a8897cd
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.iossimulator_asan.zip.sha1 b/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.iossimulator_asan.zip.sha1
index 7069298..6d1b9d4d 100644
--- a/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.iossimulator_asan.zip.sha1
+++ b/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.iossimulator_asan.zip.sha1
@@ -1 +1 @@
-d09e10dad509752c89c4c27a06e0ac4e9a90e77b
\ No newline at end of file
+24a3085b7a3de7e4365a2dc3e83044500e93ec27
\ No newline at end of file
diff --git a/ios/web/browsing_data/browsing_data_removing_util.mm b/ios/web/browsing_data/browsing_data_removing_util.mm
index 4dbdc47..7ddb2cd 100644
--- a/ios/web/browsing_data/browsing_data_removing_util.mm
+++ b/ios/web/browsing_data/browsing_data_removing_util.mm
@@ -39,8 +39,11 @@
   if (IsRemoveDataMaskSet(types, ClearBrowsingDataMask::kRemoveCookies)) {
     [result addObject:WKWebsiteDataTypeCookies];
   }
-  if (IsRemoveDataMaskSet(types, ClearBrowsingDataMask::kRemoveOriginPrivateFileSystem)) {
-    [result addObject:WKWebsiteDataTypeFileSystem];
+  if (IsRemoveDataMaskSet(
+          types, ClearBrowsingDataMask::kRemoveOriginPrivateFileSystem)) {
+    if (@available(iOS 16.0, *)) {
+      [result addObject:WKWebsiteDataTypeFileSystem];
+    }
   }
   return result;
 }
diff --git a/ios/web/public/test/js_test_util.h b/ios/web/public/test/js_test_util.h
index f3420f3..b0ae74c 100644
--- a/ios/web/public/test/js_test_util.h
+++ b/ios/web/public/test/js_test_util.h
@@ -26,6 +26,12 @@
                                 NSString* script,
                                 NSError* __autoreleasing* error);
 
+// Synchronously executes JavaScript in the content world associated with
+// `feature`.
+void ExecuteJavaScriptForFeature(web::WebState* web_state,
+                                 NSString* script,
+                                 JavaScriptFeature* feature);
+
 // These functions synchronously execute JavaScript and return result as id.
 // id will be backed up by different classes depending on resulting JS type:
 // NSString (string), NSNumber (number or boolean), NSDictionary (object),
@@ -34,19 +40,20 @@
 
 // Executes JavaScript on `web_view` and returns the result as an id.
 // `error` can be null and will be updated only if script execution fails.
-id ExecuteJavaScript(WKWebView* web_view,
-                     NSString* script,
-                     NSError* __autoreleasing* error);
+[[nodiscard]] id ExecuteJavaScript(WKWebView* web_view,
+                                   NSString* script,
+                                   NSError* __autoreleasing* error);
 
 // Executes JavaScript on `web_view` and returns the result as an id.
 // Fails if there was an error during script execution.
-id ExecuteJavaScript(WKWebView* web_view, NSString* script);
+[[nodiscard]] id ExecuteJavaScript(WKWebView* web_view, NSString* script);
 
 // Synchronously executes JavaScript in the content world associated with
 // `feature` and returns the result as id.
-id ExecuteJavaScriptForFeature(web::WebState* web_state,
-                               NSString* script,
-                               JavaScriptFeature* feature);
+[[nodiscard]] id ExecuteJavaScriptForFeatureAndReturnResult(
+    web::WebState* web_state,
+    NSString* script,
+    JavaScriptFeature* feature);
 
 // Synchronously loads `html` into `web_view`. Returns true is successful or
 // false if the `web_view` never finishes loading.
diff --git a/ios/web/public/test/js_test_util.mm b/ios/web/public/test/js_test_util.mm
index a3037f1..167582d6 100644
--- a/ios/web/public/test/js_test_util.mm
+++ b/ios/web/public/test/js_test_util.mm
@@ -96,6 +96,21 @@
   ExecuteJavaScript(web_view, script, error, /*result=*/nil);
 }
 
+void ExecuteJavaScriptForFeature(web::WebState* web_state,
+                                 NSString* script,
+                                 JavaScriptFeature* feature) {
+  JavaScriptFeatureManager* feature_manager =
+      JavaScriptFeatureManager::FromBrowserState(web_state->GetBrowserState());
+  JavaScriptContentWorld* world =
+      feature_manager->GetContentWorldForFeature(feature);
+
+  WKWebView* web_view =
+      [web::WebStateImpl::FromWebState(web_state)->GetWebController()
+          ensureWebViewCreated];
+  web::test::ExecuteJavaScriptInWebViewAndWorld(
+      web_view, world->GetWKContentWorld(), script);
+}
+
 id ExecuteJavaScript(WKWebView* web_view, NSString* script) {
   return ExecuteJavaScript(web_view, script, /*error=*/nil);
 }
@@ -108,9 +123,9 @@
   return result;
 }
 
-id ExecuteJavaScriptForFeature(web::WebState* web_state,
-                               NSString* script,
-                               JavaScriptFeature* feature) {
+id ExecuteJavaScriptForFeatureAndReturnResult(web::WebState* web_state,
+                                              NSString* script,
+                                              JavaScriptFeature* feature) {
   JavaScriptFeatureManager* feature_manager =
       JavaScriptFeatureManager::FromBrowserState(web_state->GetBrowserState());
   JavaScriptContentWorld* world =
diff --git a/ios/web/test/js_test_util_internal.h b/ios/web/test/js_test_util_internal.h
index fb97a14..b4a3550 100644
--- a/ios/web/test/js_test_util_internal.h
+++ b/ios/web/test/js_test_util_internal.h
@@ -23,15 +23,15 @@
                                         NSString* script);
 
 // Synchronously executes `script` in `content_world` and returns result.
-id ExecuteJavaScript(WKWebView* web_view,
-                     WKContentWorld* content_world,
-                     NSString* script);
+[[nodiscard]] id ExecuteJavaScript(WKWebView* web_view,
+                                   WKContentWorld* content_world,
+                                   NSString* script);
 
 // Executes `script` in `content_world` as an asynchronous JavaScript function,
 // waits for execution to complete, and returns the result.
-id ExecuteAsyncJavaScript(WKWebView* web_view,
-                          WKContentWorld* content_world,
-                          NSString* script);
+[[nodiscard]] id ExecuteAsyncJavaScript(WKWebView* web_view,
+                                        WKContentWorld* content_world,
+                                        NSString* script);
 
 }  // namespace test
 }  // namespace web
diff --git a/ios_internal b/ios_internal
index 065e00ac..0d3b8b6 160000
--- a/ios_internal
+++ b/ios_internal
@@ -1 +1 @@
-Subproject commit 065e00ac78fc26a82ff3ced278f588693dd700af
+Subproject commit 0d3b8b6b0a45531fe2f7717be2f2227d7acf9f4b
diff --git a/media/audio/BUILD.gn b/media/audio/BUILD.gn
index d133d2759..5064c9e 100644
--- a/media/audio/BUILD.gn
+++ b/media/audio/BUILD.gn
@@ -425,6 +425,10 @@
       "alsa/mock_alsa_wrapper.h",
     ]
   }
+
+  # TODO(crbug.com/40031409): Fix code that adds exit-time destructors and
+  # enable the diagnostic by removing this line.
+  configs += [ "//build/config/compiler:no_exit_time_destructors" ]
 }
 
 source_set("unit_tests") {
@@ -552,6 +556,10 @@
       "audio_low_latency_input_output_unittest.cc",
     ]
   }
+
+  # TODO(crbug.com/40031409): Fix code that adds exit-time destructors and
+  # enable the diagnostic by removing this line.
+  configs += [ "//build/config/compiler:no_exit_time_destructors" ]
 }
 
 fuzzer_test("wav_audio_handler_fuzzer") {
diff --git a/media/base/BUILD.gn b/media/base/BUILD.gn
index de17808..46cc3ff 100644
--- a/media/base/BUILD.gn
+++ b/media/base/BUILD.gn
@@ -714,6 +714,10 @@
   if (is_castos) {
     sources += [ "demuxer_memory_limit_cast_unittest.cc" ]
   }
+
+  # TODO(crbug.com/40031409): Fix code that adds exit-time destructors and
+  # enable the diagnostic by removing this line.
+  configs += [ "//build/config/compiler:no_exit_time_destructors" ]
 }
 
 source_set("perftests") {
diff --git a/media/base/media_switches.cc b/media/base/media_switches.cc
index 3cf082a..444c84d4c 100644
--- a/media/base/media_switches.cc
+++ b/media/base/media_switches.cc
@@ -1096,6 +1096,13 @@
              "AllowNonSecureOverlays",
              base::FEATURE_ENABLED_BY_DEFAULT);
 
+// Enables automatic Picture-in-Picture on Android for supported websites.
+// This triggers for active video playback or camera/microphone usage on sites
+// that have registered an auto picture-in-picture action.
+BASE_FEATURE(kAutoPictureInPictureAndroid,
+             "AutoPictureInPictureAndroid",
+             base::FEATURE_DISABLED_BY_DEFAULT);
+
 // Enables block model (LinearBlock) on supported devices.
 BASE_FEATURE(kMediaCodecBlockModel,
              "MediaCodecBlockModel",
diff --git a/media/base/media_switches.h b/media/base/media_switches.h
index 6894c952..fc967bc 100644
--- a/media/base/media_switches.h
+++ b/media/base/media_switches.h
@@ -390,6 +390,7 @@
 
 #if BUILDFLAG(IS_ANDROID)
 MEDIA_EXPORT BASE_DECLARE_FEATURE(kAllowNonSecureOverlays);
+MEDIA_EXPORT BASE_DECLARE_FEATURE(kAutoPictureInPictureAndroid);
 MEDIA_EXPORT BASE_DECLARE_FEATURE(kMediaCodecBlockModel);
 MEDIA_EXPORT BASE_DECLARE_FEATURE(kMediaCodecCodedSizeGuessing);
 MEDIA_EXPORT BASE_DECLARE_FEATURE(kMediaCodecElideEOS);
diff --git a/media/base/ranges.h b/media/base/ranges.h
index d40d8237..98fcfeb2 100644
--- a/media/base/ranges.h
+++ b/media/base/ranges.h
@@ -49,6 +49,9 @@
   // Clear all ranges.
   void clear();
 
+  // Removes the first range.
+  void pop_front();
+
   // Computes the intersection between this range and |other|.
   Ranges<T> IntersectionWith(const Ranges<T>& other) const;
 
@@ -64,6 +67,11 @@
 // EVERYTHING BELOW HERE IS IMPLEMENTATION DETAIL!!
 //////////////////////////////////////////////////////////////////////
 
+template <typename T>
+void Ranges<T>::pop_front() {
+  ranges_.erase(ranges_.begin());
+}
+
 template<class T>
 size_t Ranges<T>::Add(T start, T end) {
   if (start == end)  // Nothing to be done with empty ranges.
diff --git a/media/capture/BUILD.gn b/media/capture/BUILD.gn
index 409e80bd..92eeda5 100644
--- a/media/capture/BUILD.gn
+++ b/media/capture/BUILD.gn
@@ -386,6 +386,10 @@
       "//third_party/libyuv",
     ]
   }
+
+  # TODO(crbug.com/40031409): Fix code that adds exit-time destructors and
+  # enable the diagnostic by removing this line.
+  configs += [ "//build/config/compiler:no_exit_time_destructors" ]
 }
 
 source_set("capture_gpu_channel") {
diff --git a/media/cast/BUILD.gn b/media/cast/BUILD.gn
index da18ae4..7fa20a0c 100644
--- a/media/cast/BUILD.gn
+++ b/media/cast/BUILD.gn
@@ -253,6 +253,10 @@
       "//testing/android/native_test:native_test_native_code",
     ]
   }
+
+  # TODO(crbug.com/40031409): Fix code that adds exit-time destructors and
+  # enable the diagnostic by removing this line.
+  configs += [ "//build/config/compiler:no_exit_time_destructors" ]
 }
 
 fuzzer_test("media_vpx_quantizer_parser_fuzzer") {
diff --git a/media/filters/BUILD.gn b/media/filters/BUILD.gn
index e43652be..3e829d0 100644
--- a/media/filters/BUILD.gn
+++ b/media/filters/BUILD.gn
@@ -409,6 +409,10 @@
       "manifest_demuxer_unittest.cc",
       "stream_parser_factory_unittest.cc",
     ]
+
+    # TODO(crbug.com/40031409): Fix code that adds exit-time destructors and
+    # enable the diagnostic by removing this line.
+    configs += [ "//build/config/compiler:no_exit_time_destructors" ]
   }
 
   if (media_use_ffmpeg) {
diff --git a/media/filters/hls_manifest_demuxer_engine_unittest.cc b/media/filters/hls_manifest_demuxer_engine_unittest.cc
index 61769d8..b843c90 100644
--- a/media/filters/hls_manifest_demuxer_engine_unittest.cc
+++ b/media/filters/hls_manifest_demuxer_engine_unittest.cc
@@ -517,12 +517,15 @@
 
   EXPECT_CALL(*mock_mdeh_, GetBufferedRanges(_))
       .WillOnce(Return(Ranges<base::TimeDelta>()))  // First CheckState
+      .WillOnce(Return(Ranges<base::TimeDelta>()))  // Before appending A
       .WillOnce(Return(after_seg_a))                // After appending segment A
       .WillOnce(Return(after_seg_a))                // Second CheckState
+      .WillOnce(Return(after_seg_a))                // Before appending B
       .WillOnce(Return(after_seg_b))                // After appending segment B
       .WillOnce(Return(after_seg_b))                // MediaLog
       .WillOnce(Return(after_seg_b))                // Third CheckState
       .WillOnce(Return(after_seg_b))                // Fourth CheckState
+      .WillOnce(Return(after_seg_b))                // Before appending C
       .WillOnce(Return(after_seg_c))                // After appending segment C
       .WillOnce(Return(after_seg_c))                // MediaLog
       .WillOnce(Return(after_seg_c))                // Fifth CheckState
@@ -952,12 +955,13 @@
 
   // `GetBufferedRanges` gets called many times during this process:
   // - HlsVodRendition::CheckState (1) => empty ranges, nothing loaded.
-  // - HlsVodRendition::OnSegmentData (1) => populated by AppendAndParseData
+  // - HlsVodRendition::OnSegmentData (2) => populated by AppendAndParseData
   // - HlsVodRendition::CheckState (2) => still has data
   Ranges<base::TimeDelta> populated_ranges;
   populated_ranges.Add(base::Seconds(0), base::Seconds(5));
   EXPECT_CALL(*mock_mdeh_, GetBufferedRanges(_))
       .WillOnce(Return(Ranges<base::TimeDelta>()))
+      .WillOnce(Return(Ranges<base::TimeDelta>()))
       .WillOnce(Return(populated_ranges))
       .WillOnce(Return(populated_ranges));
 
diff --git a/media/filters/hls_rendition_impl.cc b/media/filters/hls_rendition_impl.cc
index dca6d00..dc26f99 100644
--- a/media/filters/hls_rendition_impl.cc
+++ b/media/filters/hls_rendition_impl.cc
@@ -144,14 +144,49 @@
     return;
   }
 
-  // If media time comes before the last loaded range, then a seek probably
-  // failed, and we should raise an error.
-  if (std::get<0>(ranges.back()) > media_time) {
+  // If media time is inside the first loaded range, things are OK.
+  // If it comes inside the first `gapless_playback_seek_skip_` or inside the
+  // second loaded range, we've probably hit one of those annoying muxing
+  // issues where we need to seek ahead. See crbug.com/429060503 for more
+  // information.
+  if (ranges.contains(0, media_time)) {
+    // The media time is already buffered. The buffer checks and below will
+    // decide what to do about this.
+  } else if (gapless_playback_seek_skip_.size()) {
+    // There should always be one more range than gaps between ranges, for
+    // obvious reasons.
+    DCHECK_EQ(ranges.size(), gapless_playback_seek_skip_.size() + 1);
+
+    if (gapless_playback_seek_skip_.contains(0, media_time) ||
+        ranges.contains(1, media_time)) {
+      // Seeking into a loaded range won't drop anything, so do it here, and
+      // drop the gapless ranges to match it.
+      engine_host_->Remove(role_, base::TimeDelta(),
+                           gapless_playback_seek_skip_.start(0));
+      gapless_playback_seek_skip_.pop_front();
+      ranges = engine_host_->GetBufferedRanges(role_);
+      if (ranges.size()) {
+        engine_host_->RequestSeek(ranges.start(0));
+        std::move(time_remaining_cb).Run(kNoTimestamp);
+        return;
+      }
+    }
+
+    // Media time wasn't in an expected place - this is probably caused by a
+    // really bad muxing situation with lots of dropped frames all close to
+    // each other. This probably isn't worth trying to play.
     HlsDemuxerStatus error = HlsDemuxerStatus::Codes::kInvalidLoadedRanges;
     rendition_host_->Quit(std::move(error)
                               .WithData("timestamp", media_time)
-                              .WithData("range_start", ranges.back().first)
-                              .WithData("range_end", ranges.back().second));
+                              .WithData("ranges", ranges));
+    return;
+  } else if (std::get<0>(ranges.back()) > media_time) {
+    // All loaded ranges are ahead of us, which means there was probably a
+    // failed seek.
+    HlsDemuxerStatus error = HlsDemuxerStatus::Codes::kInvalidLoadedRanges;
+    rendition_host_->Quit(std::move(error)
+                              .WithData("timestamp", media_time)
+                              .WithData("ranges", ranges));
     return;
   }
 
@@ -483,6 +518,10 @@
                                    &parse_offset_);
   }
 
+  // Sometimes slight muxing errors can introduce a very small gap in playback
+  // which we will need to seek over, like a normal MSE player.
+  auto ranges_size_pre_append = engine_host_->GetBufferedRanges(role_).size();
+
   if (!engine_host_->AppendAndParseData(role_, parse_end + base::Seconds(1),
                                         &parse_offset_, stream_data)) {
     rendition_host_->Quit(HlsDemuxerStatus::Codes::kCouldNotAppendData);
@@ -507,7 +546,32 @@
   auto ranges = engine_host_->GetBufferedRanges(role_);
   media_log_->SetProperty<MediaLogProperty::kHlsBufferedRanges>(ranges);
 
-  if (ranges.size() && ranges.contains(ranges.size() - 1, required_time)) {
+  // This append/parse has added a playback gap! This is likely caused by a
+  // bad timestamp on a frame or some other muxing error. If the gap is small,
+  // it's probably OK to just skip over it.
+  auto last_end = ranges.size() ? ranges.start(0) : base::Seconds(0);
+  bool contains_required_time = false;
+  for (size_t i = 0; i < ranges.size(); i++) {
+    auto gap = ranges.start(i) - last_end;
+    contains_required_time |= ranges.contains(i, required_time);
+    if (gap.is_zero()) {
+      // The first group has a "gap" of zero, and shouldn't do these checks.
+    } else if (gap < base::Seconds(1)) {
+      // Record this as an "autoseek point". Also handle the case where required
+      // time is inside the gap.
+      gapless_playback_seek_skip_.Add(last_end, ranges.start(i));
+      contains_required_time |=
+          gapless_playback_seek_skip_.contains(i - 1, required_time);
+    } else if (ranges.size() > ranges_size_pre_append) {
+      HlsDemuxerStatus error = HlsDemuxerStatus::Codes::kInvalidLoadedRanges;
+      rendition_host_->Quit(std::move(error)
+                                .WithData("required_time", required_time)
+                                .WithData("ranges", ranges));
+    }
+    last_end = ranges.end(i);
+  }
+
+  if (contains_required_time) {
     std::move(cb).Run();
     return;
   }
diff --git a/media/filters/hls_rendition_impl.h b/media/filters/hls_rendition_impl.h
index 8ee452d2..555bd5dc 100644
--- a/media/filters/hls_rendition_impl.h
+++ b/media/filters/hls_rendition_impl.h
@@ -124,6 +124,9 @@
 
   std::unique_ptr<MediaLog> media_log_;
 
+  // What time ranges we should skip due to missing frames.
+  Ranges<base::TimeDelta> gapless_playback_seek_skip_;
+
   // toggleable bool flags.
   bool set_stream_end_ = false;
   bool is_stopped_for_shutdown_ = false;
diff --git a/media/filters/hls_rendition_impl_unittest.cc b/media/filters/hls_rendition_impl_unittest.cc
index 4de4eff..9b2aeda 100644
--- a/media/filters/hls_rendition_impl_unittest.cc
+++ b/media/filters/hls_rendition_impl_unittest.cc
@@ -153,6 +153,7 @@
 
 using testing::_;
 using testing::ElementsAreArray;
+using testing::InSequence;
 using testing::NiceMock;
 using testing::Return;
 
@@ -234,21 +235,18 @@
         .WillRepeatedly(Return(ranges));
   }
 
-  void RespondWithRangeTwice(base::TimeDelta A,
-                             base::TimeDelta B,
-                             base::TimeDelta X,
-                             base::TimeDelta Y) {
-    Ranges<base::TimeDelta> ab;
-    if (A != B) {
-      ab.Add(A, B);
+  void RespondWithRangeSet(
+      std::vector<std::tuple<base::TimeDelta, base::TimeDelta>> ranges) {
+    InSequence s;
+    for (const auto& pair : ranges) {
+      Ranges<base::TimeDelta> range;
+      if (std::get<0>(pair) != std::get<1>(pair)) {
+        range.Add(std::get<0>(pair), std::get<1>(pair));
+      }
+      EXPECT_CALL(*mock_mdeh_, GetBufferedRanges("test"))
+          .Times(1)
+          .WillOnce(Return(range));
     }
-    Ranges<base::TimeDelta> xy;
-    if (X != Y) {
-      xy.Add(X, Y);
-    }
-    EXPECT_CALL(*mock_mdeh_, GetBufferedRanges("test"))
-        .WillOnce(Return(ab))
-        .WillOnce(Return(xy));
   }
 
   void SupplyAndExpectJunkData(base::TimeDelta initial_response_start,
@@ -275,7 +273,8 @@
     appended_range.Add(fetch_expected_time - base::Seconds(1),
                        fetch_expected_time + base::Seconds(1));
     EXPECT_CALL(*mock_mdeh_, GetBufferedRanges("test"))
-        .Times(2)
+        .Times(3)
+        .WillOnce(Return(initial_range))
         .WillOnce(Return(initial_range))
         .WillOnce(Return(appended_range));
   }
@@ -405,8 +404,11 @@
 
   // CheckState causes the rentidion to:
   // Check buffered ranges first
-  RespondWithRangeTwice(base::Seconds(0), base::Seconds(0), base::Seconds(0),
-                        base::Seconds(5));
+  RespondWithRangeSet({
+      {base::Seconds(0), base::Seconds(0)},
+      {base::Seconds(0), base::Seconds(0)},
+      {base::Seconds(0), base::Seconds(5)},
+  });
   // The first segment will be queried
   std::string tscontent = "tscontent";
   RespondToUrl("http://example.com/playlist_4500Kb_14551245.ts", tscontent);
@@ -429,8 +431,9 @@
   // CheckState causes the rentidion to:
   // Check buffered ranges first. In this case, we've loaded a bunch of content
   // already, and our loaded ranges are [0 - 8)
-  RespondWithRangeTwice(base::Seconds(0), base::Seconds(8), base::Seconds(0),
-                        base::Seconds(16));
+  RespondWithRangeSet({{base::Seconds(0), base::Seconds(8)},
+                       {base::Seconds(0), base::Seconds(8)},
+                       {base::Seconds(0), base::Seconds(16)}});
 
   // The next unqueried segment will be queried
   std::string tscontent = "tscontent";
@@ -554,8 +557,11 @@
   std::string tscontent = "tscontent";
   RespondToUrl("http://example.com/playlist_4500Kb_14551245.ts", tscontent);
   RequireAppend(base::as_byte_span(tscontent));
-  RespondWithRangeTwice(base::Seconds(0), base::Seconds(0), base::Seconds(0),
-                        base::Seconds(2));
+  RespondWithRangeSet({
+      {base::Seconds(0), base::Seconds(0)},
+      {base::Seconds(0), base::Seconds(0)},
+      {base::Seconds(0), base::Seconds(2)},
+  });
   rendition->CheckState(base::Seconds(0), 0.0,
                         BindCheckState(base::Seconds(0)));
   task_environment_.RunUntilIdle();
@@ -616,8 +622,11 @@
   // is not exhausted (it's just been updated!) and so we fetch the next one.
   // After appending and parsing, it's brought our loaded ranges up to 202, and
   // the response to check state is to run it again in 0 seconds.
-  RespondWithRangeTwice(base::Seconds(0), base::Seconds(32), base::Seconds(0),
-                        base::Seconds(202));
+  RespondWithRangeSet({
+      {base::Seconds(0), base::Seconds(32)},
+      {base::Seconds(0), base::Seconds(32)},
+      {base::Seconds(0), base::Seconds(202)},
+  });
   std::string newcontent = "newcontent";
   RespondToUrl("http://example.com/playlist_4500Kb_14551349.ts", newcontent);
   RequireAppend(base::as_byte_span(newcontent));
@@ -629,8 +638,11 @@
   // requested again, this time for the next segment. Lets pretend that next
   // segment has 20 seconds of data in it, bringing new range end to 222. The
   // response will still be 0 seconds.
-  RespondWithRangeTwice(base::Seconds(0), base::Seconds(202), base::Seconds(0),
-                        base::Seconds(222));
+  RespondWithRangeSet({
+      {base::Seconds(0), base::Seconds(202)},
+      {base::Seconds(0), base::Seconds(202)},
+      {base::Seconds(0), base::Seconds(222)},
+  });
   newcontent = "blah";
   RespondToUrl("http://example.com/playlist_4500Kb_14551350.ts", "blah");
   RequireAppend(base::as_byte_span(newcontent));
@@ -649,34 +661,183 @@
   task_environment_.RunUntilIdle();
 }
 
+TEST_F(HlsRenditionImplUnittest, TestCantSkipOverLargeGaps) {
+  auto rendition = MakeVodRendition(kDiscontinuous);
+  ASSERT_NE(rendition, nullptr);
+  std::string content = "123";
+  Ranges<base::TimeDelta> empty_range;
+  Ranges<base::TimeDelta> split_range;
+  split_range.Add(base::Seconds(0), base::Milliseconds(998));
+  split_range.Add(base::Seconds(3), base::Milliseconds(3999));
+  EXPECT_CALL(*mock_mdeh_, GetBufferedRanges("test"))
+      .WillOnce(Return(empty_range))   // Once in CheckState
+      .WillOnce(Return(empty_range))   // Before appending
+      .WillOnce(Return(split_range));  // After appending
+  RespondToUrl("https://example.com/bip00.ts", content);
+  RequireAppend(base::as_byte_span(content));
+
+  // The ranges are more than 1 second apart, so failure occurs.
+  EXPECT_CALL(*mock_hrh_, Quit(_));
+  rendition->CheckState(base::Seconds(0), 0.0, BindCheck0Sec());
+}
+
+TEST_F(HlsRenditionImplUnittest, TestSkipsSmallSingleGap) {
+  auto rendition = MakeVodRendition(kDiscontinuous);
+  ASSERT_NE(rendition, nullptr);
+  std::string content = "123";
+  Ranges<base::TimeDelta> empty_range;
+  Ranges<base::TimeDelta> split_range;
+  Ranges<base::TimeDelta> end_range;
+  split_range.Add(base::Seconds(0), base::Milliseconds(998));
+  split_range.Add(base::Seconds(1), base::Seconds(2));
+  end_range.Add(base::Seconds(1), base::Seconds(2));
+  EXPECT_CALL(*mock_mdeh_, GetBufferedRanges("test"))
+      .WillOnce(Return(empty_range))   // Once in CheckState
+      .WillOnce(Return(empty_range))   // Before appending
+      .WillOnce(Return(split_range));  // After appending
+  RespondToUrl("https://example.com/bip00.ts", content);
+  RequireAppend(base::as_byte_span(content));
+  rendition->CheckState(base::Seconds(0), 0.0, BindCheck0Sec());
+
+  // The next check state comes in at time 0.999. The rendition impl clears the
+  // things before the gap and seeks to the end of the gap.
+  EXPECT_CALL(*mock_mdeh_, GetBufferedRanges("test"))
+      .WillOnce(Return(split_range))  // CheckState initial check
+      .WillOnce(Return(end_range));   // CheckState after removing buffers.
+  EXPECT_CALL(*mock_mdeh_,
+              Remove(_, base::Seconds(0), base::Milliseconds(998)));
+  EXPECT_CALL(*mock_mdeh_, RequestSeek(base::Seconds(1)));
+  rendition->CheckState(base::Seconds(1), 0.0, BindCheckState(kNoTimestamp));
+}
+
+TEST_F(HlsRenditionImplUnittest, TestCantSkipMultipleNearbyGaps) {
+  auto rendition = MakeVodRendition(kDiscontinuous);
+  ASSERT_NE(rendition, nullptr);
+  std::string content = "123";
+  Ranges<base::TimeDelta> empty_range;
+  Ranges<base::TimeDelta> split_range;
+  split_range.Add(base::Seconds(0), base::Milliseconds(998));
+  split_range.Add(base::Milliseconds(1001), base::Milliseconds(1007));
+  split_range.Add(base::Milliseconds(1014), base::Milliseconds(1019));
+  split_range.Add(base::Milliseconds(1028), base::Milliseconds(2000));
+  EXPECT_CALL(*mock_mdeh_, GetBufferedRanges("test"))
+      .WillOnce(Return(empty_range))   // Once in CheckState
+      .WillOnce(Return(empty_range))   // Before appending
+      .WillOnce(Return(split_range));  // After appending
+  RespondToUrl("https://example.com/bip00.ts", content);
+  RequireAppend(base::as_byte_span(content));
+  rendition->CheckState(base::Seconds(0), 0.0, BindCheck0Sec());
+
+  // Check State comes in after the first gap, so failure occurs.
+  EXPECT_CALL(*mock_mdeh_, GetBufferedRanges("test"))
+      .WillOnce(Return(split_range));
+  EXPECT_CALL(*mock_hrh_, Quit(_));
+  rendition->CheckState(base::Milliseconds(1033), 0.0,
+                        BindCheckStateNoExpect());
+}
+
+TEST_F(HlsRenditionImplUnittest, TestCanSkipIntoRangesWithSpacedOutGaps) {
+  auto rendition = MakeVodRendition(kDiscontinuous);
+  ASSERT_NE(rendition, nullptr);
+  std::string content = "123";
+  Ranges<base::TimeDelta> empty_range;
+  Ranges<base::TimeDelta> split_range;
+  Ranges<base::TimeDelta> end_range;
+  split_range.Add(base::Seconds(0), base::Seconds(1));
+  split_range.Add(base::Milliseconds(1002), base::Seconds(5));
+  split_range.Add(base::Milliseconds(5002), base::Seconds(10));
+  split_range.Add(base::Milliseconds(10002), base::Seconds(15));
+
+  end_range.Add(base::Milliseconds(1002), base::Seconds(5));
+  end_range.Add(base::Milliseconds(5002), base::Seconds(10));
+  end_range.Add(base::Milliseconds(10002), base::Seconds(15));
+  EXPECT_CALL(*mock_mdeh_, GetBufferedRanges("test"))
+      .WillOnce(Return(empty_range))   // Once in CheckState
+      .WillOnce(Return(empty_range))   // Before appending
+      .WillOnce(Return(split_range));  // After appending
+  RespondToUrl("https://example.com/bip00.ts", content);
+  RequireAppend(base::as_byte_span(content));
+  rendition->CheckState(base::Seconds(0), 0.0, BindCheck0Sec());
+
+  EXPECT_CALL(*mock_mdeh_, GetBufferedRanges("test"))
+      .WillOnce(Return(split_range))  // CheckState initial check
+      .WillOnce(Return(end_range));   // CheckState after removing buffers.
+  EXPECT_CALL(*mock_mdeh_, Remove(_, base::Seconds(0), base::Seconds(1)));
+  EXPECT_CALL(*mock_mdeh_, RequestSeek(base::Milliseconds(1002)));
+  rendition->CheckState(base::Milliseconds(1010), 0.0,
+                        BindCheckState(kNoTimestamp));
+}
+
+TEST_F(HlsRenditionImplUnittest, TestGapWasRightWhereRequestedBufferEndIs) {
+  auto rendition = MakeVodRendition(kDiscontinuous);
+  ASSERT_NE(rendition, nullptr);
+  std::string content = "123";
+  Ranges<base::TimeDelta> empty_range;
+  Ranges<base::TimeDelta> split_range;
+  Ranges<base::TimeDelta> end_range;
+  split_range.Add(base::Seconds(0), base::Milliseconds(998));
+  split_range.Add(base::Milliseconds(1002), base::Seconds(3));
+  end_range.Add(base::Milliseconds(1002), base::Seconds(3));
+  EXPECT_CALL(*mock_mdeh_, GetBufferedRanges("test"))
+      .WillOnce(Return(empty_range))   // Once in CheckState
+      .WillOnce(Return(empty_range))   // Before appending
+      .WillOnce(Return(split_range));  // After appending
+  RespondToUrl("https://example.com/bip00.ts", content);
+  RequireAppend(base::as_byte_span(content));
+
+  // Our requested time landed right in the middle of the gap. but that's OK.
+  // The next CheckState will seek us.
+  rendition->CheckState(base::Seconds(1), 0.0, BindCheck0Sec());
+
+  EXPECT_CALL(*mock_mdeh_, GetBufferedRanges("test"))
+      .WillOnce(Return(split_range))  // CheckState initial check
+      .WillOnce(Return(end_range));   // CheckState after removing buffers.
+  EXPECT_CALL(*mock_mdeh_,
+              Remove(_, base::Seconds(0), base::Milliseconds(998)));
+  EXPECT_CALL(*mock_mdeh_, RequestSeek(base::Milliseconds(1002)));
+  rendition->CheckState(base::Seconds(1), 0.0, BindCheckState(kNoTimestamp));
+}
+
 TEST_F(HlsRenditionImplUnittest, TestDiscontinuity) {
   auto rendition = MakeVodRendition(kDiscontinuous);
   ASSERT_NE(rendition, nullptr);
   const std::string content = "ehh, whatever";
 
-  RespondWithRangeTwice(base::Seconds(0), base::Seconds(0), base::Seconds(0),
-                        base::Seconds(2));
+  RespondWithRangeSet({
+      {base::Seconds(0), base::Seconds(0)},
+      {base::Seconds(0), base::Seconds(0)},
+      {base::Seconds(0), base::Seconds(2)},
+  });
   RespondToUrl("https://example.com/bip00.ts", content);
   RequireAppend(base::as_byte_span(content));
   rendition->CheckState(base::Seconds(0), 0.0, BindCheck0Sec());
   task_environment_.RunUntilIdle();
 
-  RespondWithRangeTwice(base::Seconds(0), base::Seconds(0), base::Seconds(0),
-                        base::Seconds(2));
+  RespondWithRangeSet({
+      {base::Seconds(0), base::Seconds(0)},
+      {base::Seconds(0), base::Seconds(0)},
+      {base::Seconds(0), base::Seconds(2)},
+  });
   RespondToUrl("https://example.com/bip01.ts", content);
   RequireAppend(base::as_byte_span(content));
   rendition->CheckState(base::Seconds(0), 0.0, BindCheck0Sec());
   task_environment_.RunUntilIdle();
 
-  RespondWithRangeTwice(base::Seconds(0), base::Seconds(0), base::Seconds(0),
-                        base::Seconds(2));
+  RespondWithRangeSet({
+      {base::Seconds(0), base::Seconds(0)},
+      {base::Seconds(0), base::Seconds(0)},
+      {base::Seconds(0), base::Seconds(2)},
+  });
   RespondToUrl("https://example.com/bip02.ts", content);
   RequireAppend(base::as_byte_span(content));
   rendition->CheckState(base::Seconds(0), 0.0, BindCheck0Sec());
   task_environment_.RunUntilIdle();
 
-  RespondWithRangeTwice(base::Seconds(0), base::Seconds(0), base::Seconds(0),
-                        base::Seconds(2));
+  RespondWithRangeSet({
+      {base::Seconds(0), base::Seconds(0)},
+      {base::Seconds(0), base::Seconds(0)},
+      {base::Seconds(0), base::Seconds(2)},
+  });
   RespondToUrl("https://example.com/data00.ts", content);
 
   EXPECT_CALL(*mock_mdeh_, ResetParserState("test", base::Seconds(8.6), _));
@@ -685,15 +846,21 @@
   rendition->CheckState(base::Seconds(0), 0.0, BindCheck0Sec());
   task_environment_.RunUntilIdle();
 
-  RespondWithRangeTwice(base::Seconds(0), base::Seconds(0), base::Seconds(0),
-                        base::Seconds(2));
+  RespondWithRangeSet({
+      {base::Seconds(0), base::Seconds(0)},
+      {base::Seconds(0), base::Seconds(0)},
+      {base::Seconds(0), base::Seconds(2)},
+  });
   RespondToUrl("https://example.com/data01.ts", content);
   RequireAppend(base::as_byte_span(content));
   rendition->CheckState(base::Seconds(0), 0.0, BindCheck0Sec());
   task_environment_.RunUntilIdle();
 
-  RespondWithRangeTwice(base::Seconds(0), base::Seconds(0), base::Seconds(0),
-                        base::Seconds(2));
+  RespondWithRangeSet({
+      {base::Seconds(0), base::Seconds(0)},
+      {base::Seconds(0), base::Seconds(0)},
+      {base::Seconds(0), base::Seconds(2)},
+  });
   RespondToUrl("https://example.com/data02.ts", content);
   RequireAppend(base::as_byte_span(content));
   rendition->CheckState(base::Seconds(0), 0.0, BindCheck0Sec());
@@ -722,8 +889,11 @@
   /* START CHECK 1 */
   // CheckState will start the paused player, query BufferedRanges, get 0-0,
   // which will trigger an attempt to fetch.
-  RespondWithRangeTwice(base::Seconds(0), base::Seconds(0), base::Seconds(0),
-                        base::Seconds(2));
+  RespondWithRangeSet({
+      {base::Seconds(0), base::Seconds(0)},
+      {base::Seconds(0), base::Seconds(0)},
+      {base::Seconds(0), base::Seconds(2)},
+  });
 
   // The fetch will request the segment at "media_0.ts", which has an encryption
   // data attached. We need to populate that here so we can use the same key to
@@ -752,8 +922,11 @@
 
   // On the second fetch, the encryption data should remain the same,
   // so we can just use new text.
-  RespondWithRangeTwice(base::Seconds(0), base::Seconds(2), base::Seconds(0),
-                        base::Seconds(4));
+  RespondWithRangeSet({
+      {base::Seconds(0), base::Seconds(2)},
+      {base::Seconds(0), base::Seconds(2)},
+      {base::Seconds(0), base::Seconds(4)},
+  });
   RespondToUrl("https://example.com/media_1.ts", ciphertext);
   RequireAppend(base::as_byte_span(cleartext));
   rendition->CheckState(base::Seconds(0), 0.0,
@@ -773,8 +946,11 @@
 
   // CheckState will start the paused player, query BufferedRanges, get 0-4
   // which will trigger an attempt to fetch.
-  RespondWithRangeTwice(base::Seconds(0), base::Seconds(4), base::Seconds(0),
-                        base::Seconds(6));
+  RespondWithRangeSet({
+      {base::Seconds(0), base::Seconds(4)},
+      {base::Seconds(0), base::Seconds(4)},
+      {base::Seconds(0), base::Seconds(6)},
+  });
 
   // The fetch will request the segment at "media_2.ts", which has a new
   // encryption data.
@@ -803,8 +979,11 @@
   rendition->UpdatePlaylist(std::move(parsed).value());
 
   // The encryption data is the same for segment 3, since it didn't change.
-  RespondWithRangeTwice(base::Seconds(0), base::Seconds(2), base::Seconds(0),
-                        base::Seconds(4));
+  RespondWithRangeSet({
+      {base::Seconds(0), base::Seconds(2)},
+      {base::Seconds(0), base::Seconds(2)},
+      {base::Seconds(0), base::Seconds(4)},
+  });
   RespondToUrl("https://example.com/media_3.ts", ciphertext2);
   RequireAppend(base::as_byte_span(cleartext));
   rendition->CheckState(base::Seconds(0), 0.0,
@@ -825,8 +1004,11 @@
 
   // CheckState will start the paused player, query BufferedRanges, get 0-4
   // which will trigger an attempt to fetch.
-  RespondWithRangeTwice(base::Seconds(0), base::Seconds(4), base::Seconds(0),
-                        base::Seconds(6));
+  RespondWithRangeSet({
+      {base::Seconds(0), base::Seconds(4)},
+      {base::Seconds(0), base::Seconds(4)},
+      {base::Seconds(0), base::Seconds(6)},
+  });
 
   // The fetch will request the segment at "mediax_4.ts", for which the
   // encryption data has not been fetched.
@@ -846,8 +1028,11 @@
   /* START CHECK 6 */
 
   // The encryption data is the same for segment 5, since it didn't change.
-  RespondWithRangeTwice(base::Seconds(0), base::Seconds(2), base::Seconds(0),
-                        base::Seconds(4));
+  RespondWithRangeSet({
+      {base::Seconds(0), base::Seconds(2)},
+      {base::Seconds(0), base::Seconds(2)},
+      {base::Seconds(0), base::Seconds(4)},
+  });
   RespondToUrl("https://example.com/mediax_5.ts", ciphertext3);
   RequireAppend(base::as_byte_span(cleartext));
   rendition->CheckState(base::Seconds(0), 0.0,
diff --git a/media/formats/BUILD.gn b/media/formats/BUILD.gn
index e288203..578baa1 100644
--- a/media/formats/BUILD.gn
+++ b/media/formats/BUILD.gn
@@ -393,6 +393,10 @@
     "hls/types_unittest.cc",
     "hls/variable_dictionary_unittest.cc",
   ]
+
+  # TODO(crbug.com/40031409): Fix code that adds exit-time destructors and
+  # enable the diagnostic by removing this line.
+  configs += [ "//build/config/compiler:no_exit_time_destructors" ]
 }
 
 # TODO(crbug.com/40057824): This should be gated behind `enable_hls_demuxer`, once that's enabled by default.
diff --git a/media/fuchsia/video/BUILD.gn b/media/fuchsia/video/BUILD.gn
index b10f8061..caaf4d6 100644
--- a/media/fuchsia/video/BUILD.gn
+++ b/media/fuchsia/video/BUILD.gn
@@ -66,4 +66,8 @@
     "//third_party/fuchsia-sdk/sdk/fidl/fuchsia.sysmem:fuchsia.sysmem_hlcpp",
     "//third_party/fuchsia-sdk/sdk/pkg/sys_cpp",
   ]
+
+  # TODO(crbug.com/40031409): Fix code that adds exit-time destructors and
+  # enable the diagnostic by removing this line.
+  configs += [ "//build/config/compiler:no_exit_time_destructors" ]
 }
diff --git a/media/gpu/chromeos/mailbox_video_frame_converter.cc b/media/gpu/chromeos/mailbox_video_frame_converter.cc
index 282a758b..6ba81de 100644
--- a/media/gpu/chromeos/mailbox_video_frame_converter.cc
+++ b/media/gpu/chromeos/mailbox_video_frame_converter.cc
@@ -33,53 +33,6 @@
 #include "ui/gfx/gpu_memory_buffer_handle.h"
 #include "ui/gl/gl_bindings.h"
 
-namespace {
-
-// Based on `buffer_format` support by VideoPixelFormatToGfxBufferFormat.
-viz::SharedImageFormat GetSharedImageFormat(gfx::BufferFormat buffer_format) {
-  viz::SharedImageFormat format;
-  switch (buffer_format) {
-    case gfx::BufferFormat::RGBA_8888:
-      format = viz::SinglePlaneFormat::kRGBA_8888;
-      break;
-    case gfx::BufferFormat::RGBX_8888:
-      format = viz::SinglePlaneFormat::kRGBX_8888;
-      break;
-    case gfx::BufferFormat::BGRA_8888:
-      format = viz::SinglePlaneFormat::kBGRA_8888;
-      break;
-    case gfx::BufferFormat::BGRX_8888:
-      format = viz::SinglePlaneFormat::kBGRX_8888;
-      break;
-    case gfx::BufferFormat::YVU_420:
-      format = viz::MultiPlaneFormat::kYV12;
-      break;
-    case gfx::BufferFormat::YUV_420_BIPLANAR:
-      format = viz::MultiPlaneFormat::kNV12;
-      break;
-    case gfx::BufferFormat::YUVA_420_TRIPLANAR:
-      format = viz::MultiPlaneFormat::kNV12A;
-      break;
-    case gfx::BufferFormat::P010:
-      format = viz::MultiPlaneFormat::kP010;
-      break;
-    default:
-      DLOG(WARNING) << "Unsupported buffer_format: "
-                    << static_cast<int>(buffer_format);
-      NOTREACHED();
-  }
-#if BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_LINUX)
-  // If format is true multiplanar format, we prefer external sampler on
-  // ChromeOS and Linux.
-  if (format.is_multi_plane()) {
-    format.SetPrefersExternalSampler();
-  }
-#endif
-  return format;
-}
-
-}  // namespace
-
 namespace media {
 class MailboxVideoFrameConverter::ScopedSharedImage {
  public:
@@ -234,11 +187,9 @@
 
   input_frame_queue_.pop();
 
-  const auto buffer_format = VideoPixelFormatToGfxBufferFormat(frame->format());
   // GenerateSharedImage() should have checked the |origin_frame|'s format
   // (which should be the same as the |frame|'s format).
   CHECK_EQ(frame->format(), origin_frame->format());
-  CHECK(buffer_format);
 
   VideoFrame::ReleaseMailboxCB release_mailbox_cb = base::BindOnce(
       [](scoped_refptr<base::SequencedTaskRunner> parent_task_runner,
@@ -364,13 +315,19 @@
     return false;
   }
 
-  const auto buffer_format =
-      VideoPixelFormatToGfxBufferFormat(origin_frame->format());
-  if (!buffer_format) {
+  auto si_format = VideoPixelFormatToSharedImageFormat(origin_frame->format());
+  if (!si_format) {
     OnError(FROM_HERE, "Unsupported format: " +
                            VideoPixelFormatToString(origin_frame->format()));
     return false;
   }
+#if BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_LINUX)
+  // If format is true multiplanar format, we prefer external sampler on
+  // ChromeOS and Linux.
+  if (si_format->is_multi_plane()) {
+    si_format->SetPrefersExternalSampler();
+  }
+#endif
 
   auto gpu_memory_buffer_handle = origin_frame->CreateGpuMemoryBufferHandle();
   DCHECK(!gpu_memory_buffer_handle.is_null());
@@ -412,8 +369,8 @@
 
   scoped_refptr<gpu::ClientSharedImage> client_shared_image =
       shared_image_interface_->CreateSharedImage(
-          {GetSharedImageFormat(*buffer_format), shared_image_size,
-           src_color_space, shared_image_usage, "MailboxVideoFrameConverter"},
+          {*si_format, shared_image_size, src_color_space, shared_image_usage,
+           "MailboxVideoFrameConverter"},
           std::move(gpu_memory_buffer_handle));
   if (!client_shared_image) {
     OnError(FROM_HERE, "Failed to create shared image.");
diff --git a/media/learning/impl/BUILD.gn b/media/learning/impl/BUILD.gn
index 1acc0a88..1b12faac 100644
--- a/media/learning/impl/BUILD.gn
+++ b/media/learning/impl/BUILD.gn
@@ -83,6 +83,10 @@
     "//media/learning/impl",
     "//testing/gtest",
   ]
+
+  # TODO(crbug.com/40031409): Fix code that adds exit-time destructors and
+  # enable the diagnostic by removing this line.
+  configs += [ "//build/config/compiler:no_exit_time_destructors" ]
 }
 
 fuzzer_test("learning_fuzzer") {
diff --git a/media/test/BUILD.gn b/media/test/BUILD.gn
index 75e2c58..6f583aee 100644
--- a/media/test/BUILD.gn
+++ b/media/test/BUILD.gn
@@ -72,6 +72,10 @@
       "//url",
     ]
   }
+
+  # TODO(crbug.com/40031409): Fix code that adds exit-time destructors and
+  # enable the diagnostic by removing this line.
+  configs += [ "//build/config/compiler:no_exit_time_destructors" ]
 }
 
 source_set("pipeline_integration_perftests") {
diff --git a/media/video/renderable_gpu_memory_buffer_video_frame_pool.cc b/media/video/renderable_gpu_memory_buffer_video_frame_pool.cc
index 92849f8a..ae574499 100644
--- a/media/video/renderable_gpu_memory_buffer_video_frame_pool.cc
+++ b/media/video/renderable_gpu_memory_buffer_video_frame_pool.cc
@@ -39,7 +39,7 @@
 class FrameResources {
  public:
   FrameResources(scoped_refptr<InternalRefCountedPool> pool,
-                 const gfx::Size& coded_size);
+                 const gfx::Size& visible_size);
   ~FrameResources();
   FrameResources(const FrameResources& other) = delete;
   FrameResources& operator=(const FrameResources& other) = delete;
@@ -50,7 +50,7 @@
 
   // Return true if these resources can be reused for a frame with the specified
   // parameters.
-  bool IsCompatibleWith(const gfx::Size& coded_size,
+  bool IsCompatibleWith(const gfx::Size& visible_size,
                         const gfx::ColorSpace& color_space) const;
 
   // Create a VideoFrame using these resources.
@@ -66,7 +66,7 @@
   // SharedImage) will not be destroyed until after `this` is destroyed.
   const scoped_refptr<InternalRefCountedPool> pool_;
 
-  const gfx::Size coded_size_;
+  const gfx::Size visible_size_;
   scoped_refptr<gpu::ClientSharedImage> shared_image_;
   gpu::SyncToken sync_token_;
 };
@@ -91,7 +91,7 @@
   // Create a VideoFrame with the specified parameters, reusing the resources
   // of a previous frame, if possible.
   scoped_refptr<VideoFrame> MaybeCreateVideoFrame(
-      const gfx::Size& coded_size,
+      const gfx::Size& visible_size,
       const gfx::ColorSpace& color_space);
 
   // Indicate that the owner of `this` is being destroyed. This will eventually
@@ -130,7 +130,7 @@
       VideoPixelFormat format);
 
   scoped_refptr<VideoFrame> MaybeCreateVideoFrame(
-      const gfx::Size& coded_size,
+      const gfx::Size& visible_size,
       const gfx::ColorSpace& color_space) override;
 
   ~RenderableGpuMemoryBufferVideoFramePoolImpl() override;
@@ -143,8 +143,8 @@
 // FrameResources
 
 FrameResources::FrameResources(scoped_refptr<InternalRefCountedPool> pool,
-                               const gfx::Size& coded_size)
-    : pool_(std::move(pool)), coded_size_(coded_size) {}
+                               const gfx::Size& visible_size)
+    : pool_(std::move(pool)), visible_size_(visible_size) {}
 
 FrameResources::~FrameResources() {
   if (shared_image_) {
@@ -153,21 +153,20 @@
   }
 }
 
-gfx::Size GetBufferSizeInPixelsForVideoPixelFormat(
-    VideoPixelFormat format,
-    const gfx::Size& coded_size) {
+gfx::Size GetCodedSizeForVideoPixelFormat(VideoPixelFormat format,
+                                          const gfx::Size& visible_size) {
   switch (format) {
     case PIXEL_FORMAT_ARGB:
     case PIXEL_FORMAT_ABGR:
-      return coded_size;
+      return visible_size;
     case PIXEL_FORMAT_NV12:
       // Align number of rows to 2, because it's required by YUV_420_BIPLANAR
       // buffer allocation code.
       // Align buffer stride to 4, because our SharedImage shared memory backing
       // code requires it, since it sometimes treats Y-planes are 4 bytes per
       // pixel textures.
-      return {cc::MathUtil::CheckedRoundUp(coded_size.width(), 4),
-              cc::MathUtil::CheckedRoundUp(coded_size.height(), 2)};
+      return {cc::MathUtil::CheckedRoundUp(visible_size.width(), 4),
+              cc::MathUtil::CheckedRoundUp(visible_size.height(), 2)};
     default:
       NOTREACHED();
   }
@@ -190,8 +189,8 @@
 #endif
       ;
 
-  const gfx::Size buffer_size_in_pixels =
-      GetBufferSizeInPixelsForVideoPixelFormat(format, coded_size_);
+  const gfx::Size coded_size =
+      GetCodedSizeForVideoPixelFormat(format, visible_size_);
 
   gpu::SharedImageUsageSet usage =
 #if BUILDFLAG(IS_MAC)
@@ -229,12 +228,11 @@
   const viz::SharedImageFormat si_format =
       VideoPixelFormatToSharedImageFormat(format).value();
 
-  shared_image_ =
-      context->CreateSharedImage(buffer_size_in_pixels, kBufferUsage, si_format,
-                                 color_space, usage, sync_token_);
+  shared_image_ = context->CreateSharedImage(
+      coded_size, kBufferUsage, si_format, color_space, usage, sync_token_);
   if (!shared_image_) {
-    DLOG(ERROR) << "Failed to allocate shared image for frame: coded_size="
-                << coded_size_.ToString()
+    DLOG(ERROR) << "Failed to allocate shared image for frame: visible_size="
+                << visible_size_.ToString()
                 << ", si_format=" << si_format.ToString();
     return false;
   }
@@ -242,15 +240,15 @@
 }
 
 bool FrameResources::IsCompatibleWith(
-    const gfx::Size& coded_size,
+    const gfx::Size& visible_size,
     const gfx::ColorSpace& color_space) const {
-  return coded_size_ == coded_size &&
+  return visible_size_ == visible_size &&
          shared_image_->color_space() == color_space;
 }
 
 scoped_refptr<VideoFrame> FrameResources::CreateVideoFrame() {
-  const gfx::Rect visible_rect(coded_size_);
-  const gfx::Size natural_size = coded_size_;
+  const gfx::Rect visible_rect(visible_size_);
+  const gfx::Size natural_size = visible_size_;
 
   CHECK(shared_image_);
   auto video_frame = VideoFrame::WrapMappableSharedImage(
@@ -285,20 +283,20 @@
     : format_(format), context_(std::move(context)) {}
 
 scoped_refptr<VideoFrame> InternalRefCountedPool::MaybeCreateVideoFrame(
-    const gfx::Size& coded_size,
+    const gfx::Size& visible_size,
     const gfx::ColorSpace& color_space) {
   // Find or create a suitable FrameResources.
   std::unique_ptr<FrameResources> frame_resources;
   while (!available_frame_resources_.empty()) {
     frame_resources = std::move(available_frame_resources_.front());
     available_frame_resources_.pop_front();
-    if (!frame_resources->IsCompatibleWith(coded_size, color_space)) {
+    if (!frame_resources->IsCompatibleWith(visible_size, color_space)) {
       frame_resources = nullptr;
       continue;
     }
   }
   if (!frame_resources) {
-    frame_resources = std::make_unique<FrameResources>(this, coded_size);
+    frame_resources = std::make_unique<FrameResources>(this, visible_size);
     if (!frame_resources->Initialize(format_, color_space)) {
       DLOG(ERROR) << "Failed to initialize frame resources.";
       return nullptr;
@@ -370,9 +368,9 @@
 
 scoped_refptr<VideoFrame>
 RenderableGpuMemoryBufferVideoFramePoolImpl::MaybeCreateVideoFrame(
-    const gfx::Size& coded_size,
+    const gfx::Size& visible_size,
     const gfx::ColorSpace& color_space) {
-  return pool_internal_->MaybeCreateVideoFrame(coded_size, color_space);
+  return pool_internal_->MaybeCreateVideoFrame(visible_size, color_space);
 }
 
 RenderableGpuMemoryBufferVideoFramePoolImpl::
diff --git a/mojo/public/tools/bindings/generators/mojom_ts_generator.py b/mojo/public/tools/bindings/generators/mojom_ts_generator.py
index 9707addb..3901c47b 100644
--- a/mojo/public/tools/bindings/generators/mojom_ts_generator.py
+++ b/mojo/public/tools/bindings/generators/mojom_ts_generator.py
@@ -736,7 +736,8 @@
   def _ConverterImports(self):
 
     def needs_import(kind):
-      return mojom.IsStructKind(kind) or mojom.IsUnionKind(kind)
+      return mojom.IsStructKind(kind) or mojom.IsUnionKind(
+          kind) or mojom.IsEnumKind(kind)
 
     class Import:
 
diff --git a/net/base/network_change_notifier.h b/net/base/network_change_notifier.h
index d4b85e27..8801c26 100644
--- a/net/base/network_change_notifier.h
+++ b/net/base/network_change_notifier.h
@@ -15,10 +15,10 @@
 #include "base/observer_list_threadsafe.h"
 #include "base/strings/cstring_view.h"
 #include "base/time/time.h"
-#include "base/trace_event/trace_event.h"
 #include "build/build_config.h"
 #include "net/base/net_export.h"
 #include "net/base/network_handle.h"
+#include "third_party/perfetto/include/perfetto/tracing/track.h"
 
 #if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
 #include "net/base/address_map_linux.h"
diff --git a/net/cert/cert_verify_proc.cc b/net/cert/cert_verify_proc.cc
index 3974afe..b9cc2e0 100644
--- a/net/cert/cert_verify_proc.cc
+++ b/net/cert/cert_verify_proc.cc
@@ -480,6 +480,10 @@
                           verify_result, net_log);
 
   CHECK(verify_result->verified_cert);
+  if (rv == OK) {
+    CHECK_EQ(verify_result->verified_cert->intermediate_buffers().size() + 1,
+             verify_result->public_key_hashes.size());
+  }
 
   // Check for mismatched signature algorithms and unknown signature algorithms
   // in the chain. Also fills in the has_* booleans for the digest algorithms
diff --git a/net/cert/cert_verify_proc_unittest.cc b/net/cert/cert_verify_proc_unittest.cc
index d942542..d64982b 100644
--- a/net/cert/cert_verify_proc_unittest.cc
+++ b/net/cert/cert_verify_proc_unittest.cc
@@ -161,6 +161,18 @@
                                        const NetLogWithSource& net_log) {
   *verify_result = result_;
   verify_result->verified_cert = cert;
+  if (error_ == OK && verify_result->public_key_hashes.empty()) {
+    net::SHA256HashValue spki_hash;
+    EXPECT_TRUE(net::x509_util::CalculateSha256SpkiHash(
+        verify_result->verified_cert->cert_buffer(), &spki_hash));
+    verify_result->public_key_hashes.push_back(spki_hash);
+    for (const auto& intermediate :
+         verify_result->verified_cert->intermediate_buffers()) {
+      EXPECT_TRUE(net::x509_util::CalculateSha256SpkiHash(intermediate.get(),
+                                                          &spki_hash));
+      verify_result->public_key_hashes.push_back(spki_hash);
+    }
+  }
   return error_;
 }
 
@@ -1403,9 +1415,7 @@
       // Trust anchor
       "sha256/VypP3VWL7OaqTJ7mIBehWYlv8khPuFHpWiearZI2YjI="};
 
-  // |public_key_hashes| does not have an ordering guarantee.
-  EXPECT_THAT(expected_public_key_hashes,
-              testing::UnorderedElementsAreArray(public_key_hash_strings));
+  EXPECT_EQ(expected_public_key_hashes, public_key_hash_strings);
 }
 
 // Basic test for returning the chain in CertVerifyResult. Note that the
@@ -5655,9 +5665,8 @@
 // Test that trust anchors are appropriately recorded via UMA.
 TEST(CertVerifyProcTest, HasTrustAnchorVerifyUMA) {
   base::HistogramTester histograms;
-  scoped_refptr<X509Certificate> cert(
-      ImportCertFromFile(GetTestCertsDirectory(), "ok_cert.pem"));
-  ASSERT_TRUE(cert);
+  auto [leaf, intermediate, root] = CertBuilder::CreateSimpleChain3();
+  auto cert = leaf->GetX509CertificateFullChain();
 
   CertVerifyResult result;
 
@@ -5666,7 +5675,8 @@
   // in 2017 and is not anticipated to be removed from all supported platforms
   // for a few decades.
   // Note: The actual cert in |cert| does not matter for this testing, so long
-  // as it's not violating any CertVerifyProc::Verify() policies.
+  // as it's not violating any CertVerifyProc::Verify() policies and the chain
+  // has the same length.
   SHA256HashValue leaf_hash = {{0}};
   SHA256HashValue intermediate_hash = {{1}};
   SHA256HashValue root_hash = {
@@ -5686,7 +5696,7 @@
   int flags = 0;
   CertVerifyResult verify_result;
   int error = verify_proc->Verify(
-      cert.get(), "127.0.0.1", /*ocsp_response=*/std::string(),
+      cert.get(), "www.example.com", /*ocsp_response=*/std::string(),
       /*sct_list=*/std::string(), flags, &verify_result, NetLogWithSource());
   EXPECT_EQ(OK, error);
   histograms.ExpectUniqueSample(kTrustAnchorVerifyHistogram,
@@ -5698,9 +5708,8 @@
 // trust anchor.
 TEST(CertVerifyProcTest, LogsOnlyMostSpecificTrustAnchorUMA) {
   base::HistogramTester histograms;
-  scoped_refptr<X509Certificate> cert(
-      ImportCertFromFile(GetTestCertsDirectory(), "ok_cert.pem"));
-  ASSERT_TRUE(cert);
+  auto chain = CertBuilder::CreateSimpleChain(4);
+  auto cert = chain[0]->GetX509CertificateFullChain();
 
   CertVerifyResult result;
 
@@ -5708,7 +5717,8 @@
   // signing "C=US, O=Google Trust Services LLC, CN=GTS Root R3" signing an
   // intermediate and a leaf.
   // Note: The actual cert in |cert| does not matter for this testing, so long
-  // as it's not violating any CertVerifyProc::Verify() policies.
+  // as it's not violating any CertVerifyProc::Verify() policies and the chain
+  // has the same length.
   SHA256HashValue leaf_hash = {{0}};
   SHA256HashValue intermediate_hash = {{1}};
   SHA256HashValue gts_root_r3_hash = {
@@ -5733,7 +5743,7 @@
   int flags = 0;
   CertVerifyResult verify_result;
   int error = verify_proc->Verify(
-      cert.get(), "127.0.0.1", /*ocsp_response=*/std::string(),
+      cert.get(), "www.example.com", /*ocsp_response=*/std::string(),
       /*sct_list=*/std::string(), flags, &verify_result, NetLogWithSource());
   EXPECT_EQ(OK, error);
 
@@ -5749,7 +5759,7 @@
   // Since we are setting is_issued_by_known_root=true, the certificate to be
   // verified needs to have a validity period that satisfies
   // HasTooLongValidity.
-  auto [leaf, root] = CertBuilder::CreateSimpleChain2();
+  auto [leaf, intermediate, root] = CertBuilder::CreateSimpleChain3();
 
   CertVerifyResult result;
 
@@ -5772,7 +5782,7 @@
   int flags = 0;
   CertVerifyResult verify_result;
   int error = verify_proc->Verify(
-      leaf->GetX509Certificate().get(), "www.example.com",
+      leaf->GetX509CertificateFullChain().get(), "www.example.com",
       /*ocsp_response=*/std::string(),
       /*sct_list=*/std::string(), flags, &verify_result, NetLogWithSource());
   EXPECT_EQ(OK, error);
diff --git a/net/cert/cert_verify_result.h b/net/cert/cert_verify_result.h
index 202d66b..183d3551 100644
--- a/net/cert/cert_verify_result.h
+++ b/net/cert/cert_verify_result.h
@@ -69,11 +69,8 @@
   // hashes for all of the SubjectPublicKeyInfos of the chain (target,
   // intermediates, and trust anchor)
   //
-  // The ordering of the hashes in this vector is unspecified.
-  //
-  // TODO(crbug.com/41286522): change this to document and add unittests that
-  // the ordering of the hashes in this vector matches the certificates in
-  // `verified_cert`.
+  // The ordering of the hashes matches the order of the |verified_cert| chain
+  // (leaf to root).
   std::vector<SHA256HashValue> public_key_hashes;
 
   // is_issued_by_known_root is true if we recognise the root CA as a standard
diff --git a/net/cert/require_ct_delegate.h b/net/cert/require_ct_delegate.h
index 57dd7f39..8c89095 100644
--- a/net/cert/require_ct_delegate.h
+++ b/net/cert/require_ct_delegate.h
@@ -38,9 +38,8 @@
   // the default handling of Certificate Transparency requirements, if
   // desired.
   // |hostname| contains the host being contacted, serving the certificate
-  // |chain|, with the set of hashes |hashes|. Note that |hashes| and
-  // |chain| are not guaranteed to be in the same order - that is, the first
-  // hash in |hashes| is NOT guaranteed to be for the leaf cert in |chain|.
+  // |chain|, with the hashes |hashes| which must be in the same order as the
+  // certificate chain (leaf to root).
   virtual CTRequirementLevel IsCTRequiredForHost(
       std::string_view hostname,
       const X509Certificate* chain,
@@ -53,6 +52,9 @@
   // connection's CTPolicyEnforcer and |policy_compliance| indicates that
   // the connection does not comply.
   //
+  // |public_key_hashes| must be in the same order as the certificate chain
+  // (leaf to root).
+  //
   // If |delegate| is null, CT will not be required.
   static ct::CTRequirementsStatus CheckCTRequirements(
       const RequireCTDelegate* delegate,
diff --git a/net/dns/dns_config_service_linux.cc b/net/dns/dns_config_service_linux.cc
index 7aaf9fa..8c0bf26c 100644
--- a/net/dns/dns_config_service_linux.cc
+++ b/net/dns/dns_config_service_linux.cc
@@ -217,7 +217,8 @@
 }
 
 bool IsNsswitchConfigCompatible(
-    const std::vector<NsswitchReader::ServiceSpecification>& nsswitch_hosts) {
+    const std::vector<NsswitchReader::ServiceSpecification>& nsswitch_hosts,
+    ResolvReader& resolv_reader) {
   bool files_found = false;
   for (const NsswitchReader::ServiceSpecification& specification :
        nsswitch_hosts) {
@@ -253,6 +254,20 @@
         }
         break;
 
+      case NsswitchReader::Service::kResolve:
+        // If /etc/resolv.conf points to systemd-resolved then treat nss-resolve
+        // the same as nss-dns. If it's not then consider the nsswitch
+        // configuration incompatible.
+        if (!resolv_reader.IsLikelySystemdResolved()) {
+          RecordIncompatibleNsswitchReason(
+              IncompatibleNsswitchReason::kIncompatibleService,
+              specification.service);
+          return false;
+        }
+        // systemd-resolved also supports looking up records from /etc/hosts.
+        files_found = true;
+        [[fallthrough]];
+
       case NsswitchReader::Service::kDns:
         if (!files_found) {
           RecordIncompatibleNsswitchReason(
@@ -280,7 +295,6 @@
       case NsswitchReader::Service::kMdns:
       case NsswitchReader::Service::kMdns4:
       case NsswitchReader::Service::kMdns6:
-      case NsswitchReader::Service::kResolve:
       case NsswitchReader::Service::kNis:
         RecordIncompatibleNsswitchReason(
             IncompatibleNsswitchReason::kIncompatibleService,
@@ -468,7 +482,7 @@
         std::vector<NsswitchReader::ServiceSpecification> nsswitch_hosts =
             nsswitch_reader_->ReadAndParseHosts();
         dns_config_->unhandled_options =
-            !IsNsswitchConfigCompatible(nsswitch_hosts);
+            !IsNsswitchConfigCompatible(nsswitch_hosts, *resolv_reader_);
         base::UmaHistogramBoolean("Net.DNS.DnsConfig.Nsswitch.Compatible",
                                   !dns_config_->unhandled_options);
       }
diff --git a/net/dns/dns_config_service_linux_unittest.cc b/net/dns/dns_config_service_linux_unittest.cc
index 412a8032..cb2eb5c 100644
--- a/net/dns/dns_config_service_linux_unittest.cc
+++ b/net/dns/dns_config_service_linux_unittest.cc
@@ -278,9 +278,18 @@
     blocking_helper_ = blocking_helper;
   }
 
+  bool IsLikelySystemdResolved() override {
+    return is_likely_systemd_resolved_;
+  }
+
+  void set_is_likely_systemd_resolved(bool value) {
+    is_likely_systemd_resolved_ = value;
+  }
+
  private:
   std::unique_ptr<TestScopedResState> value_;
   raw_ptr<BlockingHelper> blocking_helper_ = nullptr;
+  bool is_likely_systemd_resolved_ = false;
 };
 
 class TestNsswitchReader : public NsswitchReader {
@@ -883,6 +892,48 @@
   EXPECT_TRUE(config->unhandled_options);
 }
 
+TEST_F(DnsConfigServiceLinuxTest,
+       AcceptsNsswitchResolveWithResolvConfRedirect) {
+  auto res = std::make_unique<struct __res_state>();
+  InitializeResState(res.get());
+  resolv_reader_->set_value(std::move(res));
+  resolv_reader_->set_is_likely_systemd_resolved(true);
+
+  nsswitch_reader_->set_value(
+      {NsswitchReader::ServiceSpecification(NsswitchReader::Service::kFiles),
+       NsswitchReader::ServiceSpecification(
+           NsswitchReader::Service::kResolve)});
+
+  CallbackHelper callback_helper;
+  service_.ReadConfig(callback_helper.GetCallback());
+  std::optional<DnsConfig> config = callback_helper.WaitForResult();
+  EXPECT_TRUE(resolv_reader_->closed());
+
+  ASSERT_TRUE(config.has_value());
+  EXPECT_TRUE(config->IsValid());
+  EXPECT_FALSE(config->unhandled_options);
+}
+
+TEST_F(DnsConfigServiceLinuxTest,
+       AcceptsNsswitchResolveWithResolvConfRedirectNoFiles) {
+  auto res = std::make_unique<struct __res_state>();
+  InitializeResState(res.get());
+  resolv_reader_->set_value(std::move(res));
+  resolv_reader_->set_is_likely_systemd_resolved(true);
+
+  nsswitch_reader_->set_value({NsswitchReader::ServiceSpecification(
+      NsswitchReader::Service::kResolve)});
+
+  CallbackHelper callback_helper;
+  service_.ReadConfig(callback_helper.GetCallback());
+  std::optional<DnsConfig> config = callback_helper.WaitForResult();
+  EXPECT_TRUE(resolv_reader_->closed());
+
+  ASSERT_TRUE(config.has_value());
+  EXPECT_TRUE(config->IsValid());
+  EXPECT_FALSE(config->unhandled_options);
+}
+
 TEST_F(DnsConfigServiceLinuxTest, RejectsNsswitchNis) {
   auto res = std::make_unique<struct __res_state>();
   InitializeResState(res.get());
diff --git a/net/dns/public/resolv_reader.cc b/net/dns/public/resolv_reader.cc
index e393c4ca..42875701 100644
--- a/net/dns/public/resolv_reader.cc
+++ b/net/dns/public/resolv_reader.cc
@@ -33,6 +33,30 @@
   return res;
 }
 
+bool ResolvReader::IsLikelySystemdResolved() {
+#if BUILDFLAG(IS_LINUX)
+  // Look for a single 127.0.0.53:53 nameserver endpoint. The only known
+  // significant usage of such a configuration is the systemd-resolved local
+  // resolver, so it is then a fairly safe assumption that any DNS queries to
+  // the nameserver will be handled by systemd-resolved.
+  //
+  // This code path is only reachable if the system has nss-resolve configured
+  // in nsswitch.conf, which is another indicator that systemd-resolved is
+  // likely to be in use.
+  std::unique_ptr<ScopedResState> res = GetResState();
+  if (res) {
+    std::optional<std::vector<IPEndPoint>> nameservers =
+        GetNameservers(res->state());
+    if (nameservers) {
+      return nameservers->size() == 1 &&
+             nameservers->front() == IPEndPoint(IPAddress(127, 0, 0, 53), 53);
+    }
+  }
+#endif
+
+  return false;
+}
+
 std::optional<std::vector<IPEndPoint>> GetNameservers(
     const struct __res_state& res) {
   std::vector<IPEndPoint> nameservers;
diff --git a/net/dns/public/resolv_reader.h b/net/dns/public/resolv_reader.h
index ec64e26..42f8728 100644
--- a/net/dns/public/resolv_reader.h
+++ b/net/dns/public/resolv_reader.h
@@ -25,6 +25,10 @@
 
   // Null on failure.
   virtual std::unique_ptr<ScopedResState> GetResState();
+
+  // Returns whether or not resolv.conf contains configuration that will forward
+  // all DNS calls to a local resolver likely using systemd-resolved.
+  virtual bool IsLikelySystemdResolved();
 };
 
 // Returns configured DNS servers or nullopt on failure.
diff --git a/net/http/http_network_transaction.cc b/net/http/http_network_transaction.cc
index df5a7cb..89dc681 100644
--- a/net/http/http_network_transaction.cc
+++ b/net/http/http_network_transaction.cc
@@ -2454,8 +2454,8 @@
   if (result == OK) {
     base::UmaHistogramEnumeration(
         base::StrCat({
-            "Net.NetworkTransaction.NegotiatedProtocol.",
-            IsGoogleHostWithAlpnH3(url_.host_piece()) ? "GoogleHost." : "",
+            "Net.NetworkTransaction.NegotiatedProtocol",
+            IsGoogleHostWithAlpnH3(url_.host_piece()) ? ".GoogleHost" : "",
         }),
         negotiated_protocol_);
 
diff --git a/net/quic/quic_session_pool.h b/net/quic/quic_session_pool.h
index 93b34b2..fc83a860 100644
--- a/net/quic/quic_session_pool.h
+++ b/net/quic/quic_session_pool.h
@@ -979,15 +979,13 @@
   const raw_ptr<QuicSessionPool> quic_session_pool_;
 };
 
-// Key for QuicCryptoClienConfigOwners within a session pool.k
+// Key for QuicCryptoClienConfigOwners within a session pool.
 struct NET_EXPORT_PRIVATE QuicSessionPool::QuicCryptoClientConfigKey {
   QuicCryptoClientConfigKey() = default;
   explicit QuicCryptoClientConfigKey(const QuicSessionKey& session_key)
       : network_anonymization_key(session_key.network_anonymization_key()),
         proxy_chain(session_key.proxy_chain()),
         session_usage(session_key.session_usage()) {}
-  explicit QuicCryptoClientConfigKey(const NetworkAnonymizationKey& nak)
-      : network_anonymization_key(nak) {}
 
   bool operator==(const QuicCryptoClientConfigKey& other) const;
   bool operator<(const QuicCryptoClientConfigKey& other) const;
diff --git a/net/quic/quic_session_pool_test.cc b/net/quic/quic_session_pool_test.cc
index ee7a239..1d57901 100644
--- a/net/quic/quic_session_pool_test.cc
+++ b/net/quic/quic_session_pool_test.cc
@@ -137,6 +137,19 @@
   return ParsedQuicVersionToString(p.version);
 }
 
+QuicSessionPool::QuicCryptoClientConfigKey CreateTestQuicCryptoClientConfigKey(
+    const NetworkAnonymizationKey& network_anonymization_key) {
+  // Note: Most of the values in this QuicSessionKey aren't actually used when
+  // constructing the QuicCryptoClientConfigKey, but still attempt to use a
+  // QuicSessionKey with realistic values.
+  return QuicSessionPool::QuicCryptoClientConfigKey(QuicSessionKey(
+      QuicSessionPoolTestBase::kDefaultServerHostName,
+      QuicSessionPoolTestBase::kDefaultServerPort,
+      PrivacyMode::PRIVACY_MODE_DISABLED, ProxyChain::Direct(),
+      SessionUsage::kDestination, SocketTag(), network_anonymization_key,
+      SecureDnsPolicy::kAllow, /*require_dns_https_alpn=*/false));
+}
+
 std::vector<TestParams> GetTestParams() {
   std::vector<TestParams> params;
   quic::ParsedQuicVersionVector all_supported_versions =
@@ -692,7 +705,8 @@
   EXPECT_EQ(ERR_IO_PENDING, builder.CallRequest());
   EXPECT_THAT(callback_.WaitForResult(), IsOk());
 
-  QuicSessionPool::QuicCryptoClientConfigKey key1(network_anonymization_key1);
+  QuicSessionPool::QuicCryptoClientConfigKey key1 =
+      CreateTestQuicCryptoClientConfigKey(network_anonymization_key1);
   EXPECT_FALSE(QuicSessionPoolPeer::CryptoConfigCacheIsEmpty(
       pool_.get(), quic_server_id1, key1));
 
@@ -733,7 +747,8 @@
   EXPECT_EQ(ERR_IO_PENDING, builder2.CallRequest());
   EXPECT_THAT(callback_.WaitForResult(), IsOk());
 
-  QuicSessionPool::QuicCryptoClientConfigKey key2(network_anonymization_key2);
+  QuicSessionPool::QuicCryptoClientConfigKey key2 =
+      CreateTestQuicCryptoClientConfigKey(network_anonymization_key2);
   EXPECT_FALSE(QuicSessionPoolPeer::CryptoConfigCacheIsEmpty(
       pool_.get(), quic_server_id2, key2));
   std::unique_ptr<QuicCryptoClientConfigHandle> crypto_config_handle2 =
@@ -12357,20 +12372,20 @@
   const SchemefulSite kSite1(GURL("https://foo.test/"));
   const auto kNetworkAnonymizationKey1 =
       NetworkAnonymizationKey::CreateSameSite(kSite1);
-  const QuicSessionPool::QuicCryptoClientConfigKey kKey1(
-      kNetworkAnonymizationKey1);
+  const QuicSessionPool::QuicCryptoClientConfigKey kKey1 =
+      CreateTestQuicCryptoClientConfigKey(kNetworkAnonymizationKey1);
 
   const SchemefulSite kSite2(GURL("https://bar.test/"));
   const auto kNetworkAnonymizationKey2 =
       NetworkAnonymizationKey::CreateSameSite(kSite2);
-  const QuicSessionPool::QuicCryptoClientConfigKey kKey2(
-      kNetworkAnonymizationKey2);
+  const QuicSessionPool::QuicCryptoClientConfigKey kKey2 =
+      CreateTestQuicCryptoClientConfigKey(kNetworkAnonymizationKey2);
 
   const SchemefulSite kSite3(GURL("https://baz.test/"));
   const auto kNetworkAnonymizationKey3 =
       NetworkAnonymizationKey::CreateSameSite(kSite3);
-  const QuicSessionPool::QuicCryptoClientConfigKey kKey3(
-      kNetworkAnonymizationKey3);
+  const QuicSessionPool::QuicCryptoClientConfigKey kKey3 =
+      CreateTestQuicCryptoClientConfigKey(kNetworkAnonymizationKey3);
 
   Initialize();
 
@@ -12412,20 +12427,20 @@
   const SchemefulSite kSite1(GURL("https://foo.test/"));
   const auto kNetworkAnonymizationKey1 =
       NetworkAnonymizationKey::CreateSameSite(kSite1);
-  const QuicSessionPool::QuicCryptoClientConfigKey kKey1(
-      kNetworkAnonymizationKey1);
+  const QuicSessionPool::QuicCryptoClientConfigKey kKey1 =
+      CreateTestQuicCryptoClientConfigKey(kNetworkAnonymizationKey1);
 
   const SchemefulSite kSite2(GURL("https://bar.test/"));
   const auto kNetworkAnonymizationKey2 =
       NetworkAnonymizationKey::CreateSameSite(kSite2);
-  const QuicSessionPool::QuicCryptoClientConfigKey kKey2(
-      kNetworkAnonymizationKey2);
+  const QuicSessionPool::QuicCryptoClientConfigKey kKey2 =
+      CreateTestQuicCryptoClientConfigKey(kNetworkAnonymizationKey2);
 
   const SchemefulSite kSite3(GURL("https://baz.test/"));
   const auto kNetworkAnonymizationKey3 =
       NetworkAnonymizationKey::CreateSameSite(kSite3);
-  const QuicSessionPool::QuicCryptoClientConfigKey kKey3(
-      kNetworkAnonymizationKey3);
+  const QuicSessionPool::QuicCryptoClientConfigKey kKey3 =
+      CreateTestQuicCryptoClientConfigKey(kNetworkAnonymizationKey3);
 
   Initialize();
 
@@ -12508,8 +12523,8 @@
 
     std::unique_ptr<QuicCryptoClientConfigHandle> crypto_config_handle =
         QuicSessionPoolPeer::GetCryptoConfig(
-            pool_.get(), QuicSessionPool::QuicCryptoClientConfigKey(
-                             network_anonymization_keys[i]));
+            pool_.get(),
+            CreateTestQuicCryptoClientConfigKey(network_anonymization_keys[i]));
     crypto_config_handle->GetConfig()->set_user_agent_id(
         base::NumberToString(i));
     crypto_config_handles.emplace_back(std::move(crypto_config_handle));
@@ -12524,8 +12539,8 @@
     // A new handle for the same NAK returns the same crypto config.
     std::unique_ptr<QuicCryptoClientConfigHandle> crypto_config_handle =
         QuicSessionPoolPeer::GetCryptoConfig(
-            pool_.get(), QuicSessionPool::QuicCryptoClientConfigKey(
-                             network_anonymization_keys[i]));
+            pool_.get(),
+            CreateTestQuicCryptoClientConfigKey(network_anonymization_keys[i]));
     EXPECT_EQ(base::NumberToString(i),
               crypto_config_handle->GetConfig()->user_agent_id());
   }
@@ -12543,8 +12558,8 @@
     // evicted. Otherwise, it will return the same one.
     std::unique_ptr<QuicCryptoClientConfigHandle> crypto_config_handle =
         QuicSessionPoolPeer::GetCryptoConfig(
-            pool_.get(), QuicSessionPool::QuicCryptoClientConfigKey(
-                             network_anonymization_keys[i]));
+            pool_.get(),
+            CreateTestQuicCryptoClientConfigKey(network_anonymization_keys[i]));
     if (kNumSessionsToMake - i > kNumSessionsToMake) {
       EXPECT_EQ("", crypto_config_handle->GetConfig()->user_agent_id());
     } else {
@@ -12671,7 +12686,7 @@
       EXPECT_EQ(i - (kMaxRecentCryptoConfigs + 1) < j && j <= i,
                 !QuicSessionPoolPeer::CryptoConfigCacheIsEmpty(
                     pool_.get(), kQuicServerId,
-                    QuicSessionPool::QuicCryptoClientConfigKey(
+                    CreateTestQuicCryptoClientConfigKey(
                         network_anonymization_keys[j])));
     }
 
@@ -12686,7 +12701,7 @@
       EXPECT_EQ(i - kMaxRecentCryptoConfigs < j && j <= i,
                 !QuicSessionPoolPeer::CryptoConfigCacheIsEmpty(
                     pool_.get(), kQuicServerId,
-                    QuicSessionPool::QuicCryptoClientConfigKey(
+                    CreateTestQuicCryptoClientConfigKey(
                         network_anonymization_keys[j])));
     }
   }
diff --git a/net/ssl/ssl_info.h b/net/ssl/ssl_info.h
index 3926bd0f..686ab12 100644
--- a/net/ssl/ssl_info.h
+++ b/net/ssl/ssl_info.h
@@ -92,11 +92,10 @@
 
   HandshakeType handshake_type = HANDSHAKE_UNKNOWN;
 
-  // The hashes of the SubjectPublicKeyInfo from each certificate in the
-  // verified chain.
-  //
-  // TODO(crbug.com/41286522): change to document/require this be in the same
-  // order as verified cert chain.
+  // If the certificate was successfully verified, contains the hashes of the
+  // SubjectPublicKeyInfo from each certificate in the verified chain. The
+  // ordering of the hashes matches the order of the verified chain (leaf to
+  // root).
   std::vector<SHA256HashValue> public_key_hashes;
 
   // List of SignedCertificateTimestamps and their corresponding validation
diff --git a/remoting/base/cloud_session_authz_service_client_factory.cc b/remoting/base/cloud_session_authz_service_client_factory.cc
index f37b076..3922a07 100644
--- a/remoting/base/cloud_session_authz_service_client_factory.cc
+++ b/remoting/base/cloud_session_authz_service_client_factory.cc
@@ -229,6 +229,13 @@
         session_policies.host_udp_port_range.max_port = max_port;
       }
     }
+    if (response->session_policies().has_maximum_session_duration()) {
+      auto maximum_session_duration = base::Seconds(
+          response->session_policies().maximum_session_duration().seconds());
+      session_policies.maximum_session_duration =
+          std::max(maximum_session_duration,
+                   SessionPolicies::kMinMaximumSessionDuration);
+    }
     response_struct->session_policies.emplace(std::move(session_policies));
   }
 
diff --git a/remoting/host/BUILD.gn b/remoting/host/BUILD.gn
index 065bff0d..0d7cfe1 100644
--- a/remoting/host/BUILD.gn
+++ b/remoting/host/BUILD.gn
@@ -948,6 +948,10 @@
     sources += [ "evaluate_capability_unittest.cc" ]
     data_deps = [ "//remoting/test:capability_test_stub" ]
   }
+
+  # TODO(crbug.com/40031409): Fix code that adds exit-time destructors and
+  # enable the diagnostic by removing this line.
+  configs += [ "//build/config/compiler:no_exit_time_destructors" ]
 }
 
 group("remoting_host_branded") {
diff --git a/remoting/host/file_transfer/BUILD.gn b/remoting/host/file_transfer/BUILD.gn
index 6e0d49b..368e919 100644
--- a/remoting/host/file_transfer/BUILD.gn
+++ b/remoting/host/file_transfer/BUILD.gn
@@ -179,4 +179,8 @@
       "local_file_operations_unittest.cc",
     ]
   }
+
+  # TODO(crbug.com/40031409): Fix code that adds exit-time destructors and
+  # enable the diagnostic by removing this line.
+  configs += [ "//build/config/compiler:no_exit_time_destructors" ]
 }
diff --git a/remoting/proto/google/internal/remoting/cloud/v1alpha/session_policies.proto b/remoting/proto/google/internal/remoting/cloud/v1alpha/session_policies.proto
index 46fe3fe..666e0c7e4 100644
--- a/remoting/proto/google/internal/remoting/cloud/v1alpha/session_policies.proto
+++ b/remoting/proto/google/internal/remoting/cloud/v1alpha/session_policies.proto
@@ -8,6 +8,8 @@
 
 package google.internal.remoting.cloud.v1alpha;
 
+import "duration.proto";
+
 // Policies to be applied to the CRD host and client.
 // Absent values indicate that a sensible default value should be used, which
 // is usually fail-open. For example, `allow_file_transfer` being absent
@@ -52,4 +54,10 @@
   // Restrict the UDP port range used by the remote access host. Defaults to no
   // restrictions.
   optional PortRange host_udp_port_range = 7;
+
+  // The maximum length of time for either:
+  // 1) A single session if 2FA is not configured.
+  // 2) A session continuum (concurrent sessions) when 2FA is required.
+  // Default is no maximum session length.
+  optional Duration maximum_session_duration = 8;
 }
diff --git a/sandbox/policy/sandbox.cc b/sandbox/policy/sandbox.cc
index 84971680..fde7bb8 100644
--- a/sandbox/policy/sandbox.cc
+++ b/sandbox/policy/sandbox.cc
@@ -111,6 +111,10 @@
   return (status & kLayer1Flags) != 0 && (status & kLayer2Flags) != 0;
 #elif BUILDFLAG(IS_MAC)
   return Seatbelt::IsSandboxed();
+#elif BUILDFLAG(IS_IOS)
+  // Process launching on iOS is only supported via BrowserEngineKit which
+  // will automatically sandbox processes.
+  return !is_browser;
 #elif BUILDFLAG(IS_WIN)
 #if !defined(COMPONENT_BUILD)
   // Target services is not available in the component build.
diff --git a/services/network/scheduler/network_service_task_queues.cc b/services/network/scheduler/network_service_task_queues.cc
index b8ccd348..766575f7 100644
--- a/services/network/scheduler/network_service_task_queues.cc
+++ b/services/network/scheduler/network_service_task_queues.cc
@@ -74,7 +74,7 @@
 
     // Sample with a 0.001 probability to reduce metrics overhead.
     if (sampler_.ShouldSample(0.001)) {
-      base::UmaHistogramCounts100(
+      base::UmaHistogramCounts1000(
           base::StrCat(
               {"NetworkService.Scheduler.IOThread.NumberOfPendingTasks.",
                queue_name_, "Queue"}),
diff --git a/services/on_device_model/BUILD.gn b/services/on_device_model/BUILD.gn
index d1e2d744..073c6100 100644
--- a/services/on_device_model/BUILD.gn
+++ b/services/on_device_model/BUILD.gn
@@ -25,7 +25,6 @@
   sources = [
     "on_device_model_service.cc",
     "on_device_model_service.h",
-    "pre_sandbox_init.cc",
   ]
   deps = [
     ":backend_interfaces",
diff --git a/services/on_device_model/DEPS b/services/on_device_model/DEPS
index f013654b..654bc958 100644
--- a/services/on_device_model/DEPS
+++ b/services/on_device_model/DEPS
@@ -8,10 +8,3 @@
   "+components/crash/core/common/crash_key.h",
   "+components/optimization_guide/core/optimization_guide_features.h",
 ]
-
-specific_include_rules = {
-  "pre_sandbox_init\.cc": [
-    "+chromeos/ash/components/dbus",
-    "+chromeos/dbus/init",
-  ],
-}
diff --git a/services/on_device_model/OWNERS b/services/on_device_model/OWNERS
index a08fc83..4af9e89 100644
--- a/services/on_device_model/OWNERS
+++ b/services/on_device_model/OWNERS
@@ -2,6 +2,3 @@
 cduvall@chromium.org # Primary
 holte@chromium.org
 wittman@chromium.org
-
-per-file pre_sandbox_init.cc=set noparent
-per-file pre_sandbox_init.cc=file://ipc/SECURITY_OWNERS
diff --git a/services/on_device_model/ml/on_device_model_executor.cc b/services/on_device_model/ml/on_device_model_executor.cc
index 1bc47f3..5b0953f4 100644
--- a/services/on_device_model/ml/on_device_model_executor.cc
+++ b/services/on_device_model/ml/on_device_model_executor.cc
@@ -283,7 +283,8 @@
     return base::unexpected(
         on_device_model::ServiceDisconnectReason::kFailedToLoadLibrary);
   }
-  if (!optimization_guide::features::ForceCpuBackendForOnDeviceModel() &&
+  if (!base::FeatureList::IsEnabled(
+          on_device_model::features::kOnDeviceModelForceCpuBackend) &&
       ml::IsGpuBlocked(chrome_ml_->api())) {
     return base::unexpected(
         on_device_model::ServiceDisconnectReason::kGpuBlocked);
@@ -577,7 +578,8 @@
   }
   // TODO(crbug.com/400998489): Cache files are experimental for now.
   data.cache_file =
-      optimization_guide::features::ForceCpuBackendForOnDeviceModel() &&
+      base::FeatureList::IsEnabled(
+          on_device_model::features::kOnDeviceModelForceCpuBackend) &&
               assets.cache.IsValid()
           ? assets.cache.TakePlatformFile()
           : base::kInvalidPlatformFile;
diff --git a/services/on_device_model/on_device_model_service.h b/services/on_device_model/on_device_model_service.h
index 8184aa0..043d15c6 100644
--- a/services/on_device_model/on_device_model_service.h
+++ b/services/on_device_model/on_device_model_service.h
@@ -23,10 +23,6 @@
 #include "services/on_device_model/public/mojom/on_device_model.mojom.h"
 #include "services/on_device_model/public/mojom/on_device_model_service.mojom.h"
 
-#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
-#include "sandbox/policy/linux/sandbox_linux.h"
-#endif
-
 namespace on_device_model {
 
 inline constexpr base::TimeDelta kDefaultModelIdleTimeout = base::Minutes(5);
@@ -34,19 +30,6 @@
 class COMPONENT_EXPORT(ON_DEVICE_MODEL) OnDeviceModelService
     : public mojom::OnDeviceModelService {
  public:
-  // Must be called in the service's process before sandbox initialization.
-  // These are defined separately in pre_sandbox_init.cc for explicit security
-  // review coverage.
-  [[nodiscard]] static bool PreSandboxInit();
-
-  // Must be called in the service's process after the run loop finished.
-  [[nodiscard]] static bool Shutdown();
-
-#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
-  static void AddSandboxLinuxOptions(
-      sandbox::policy::SandboxLinux::Options& options);
-#endif
-
   OnDeviceModelService(
       mojo::PendingReceiver<mojom::OnDeviceModelService> receiver,
       const ml::ChromeML& impl);
diff --git a/services/on_device_model/public/cpp/BUILD.gn b/services/on_device_model/public/cpp/BUILD.gn
index 931ee35..8d2b53458 100644
--- a/services/on_device_model/public/cpp/BUILD.gn
+++ b/services/on_device_model/public/cpp/BUILD.gn
@@ -8,6 +8,8 @@
 component("cpp") {
   output_name = "on_device_model_cpp"
   sources = [
+    "cpu.cc",
+    "cpu.h",
     "features.cc",
     "features.h",
     "service_client.cc",
@@ -19,6 +21,7 @@
     ":buildflags",
     ":types",
     "//base",
+    "//components/optimization_guide/core:features",
     "//services/on_device_model/public/mojom",
   ]
   defines = [ "IS_ON_DEVICE_MODEL_CPP_IMPL" ]
@@ -40,7 +43,7 @@
     "//base",
     "//mojo/public/cpp/base:shared_typemap_traits",
     "//services/on_device_model/public/mojom:mojom_shared",
-    "//skia:skia_core_public_headers"
+    "//skia:skia_core_public_headers",
   ]
   defines = [ "IS_ON_DEVICE_MODEL_CPP_IMPL" ]
 }
diff --git a/services/on_device_model/public/cpp/cpu.cc b/services/on_device_model/public/cpp/cpu.cc
new file mode 100644
index 0000000..1d58984e
--- /dev/null
+++ b/services/on_device_model/public/cpp/cpu.cc
@@ -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.
+
+#include "services/on_device_model/public/cpp/cpu.h"
+
+#include "base/feature_list.h"
+#include "base/system/sys_info.h"
+#include "services/on_device_model/public/cpp/features.h"
+
+namespace on_device_model {
+namespace {
+
+const base::FeatureParam<int> kRAMThreshold{&features::kOnDeviceModelCpuBackend,
+                                            "on_device_cpu_ram_threshold_mb",
+                                            15000};
+
+const base::FeatureParam<int> kProcessorThreshold{
+    &features::kOnDeviceModelCpuBackend,
+    "on_device_cpu_processor_count_threshold", 4};
+
+}  // namespace
+
+bool IsCpuCapable() {
+  if (base::FeatureList::IsEnabled(features::kOnDeviceModelForceCpuBackend)) {
+    return true;
+  }
+  return base::FeatureList::IsEnabled(features::kOnDeviceModelCpuBackend) &&
+         base::SysInfo::AmountOfPhysicalMemoryMB() >= kRAMThreshold.Get() &&
+         base::SysInfo::NumberOfProcessors() >= kProcessorThreshold.Get();
+}
+
+}  // namespace on_device_model
diff --git a/services/on_device_model/public/cpp/cpu.h b/services/on_device_model/public/cpp/cpu.h
new file mode 100644
index 0000000..15bbdc3c
--- /dev/null
+++ b/services/on_device_model/public/cpp/cpu.h
@@ -0,0 +1,18 @@
+// Copyright 2025 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef SERVICES_ON_DEVICE_MODEL_PUBLIC_CPP_CPU_H_
+#define SERVICES_ON_DEVICE_MODEL_PUBLIC_CPP_CPU_H_
+
+#include "base/component_export.h"
+
+namespace on_device_model {
+
+// Whether the device is capable of running the on-device model on CPU.
+COMPONENT_EXPORT(ON_DEVICE_MODEL_CPP)
+bool IsCpuCapable();
+
+}  // namespace on_device_model
+
+#endif  // SERVICES_ON_DEVICE_MODEL_PUBLIC_CPP_CPU_H_
diff --git a/services/on_device_model/public/cpp/features.cc b/services/on_device_model/public/cpp/features.cc
index 19280fed..d6d67d9 100644
--- a/services/on_device_model/public/cpp/features.cc
+++ b/services/on_device_model/public/cpp/features.cc
@@ -10,4 +10,12 @@
              "UseFakeChromeML",
              base::FEATURE_DISABLED_BY_DEFAULT);
 
+BASE_FEATURE(kOnDeviceModelForceCpuBackend,
+             "OnDeviceModelForceCpuBackend",
+             base::FEATURE_DISABLED_BY_DEFAULT);
+
+BASE_FEATURE(kOnDeviceModelCpuBackend,
+             "OnDeviceModelCpuBackend",
+             base::FEATURE_DISABLED_BY_DEFAULT);
+
 }  // namespace on_device_model::features
diff --git a/services/on_device_model/public/cpp/features.h b/services/on_device_model/public/cpp/features.h
index 1c1d7d2..f24b87d 100644
--- a/services/on_device_model/public/cpp/features.h
+++ b/services/on_device_model/public/cpp/features.h
@@ -14,6 +14,14 @@
 COMPONENT_EXPORT(ON_DEVICE_MODEL_CPP)
 BASE_DECLARE_FEATURE(kUseFakeChromeML);
 
+// Whether the on-device model should be limited to running only on the CPU.
+COMPONENT_EXPORT(ON_DEVICE_MODEL_CPP)
+BASE_DECLARE_FEATURE(kOnDeviceModelForceCpuBackend);
+
+// Whether the CPU backend for the on-device model is enabled.
+COMPONENT_EXPORT(ON_DEVICE_MODEL_CPP)
+BASE_DECLARE_FEATURE(kOnDeviceModelCpuBackend);
+
 }  // namespace on_device_model::features
 
 #endif  // SERVICES_ON_DEVICE_MODEL_PUBLIC_CPP_FEATURES_H_
diff --git a/services/webnn/tflite/graph_builder_tflite.cc b/services/webnn/tflite/graph_builder_tflite.cc
index 9348af1..27ea693 100644
--- a/services/webnn/tflite/graph_builder_tflite.cc
+++ b/services/webnn/tflite/graph_builder_tflite.cc
@@ -710,8 +710,9 @@
        // any dimension.
        /*reduce_sum_square_input=*/
        {kFloat16To32AndInt32, SupportedRanks::UpTo(8)},
+       // ReLU is sometimes emulated using maximum.
        /*relu_input=*/
-       {kFloat16To32AndInt8To64, SupportedRanks::UpTo(8)},
+       {kFloat16To32AndInt8To64, SupportedRanks::UpTo(5)},
        // https://source.chromium.org/chromium/chromium/src/+/main:third_party/tflite/src/tensorflow/lite/kernels/internal/reference/resize_bilinear.h
        // https://source.chromium.org/chromium/chromium/src/+/main:third_party/tflite/src/tensorflow/lite/kernels/internal/reference/resize_nearest_neighbor.h
        /*resample2d_input=*/
diff --git a/testing/variations/fieldtrial_testing_config.json b/testing/variations/fieldtrial_testing_config.json
index c79af85..49d0fe5 100644
--- a/testing/variations/fieldtrial_testing_config.json
+++ b/testing/variations/fieldtrial_testing_config.json
@@ -10826,6 +10826,22 @@
             ]
         }
     ],
+    "GlicFreWarming": [
+        {
+            "platforms": [
+                "mac",
+                "windows"
+            ],
+            "experiments": [
+                {
+                    "name": "Enabled",
+                    "enable_features": [
+                        "GlicFreWarming"
+                    ]
+                }
+            ]
+        }
+    ],
     "GlicRolloutDogfood": [
         {
             "platforms": [
@@ -12917,22 +12933,6 @@
             ]
         }
     ],
-    "IOSReaderMode": [
-        {
-            "platforms": [
-                "ios"
-            ],
-            "experiments": [
-                {
-                    "name": "Enabled",
-                    "enable_features": [
-                        "EnableReaderMode",
-                        "EnableReaderModePageEligibilityForToolsMenu"
-                    ]
-                }
-            ]
-        }
-    ],
     "IOSReaderModeMetrics": [
         {
             "platforms": [
diff --git a/third_party/androidx/BUILD.gn b/third_party/androidx/BUILD.gn
index cff4bbd..57de6ada 100644
--- a/third_party/androidx/BUILD.gn
+++ b/third_party/androidx/BUILD.gn
@@ -2304,6 +2304,7 @@
     deps = [
       ":androidx_annotation_annotation_experimental_java",
       ":androidx_collection_collection_java",
+      ":androidx_compose_runtime_runtime_java",
       "//third_party/kotlin_stdlib:kotlin_stdlib_java",
     ]
   }
diff --git a/third_party/androidx/build.gradle b/third_party/androidx/build.gradle
index 51bcef1..0e974d9 100644
--- a/third_party/androidx/build.gradle
+++ b/third_party/androidx/build.gradle
@@ -306,7 +306,7 @@
     google()
     maven {
         // This URL is generated by the fetch_all_androidx.py script.
-        url 'https://androidx.dev/snapshots/builds/13802560/artifacts/repository'
+        url 'https://androidx.dev/snapshots/builds/13806083/artifacts/repository'
     }
     mavenCentral()
 }
diff --git a/third_party/blink/common/BUILD.gn b/third_party/blink/common/BUILD.gn
index a03518e..c8ebba4e 100644
--- a/third_party/blink/common/BUILD.gn
+++ b/third_party/blink/common/BUILD.gn
@@ -45,6 +45,7 @@
              "../renderer/build/scripts/make_runtime_features.py",
              "../renderer/build/scripts/templates/features_generated.cc.tmpl",
              "../renderer/platform/runtime_enabled_features.json5",
+             "../renderer/platform/runtime_enabled_features.override.json5",
            ]
   outputs = [ "$root_gen_dir/third_party/blink/common/features_generated.cc" ]
 
@@ -69,6 +70,7 @@
              "../renderer/build/scripts/make_runtime_features.py",
              "../renderer/build/scripts/templates/origin_trials.cc.tmpl",
              "../renderer/platform/runtime_enabled_features.json5",
+             "../renderer/platform/runtime_enabled_features.override.json5",
            ]
   outputs = [
     "$root_gen_dir/third_party/blink/common/origin_trials/origin_trials.cc",
@@ -92,6 +94,7 @@
              "../renderer/build/scripts/make_runtime_features.py",
              "../renderer/build/scripts/templates/runtime_feature_state_context.cc.tmpl",
              "../renderer/platform/runtime_enabled_features.json5",
+             "../renderer/platform/runtime_enabled_features.override.json5",
            ]
 
   outputs = [ "$root_gen_dir/third_party/blink/common/runtime_feature_state/runtime_feature_state_context.cc" ]
diff --git a/third_party/blink/common/features.cc b/third_party/blink/common/features.cc
index 5808375..53fb153d 100644
--- a/third_party/blink/common/features.cc
+++ b/third_party/blink/common/features.cc
@@ -2154,11 +2154,6 @@
              "Prerender2EarlyDocumentLifecycleUpdate",
              base::FEATURE_DISABLED_BY_DEFAULT);
 
-// Enable limiting previews loading hints to specific resource types.
-BASE_FEATURE(kPreviewsResourceLoadingHintsSpecificResourceTypes,
-             "PreviewsResourceLoadingHintsSpecificResourceTypes",
-             base::FEATURE_DISABLED_BY_DEFAULT);
-
 #if BUILDFLAG(IS_WIN)
 BASE_FEATURE(kPrewarmDefaultFontFamilies,
              "PrewarmDefaultFontFamilies",
diff --git a/third_party/blink/public/BUILD.gn b/third_party/blink/public/BUILD.gn
index a194cb53..c1b93d4 100644
--- a/third_party/blink/public/BUILD.gn
+++ b/third_party/blink/public/BUILD.gn
@@ -474,6 +474,7 @@
   inputs =
       scripts_for_json5_files + [
         runtime_enabled_features_json5,
+        "../renderer/platform/runtime_enabled_features.override.json5",
         "../renderer/build/scripts/make_runtime_features_utilities.py",
         "../renderer/build/scripts/make_runtime_features.py",
         "../renderer/build/scripts/templates/features_generated.h.tmpl",
diff --git a/third_party/blink/public/common/BUILD.gn b/third_party/blink/public/common/BUILD.gn
index 6f91ba4..17a9a837 100644
--- a/third_party/blink/public/common/BUILD.gn
+++ b/third_party/blink/public/common/BUILD.gn
@@ -47,6 +47,7 @@
              "../../renderer/build/scripts/templates/runtime_feature_state_context.h.tmpl",
              "../../renderer/build/scripts/templates/runtime_feature_state_read_context.h.tmpl",
              "../../renderer/platform/runtime_enabled_features.json5",
+             "../../renderer/platform/runtime_enabled_features.override.json5",
            ]
 
   outputs = [
diff --git a/third_party/blink/public/common/features.h b/third_party/blink/public/common/features.h
index bda65e4..d584e71e 100644
--- a/third_party/blink/public/common/features.h
+++ b/third_party/blink/public/common/features.h
@@ -1456,9 +1456,6 @@
 // crbug.com/353628449 for more details.
 BLINK_COMMON_EXPORT BASE_DECLARE_FEATURE(kPageHideEventForPrerender2);
 
-BLINK_COMMON_EXPORT BASE_DECLARE_FEATURE(
-    kPreviewsResourceLoadingHintsSpecificResourceTypes);
-
 #if BUILDFLAG(IS_WIN)
 // Enables prewarming the default font families.
 BLINK_COMMON_EXPORT BASE_DECLARE_FEATURE(kPrewarmDefaultFontFamilies);
diff --git a/third_party/blink/public/mojom/BUILD.gn b/third_party/blink/public/mojom/BUILD.gn
index 3c1bf9a8..63410b5 100644
--- a/third_party/blink/public/mojom/BUILD.gn
+++ b/third_party/blink/public/mojom/BUILD.gn
@@ -62,6 +62,7 @@
     "content_extraction/frame_metadata_observer_registry.mojom",
     "content_extraction/inner_html.mojom",
     "content_extraction/inner_text.mojom",
+    "content_extraction/script_tools.mojom",
     "content_index/content_index.mojom",
     "context_menu/context_menu.mojom",
     "conversions/conversions.mojom",
diff --git a/third_party/blink/public/mojom/content_extraction/script_tools.mojom b/third_party/blink/public/mojom/content_extraction/script_tools.mojom
new file mode 100644
index 0000000..f30108aa
--- /dev/null
+++ b/third_party/blink/public/mojom/content_extraction/script_tools.mojom
@@ -0,0 +1,21 @@
+// 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 blink.mojom;
+
+// See AnnotationsDict in tool_registration_params.idl for details of this
+// data structure.
+struct ScriptToolAnnotations {
+  bool read_only;
+};
+
+// See ToolRegistrationParams in tool_registration_params.idl for details of
+// this data structure.
+struct ScriptTool {
+  // These directly map to the corresponding fields in the web API.
+  string name;
+  string description;
+  string input_schema;
+  ScriptToolAnnotations annotations;
+};
diff --git a/third_party/blink/public/mojom/origin_trials/BUILD.gn b/third_party/blink/public/mojom/origin_trials/BUILD.gn
index bf3723d..5c619e1 100644
--- a/third_party/blink/public/mojom/origin_trials/BUILD.gn
+++ b/third_party/blink/public/mojom/origin_trials/BUILD.gn
@@ -61,6 +61,7 @@
              "../../../renderer/build/scripts/make_runtime_features.py",
              "../../../renderer/build/scripts/templates/origin_trial_feature.mojom.tmpl",
              "../../../renderer/platform/runtime_enabled_features.json5",
+             "../../../renderer/platform/runtime_enabled_features.override.json5",
            ]
   outputs = [ "$root_gen_dir/third_party/blink/public/mojom/origin_trials/origin_trial_feature.mojom" ]
 }
diff --git a/third_party/blink/public/mojom/runtime_feature_state/BUILD.gn b/third_party/blink/public/mojom/runtime_feature_state/BUILD.gn
index 7e177f2..637a05e 100644
--- a/third_party/blink/public/mojom/runtime_feature_state/BUILD.gn
+++ b/third_party/blink/public/mojom/runtime_feature_state/BUILD.gn
@@ -42,6 +42,7 @@
         "../../../renderer/build/scripts/make_runtime_features.py",
         "../../../renderer/build/scripts/templates/runtime_feature.mojom.tmpl",
         "../../../renderer/platform/runtime_enabled_features.json5",
+        "../../../renderer/platform/runtime_enabled_features.override.json5",
       ]
   outputs = [ "$root_gen_dir/third_party/blink/public/mojom/runtime_feature_state/runtime_feature.mojom" ]
 }
diff --git a/third_party/blink/renderer/bindings/generated_in_core.gni b/third_party/blink/renderer/bindings/generated_in_core.gni
index 00fb5c9..6fc4020 100644
--- a/third_party/blink/renderer/bindings/generated_in_core.gni
+++ b/third_party/blink/renderer/bindings/generated_in_core.gni
@@ -82,6 +82,8 @@
   "$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_scheduler_post_task_callback.h",
   "$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_subscribe_callback.cc",
   "$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_subscribe_callback.h",
+  "$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_tool_function.cc",
+  "$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_tool_function.h",
   "$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_underlying_source_cancel_callback.cc",
   "$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_underlying_source_cancel_callback.h",
   "$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_underlying_source_pull_callback.cc",
@@ -150,6 +152,10 @@
   "$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_clipboard_event_init.h",
   "$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_close_watcher_options.cc",
   "$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_close_watcher_options.h",
+  "$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_tool_registration_params.cc",
+  "$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_tool_registration_params.h",
+  "$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_annotations_dict.cc",
+  "$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_annotations_dict.h",
   "$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_command_event_init.cc",
   "$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_command_event_init.h",
   "$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_composition_event_init.cc",
@@ -1732,6 +1738,8 @@
   "$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_view_timeline.h",
   "$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_view_transition.cc",
   "$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_view_transition.h",
+  "$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_automation_delegate.cc",
+  "$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_automation_delegate.h",
   "$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_view_transition_type_set.cc",
   "$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_view_transition_type_set.h",
   "$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_visibility_state_entry.cc",
diff --git a/third_party/blink/renderer/bindings/idl_in_core.gni b/third_party/blink/renderer/bindings/idl_in_core.gni
index c6ea950c..c4b0107d 100644
--- a/third_party/blink/renderer/bindings/idl_in_core.gni
+++ b/third_party/blink/renderer/bindings/idl_in_core.gni
@@ -786,6 +786,9 @@
   "//third_party/blink/renderer/core/view_transition/view_transition_options.idl",
   "//third_party/blink/renderer/core/view_transition/view_transition_type_set.idl",
   "//third_party/blink/renderer/core/view_transition/view_transition_supplement.idl",
+  "//third_party/blink/renderer/core/script_tools/automation_delegate.idl",
+  "//third_party/blink/renderer/core/script_tools/automation_delegate_supplement.idl",
+  "//third_party/blink/renderer/core/script_tools/tool_registration_params.idl",
   "//third_party/blink/renderer/core/workers/abstract_worker.idl",
   "//third_party/blink/renderer/core/workers/dedicated_worker_global_scope.idl",
   "//third_party/blink/renderer/core/workers/shared_worker.idl",
diff --git a/third_party/blink/renderer/bindings/scripts/web_idl/runtime_enabled_features.py b/third_party/blink/renderer/bindings/scripts/web_idl/runtime_enabled_features.py
index 0372e96..d6d6e2b0 100644
--- a/third_party/blink/renderer/bindings/scripts/web_idl/runtime_enabled_features.py
+++ b/third_party/blink/renderer/bindings/scripts/web_idl/runtime_enabled_features.py
@@ -3,7 +3,7 @@
 # found in the LICENSE file.
 
 import json5
-
+import os
 
 class RuntimeEnabledFeatures(object):
     """Represents a set of definitions of runtime enabled features."""
@@ -29,9 +29,18 @@
             with open(filepath, encoding='utf-8') as file_obj:
                 datastore = json5.load(file_obj)
 
-            for entry in datastore["data"]:
-                assert entry["name"] not in cls._features
-                cls._features[entry["name"]] = entry
+                for entry in datastore["data"]:
+                    assert entry["name"] not in cls._features
+                    cls._features[entry["name"]] = entry
+
+            file_root, file_ext = os.path.splitext(filepath)
+            override_file_path = f"{file_root}.override{file_ext}"
+            if os.path.exists(override_file_path):
+                with open(override_file_path, encoding='utf-8') as file_obj:
+                    datastore = json5.load(file_obj)
+
+                    for entry in datastore["data"]:
+                        cls._features[entry["name"]] = entry
 
         cls._is_initialized = True
 
diff --git a/third_party/blink/renderer/bindings/scripts/web_idl/runtime_enabled_features_unittest.py b/third_party/blink/renderer/bindings/scripts/web_idl/runtime_enabled_features_unittest.py
new file mode 100644
index 0000000..051adac
--- /dev/null
+++ b/third_party/blink/renderer/bindings/scripts/web_idl/runtime_enabled_features_unittest.py
@@ -0,0 +1,73 @@
+# 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.
+"""Unit tests for runtime_enabled_features.py."""
+
+import unittest
+import os
+
+from .runtime_enabled_features import RuntimeEnabledFeatures
+
+
+class RuntimeEnabledFeaturesTest(unittest.TestCase):
+
+    def path_of_test_file(self, file_name):
+        return os.path.join(os.path.dirname(os.path.realpath(__file__)),
+                            'tests', file_name)
+
+    def test_cycle(self):
+        runtime_enabled_features_paths = self.path_of_test_file(
+            'runtime_enabled_features_valid.json5')
+        RuntimeEnabledFeatures.init(filepaths=[runtime_enabled_features_paths])
+        assert RuntimeEnabledFeatures._is_initialized is True
+
+        expected = [{
+            'name': 'Feature1',
+            'browser_process_read_write_access': False,
+            'origin_trial_feature_name': None,
+            'status': 'stable',
+        }, {
+            'name': 'Feature2',
+            'browser_process_read_write_access': None,
+            'origin_trial_feature_name': None,
+            'status': 'test',
+        }, {
+            'name': 'Feature3',
+            'browser_process_read_write_access': None,
+            'origin_trial_feature_name': None,
+            'status': 'experimental',
+        }, {
+            'name': 'Feature4',
+            'browser_process_read_write_access': None,
+            'origin_trial_feature_name': None,
+            'status': 'stable',
+        }, {
+            'name': 'Feature5',
+            'browser_process_read_write_access': True,
+            'origin_trial_feature_name': None,
+            'status': 'stable',
+        }, {
+            'name': 'Feature6',
+            'browser_process_read_write_access': None,
+            'origin_trial_feature_name': 'Feature6OT',
+            'status': 'experimental',
+        }]
+
+        self.assertEqual(len(RuntimeEnabledFeatures._features), len(expected))
+        for expected_item in expected:
+            self.assertIn(expected_item['name'],
+                          RuntimeEnabledFeatures._features)
+            actual_feature = RuntimeEnabledFeatures._features[
+                expected_item['name']]
+
+            self.assertEqual(
+                expected_item['browser_process_read_write_access'],
+                actual_feature.get('browser_process_read_write_access'))
+            self.assertEqual(expected_item['status'],
+                             actual_feature.get('status'))
+            self.assertEqual(expected_item['origin_trial_feature_name'],
+                             actual_feature.get('origin_trial_feature_name'))
+
+
+if __name__ == "__main__":
+    unittest.main()
diff --git a/third_party/blink/renderer/bindings/scripts/web_idl/tests/runtime_enabled_features_valid.json5 b/third_party/blink/renderer/bindings/scripts/web_idl/tests/runtime_enabled_features_valid.json5
new file mode 100644
index 0000000..1ecd119e3
--- /dev/null
+++ b/third_party/blink/renderer/bindings/scripts/web_idl/tests/runtime_enabled_features_valid.json5
@@ -0,0 +1,31 @@
+{
+  parameters: {
+    status: {
+      valid_values: ["stable", "experimental", "test"],
+      valid_keys: ["Android", "Win", "ChromeOS", "Mac", "Linux", "iOS"]
+    },
+    origin_trial_feature_name: {
+    },
+    browser_process_read_write_access: {
+      default: false,
+      value_type: "bool",
+    },
+  },
+
+  data: [
+    {
+      name: "Feature1",
+      browser_process_read_write_access: true,
+      status: "stable",
+    },
+    {
+      name: "Feature2",
+      origin_trial_feature_name: "Feature2OT",
+      status: "experimental",
+    },
+    {
+      name: "Feature3",
+      status: "experimental",
+    },
+  ],
+}
\ No newline at end of file
diff --git a/third_party/blink/renderer/bindings/scripts/web_idl/tests/runtime_enabled_features_valid.override.json5 b/third_party/blink/renderer/bindings/scripts/web_idl/tests/runtime_enabled_features_valid.override.json5
new file mode 100644
index 0000000..6ae7300
--- /dev/null
+++ b/third_party/blink/renderer/bindings/scripts/web_idl/tests/runtime_enabled_features_valid.override.json5
@@ -0,0 +1,27 @@
+{
+  data: [
+    {
+      name: "Feature1",
+      browser_process_read_write_access: false,
+      status: "stable",
+    },
+    {
+      name: "Feature2",
+      status: "test",
+    },
+    {
+      name: "Feature4",
+      status: "stable",
+    },
+    {
+      name: "Feature5",
+      browser_process_read_write_access: true,
+      status: "stable",
+    },
+    {
+      name: "Feature6",
+      origin_trial_feature_name: "Feature6OT",
+      status: "experimental",
+    },
+  ],
+}
\ No newline at end of file
diff --git a/third_party/blink/renderer/build/scripts/json5_generator.py b/third_party/blink/renderer/build/scripts/json5_generator.py
index eb1e75b..fa11cd0 100644
--- a/third_party/blink/renderer/build/scripts/json5_generator.py
+++ b/third_party/blink/renderer/build/scripts/json5_generator.py
@@ -125,6 +125,38 @@
         return Json5File(file_paths, merged_doc, default_metadata,
                          default_parameters)
 
+    def load_override_file(self, file_path):
+        assert file_path.endswith(".json5")
+
+        name_dict = {}
+        overrides_list = []
+        with open(os.path.abspath(file_path), encoding='utf-8') as json5_file:
+            doc = json5.loads(json5_file.read())
+            items = doc["data"]
+            if len(items) > 0:
+                self.file_paths.append(file_path)
+
+            if type(items) is list:
+                for item in items:
+                    entry = self._get_entry(item)
+                    name_dict[entry["name"]] = entry
+                    overrides_list.append(entry)
+            else:
+                for key, value in items.items():
+                    value["name"] = key
+                    entry = self._get_entry(value)
+                    overrides_list.append(entry)
+                overrides_list.sort(key=lambda entry: entry["name"])
+
+        for index, entry in enumerate(self.name_dictionaries):
+            name = entry['name']
+            if name in name_dict:
+                self.name_dictionaries[index] = name_dict.pop(name)
+
+        for entry in overrides_list:
+            if entry['name'] in name_dict:
+                self.name_dictionaries.append(entry)
+
     def _process(self, doc):
         # Process optional metadata map entries.
         for key, value in doc.get("metadata", {}).items():
@@ -259,7 +291,8 @@
         self.gperf_path = None
         if json5_files:
             self.json5_file = Json5File.load_from_files(
-                json5_files, self.default_metadata, self.default_parameters)
+                self._input_files, self.default_metadata,
+                self.default_parameters)
         match = re.search(r'\bgen[\\/]', output_dir)
         if match:
             self._relative_output_dir = output_dir[match.end():].replace(
diff --git a/third_party/blink/renderer/build/scripts/json5_generator_unittest.py b/third_party/blink/renderer/build/scripts/json5_generator_unittest.py
index 23f2382a..391556e 100644
--- a/third_party/blink/renderer/build/scripts/json5_generator_unittest.py
+++ b/third_party/blink/renderer/build/scripts/json5_generator_unittest.py
@@ -47,10 +47,59 @@
                 'random': 'values',
                 'default': 'valid'
             }
+        }, {
+            'name': 'item3',
+            'param1': {
+                'keys': 'valid',
+                'default': 'values'
+            }
         }]
         self.assertEqual(len(actual), len(expected))
         for exp, act in zip(expected, actual):
             self.assertDictEqual(exp['param1'], act['param1'])
+            self.assertIsNone(act['param2'])
+
+    def test_valid_dict_value_parse_override(self):
+        json5_file = Json5File.load_from_files(
+            [self.path_of_test_file('json5_generator_valid_dict_value.json5')])
+        json5_file.load_override_file(
+            self.path_of_test_file(
+                'json5_generator_valid_dict_value.override.json5'))
+
+        actual = json5_file.name_dictionaries
+        expected = [{
+            'name': 'item1',
+            'param1': {
+                'keys': 'valid',
+                'default': 'values'
+            }
+        }, {
+            'name': 'item2',
+            'param1': {
+                'random': 'values',
+                'default': 'valid'
+            }
+        }, {
+            'name': 'item3',
+            'param2': {
+                'key': 'single',
+                'default': 'value'
+            }
+        }, {
+            'name': 'item4',
+            'param1': {
+                'keys': 'valid',
+                'random': 'values'
+            }
+        }]
+        self.assertEqual(len(actual), len(expected))
+        for exp, act in zip(expected, actual):
+            param_name = 'param1'
+            if exp['name'] == 'item3':
+                self.assertIsNone(act['param1'])
+                param_name = 'param2'
+            self.assertDictEqual(exp[param_name], act[param_name])
+
 
     def test_no_valid_keys(self):
         with self.assertRaises(AssertionError):
diff --git a/third_party/blink/renderer/build/scripts/make_runtime_features.py b/third_party/blink/renderer/build/scripts/make_runtime_features.py
index 02725e7c..7ccb7d2 100755
--- a/third_party/blink/renderer/build/scripts/make_runtime_features.py
+++ b/third_party/blink/renderer/build/scripts/make_runtime_features.py
@@ -53,6 +53,13 @@
     def __init__(self, json5_file_path, output_dir):
         super(BaseRuntimeFeatureWriter, self).__init__(json5_file_path,
                                                        output_dir)
+
+        for path in json5_file_path:
+            file_root, file_ext = os.path.splitext(path)
+            override_file_path = f"{file_root}.override{file_ext}"
+            if os.path.exists(override_file_path):
+                self.json5_file.load_override_file(override_file_path)
+
         # Subclasses should add generated output files and their contents to this dict.
         self._outputs = {}
         assert self.file_basename
diff --git a/third_party/blink/renderer/build/scripts/tests/json5_generator_valid_dict_value.json5 b/third_party/blink/renderer/build/scripts/tests/json5_generator_valid_dict_value.json5
index d3a12b4c..57f0620 100644
--- a/third_party/blink/renderer/build/scripts/tests/json5_generator_valid_dict_value.json5
+++ b/third_party/blink/renderer/build/scripts/tests/json5_generator_valid_dict_value.json5
@@ -2,7 +2,11 @@
   parameters: {
     param1: {
       valid_values: ["valid", "values"],
-      valid_keys: ["random", "keys"],
+      valid_keys: ["random", "keys", "default"],
+    },
+    param2: {
+      valid_values: ["single", "value"],
+      valid_keys: ["key", "default"],
     }
   },
 
@@ -15,5 +19,9 @@
       name: "item2",
       param1: {"random": "values", "default": "valid"},
     },
+    {
+      name: "item3",
+      param1: {"keys": "valid", "default": "values"},
+    },
   ],
 }
\ No newline at end of file
diff --git a/third_party/blink/renderer/build/scripts/tests/json5_generator_valid_dict_value.override.json5 b/third_party/blink/renderer/build/scripts/tests/json5_generator_valid_dict_value.override.json5
new file mode 100644
index 0000000..a777638
--- /dev/null
+++ b/third_party/blink/renderer/build/scripts/tests/json5_generator_valid_dict_value.override.json5
@@ -0,0 +1,16 @@
+{
+  data: [
+    {
+      name: "item1",
+      param1: {"keys": "valid", "default": "values"},
+    },
+    {
+      name: "item3",
+      param2: {"key": "single", "default": "value"},
+    },
+    {
+      name: "item4",
+      param1: {"keys": "valid", "random": "values"},
+    },
+  ],
+}
\ No newline at end of file
diff --git a/third_party/blink/renderer/core/BUILD.gn b/third_party/blink/renderer/core/BUILD.gn
index 1921e97..23548dd 100644
--- a/third_party/blink/renderer/core/BUILD.gn
+++ b/third_party/blink/renderer/core/BUILD.gn
@@ -66,6 +66,7 @@
 import(
     "//third_party/blink/renderer/core/scheduler_integration_tests/build.gni")
 import("//third_party/blink/renderer/core/script/build.gni")
+import("//third_party/blink/renderer/core/script_tools/build.gni")
 import("//third_party/blink/renderer/core/scroll/build.gni")
 import("//third_party/blink/renderer/core/shadow_realm/build.gni")
 import("//third_party/blink/renderer/core/speculation_rules/build.gni")
@@ -271,6 +272,7 @@
   sources += rebase_path(blink_core_sources_display_lock, "", "display_lock")
   sources +=
       rebase_path(blink_core_sources_view_transition, "", "view_transition")
+  sources += rebase_path(blink_core_sources_script_tools, "", "script_tools")
   sources += rebase_path(blink_core_sources_dom, "", "dom")
   sources += rebase_path(blink_core_sources_editing, "", "editing")
   sources += rebase_path(blink_core_sources_events, "", "events")
@@ -719,6 +721,7 @@
              "../build/scripts/templates/internal_runtime_flags.h.tmpl",
              "../build/scripts/templates/internal_runtime_flags.idl.tmpl",
              "../platform/runtime_enabled_features.json5",
+             "../platform/runtime_enabled_features.override.json5",
            ]
 
   args = [
@@ -1166,6 +1169,7 @@
              "../build/scripts/make_runtime_features_utilities.py",
              "../build/scripts/templates/policy_helper.cc.tmpl",
              "../platform/runtime_enabled_features.json5",
+             "../platform/runtime_enabled_features.override.json5",
              "./permissions_policy/document_policy_features.json5",
              "//services/network/public/cpp/permissions_policy/permissions_policy_features.json5",
            ]
diff --git a/third_party/blink/renderer/core/layout/masonry/masonry_layout_algorithm.cc b/third_party/blink/renderer/core/layout/masonry/masonry_layout_algorithm.cc
index 535fa39..dd9607b3 100644
--- a/third_party/blink/renderer/core/layout/masonry/masonry_layout_algorithm.cc
+++ b/third_party/blink/renderer/core/layout/masonry/masonry_layout_algorithm.cc
@@ -302,10 +302,6 @@
     // Update `running_positions` of the tracks that the items spans to include
     // the size of the item, the size of the gap in the stacking axis, and the
     // margin.
-    //
-    // TODO(celestepan): Once we account for writing direction, we may have to
-    // ensure that we are adding the block/inline size of the item based on
-    // whether or not it is parallel to the direction of the masonry axis.
     auto new_running_position =
         max_position + stacking_axis_gap +
         (is_for_columns ? fragment.BlockSize() + margins.BlockSum()
@@ -367,17 +363,18 @@
           item_style.GetWritingMode(), GetConstraintSpace().GetWritingMode());
       bool use_item_inline_contribution =
           is_for_columns ? is_parallel : !is_parallel;
-
       // TODO(almaher): Subgrids have extra margin to handle unique gap sizes.
       // This requires access to the subgrid track collection, where that extra
       // margin is accumulated.
       const BoxStrut margins =
           ComputeMarginsFor(space, item_style, GetConstraintSpace());
+      const LayoutUnit margins_sum =
+          is_for_columns ? margins.InlineSum() : margins.BlockSum();
 
       if (use_item_inline_contribution) {
         MinMaxSizes min_max_sizes =
             ComputeMinAndMaxContentContributionForSelf(item_node, space).sizes;
-        min_max_sizes += margins.InlineSum();
+        min_max_sizes += margins_sum;
 
         // We have a repeat() track definition with an auto sized track(s). The
         // current track sizing pass is used to find the track size to apply
@@ -398,7 +395,7 @@
             ComputeMasonryItemBlockContribution(
                 grid_axis_direction, sizing_constraint, space, &item_data,
                 needs_auto_track_size) +
-            margins.BlockSum();
+            margins_sum;
 
         // We have a repeat() track definition with an auto sized track(s). The
         // current track sizing pass is used to find the track size to apply
diff --git a/third_party/blink/renderer/core/script_tools/automation_delegate.cc b/third_party/blink/renderer/core/script_tools/automation_delegate.cc
new file mode 100644
index 0000000..3d4c908
--- /dev/null
+++ b/third_party/blink/renderer/core/script_tools/automation_delegate.cc
@@ -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.
+
+#include "third_party/blink/renderer/core/script_tools/automation_delegate.h"
+
+#include "third_party/blink/renderer/bindings/core/v8/v8_annotations_dict.h"
+
+namespace blink {
+
+namespace {
+
+String ValidateAndStringifyObject(ScriptState* script_state,
+                                  const ScriptObject& input) {
+  v8::Local<v8::String> value;
+  if (!v8::JSON::Stringify(script_state->GetContext(), input.V8Object())
+           .ToLocal(&value)) {
+    return String();
+  }
+  return ToBlinkString<String>(script_state->GetIsolate(), value,
+                               kDoNotExternalize);
+}
+
+}  // namespace
+
+AutomationDelegate::AutomationDelegate() = default;
+
+void AutomationDelegate::registerTool(ScriptState* script_state,
+                                      ToolRegistrationParams* params,
+                                      ExceptionState& exception_state) {
+  if (tool_map_.find(params->name()) != tool_map_.end()) {
+    exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError,
+                                      "Duplicate tool name");
+    return;
+  }
+
+  String input_schema;
+  if (params->hasInputSchema()) {
+    input_schema =
+        ValidateAndStringifyObject(script_state, params->inputSchema());
+    if (!input_schema) {
+      exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError,
+                                        "Invalid input schema");
+      return;
+    }
+  }
+
+  auto* tool_data = MakeGarbageCollected<ToolData>();
+
+  auto script_tool = mojom::blink::ScriptTool::New();
+  script_tool->name = params->name();
+  script_tool->description = params->description();
+  script_tool->input_schema = input_schema;
+
+  if (params->hasAnnotations()) {
+    script_tool->annotations = mojom::blink::ScriptToolAnnotations::New();
+    script_tool->annotations->read_only = params->annotations()->readOnlyHint();
+  }
+
+  tool_data->script_tool = std::move(script_tool);
+  tool_data->tool_function = params->execute();
+
+  tool_map_.insert(params->name(), std::move(tool_data));
+}
+
+void AutomationDelegate::unregisterTool(ScriptState* script_state,
+                                        const String& tool_name,
+                                        ExceptionState& exception_state) {
+  auto it = tool_map_.find(tool_name);
+  if (it == tool_map_.end()) {
+    exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError,
+                                      "Invalid tool name");
+    return;
+  }
+
+  tool_map_.erase(it);
+}
+
+void AutomationDelegate::Trace(Visitor* visitor) const {
+  ScriptWrappable::Trace(visitor);
+  visitor->Trace(tool_map_);
+}
+
+void AutomationDelegate::ToolData::Trace(Visitor* visitor) const {
+  visitor->Trace(tool_function);
+}
+
+}  // namespace blink
diff --git a/third_party/blink/renderer/core/script_tools/automation_delegate.h b/third_party/blink/renderer/core/script_tools/automation_delegate.h
new file mode 100644
index 0000000..06240bd
--- /dev/null
+++ b/third_party/blink/renderer/core/script_tools/automation_delegate.h
@@ -0,0 +1,51 @@
+// Copyright 2025 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_SCRIPT_TOOLS_AUTOMATION_DELEGATE_H_
+#define THIRD_PARTY_BLINK_RENDERER_CORE_SCRIPT_TOOLS_AUTOMATION_DELEGATE_H_
+
+#include "base/functional/callback.h"
+#include "third_party/blink/public/mojom/content_extraction/script_tools.mojom-blink.h"
+#include "third_party/blink/renderer/bindings/core/v8/v8_automation_delegate.h"
+#include "third_party/blink/renderer/bindings/core/v8/v8_tool_function.h"
+#include "third_party/blink/renderer/bindings/core/v8/v8_tool_registration_params.h"
+#include "third_party/blink/renderer/core/core_export.h"
+#include "third_party/blink/renderer/core/frame/local_dom_window.h"
+#include "third_party/blink/renderer/platform/allow_discouraged_type.h"
+
+namespace blink {
+
+class CORE_EXPORT AutomationDelegate : public ScriptWrappable {
+  DEFINE_WRAPPERTYPEINFO();
+
+ public:
+  explicit AutomationDelegate();
+
+  void ForEachScriptTool(
+      base::FunctionRef<void(const mojom::blink::ScriptTool&)>) const;
+
+  void registerTool(ScriptState* state,
+                    ToolRegistrationParams* params,
+                    ExceptionState& exception_state);
+  void unregisterTool(ScriptState* state,
+                      const String& name,
+                      ExceptionState& exception_state);
+
+  void Trace(Visitor*) const override;
+
+ private:
+  class ToolData : public GarbageCollected<ToolData> {
+   public:
+    void Trace(Visitor* visitor) const;
+
+    mojo::StructPtr<mojom::blink::ScriptTool> script_tool;
+    Member<V8ToolFunction> tool_function;
+  };
+
+  HeapHashMap<String, Member<ToolData>> tool_map_;
+};
+
+}  // namespace blink
+
+#endif  // THIRD_PARTY_BLINK_RENDERER_CORE_SCRIPT_TOOLS_AUTOMATION_DELEGATE_H_
diff --git a/third_party/blink/renderer/core/script_tools/automation_delegate.idl b/third_party/blink/renderer/core/script_tools/automation_delegate.idl
new file mode 100644
index 0000000..d048ab05
--- /dev/null
+++ b/third_party/blink/renderer/core/script_tools/automation_delegate.idl
@@ -0,0 +1,12 @@
+// Copyright 2025 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+[
+    Exposed=Window,
+    ImplementedAs=AutomationDelegate,
+    RuntimeEnabled=ScriptTools
+] interface AutomationDelegate {
+  [CallWith=ScriptState, RaisesException] undefined registerTool(ToolRegistrationParams params);
+  [CallWith=ScriptState, RaisesException] undefined unregisterTool(DOMString tool_name);
+};
diff --git a/third_party/blink/renderer/core/script_tools/automation_delegate_supplement.cc b/third_party/blink/renderer/core/script_tools/automation_delegate_supplement.cc
new file mode 100644
index 0000000..bcc3964
--- /dev/null
+++ b/third_party/blink/renderer/core/script_tools/automation_delegate_supplement.cc
@@ -0,0 +1,49 @@
+// 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 "third_party/blink/renderer/core/script_tools/automation_delegate_supplement.h"
+
+#include "third_party/blink/renderer/core/frame/local_dom_window.h"
+
+namespace blink {
+
+// static
+const char AutomationDelegateSupplement::kSupplementName[] =
+    "AutomationDelegateSupplement";
+
+// static
+AutomationDelegateSupplement& AutomationDelegateSupplement::From(
+    LocalDOMWindow& window) {
+  AutomationDelegateSupplement* supplement =
+      Supplement<LocalDOMWindow>::From<AutomationDelegateSupplement>(window);
+  if (!supplement) {
+    supplement = MakeGarbageCollected<AutomationDelegateSupplement>(window);
+    ProvideTo(window, supplement);
+  }
+  return *supplement;
+}
+
+// static
+AutomationDelegate* AutomationDelegateSupplement::automationDelegate(
+    LocalDOMWindow& window) {
+  return From(window).automationDelegate();
+}
+
+AutomationDelegateSupplement::AutomationDelegateSupplement(
+    LocalDOMWindow& window)
+    : Supplement<LocalDOMWindow>(window) {}
+
+void AutomationDelegateSupplement::Trace(Visitor* visitor) const {
+  visitor->Trace(automation_delegate_);
+  Supplement<LocalDOMWindow>::Trace(visitor);
+}
+
+AutomationDelegate* AutomationDelegateSupplement::automationDelegate() {
+  if (!automation_delegate_) {
+    automation_delegate_ = MakeGarbageCollected<AutomationDelegate>();
+  }
+  return automation_delegate_.Get();
+}
+
+}  // namespace blink
diff --git a/third_party/blink/renderer/core/script_tools/automation_delegate_supplement.h b/third_party/blink/renderer/core/script_tools/automation_delegate_supplement.h
new file mode 100644
index 0000000..9d78c85
--- /dev/null
+++ b/third_party/blink/renderer/core/script_tools/automation_delegate_supplement.h
@@ -0,0 +1,41 @@
+// 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 THIRD_PARTY_BLINK_RENDERER_CORE_SCRIPT_TOOLS_AUTOMATION_DELEGATE_SUPPLEMENT_H_
+#define THIRD_PARTY_BLINK_RENDERER_CORE_SCRIPT_TOOLS_AUTOMATION_DELEGATE_SUPPLEMENT_H_
+
+#include "third_party/blink/renderer/core/core_export.h"
+#include "third_party/blink/renderer/core/script_tools/automation_delegate.h"
+#include "third_party/blink/renderer/platform/heap/garbage_collected.h"
+#include "third_party/blink/renderer/platform/supplementable.h"
+
+namespace blink {
+
+class LocalDOMWindow;
+
+class CORE_EXPORT AutomationDelegateSupplement final
+    : public GarbageCollected<AutomationDelegateSupplement>,
+      public Supplement<LocalDOMWindow> {
+ public:
+  static const char kSupplementName[];
+
+  static AutomationDelegateSupplement& From(LocalDOMWindow&);
+  static AutomationDelegate* automationDelegate(LocalDOMWindow&);
+
+  explicit AutomationDelegateSupplement(LocalDOMWindow&);
+  AutomationDelegateSupplement(const AutomationDelegateSupplement&) = delete;
+  AutomationDelegateSupplement& operator=(const AutomationDelegateSupplement&) =
+      delete;
+
+  void Trace(Visitor*) const override;
+
+ private:
+  AutomationDelegate* automationDelegate();
+
+  Member<AutomationDelegate> automation_delegate_;
+};
+
+}  // namespace blink
+
+#endif  // THIRD_PARTY_BLINK_RENDERER_CORE_SCRIPT_TOOLS_AUTOMATION_DELEGATE_SUPPLEMENT_H_
diff --git a/third_party/blink/renderer/core/script_tools/automation_delegate_supplement.idl b/third_party/blink/renderer/core/script_tools/automation_delegate_supplement.idl
new file mode 100644
index 0000000..e84a035
--- /dev/null
+++ b/third_party/blink/renderer/core/script_tools/automation_delegate_supplement.idl
@@ -0,0 +1,10 @@
+// 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.
+
+[
+  RuntimeEnabled=ScriptTools,
+  ImplementedAs=AutomationDelegateSupplement
+] partial interface Window {
+  [Replaceable] readonly attribute AutomationDelegate automationDelegate;
+};
diff --git a/third_party/blink/renderer/core/script_tools/build.gni b/third_party/blink/renderer/core/script_tools/build.gni
new file mode 100644
index 0000000..67bdfc5
--- /dev/null
+++ b/third_party/blink/renderer/core/script_tools/build.gni
@@ -0,0 +1,10 @@
+# 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.
+
+blink_core_sources_script_tools = [
+  "automation_delegate.cc",
+  "automation_delegate.h",
+  "automation_delegate_supplement.cc",
+  "automation_delegate_supplement.h",
+]
diff --git a/third_party/blink/renderer/core/script_tools/tool_registration_params.idl b/third_party/blink/renderer/core/script_tools/tool_registration_params.idl
new file mode 100644
index 0000000..5b86a5fa
--- /dev/null
+++ b/third_party/blink/renderer/core/script_tools/tool_registration_params.idl
@@ -0,0 +1,19 @@
+// Copyright 2025 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// TODO(khushalsagar): Link to the explainer for details of the WebIDL.
+
+callback ToolFunction = Promise<any>(any... parameters);
+
+dictionary AnnotationsDict {
+  boolean readOnlyHint;
+};
+
+dictionary ToolRegistrationParams {
+  required ToolFunction execute;
+  required DOMString name;
+  required DOMString description;
+  object inputSchema;
+  AnnotationsDict annotations;
+};
diff --git a/third_party/blink/renderer/modules/content_extraction/frame_metadata_observer_registry.cc b/third_party/blink/renderer/modules/content_extraction/frame_metadata_observer_registry.cc
index e12d5bf..521a01976 100644
--- a/third_party/blink/renderer/modules/content_extraction/frame_metadata_observer_registry.cc
+++ b/third_party/blink/renderer/modules/content_extraction/frame_metadata_observer_registry.cc
@@ -121,10 +121,7 @@
 }
 
 void FrameMetadataObserverRegistry::OnPaidContentMetadataChanged() {
-  PaidContent paid_content;
-  // TODO(gklassen): Add a variant of QueryPaidElements that just checks for
-  // presence and doesn't look for elements.
-  bool has_paid_content = paid_content.QueryPaidElements(*GetSupplementable());
+  bool has_paid_content = PaidContent::HasPaidContent(*GetSupplementable());
 
   // TODO(gklassen): Add a MuationObserver to monitor for changes during the
   // lifetime of the page.
diff --git a/third_party/blink/renderer/modules/content_extraction/paid_content.cc b/third_party/blink/renderer/modules/content_extraction/paid_content.cc
index 175ab03..bc8d1ff3 100644
--- a/third_party/blink/renderer/modules/content_extraction/paid_content.cc
+++ b/third_party/blink/renderer/modules/content_extraction/paid_content.cc
@@ -8,8 +8,8 @@
 #include "third_party/blink/renderer/core/dom/document.h"
 #include "third_party/blink/renderer/core/dom/element.h"
 #include "third_party/blink/renderer/core/dom/static_node_list.h"
-#include "third_party/blink/renderer/core/html/html_meta_element.h"
 #include "third_party/blink/renderer/core/html/html_head_element.h"
+#include "third_party/blink/renderer/core/html/html_meta_element.h"
 #include "third_party/blink/renderer/core/html/html_script_element.h"
 #include "third_party/blink/renderer/platform/json/json_parser.h"
 #include "third_party/blink/renderer/platform/json/json_values.h"
@@ -20,10 +20,10 @@
 
 const char kIsAccessibleForFree[] = "isAccessibleForFree";
 
-bool ObjectValuePresentAndEquals(const JSONObject* object,
+bool ObjectValuePresentAndEquals(const JSONObject& object,
                                  const String& key,
                                  const String& value) {
-  JSONValue* json_value = object->Get(key);
+  JSONValue* json_value = object.Get(key);
   if (!json_value) {
     return false;
   }
@@ -35,10 +35,10 @@
   return str_val == value;
 }
 
-bool ObjectValuePresentAndEndsWith(const JSONObject* object,
+bool ObjectValuePresentAndEndsWith(const JSONObject& object,
                                    const String& key,
                                    const String& value) {
-  JSONValue* json_value = object->Get(key);
+  JSONValue* json_value = object.Get(key);
   if (!json_value) {
     return false;
   }
@@ -50,8 +50,8 @@
   return str_val.EndsWith(value);
 }
 
-bool ObjectValuePresentAndFalse(const JSONObject* object, const String& key) {
-  JSONValue* json_value = object->Get(key);
+bool ObjectValuePresentAndFalse(const JSONObject& object, const String& key) {
+  JSONValue* json_value = object.Get(key);
   if (!json_value) {
     return false;
   }
@@ -93,6 +93,55 @@
   return false;
 }
 
+// Check if the script element is ld+json and has paid content.  Returns the
+// script object if paid content is found, and nullptr otherwise.
+std::unique_ptr<JSONObject> ScriptHasPaidContent(HTMLScriptElement& script_element) {
+  ScriptElementBase& script_element_base =
+      static_cast<ScriptElementBase&>(script_element);
+  if (script_element_base.TypeAttributeValue() != "application/ld+json") {
+    return nullptr;
+  }
+  std::unique_ptr<JSONValue> json_value = ParseJSON(script_element.textContent());
+  if (!json_value || json_value->GetType() != JSONValue::kTypeObject) {
+    // JSON parsing failed or it's not an object.
+    return nullptr;
+  }
+  // We know it's an object, so we can safely cast and transfer ownership.
+  std::unique_ptr<JSONObject> script_obj =
+      std::unique_ptr<JSONObject>(static_cast<JSONObject*>(json_value.release()));
+
+  // check for "@context":"https://schema.org" (or "http://schema.org")
+  if (!ObjectValuePresentAndEndsWith(*script_obj, "@context", "//schema.org")) {
+    return nullptr;
+  }
+
+  // If we decided to filter for "@type" that should be done here.
+  // Supported types are
+  // Article, NewsArticle, Blog, Comment, Course, HowTo, Message, Review,
+  // and WebPage. Multiple types are supported.
+
+  // check for isAccessibleForFree=false
+  if (!ObjectValuePresentAndFalse(*script_obj, kIsAccessibleForFree)) {
+    return nullptr;
+  };
+  return script_obj;
+}
+
+bool PaidContent::HasPaidContent(Document& document) {
+  // check each ld+json script child of the head element
+  const HTMLHeadElement* head = document.head();
+  if (head) {
+    for (HTMLScriptElement& script_element :
+         Traversal<HTMLScriptElement>::ChildrenOf(*head)) {
+      if (ScriptHasPaidContent(script_element)) {
+        return true;
+      }
+    }
+  }
+
+  return false;
+}
+
 bool PaidContent::QueryPaidElements(Document& document) {
   bool paid_content_present = false;
 
@@ -103,36 +152,10 @@
   }
   for (HTMLScriptElement& script_element :
        Traversal<HTMLScriptElement>::ChildrenOf(*head)) {
-    ScriptElementBase& script_element_base =
-        static_cast<ScriptElementBase&>(script_element);
-    if (script_element_base.TypeAttributeValue() != "application/ld+json") {
-      continue;
-    }
-    auto json_value = ParseJSON(script_element.textContent());
-    if (!json_value.get() || json_value->GetType() != JSONValue::kTypeObject) {
-      // JSON parsing failed.
-      continue;
-    }
-    JSONObject* script_obj = JSONObject::Cast(json_value.get());
+    std::unique_ptr<JSONObject> script_obj = ScriptHasPaidContent(script_element);
     if (!script_obj) {
       continue;
     }
-
-    // check for "@context":"https://schema.org" (or "http://schema.org")
-    if (!ObjectValuePresentAndEndsWith(script_obj, "@context",
-                                       "//schema.org")) {
-      continue;
-    }
-
-    // If we decided to filter for "@type" that should be done here.
-    // Supported types are
-    // Article, NewsArticle, Blog, Comment, Course, HowTo, Message, Review,
-    // and WebPage. Multiple types are supported.
-
-    // check for isAccessibleForFree=false
-    if (!ObjectValuePresentAndFalse(script_obj, kIsAccessibleForFree)) {
-      continue;
-    }
     paid_content_present = true;
 
     bool has_part_found = false;
@@ -169,8 +192,8 @@
 
 bool PaidContent::AppendHasPartElements(Document& document,
                                         JSONObject& hasPart_obj) {
-  if (ObjectValuePresentAndEquals(&hasPart_obj, "@type", "WebPageElement") &&
-      ObjectValuePresentAndFalse(&hasPart_obj, kIsAccessibleForFree)) {
+  if (ObjectValuePresentAndEquals(hasPart_obj, "@type", "WebPageElement") &&
+      ObjectValuePresentAndFalse(hasPart_obj, kIsAccessibleForFree)) {
     JSONValue* selector_val = hasPart_obj.Get("cssSelector");
     if (selector_val && selector_val->GetType() == JSONValue::kTypeString) {
       String selector;
diff --git a/third_party/blink/renderer/modules/content_extraction/paid_content.h b/third_party/blink/renderer/modules/content_extraction/paid_content.h
index 960685d..be8155a7 100644
--- a/third_party/blink/renderer/modules/content_extraction/paid_content.h
+++ b/third_party/blink/renderer/modules/content_extraction/paid_content.h
@@ -18,6 +18,10 @@
   STACK_ALLOCATED();
 
  public:
+  // Returns true if the document has paid content (marked as
+  // isAccessibleForFree=false)
+  static bool HasPaidContent(Document& document);
+
   // Queries the document for elements marked as isAccessibleForFree=false.
   bool QueryPaidElements(Document& document);
 
diff --git a/third_party/blink/renderer/modules/mediastream/media_stream_audio_processing_layout.cc b/third_party/blink/renderer/modules/mediastream/media_stream_audio_processing_layout.cc
index f7e8fdc2..01a22b0 100644
--- a/third_party/blink/renderer/modules/mediastream/media_stream_audio_processing_layout.cc
+++ b/third_party/blink/renderer/modules/mediastream/media_stream_audio_processing_layout.cc
@@ -211,8 +211,8 @@
     int platform_effects) {
   AudioProcessingProperties properties_aec_only(
       AudioProcessingProperties::Disabled());
-  properties_aec_only.echo_cancellation_type =
-      properties.echo_cancellation_type;
+  properties_aec_only.echo_cancellation_mode =
+      properties.echo_cancellation_mode;
 
   EchoCanceller echo_canceller =
       EchoCanceller::From(properties_aec_only, platform_effects);
@@ -238,8 +238,8 @@
   AudioProcessingProperties properties(AudioProcessingProperties::Disabled());
   if (platform_aec) {
     CHECK(available_platform_effects & media::AudioParameters::ECHO_CANCELLER);
-    properties.echo_cancellation_type = AudioProcessingProperties::
-        EchoCancellationType::kEchoCancellationSystem;
+    // TODO(crbug.com/428856440): Use EchoCancellationMode::kAll when supported.
+    properties.echo_cancellation_mode = EchoCancellationMode::kBrowserDecides;
   }
 
   return MakeForUnprocessedLocalSource(properties, available_platform_effects);
diff --git a/third_party/blink/renderer/modules/mediastream/media_stream_audio_processing_layout_test.cc b/third_party/blink/renderer/modules/mediastream/media_stream_audio_processing_layout_test.cc
index 63ce3f14..96834b8 100644
--- a/third_party/blink/renderer/modules/mediastream/media_stream_audio_processing_layout_test.cc
+++ b/third_party/blink/renderer/modules/mediastream/media_stream_audio_processing_layout_test.cc
@@ -12,7 +12,6 @@
 
 namespace blink {
 
-using EchoCancellationType = AudioProcessingProperties::EchoCancellationType;
 using VoiceIsolationType = AudioProcessingProperties::VoiceIsolationType;
 using PlatformEffectsMask = media::AudioParameters::PlatformEffectsMask;
 
@@ -56,7 +55,7 @@
 TEST(AudioProcessingPropertiesToAudioProcessingSettingsTest,
      AllBrowserPropertiesEnabled) {
   const AudioProcessingProperties properties{
-      .echo_cancellation_type = EchoCancellationType::kEchoCancellationAec3,
+      .echo_cancellation_mode = EchoCancellationMode::kBrowserDecides,
       .auto_gain_control = true,
       .noise_suppression = true};
   const media::AudioProcessingSettings settings =
@@ -71,8 +70,8 @@
 
 TEST(AudioProcessingPropertiesToAudioProcessingSettingsTest,
      SystemAecDisablesBrowserAec) {
-  AudioProcessingProperties properties{
-      .echo_cancellation_type = EchoCancellationType::kEchoCancellationSystem};
+  AudioProcessingProperties properties{.echo_cancellation_mode =
+                                           EchoCancellationMode::kAll};
   media::AudioProcessingSettings settings =
       MediaStreamAudioProcessingLayout::ComputeWebrtcProcessingSettingsForTests(
           properties,
@@ -109,11 +108,11 @@
       media::kEnforceSystemEchoCancellation, {{"allow_ns_in_tandem", "true"}});
 
   constexpr AudioProcessingProperties properties{
-      .echo_cancellation_type = EchoCancellationType::kEchoCancellationSystem};
+      .echo_cancellation_mode = EchoCancellationMode::kAll};
   media::AudioProcessingSettings settings_without_system_ns =
       MediaStreamAudioProcessingLayout::ComputeWebrtcProcessingSettingsForTests(
           properties,
-          /*enabled_platform_effects=*/0,
+          /*enabled_platform_effects=*/PlatformEffectsMask::ECHO_CANCELLER,
           /*multichannel_processing=*/true);
 
   EXPECT_TRUE(settings_without_system_ns.noise_suppression);
@@ -121,13 +120,17 @@
   media::AudioProcessingSettings settings_with_system_ns =
       MediaStreamAudioProcessingLayout::ComputeWebrtcProcessingSettingsForTests(
           properties,
-          /*enabled_platform_effects=*/PlatformEffectsMask::NOISE_SUPPRESSION,
+          /*enabled_platform_effects=*/PlatformEffectsMask::NOISE_SUPPRESSION |
+              PlatformEffectsMask::ECHO_CANCELLER,
           /*multichannel_processing=*/true);
 
   EXPECT_TRUE(settings_with_system_ns.noise_suppression);
 
   MediaStreamAudioProcessingLayout processing_layout(
-      properties, PlatformEffectsMask::NOISE_SUPPRESSION, /*channels=*/1);
+      properties,
+      PlatformEffectsMask::NOISE_SUPPRESSION |
+          PlatformEffectsMask::ECHO_CANCELLER,
+      /*channels=*/1);
   EXPECT_TRUE(processing_layout.NoiseSuppressionInTandem());
 }
 #endif
@@ -161,11 +164,11 @@
       media::kEnforceSystemEchoCancellation, {{"allow_agc_in_tandem", "true"}});
 
   constexpr AudioProcessingProperties properties{
-      .echo_cancellation_type = EchoCancellationType::kEchoCancellationSystem};
+      .echo_cancellation_mode = EchoCancellationMode::kAll};
   media::AudioProcessingSettings settings_without_system_agc =
       MediaStreamAudioProcessingLayout::ComputeWebrtcProcessingSettingsForTests(
           properties,
-          /*enabled_platform_effects=*/0,
+          /*enabled_platform_effects=*/PlatformEffectsMask::ECHO_CANCELLER,
           /*multichannel_processing=*/true);
 
   EXPECT_TRUE(settings_without_system_agc.automatic_gain_control);
@@ -174,21 +177,25 @@
       MediaStreamAudioProcessingLayout::ComputeWebrtcProcessingSettingsForTests(
           properties,
           /*enabled_platform_effects=*/
-          PlatformEffectsMask::AUTOMATIC_GAIN_CONTROL,
+          PlatformEffectsMask::AUTOMATIC_GAIN_CONTROL |
+              PlatformEffectsMask::ECHO_CANCELLER,
           /*multichannel_processing=*/true);
 
   EXPECT_TRUE(settings_with_system_agc.automatic_gain_control);
 
   MediaStreamAudioProcessingLayout processing_layout(
-      properties, PlatformEffectsMask::AUTOMATIC_GAIN_CONTROL, /*channels=*/1);
+      properties,
+      PlatformEffectsMask::AUTOMATIC_GAIN_CONTROL |
+          PlatformEffectsMask::ECHO_CANCELLER,
+      /*channels=*/1);
   EXPECT_TRUE(processing_layout.AutomaticGainControlInTandem());
 }
 #endif
 
 TEST(AudioProcessingPropertiesTest, VerifyDefaultProcessingState) {
   constexpr AudioProcessingProperties kDefaultProperties;
-  EXPECT_EQ(kDefaultProperties.echo_cancellation_type,
-            EchoCancellationType::kEchoCancellationAec3);
+  EXPECT_EQ(kDefaultProperties.echo_cancellation_mode,
+            EchoCancellationMode::kBrowserDecides);
   EXPECT_TRUE(kDefaultProperties.auto_gain_control);
   EXPECT_TRUE(kDefaultProperties.noise_suppression);
   EXPECT_EQ(kDefaultProperties.voice_isolation,
@@ -197,14 +204,12 @@
 
 class MediaStreamAudioProcessingLayoutTest
     : public testing::TestWithParam<
-          testing::tuple<AudioProcessingProperties::EchoCancellationType,
-                         bool,
-                         bool>> {};
+          testing::tuple<EchoCancellationMode, bool, bool>> {};
 
 TEST_P(MediaStreamAudioProcessingLayoutTest,
        PlatformAecNsAgcCorrectIfAvailale) {
   AudioProcessingProperties properties;
-  properties.echo_cancellation_type = std::get<0>(GetParam());
+  properties.echo_cancellation_mode = std::get<0>(GetParam());
   properties.noise_suppression = std::get<1>(GetParam());
   properties.auto_gain_control = std::get<2>(GetParam());
 
@@ -264,12 +269,10 @@
     All,
     MediaStreamAudioProcessingLayoutTest,
     ::testing::Combine(
-        ::testing::ValuesIn({AudioProcessingProperties::EchoCancellationType::
-                                 kEchoCancellationDisabled,
-                             AudioProcessingProperties::EchoCancellationType::
-                                 kEchoCancellationSystem,
-                             AudioProcessingProperties::EchoCancellationType::
-                                 kEchoCancellationAec3}),
+        ::testing::ValuesIn({EchoCancellationMode::kDisabled,
+                             EchoCancellationMode::kRemoteOnly,
+                             EchoCancellationMode::kAll,
+                             EchoCancellationMode::kBrowserDecides}),
         // ACG and NS on/off.
         ::testing::Bool(),
         ::testing::Bool()));
@@ -291,11 +294,9 @@
   }
 
   AudioProcessingProperties properties;
-  properties.echo_cancellation_type =
-      aec_enabled ? AudioProcessingProperties::EchoCancellationType::
-                        kEchoCancellationAec3
-                  : AudioProcessingProperties::EchoCancellationType::
-                        kEchoCancellationDisabled;
+  properties.echo_cancellation_mode =
+      aec_enabled ? EchoCancellationMode::kBrowserDecides
+                  : EchoCancellationMode::kDisabled;
 
   MediaStreamAudioProcessingLayout processing_layout(
       properties, /*available_platform_effects=*/0, /*channels=*/1);
diff --git a/third_party/blink/renderer/modules/mediastream/media_stream_audio_processor_test.cc b/third_party/blink/renderer/modules/mediastream/media_stream_audio_processor_test.cc
index 6d2b861..3b71cbd 100644
--- a/third_party/blink/renderer/modules/mediastream/media_stream_audio_processor_test.cc
+++ b/third_party/blink/renderer/modules/mediastream/media_stream_audio_processor_test.cc
@@ -705,7 +705,8 @@
 
 namespace {
 scoped_refptr<MediaStreamAudioProcessor> CreateAudioProcessorWithProperties(
-    AudioProcessingProperties properties) {
+    AudioProcessingProperties properties,
+    int enabled_platform_effects = 0) {
   MockProcessedCaptureCallback mock_capture_callback;
   scoped_refptr<WebRtcAudioDeviceImpl> webrtc_audio_device(
       new webrtc::RefCountedObject<WebRtcAudioDeviceImpl>());
@@ -717,8 +718,7 @@
           mock_capture_callback.Get(),
           MediaStreamAudioProcessingLayout::
               ComputeWebrtcProcessingSettingsForTests(
-                  properties,
-                  /*enabled_platform_effects=*/0,
+                  properties, enabled_platform_effects,
                   /*multichannel_processing=*/true),
           params, webrtc_audio_device));
   return audio_processor;
@@ -756,8 +756,7 @@
      FalseWhenOnlyHardwareEffectsAreUsed) {
   test::TaskEnvironment task_environment_;
   AudioProcessingProperties properties(AudioProcessingProperties::Disabled());
-  properties.echo_cancellation_type =
-      AudioProcessingProperties::EchoCancellationType::kEchoCancellationSystem;
+  properties.echo_cancellation_mode = EchoCancellationMode::kAll;
   MediaStreamAudioProcessingLayout processing_layout(
       properties,
       /*available_platform_effects=*/PlatformEffectsMask::ECHO_CANCELLER,
@@ -765,7 +764,9 @@
   EXPECT_FALSE(processing_layout.NeedWebrtcAudioProcessing());
 
   scoped_refptr<MediaStreamAudioProcessor> audio_processor =
-      CreateAudioProcessorWithProperties(properties);
+      CreateAudioProcessorWithProperties(
+          properties,
+          /*enabled_platform_effects=*/PlatformEffectsMask::ECHO_CANCELLER);
   EXPECT_FALSE(audio_processor->has_webrtc_audio_processing());
 }
 
@@ -782,8 +783,7 @@
      MAYBE_TrueWhenSoftwareEchoCancellationIsEnabled) {
   test::TaskEnvironment task_environment_;
   AudioProcessingProperties properties(AudioProcessingProperties::Disabled());
-  properties.echo_cancellation_type =
-      AudioProcessingProperties::EchoCancellationType::kEchoCancellationAec3;
+  properties.echo_cancellation_mode = EchoCancellationMode::kRemoteOnly;
   MediaStreamAudioProcessingLayout processing_layout(
       properties,
       /*available_platform_effects=*/PlatformEffectsMask::ECHO_CANCELLER,
diff --git a/third_party/blink/renderer/modules/mediastream/media_stream_constraints_util_audio.cc b/third_party/blink/renderer/modules/mediastream/media_stream_constraints_util_audio.cc
index 32742a3e..e61de5f 100644
--- a/third_party/blink/renderer/modules/mediastream/media_stream_constraints_util_audio.cc
+++ b/third_party/blink/renderer/modules/mediastream/media_stream_constraints_util_audio.cc
@@ -10,7 +10,7 @@
 #include <tuple>
 #include <utility>
 
-#include "base/notreached.h"
+#include "base/containers/contains.h"
 #include "base/strings/string_number_conversions.h"
 #include "build/build_config.h"
 #include "media/audio/audio_features.h"
@@ -35,7 +35,6 @@
 using blink::AudioProcessingProperties;
 using ConstraintSet = MediaTrackConstraintSetPlatform;
 using BooleanConstraint = blink::BooleanConstraint;
-using EchoCancellationType = AudioProcessingProperties::EchoCancellationType;
 using VoiceIsolationType = AudioProcessingProperties::VoiceIsolationType;
 using ProcessingType = AudioCaptureSettings::ProcessingType;
 using StringConstraint = blink::StringConstraint;
@@ -46,8 +45,6 @@
 namespace {
 using BoolSet = blink::media_constraints::DiscreteSet<bool>;
 using DoubleRangeSet = blink::media_constraints::NumericRangeSet<double>;
-using EchoCancellationTypeSet =
-    blink::media_constraints::DiscreteSet<EchoCancellationType>;
 using VoiceIsolationTypeSet =
     blink::media_constraints::DiscreteSet<VoiceIsolationType>;
 using IntRangeSet = blink::media_constraints::NumericRangeSet<int>;
@@ -74,8 +71,9 @@
  public:
   enum class EcModeScore : int {
     kDisabled = 1,
-    kSystem = 2,
-    kAec3 = 3,
+    kRemoteOnly = 2,
+    kAll = 3,
+    kBrowserDecides = 4,
   };
 
   explicit Score(double fitness,
@@ -108,7 +106,7 @@
     return *this;
   }
 
-  void set_ec_type_score(EcModeScore ec_mode_score) {
+  void set_ec_mode_score(EcModeScore ec_mode_score) {
     std::get<2>(score) = ec_mode_score;
   }
 
@@ -417,23 +415,21 @@
   return ec_mode != EchoCancellationMode::kDisabled;
 }
 
-// Container to manage the properties related to echo cancellation:
-// echoCancellation and echoCancellationType.
+// Container to manage the properties related to echo cancellation.
 class EchoCancellationContainer {
  public:
   // Default constructor intended to temporarily create an empty object.
   EchoCancellationContainer()
-      : ec_type_allowed_values_(EchoCancellationTypeSet::EmptySet()),
+      : ec_allowed_values_(EchoCancellationModeSet::EmptySet()),
         device_parameters_(media::AudioParameters::UnavailableDeviceParams()),
         is_device_capture_(true) {}
 
-  EchoCancellationContainer(Vector<EchoCancellationType> allowed_values,
+  EchoCancellationContainer(Vector<EchoCancellationMode> allowed_values,
                             std::optional<SourceInfo> source_info,
                             bool is_device_capture,
                             media::AudioParameters device_parameters,
                             bool is_reconfiguration_allowed)
-      : ec_type_allowed_values_(
-            EchoCancellationTypeSet(std::move(allowed_values))),
+      : ec_allowed_values_(EchoCancellationModeSet(std::move(allowed_values))),
         device_parameters_(device_parameters),
         is_device_capture_(is_device_capture) {
     if (!source_info) {
@@ -455,21 +451,18 @@
         // Allowing it when the system echo cancellation is enforced via flag,
         // for evaluation purposes.
         media::IsSystemEchoCancellationEnforced() ||
-        source_info->properties().echo_cancellation_type !=
-            EchoCancellationType::kEchoCancellationSystem;
+        (source_info->properties().echo_cancellation_mode !=
+             EchoCancellationMode::kDisabled &&
+         !EchoCanceller::From(source_info->properties(),
+                              device_parameters.effects())
+              .IsPlatformProvided());
 #endif
     if (is_reconfiguration_allowed && is_aec_reconfiguration_supported) {
       return;
     }
 
-    ec_type_allowed_values_ = EchoCancellationTypeSet(
-        {source_info->properties().echo_cancellation_type});
-    EchoCancellationMode ec_value =
-        source_info->properties().echo_cancellation_type ==
-                EchoCancellationType::kEchoCancellationDisabled
-            ? EchoCancellationMode::kDisabled
-            : EchoCancellationMode::kBrowserDecides;
-    ec_allowed_values_ = EchoCancellationModeSet({ec_value});
+    ec_allowed_values_ = EchoCancellationModeSet(
+        {source_info->properties().echo_cancellation_mode});
   }
 
   const char* ApplyConstraintSet(const ConstraintSet& constraint_set) {
@@ -479,28 +472,20 @@
 
     // Apply echoCancellation constraint.
     ec_allowed_values_ = ec_allowed_values_.Intersection(ec_set);
-    if (ec_allowed_values_.IsEmpty())
-      return constraint_set.echo_cancellation.GetName();
-    // Translate the boolean values into EC modes.
-    ec_type_allowed_values_ = ec_type_allowed_values_.Intersection(
-        ToEchoCancellationTypes(ec_allowed_values_));
-
-    // Finally, if this container is empty, fail due to contradiction of the
-    // resulting allowed values for ec and/or ec_type.
     return IsEmpty() ? constraint_set.echo_cancellation.GetName() : nullptr;
   }
 
-  std::tuple<Score, EchoCancellationType> SelectSettingsAndScore(
+  std::tuple<Score, EchoCancellationMode> SelectSettingsAndScore(
       const ConstraintSet& constraint_set) const {
-    EchoCancellationType selected_ec_type = SelectBestEcMode(constraint_set);
+    EchoCancellationMode selected_ec_mode = SelectBestEcMode(constraint_set);
     double fitness =
-        Fitness(selected_ec_type, constraint_set.echo_cancellation);
+        Fitness(selected_ec_mode, constraint_set.echo_cancellation);
     Score score(fitness);
-    score.set_ec_type_score(GetEcTypeScore(selected_ec_type));
-    return std::make_tuple(score, selected_ec_type);
+    score.set_ec_mode_score(GetEcModeScore(selected_ec_mode));
+    return std::make_tuple(score, selected_ec_mode);
   }
 
-  bool IsEmpty() const { return ec_type_allowed_values_.IsEmpty(); }
+  bool IsEmpty() const { return ec_allowed_values_.IsEmpty(); }
 
   // Audio-processing properties are disabled by default for content capture,
   // or if the |echo_cancellation| constraint is false.
@@ -518,8 +503,6 @@
 
   bool GetDefaultValueForAudioProperties(
       const BooleanOrStringConstraint& ec_constraint) const {
-    DCHECK(!ec_type_allowed_values_.is_universal());
-
     std::optional<EchoCancellationMode> ideal_mode =
         IdealEchoCancellationModeFromConstraint(ec_constraint);
     if (ideal_mode && ec_allowed_values_.Contains(*ideal_mode)) {
@@ -534,83 +517,59 @@
   }
 
  private:
-  static Score::EcModeScore GetEcTypeScore(EchoCancellationType ec_type) {
+  static Score::EcModeScore GetEcModeScore(EchoCancellationMode ec_type) {
     switch (ec_type) {
-      case EchoCancellationType::kEchoCancellationDisabled:
+      case EchoCancellationMode::kDisabled:
         return Score::EcModeScore::kDisabled;
-      case EchoCancellationType::kEchoCancellationSystem:
-        return Score::EcModeScore::kSystem;
-      case EchoCancellationType::kEchoCancellationAec3:
-        return Score::EcModeScore::kAec3;
+      case EchoCancellationMode::kBrowserDecides:
+        return Score::EcModeScore::kBrowserDecides;
+      case EchoCancellationMode::kAll:
+        return Score::EcModeScore::kAll;
+      case EchoCancellationMode::kRemoteOnly:
+        return Score::EcModeScore::kRemoteOnly;
     }
   }
 
-  static EchoCancellationTypeSet ToEchoCancellationTypes(
-      const EchoCancellationModeSet& ec_set) {
-    Vector<EchoCancellationType> types;
-
-    if (ec_set.Contains(EchoCancellationMode::kDisabled)) {
-      types.push_back(EchoCancellationType::kEchoCancellationDisabled);
-    }
-
-    if (ec_set.Contains(EchoCancellationMode::kBrowserDecides)) {
-      types.push_back(EchoCancellationType::kEchoCancellationAec3);
-      types.push_back(EchoCancellationType::kEchoCancellationSystem);
-    }
-
-    return EchoCancellationTypeSet(std::move(types));
-  }
-
-  EchoCancellationType SelectBestEcMode(
+  EchoCancellationMode SelectBestEcMode(
       const ConstraintSet& constraint_set) const {
     DCHECK(!IsEmpty());
-    DCHECK(!ec_type_allowed_values_.is_universal());
 
     // Try to use an ideal candidate, if supplied.
-    bool is_ec_preferred =
-        ShouldUseEchoCancellation(constraint_set.echo_cancellation);
-
-    if (!is_ec_preferred &&
-        ec_type_allowed_values_.Contains(
-            EchoCancellationType::kEchoCancellationDisabled)) {
-      return EchoCancellationType::kEchoCancellationDisabled;
+    std::optional<EchoCancellationMode> ideal_mode =
+        IdealEchoCancellationModeFromConstraint(
+            constraint_set.echo_cancellation);
+    if (ideal_mode && ec_allowed_values_.Contains(*ideal_mode)) {
+      return *ideal_mode;
     }
 
     // If no ideal could be selected and the set contains only one value, pick
     // that one.
-    if (ec_type_allowed_values_.elements().size() == 1) {
-      return ec_type_allowed_values_.FirstElement();
+    if (ec_allowed_values_.elements().size() == 1) {
+      return ec_allowed_values_.FirstElement();
     }
 
-    // If no type has been selected, choose system if the device has the
-    // ECHO_CANCELLER flag set. Never automatically enable an experimental
-    // system echo canceller.
-    if (device_parameters_.IsValid() &&
-        ec_type_allowed_values_.Contains(
-            EchoCancellationType::kEchoCancellationSystem) &&
-        (device_parameters_.effects() &
-         media::AudioParameters::ECHO_CANCELLER)) {
-      return EchoCancellationType::kEchoCancellationSystem;
+    // For device (microphone) capture, kBrowserDecides is the preferred option.
+    if (is_device_capture_) {
+      if (ec_allowed_values_.Contains(EchoCancellationMode::kBrowserDecides)) {
+        return EchoCancellationMode::kBrowserDecides;
+      }
+      CHECK(ec_allowed_values_.Contains(EchoCancellationMode::kDisabled));
+      return EchoCancellationMode::kDisabled;
     }
 
-    // At this point we have at least two elements, hence the only two options
-    // from which to select are either AEC3 or System, where AEC3 has higher
-    // priority.
-    if (ec_type_allowed_values_.Contains(
-            EchoCancellationType::kEchoCancellationAec3)) {
-      return EchoCancellationType::kEchoCancellationAec3;
+    // For content (screen) capture, kDisabled is the preferred option.
+    if (ec_allowed_values_.Contains(EchoCancellationMode::kDisabled)) {
+      return EchoCancellationMode::kDisabled;
     }
-
-    DCHECK(ec_type_allowed_values_.Contains(
-        EchoCancellationType::kEchoCancellationDisabled));
-    return EchoCancellationType::kEchoCancellationDisabled;
+    CHECK(ec_allowed_values_.Contains(EchoCancellationMode::kBrowserDecides));
+    return EchoCancellationMode::kBrowserDecides;
   }
 
   // This function computes the fitness score of the given |ec_mode|. The
   // fitness is determined by the ideal values of |ec_constraint|. If |ec_mode|
   // satisfies the constraint, the fitness score results in a value of 1, and 0
   // otherwise. If no ideal value is specified, the fitness is 1.
-  double Fitness(const EchoCancellationType& ec_type,
+  double Fitness(EchoCancellationMode ec_mode,
                  const BooleanOrStringConstraint& ec_constraint) const {
     std::optional<EchoCancellationMode> ideal_mode =
         IdealEchoCancellationModeFromConstraint(ec_constraint);
@@ -620,55 +579,17 @@
 
     switch (*ideal_mode) {
       case EchoCancellationMode::kBrowserDecides:
-        return ec_type != EchoCancellationType::kEchoCancellationDisabled;
+        return ec_mode != EchoCancellationMode::kDisabled;
       case EchoCancellationMode::kDisabled:
-        return ec_type == EchoCancellationType::kEchoCancellationDisabled;
+        return ec_mode == EchoCancellationMode::kDisabled;
       case EchoCancellationMode::kAll:
       case EchoCancellationMode::kRemoteOnly:
-        NOTREACHED();
+        // TODO(crbug.com/428856440): Support these values.
+        return 0;
     }
   }
 
-  bool EchoCancellationTypeContains(EchoCancellationMode ec_mode) const {
-    DCHECK(!ec_type_allowed_values_.is_universal());
-    switch (ec_mode) {
-      case EchoCancellationMode::kBrowserDecides:
-        return ec_type_allowed_values_.Contains(
-                   EchoCancellationType::kEchoCancellationAec3) ||
-               ec_type_allowed_values_.Contains(
-                   EchoCancellationType::kEchoCancellationSystem);
-      case EchoCancellationMode::kDisabled:
-        return ec_type_allowed_values_.Contains(
-            EchoCancellationType::kEchoCancellationDisabled);
-      case EchoCancellationMode::kAll:
-      case EchoCancellationMode::kRemoteOnly:
-        NOTREACHED();
-    }
-  }
-
-  bool ShouldUseEchoCancellation(
-      const BooleanOrStringConstraint& ec_constraint) const {
-    DCHECK(!ec_type_allowed_values_.is_universal());
-
-    std::optional<EchoCancellationMode> ideal_mode =
-        IdealEchoCancellationModeFromConstraint(ec_constraint);
-
-    if (ideal_mode && EchoCancellationTypeContains(*ideal_mode)) {
-      return IsEnabledEchoCancellationMode(*ideal_mode);
-    }
-
-    // Echo cancellation is enabled by default for device capture and disabled
-    // by default for content capture.
-    if (EchoCancellationTypeContains(EchoCancellationMode::kBrowserDecides) &&
-        EchoCancellationTypeContains(EchoCancellationMode::kDisabled)) {
-      return is_device_capture_;
-    }
-
-    return EchoCancellationTypeContains(EchoCancellationMode::kBrowserDecides);
-  }
-
   EchoCancellationModeSet ec_allowed_values_;
-  EchoCancellationTypeSet ec_type_allowed_values_;
   media::AudioParameters device_parameters_;
   bool is_device_capture_;
 };
@@ -713,7 +634,7 @@
 class VoiceIsolationContainer {
  public:
   // Default constructor intended to temporarily create an empty object.
-  VoiceIsolationContainer(BoolSet allowed_values = BoolSet())
+  explicit VoiceIsolationContainer(BoolSet allowed_values = BoolSet())
       : allowed_values_(std::move(allowed_values)) {}
 
   const char* ApplyConstraintSet(const ConstraintSet& constraint_set) {
@@ -789,12 +710,12 @@
       bool is_reconfiguration_allowed) {
     return ProcessingBasedContainer(
         ProcessingType::kApmProcessed,
-        {EchoCancellationType::kEchoCancellationAec3,
-         EchoCancellationType::kEchoCancellationDisabled},
-        BoolSet(),                               /* auto_gain_control_set */
-        BoolSet(),                               /* noise_suppression_set */
-        BoolSet(),                               /* voice_isolation_set */
-        IntRangeSet::FromValue(GetSampleSize()), /* sample_size_range */
+        {EchoCancellationMode::kBrowserDecides,
+         EchoCancellationMode::kDisabled},
+        BoolSet(),                                  /* auto_gain_control_set */
+        BoolSet(),                                  /* noise_suppression_set */
+        BoolSet(),                                  /* voice_isolation_set */
+        IntRangeSet::FromValue(GetSampleSize()),    /* sample_size_range */
         GetApmSupportedChannels(device_parameters), /* channels_set */
         IntRangeSet::FromValue(
             media::WebRtcAudioProcessingSampleRateHz()), /* sample_rate_range */
@@ -812,8 +733,7 @@
       const media::AudioParameters& device_parameters,
       bool is_reconfiguration_allowed) {
     return ProcessingBasedContainer(
-        ProcessingType::kNoApmProcessed,
-        {EchoCancellationType::kEchoCancellationDisabled},
+        ProcessingType::kNoApmProcessed, {EchoCancellationMode::kDisabled},
         BoolSet({false}),                        /* auto_gain_control_set */
         BoolSet({false}),                        /* noise_suppression_set */
         BoolSet(),                               /* voice_isolation_set */
@@ -835,8 +755,7 @@
       const media::AudioParameters& device_parameters,
       bool is_reconfiguration_allowed) {
     return ProcessingBasedContainer(
-        ProcessingType::kUnprocessed,
-        {EchoCancellationType::kEchoCancellationDisabled},
+        ProcessingType::kUnprocessed, {EchoCancellationMode::kDisabled},
         BoolSet({false}),                        /* auto_gain_control_set */
         BoolSet({false}),                        /* noise_suppression_set */
         BoolSet({false}),                        /* voice_isolation_set */
@@ -946,7 +865,7 @@
 
     AudioProcessingProperties properties;
     Score ec_score(0.0);
-    std::tie(ec_score, properties.echo_cancellation_type) =
+    std::tie(ec_score, properties.echo_cancellation_mode) =
         echo_cancellation_container_.SelectSettingsAndScore(constraint_set);
     score += ec_score;
 
@@ -993,11 +912,11 @@
   // Private constructor intended to instantiate different variants of this
   // class based on the initial values provided. The appropriate way to
   // instantiate this class is via the three factory methods provided.
-  // System echo cancellation should not be explicitly included in
-  // |echo_cancellation_type|. It is added automatically based on the value of
-  // |device_parameters|.
+  // TODO(crbug.com/428856440): Do not explicitly include
+  // EchoCancellationMode::kAll in `echo_cancellation_modes`. It should be
+  // added automatically based on the value of |device_parameters|.
   ProcessingBasedContainer(ProcessingType processing_type,
-                           Vector<EchoCancellationType> echo_cancellation_types,
+                           Vector<EchoCancellationMode> echo_cancellation_modes,
                            BoolSet auto_gain_control_set,
                            BoolSet noise_suppression_set,
                            BoolSet voice_isolation_set,
@@ -1015,13 +934,16 @@
         latency_container_(
             GetAllowedLatency(processing_type, device_parameters)) {
     // If the parameters indicate that system echo cancellation is available, we
-    // add such value in the allowed values for the EC type.
-    if (device_parameters.effects() & media::AudioParameters::ECHO_CANCELLER) {
-      echo_cancellation_types.push_back(
-          EchoCancellationType::kEchoCancellationSystem);
+    // add support in the allowed values for `echo_cancellation_modes`.
+    // TODO(crbug.com/428856440): Also add EchoCancellationMode::kAll
+    if ((device_parameters.effects() &
+         media::AudioParameters::ECHO_CANCELLER) &&
+        !base::Contains(echo_cancellation_modes,
+                        EchoCancellationMode::kBrowserDecides)) {
+      echo_cancellation_modes.push_back(EchoCancellationMode::kBrowserDecides);
     }
     echo_cancellation_container_ = EchoCancellationContainer(
-        std::move(echo_cancellation_types), source_info, is_device_capture,
+        std::move(echo_cancellation_modes), source_info, is_device_capture,
         device_parameters, is_reconfiguration_allowed);
 
     auto_gain_control_container_ =
diff --git a/third_party/blink/renderer/modules/mediastream/media_stream_constraints_util_audio_test.cc b/third_party/blink/renderer/modules/mediastream/media_stream_constraints_util_audio_test.cc
index 39bc7af..326b6a7 100644
--- a/third_party/blink/renderer/modules/mediastream/media_stream_constraints_util_audio_test.cc
+++ b/third_party/blink/renderer/modules/mediastream/media_stream_constraints_util_audio_test.cc
@@ -42,7 +42,6 @@
 
 using blink::AudioCaptureSettings;
 using blink::AudioProcessingProperties;
-using EchoCancellationType = AudioProcessingProperties::EchoCancellationType;
 using ProcessingType = AudioCaptureSettings::ProcessingType;
 
 namespace {
@@ -253,14 +252,14 @@
     }
   }
 
-  void CheckEchoCancellationTypeDefault(const AudioCaptureSettings& result) {
+  void CheckEchoCancellationModeDefault(const AudioCaptureSettings& result) {
     const auto& properties = result.audio_processing_properties();
     if (IsDeviceCapture()) {
-      EXPECT_EQ(properties.echo_cancellation_type,
-                EchoCancellationType::kEchoCancellationAec3);
+      EXPECT_EQ(properties.echo_cancellation_mode,
+                EchoCancellationMode::kBrowserDecides);
     } else {
-      EXPECT_EQ(properties.echo_cancellation_type,
-                EchoCancellationType::kEchoCancellationDisabled);
+      EXPECT_EQ(properties.echo_cancellation_mode,
+                EchoCancellationMode::kDisabled);
     }
   }
 
@@ -279,8 +278,8 @@
 
     // Finally, if the chosen echo cancellation type is either AEC3 or AEC2, the
     // only possible processing type to expect is kWebRtcProcessed.
-    if (properties.echo_cancellation_type ==
-        EchoCancellationType::kEchoCancellationAec3) {
+    if (properties.echo_cancellation_mode ==
+        EchoCancellationMode::kBrowserDecides) {
       expected_type = ProcessingType::kApmProcessed;
     }
     EXPECT_EQ(result.processing_type(), expected_type);
@@ -304,17 +303,16 @@
       const AudioCaptureSettings& result) {
     CheckProcessingType(result);
     CheckBoolDefaults(exclude_main_settings, exclude_audio_properties, result);
-    CheckEchoCancellationTypeDefault(result);
+    CheckEchoCancellationModeDefault(result);
     CheckDeviceDefaults(result);
   }
 
-  void CheckAudioProcessingPropertiesForIdealEchoCancellationType(
+  void CheckAudioProcessingPropertiesForIdealEchoCancellationMode(
       const AudioCaptureSettings& result) {
     const AudioProcessingProperties& properties =
         result.audio_processing_properties();
 
-    EXPECT_EQ(EchoCancellationType::kEchoCancellationSystem,
-              properties.echo_cancellation_type);
+    EXPECT_EQ(EchoCancellationMode::kAll, properties.echo_cancellation_mode);
     EXPECT_TRUE(properties.auto_gain_control);
     EXPECT_TRUE(properties.noise_suppression);
 
@@ -325,20 +323,6 @@
     CheckDevice(*system_echo_canceller_device_, result);
   }
 
-  EchoCancellationType GetEchoCancellationTypeFromConstraintString(
-      const blink::WebString& constraint_string) {
-    if (constraint_string == kEchoCancellationTypeValues[0])
-      return EchoCancellationType::kEchoCancellationAec3;
-    if (constraint_string == kEchoCancellationTypeValues[1])
-      return EchoCancellationType::kEchoCancellationAec3;
-    if (constraint_string == kEchoCancellationTypeValues[2])
-      return EchoCancellationType::kEchoCancellationSystem;
-
-    ADD_FAILURE() << "Invalid echo cancellation type constraint: "
-                  << constraint_string.Ascii();
-    return EchoCancellationType::kEchoCancellationDisabled;
-  }
-
   void CheckLatencyConstraint(const AudioDeviceCaptureCapability* device,
                               double min_latency,
                               double max_latency) {
@@ -401,13 +385,6 @@
   std::unique_ptr<ProcessedLocalAudioSource> system_echo_canceller_source_;
   const WTF::Vector<media::Point> kMicPositions = {{8, 8, 8}, {4, 4, 4}};
 
-  // TODO(grunell): Store these as separate constants and compare against those
-  // in tests, instead of indexing the vector.
-  const WTF::Vector<blink::WebString> kEchoCancellationTypeValues = {
-      blink::WebString::FromASCII("browser"),
-      blink::WebString::FromASCII("aec3"),
-      blink::WebString::FromASCII("system")};
-
  private:
   ScopedTestingPlatformSupport<IOTaskRunnerTestingPlatformSupport> platform_;
 };
@@ -805,8 +782,8 @@
   EXPECT_EQ(result.device_id(), "default_device");
   // By default, use the default deevice with echo cancellation enabled
   // and 1 channel,
-  EXPECT_EQ(result.audio_processing_properties().echo_cancellation_type,
-            EchoCancellationType::kEchoCancellationAec3);
+  EXPECT_EQ(result.audio_processing_properties().echo_cancellation_mode,
+            EchoCancellationMode::kBrowserDecides);
   EXPECT_EQ(result.num_channels(), 1);
 
   ResetFactory();
@@ -816,8 +793,8 @@
   EXPECT_TRUE(result.HasValue());
   EXPECT_EQ(result.device_id(), "default_device");
   // By default, use 1 channel, even with a stereo device.
-  EXPECT_EQ(result.audio_processing_properties().echo_cancellation_type,
-            EchoCancellationType::kEchoCancellationAec3);
+  EXPECT_EQ(result.audio_processing_properties().echo_cancellation_mode,
+            EchoCancellationMode::kBrowserDecides);
   EXPECT_EQ(result.num_channels(), 1);
 
   ResetFactory();
@@ -825,8 +802,8 @@
   result = SelectSettings();
   EXPECT_TRUE(result.HasValue());
   EXPECT_EQ(result.device_id(), "default_device");
-  EXPECT_EQ(result.audio_processing_properties().echo_cancellation_type,
-            EchoCancellationType::kEchoCancellationAec3);
+  EXPECT_EQ(result.audio_processing_properties().echo_cancellation_mode,
+            EchoCancellationMode::kBrowserDecides);
   EXPECT_EQ(result.num_channels(), 2);
 
   ResetFactory();
@@ -834,8 +811,8 @@
   result = SelectSettings();
   EXPECT_TRUE(result.HasValue());
   EXPECT_EQ(result.device_id(), "default_device");
-  EXPECT_EQ(result.audio_processing_properties().echo_cancellation_type,
-            EchoCancellationType::kEchoCancellationAec3);
+  EXPECT_EQ(result.audio_processing_properties().echo_cancellation_mode,
+            EchoCancellationMode::kBrowserDecides);
   EXPECT_EQ(result.num_channels(), 2);
 
   ResetFactory();
@@ -843,8 +820,8 @@
   result = SelectSettings();
   EXPECT_TRUE(result.HasValue());
   EXPECT_EQ(result.device_id(), "4_channels_device");
-  EXPECT_EQ(result.audio_processing_properties().echo_cancellation_type,
-            EchoCancellationType::kEchoCancellationAec3);
+  EXPECT_EQ(result.audio_processing_properties().echo_cancellation_mode,
+            EchoCancellationMode::kBrowserDecides);
   EXPECT_EQ(result.num_channels(), 4);
 }
 
@@ -1210,7 +1187,7 @@
     EXPECT_EQ(kArbitraryDeviceID.Utf8(), result.device_id());
     CheckBoolDefaults(AudioSettingsBoolMembers(), AudioPropertiesBoolMembers(),
                       result);
-    CheckEchoCancellationTypeDefault(result);
+    CheckEchoCancellationModeDefault(result);
   }
 }
 
@@ -1230,7 +1207,7 @@
   CheckProcessingType(result);
   CheckBoolDefaults(AudioSettingsBoolMembers(), AudioPropertiesBoolMembers(),
                     result);
-  CheckEchoCancellationTypeDefault(result);
+  CheckEchoCancellationModeDefault(result);
 }
 
 TEST_P(MediaStreamConstraintsUtilAudioTest, ExactValidDeviceID) {
@@ -1242,19 +1219,13 @@
     CheckProcessingType(result);
     CheckBoolDefaults(AudioSettingsBoolMembers(), AudioPropertiesBoolMembers(),
                       result);
-    EchoCancellationType expected_echo_cancellation_type =
-        EchoCancellationType::kEchoCancellationDisabled;
+    EchoCancellationMode expected_echo_cancellation_mode =
+        EchoCancellationMode::kDisabled;
     if (IsDeviceCapture()) {
-      const bool has_system_echo_cancellation =
-          device.Parameters().effects() &
-          media::AudioParameters::ECHO_CANCELLER;
-      expected_echo_cancellation_type =
-          has_system_echo_cancellation
-              ? EchoCancellationType::kEchoCancellationSystem
-              : EchoCancellationType::kEchoCancellationAec3;
+      expected_echo_cancellation_mode = EchoCancellationMode::kBrowserDecides;
     }
-    EXPECT_EQ(expected_echo_cancellation_type,
-              result.audio_processing_properties().echo_cancellation_type);
+    EXPECT_EQ(expected_echo_cancellation_mode,
+              result.audio_processing_properties().echo_cancellation_mode);
   }
 }
 
@@ -1267,19 +1238,13 @@
     CheckProcessingType(result);
     CheckBoolDefaults(AudioSettingsBoolMembers(), AudioPropertiesBoolMembers(),
                       result);
-    EchoCancellationType expected_echo_cancellation_type =
-        EchoCancellationType::kEchoCancellationDisabled;
+    EchoCancellationMode expected_echo_cancellation_mode =
+        EchoCancellationMode::kDisabled;
     if (IsDeviceCapture()) {
-      const bool has_system_echo_cancellation =
-          device.Parameters().effects() &
-          media::AudioParameters::ECHO_CANCELLER;
-      expected_echo_cancellation_type =
-          has_system_echo_cancellation
-              ? EchoCancellationType::kEchoCancellationSystem
-              : EchoCancellationType::kEchoCancellationAec3;
+      expected_echo_cancellation_mode = EchoCancellationMode::kBrowserDecides;
     }
-    EXPECT_EQ(expected_echo_cancellation_type,
-              result.audio_processing_properties().echo_cancellation_type);
+    EXPECT_EQ(expected_echo_cancellation_mode,
+              result.audio_processing_properties().echo_cancellation_mode);
   }
 }
 
@@ -1308,11 +1273,11 @@
         // With content capture, the echo_cancellation constraint controls
         // only the echo_cancellation properties. The other audio processing
         // properties default to false.
-        const EchoCancellationType expected_echo_cancellation_type =
-            value ? EchoCancellationType::kEchoCancellationAec3
-                  : EchoCancellationType::kEchoCancellationDisabled;
-        EXPECT_EQ(expected_echo_cancellation_type,
-                  properties.echo_cancellation_type);
+        const EchoCancellationMode expected_echo_cancellation_mode =
+            value ? EchoCancellationMode::kBrowserDecides
+                  : EchoCancellationMode::kDisabled;
+        EXPECT_EQ(expected_echo_cancellation_mode,
+                  properties.echo_cancellation_mode);
         const bool enable_webrtc_audio_processing =
             IsDeviceCapture() ? value : false;
         EXPECT_EQ(enable_webrtc_audio_processing, properties.auto_gain_control);
@@ -1364,11 +1329,11 @@
         // enables/disables all audio processing by default, WebRTC echo
         // cancellation is always disabled, and system echo cancellation is
         // disabled if the echo_cancellation constraint is false.
-        const EchoCancellationType expected_echo_cancellation_type =
-            value ? EchoCancellationType::kEchoCancellationSystem
-                  : EchoCancellationType::kEchoCancellationDisabled;
-        EXPECT_EQ(expected_echo_cancellation_type,
-                  properties.echo_cancellation_type);
+        const EchoCancellationMode expected_echo_cancellation_mode =
+            value ? EchoCancellationMode::kBrowserDecides
+                  : EchoCancellationMode::kDisabled;
+        EXPECT_EQ(expected_echo_cancellation_mode,
+                  properties.echo_cancellation_mode);
         EXPECT_EQ(value, properties.auto_gain_control);
         EXPECT_EQ(value, properties.noise_suppression);
 
@@ -1417,8 +1382,8 @@
         auto result = SelectSettings();
         EXPECT_TRUE(result.HasValue());
         CheckProcessingType(result);
-        EXPECT_EQ(EchoCancellationType::kEchoCancellationDisabled,
-                  result.audio_processing_properties().echo_cancellation_type);
+        EXPECT_EQ(EchoCancellationMode::kDisabled,
+                  result.audio_processing_properties().echo_cancellation_mode);
         EXPECT_TRUE(result.audio_processing_properties().*
                     GetAudioProcessingProperties()[i]);
         for (WTF::wtf_size_t j = 0; j < GetAudioProcessingProperties().size();
@@ -1442,8 +1407,7 @@
   // Create a capability that is based on a already opened source with system
   // echo cancellation enabled.
   AudioProcessingProperties properties;
-  properties.echo_cancellation_type =
-      EchoCancellationType::kEchoCancellationSystem;
+  properties.echo_cancellation_mode = EchoCancellationMode::kBrowserDecides;
   std::unique_ptr<ProcessedLocalAudioSource> system_echo_canceller_source =
       GetProcessedLocalAudioSource(
           properties, false /* disable_local_echo */,
@@ -1508,7 +1472,7 @@
   CheckProcessingType(result);
   CheckBoolDefaults({&AudioCaptureSettings::render_to_associated_sink}, {},
                     result);
-  CheckEchoCancellationTypeDefault(result);
+  CheckEchoCancellationModeDefault(result);
   EXPECT_TRUE(result.render_to_associated_sink());
 }
 
@@ -1526,7 +1490,7 @@
   CheckDeviceDefaults(result);
   CheckBoolDefaults({}, {&AudioProcessingProperties::noise_suppression},
                     result);
-  CheckEchoCancellationTypeDefault(result);
+  CheckEchoCancellationModeDefault(result);
   EXPECT_TRUE(result.audio_processing_properties().noise_suppression);
 }
 
@@ -1540,7 +1504,7 @@
   CheckDeviceDefaults(result);
   CheckBoolDefaults({}, {&AudioProcessingProperties::noise_suppression},
                     result);
-  CheckEchoCancellationTypeDefault(result);
+  CheckEchoCancellationModeDefault(result);
   // The fourth advanced set is ignored because it contradicts the second set.
   EXPECT_TRUE(result.audio_processing_properties().noise_suppression);
 }
@@ -1632,8 +1596,7 @@
   for (bool use_defaults : {true, false}) {
     AudioProcessingProperties properties;
     if (!use_defaults) {
-      properties.echo_cancellation_type =
-          EchoCancellationType::kEchoCancellationDisabled;
+      properties.echo_cancellation_mode = EchoCancellationMode::kDisabled;
       properties.auto_gain_control = !properties.auto_gain_control;
       properties.noise_suppression = !properties.noise_suppression;
     }
@@ -1685,16 +1648,16 @@
     // Test same as above but for echo cancellation.
     constraint_factory_.Reset();
     constraint_factory_.basic().echo_cancellation.SetExactBoolean(
-        properties.echo_cancellation_type ==
-        EchoCancellationType::kEchoCancellationAec3);
+        properties.echo_cancellation_mode ==
+        EchoCancellationMode::kBrowserDecides);
     auto result = SelectSettingsAudioCapture(
         source.get(), constraint_factory_.CreateMediaConstraints());
     EXPECT_TRUE(result.HasValue());
 
     constraint_factory_.Reset();
     constraint_factory_.basic().echo_cancellation.SetExactBoolean(
-        properties.echo_cancellation_type !=
-        EchoCancellationType::kEchoCancellationAec3);
+        properties.echo_cancellation_mode !=
+        EchoCancellationMode::kBrowserDecides);
     result = SelectSettingsAudioCapture(
         source.get(), constraint_factory_.CreateMediaConstraints());
     EXPECT_FALSE(result.HasValue());
@@ -1803,8 +1766,8 @@
         /*is_reconfiguration_allowed=*/false);
     EXPECT_TRUE(result.HasValue());
     EXPECT_EQ(result.device_id(), kUnusedDeviceID.Utf8());
-    EXPECT_EQ(result.audio_processing_properties().echo_cancellation_type,
-              EchoCancellationType::kEchoCancellationDisabled);
+    EXPECT_EQ(result.audio_processing_properties().echo_cancellation_mode,
+              EchoCancellationMode::kDisabled);
   }
 
   {
@@ -1816,8 +1779,8 @@
         /*is_reconfiguration_allowed=*/false);
     EXPECT_TRUE(result.HasValue());
     EXPECT_EQ(result.device_id(), processed_source->device().id);
-    EXPECT_EQ(result.audio_processing_properties().echo_cancellation_type,
-              EchoCancellationType::kEchoCancellationAec3);
+    EXPECT_EQ(result.audio_processing_properties().echo_cancellation_mode,
+              EchoCancellationMode::kBrowserDecides);
   }
 }
 
diff --git a/third_party/blink/renderer/modules/mediastream/processed_local_audio_source_test.cc b/third_party/blink/renderer/modules/mediastream/processed_local_audio_source_test.cc
index 12986e47..e532562 100644
--- a/third_party/blink/renderer/modules/mediastream/processed_local_audio_source_test.cc
+++ b/third_party/blink/renderer/modules/mediastream/processed_local_audio_source_test.cc
@@ -405,16 +405,13 @@
     AudioProcessingProperties properties;
     switch (GetAecState()) {
       case AEC_DISABLED:
-        properties.echo_cancellation_type = AudioProcessingProperties::
-            EchoCancellationType::kEchoCancellationDisabled;
+        properties.echo_cancellation_mode = EchoCancellationMode::kDisabled;
         break;
       case BROWSER_AEC:
-        properties.echo_cancellation_type = AudioProcessingProperties::
-            EchoCancellationType::kEchoCancellationAec3;
+        properties.echo_cancellation_mode = EchoCancellationMode::kRemoteOnly;
         break;
       case SYSTEM_AEC:
-        properties.echo_cancellation_type = AudioProcessingProperties::
-            EchoCancellationType::kEchoCancellationSystem;
+        properties.echo_cancellation_mode = EchoCancellationMode::kAll;
         break;
     }
 
@@ -477,7 +474,7 @@
   if (IsVoiceIsolationSupported()) {
     platform_effects |= media::AudioParameters::VOICE_ISOLATION_SUPPORTED;
   }
-  if (IsSystemAecDefaultEnabled()) {
+  if (IsSystemAecDefaultEnabled() || GetAecState() == SYSTEM_AEC) {
     platform_effects |= media::AudioParameters::ECHO_CANCELLER;
   }
 
@@ -527,22 +524,18 @@
                         media::AudioParameters::AUTOMATIC_GAIN_CONTROL))) {
     return true;
   }
-  LOG(ERROR) << "\n expected: " << expected.AsHumanReadableString()
-             << "\n      arg: " << arg.AsHumanReadableString();
   return false;
 }
 
 class ProcessedLocalAudioSourcePlatformEffectsTest
     : public ProcessedLocalAudioSourceBase,
       public testing::WithParamInterface<
-          testing::tuple<AudioProcessingProperties::EchoCancellationType,
-                         bool,
-                         bool>> {};
+          testing::tuple<EchoCancellationMode, bool, bool>> {};
 
 TEST_P(ProcessedLocalAudioSourcePlatformEffectsTest,
        PlatformAecNsAgcCorrectIfAvailale) {
   AudioProcessingProperties properties;
-  properties.echo_cancellation_type = std::get<0>(GetParam());
+  properties.echo_cancellation_mode = std::get<0>(GetParam());
   properties.noise_suppression = std::get<1>(GetParam());
   properties.auto_gain_control = std::get<2>(GetParam());
 
@@ -623,12 +616,9 @@
     All,
     ProcessedLocalAudioSourcePlatformEffectsTest,
     ::testing::Combine(
-        ::testing::ValuesIn({AudioProcessingProperties::EchoCancellationType::
-                                 kEchoCancellationDisabled,
-                             AudioProcessingProperties::EchoCancellationType::
-                                 kEchoCancellationSystem,
-                             AudioProcessingProperties::EchoCancellationType::
-                                 kEchoCancellationAec3}),
+        ::testing::ValuesIn({EchoCancellationMode::kDisabled,
+                             EchoCancellationMode::kAll,
+                             EchoCancellationMode::kBrowserDecides}),
         // ACG and NS on/off.
         ::testing::Bool(),
         ::testing::Bool()));
diff --git a/third_party/blink/renderer/modules/mediastream/user_media_processor.cc b/third_party/blink/renderer/modules/mediastream/user_media_processor.cc
index 8501198..99e8c21 100644
--- a/third_party/blink/renderer/modules/mediastream/user_media_processor.cc
+++ b/third_party/blink/renderer/modules/mediastream/user_media_processor.cc
@@ -53,6 +53,7 @@
 #include "third_party/blink/renderer/modules/mediastream/processed_local_audio_source.h"
 #include "third_party/blink/renderer/modules/mediastream/scoped_media_stream_tracer.h"
 #include "third_party/blink/renderer/modules/mediastream/user_media_client.h"
+#include "third_party/blink/renderer/platform/mediastream/media_stream_audio_processor_options.h"
 #include "third_party/blink/renderer/platform/mediastream/media_stream_audio_source.h"
 #include "third_party/blink/renderer/platform/mediastream/media_stream_audio_track.h"
 #include "third_party/blink/renderer/platform/mediastream/media_stream_component_impl.h"
@@ -207,13 +208,15 @@
         processed_source->GetAudioProcessingProperties();
     CHECK(properties);
 
-    source->SetAudioProcessingProperties(
-        properties->echo_cancellation_type ==
-                AudioProcessingProperties::EchoCancellationType::
-                    kEchoCancellationDisabled
+    // TODO(crbug.com/428856440): Use properties->echo_cancellation_mode
+    // directly once we support all EchoCancellationMode values on the Web.
+    EchoCancellationMode source_echo_cancellation_mode =
+        properties->echo_cancellation_mode == EchoCancellationMode::kDisabled
             ? EchoCancellationMode::kDisabled
-            : EchoCancellationMode::kBrowserDecides,
-        properties->auto_gain_control, properties->noise_suppression,
+            : EchoCancellationMode::kBrowserDecides;
+    source->SetAudioProcessingProperties(
+        source_echo_cancellation_mode, properties->auto_gain_control,
+        properties->noise_suppression,
         properties->voice_isolation ==
             AudioProcessingProperties::VoiceIsolationType::
                 kVoiceIsolationEnabled);
diff --git a/third_party/blink/renderer/modules/webgl/webgl_rendering_context_webgpu_base.cc b/third_party/blink/renderer/modules/webgl/webgl_rendering_context_webgpu_base.cc
index cb8877e..9e86a9ec 100644
--- a/third_party/blink/renderer/modules/webgl/webgl_rendering_context_webgpu_base.cc
+++ b/third_party/blink/renderer/modules/webgl/webgl_rendering_context_webgpu_base.cc
@@ -3626,10 +3626,6 @@
   }
 }
 
-bool WebGLRenderingContextWebGPUBase::IsGPUDeviceDestroyed() {
-  return IsLost();
-}
-
 void WebGLRenderingContextWebGPUBase::Trace(Visitor* visitor) const {
   visitor->Trace(draw_framebuffer_binding_);
   visitor->Trace(read_framebuffer_binding_);
diff --git a/third_party/blink/renderer/modules/webgl/webgl_rendering_context_webgpu_base.h b/third_party/blink/renderer/modules/webgl/webgl_rendering_context_webgpu_base.h
index 61af821..4086f905 100644
--- a/third_party/blink/renderer/modules/webgl/webgl_rendering_context_webgpu_base.h
+++ b/third_party/blink/renderer/modules/webgl/webgl_rendering_context_webgpu_base.h
@@ -1302,7 +1302,6 @@
   void OnTextureTransferred() override;
   void InitializeLayer(cc::Layer* layer) override;
   void SetNeedsCompositingUpdate() override;
-  bool IsGPUDeviceDestroyed() override;
 
   void Trace(Visitor*) const override;
 
diff --git a/third_party/blink/renderer/modules/webgpu/gpu_canvas_context.cc b/third_party/blink/renderer/modules/webgpu/gpu_canvas_context.cc
index 69739f2..4adf7c13 100644
--- a/third_party/blink/renderer/modules/webgpu/gpu_canvas_context.cc
+++ b/third_party/blink/renderer/modules/webgpu/gpu_canvas_context.cc
@@ -825,10 +825,6 @@
   }
 }
 
-bool GPUCanvasContext::IsGPUDeviceDestroyed() {
-  return device_->IsDestroyed();
-}
-
 void GPUCanvasContext::CopyToSwapTexture() {
   DCHECK(copy_to_swap_texture_required_);
   DCHECK(texture_);
diff --git a/third_party/blink/renderer/modules/webgpu/gpu_canvas_context.h b/third_party/blink/renderer/modules/webgpu/gpu_canvas_context.h
index 6c0c73b..3323cca 100644
--- a/third_party/blink/renderer/modules/webgpu/gpu_canvas_context.h
+++ b/third_party/blink/renderer/modules/webgpu/gpu_canvas_context.h
@@ -113,7 +113,6 @@
   void OnTextureTransferred() override;
   void InitializeLayer(cc::Layer* layer) override;
   void SetNeedsCompositingUpdate() override;
-  bool IsGPUDeviceDestroyed() override;
 
  private:
   CanvasResourceProvider* GetOrCreateCanvasResourceProvider();
diff --git a/third_party/blink/renderer/modules/webgpu/gpu_device.cc b/third_party/blink/renderer/modules/webgpu/gpu_device.cc
index c3d44bc..2466570 100644
--- a/third_party/blink/renderer/modules/webgpu/gpu_device.cc
+++ b/third_party/blink/renderer/modules/webgpu/gpu_device.cc
@@ -45,6 +45,7 @@
 #include "third_party/blink/renderer/modules/webgpu/gpu_uncaptured_error_event.h"
 #include "third_party/blink/renderer/modules/webgpu/gpu_validation_error.h"
 #include "third_party/blink/renderer/modules/webgpu/string_utils.h"
+#include "third_party/blink/renderer/platform/graphics/gpu/webgpu_mailbox_texture.h"
 #include "third_party/blink/renderer/platform/heap/thread_state.h"
 
 namespace blink {
@@ -505,11 +506,22 @@
 void GPUDevice::destroy(v8::Isolate* isolate) {
   destroyed_ = true;
   external_texture_cache_->Destroy();
-  // Dissociate mailboxes before destroying the device. This ensures that
-  // mailbox operations which run during dissociation can succeed.
-  DissociateMailboxes();
   UnmapAllMappableBuffers(isolate);
+
+  // Texture dissociate mailbox will clear texture contents to black if device
+  // is destroyed/lost. Make sure to destroy the device before dissociating the
+  // mailboxes.
   GetHandle().Destroy();
+
+  for (auto& texture : textures_with_mailbox_) {
+    // Since the device is now lost, the texture will be fully cleared (using
+    // Skia) before presenting, so there's no need to run the AlphaClearer. (The
+    // AlphaClearer wouldn't work on the destroyed device anyway.)
+    texture->GetMailboxTexture()->UnsetAlphaClearer();
+    texture->DissociateMailbox();
+  }
+  textures_with_mailbox_.clear();
+
   FlushNow();
 }
 
@@ -748,13 +760,6 @@
   }
 }
 
-void GPUDevice::DissociateMailboxes() {
-  for (auto& texture : textures_with_mailbox_) {
-    texture->DissociateMailbox();
-  }
-  textures_with_mailbox_.clear();
-}
-
 void GPUDevice::UnmapAllMappableBuffers(v8::Isolate* isolate) {
   for (GPUBuffer* buffer : mappable_buffers_) {
     buffer->unmap(isolate);
diff --git a/third_party/blink/renderer/modules/webgpu/gpu_device.h b/third_party/blink/renderer/modules/webgpu/gpu_device.h
index c88db32..5c6a41d 100644
--- a/third_party/blink/renderer/modules/webgpu/gpu_device.h
+++ b/third_party/blink/renderer/modules/webgpu/gpu_device.h
@@ -197,7 +197,6 @@
 
   // Used by USING_PRE_FINALIZER.
   void Dispose();
-  void DissociateMailboxes();
   void UnmapAllMappableBuffers(v8::Isolate* isolate);
 
   void OnUncapturedError(const wgpu::Device& device,
diff --git a/third_party/blink/renderer/platform/BUILD.gn b/third_party/blink/renderer/platform/BUILD.gn
index 4595323e..d18c54d4 100644
--- a/third_party/blink/renderer/platform/BUILD.gn
+++ b/third_party/blink/renderer/platform/BUILD.gn
@@ -50,6 +50,7 @@
   runtime_enabled_features_json5 = "runtime_enabled_features.json5"
   inputs = scripts_for_json5_files + [
              runtime_enabled_features_json5,
+             "runtime_enabled_features.override.json5",
              "../build/scripts/make_runtime_features_utilities.py",
              "../build/scripts/templates/runtime_enabled_features.cc.tmpl",
              "../build/scripts/templates/runtime_enabled_features.h.tmpl",
@@ -85,6 +86,7 @@
              "../build/scripts/make_runtime_features_utilities.py",
              "../build/scripts/make_runtime_features.py",
              "runtime_enabled_features.json5",
+             "runtime_enabled_features.override.json5",
              "../build/scripts/templates/runtime_feature_state_override_context.h.tmpl",
              "../build/scripts/templates/runtime_feature_state_override_context.cc.tmpl",
            ]
diff --git a/third_party/blink/renderer/platform/PRESUBMIT.py b/third_party/blink/renderer/platform/PRESUBMIT.py
index 7b87d61..f7996d0 100644
--- a/third_party/blink/renderer/platform/PRESUBMIT.py
+++ b/third_party/blink/renderer/platform/PRESUBMIT.py
@@ -29,7 +29,8 @@
         sys.path.remove(json5_path)
 
 
-def _CheckRuntimeEnabledFeaturesSorted(features, output_api):
+def _CheckRuntimeEnabledFeaturesSorted(features, features_filename,
+                                       output_api):
     """Check: runtime_enabled_features.json5 feature list sorted alphabetically.
     """
     names = [feature['name'] for feature in features]
@@ -44,18 +45,17 @@
     differ = difflib.Differ()
     diff = differ.compare(names, names_sorted)
     return [
-        output_api.PresubmitError(
-            'runtime_enabled_features.json5 features must be sorted alphabetically. '
-            'Diff of feature order follows:',
-            long_text='\n'.join(diff))
+        output_api.PresubmitError(features_filename +
+                                  ' features must be sorted alphabetically. '
+                                  'Diff of feature order follows:',
+                                  long_text='\n'.join(diff))
     ]
 
 
-def _CommonChecks(input_api, output_api):
+def _CheckRuntimeEnabledFile(file_name, input_api, output_api):
     """Checks common to both upload and commit."""
-    # Read runtime_enabled_features.json5 using the JSON5 parser.
-    features_filename = os.path.join(input_api.PresubmitLocalPath(),
-                                     'runtime_enabled_features.json5')
+    # Read json5 using the JSON5 parser.
+    features_filename = os.path.join(input_api.PresubmitLocalPath(), file_name)
     try:
         features = RuntimeEnabledFeatures(input_api, features_filename)
     except:
@@ -65,7 +65,22 @@
         ]
 
     results = []
-    results.extend(_CheckRuntimeEnabledFeaturesSorted(features, output_api))
+    results.extend(
+        _CheckRuntimeEnabledFeaturesSorted(features, features_filename,
+                                           output_api))
+
+    return results
+
+
+def _CommonChecks(input_api, output_api):
+    """Checks common to both upload and commit."""
+    results = []
+    results.extend(
+        _CheckRuntimeEnabledFile('runtime_enabled_features.json5', input_api,
+                                 output_api))
+    results.extend(
+        _CheckRuntimeEnabledFile('runtime_enabled_features.override.json5',
+                                 input_api, output_api))
 
     return results
 
diff --git a/third_party/blink/renderer/platform/RuntimeEnabledFeatures.md b/third_party/blink/renderer/platform/RuntimeEnabledFeatures.md
index 0f9f7239..be1a731f 100644
--- a/third_party/blink/renderer/platform/RuntimeEnabledFeatures.md
+++ b/third_party/blink/renderer/platform/RuntimeEnabledFeatures.md
@@ -263,25 +263,29 @@
 ```
 After applying most other feature settings, the features requested feature settings (comma-separated) are changed. "disable" is applied later (and takes precedence), regardless of the order the switches appear on the command line. These switches only affect Blink's state. Some features may need to be switched on in Chromium as well; in this case, a specific flag is required.
 
+## For Embedders
+Downstream forks can customize `runtime_enabled_features.json5` without dealing with merge conflicts by adding entries into `runtime_enabled_features.override.json5`. Some examples of how to override can be found in `runtime_enabled_features.override.json5`.
+
 **Announcement**
-https://groups.google.com/a/chromium.org/d/msg/blink-dev/JBakhu5J6Qs/re2LkfEslTAJ
+[PSA: Runtime Features system is now auto-generated from a .in file.](https://groups.google.com/a/chromium.org/d/msg/blink-dev/JBakhu5J6Qs/re2LkfEslTAJ)
 
+**Links**
+* [web tests](https://chromium.googlesource.com/chromium/src/+/main/docs/testing/web_tests.md)
+* [supportedPlatforms](https://chromium.googlesource.com/chromium/src/+/main/third_party/blink/renderer/platform/runtime_enabled_features.json5#36)
+* [cssProperties](https://chromium.googlesource.com/chromium/src/+/main/third_party/blink/renderer/core/css/css_properties.json5)
+* [virtual test suite](https://chromium.googlesource.com/chromium/src/+/main/docs/testing/web_tests.md#testing-runtime-flags)
+* [flag-specific](https://chromium.googlesource.com/chromium/src/+/main/docs/testing/web_tests.md#testing-runtime-flags)
+* [trybot (example)](https://chromium-review.googlesource.com/c/chromium/src/+/1850255)
+* [LayoutNG](https://docs.google.com/document/d/17t6HjA5X8T5xq1LlKoLEGTn_MioGCdEPpijpJeLalK0/edit#heading=h.guvbepjyp0oj)
+* [BlinkGenPropertyTrees](https://crbug.com/836884)
+* [blink launch process](https://www.chromium.org/blink/launching-features)
+* [Blink extended attribute](https://chromium.googlesource.com/chromium/src/+/main/third_party/blink/renderer/bindings/IDLExtendedAttributes.md)
+* [make_runtime_features.py](https://chromium.googlesource.com/chromium/src/+/main/third_party/blink/renderer/build/scripts/make_runtime_features.py)
+* [runtime_enabled_features.json5](https://chromium.googlesource.com/chromium/src/+/main/third_party/blink/renderer/platform/runtime_enabled_features.json5)
+* [make_internal_runtime_flags.py](https://chromium.googlesource.com/chromium/src/+/main/third_party/blink/renderer/build/scripts/make_internal_runtime_flags.py)
+* [code_generator_v8.py](https://chromium.googlesource.com/chromium/src/+/main/third_party/blink/renderer/bindings/scripts/code_generator_v8.py)
+* [virtual/stable](https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/web_tests/VirtualTestSuites;drc=9878f26d52d32871ed1c085444196e5453909eec;l=112)
+* [content/child/runtime_features.cc](https://source.chromium.org/chromium/chromium/src/+/main:content/child/runtime_features.cc)
+* [initialize blink features](https://chromium.googlesource.com/chromium/src/+/main/docs/initialize_blink_features.md)
+* [controlled by chromium feature](https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/platform/runtime_enabled_features.json5;drc=70bddadf50a14254072cf7ca0bcf83e4331a7d4f;l=833)
 
-[web tests]: <https://chromium.googlesource.com/chromium/src/+/main/docs/testing/web_tests.md>
-[supportedPlatforms]: <https://chromium.googlesource.com/chromium/src/+/main/third_party/blink/renderer/platform/runtime_enabled_features.json5#36>
-[cssProperties]: <https://chromium.googlesource.com/chromium/src/+/main/third_party/blink/renderer/core/css/css_properties.json5>
-[virtual test suite]: <https://chromium.googlesource.com/chromium/src/+/main/docs/testing/web_tests.md#testing-runtime-flags>
-[flag-specific]: <https://chromium.googlesource.com/chromium/src/+/main/docs/testing/web_tests.md#testing-runtime-flags>
-[trybot (example)]: <https://chromium-review.googlesource.com/c/chromium/src/+/1850255>
-[LayoutNG]: <https://docs.google.com/document/d/17t6HjA5X8T5xq1LlKoLEGTn_MioGCdEPpijpJeLalK0/edit#heading=h.guvbepjyp0oj>
-[BlinkGenPropertyTrees]: <https://crbug.com/836884>
-[blink launch process]: <https://www.chromium.org/blink/launching-features>
-[Blink extended attribute]: <https://chromium.googlesource.com/chromium/src/+/main/third_party/blink/renderer/bindings/IDLExtendedAttributes.md>
-[make_runtime_features.py]: <https://chromium.googlesource.com/chromium/src/+/main/third_party/blink/renderer/build/scripts/make_runtime_features.py>
-[runtime_enabled_features.json5]: <https://chromium.googlesource.com/chromium/src/+/main/third_party/blink/renderer/platform/runtime_enabled_features.json5>
-[make_internal_runtime_flags.py]: <https://chromium.googlesource.com/chromium/src/+/main/third_party/blink/renderer/build/scripts/make_internal_runtime_flags.py>
-[code_generator_v8.py]: <https://chromium.googlesource.com/chromium/src/+/main/third_party/blink/renderer/bindings/scripts/code_generator_v8.py>
-[virtual/stable]: <https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/web_tests/VirtualTestSuites;drc=9878f26d52d32871ed1c085444196e5453909eec;l=112>
-[content/child/runtime_features.cc]: <https://source.chromium.org/chromium/chromium/src/+/main:content/child/runtime_features.cc>
-[initialize blink features]: <https://chromium.googlesource.com/chromium/src/+/main/docs/initialize_blink_features.md>
-[controlled by chromium feature]: <https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/platform/runtime_enabled_features.json5;drc=70bddadf50a14254072cf7ca0bcf83e4331a7d4f;l=833>
diff --git a/third_party/blink/renderer/platform/fonts/script_run_iterator.cc b/third_party/blink/renderer/platform/fonts/script_run_iterator.cc
index 80b7ee7..59d8d3fb 100644
--- a/third_party/blink/renderer/platform/fonts/script_run_iterator.cc
+++ b/third_party/blink/renderer/platform/fonts/script_run_iterator.cc
@@ -10,6 +10,7 @@
 #include "base/containers/contains.h"
 #include "base/logging.h"
 #include "base/notreached.h"
+#include "third_party/blink/renderer/platform/runtime_enabled_features.h"
 #include "third_party/blink/renderer/platform/text/character.h"
 #include "third_party/blink/renderer/platform/text/icu_error.h"
 #include "third_party/blink/renderer/platform/wtf/text/character_names.h"
@@ -470,16 +471,21 @@
 
   UNSAFE_TODO(U16_NEXT(text_, ahead_pos_, length_, ahead_character_));
 
-  if (!next_set_->empty() && next_set_->front() != USCRIPT_COMMON &&
-      Character::IsGcMark(ahead_character_)) [[unlikely]] {
+  if (Character::IsGcMark(ahead_character_)) [[unlikely]] {
     // A combining mark--whatever its Script property value--should inherit the
     // script property value of its base character.
     // https://www.unicode.org/reports/tr24/#Nonspacing_Marks
-    // `USCRIPT_COMMON` could try looking for more context, but the script of
-    // the combining mark may be still useful, and is backward compatible.
-    // https://www.unicode.org/reports/tr24/#Common
-    *ahead_set_ = *next_set_;
-    return true;
+    if (RuntimeEnabledFeatures::ScriptRunIteratorCombiningMarkAlwaysEnabled()) {
+      ahead_set_->resize(1);
+      ahead_set_->front() = USCRIPT_INHERITED;
+      return true;
+    } else if (!next_set_->empty() && next_set_->front() != USCRIPT_COMMON) {
+      // `USCRIPT_COMMON` could try looking for more context, but the script of
+      // the combining mark may be still useful, and is backward compatible.
+      // https://www.unicode.org/reports/tr24/#Common
+      *ahead_set_ = *next_set_;
+      return true;
+    }
   }
 
   script_data_->GetScripts(ahead_character_, *ahead_set_);
diff --git a/third_party/blink/renderer/platform/fonts/script_run_iterator_test.cc b/third_party/blink/renderer/platform/fonts/script_run_iterator_test.cc
index dec579c..b2631417 100644
--- a/third_party/blink/renderer/platform/fonts/script_run_iterator_test.cc
+++ b/third_party/blink/renderer/platform/fonts/script_run_iterator_test.cc
@@ -668,26 +668,19 @@
   CHECK_MOCK_SCRIPT_RUNS({{"<ch><cl><cg>", USCRIPT_HAN}});
 }
 
-// UDatta (\xE0\xA5\x91) is inherited with LATIN, DEVANAGARI, BENGALI and
-// other Indic scripts. Since it has LATIN, and the
-// dotted circle U+25CC (\xE2\x97\x8C) is COMMON and has adopted the
-// preceding LATIN, it gets the LATIN. This is standard.
+// UDatta (\xE0\xA5\x91) is General Category Mn (Nonspacing Mark), and thus
+// its script value is copied from its base character as per the [UAX24]
+// (https://www.unicode.org/reports/tr24/#Nonspacing_Marks).
+// The previous character, the dotted circle U+25CC (\xE2\x97\x8C) is COMMON
+// and has adopted the preceding LATIN, it gets the LATIN.
 TEST_F(ScriptRunIteratorTest, LatinDottedCircleUdatta) {
   CHECK_SCRIPT_RUNS({{"Latin \xE2\x97\x8C\xE0\xA5\x91", USCRIPT_LATIN}});
 }
 
-// In this situation, UDatta U+0951 (\xE0\xA5\x91) doesn't share a script
-// with the value inherited by the dotted circle U+25CC (\xE2\x97\x8C).
-// It captures the preceding dotted circle and breaks it from the run it would
-// normally have been in. U+0951 is used in multiple scripts (DEVA, BENG, LATN,
-// etc) and has multiple values for Script_Extension property. At the moment,
-// getScripts() treats the script with the lowest script code as 'true' primary,
-// and BENG comes before DEVA in the script enum so that we get BENGALI.
-// Taking into account a Unicode block and returning DEVANAGARI would be
-// slightly better.
+// Although UDatta (\xE0\xA5\x91) doesn't have HAN, its used script value is
+// INHERITED, and thus the same logic as `LatinDottedCircleUdatta` apply.
 TEST_F(ScriptRunIteratorTest, HanDottedCircleUdatta) {
-  CHECK_SCRIPT_RUNS({{"萬國碼 ", USCRIPT_HAN},
-                     {"\xE2\x97\x8C\xE0\xA5\x91", USCRIPT_BENGALI}});
+  CHECK_SCRIPT_RUNS({{"萬國碼 \xE2\x97\x8C\xE0\xA5\x91", USCRIPT_HAN}});
 }
 
 // Tatweel is \xD9\x80 Lm, Fathatan is \xD9\x8B Mn. The script of tatweel is
@@ -717,11 +710,9 @@
 }
 
 // The Udatta U+0951 (\xE0\xA5\x91) is inherited, and will capture the space
-// and turn it into Bengali because SCRIPT_BENAGLI is 4 and SCRIPT_DEVANAGARI
-// is 10. See TODO comment for |getScripts| and HanDottedCircleUdatta.
+// which is USCRIPT_COMMON. This will inherit the previous script, USCRIPT_HAN.
 TEST_F(ScriptRunIteratorTest, HanSpaceUdatta) {
-  CHECK_SCRIPT_RUNS(
-      {{"萬國碼", USCRIPT_HAN}, {" \xE0\xA5\x91", USCRIPT_BENGALI}});
+  CHECK_SCRIPT_RUNS({{"萬國碼 \xE0\xA5\x91", USCRIPT_HAN}});
 }
 
 // Corresponds to one test in RunSegmenter, where orientation of the
@@ -773,7 +764,7 @@
 }
 
 TEST_F(ScriptRunIteratorTest, CommonMalayalam) {
-  CHECK_SCRIPT_RUNS({{"100-ാം", USCRIPT_MALAYALAM}});
+  CHECK_SCRIPT_RUNS({{"100-ാം", USCRIPT_COMMON}});
 }
 
 std::pair<int, UChar32> MaximumScriptExtensions() {
diff --git a/third_party/blink/renderer/platform/graphics/gpu/webgpu_mailbox_texture.cc b/third_party/blink/renderer/platform/graphics/gpu/webgpu_mailbox_texture.cc
index 9d36bcd..8a648b0 100644
--- a/third_party/blink/renderer/platform/graphics/gpu/webgpu_mailbox_texture.cc
+++ b/third_party/blink/renderer/platform/graphics/gpu/webgpu_mailbox_texture.cc
@@ -249,6 +249,10 @@
   alpha_clearer_ = std::move(alpha_clearer);
 }
 
+void WebGPUMailboxTexture::UnsetAlphaClearer() {
+  alpha_clearer_ = nullptr;
+}
+
 gpu::SyncToken WebGPUMailboxTexture::Dissociate() {
   gpu::SyncToken finished_access_token;
   if (wire_texture_id_ != 0) {
diff --git a/third_party/blink/renderer/platform/graphics/gpu/webgpu_mailbox_texture.h b/third_party/blink/renderer/platform/graphics/gpu/webgpu_mailbox_texture.h
index 101bb34..1f28e64 100644
--- a/third_party/blink/renderer/platform/graphics/gpu/webgpu_mailbox_texture.h
+++ b/third_party/blink/renderer/platform/graphics/gpu/webgpu_mailbox_texture.h
@@ -65,6 +65,7 @@
 
   void SetNeedsPresent(bool needs_present) { needs_present_ = needs_present; }
   void SetAlphaClearer(scoped_refptr<WebGPUTextureAlphaClearer> alpha_clearer);
+  void UnsetAlphaClearer();
 
   // Dissociates this mailbox texture from WebGPU, presenting the image if
   // necessary. Returns a sync token which will satisfy when the mailbox's
diff --git a/third_party/blink/renderer/platform/graphics/gpu/webgpu_swap_buffer_provider.cc b/third_party/blink/renderer/platform/graphics/gpu/webgpu_swap_buffer_provider.cc
index 651d5985..21929a5 100644
--- a/third_party/blink/renderer/platform/graphics/gpu/webgpu_swap_buffer_provider.cc
+++ b/third_party/blink/renderer/platform/graphics/gpu/webgpu_swap_buffer_provider.cc
@@ -257,10 +257,6 @@
     return nullptr;
   }
 
-  if (client_ && client_->IsGPUDeviceDestroyed()) {
-    return nullptr;
-  }
-
   scoped_refptr<gpu::ClientSharedImage> shared_image = GetCurrentSharedImage();
 
   ReleaseWGPUTextureAccessIfNeeded();
@@ -322,10 +318,6 @@
     return false;
   }
 
-  if (client_ && client_->IsGPUDeviceDestroyed()) {
-    return false;
-  }
-
   DCHECK(frame_pool);
 
   auto* frame_pool_ri = frame_pool->GetRasterInterface();
diff --git a/third_party/blink/renderer/platform/graphics/gpu/webgpu_swap_buffer_provider.h b/third_party/blink/renderer/platform/graphics/gpu/webgpu_swap_buffer_provider.h
index 08b980ac..088b081 100644
--- a/third_party/blink/renderer/platform/graphics/gpu/webgpu_swap_buffer_provider.h
+++ b/third_party/blink/renderer/platform/graphics/gpu/webgpu_swap_buffer_provider.h
@@ -43,12 +43,6 @@
     virtual void OnTextureTransferred() = 0;
     virtual void InitializeLayer(cc::Layer* layer) = 0;
     virtual void SetNeedsCompositingUpdate() = 0;
-    // Check whether GPUDevice is destroyed. wgpu::Device doesn't have interface
-    // to check device destroyed state.
-    // TODO(crbug.com/370694819): Move device destroy fallback logics from
-    // renderer process to gpu process so that we can detect device destroy
-    // immediately.
-    virtual bool IsGPUDeviceDestroyed() = 0;
   };
 
   WebGPUSwapBufferProvider(
diff --git a/third_party/blink/renderer/platform/graphics/gpu/webgpu_swap_buffer_provider_test.cc b/third_party/blink/renderer/platform/graphics/gpu/webgpu_swap_buffer_provider_test.cc
index 29f0218..f4447a8 100644
--- a/third_party/blink/renderer/platform/graphics/gpu/webgpu_swap_buffer_provider_test.cc
+++ b/third_party/blink/renderer/platform/graphics/gpu/webgpu_swap_buffer_provider_test.cc
@@ -102,7 +102,6 @@
   }
   void InitializeLayer(cc::Layer* layer) override {}
   void SetNeedsCompositingUpdate() override {}
-  bool IsGPUDeviceDestroyed() override { return false; }
 
   scoped_refptr<WebGPUMailboxTexture> texture;
 };
diff --git a/third_party/blink/renderer/platform/mediastream/media_stream_audio_processor_options.cc b/third_party/blink/renderer/platform/mediastream/media_stream_audio_processor_options.cc
index 6b867527..de7b3ec 100644
--- a/third_party/blink/renderer/platform/mediastream/media_stream_audio_processor_options.cc
+++ b/third_party/blink/renderer/platform/mediastream/media_stream_audio_processor_options.cc
@@ -11,10 +11,23 @@
 
 namespace blink {
 
+const char* EchoCancellationModeToString(EchoCancellationMode ec_mode) {
+  switch (ec_mode) {
+    case EchoCancellationMode::kDisabled:
+      return "disabled";
+    case EchoCancellationMode::kBrowserDecides:
+      return "browser-decides";
+    case EchoCancellationMode::kAll:
+      return "all";
+    case EchoCancellationMode::kRemoteOnly:
+      return "remote-only";
+  }
+}
+
 // static
 const AudioProcessingProperties& AudioProcessingProperties::Disabled() {
   static constexpr AudioProcessingProperties kDisabledProperties{
-      .echo_cancellation_type = EchoCancellationType::kEchoCancellationDisabled,
+      .echo_cancellation_mode = EchoCancellationMode::kDisabled,
       .auto_gain_control = false,
       .noise_suppression = false,
       .voice_isolation = VoiceIsolationType::kVoiceIsolationDefault};
@@ -24,7 +37,7 @@
 
 bool AudioProcessingProperties::HasSameReconfigurableSettings(
     const AudioProcessingProperties& other) const {
-  return echo_cancellation_type == other.echo_cancellation_type;
+  return echo_cancellation_mode == other.echo_cancellation_mode;
 }
 
 bool AudioProcessingProperties::HasSameNonReconfigurableSettings(
@@ -35,21 +48,11 @@
 }
 
 std::string AudioProcessingProperties::ToString() const {
-  auto aec_to_string = [](EchoCancellationType type) {
-    switch (type) {
-      case EchoCancellationType::kEchoCancellationDisabled:
-        return "disabled";
-      case EchoCancellationType::kEchoCancellationAec3:
-        return "aec3";
-      case EchoCancellationType::kEchoCancellationSystem:
-        return "system";
-    }
-  };
   auto str = base::StringPrintf(
-      "echo_cancellation_type: %s, "
+      "echo_cancellation_mode: %s, "
       "auto_gain_control: %s, "
       "noise_suppression: %s, ",
-      aec_to_string(echo_cancellation_type),
+      EchoCancellationModeToString(echo_cancellation_mode),
       base::ToString(auto_gain_control).c_str(),
       base::ToString(noise_suppression).c_str());
   return str;
@@ -64,29 +67,7 @@
 // static
 EchoCanceller EchoCanceller::From(const AudioProcessingProperties& properties,
                                   int available_platform_effects) {
-  return From(properties.echo_cancellation_type);
-}
-
-// static
-EchoCanceller EchoCanceller::From(
-    AudioProcessingProperties::EchoCancellationType type) {
-  using EchoCancellationType = AudioProcessingProperties::EchoCancellationType;
-  auto to_echo_canceller_type = [](EchoCancellationType type) {
-    switch (type) {
-      case EchoCancellationType::kEchoCancellationDisabled:
-        return Type::kNone;
-      case EchoCancellationType::kEchoCancellationSystem:
-        return Type::kPlatformProvided;
-      case EchoCancellationType::kEchoCancellationAec3:
-        return media::IsSystemLoopbackAsAecReferenceForcedOn()
-                   ? Type::kLoopbackBased
-               : media::IsChromeWideEchoCancellationEnabled()
-                   ? Type::kChromeWide
-                   : Type::kPeerConnection;
-    }
-  };
-
-  return EchoCanceller(to_echo_canceller_type(type));
+  return From(properties.echo_cancellation_mode, available_platform_effects);
 }
 
 // static
diff --git a/third_party/blink/renderer/platform/mediastream/media_stream_audio_processor_options.h b/third_party/blink/renderer/platform/mediastream/media_stream_audio_processor_options.h
index 66a9213..a82e3fb 100644
--- a/third_party/blink/renderer/platform/mediastream/media_stream_audio_processor_options.h
+++ b/third_party/blink/renderer/platform/mediastream/media_stream_audio_processor_options.h
@@ -18,18 +18,10 @@
   kAll
 };
 
+const char* EchoCancellationModeToString(EchoCancellationMode);
+
 // The result of parsing media stream constraints.
 struct PLATFORM_EXPORT AudioProcessingProperties {
-  enum class EchoCancellationType {
-    // Echo cancellation disabled.
-    kEchoCancellationDisabled,
-    // The WebRTC-provided AEC3 echo canceller.
-    kEchoCancellationAec3,
-    // System echo canceller, for example an OS-provided or hardware echo
-    // canceller.
-    kEchoCancellationSystem
-  };
-
   enum class VoiceIsolationType {
     // Voice isolation behavior selected by the system is used.
     kVoiceIsolationDefault,
@@ -50,8 +42,8 @@
 
   std::string ToString() const;
 
-  EchoCancellationType echo_cancellation_type =
-      EchoCancellationType::kEchoCancellationAec3;
+  EchoCancellationMode echo_cancellation_mode =
+      EchoCancellationMode::kBrowserDecides;
   bool auto_gain_control = true;
   bool noise_suppression = true;
   VoiceIsolationType voice_isolation =
@@ -76,11 +68,6 @@
   static EchoCanceller From(const AudioProcessingProperties& properties,
                             int available_platform_effects);
 
-  // Can be removed when AudioProcessingProperties are switched to using
-  // EchoCancellation mode.
-  static EchoCanceller From(
-      AudioProcessingProperties::EchoCancellationType type);
-
   static EchoCanceller From(EchoCancellationMode mode,
                             int available_platform_effects);
 
diff --git a/third_party/blink/renderer/platform/mediastream/media_stream_source.cc b/third_party/blink/renderer/platform/mediastream/media_stream_source.cc
index 16a2376c2..7a0f9bc 100644
--- a/third_party/blink/renderer/platform/mediastream/media_stream_source.cc
+++ b/third_party/blink/renderer/platform/mediastream/media_stream_source.cc
@@ -175,10 +175,10 @@
     bool noise_supression,
     bool voice_isolation) {
   SendLogMessage(
-      String::Format("%s({echo_cancellation=%d}, {auto_gain_control=%d}, "
+      String::Format("%s({echo_cancellation=%s}, {auto_gain_control=%d}, "
                      "{noise_supression=%d}, {voice_isolation=%d})",
-                     __func__, echo_cancellation, auto_gain_control,
-                     noise_supression, voice_isolation)
+                     __func__, EchoCancellationModeToString(echo_cancellation),
+                     auto_gain_control, noise_supression, voice_isolation)
           .Utf8());
   echo_cancellation_ = echo_cancellation;
   auto_gain_control_ = auto_gain_control;
diff --git a/third_party/blink/renderer/platform/runtime_enabled_features.json5 b/third_party/blink/renderer/platform/runtime_enabled_features.json5
index 2dec3cf..4ca6cbc3 100644
--- a/third_party/blink/renderer/platform/runtime_enabled_features.json5
+++ b/third_party/blink/renderer/platform/runtime_enabled_features.json5
@@ -4146,6 +4146,15 @@
       base_feature: "none",
     },
     {
+      // crbug.com/378692756
+      name: "ScriptRunIteratorCombiningMarkAlways",
+      status: "stable",
+    },
+    {
+      name: "ScriptTools",
+      status: "test",
+    },
+    {
       name: "ScrollAnchorPriorityCandidateSubtree",
       status: "stable",
     },
diff --git a/third_party/blink/renderer/platform/runtime_enabled_features.override.json5 b/third_party/blink/renderer/platform/runtime_enabled_features.override.json5
new file mode 100644
index 0000000..43cfe5a5
--- /dev/null
+++ b/third_party/blink/renderer/platform/runtime_enabled_features.override.json5
@@ -0,0 +1,50 @@
+{
+  // See third_party/blink/renderer/platform/RuntimeEnabledFeatures.md
+  //
+  // This file should not be changed upstream.
+  //
+  // This file provides a way for downstream forks to customize
+  // runtime_enabled_features.json5. Downstream forks are able to modify
+  // this file to add new feature flags or modify existing ones.
+  // This file inherits parameters from runtime_enabled_features.json5,
+  // and they can not be changed in this file.
+  //
+  // Examples:
+  //
+  // Adding a new feature flag enabled by default:
+  // data: [
+  //   {
+  //     name: "NewFeature",
+  //     status: "stable",
+  //     base_feature: "none",
+  //   },
+  // ]
+  //
+  // Marking an existing feature as experimental:
+  // data: [
+  //   {
+  //     name: "ExistingFeature",
+  //     status: "experimental",
+  //   },
+  // ]
+  //
+  // Marking an existing feature as available on certain platforms:
+  // data: [
+  //   {
+  //     name: "ExistingFeature",
+  //     origin_trial_feature_name: "ExistingFeature",
+  //     origin_trial_os: ["win", "mac"],
+  //     origin_trial_allows_third_party: true,
+  //     status: {
+  //       "Win": "experimental",
+  //       "Mac": "experimental",
+  //       "Linux": "",
+  //       "Android":"stable"
+  //     },
+  //   },
+  // ]
+
+  data: [
+    // Entries go here.
+  ]
+}
\ No newline at end of file
diff --git a/third_party/blink/renderer/platform/widget/compositing/render_frame_metadata_observer_impl.cc b/third_party/blink/renderer/platform/widget/compositing/render_frame_metadata_observer_impl.cc
index ae5a033..4de9fc6d 100644
--- a/third_party/blink/renderer/platform/widget/compositing/render_frame_metadata_observer_impl.cc
+++ b/third_party/blink/renderer/platform/widget/compositing/render_frame_metadata_observer_impl.cc
@@ -62,7 +62,7 @@
     send_metadata |= force_send;
   }
 
-#if BUILDFLAG(IS_ANDROID)
+#if BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_IOS)
   bool is_frequency_all_updates =
       root_scroll_offset_update_frequency_.value_or(
           cc::mojom::blink::RootScrollOffsetUpdateFrequency::kNone) ==
@@ -99,8 +99,8 @@
         needs_activation_notification;
     render_frame_metadata_observer_client_->OnRenderFrameMetadataChanged(
         needs_activation_notification ? last_frame_token_ : 0u, metadata_copy);
-#if BUILDFLAG(IS_ANDROID)
-    last_root_scroll_offset_android_ = metadata_copy.root_scroll_offset;
+#if BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_IOS)
+    last_root_scroll_offset_ = metadata_copy.root_scroll_offset;
 #endif
     TRACE_EVENT_WITH_FLOW1(
         TRACE_DISABLED_BY_DEFAULT("viz.surface_id_flow"),
@@ -116,13 +116,12 @@
             : "null");
   }
 
-#if BUILDFLAG(IS_ANDROID)
+#if BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_IOS)
   if (send_root_scroll_offset_changed) {
     DCHECK(!send_metadata);
     render_frame_metadata_observer_client_->OnRootScrollOffsetChanged(
         *render_frame_metadata.root_scroll_offset);
-    last_root_scroll_offset_android_ =
-        *render_frame_metadata.root_scroll_offset;
+    last_root_scroll_offset_ = *render_frame_metadata.root_scroll_offset;
   }
 #endif
 
@@ -135,7 +134,7 @@
   }
 }
 
-#if BUILDFLAG(IS_ANDROID)
+#if BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_IOS)
 void RenderFrameMetadataObserverImpl::UpdateRootScrollOffsetUpdateFrequency(
     cc::mojom::blink::RootScrollOffsetUpdateFrequency frequency) {
   if (!RuntimeEnabledFeatures::CCTNewRFMPushBehaviorEnabled()) {
@@ -254,7 +253,7 @@
   return false;
 }
 
-#if BUILDFLAG(IS_ANDROID)
+#if BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_IOS)
 void RenderFrameMetadataObserverImpl::DidEndScroll() {
   if (!last_render_frame_metadata_.has_value()) {
     return;
@@ -262,7 +261,7 @@
 
   auto root_scroll_offset = last_render_frame_metadata_->root_scroll_offset;
   if (!root_scroll_offset.has_value() ||
-      root_scroll_offset == last_root_scroll_offset_android_) {
+      root_scroll_offset == last_root_scroll_offset_) {
     return;
   }
 
@@ -274,7 +273,7 @@
 
   render_frame_metadata_observer_client_->OnRootScrollOffsetChanged(
       root_scroll_offset.value());
-  last_root_scroll_offset_android_ = root_scroll_offset;
+  last_root_scroll_offset_ = root_scroll_offset;
 }
 #endif
 
diff --git a/third_party/blink/renderer/platform/widget/compositing/render_frame_metadata_observer_impl.h b/third_party/blink/renderer/platform/widget/compositing/render_frame_metadata_observer_impl.h
index 99582b3..b25d6ef9 100644
--- a/third_party/blink/renderer/platform/widget/compositing/render_frame_metadata_observer_impl.h
+++ b/third_party/blink/renderer/platform/widget/compositing/render_frame_metadata_observer_impl.h
@@ -43,12 +43,12 @@
       const cc::RenderFrameMetadata& render_frame_metadata,
       viz::CompositorFrameMetadata* compositor_frame_metadata,
       bool force_send) override;
-#if BUILDFLAG(IS_ANDROID)
+#if BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_IOS)
   void DidEndScroll() override;
 #endif
 
   // mojom::RenderFrameMetadataObserver:
-#if BUILDFLAG(IS_ANDROID)
+#if BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_IOS)
   void UpdateRootScrollOffsetUpdateFrequency(
       cc::mojom::blink::RootScrollOffsetUpdateFrequency frequency) override;
 #endif
@@ -69,14 +69,14 @@
 
   void SendLastRenderFrameMetadata();
 
-#if BUILDFLAG(IS_ANDROID)
+#if BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_IOS)
   // This will determine the frequency to notify
   // |render_frame_metadata_observer_client_| of the frame submissions that
   // involve a root scroll offset change. See |RootScrollOffsetUpdateFrequency|
   // for details.
   std::optional<cc::mojom::blink::RootScrollOffsetUpdateFrequency>
       root_scroll_offset_update_frequency_;
-  std::optional<gfx::PointF> last_root_scroll_offset_android_;
+  std::optional<gfx::PointF> last_root_scroll_offset_;
 #endif
 
   // When true this will notify |render_frame_metadata_observer_client_| of all
diff --git a/third_party/blink/renderer/platform/widget/compositing/render_frame_metadata_observer_impl_unittest.cc b/third_party/blink/renderer/platform/widget/compositing/render_frame_metadata_observer_impl_unittest.cc
index 99229666..2d1c93e 100644
--- a/third_party/blink/renderer/platform/widget/compositing/render_frame_metadata_observer_impl_unittest.cc
+++ b/third_party/blink/renderer/platform/widget/compositing/render_frame_metadata_observer_impl_unittest.cc
@@ -48,7 +48,7 @@
                void(uint32_t frame_token,
                     const cc::RenderFrameMetadata& metadata));
   MOCK_METHOD1(OnFrameSubmissionForTesting, void(uint32_t frame_token));
-#if BUILDFLAG(IS_ANDROID)
+#if BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_IOS)
   MOCK_METHOD1(OnRootScrollOffsetChanged, void(const gfx::PointF& offset));
 #endif
 
@@ -129,10 +129,11 @@
   }
 }
 
-// This test verifies that a frame token is not requested from viz when
-// the root scroll offset changes on Android.
-#if BUILDFLAG(IS_ANDROID)
-TEST_F(RenderFrameMetadataObserverImplTest, ShouldSendFrameTokenOnAndroid) {
+#if BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_IOS)
+// This test verifies that a frame token is not requested from viz when the root
+// scroll offset changes.
+TEST_F(RenderFrameMetadataObserverImplTest,
+       ShouldNotSendFrameTokenOnRootScrollOffsetUpdate) {
   viz::CompositorFrameMetadata compositor_frame_metadata;
   compositor_frame_metadata.send_frame_token_to_embedder = false;
   compositor_frame_metadata.frame_token = 1337;
@@ -160,7 +161,7 @@
   observer_impl().OnRenderFrameSubmission(render_frame_metadata,
                                           &compositor_frame_metadata,
                                           false /* force_send */);
-  // Android does not need a corresponding frame token.
+  // Embedder does not need a corresponding frame token.
   EXPECT_FALSE(compositor_frame_metadata.send_frame_token_to_embedder);
   {
     base::RunLoop run_loop;
@@ -449,7 +450,7 @@
     run_loop.Run();
   }
 }
-#endif
+#endif  // BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_IOS)
 
 // This test verifies that a request to force send metadata is respected.
 TEST_F(RenderFrameMetadataObserverImplTest, ForceSendMetadata) {
diff --git a/third_party/blink/web_tests/NeverFixTests b/third_party/blink/web_tests/NeverFixTests
index e5446ef2..d4eb6ed 100644
--- a/third_party/blink/web_tests/NeverFixTests
+++ b/third_party/blink/web_tests/NeverFixTests
@@ -445,6 +445,9 @@
 external/wpt/css/css-text/white-space/trailing-ogham-002.html [ Skip ]
 external/wpt/css/css-text/white-space/trailing-ogham-003.html [ Skip ]
 
+# This test conflicts with UAX#24.
+crbug.com/378692756 external/wpt/css/css-text/shaping/shaping-arabic-diacritics-002.html [ Skip ]
+
 # 1px pixel differences that are not relevant to the tests.
 external/wpt/css/CSS2/text/text-transform-bicameral-007.xht [ Skip ]
 [ Mac ] external/wpt/css/CSS2/text/text-transform-bicameral-014.xht [ Skip ]
diff --git a/third_party/blink/web_tests/TestExpectations b/third_party/blink/web_tests/TestExpectations
index cc2ab3f..b1c0882 100644
--- a/third_party/blink/web_tests/TestExpectations
+++ b/third_party/blink/web_tests/TestExpectations
@@ -1134,7 +1134,6 @@
 crbug.com/1076027 wpt_internal/css/css-masonry/row-auto-repeat-014.html [ Failure ]
 crbug.com/1076027 wpt_internal/css/css-masonry/row-auto-repeat-auto-006.html [ Failure ]
 crbug.com/1076027 wpt_internal/css/css-masonry/row-auto-repeat-auto-012.html [ Failure ]
-crbug.com/1076027 wpt_internal/css/css-masonry/row-explicit-placement-006.html [ Failure ]
 # Masonry named line failures
 crbug.com/1076027 external/wpt/css/css-grid/masonry/tentative/grid-placement/masonry-grid-placement-named-lines-001.html [ Crash Failure ]
 crbug.com/1076027 external/wpt/css/css-grid/masonry/tentative/grid-placement/masonry-grid-placement-named-lines-002.html [ Crash Failure ]
diff --git a/third_party/blink/web_tests/webexposed/global-interface-listing-expected.txt b/third_party/blink/web_tests/webexposed/global-interface-listing-expected.txt
index 2eced4d..a1c6117 100644
--- a/third_party/blink/web_tests/webexposed/global-interface-listing-expected.txt
+++ b/third_party/blink/web_tests/webexposed/global-interface-listing-expected.txt
@@ -377,6 +377,11 @@
     attribute @@toStringTag
     getter clientDataJSON
     method constructor
+interface AutomationDelegate
+    attribute @@toStringTag
+    method constructor
+    method registerTool
+    method unregisterTool
 interface BackForwardCacheRestoration : PerformanceEntry
     attribute @@toStringTag
     getter pageshowEventEnd
@@ -13116,6 +13121,7 @@
     attribute propertyNamesInGlobal
     attribute testRunner
     attribute textInputController
+    getter automationDelegate
     getter caches
     getter clientInformation
     getter closed
@@ -13368,6 +13374,7 @@
     method webkitRequestAnimationFrame
     method webkitRequestFileSystem
     method webkitResolveLocalFileSystemURL
+    setter automationDelegate
     setter clientInformation
     setter devicePixelRatio
     setter event
diff --git a/third_party/blink/web_tests/wpt_internal/css/css-masonry/column-explicit-placement-003-ref.html b/third_party/blink/web_tests/wpt_internal/css/css-masonry/column-explicit-placement-003-ref.html
new file mode 100644
index 0000000..f285581
--- /dev/null
+++ b/third_party/blink/web_tests/wpt_internal/css/css-masonry/column-explicit-placement-003-ref.html
@@ -0,0 +1,58 @@
+<!DOCTYPE html>
+<html>
+<link rel="help" href="https://drafts.csswg.org/css-grid-3">
+<link rel="author" title="Celeste Pan" href="mailto:celestepan@microsoft.com">
+<style>
+.grid {
+    display: grid;
+    background: gray;
+    grid-template-columns: auto auto auto;
+    height: 200px;
+    width: 500px;
+    gap: 20px;
+    padding: 20px;
+}
+
+.first-track {
+    background: lightskyblue;
+    grid-column-start: 1;
+    writing-mode: vertical-rl;
+    height: max-content;
+}
+
+.second-track {
+    background: lightcoral;
+    grid-column-start: 2;
+    width: fit-content;
+    height: fit-content;
+}
+
+.third-track {
+    background: lightgreen;
+    grid-column-start: 3;
+    writing-mode: vertical-lr;
+    height: max-content;
+}
+
+.flex {
+    display: flex;
+    flex-direction: column;
+    gap: 20px;
+}
+</style>
+<body>
+  <p>Test that orthoganal masonry with explicit placement are correctly positioned within the grid axis and that the margins on the orthogonal items correctly affect track sizing.</p>
+  <div class="grid">
+    <div class="flex">
+      <div class="first-track">This is some text</div>
+      <div class="first-track" style="margin-right: 10px;">This is some text</div>
+      <div class="first-track" style="writing-mode: horizontal-tb;">This is some text</div>
+    </div>
+    <div class="second-track" style="margin-bottom: 10px;">Some larger words in this sentence</div>
+    <div class="flex" style="grid-column-start: 3;">
+      <div class="third-track">The cat cannot be separated from milk</div>
+      <div class="third-track">This is some other text</div>
+    </div>
+  </div>
+</body>
+</html>
diff --git a/third_party/blink/web_tests/wpt_internal/css/css-masonry/column-explicit-placement-003.html b/third_party/blink/web_tests/wpt_internal/css/css-masonry/column-explicit-placement-003.html
new file mode 100644
index 0000000..bcdce25b
--- /dev/null
+++ b/third_party/blink/web_tests/wpt_internal/css/css-masonry/column-explicit-placement-003.html
@@ -0,0 +1,51 @@
+<!DOCTYPE html>
+<html>
+<link rel="help" href="https://drafts.csswg.org/css-grid-3">
+<link rel="match" href="column-explicit-placement-003-ref.html">
+<link rel="author" title="Celeste Pan" href="mailto:celestepan@microsoft.com">
+<style>
+.masonry {
+    display: masonry;
+    masonry-direction: column;
+    background: gray;
+    item-tolerance: 0;
+    grid-template-columns: auto auto auto;
+    height: 200px;
+    width: 500px;
+    gap: 20px;
+    padding: 20px;
+}
+
+.first-track {
+    background: lightskyblue;
+    grid-column-start: 1;
+    writing-mode: vertical-rl;
+    height: max-content;
+}
+
+.second-track {
+    background: lightcoral;
+    grid-column-start: 2;
+    width: fit-content;
+    height: fit-content;
+}
+
+.third-track {
+    background: lightgreen;
+    grid-column-start: 3;
+    writing-mode: vertical-lr;
+    height: max-content;
+}
+</style>
+<body>
+  <p>Test that orthoganal masonry with explicit placement are correctly positioned within the grid axis and that the margins on the orthogonal items correctly affect track sizing.</p>
+  <div class="masonry">
+    <div class="first-track">This is some text</div>
+    <div class="first-track" style="margin-right: 10px;">This is some text</div>
+    <div class="first-track" style="writing-mode: horizontal-tb">This is some text</div>
+    <div class="second-track" style="margin-bottom: 10px;">Some larger words in this sentence</div>
+    <div class="third-track">The cat cannot be separated from milk</div>
+    <div class="third-track">This is some other text</div>
+  </div>
+</body>
+</html>
diff --git a/third_party/blink/web_tests/wpt_internal/css/css-masonry/row-explicit-placement-006-ref.html b/third_party/blink/web_tests/wpt_internal/css/css-masonry/row-explicit-placement-006-ref.html
index cbc055f..ae9e8d0 100644
--- a/third_party/blink/web_tests/wpt_internal/css/css-masonry/row-explicit-placement-006-ref.html
+++ b/third_party/blink/web_tests/wpt_internal/css/css-masonry/row-explicit-placement-006-ref.html
@@ -16,17 +16,20 @@
     grid-row-start: 1;
     writing-mode: vertical-rl;
     margin-top: 10px;
+    width: fit-content;
 }
 
 .second-track {
     background: lightcoral;
     grid-row-start: 2;
+    width: fit-content;
 }
 
 .third-track {
     background: lightgreen;
     grid-row-start: 3;
     writing-mode: vertical-lr;
+    width: fit-content;
 }
 </style>
 <body>
diff --git a/third_party/blink/web_tests/wpt_internal/css/css-masonry/row-explicit-placement-008-ref.html b/third_party/blink/web_tests/wpt_internal/css/css-masonry/row-explicit-placement-008-ref.html
new file mode 100644
index 0000000..eb4ab23
--- /dev/null
+++ b/third_party/blink/web_tests/wpt_internal/css/css-masonry/row-explicit-placement-008-ref.html
@@ -0,0 +1,60 @@
+<!DOCTYPE html>
+<html>
+<link rel="help" href="https://drafts.csswg.org/css-grid-3">
+<link rel="author" title="Celeste Pan" href="mailto:celestepan@microsoft.com">
+<style>
+.grid {
+    display: grid;
+    background: gray;
+    grid-template-rows: repeat(3, 100px);
+    grid-template-columns: repeat(3, 100px);
+    height: 200px;
+    gap: 20px;
+    padding: 20px;
+}
+
+.first-track {
+    background: lightskyblue;
+    grid-row-start: 1;
+    writing-mode: vertical-rl;
+    margin-top: 10px;
+    width: fit-content;
+}
+
+.second-track {
+    background: lightcoral;
+    grid-row-start: 2;
+    width: max-content;
+    grid-column: span 2;
+}
+
+.third-track {
+    background: lightgreen;
+    grid-row-start: 3;
+    writing-mode: vertical-lr;
+    width: fit-content;
+}
+
+.flex {
+    display: flex;
+    flex-direction: row;
+    gap: 20px;
+    width: max-content;
+}
+</style>
+<body>
+  <p>Test that orthoganal masonry with explicit placement are correctly positioned within the grid axis and that the margins on the orthogonal items correctly affect track sizing.</p>
+  <div class="grid">
+    <div class="flex">
+      <div class="first-track">This is some text</div>
+      <div class="first-track" style="margin-right: 10px;">This is some text</div>
+      <div class="first-track" style="writing-mode: horizontal-tb; width: max-content;">This is some text</div>
+    </div>
+    <div class="second-track" style="margin-bottom: 10px;">Some larger words in this sentence</div>
+    <div class="flex" style="grid-row-start: 3;">
+      <div class="third-track">The cat cannot be separated from milk</div>
+      <div class="third-track">This is some other text</div>
+    </div>
+  </div>
+</body>
+</html>
diff --git a/third_party/blink/web_tests/wpt_internal/css/css-masonry/row-explicit-placement-008.html b/third_party/blink/web_tests/wpt_internal/css/css-masonry/row-explicit-placement-008.html
new file mode 100644
index 0000000..df35f889
--- /dev/null
+++ b/third_party/blink/web_tests/wpt_internal/css/css-masonry/row-explicit-placement-008.html
@@ -0,0 +1,47 @@
+<!DOCTYPE html>
+<html>
+<link rel="help" href="https://drafts.csswg.org/css-grid-3">
+<link rel="match" href="row-explicit-placement-008-ref.html">
+<link rel="author" title="Celeste Pan" href="mailto:celestepan@microsoft.com">
+<style>
+.masonry {
+    display: masonry;
+    masonry-direction: row;
+    grid-template-rows: repeat(3, 100px);
+    background: gray;
+    item-tolerance: 0;
+    height: 200px;
+    gap: 20px;
+    padding: 20px;
+}
+
+.first-track {
+    background: lightskyblue;
+    grid-row-start: 1;
+    writing-mode: vertical-rl;
+    margin-top: 10px;
+}
+
+.second-track {
+    background: lightcoral;
+    grid-row-start: 2;
+}
+
+.third-track {
+    background: lightgreen;
+    grid-row-start: 3;
+    writing-mode: vertical-lr;
+}
+</style>
+<body>
+  <p>Test that orthoganal masonry with explicit placement are correctly positioned within the grid axis and that the margins on the orthogonal items correctly affect track sizing.</p>
+  <div class="masonry">
+    <div class="first-track">This is some text</div>
+    <div class="first-track" style="margin-right: 10px;">This is some text</div>
+    <div class="first-track" style="writing-mode: horizontal-tb">This is some text</div>
+    <div class="second-track" style="margin-bottom: 10px;">Some larger words in this sentence</div>
+    <div class="third-track">The cat cannot be separated from milk</div>
+    <div class="third-track">This is some other text</div>
+  </div>
+</body>
+</html>
diff --git a/third_party/blink/web_tests/wpt_internal/script_tools/duplicate_tool_registration.html b/third_party/blink/web_tests/wpt_internal/script_tools/duplicate_tool_registration.html
new file mode 100644
index 0000000..56fa9bf
--- /dev/null
+++ b/third_party/blink/web_tests/wpt_internal/script_tools/duplicate_tool_registration.html
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<html>
+<title>Script Tools: invalid use of register/unregister tools</title>
+<link rel="author" href="mailto:khushalsagar@chromium.org">
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<script>
+promise_test(async () => {
+  async function empty() {
+    return "empty";
+  }
+
+  const automationDelegate = window.automationDelegate;
+  assert_throws_dom(
+    'InvalidStateError',
+    () => {
+      automationDelegate.registerTool({
+        execute: empty,
+        name: "empty",
+        description: "echo empty",
+      });
+
+      automationDelegate.registerTool({
+        execute: empty,
+        name: "empty",
+        description: "echo empty",
+      });
+    },
+    "duplicate tool registration is invalid."
+  );
+}, "duplicate tool registration is invalid.");
+</script>
diff --git a/third_party/blink/web_tests/wpt_internal/script_tools/register_tool_no_schema.html b/third_party/blink/web_tests/wpt_internal/script_tools/register_tool_no_schema.html
new file mode 100644
index 0000000..24d17ca
--- /dev/null
+++ b/third_party/blink/web_tests/wpt_internal/script_tools/register_tool_no_schema.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<html>
+<title>Script Tools: register tool with no schema</title>
+<link rel="author" href="mailto:khushalsagar@chromium.org">
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<script>
+promise_test(async () => {
+  async function empty() {
+    return "empty";
+  }
+
+  const automationDelegate = window.automationDelegate;
+
+  automationDelegate.registerTool({
+    execute: empty,
+    name: "empty",
+    description: "echo empty",
+  });
+
+  automationDelegate.unregisterTool("empty");
+}, "register tool with only required params");
+</script>
diff --git a/third_party/blink/web_tests/wpt_internal/script_tools/register_tool_with_schema.html b/third_party/blink/web_tests/wpt_internal/script_tools/register_tool_with_schema.html
new file mode 100644
index 0000000..d3b6da8
--- /dev/null
+++ b/third_party/blink/web_tests/wpt_internal/script_tools/register_tool_with_schema.html
@@ -0,0 +1,38 @@
+<!DOCTYPE html>
+<html>
+<title>Script Tools: register/unregister tool with schema</title>
+<link rel="author" href="mailto:khushalsagar@chromium.org">
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<script>
+promise_test(async () => {
+  async function echo(obj) {
+    return obj.text;
+  }
+
+  const automationDelegate = window.automationDelegate;
+
+  automationDelegate.registerTool({
+    execute: echo,
+    name: "echo",
+    description: "echo input",
+    inputSchema: {
+        type: "object",
+        properties: {
+            "text": {
+                description: "Value to echo",
+                type: "string",
+            }
+        },
+        required: ["text"]
+    },
+    annotations: {
+      readOnlyHint: "true"
+    },
+  });
+
+  automationDelegate.unregisterTool("echo");
+}, "register and unregister script tool");
+</script>
diff --git a/third_party/blink/web_tests/wpt_internal/script_tools/unregister_invalid_tool.html b/third_party/blink/web_tests/wpt_internal/script_tools/unregister_invalid_tool.html
new file mode 100644
index 0000000..7a0aa72
--- /dev/null
+++ b/third_party/blink/web_tests/wpt_internal/script_tools/unregister_invalid_tool.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<html>
+<title>Script Tools: invalid use of register/unregister tools</title>
+<link rel="author" href="mailto:khushalsagar@chromium.org">
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<script>
+promise_test(async () => {
+  const automationDelegate = window.automationDelegate;
+  assert_throws_dom(
+    'InvalidStateError',
+    () => {
+      automationDelegate.unregisterTool("empty");
+    },
+    "unregisterTool which doesn't exist"
+  );
+}, "unregisterTool which doesn't exist");
+</script>
diff --git a/third_party/chromite b/third_party/chromite
index 24384ed..eecb3d6 160000
--- a/third_party/chromite
+++ b/third_party/chromite
@@ -1 +1 @@
-Subproject commit 24384ed41afa31690853277445c62c3c2e3ecda5
+Subproject commit eecb3d62cbf51e097396f0c9adbc0758c1519967
diff --git a/third_party/compiler-rt/src b/third_party/compiler-rt/src
index 0378b4d..9d7069a 160000
--- a/third_party/compiler-rt/src
+++ b/third_party/compiler-rt/src
@@ -1 +1 @@
-Subproject commit 0378b4d5676bcf174e02493e0dd4d1507987014d
+Subproject commit 9d7069a223da41e867c1ac0bd5c10cbf7398ee85
diff --git a/third_party/dawn b/third_party/dawn
index 4b97c56..26b0c00 160000
--- a/third_party/dawn
+++ b/third_party/dawn
@@ -1 +1 @@
-Subproject commit 4b97c56324e5b31394618a40b9946c73ac24abcf
+Subproject commit 26b0c00765b409bb057e3b221de04f6f7b25fef7
diff --git a/third_party/devtools-frontend/src b/third_party/devtools-frontend/src
index 2652463..4fc8b1c 160000
--- a/third_party/devtools-frontend/src
+++ b/third_party/devtools-frontend/src
@@ -1 +1 @@
-Subproject commit 26524631b64cbecd4e12a02ba13f177ae90735ad
+Subproject commit 4fc8b1ccc06c830adf41d9ba9ad7fbc648ebba12
diff --git a/third_party/googletest/src b/third_party/googletest/src
index 309dab8..7e17b15 160000
--- a/third_party/googletest/src
+++ b/third_party/googletest/src
@@ -1 +1 @@
-Subproject commit 309dab8d4bbfcef0ef428762c6fec7172749de0f
+Subproject commit 7e17b15f1547bb8dd9c2fed91043b7af3437387f
diff --git a/third_party/jsoncpp/BUILD.gn b/third_party/jsoncpp/BUILD.gn
index 0d445cc..822bc39 100644
--- a/third_party/jsoncpp/BUILD.gn
+++ b/third_party/jsoncpp/BUILD.gn
@@ -38,6 +38,9 @@
   if (!is_win || is_clang) {
     cflags_cc = [ "-Wno-implicit-fallthrough" ]
   }
+
+  # jsoncpp makes use of static objects for things like a null singleton.
+  configs += [ "//build/config/compiler:no_exit_time_destructors" ]
 }
 
 if (build_with_chromium) {
diff --git a/third_party/llvm-libc/src b/third_party/llvm-libc/src
index 3a9c4311..35acb9c 160000
--- a/third_party/llvm-libc/src
+++ b/third_party/llvm-libc/src
@@ -1 +1 @@
-Subproject commit 3a9c43119ae6b3251df16ea2ee362b41c822fe6e
+Subproject commit 35acb9ccce9146434680d179b47caf14d1d310b1
diff --git a/third_party/pdfium b/third_party/pdfium
index 441eafa..0db284a 160000
--- a/third_party/pdfium
+++ b/third_party/pdfium
@@ -1 +1 @@
-Subproject commit 441eafa8b862fb3aa21e2c3c59daf2892d694099
+Subproject commit 0db284a42a94da8579c1491ee9756ea66dfbf75c
diff --git a/third_party/re2/src b/third_party/re2/src
index 6e9f66fb..79741d6 160000
--- a/third_party/re2/src
+++ b/third_party/re2/src
@@ -1 +1 @@
-Subproject commit 6e9f66fb1290b8c298a66a1803305789cbac0e88
+Subproject commit 79741d66ed4a4b0787a28e3899db6335de5dabef
diff --git a/third_party/skia b/third_party/skia
index b977488..264332c 160000
--- a/third_party/skia
+++ b/third_party/skia
@@ -1 +1 @@
-Subproject commit b977488cbee621bae41ef3921a2c409b6bf8e4ab
+Subproject commit 264332c5ec8db66b2e0d2c8fedf72ed09361765d
diff --git a/third_party/webgpu-cts/src b/third_party/webgpu-cts/src
index 5b47767..26530c7 160000
--- a/third_party/webgpu-cts/src
+++ b/third_party/webgpu-cts/src
@@ -1 +1 @@
-Subproject commit 5b477670f53e5fefcf4bd829a2952013ef9d1953
+Subproject commit 26530c7da1c48ea5d0bf4c5da83b15b996eb522f
diff --git a/tools/gritsettings/resource_ids.spec b/tools/gritsettings/resource_ids.spec
index 7df795d9..7693ff03 100644
--- a/tools/gritsettings/resource_ids.spec
+++ b/tools/gritsettings/resource_ids.spec
@@ -743,6 +743,10 @@
     "META": {"sizes": {"includes": [10]}},
     "includes": [5230],
   },
+  "<(SHARED_INTERMEDIATE_DIR)/chrome/browser/resources/tab_strip_internals/resources.grd": {
+    "META": {"sizes": {"includes": [5]}},
+    "includes": [5240],
+  },
   # END chrome/ WebUI resources section
 
   # START chrome/ miscellaneous section.
diff --git a/tools/json_schema_compiler/schema_loader.py b/tools/json_schema_compiler/schema_loader.py
index eccbc34..cc13dba 100644
--- a/tools/json_schema_compiler/schema_loader.py
+++ b/tools/json_schema_compiler/schema_loader.py
@@ -7,6 +7,7 @@
 
 import idl_schema
 import json_schema
+import web_idl_schema
 
 
 class SchemaLoader(object):
@@ -27,6 +28,12 @@
       api_defs = json_schema.Load(schema_path)
     elif schema_extension == '.idl':
       api_defs = idl_schema.Load(schema_path)
+    elif schema_extension == '.webidl':
+      # The use of '.webidl' as a file extension is a temporary measure to
+      # distinguish schemas which should use the modern WebIDL parser and
+      # processor. Once we have migrated everything to that format we should
+      # change them all to use '.idl' and remove the old code path.
+      api_defs = web_idl_schema.Load(schema_path)
     else:
       sys.exit('Did not recognize file extension %s for schema %s' %
                (schema_extension, schema))
diff --git a/extensions/common/api/alarms.idl b/tools/json_schema_compiler/test/converted_schemas/alarms.idl
similarity index 100%
rename from extensions/common/api/alarms.idl
rename to tools/json_schema_compiler/test/converted_schemas/alarms.idl
diff --git a/tools/json_schema_compiler/test/converted_schemas/alarms.webidl b/tools/json_schema_compiler/test/converted_schemas/alarms.webidl
new file mode 100644
index 0000000..0437499
--- /dev/null
+++ b/tools/json_schema_compiler/test/converted_schemas/alarms.webidl
@@ -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.
+
+dictionary Alarm {
+  // Name of this alarm.
+  DOMString name;
+
+  // Time at which this alarm was scheduled to fire, in milliseconds past the
+  // epoch (e.g. <code>Date.now() + n</code>).  For performance reasons, the
+  // alarm may have been delayed an arbitrary amount beyond this.
+  double scheduledTime;
+
+  // If not null, the alarm is a repeating alarm and will fire again in
+  // <var>periodInMinutes</var> minutes.
+  double? periodInMinutes;
+};
+
+// TODO(mpcomplete): rename to CreateInfo when http://crbug.com/123073 is
+// fixed.
+dictionary AlarmCreateInfo {
+  // Time at which the alarm should fire, in milliseconds past the epoch
+  // (e.g. <code>Date.now() + n</code>).
+  double? when;
+
+  // Length of time in minutes after which the <code>onAlarm</code> event
+  // should fire.
+  //
+  // <!-- TODO: need minimum=0 -->
+  double? delayInMinutes;
+
+  // If set, the onAlarm event should fire every <var>periodInMinutes</var>
+  // minutes after the initial event specified by <var>when</var> or
+  // <var>delayInMinutes</var>.  If not set, the alarm will only fire once.
+  //
+  // <!-- TODO: need minimum=0 -->
+  double? periodInMinutes;
+};
+
+// Listener callback for the onAlarm event.
+// |alarm|: The alarm that has elapsed.
+callback OnAlarmListener = undefined (Alarm alarm);
+
+interface OnAlarmEvent : ExtensionEvent {
+  static undefined addListener(OnAlarmListener listener);
+  static undefined removeListener(OnAlarmListener listener);
+  static boolean hasListener(OnAlarmListener listener);
+};
+
+// Use the <code>chrome.alarms</code> API to schedule code to run
+// periodically or at a specified time in the future.
+interface Alarms {
+  // Creates an alarm.  Near the time(s) specified by <var>alarmInfo</var>,
+  // the <code>onAlarm</code> event is fired. If there is another alarm with
+  // the same name (or no name if none is specified), it will be cancelled and
+  // replaced by this alarm.
+  //
+  // In order to reduce the load on the user's machine, Chrome limits alarms
+  // to at most once every 30 seconds but may delay them an arbitrary amount
+  // more.  That is, setting <code>delayInMinutes</code> or
+  // <code>periodInMinutes</code> to less than <code>0.5</code> will not be
+  // honored and will cause a warning.  <code>when</code> can be set to less
+  // than 30 seconds after "now" without warning but won't actually cause the
+  // alarm to fire for at least 30 seconds.
+  //
+  // To help you debug your app or extension, when you've loaded it unpacked,
+  // there's no limit to how often the alarm can fire.
+  //
+  // |name|: Optional name to identify this alarm. Defaults to the empty
+  // string.
+  // |alarmInfo|: Describes when the alarm should fire.  The initial time must
+  // be specified by either <var>when</var> or <var>delayInMinutes</var> (but
+  // not both).  If <var>periodInMinutes</var> is set, the alarm will repeat
+  // every <var>periodInMinutes</var> minutes after the initial event.  If
+  // neither <var>when</var> or <var>delayInMinutes</var> is set for a
+  // repeating alarm, <var>periodInMinutes</var> is used as the default for
+  // <var>delayInMinutes</var>.
+  // |Returns|: Invoked when the alarm has been created.
+  static Promise<undefined> create(
+      optional DOMString name,
+      AlarmCreateInfo alarmInfo);
+
+  // Retrieves details about the specified alarm.
+  // |name|: The name of the alarm to get. Defaults to the empty string.
+  // |PromiseValue|: alarm
+  [requiredCallback] static Promise<Alarm?> get(optional DOMString name);
+
+  // Gets an array of all the alarms.
+  // |PromiseValue|: alarms
+  [requiredCallback] static Promise<sequence<Alarm>> getAll();
+
+  // Clears the alarm with the given name.
+  // |name|: The name of the alarm to clear. Defaults to the empty string.
+  // |PromiseValue|: wasCleared
+  static Promise<boolean> clear(optional DOMString name);
+
+  // Clears all alarms.
+  // |PromiseValue|: wasCleared
+  static Promise<boolean> clearAll();
+
+  // Fired when an alarm has elapsed. Useful for event pages.
+  static attribute OnAlarmEvent onAlarm;
+};
+
+partial interface Browser {
+  static attribute Alarms alarms;
+};
diff --git a/tools/json_schema_compiler/web_idl_diff_tool.py b/tools/json_schema_compiler/web_idl_diff_tool.py
new file mode 100755
index 0000000..8423569
--- /dev/null
+++ b/tools/json_schema_compiler/web_idl_diff_tool.py
@@ -0,0 +1,62 @@
+#!/usr/bin/env python3
+# 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 json
+import os
+import sys
+
+from difflib import unified_diff
+from schema_loader import SchemaLoader
+
+# Utility for running two separate API schema files through the SchemaLoader and
+# comparing the output of each. Intended to be used when converting an extension
+# API schema file from one format to another i.e. from the old IDL or JSON
+# format to WebIDL.
+#
+# This file can be run manually, but is also used in automated tests for schema
+# files which have been converted to WebIDL to ensure there is no functional
+# changes introduced during the conversion process.
+
+
+def LoadAndReturnUnifiedDiff(file_one: str, file_two: str) -> str:
+  """Loads two schemas passed in and returns any differences from the output.
+
+  Args:
+    file_one: Filepath string to the first schema.
+    file_two: Filepath string to the second schema.
+
+  Returns:
+    A human readable string containing the diff between the outputs of the
+    SchemaLoader in the unified_diff format. May be an empty string if there is
+    no difference detected.
+  """
+  root = os.path.join(os.path.dirname(os.path.realpath(__file__)), os.pardir,
+                      os.pardir)
+  schema_one = SchemaLoader(root).LoadSchema(file_one)
+  schema_two = SchemaLoader(root).LoadSchema(file_two)
+
+  difference = unified_diff(
+      json.dumps(schema_one, indent=2, sort_keys=True).splitlines(),
+      json.dumps(schema_two, indent=2, sort_keys=True).splitlines())
+
+  diff_list = list(difference)
+  if not diff_list:
+    return ''
+  return '\n'.join(diff_list)
+
+
+if __name__ == "__main__":
+  args = sys.argv[1:]
+  if len(args) != 2:
+    raise Exception(
+        'Must be called with two parameters, each a file path to an API schema'
+        ' file you want to compare the differences between after they are both'
+        ' parsed and processed.')
+
+  diff = LoadAndReturnUnifiedDiff(args[0], args[1])
+  if diff:
+    print(diff)
+  else:
+    print('No difference found!')
diff --git a/tools/json_schema_compiler/web_idl_diff_tool_test.py b/tools/json_schema_compiler/web_idl_diff_tool_test.py
new file mode 100755
index 0000000..30baf1d
--- /dev/null
+++ b/tools/json_schema_compiler/web_idl_diff_tool_test.py
@@ -0,0 +1,37 @@
+#!/usr/bin/env python3
+# 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 unittest
+import web_idl_diff_tool
+
+# Test to ensure there are no difference in parsing + processing extension API
+# schemas when converting them to be written using WebIDL. Uses the alarms API
+# schema files as an example to test, copied over at the time they were
+# initially converted to WebIDL.
+
+
+class WebIdlDiffToolTest(unittest.TestCase):
+
+  def testIdlToWebIdlConversion(self):
+    converted_schemas = [
+        ('alarms.idl', 'alarms.webidl'),
+    ]
+    # LoadAndReturnUnifiedDiff expects file paths relative to the repo root.
+    converted_schema_path = 'tools/json_schema_compiler/test/converted_schemas/'
+    for old_schema_name, new_schema_name in converted_schemas:
+      old_filename = converted_schema_path + old_schema_name
+      new_filename = converted_schema_path + new_schema_name
+      diff = web_idl_diff_tool.LoadAndReturnUnifiedDiff(old_filename,
+                                                        new_filename)
+      self.assertEqual(
+          '',
+          diff,
+          f"Difference detected between '{old_filename}' and"
+          f" '{new_filename}':\n{diff}",
+      )
+
+
+if __name__ == '__main__':
+  unittest.main()
diff --git a/tools/json_schema_compiler/web_idl_schema.py b/tools/json_schema_compiler/web_idl_schema.py
index 2cf5046b..c0964759 100755
--- a/tools/json_schema_compiler/web_idl_schema.py
+++ b/tools/json_schema_compiler/web_idl_schema.py
@@ -3,6 +3,7 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
+import importlib
 import itertools
 import json
 import linecache
@@ -27,14 +28,13 @@
 # so let's set things up the way it wants.
 _idl_generators_path = os.path.join(os.path.dirname(os.path.realpath(__file__)),
                                     os.pardir, os.pardir, 'tools')
-if _idl_generators_path in sys.path:
+sys.path.insert(0, _idl_generators_path)
+try:
+  import idl_parser
+  importlib.reload(idl_parser)
   from idl_parser import idl_parser, idl_lexer, idl_node
-else:
-  sys.path.insert(0, _idl_generators_path)
-  try:
-    from idl_parser import idl_parser, idl_lexer, idl_node
-  finally:
-    sys.path.pop(0)
+finally:
+  sys.path.pop(0)
 
 IDLNode = idl_node.IDLNode  # Used for type hints.
 
diff --git a/tools/mac/download_symbols.py b/tools/mac/download_symbols.py
index 64beb0f..f36bc35 100755
--- a/tools/mac/download_symbols.py
+++ b/tools/mac/download_symbols.py
@@ -11,7 +11,6 @@
 """
 
 import argparse
-import csv
 import json
 import os.path
 import platform
@@ -23,7 +22,7 @@
 
 _VERSION_HISTORY = 'https://versionhistory.googleapis.com/v1/chrome/platforms/{platform}/channels/all/versions'
 
-_CHANNEL_REGEX = '.*channels\/(\w+)\/versions.*'
+_CHANNEL_REGEX = r'channels/(\w+)/versions'
 
 _DSYM_URL_TEMPLATE = 'https://dl.google.com/chrome/mac/{channel}/dsym/googlechrome-{version}-{arch}-dsym.tar.bz2'
 
@@ -33,8 +32,9 @@
 
     Args:
         version: The version to download symbols for.
-        channel: The release channel (stable, beta, dev, canary) to download
-                 symbols for. If None, attempts to guess the channel.
+        channel: The release channel (extended, stable, beta, dev, canary,
+                 canary_asan) to download symbols for. If None, attempts to
+                 guess the channel.
         arch: The CPU architecture (x86_64, arm64 / aarch64) to download
               symbols for.
         dest_dir: The location to download symbols to. The dSYMs will be
@@ -42,18 +42,17 @@
 
     Returns:
         The path to the directory containing the dSYMs, which will be a
-        subdirectory of `dest_dir`.
+        subdirectory of `dest_dir`, or None if there is an error.
     """
     if channel is None:
         channel = _identify_channel(version, arch)
         if channel:
             print(
-                'Using release channel {} for {}'.format(channel, version),
+                f'Using release channel "{channel}" for {version}',
                 file=sys.stderr)
         else:
             print(
-                'Could not identify channel for Chrome version {}'.format(
-                    version),
+                f'Could not identify channel for Chrome version {version}',
                 file=sys.stderr)
             return None
 
@@ -61,12 +60,13 @@
     if arch == 'aarch64':
         arch = 'arm64'
 
-    extracted_dir = _download_and_extract(version, channel, arch, dest_dir)
-    if not extracted_dir:
-        print(
-            'Could not find dSYMs for Chrome {} {}'.format(version, arch),
-            file=sys.stderr)
-    return extracted_dir
+    try:
+        return _download_and_extract(version, channel, arch, dest_dir)
+    except Exception as err:
+        print(f'Could not find dSYMs for Chrome {version} {arch}: '
+              f'{err}',
+              file=sys.stderr)
+        return None
 
 
 def get_symbol_directory(version, channel, arch, dest_dir):
@@ -88,17 +88,18 @@
         history = json.loads(history_resp.read().decode('utf-8'))
         for entry in history['versions']:
             if entry['version'] == version:
-                match = re.match(_CHANNEL_REGEX, entry['name'])
+                match = re.search(_CHANNEL_REGEX, entry['name'])
                 if match:
                     return match[1]
 
     # Fall back to sending HEAD HTTP requests to each of the possible symbol
     # locations.
     print(
-        'Unable to identify release channel for {}, now brute-force searching'
-        .format(version),
+        f'Unable to identify release channel for {version}, '
+        'now brute-force searching',
         file=sys.stderr)
-    for channel in ('stable', 'beta', 'dev', 'canary'):
+    for channel in ('extended', 'stable', 'beta', 'dev', 'canary',
+                    'canary_asan'):
         url, _ = _get_url_and_dest(version, channel, arch, '')
         req = request.Request(url, method='HEAD')
         try:
@@ -123,7 +124,7 @@
 
 def _download_and_extract(version, channel, arch, dest_dir):
     """Performs the download and extraction of the symbol files. Returns the
-    path to the extracted symbol files on success, None on error.
+    path to the extracted symbol files on success, raises on error.
     """
     url, dest_dir = _get_url_and_dest(version, channel, arch, dest_dir)
     remove_on_failure = False
@@ -134,17 +135,15 @@
     try:
         with request.urlopen(url) as symbol_request:
             print(
-                'Downloading and extracting symbols to {}'.format(dest_dir),
+                f'Downloading and extracting symbols to {dest_dir}',
                 file=sys.stderr)
             print('This will take a minute...', file=sys.stderr)
-            if _extract_symbols_to(symbol_request, dest_dir):
-                return dest_dir
+            _extract_symbols_to(symbol_request, dest_dir)
+            return dest_dir
     except:
-        pass
-
-    if remove_on_failure:
-        shutil.rmtree(dest_dir)
-    return None
+        if remove_on_failure:
+            shutil.rmtree(dest_dir)
+        raise
 
 
 def _extract_symbols_to(symbol_request, dest_dir):
@@ -155,7 +154,8 @@
         dest_dir: The destination directory into which the files will be
                   extracted.
 
-    Returns: True on successful download and extraction, False on error.
+    Raises:
+        Exception if there is an error.
     """
     proc = subprocess.Popen(['tar', 'xjf', '-'],
                             cwd=dest_dir,
@@ -170,7 +170,8 @@
         proc.stdin.write(data)
     proc.wait()
 
-    return proc.returncode == 0
+    if proc.returncode != 0:
+        raise Exception(f"Untarring failed with exit code {proc.returncode}")
 
 
 def main():
diff --git a/tools/metrics/actions/actions.xml b/tools/metrics/actions/actions.xml
index 606393f..87050c8 100644
--- a/tools/metrics/actions/actions.xml
+++ b/tools/metrics/actions/actions.xml
@@ -38994,6 +38994,14 @@
   </description>
 </action>
 
+<action name="SidePanel.Comments.Shown">
+  <owner>mickeyburks@chromium.org</owner>
+  <owner>top-chrome-desktop-ui@google.com</owner>
+  <description>
+    Recorded when the comments side panel entry is shown.
+  </description>
+</action>
+
 <action name="SidePanel.Companion.Pinned.BySidePanelHeaderButton">
   <obsolete>Deprecated 11/2024. Companion was deprecated and removed.</obsolete>
   <owner>corising@chromium.org</owner>
diff --git a/tools/metrics/histograms/metadata/custom_tabs/enums.xml b/tools/metrics/histograms/metadata/custom_tabs/enums.xml
index 89075b2..d61abb0 100644
--- a/tools/metrics/histograms/metadata/custom_tabs/enums.xml
+++ b/tools/metrics/histograms/metadata/custom_tabs/enums.xml
@@ -108,7 +108,9 @@
   <int value="40" label="TWA origins EXTRA_ADDITIONAL_TRUSTED_ORIGINS"/>
   <int value="41" label="Url bar hiding EXTRA_ENABLE_URLBAR_HIDING"/>
   <int value="42" label="Translate specified EXTRA_AUTO_TRANSLATE_LANGUAGE"/>
-  <int value="43" label="Experiment override EXTRA_AGA_EXPERIMENT_ENABLE"/>
+  <int value="43"
+      label="(Obsolete) Experiment override EXTRA_AGA_EXPERIMENT_ENABLE.
+             Removed 2025/07."/>
   <int value="44" label="Partial width custom tab CTF_PARTIAL_SIDE_SHEET"/>
   <int value="45" label="Break point EXTRA_ACTIVITY_SIDE_SHEET_BREAKPOINT_DP"/>
   <int value="46" label="Initial width EXTRA_INITIAL_ACTIVITY_WIDTH_PX"/>
diff --git a/tools/metrics/histograms/metadata/extensions/histograms.xml b/tools/metrics/histograms/metadata/extensions/histograms.xml
index f3ac2a1..2b3bd436 100644
--- a/tools/metrics/histograms/metadata/extensions/histograms.xml
+++ b/tools/metrics/histograms/metadata/extensions/histograms.xml
@@ -1179,7 +1179,7 @@
 
 <histogram
     name="Extensions.DeclarativeNetRequest.RegexRulesBeforeRequestActionTime.{RegexRulesetSize}"
-    units="microseconds" expires_after="2025-11-15">
+    units="microseconds" expires_after="2026-08-01">
   <owner>kelvinjiang@chromium.org</owner>
   <owner>src/extensions/OWNERS</owner>
   <summary>
@@ -1245,7 +1245,9 @@
 </histogram>
 
 <histogram name="Extensions.DeclarativeNetRequest.RequestHeaderAdded"
-    enum="WebRequest.RequestHeader" expires_after="2025-08-08">
+    enum="WebRequest.RequestHeader" expires_after="never">
+<!-- expires-never: For monitoring Core API usage. -->
+
   <owner>rdevlin.cronin@chromium.org</owner>
   <owner>kelvinjiang@chromium.org</owner>
   <owner>src/extensions/OWNERS</owner>
@@ -1318,7 +1320,7 @@
 
 <histogram
     name="Extensions.DeclarativeNetRequest.RulesetMatchingBeforeRequestActionTime.{RulesetSize}"
-    units="microseconds" expires_after="2025-08-28">
+    units="microseconds" expires_after="2026-08-01">
   <owner>rdevlin.cronin@chromium.org</owner>
   <owner>kelvinjiang@chromium.org</owner>
   <owner>src/extensions/OWNERS</owner>
@@ -1333,7 +1335,7 @@
 
 <histogram
     name="Extensions.DeclarativeNetRequest.RulesetMatchingHeadersReceivedActionTime.{RulesetSize}"
-    units="microseconds" expires_after="2025-08-28">
+    units="microseconds" expires_after="2026-08-01">
   <owner>rdevlin.cronin@chromium.org</owner>
   <owner>kelvinjiang@chromium.org</owner>
   <owner>src/extensions/OWNERS</owner>
@@ -3919,7 +3921,7 @@
 </histogram>
 
 <histogram name="Extensions.ManifestVersion3Count.{ManifestLocation}"
-    units="number of extensions" expires_after="2025-08-01">
+    units="number of extensions" expires_after="2026-08-01">
   <owner>rdevlin.cronin@chromium.org</owner>
   <owner>extensions-core@chromium.org</owner>
   <summary>
@@ -4131,7 +4133,7 @@
   </summary>
 </histogram>
 
-<histogram name="Extensions.NetworkDelay" units="ms" expires_after="2025-07-13">
+<histogram name="Extensions.NetworkDelay" units="ms" expires_after="2026-08-01">
   <owner>rdevlin.cronin@chromium.org</owner>
   <owner>kelvinjiang@chromium.org</owner>
   <owner>src/extensions/OWNERS</owner>
@@ -5220,7 +5222,7 @@
 
 <histogram
     name="Extensions.WebRequest.BeforeRequestDeclarativeNetRequestEvaluationTime"
-    units="ms" expires_after="2025-06-30">
+    units="ms" expires_after="2026-08-01">
   <owner>rdevlin.cronin@chromium.org</owner>
   <owner>src/extensions/OWNERS</owner>
   <summary>
@@ -5232,7 +5234,7 @@
 
 <histogram
     name="Extensions.WebRequest.BeforeRequestDeclarativeNetRequestEvaluationTimeInMicroseconds"
-    units="microseconds" expires_after="2025-08-31">
+    units="microseconds" expires_after="2026-08-01">
   <owner>rdevlin.cronin@chromium.org</owner>
   <owner>src/extensions/OWNERS</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/facilitated_payments/enums.xml b/tools/metrics/histograms/metadata/facilitated_payments/enums.xml
index d51ec65..9db1608 100644
--- a/tools/metrics/histograms/metadata/facilitated_payments/enums.xml
+++ b/tools/metrics/histograms/metadata/facilitated_payments/enums.xml
@@ -75,6 +75,17 @@
   <int value="2" label="Abandoned"/>
 </enum>
 
+<!-- LINT.IfChange(FacilitatedPayments.Pix.AccountLinking.FlowExitedReason) -->
+
+<enum name="FacilitatedPayments.Pix.AccountLinking.FlowExitedReason">
+  <int value="0" label="Screen not shown"/>
+  <int value="1" label="Screen was closed not by the user"/>
+  <int value="2" label="Screen was closed by the user"/>
+  <int value="3" label="User clicked the No Thanks button"/>
+</enum>
+
+<!-- LINT.ThenChange(/components/facilitated_payments/core/metrics/facilitated_payments_metrics.h:PixAccountLinkingFlowExitedReason) -->
+
 <!-- LINT.IfChange(FacilitatedPayments.PixFlowExitedReason) -->
 
 <enum name="FacilitatedPayments.PixFlowExitedReason">
diff --git a/tools/metrics/histograms/metadata/facilitated_payments/histograms.xml b/tools/metrics/histograms/metadata/facilitated_payments/histograms.xml
index deb4752..ac3cd98e 100644
--- a/tools/metrics/histograms/metadata/facilitated_payments/histograms.xml
+++ b/tools/metrics/histograms/metadata/facilitated_payments/histograms.xml
@@ -293,6 +293,20 @@
   <token key="Scheme" variants="EwalletScheme"/>
 </histogram>
 
+<histogram name="FacilitatedPayments.Pix.AccountLinking.FlowExitedReason"
+    enum="FacilitatedPayments.Pix.AccountLinking.FlowExitedReason"
+    expires_after="2026-06-01">
+  <owner>longsheng@google.com</owner>
+  <owner>siashah@google.com</owner>
+  <owner>payments-autofill-team@google.com</owner>
+  <summary>
+    This histogram logs the reasons as to why the Pix account linking flow was
+    exited. The flow is said to be completed if Chrome successfully opens
+    Wallet's Pix account linking page. [Trigger] Pix account linking flow was
+    exited early. [Frequency] Logged at most once per payflow.
+  </summary>
+</histogram>
+
 <histogram
     name="FacilitatedPayments.Pix.AccountLinking.GetDetailsForCreatePaymentInstrument.Latency"
     units="ms" expires_after="2026-06-01">
@@ -336,18 +350,17 @@
   </summary>
 </histogram>
 
-<histogram name="FacilitatedPayments.Pix.AccountLinkingPromptShown"
+<histogram name="FacilitatedPayments.Pix.AccountLinking.PromptShown"
     enum="BooleanShown" expires_after="2026-06-01">
   <owner>longsheng@google.com</owner>
-  <owner>vishwasuppoor@google.com</owner>
   <owner>siashah@google.com</owner>
   <owner>payments-autofill-team@google.com</owner>
   <summary>
     The count for the number of times the Pix account linking prompt is shown.
-    Records true every time the prompt is shown. [Frequency] Logged once per Pix
-    account linking flow. [Trigger] The histogram is logged when the prompt
-    appears after user completes the payment on their bank/payment app and
-    return back to Chrome.
+    'Shown' is logged when the prompt is shown and `Not shown` is not logged.
+    [Frequency] Logged once per Pix account linking flow. [Trigger] The
+    histogram is logged when the prompt appears after user completes the payment
+    on their bank/payment app and return back to Chrome.
   </summary>
 </histogram>
 
diff --git a/tools/metrics/histograms/metadata/glic/enums.xml b/tools/metrics/histograms/metadata/glic/enums.xml
index 8ec76d55..492d7dc 100644
--- a/tools/metrics/histograms/metadata/glic/enums.xml
+++ b/tools/metrics/histograms/metadata/glic/enums.xml
@@ -281,6 +281,27 @@
 
 <!-- LINT.ThenChange(//chrome/browser/glic/glic_metrics.h:Error) -->
 
+<!-- LINT.IfChange(GlicPrewarmingChecksResult) -->
+
+<enum name="GlicPrewarmingChecksResult">
+  <int value="0" label="Success"/>
+  <int value="1" label="Warming Disabled"/>
+  <int value="2" label="Profile Gone"/>
+  <int value="3" label="Profile Not Ready (Unknown)"/>
+  <int value="4" label="Profile Requires Sign In"/>
+  <int value="5" label="Profile Not Eligible"/>
+  <int value="6" label="Profile Not Rolled Out"/>
+  <int value="7" label="Profile Disallowed By Admin"/>
+  <int value="8" label="Profile Not Enabled (Other)"/>
+  <int value="9" label="Profile Is Last Loaded"/>
+  <int value="10" label="Profile Is Last Active"/>
+  <int value="11" label="Blocked By Shown Glic"/>
+  <int value="12" label="Under Memory Pressure"/>
+  <int value="13" label="Cellular Connection"/>
+</enum>
+
+<!-- LINT.ThenChange(//chrome/browser/glic/glic_profile_manager.h:GlicPrewarmingChecksResult) -->
+
 <!-- LINT.IfChange(GlicRequestEvent) -->
 
 <enum name="GlicRequestEvent">
@@ -385,6 +406,18 @@
 
 <!-- LINT.ThenChange(//chrome/browser/glic/host/glic.mojom:WebClientMode) -->
 
+<!-- LINT.IfChange(LoadingStage) -->
+
+<enum name="LoadingStage">
+  <int value="0" label="Not loading"/>
+  <int value="1" label="Awaiting profile ready"/>
+  <int value="2" label="Awaiting cookie sync"/>
+  <int value="3" label="Loading web client"/>
+  <int value="4" label="Awaiting notifyPanelWillOpen()"/>
+</enum>
+
+<!-- LINT.ThenChange(//chrome/browser/resources/glic/glic_app_controller.ts:LoadingStage) -->
+
 <enum name="PdfRequestStates">
   <int value="0" label="PDF main doc; PDF found"/>
   <int value="1" label="PDF main doc; PDF not found"/>
diff --git a/tools/metrics/histograms/metadata/glic/histograms.xml b/tools/metrics/histograms/metadata/glic/histograms.xml
index 123a854..480100f 100644
--- a/tools/metrics/histograms/metadata/glic/histograms.xml
+++ b/tools/metrics/histograms/metadata/glic/histograms.xml
@@ -315,6 +315,16 @@
   </summary>
 </histogram>
 
+<histogram name="Glic.Host.LoadingStageAtTimeOut" enum="LoadingStage"
+    expires_after="2026-07-17">
+  <owner>iwells@chromium.org</owner>
+  <owner>harringtond@chromium.org</owner>
+  <summary>
+    Histogram that records which loading stage the glic host was in when loading
+    timed out. Only recorded when loading fails due to timeout.
+  </summary>
+</histogram>
+
 <histogram name="Glic.Host.WebClientState.{Event}"
     enum="GlicDetailedWebClientState" expires_after="2026-01-15">
   <owner>harringtond@chromium.org</owner>
@@ -326,6 +336,7 @@
     <variant name="OnCommit" summary="Recorded before commit on the webview."/>
     <variant name="OnDestroy"
         summary="Recorded before the webview is destroyed."/>
+    <variant name="OnLoadTimeout" summary="Recorded when loading times out."/>
   </token>
 </histogram>
 
@@ -636,6 +647,18 @@
   </summary>
 </histogram>
 
+<histogram name="Glic.Prewarming.ChecksResult"
+    enum="GlicPrewarmingChecksResult" expires_after="2026-01-15">
+  <owner>dullweber@chromium.org</owner>
+  <owner>vollick@chromium.org</owner>
+  <owner>jbroman@chromium.org</owner>
+  <summary>
+    Records the reason why Glic preloading was or was not attempted. Recorded
+    each time we try to preload if the user has Glic enabled and went through
+    the FRE.
+  </summary>
+</histogram>
+
 <histogram name="Glic.Response.Attached" enum="Boolean"
     expires_after="2026-01-15">
   <owner>carlosk@chromium.org</owner>
@@ -943,16 +966,6 @@
   </token>
 </histogram>
 
-<histogram name="Glic.ShouldPreload" enum="Boolean" expires_after="2026-01-15">
-  <owner>dullweber@chromium.org</owner>
-  <owner>vollick@chromium.org</owner>
-  <summary>
-    Record whether the current profile is eligible to preload glic. Recorded
-    each time we try to preload if the user has glic enabled and went through
-    the FRE.
-  </summary>
-</histogram>
-
 <histogram name="Glic.TabContext.PdfContentsRequested" enum="PdfRequestStates"
     expires_after="2026-01-15">
   <owner>carlosk@chromium.org</owner>
diff --git a/tools/metrics/histograms/metadata/ios/enums.xml b/tools/metrics/histograms/metadata/ios/enums.xml
index 0398f1a..3b51055 100644
--- a/tools/metrics/histograms/metadata/ios/enums.xml
+++ b/tools/metrics/histograms/metadata/ios/enums.xml
@@ -1383,6 +1383,7 @@
   <int value="16" label="Trusted Vault Key Retrieval [Tip]"/>
   <int value="17" label="Reminder"/>
   <int value="18" label="Commerce"/>
+  <int value="19" label="Content"/>
 </enum>
 
 <!-- LINT.ThenChange(/ios/chrome/browser/push_notification/model/constants.h:NotificationType) -->
diff --git a/tools/metrics/histograms/metadata/ui/enums.xml b/tools/metrics/histograms/metadata/ui/enums.xml
index b492a20..0d0a202 100644
--- a/tools/metrics/histograms/metadata/ui/enums.xml
+++ b/tools/metrics/histograms/metadata/ui/enums.xml
@@ -585,6 +585,7 @@
   <int value="-2084645929" label="chrome://infobar-internals/"/>
   <int value="-2069349961" label="chrome://history-clusters-internals/"/>
   <int value="-2061366287" label="chrome://dev-ui-loader/"/>
+  <int value="-2038391710" label="chrome://tab-strip-internals/"/>
   <int value="-2034706497" label="chrome://net-internals/"/>
   <int value="-2031269077" label="chrome://signin-error/"/>
   <int value="-2025866230" label="chrome://usb-internals/"/>
diff --git a/ui/android/event_forwarder.cc b/ui/android/event_forwarder.cc
index 1bef36b..567fe91 100644
--- a/ui/android/event_forwarder.cc
+++ b/ui/android/event_forwarder.cc
@@ -15,6 +15,7 @@
 #include "ui/events/android/gesture_event_android.h"
 #include "ui/events/android/gesture_event_type.h"
 #include "ui/events/android/key_event_android.h"
+#include "ui/events/android/motion_event_android_factory.h"
 #include "ui/events/android/motion_event_android_java.h"
 
 // Must come after all headers that specialize FromJniType() / ToJniType().
@@ -135,14 +136,20 @@
   // base::TimeTicks::FromUptimeMillis to get base::TimeTicks for this
   // milliseconds timestamp.
   base::TimeTicks down_time = base::TimeTicks::FromUptimeMillis(down_time_ms);
-  ui::MotionEventAndroidJava event(
-      env, motion_event, 1.f / view_->GetDipScale(), 0.f, 0.f, 0.f,
+  auto event = ui::MotionEventAndroidFactory::CreateFromJava(
+      env, /*event=*/motion_event,
+      /*pix_to_dip=*/1.f / view_->GetDipScale(),
+      /*ticks_x=*/0.f,
+      /*ticks_y=*/0.f,
+      /*tick_multiplier=*/0.f,
       base::TimeTicks::FromJavaNanoTime(oldest_event_time_ns),
       base::TimeTicks::FromJavaNanoTime(latest_event_time_ns), down_time,
       android_action, pointer_count, history_size, action_index,
-      0 /* action_button */, android_gesture_classification,
-      android_button_state, raw_pos_x - pos_x_0, raw_pos_y - pos_y_0,
-      for_touch_handle, &pointer0, pointer1.get(), is_latest_event_resampled);
+      /*android_action_button=*/0, android_gesture_classification,
+      android_button_state,
+      /*raw_offset_x_pixels=*/raw_pos_x - pos_x_0,
+      /*raw_offset_y_pixels=*/raw_pos_y - pos_y_0, for_touch_handle, &pointer0,
+      pointer1.get(), is_latest_event_resampled);
 
   if (send_touch_moves_to_observers ||
       android_action !=
@@ -153,10 +160,10 @@
     // cleanup the observer API.
     // TODO(b/328601354): Confirm touch moves are not required, and if they are
     // not required cleanup the observer API.
-    observers_.Notify(&Observer::OnTouchEvent, event);
+    observers_.Notify(&Observer::OnTouchEvent, *event);
   }
 
-  return view_->OnTouchEvent(event);
+  return view_->OnTouchEvent(*event);
 }
 
 void EventForwarder::OnMouseEvent(
@@ -181,17 +188,27 @@
       /*touch_major_pixels=*/0.0f, /*touch_minor_pixels=*/0.0f,
       /*pressure=*/pressure, /*orientation_rad=*/orientation, /*tilt_rad=*/tilt,
       /*tool_type=*/android_tool_type);
-  ui::MotionEventAndroidJava event(
-      env, /*event=*/motion_event, 1.f / view_->GetDipScale(), 0.f, 0.f, 0.f,
-      base::TimeTicks::FromJavaNanoTime(time_ns), android_action,
-      /*pointer_count=*/1, /*history_size=*/0, /*action_index=*/0,
-      android_action_button, /*android_gesture_classification=*/0,
-      android_button_state, /*raw_offset_x_pixels=*/0,
-      /*raw_offset_y_pixels=*/0, /*for_touch_handle=*/false, &pointer, nullptr);
+  auto event = ui::MotionEventAndroidFactory::CreateFromJava(
+      env, /*event=*/motion_event,
+      /*pix_to_dip=*/1.f / view_->GetDipScale(),
+      /*ticks_x=*/0.f,
+      /*ticks_y=*/0.f,
+      /*tick_multiplier=*/0.f,
+      /*oldest_event_time=*/base::TimeTicks::FromJavaNanoTime(time_ns),
+      android_action,
+      /*pointer_count=*/1,
+      /*history_size=*/0,
+      /*action_index=*/0, android_action_button,
+      /*android_gesture_classification=*/0, android_button_state,
+      /*raw_offset_x_pixels=*/0,
+      /*raw_offset_y_pixels=*/0,
+      /*for_touch_handle=*/false,
+      /*pointer0=*/&pointer,
+      /*pointer1=*/nullptr);
 
-  observers_.Notify(&Observer::OnMouseEvent, event);
+  observers_.Notify(&Observer::OnMouseEvent, *event);
 
-  view_->OnMouseEvent(event);
+  view_->OnMouseEvent(*event);
 }
 
 void EventForwarder::OnDragEvent(JNIEnv* env,
@@ -250,15 +267,32 @@
   // base::TimeTicks::FromUptimeMillis to get base::TimeTicks for this
   // milliseconds timestamp.
   base::TimeTicks down_time = base::TimeTicks::FromUptimeMillis(down_time_ms);
-  ui::MotionEventAndroidJava event(
-      env, motion_event, 1.f / view_->GetDipScale(), 0.f, 0.f, 0.f,
-      base::TimeTicks::FromJavaNanoTime(event_time_ns),
-      base::TimeTicks::FromJavaNanoTime(event_time_ns), down_time, 0, 1, 0, 0,
-      0, 0, 0, 0, 0, false, &pointer0, nullptr, false);
+  auto event = ui::MotionEventAndroidFactory::CreateFromJava(
+      env, /*event=*/motion_event,
+      /*pix_to_dip=*/1.f / view_->GetDipScale(),
+      /*ticks_x=*/0.f,
+      /*ticks_y=*/0.f,
+      /*tick_multiplier=*/0.f,
+      /*oldest_event_time=*/base::TimeTicks::FromJavaNanoTime(event_time_ns),
+      /*latest_event_time=*/base::TimeTicks::FromJavaNanoTime(event_time_ns),
+      down_time,
+      /*android_action=*/0,
+      /*pointer_count=*/1,
+      /*history_size=*/0,
+      /*action_index=*/0,
+      /*android_action_button=*/0,
+      /*android_gesture_classification=*/0,
+      /*android_button_state=*/0,
+      /*raw_offset_x_pixels=*/0,
+      /*raw_offset_y_pixels=*/0,
+      /*for_touch_handle=*/false,
+      /*pointer0=*/&pointer0,
+      /*pointer1=*/nullptr,
+      /*is_latest_event_time_resampled=*/false);
 
-  observers_.Notify(&Observer::OnGenericMotionEvent, event);
+  observers_.Notify(&Observer::OnGenericMotionEvent, *event);
 
-  return view_->OnGenericMotionEvent(event);
+  return view_->OnGenericMotionEvent(*event);
 }
 
 jboolean EventForwarder::OnKeyUp(JNIEnv* env,
diff --git a/ui/android/view_android_unittest.cc b/ui/android/view_android_unittest.cc
index 4d9267c..f3a65337 100644
--- a/ui/android/view_android_unittest.cc
+++ b/ui/android/view_android_unittest.cc
@@ -13,6 +13,7 @@
 #include "ui/android/view_android_observer.h"
 #include "ui/android/window_android.h"
 #include "ui/events/android/event_handler_android.h"
+#include "ui/events/android/motion_event_android_factory.h"
 #include "ui/events/android/motion_event_android_java.h"
 #include "ui/events/motionevent_jni_headers/MotionEvent_jni.h"
 #include "ui/events/test/scoped_event_test_tick_clock.h"
@@ -86,10 +87,25 @@
             env, /*downTime=*/0, /*eventTime=*/0, /*action=*/0, /*x=*/0,
             /*y=*/0, /*metaState=*/0);
 
-    ui::MotionEventAndroidJava event(env, obj, 1.f, 0, 0, 0, base::TimeTicks(),
-                                     0, 1, 0, 0, 0, 0, 0, 0, 0, false,
-                                     &pointer0, nullptr);
-    root_.OnTouchEvent(event);
+    auto event = ui::MotionEventAndroidFactory::CreateFromJava(
+        env, obj,
+        /*pix_to_dip=*/1.f,
+        /*ticks_x=*/0,
+        /*ticks_y=*/0,
+        /*tick_multiplier=*/0,
+        /*oldest_event_time=*/base::TimeTicks(),
+        /*android_action=*/0,
+        /*pointer_count=*/1,
+        /*history_size=*/0,
+        /*action_index=*/0,
+        /*android_action_button=*/0,
+        /*android_gesture_classification=*/0,
+        /*android_button_state=*/0,
+        /*raw_offset_x_pixels=*/0,
+        /*raw_offset_y_pixels=*/0,
+        /*for_touch_handle=*/false, &pointer0,
+        /*pointer1=*/nullptr);
+    root_.OnTouchEvent(*event);
   }
 
   void ExpectHit(const TestEventHandler& hitHandler) {
diff --git a/ui/aura/BUILD.gn b/ui/aura/BUILD.gn
index 24e80a2d..8cb1fe1 100644
--- a/ui/aura/BUILD.gn
+++ b/ui/aura/BUILD.gn
@@ -351,28 +351,3 @@
 
   data_deps = [ "//third_party/angle:includes" ]
 }
-
-# This target is added as a dependency of browser interactive_ui_tests. It must
-# be source_set, otherwise the linker will drop the tests as dead code.
-source_set("aura_interactive_ui_tests") {
-  testonly = true
-  if (is_win) {
-    sources = [ "native_window_occlusion_tracker_win_interactive_test.cc" ]
-
-    deps = [
-      ":aura",
-      ":test_support",
-      "//base/test:test_support",
-      "//mojo/core/embedder",
-      "//testing/gtest",
-      "//ui/base:test_support",
-      "//ui/base/ime/init",
-      "//ui/display:display",
-      "//ui/gfx",
-      "//ui/gfx/geometry",
-      "//ui/gl:test_support",
-      "//ui/gl/init",
-      "//ui/views:views",
-    ]
-  }
-}
diff --git a/ui/aura/native_window_occlusion_tracker_win.cc b/ui/aura/native_window_occlusion_tracker_win.cc
index f7ac6acc..1a8dcd6 100644
--- a/ui/aura/native_window_occlusion_tracker_win.cc
+++ b/ui/aura/native_window_occlusion_tracker_win.cc
@@ -10,7 +10,6 @@
 #include <string>
 
 #include "base/containers/contains.h"
-#include "base/feature_list.h"
 #include "base/functional/bind.h"
 #include "base/functional/callback.h"
 #include "base/memory/scoped_refptr.h"
@@ -22,7 +21,6 @@
 #include "base/task/thread_pool.h"
 #include "ui/aura/window_occlusion_tracker.h"
 #include "ui/aura/window_tree_host.h"
-#include "ui/base/ui_base_features.h"
 #include "ui/gfx/geometry/skia_conversions.h"
 #include "ui/gfx/win/hwnd_util.h"
 
@@ -316,8 +314,6 @@
         UpdateOcclusionStateCallback update_occlusion_state_callback)
     : task_runner_(task_runner),
       ui_thread_task_runner_(ui_thread_task_runner),
-      calculate_occluded_region_(base::FeatureList::IsEnabled(
-          features::kApplyNativeOccludedRegionToWindowTracker)),
       update_occlusion_state_callback_(update_occlusion_state_callback) {
   ::CoCreateInstance(__uuidof(VirtualDesktopManager), nullptr, CLSCTX_ALL,
                      IID_PPV_ARGS(&virtual_desktop_manager_));
@@ -673,7 +669,6 @@
 
   num_root_windows_with_unknown_occlusion_state_--;
 
-  SkRegion occluded_window_region = unoccluded_desktop_region_;
   SkRegion curr_unoccluded_destkop = unoccluded_desktop_region_;
   if (window_is_occluding) {
     unoccluded_desktop_region_.op(gfx::RectToSkIRect(window_rect),
@@ -697,18 +692,7 @@
     return true;
   }
   it->second.occlusion_state = Window::OcclusionState::VISIBLE;
-  if (!calculate_occluded_region_ || window_rect.IsEmpty())
-    return true;
 
-  occluded_window_region.op(gfx::RectToSkIRect(window_rect),
-                            SkRegion::kIntersect_Op);
-  if (occluded_window_region.isEmpty())
-    return true;
-
-  occluded_window_region.op(gfx::RectToSkIRect(window_rect),
-                            SkRegion::kReverseDifference_Op);
-  occluded_window_region.translate(-window_rect.x(), -window_rect.y());
-  it->second.occluded_region_pixels.swap(occluded_window_region);
   return true;
 }
 
diff --git a/ui/aura/native_window_occlusion_tracker_win.h b/ui/aura/native_window_occlusion_tracker_win.h
index 48e6f8f..6e6ed37 100644
--- a/ui/aura/native_window_occlusion_tracker_win.h
+++ b/ui/aura/native_window_occlusion_tracker_win.h
@@ -231,10 +231,6 @@
     // task is posted to this task runner.
     const scoped_refptr<base::SequencedTaskRunner> ui_thread_task_runner_;
 
-    // True if the occluded region should be tracked. This caches the value of
-    // the feature `kApplyNativeOccludedRegionToWindowTracker`.
-    const bool calculate_occluded_region_;
-
     // Callback used to update occlusion state on UI thread.
     UpdateOcclusionStateCallback update_occlusion_state_callback_;
 
diff --git a/ui/aura/native_window_occlusion_tracker_win_interactive_test.cc b/ui/aura/native_window_occlusion_tracker_win_interactive_test.cc
deleted file mode 100644
index 1c60dc4..0000000
--- a/ui/aura/native_window_occlusion_tracker_win_interactive_test.cc
+++ /dev/null
@@ -1,679 +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.
-
-#include "base/memory/raw_ptr.h"
-#include "ui/aura/native_window_occlusion_tracker_win.h"
-
-#include <winuser.h>
-
-#include "base/at_exit.h"
-#include "base/command_line.h"
-#include "base/feature_list.h"
-#include "base/run_loop.h"
-#include "base/task/current_thread.h"
-#include "base/task/thread_pool/thread_pool_instance.h"
-#include "base/test/bind.h"
-#include "base/test/scoped_feature_list.h"
-#include "base/win/scoped_gdi_object.h"
-#include "mojo/core/embedder/embedder.h"
-#include "testing/gtest/include/gtest/gtest.h"
-#include "ui/aura/env.h"
-#include "ui/aura/test/aura_test_base.h"
-#include "ui/aura/test/test_focus_client.h"
-#include "ui/aura/test/test_screen.h"
-#include "ui/aura/test/test_window_delegate.h"
-#include "ui/aura/test/test_window_parenting_client.h"
-#include "ui/aura/window.h"
-#include "ui/aura/window_observer.h"
-#include "ui/aura/window_occlusion_tracker.h"
-#include "ui/aura/window_tree_host.h"
-#include "ui/aura/window_tree_host_platform.h"
-#include "ui/base/test/ui_controls.h"
-#include "ui/base/ui_base_features.h"
-#include "ui/display/display.h"
-#include "ui/display/win/dpi.h"
-#include "ui/gfx/geometry/rect.h"
-#include "ui/gfx/win/singleton_hwnd.h"
-#include "ui/gfx/win/window_impl.h"
-#include "ui/gl/test/gl_surface_test_support.h"
-
-namespace aura {
-
-// This class is used to verify expectations about occlusion state changes by
-// adding instances of it as an observer of aura:Windows the tests create and
-// checking that they get the expected call(s) to OnOcclusionStateChanged.
-// The tests verify that the current state, when idle, is the expected state,
-// because the state can be VISIBLE before it reaches the expected state.
-class MockWindowTreeHostObserver : public WindowTreeHostObserver {
- public:
-  explicit MockWindowTreeHostObserver(base::OnceClosure quit_closure)
-      : quit_closure_(std::move(quit_closure)) {}
-
-  MockWindowTreeHostObserver(const MockWindowTreeHostObserver&) = delete;
-  MockWindowTreeHostObserver& operator=(const MockWindowTreeHostObserver&) =
-      delete;
-
-  ~MockWindowTreeHostObserver() override { EXPECT_FALSE(is_expecting_call()); }
-
-  // WindowTreeHostObserver:
-  void OnOcclusionStateChanged(WindowTreeHost* host,
-                               Window::OcclusionState new_state,
-                               const SkRegion& occluded_region) override {
-    // Should only get notified when the occlusion state changes.
-    EXPECT_NE(new_state, cur_state_);
-    cur_state_ = new_state;
-    if (expectation_ != Window::OcclusionState::UNKNOWN &&
-        cur_state_ == expectation_) {
-      EXPECT_FALSE(quit_closure_.is_null());
-      std::move(quit_closure_).Run();
-    }
-  }
-
-  void set_quit_closure(base::OnceClosure quit_closure) {
-    quit_closure_ = std::move(quit_closure);
-  }
-
-  void set_expectation(Window::OcclusionState expectation) {
-    expectation_ = expectation;
-  }
-
-  bool is_expecting_call() const { return expectation_ != cur_state_; }
-
- private:
-  Window::OcclusionState expectation_ = Window::OcclusionState::UNKNOWN;
-  Window::OcclusionState cur_state_ = Window::OcclusionState::UNKNOWN;
-  base::OnceClosure quit_closure_;
-};
-
-class MockWindowObserver : public WindowObserver {
- public:
-  explicit MockWindowObserver(Window* window) : window_(window) {
-    window_->AddObserver(this);
-  }
-
-  ~MockWindowObserver() override {
-    if (window_)
-      window_->RemoveObserver(this);
-  }
-
-  void set_quit_closure(base::OnceClosure quit_closure) {
-    quit_closure_ = std::move(quit_closure);
-  }
-
-  void set_expectation(Window::OcclusionState expectation) {
-    expectation_ = expectation;
-  }
-
-  // WindowObserver:
-  void OnWindowOcclusionChanged(Window* window) override {
-    if (expectation_ == window->GetOcclusionState()) {
-      ASSERT_FALSE(quit_closure_.is_null());
-      std::move(quit_closure_).Run();
-    }
-  }
-
-  void OnWindowDestroyed(Window* window) override {
-    window_->RemoveObserver(this);
-    window_ = nullptr;
-  }
-
- private:
-  raw_ptr<Window> window_;
-  Window::OcclusionState expectation_ = Window::OcclusionState::UNKNOWN;
-  base::OnceClosure quit_closure_;
-};
-
-// Test wrapper around native window HWND.
-class TestNativeWindow : public gfx::WindowImpl {
- public:
-  TestNativeWindow() {}
-
-  TestNativeWindow(const TestNativeWindow&) = delete;
-  TestNativeWindow& operator=(const TestNativeWindow&) = delete;
-
-  ~TestNativeWindow() override;
-
- private:
-  // Overridden from gfx::WindowImpl:
-  BOOL ProcessWindowMessage(HWND window,
-                            UINT message,
-                            WPARAM w_param,
-                            LPARAM l_param,
-                            LRESULT& result,
-                            DWORD msg_map_id) override {
-    return FALSE;  // Results in DefWindowProc().
-  }
-};
-
-TestNativeWindow::~TestNativeWindow() {
-  if (hwnd())
-    DestroyWindow(hwnd());
-}
-
-class NativeWindowOcclusionTrackerTest : public test::AuraTestBase {
- public:
-  NativeWindowOcclusionTrackerTest() {
-    // These interactive_ui_tests are not based on browser tests which would
-    // normally handle initializing mojo. We can safely initialize mojo at the
-    // start of the test here since a new process is launched for each test.
-    mojo::core::Init();
-  }
-
-  NativeWindowOcclusionTrackerTest(const NativeWindowOcclusionTrackerTest&) =
-      delete;
-  NativeWindowOcclusionTrackerTest& operator=(
-      const NativeWindowOcclusionTrackerTest&) = delete;
-
-  void SetUp() override {
-    if (gl::GetGLImplementation() == gl::kGLImplementationNone)
-      gl::GLSurfaceTestSupport::InitializeOneOff();
-
-    scoped_feature_list_.InitWithFeatures(
-        {features::kCalculateNativeWinOcclusion,
-         features::kApplyNativeOccludedRegionToWindowTracker},
-        {});
-
-    AuraTestBase::SetUp();
-  }
-
-  void SetNativeWindowBounds(HWND hwnd, const gfx::Rect& bounds) {
-    RECT wr = bounds.ToRECT();
-    AdjustWindowRectEx(&wr, GetWindowLong(hwnd, GWL_STYLE), FALSE,
-                       GetWindowLong(hwnd, GWL_EXSTYLE));
-
-    // Make sure to keep the window onscreen, as AdjustWindowRectEx() may have
-    // moved part of it offscreen. But, if the original requested bounds are
-    // offscreen, don't adjust the position.
-    gfx::Rect window_bounds(wr);
-    if (bounds.x() >= 0)
-      window_bounds.set_x(std::max(0, window_bounds.x()));
-    if (bounds.y() >= 0)
-      window_bounds.set_y(std::max(0, window_bounds.y()));
-    SetWindowPos(hwnd, HWND_TOP, window_bounds.x(), window_bounds.y(),
-                 window_bounds.width(), window_bounds.height(),
-                 SWP_NOREPOSITION);
-    EXPECT_TRUE(UpdateWindow(hwnd));
-  }
-
-  HWND CreateNativeWindowWithBounds(const gfx::Rect& bounds) {
-    std::unique_ptr<TestNativeWindow> native_win =
-        std::make_unique<TestNativeWindow>();
-    native_win->set_window_style(WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN);
-    native_win->Init(nullptr, bounds);
-    HWND hwnd = native_win->hwnd();
-    SetNativeWindowBounds(hwnd, bounds);
-    ShowWindow(hwnd, SW_SHOWNORMAL);
-    EXPECT_TRUE(UpdateWindow(hwnd));
-    native_wins_.push_back(std::move(native_win));
-    return hwnd;
-  }
-
-  Window* CreateTrackedAuraWindowWithBounds(
-      MockWindowTreeHostObserver* observer,
-      const gfx::Rect& bounds) {
-    host()->Show();
-    host()->SetBoundsInPixels(bounds);
-    if (observer)
-      host()->AddObserver(observer);
-
-    Window* window = CreateNormalWindow(1, host()->window(), nullptr);
-    window->SetBounds(gfx::Rect(bounds.size()));
-
-    Env::GetInstance()->GetWindowOcclusionTracker()->Track(window);
-    return window;
-  }
-
-  int GetNumVisibleRootWindows() {
-    return NativeWindowOcclusionTrackerWin::GetOrCreateInstance()
-        ->num_visible_root_windows_;
-  }
-
-  void MakeFullscreen(HWND hwnd) {
-    DWORD style = GetWindowLong(hwnd, GWL_STYLE);
-    DWORD ex_style = GetWindowLong(hwnd, GWL_STYLE);
-    SetWindowLong(hwnd, GWL_STYLE, style & ~(WS_CAPTION | WS_THICKFRAME));
-    SetWindowLong(hwnd, GWL_EXSTYLE,
-                  ex_style & ~(WS_EX_DLGMODALFRAME | WS_EX_WINDOWEDGE |
-                               WS_EX_CLIENTEDGE | WS_EX_STATICEDGE));
-    MONITORINFO monitor_info;
-    monitor_info.cbSize = sizeof(monitor_info);
-    GetMonitorInfo(MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST),
-                   &monitor_info);
-    gfx::Rect window_rect(monitor_info.rcMonitor);
-    SetWindowPos(hwnd, nullptr, window_rect.x(), window_rect.y(),
-                 window_rect.width(), window_rect.height(),
-                 SWP_FRAMECHANGED | SWP_ASYNCWINDOWPOS);
-  }
-
- private:
-  base::test::ScopedFeatureList scoped_feature_list_;
-  std::vector<std::unique_ptr<TestNativeWindow>> native_wins_;
-};
-
-// Simple test completely covering an aura window with a native window.
-TEST_F(NativeWindowOcclusionTrackerTest, SimpleOcclusion) {
-  base::RunLoop run_loop;
-
-  MockWindowTreeHostObserver observer(run_loop.QuitClosure());
-  CreateTrackedAuraWindowWithBounds(&observer, gfx::Rect(0, 0, 100, 100));
-  observer.set_expectation(Window::OcclusionState::OCCLUDED);
-  CreateNativeWindowWithBounds(gfx::Rect(0, 0, 100, 100));
-  run_loop.Run();
-  EXPECT_FALSE(observer.is_expecting_call());
-  host()->RemoveObserver(&observer);
-}
-
-// Simple test partially covering an aura window with a native window.
-TEST_F(NativeWindowOcclusionTrackerTest, PartialOcclusion) {
-  base::RunLoop run_loop;
-
-  MockWindowTreeHostObserver observer(run_loop.QuitClosure());
-  CreateTrackedAuraWindowWithBounds(&observer, gfx::Rect(0, 0, 100, 100));
-  observer.set_expectation(Window::OcclusionState::VISIBLE);
-  CreateNativeWindowWithBounds(gfx::Rect(0, 0, 50, 50));
-  run_loop.Run();
-  EXPECT_FALSE(observer.is_expecting_call());
-  host()->RemoveObserver(&observer);
-}
-
-// Simple test that a partly off screen aura window, with the on screen part
-// occluded by a native window, is considered occluded.
-TEST_F(NativeWindowOcclusionTrackerTest, OffscreenOcclusion) {
-  base::RunLoop run_loop;
-
-  MockWindowTreeHostObserver observer(run_loop.QuitClosure());
-  CreateTrackedAuraWindowWithBounds(&observer, gfx::Rect(0, 0, 100, 100));
-
-  // Move the tracked window 50 pixels offscreen to the left.
-  int screen_left = GetSystemMetrics(SM_XVIRTUALSCREEN);
-  SetWindowPos(host()->GetAcceleratedWidget(), HWND_TOP, screen_left - 50, 0,
-               100, 100, SWP_NOZORDER | SWP_NOSIZE);
-
-  // Create a native window that covers the onscreen part of the tracked window.
-  CreateNativeWindowWithBounds(gfx::Rect(screen_left, 0, 50, 100));
-  observer.set_expectation(Window::OcclusionState::OCCLUDED);
-  run_loop.Run();
-  EXPECT_FALSE(observer.is_expecting_call());
-  host()->RemoveObserver(&observer);
-}
-
-// Simple test with an aura window and native window that do not overlap.
-TEST_F(NativeWindowOcclusionTrackerTest, SimpleVisible) {
-  base::RunLoop run_loop;
-  MockWindowTreeHostObserver observer(run_loop.QuitClosure());
-  CreateTrackedAuraWindowWithBounds(&observer, gfx::Rect(0, 0, 100, 100));
-  observer.set_expectation(Window::OcclusionState::VISIBLE);
-  CreateNativeWindowWithBounds(gfx::Rect(200, 0, 100, 100));
-
-  run_loop.Run();
-  EXPECT_FALSE(observer.is_expecting_call());
-  host()->RemoveObserver(&observer);
-}
-
-// Simple test with a minimized aura window and native window.
-TEST_F(NativeWindowOcclusionTrackerTest, SimpleHidden) {
-  base::RunLoop run_loop;
-  MockWindowTreeHostObserver observer(run_loop.QuitClosure());
-  CreateTrackedAuraWindowWithBounds(&observer, gfx::Rect(0, 0, 100, 100));
-  CreateNativeWindowWithBounds(gfx::Rect(200, 0, 100, 100));
-  // Iconify the tracked aura window and check that its occlusion state
-  // is HIDDEN.
-  CloseWindow(host()->GetAcceleratedWidget());
-  observer.set_expectation(Window::OcclusionState::HIDDEN);
-  run_loop.Run();
-  EXPECT_FALSE(observer.is_expecting_call());
-  host()->RemoveObserver(&observer);
-}
-
-// Test that minimizing and restoring an app window results in the occlusion
-// tracker re-registering for win events and detecting that a native window
-// occludes the app window.
-TEST_F(NativeWindowOcclusionTrackerTest, OcclusionAfterVisibilityToggle) {
-  base::RunLoop run_loop;
-  MockWindowTreeHostObserver observer(run_loop.QuitClosure());
-  CreateTrackedAuraWindowWithBounds(&observer, gfx::Rect(0, 0, 100, 100));
-  observer.set_expectation(Window::OcclusionState::VISIBLE);
-  run_loop.Run();
-
-  base::RunLoop run_loop2;
-  observer.set_expectation(Window::OcclusionState::HIDDEN);
-  observer.set_quit_closure(run_loop2.QuitClosure());
-  // host()->window()->Hide() is needed to generate OnWindowVisibilityChanged
-  // notifications.
-  host()->window()->Hide();
-  // This makes the window iconic.
-  ::CloseWindow(host()->GetAcceleratedWidget());
-  run_loop2.Run();
-  // HIDDEN state is set synchronously by OnWindowVsiblityChanged notification,
-  // before occlusion is calculated, so the above expectation will be met w/o an
-  // occlusion calculation.
-  // Loop until an occlusion calculation has run with no non-hidden app windows.
-
-  do {
-    // Need to pump events in order for UpdateOcclusionState to get called, and
-    // update the number of non hidden root windows. When that number is 0,
-    // occlusion has been calculated with no visible root windows.
-    base::RunLoop().RunUntilIdle();
-  } while (GetNumVisibleRootWindows() != 0);
-
-  base::RunLoop run_loop3;
-  observer.set_expectation(Window::OcclusionState::VISIBLE);
-  observer.set_quit_closure(run_loop3.QuitClosure());
-  host()->window()->Show();
-  // This opens the window made iconic above.
-  OpenIcon(host()->GetAcceleratedWidget());
-  run_loop3.Run();
-
-  // Open a native window that occludes the visible app window.
-  base::RunLoop run_loop4;
-  observer.set_expectation(Window::OcclusionState::OCCLUDED);
-  observer.set_quit_closure(run_loop4.QuitClosure());
-  CreateNativeWindowWithBounds(gfx::Rect(0, 0, 100, 100));
-  run_loop4.Run();
-  EXPECT_FALSE(observer.is_expecting_call());
-  host()->RemoveObserver(&observer);
-}
-
-// Test that locking the screen causes visible windows to become occluded.
-TEST_F(NativeWindowOcclusionTrackerTest, LockScreenVisibleOcclusion) {
-  base::RunLoop run_loop;
-
-  MockWindowTreeHostObserver observer(run_loop.QuitClosure());
-  CreateTrackedAuraWindowWithBounds(&observer, gfx::Rect(0, 0, 100, 100));
-  observer.set_expectation(Window::OcclusionState::VISIBLE);
-  run_loop.Run();
-  EXPECT_FALSE(observer.is_expecting_call());
-
-  observer.set_expectation(Window::OcclusionState::OCCLUDED);
-  base::RunLoop run_loop2;
-  observer.set_quit_closure(run_loop2.QuitClosure());
-  // Unfortunately, this relies on knowing that NativeWindowOcclusionTracker
-  // uses SessionChangeObserver to listen for WM_WTSSESSION_CHANGE messages, but
-  // actually locking the screen isn't feasible.
-  DWORD current_session_id = 0;
-  ProcessIdToSessionId(::GetCurrentProcessId(), &current_session_id);
-  PostMessage(gfx::SingletonHwnd::GetInstance()->hwnd(), WM_WTSSESSION_CHANGE,
-              WTS_SESSION_LOCK, current_session_id);
-  run_loop2.Run();
-  EXPECT_FALSE(observer.is_expecting_call());
-  host()->RemoveObserver(&observer);
-}
-
-// Test that locking the screen leaves hidden windows as hidden.
-TEST_F(NativeWindowOcclusionTrackerTest, LockScreenHiddenOcclusion) {
-  base::RunLoop run_loop;
-
-  MockWindowTreeHostObserver observer(run_loop.QuitClosure());
-  CreateTrackedAuraWindowWithBounds(&observer, gfx::Rect(0, 0, 100, 100));
-  // Iconify the tracked aura window and check that its occlusion state
-  // is HIDDEN.
-  CloseWindow(host()->GetAcceleratedWidget());
-  observer.set_expectation(Window::OcclusionState::HIDDEN);
-  run_loop.Run();
-  EXPECT_FALSE(observer.is_expecting_call());
-
-  // Observer only gets notified on occlusion state changes, so force the
-  // state to VISIBLE so that setting the state to hidden will trigger
-  // a notification.
-  host()->SetNativeWindowOcclusionState(Window::OcclusionState::VISIBLE, {});
-
-  observer.set_expectation(Window::OcclusionState::HIDDEN);
-  base::RunLoop run_loop2;
-  observer.set_quit_closure(run_loop2.QuitClosure());
-  // Unfortunately, this relies on knowing that NativeWindowOcclusionTracker
-  // uses SessionChangeObserver to listen for WM_WTSSESSION_CHANGE messages, but
-  // actually locking the screen isn't feasible.
-  DWORD current_session_id = 0;
-  ProcessIdToSessionId(::GetCurrentProcessId(), &current_session_id);
-  PostMessage(gfx::SingletonHwnd::GetInstance()->hwnd(), WM_WTSSESSION_CHANGE,
-              WTS_SESSION_LOCK, current_session_id);
-  run_loop2.Run();
-  EXPECT_FALSE(observer.is_expecting_call());
-  host()->RemoveObserver(&observer);
-}
-
-// Test that locking the screen from a different session doesn't mark window
-// as occluded.
-TEST_F(NativeWindowOcclusionTrackerTest, LockScreenDifferentSession) {
-  base::RunLoop run_loop;
-
-  MockWindowTreeHostObserver observer(run_loop.QuitClosure());
-  CreateTrackedAuraWindowWithBounds(&observer, gfx::Rect(0, 0, 100, 100));
-  observer.set_expectation(Window::OcclusionState::VISIBLE);
-  run_loop.Run();
-  EXPECT_FALSE(observer.is_expecting_call());
-
-  // Observer only gets notified on occlusion state changes, so force the
-  // state to OCCLUDED so that setting the state to VISIBLE will trigger
-  // a notification.
-  host()->SetNativeWindowOcclusionState(Window::OcclusionState::OCCLUDED, {});
-
-  // Generate a session change lock screen with a session id that's not
-  // |current_session_id|.
-  DWORD current_session_id = 0;
-  ProcessIdToSessionId(::GetCurrentProcessId(), &current_session_id);
-  PostMessage(gfx::SingletonHwnd::GetInstance()->hwnd(), WM_WTSSESSION_CHANGE,
-              WTS_SESSION_LOCK, current_session_id + 1);
-
-  observer.set_expectation(Window::OcclusionState::VISIBLE);
-  base::RunLoop run_loop2;
-  observer.set_quit_closure(run_loop2.QuitClosure());
-  // Create a native window to trigger occlusion calculation.
-  CreateNativeWindowWithBounds(gfx::Rect(0, 0, 50, 50));
-  run_loop2.Run();
-  EXPECT_FALSE(observer.is_expecting_call());
-  host()->RemoveObserver(&observer);
-}
-
-// Test that display off & on power state notification causes visible windows to
-// become occluded, then visible.
-TEST_F(NativeWindowOcclusionTrackerTest, DisplayOnOffHandling) {
-  base::RunLoop run_loop;
-
-  MockWindowTreeHostObserver observer(run_loop.QuitClosure());
-  CreateTrackedAuraWindowWithBounds(&observer, gfx::Rect(0, 0, 100, 100));
-  observer.set_expectation(Window::OcclusionState::VISIBLE);
-  run_loop.Run();
-  EXPECT_FALSE(observer.is_expecting_call());
-
-  NativeWindowOcclusionTrackerWin* occlusion_tracker =
-      NativeWindowOcclusionTrackerWin::GetOrCreateInstance();
-
-  observer.set_expectation(Window::OcclusionState::OCCLUDED);
-  base::RunLoop run_loop2;
-  observer.set_quit_closure(run_loop2.QuitClosure());
-
-  // Turning display off and on isn't feasible, so send a notification.
-  occlusion_tracker->OnDisplayStateChanged(/*display_on=*/false);
-  run_loop2.Run();
-  EXPECT_FALSE(observer.is_expecting_call());
-
-  observer.set_expectation(Window::OcclusionState::VISIBLE);
-  base::RunLoop run_loop3;
-  observer.set_quit_closure(run_loop3.QuitClosure());
-  occlusion_tracker->OnDisplayStateChanged(/*display_on=*/true);
-  run_loop3.Run();
-  EXPECT_FALSE(observer.is_expecting_call());
-  host()->RemoveObserver(&observer);
-}
-
-// Verifies that a window is not occluded if the only window occluding it is
-// being moved/dragged.
-//
-// TODO(crbug.com/40801894): Flaky on Windows.
-TEST_F(NativeWindowOcclusionTrackerTest,
-       DISABLED_MovingWindowNotConsideredInCalculations) {
-  // Needed as this test triggers a native nested message loop.
-  base::CurrentThread::ScopedAllowApplicationTasksInNativeNestedLoop
-      allow_nesting;
-
-  // Create the initial window.
-  base::RunLoop run_loop;
-  MockWindowTreeHostObserver observer(run_loop.QuitClosure());
-  CreateTrackedAuraWindowWithBounds(&observer, gfx::Rect(40, 40, 100, 100));
-  observer.set_expectation(Window::OcclusionState::VISIBLE);
-  run_loop.Run();
-  EXPECT_FALSE(observer.is_expecting_call());
-
-  // Creates a new window that obscures the initial window.
-  CreateNativeWindowWithBounds(gfx::Rect(0, 0, 200, 200));
-  observer.set_expectation(Window::OcclusionState::OCCLUDED);
-  base::RunLoop run_loop2;
-  observer.set_quit_closure(run_loop2.QuitClosure());
-  run_loop2.Run();
-  EXPECT_FALSE(observer.is_expecting_call());
-
-  // Start a window move loop. As windows being moved/dragged are not considered
-  // during occlusion calculation, the initial window should become visible.
-  base::RunLoop run_loop3(base::RunLoop::Type::kNestableTasksAllowed);
-  observer.set_expectation(Window::OcclusionState::VISIBLE);
-  observer.set_quit_closure(base::BindLambdaForTesting([&] {
-    // Release the mouse, which should make the initial window occluded.
-    observer.set_expectation(Window::OcclusionState::OCCLUDED);
-    observer.set_quit_closure(run_loop3.QuitClosure());
-    ASSERT_TRUE(
-        ui_controls::SendMouseEvents(ui_controls::LEFT, ui_controls::UP));
-  }));
-  ASSERT_TRUE(ui_controls::SendMouseMove(40, 8));
-  ASSERT_TRUE(
-      ui_controls::SendMouseEvents(ui_controls::LEFT, ui_controls::DOWN));
-  run_loop3.Run();
-  EXPECT_FALSE(observer.is_expecting_call());
-
-  host()->RemoveObserver(&observer);
-}
-
-// Test that a maximized aura window that is covered by a fullscreen window
-// is marked as occluded. TODO(crbug.com/40833493): Fix flakiness.
-TEST_F(NativeWindowOcclusionTrackerTest,
-       DISABLED_MaximizedOccludedByFullscreenWindow) {
-  // Create an aura window that is maximized.
-  base::RunLoop run_loop1;
-  MockWindowTreeHostObserver observer(run_loop1.QuitClosure());
-  HWND hwnd_aura_window_maximized =
-      CreateTrackedAuraWindowWithBounds(&observer, gfx::Rect(0, 0, 100, 100))
-          ->GetHost()
-          ->GetAcceleratedWidget();
-  ShowWindow(hwnd_aura_window_maximized, SW_SHOWMAXIMIZED);
-  observer.set_expectation(Window::OcclusionState::VISIBLE);
-  run_loop1.Run();
-  EXPECT_FALSE(observer.is_expecting_call());
-  // Create a fullscreen native window that occludes the aura window.
-  base::RunLoop run_loop2;
-  observer.set_quit_closure(run_loop2.QuitClosure());
-  observer.set_expectation(Window::OcclusionState::OCCLUDED);
-  HWND hwnd_native_window =
-      CreateNativeWindowWithBounds(gfx::Rect(0, 0, 100, 100));
-  MakeFullscreen(hwnd_native_window);
-  run_loop2.Run();
-  EXPECT_FALSE(observer.is_expecting_call());
-  host()->RemoveObserver(&observer);
-}
-
-TEST_F(NativeWindowOcclusionTrackerTest, OccludedRegionSimple) {
-  Window* tracked_aura_window =
-      CreateTrackedAuraWindowWithBounds(nullptr, gfx::Rect(20, 20, 200, 200));
-  tracked_aura_window->SetBounds(gfx::Rect(0, 0, 60, 60));
-
-  MockWindowObserver observer(tracked_aura_window);
-  base::RunLoop run_loop;
-  observer.set_expectation(Window::OcclusionState::OCCLUDED);
-  observer.set_quit_closure(run_loop.QuitClosure());
-  HWND obscuring_hwnd =
-      CreateNativeWindowWithBounds(gfx::Rect(20, 20, 110, 110));
-  run_loop.Run();
-  EXPECT_EQ(Window::OcclusionState::OCCLUDED,
-            tracked_aura_window->GetOcclusionState());
-
-  base::RunLoop run_loop2;
-  observer.set_expectation(Window::OcclusionState::VISIBLE);
-  observer.set_quit_closure(run_loop2.QuitClosure());
-  tracked_aura_window->SetBounds(gfx::Rect(160, 160, 20, 20));
-  run_loop2.Run();
-  EXPECT_EQ(Window::OcclusionState::VISIBLE,
-            tracked_aura_window->GetOcclusionState());
-
-  base::RunLoop run_loop3;
-  observer.set_expectation(Window::OcclusionState::OCCLUDED);
-  observer.set_quit_closure(run_loop3.QuitClosure());
-  SetNativeWindowBounds(obscuring_hwnd, gfx::Rect(140, 140, 110, 110));
-  run_loop3.Run();
-  EXPECT_EQ(Window::OcclusionState::OCCLUDED,
-            tracked_aura_window->GetOcclusionState());
-}
-
-TEST_F(NativeWindowOcclusionTrackerTest, OccludedRegionComplex) {
-  Window* tracked_aura_window =
-      CreateTrackedAuraWindowWithBounds(nullptr, gfx::Rect(20, 20, 200, 200));
-  tracked_aura_window->SetBounds(gfx::Rect(0, 0, 60, 60));
-
-  MockWindowObserver observer(tracked_aura_window);
-  base::RunLoop run_loop;
-  observer.set_expectation(Window::OcclusionState::OCCLUDED);
-  observer.set_quit_closure(run_loop.QuitClosure());
-  CreateNativeWindowWithBounds(gfx::Rect(20, 20, 110, 110));
-  run_loop.Run();
-  EXPECT_EQ(Window::OcclusionState::OCCLUDED,
-            tracked_aura_window->GetOcclusionState());
-
-  base::RunLoop run_loop2;
-  observer.set_expectation(Window::OcclusionState::VISIBLE);
-  observer.set_quit_closure(run_loop2.QuitClosure());
-  tracked_aura_window->SetBounds(gfx::Rect(160, 160, 20, 20));
-  run_loop2.Run();
-  EXPECT_EQ(Window::OcclusionState::VISIBLE,
-            tracked_aura_window->GetOcclusionState());
-
-  base::RunLoop run_loop3;
-  observer.set_expectation(Window::OcclusionState::OCCLUDED);
-  observer.set_quit_closure(run_loop3.QuitClosure());
-  CreateNativeWindowWithBounds(gfx::Rect(140, 140, 110, 110));
-  run_loop3.Run();
-  EXPECT_EQ(Window::OcclusionState::OCCLUDED,
-            tracked_aura_window->GetOcclusionState());
-}
-
-class NativeWindowOcclusionTrackerTestWithDpi2
-    : public NativeWindowOcclusionTrackerTest {
- public:
-  // NativeWindowOcclusionTrackerTest:
-  void SetUp() override {
-    display::Display::SetForceDeviceScaleFactor(2.0);
-    NativeWindowOcclusionTrackerTest::SetUp();
-  }
-};
-
-TEST_F(NativeWindowOcclusionTrackerTestWithDpi2, OccludedRegionSimple) {
-  Window* tracked_aura_window =
-      CreateTrackedAuraWindowWithBounds(nullptr, gfx::Rect(20, 20, 200, 200));
-  tracked_aura_window->SetBounds(gfx::Rect(0, 0, 30, 30));
-
-  MockWindowObserver observer(tracked_aura_window);
-  base::RunLoop run_loop;
-  observer.set_expectation(Window::OcclusionState::OCCLUDED);
-  observer.set_quit_closure(run_loop.QuitClosure());
-  HWND obscuring_hwnd =
-      CreateNativeWindowWithBounds(gfx::Rect(20, 20, 110, 110));
-  run_loop.Run();
-  EXPECT_EQ(Window::OcclusionState::OCCLUDED,
-            tracked_aura_window->GetOcclusionState());
-
-  base::RunLoop run_loop2;
-  observer.set_expectation(Window::OcclusionState::VISIBLE);
-  observer.set_quit_closure(run_loop2.QuitClosure());
-  tracked_aura_window->SetBounds(gfx::Rect(80, 80, 20, 20));
-  run_loop2.Run();
-  EXPECT_EQ(Window::OcclusionState::VISIBLE,
-            tracked_aura_window->GetOcclusionState());
-
-  base::RunLoop run_loop3;
-  observer.set_expectation(Window::OcclusionState::OCCLUDED);
-  observer.set_quit_closure(run_loop3.QuitClosure());
-  SetNativeWindowBounds(obscuring_hwnd, gfx::Rect(140, 140, 110, 110));
-  run_loop3.Run();
-  EXPECT_EQ(Window::OcclusionState::OCCLUDED,
-            tracked_aura_window->GetOcclusionState());
-}
-
-}  // namespace aura
diff --git a/ui/base/ui_base_features.cc b/ui/base/ui_base_features.cc
index d919cb7..f9dd490 100644
--- a/ui/base/ui_base_features.cc
+++ b/ui/base/ui_base_features.cc
@@ -22,11 +22,6 @@
 namespace features {
 
 #if BUILDFLAG(IS_WIN)
-// If enabled, the occluded region of the HWND is supplied to WindowTracker.
-BASE_FEATURE(kApplyNativeOccludedRegionToWindowTracker,
-             "ApplyNativeOccludedRegionToWindowTracker",
-             base::FEATURE_DISABLED_BY_DEFAULT);
-
 // If enabled, calculate native window occlusion - Windows-only.
 BASE_FEATURE(kCalculateNativeWinOcclusion,
              "CalculateNativeWinOcclusion",
@@ -148,15 +143,9 @@
 COMPONENT_EXPORT(UI_BASE_FEATURES)
 BASE_FEATURE(kOverrideDefaultOzonePlatformHintToAuto,
              "OverrideDefaultOzonePlatformHintToAuto",
-             base::FEATURE_DISABLED_BY_DEFAULT);
+             base::FEATURE_ENABLED_BY_DEFAULT);
 #endif  // BUILDFLAG(IS_LINUX)
 
-// Update of the virtual keyboard settings UI as described in
-// https://crbug.com/876901.
-BASE_FEATURE(kInputMethodSettingsUiUpdate,
-             "InputMethodSettingsUiUpdate",
-             base::FEATURE_DISABLED_BY_DEFAULT);
-
 // Uses a stylus-specific tap slop region parameter for gestures.  Stylus taps
 // tend to slip more than touch taps (presumably because the user doesn't feel
 // the movement friction with a stylus).  As a result, it is harder to tap with
diff --git a/ui/base/ui_base_features.h b/ui/base/ui_base_features.h
index 74d5288..e4c74a5 100644
--- a/ui/base/ui_base_features.h
+++ b/ui/base/ui_base_features.h
@@ -23,8 +23,6 @@
 COMPONENT_EXPORT(UI_BASE_FEATURES)
 BASE_DECLARE_FEATURE(kSettingsShowsPerKeyboardSettings);
 #endif  // BUILDFLAG(IS_CHROMEOS)
-COMPONENT_EXPORT(UI_BASE_FEATURES)
-BASE_DECLARE_FEATURE(kInputMethodSettingsUiUpdate);
 COMPONENT_EXPORT(UI_BASE_FEATURES) BASE_DECLARE_FEATURE(kSystemKeyboardLock);
 COMPONENT_EXPORT(UI_BASE_FEATURES)
 BASE_DECLARE_FEATURE(kUiCompositorScrollWithLayers);
@@ -39,8 +37,6 @@
 
 #if BUILDFLAG(IS_WIN)
 COMPONENT_EXPORT(UI_BASE_FEATURES)
-BASE_DECLARE_FEATURE(kApplyNativeOccludedRegionToWindowTracker);
-COMPONENT_EXPORT(UI_BASE_FEATURES)
 BASE_DECLARE_FEATURE(kCalculateNativeWinOcclusion);
 
 COMPONENT_EXPORT(UI_BASE_FEATURES)
diff --git a/ui/events/BUILD.gn b/ui/events/BUILD.gn
index c637faf..48c7604 100644
--- a/ui/events/BUILD.gn
+++ b/ui/events/BUILD.gn
@@ -401,6 +401,7 @@
       "android/gesture_event_type.h",
       "android/key_event_utils.h",
       "android/motion_event_android.h",
+      "android/motion_event_android_factory.h",
       "android/motion_event_android_java.h",
       "android/motion_event_android_native.h",
 
@@ -416,6 +417,7 @@
       "android/gesture_event_android.cc",
       "android/key_event_utils.cc",
       "android/motion_event_android.cc",
+      "android/motion_event_android_factory.cc",
       "android/motion_event_android_java.cc",
       "android/motion_event_android_native.cc",
       "back_gesture_event.cc",
diff --git a/ui/events/android/motion_event_android.cc b/ui/events/android/motion_event_android.cc
index dfa855cc..9cf536d 100644
--- a/ui/events/android/motion_event_android.cc
+++ b/ui/events/android/motion_event_android.cc
@@ -314,6 +314,60 @@
   return pixels * pix_to_dip_;
 }
 
+bool MotionEventAndroid::IsPointerCacheable(size_t pointer_index) const {
+  return pointer_index < MAX_POINTERS_TO_CACHE;
+}
+
+int MotionEventAndroid::GetCachedPointerId(size_t pointer_index) const {
+  CHECK(IsPointerCacheable(pointer_index));
+  return cached_pointers_[pointer_index].id;
+}
+
+const gfx::PointF& MotionEventAndroid::GetCachedPointerPosition(
+    size_t pointer_index) const {
+  CHECK(IsPointerCacheable(pointer_index));
+  return cached_pointers_[pointer_index].position;
+}
+
+float MotionEventAndroid::GetCachedPointerTouchMajor(
+    size_t pointer_index) const {
+  CHECK(IsPointerCacheable(pointer_index));
+  return cached_pointers_[pointer_index].touch_major;
+}
+
+float MotionEventAndroid::GetCachedPointerTouchMinor(
+    size_t pointer_index) const {
+  CHECK(IsPointerCacheable(pointer_index));
+  return cached_pointers_[pointer_index].touch_minor;
+}
+
+float MotionEventAndroid::GetCachedPointerPressure(size_t pointer_index) const {
+  CHECK(IsPointerCacheable(pointer_index));
+  return cached_pointers_[pointer_index].pressure;
+}
+
+float MotionEventAndroid::GetCachedPointerOrientation(
+    size_t pointer_index) const {
+  CHECK(IsPointerCacheable(pointer_index));
+  return cached_pointers_[pointer_index].orientation;
+}
+
+float MotionEventAndroid::GetCachedPointerTiltX(size_t pointer_index) const {
+  CHECK(IsPointerCacheable(pointer_index));
+  return cached_pointers_[pointer_index].tilt_x;
+}
+
+float MotionEventAndroid::GetCachedPointerTiltY(size_t pointer_index) const {
+  CHECK(IsPointerCacheable(pointer_index));
+  return cached_pointers_[pointer_index].tilt_y;
+}
+
+MotionEvent::ToolType MotionEventAndroid::GetCachedPointerToolType(
+    size_t pointer_index) const {
+  CHECK(IsPointerCacheable(pointer_index));
+  return cached_pointers_[pointer_index].tool_type;
+}
+
 #define TOOL_TYPE_CASE(x)              \
   case JNI_MotionEvent::TOOL_TYPE_##x: \
     return MotionEventAndroid::ToolType::x
diff --git a/ui/events/android/motion_event_android.h b/ui/events/android/motion_event_android.h
index bdce13bf..7b47d15 100644
--- a/ui/events/android/motion_event_android.h
+++ b/ui/events/android/motion_event_android.h
@@ -26,6 +26,12 @@
 // while all *output* coordinates are in DIPs (as with WebTouchEvent).
 class EVENTS_EXPORT MotionEventAndroid : public MotionEvent {
  public:
+  // A struct to hold the oldest and latest event times.
+  struct EventTimes {
+    base::TimeTicks oldest;
+    base::TimeTicks latest;
+  };
+
   // Returns the motion event action defined in Java layer for a given
   // MotionEvent::Action.
   static int GetAndroidAction(Action action);
@@ -133,8 +139,44 @@
   // redundant JNI fetches for the same bits.
   enum { MAX_POINTERS_TO_CACHE = 2 };
 
+  // Returns true if the pointer at `pointer_index` is cached and its data
+  // should be retrieved from the cache.
+  bool IsPointerCacheable(size_t pointer_index) const;
+
+  // Returns the id of the pointer at `pointer_index` from the cache.
+  int GetCachedPointerId(size_t pointer_index) const;
+
+  // Returns the position of the pointer at `pointer_index` from the cache.
+  const gfx::PointF& GetCachedPointerPosition(size_t pointer_index) const;
+
+  // Returns the touch major/minor of the pointer at `pointer_index` from the
+  // cache.
+  float GetCachedPointerTouchMajor(size_t pointer_index) const;
+  float GetCachedPointerTouchMinor(size_t pointer_index) const;
+
+  // Returns the pressure/orientation of the pointer at `pointer_index` from the
+  // cache.
+  float GetCachedPointerPressure(size_t pointer_index) const;
+  float GetCachedPointerOrientation(size_t pointer_index) const;
+
+  // Returns the tilt of the pointer at `pointer_index` from the cache.
+  float GetCachedPointerTiltX(size_t pointer_index) const;
+  float GetCachedPointerTiltY(size_t pointer_index) const;
+
+  // Returns the tool type of the pointer at `pointer_index` from the cache.
+  ToolType GetCachedPointerToolType(size_t pointer_index) const;
+
   MotionEventAndroid(const MotionEventAndroid& e, const gfx::PointF& point);
 
+  static ToolType FromAndroidToolType(int android_tool_type);
+  static base::TimeTicks FromAndroidTime(base::TimeTicks time);
+  static float ToValidFloat(float x);
+  static void ConvertTiltOrientationToTiltXY(float tilt_rad,
+                                             float orientation_rad,
+                                             float* tilt_x,
+                                             float* tilt_y);
+
+ private:
   struct CachedPointer {
     CachedPointer();
     int id = 0;
@@ -150,15 +192,6 @@
 
   std::array<CachedPointer, MAX_POINTERS_TO_CACHE> cached_pointers_;
 
-  static ToolType FromAndroidToolType(int android_tool_type);
-  static base::TimeTicks FromAndroidTime(base::TimeTicks time);
-  static float ToValidFloat(float x);
-  static void ConvertTiltOrientationToTiltXY(float tilt_rad,
-                                             float orientation_rad,
-                                             float* tilt_x,
-                                             float* tilt_y);
-
- private:
   CachedPointer FromAndroidPointer(const Pointer& pointer) const;
   CachedPointer CreateCachedPointer(const CachedPointer& pointer,
                                     const gfx::PointF& point) const;
diff --git a/ui/events/android/motion_event_android_factory.cc b/ui/events/android/motion_event_android_factory.cc
new file mode 100644
index 0000000..2c98452
--- /dev/null
+++ b/ui/events/android/motion_event_android_factory.cc
@@ -0,0 +1,168 @@
+// 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 "ui/events/android/motion_event_android_factory.h"
+
+#include <android/input.h>
+
+#include "base/memory/ptr_util.h"
+#include "ui/events/android/motion_event_android_java.h"
+#include "ui/events/android/motion_event_android_native.h"
+
+namespace ui {
+
+// static
+std::unique_ptr<MotionEventAndroid> MotionEventAndroidFactory::CreateFromJava(
+    JNIEnv* env,
+    const base::android::JavaRef<jobject>& event,
+    jfloat pix_to_dip,
+    jfloat ticks_x,
+    jfloat ticks_y,
+    jfloat tick_multiplier,
+    base::TimeTicks oldest_event_time,
+    jint android_action,
+    jint pointer_count,
+    jint history_size,
+    jint action_index,
+    jint android_action_button,
+    jint android_gesture_classification,
+    jint android_button_state,
+    jfloat raw_offset_x_pixels,
+    jfloat raw_offset_y_pixels,
+    jboolean for_touch_handle,
+    const MotionEventAndroid::Pointer* const pointer0,
+    const MotionEventAndroid::Pointer* const pointer1) {
+  return base::WrapUnique<MotionEventAndroid>(new MotionEventAndroidJava(
+      env, event, pix_to_dip, ticks_x, ticks_y, tick_multiplier,
+      oldest_event_time, android_action, pointer_count, history_size,
+      action_index, android_action_button, android_gesture_classification,
+      android_button_state, raw_offset_x_pixels, raw_offset_y_pixels,
+      for_touch_handle, pointer0, pointer1));
+}
+
+// static
+std::unique_ptr<MotionEventAndroid> MotionEventAndroidFactory::CreateFromJava(
+    JNIEnv* env,
+    const base::android::JavaRef<jobject>& event,
+    jfloat pix_to_dip,
+    jfloat ticks_x,
+    jfloat ticks_y,
+    jfloat tick_multiplier,
+    base::TimeTicks oldest_event_time,
+    base::TimeTicks latest_event_time,
+    base::TimeTicks down_time_ms,
+    jint android_action,
+    jint pointer_count,
+    jint history_size,
+    jint action_index,
+    jint android_action_button,
+    jint android_gesture_classification,
+    jint android_button_state,
+    jfloat raw_offset_x_pixels,
+    jfloat raw_offset_y_pixels,
+    jboolean for_touch_handle,
+    const MotionEventAndroid::Pointer* const pointer0,
+    const MotionEventAndroid::Pointer* const pointer1,
+    bool is_latest_event_time_resampled) {
+  return base::WrapUnique<MotionEventAndroid>(new MotionEventAndroidJava(
+      env, event, pix_to_dip, ticks_x, ticks_y, tick_multiplier,
+      oldest_event_time, latest_event_time, down_time_ms, android_action,
+      pointer_count, history_size, action_index, android_action_button,
+      android_gesture_classification, android_button_state, raw_offset_x_pixels,
+      raw_offset_y_pixels, for_touch_handle, pointer0, pointer1,
+      is_latest_event_time_resampled));
+}
+
+// static
+std::unique_ptr<MotionEventAndroid> MotionEventAndroidFactory::CreateFromNative(
+    base::android::ScopedInputEvent input_event,
+    float pix_to_dip,
+    float y_offset_pix,
+    std::optional<MotionEventAndroid::EventTimes> event_times) {
+  const AInputEvent* event = input_event.a_input_event();
+
+  CHECK(event != nullptr);
+  CHECK(AInputEvent_getType(event) == AINPUT_EVENT_TYPE_MOTION);
+
+  const size_t history_size = AMotionEvent_getHistorySize(event);
+  // AMotionEvent_getEventTime and AMotionEvent_getHistoricalEventTime returns
+  // the time with nanoseconds precision.
+  if (!event_times) {
+    event_times = MotionEventAndroid::EventTimes();
+    event_times->latest =
+        base::TimeTicks::FromJavaNanoTime(AMotionEvent_getEventTime(event));
+    event_times->oldest =
+        (history_size == 0)
+            ? event_times->latest
+            : base::TimeTicks::FromJavaNanoTime(
+                  AMotionEvent_getHistoricalEventTime(event,
+                                                      /*history_index=*/0));
+  }
+  const jlong down_time_ms =
+      base::TimeTicks::FromJavaNanoTime(AMotionEvent_getDownTime(event))
+          .ToUptimeMillis();
+  // Native side doesn't have MotionEvent.getActionMasked() or
+  // MotionEvent.getActionIndex counterparts.
+  const int action = AMotionEvent_getAction(event);
+  const int masked_action = action & AMOTION_EVENT_ACTION_MASK;
+  const int action_index = (action & AMOTION_EVENT_ACTION_POINTER_INDEX_MASK) >>
+                           AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT;
+
+  const size_t pointer_count = AMotionEvent_getPointerCount(event);
+
+  const std::unique_ptr<ui::MotionEventAndroid::Pointer> pointer0 =
+      std::make_unique<ui::MotionEventAndroid::Pointer>(
+          /*id=*/AMotionEvent_getPointerId(event, 0),
+          /*pos_x_pixels=*/AMotionEvent_getX(event, 0),
+          /*pos_y_pixels=*/AMotionEvent_getY(event, 0) + y_offset_pix,
+          /*touch_major_pixels=*/AMotionEvent_getTouchMajor(event, 0),
+          /*touch_minor_pixels=*/AMotionEvent_getTouchMinor(event, 0),
+          /*pressure=*/AMotionEvent_getPressure(event, 0),
+          /*orienation_rad=*/AMotionEvent_getOrientation(event, 0),
+          /*tilt_rad=*/
+          AMotionEvent_getAxisValue(event, AMOTION_EVENT_AXIS_TILT, 0),
+          /*tool_type=*/AMotionEvent_getToolType(event, 0));
+
+  std::unique_ptr<ui::MotionEventAndroid::Pointer> pointer1 = nullptr;
+  if (pointer_count > 1) {
+    pointer1 = std::make_unique<ui::MotionEventAndroid::Pointer>(
+        /*id=*/AMotionEvent_getPointerId(event, 1),
+        /*pos_x_pixels=*/AMotionEvent_getX(event, 1),
+        /*pos_y_pixels=*/AMotionEvent_getY(event, 1) + y_offset_pix,
+        /*touch_major_pixels=*/AMotionEvent_getTouchMajor(event, 1),
+        /*touch_minor_pixels=*/AMotionEvent_getTouchMinor(event, 1),
+        /*pressure=*/AMotionEvent_getPressure(event, 1),
+        /*orientation_rad=*/AMotionEvent_getOrientation(event, 1),
+        /*tilt_rad=*/
+        AMotionEvent_getAxisValue(event, AMOTION_EVENT_AXIS_TILT, 1),
+        /*tool_type=*/AMotionEvent_getToolType(event, 1));
+  }
+
+  // TODO(crbug.com/373345667): Move this and other duplicate calculations to
+  // base class.
+  const float raw_offset_x_pixels =
+      AMotionEvent_getRawX(event, 0) - pointer0->pos_x_pixels;
+  const float raw_offset_y_pixels =
+      AMotionEvent_getRawY(event, 0) - pointer0->pos_y_pixels;
+
+  int gesture_classification = 0;
+  if (__builtin_available(android 33, *)) {
+    gesture_classification = AMotionEvent_getClassification(event);
+  }
+
+  return base::WrapUnique<MotionEventAndroid>(new MotionEventAndroidNative(
+      std::move(input_event), pix_to_dip,
+      /*ticks_x=*/0.f,
+      /*ticks_y=*/0.f,
+      /*tick_multiplier=*/0.f, event_times->oldest, event_times->latest,
+      base::TimeTicks::FromUptimeMillis(down_time_ms), masked_action,
+      pointer_count, history_size, action_index,
+      /*android_action_button=*/0, gesture_classification,
+      AMotionEvent_getButtonState(event), AMotionEvent_getMetaState(event),
+      raw_offset_x_pixels, raw_offset_y_pixels,
+      /*for_touch_handle=*/false, pointer0.get(), pointer1.get(),
+      y_offset_pix));
+}
+
+}  // namespace ui
diff --git a/ui/events/android/motion_event_android_factory.h b/ui/events/android/motion_event_android_factory.h
new file mode 100644
index 0000000..67326c58
--- /dev/null
+++ b/ui/events/android/motion_event_android_factory.h
@@ -0,0 +1,78 @@
+// 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 UI_EVENTS_ANDROID_MOTION_EVENT_ANDROID_FACTORY_H_
+#define UI_EVENTS_ANDROID_MOTION_EVENT_ANDROID_FACTORY_H_
+
+#include <jni.h>
+
+#include <memory>
+
+#include "base/android/scoped_input_event.h"
+#include "base/android/scoped_java_ref.h"
+#include "base/time/time.h"
+#include "ui/events/android/motion_event_android.h"
+#include "ui/events/events_export.h"
+
+namespace ui {
+
+// Factory class for creating instances of MotionEventAndroid.
+class EVENTS_EXPORT MotionEventAndroidFactory {
+ public:
+  // Creates a MotionEventAndroid from a Java MotionEvent object.
+  static std::unique_ptr<MotionEventAndroid> CreateFromJava(
+      JNIEnv* env,
+      const base::android::JavaRef<jobject>& event,
+      jfloat pix_to_dip,
+      jfloat ticks_x,
+      jfloat ticks_y,
+      jfloat tick_multiplier,
+      base::TimeTicks oldest_event_time,
+      jint android_action,
+      jint pointer_count,
+      jint history_size,
+      jint action_index,
+      jint android_action_button,
+      jint android_gesture_classification,
+      jint android_button_state,
+      jfloat raw_offset_x_pixels,
+      jfloat raw_offset_y_pixels,
+      jboolean for_touch_handle,
+      const MotionEventAndroid::Pointer* const pointer0,
+      const MotionEventAndroid::Pointer* const pointer1);
+
+  static std::unique_ptr<MotionEventAndroid> CreateFromJava(
+      JNIEnv* env,
+      const base::android::JavaRef<jobject>& event,
+      jfloat pix_to_dip,
+      jfloat ticks_x,
+      jfloat ticks_y,
+      jfloat tick_multiplier,
+      base::TimeTicks oldest_event_time,
+      base::TimeTicks latest_event_time,
+      base::TimeTicks down_time_ms,
+      jint android_action,
+      jint pointer_count,
+      jint history_size,
+      jint action_index,
+      jint android_action_button,
+      jint android_gesture_classification,
+      jint android_button_state,
+      jfloat raw_offset_x_pixels,
+      jfloat raw_offset_y_pixels,
+      jboolean for_touch_handle,
+      const MotionEventAndroid::Pointer* const pointer0,
+      const MotionEventAndroid::Pointer* const pointer1,
+      bool is_latest_event_time_resampled);
+
+  static std::unique_ptr<MotionEventAndroid> CreateFromNative(
+      base::android::ScopedInputEvent input_event,
+      float pix_to_dip,
+      float y_offset_pix,
+      std::optional<MotionEventAndroid::EventTimes> event_times);
+};
+
+}  // namespace ui
+
+#endif  // UI_EVENTS_ANDROID_MOTION_EVENT_ANDROID_FACTORY_H_
diff --git a/ui/events/android/motion_event_android_java.cc b/ui/events/android/motion_event_android_java.cc
index 5872c91..c99089b8 100644
--- a/ui/events/android/motion_event_android_java.cc
+++ b/ui/events/android/motion_event_android_java.cc
@@ -11,6 +11,7 @@
 #include "base/android/jni_android.h"
 #include "base/android/scoped_java_ref.h"
 #include "base/feature_list.h"
+#include "base/memory/ptr_util.h"
 #include "base/notreached.h"
 #include "base/numerics/angle_conversions.h"
 #include "ui/base/ui_base_features.h"
@@ -129,9 +130,7 @@
 
 std::unique_ptr<MotionEventAndroid> MotionEventAndroidJava::CreateFor(
     const gfx::PointF& point) const {
-  std::unique_ptr<MotionEventAndroid> event(
-      new MotionEventAndroidJava(*this, point));
-  return event;
+  return base::WrapUnique(new MotionEventAndroidJava(*this, point));
 }
 
 MotionEventAndroidJava::~MotionEventAndroidJava() = default;
@@ -142,8 +141,8 @@
 
 int MotionEventAndroidJava::GetPointerId(size_t pointer_index) const {
   DCHECK_LT(pointer_index, GetPointerCount());
-  if (pointer_index < MAX_POINTERS_TO_CACHE) {
-    return cached_pointers_[pointer_index].id;
+  if (IsPointerCacheable(pointer_index)) {
+    return GetCachedPointerId(pointer_index);
   }
   return JNI_MotionEvent::Java_MotionEvent_getPointerId(AttachCurrentThread(),
                                                         event_, pointer_index);
@@ -151,8 +150,8 @@
 
 float MotionEventAndroidJava::GetX(size_t pointer_index) const {
   DCHECK_LT(pointer_index, GetPointerCount());
-  if (pointer_index < MAX_POINTERS_TO_CACHE) {
-    return cached_pointers_[pointer_index].position.x();
+  if (IsPointerCacheable(pointer_index)) {
+    return GetCachedPointerPosition(pointer_index).x();
   }
   return ToDips(JNI_MotionEvent::Java_MotionEvent_getX(AttachCurrentThread(),
                                                        event_, pointer_index));
@@ -160,8 +159,8 @@
 
 float MotionEventAndroidJava::GetY(size_t pointer_index) const {
   DCHECK_LT(pointer_index, GetPointerCount());
-  if (pointer_index < MAX_POINTERS_TO_CACHE) {
-    return cached_pointers_[pointer_index].position.y();
+  if (IsPointerCacheable(pointer_index)) {
+    return GetCachedPointerPosition(pointer_index).y();
   }
   return ToDips(JNI_MotionEvent::Java_MotionEvent_getY(AttachCurrentThread(),
                                                        event_, pointer_index));
@@ -169,8 +168,8 @@
 
 float MotionEventAndroidJava::GetXPix(size_t pointer_index) const {
   DCHECK_LT(pointer_index, GetPointerCount());
-  if (pointer_index < MAX_POINTERS_TO_CACHE) {
-    return cached_pointers_[pointer_index].position.x() / pix_to_dip();
+  if (IsPointerCacheable(pointer_index)) {
+    return GetCachedPointerPosition(pointer_index).x() / pix_to_dip();
   }
   return JNI_MotionEvent::Java_MotionEvent_getX(AttachCurrentThread(), event_,
                                                 pointer_index);
@@ -178,8 +177,8 @@
 
 float MotionEventAndroidJava::GetYPix(size_t pointer_index) const {
   DCHECK_LT(pointer_index, GetPointerCount());
-  if (pointer_index < MAX_POINTERS_TO_CACHE) {
-    return cached_pointers_[pointer_index].position.y() / pix_to_dip();
+  if (IsPointerCacheable(pointer_index)) {
+    return GetCachedPointerPosition(pointer_index).y() / pix_to_dip();
   }
   return JNI_MotionEvent::Java_MotionEvent_getY(AttachCurrentThread(), event_,
                                                 pointer_index);
@@ -192,8 +191,8 @@
 
 float MotionEventAndroidJava::GetTouchMajor(size_t pointer_index) const {
   DCHECK_LT(pointer_index, GetPointerCount());
-  if (pointer_index < MAX_POINTERS_TO_CACHE) {
-    return cached_pointers_[pointer_index].touch_major;
+  if (IsPointerCacheable(pointer_index)) {
+    return GetCachedPointerTouchMajor(pointer_index);
   }
   return ToDips(JNI_MotionEvent::Java_MotionEvent_getTouchMajor(
       AttachCurrentThread(), event_, pointer_index));
@@ -201,8 +200,8 @@
 
 float MotionEventAndroidJava::GetTouchMinor(size_t pointer_index) const {
   DCHECK_LT(pointer_index, GetPointerCount());
-  if (pointer_index < MAX_POINTERS_TO_CACHE) {
-    return cached_pointers_[pointer_index].touch_minor;
+  if (IsPointerCacheable(pointer_index)) {
+    return GetCachedPointerTouchMinor(pointer_index);
   }
   return ToDips(JNI_MotionEvent::Java_MotionEvent_getTouchMinor(
       AttachCurrentThread(), event_, pointer_index));
@@ -210,8 +209,8 @@
 
 float MotionEventAndroidJava::GetOrientation(size_t pointer_index) const {
   DCHECK_LT(pointer_index, GetPointerCount());
-  if (pointer_index < MAX_POINTERS_TO_CACHE) {
-    return cached_pointers_[pointer_index].orientation;
+  if (IsPointerCacheable(pointer_index)) {
+    return GetCachedPointerOrientation(pointer_index);
   }
   return MotionEventAndroid::ToValidFloat(
       JNI_MotionEvent::Java_MotionEvent_getOrientation(AttachCurrentThread(),
@@ -220,8 +219,8 @@
 
 float MotionEventAndroidJava::GetPressure(size_t pointer_index) const {
   DCHECK_LT(pointer_index, GetPointerCount());
-  if (pointer_index < MAX_POINTERS_TO_CACHE) {
-    return cached_pointers_[pointer_index].pressure;
+  if (IsPointerCacheable(pointer_index)) {
+    return GetCachedPointerPressure(pointer_index);
   }
   return JNI_MotionEvent::Java_MotionEvent_getPressure(AttachCurrentThread(),
                                                        event_, pointer_index);
@@ -229,8 +228,8 @@
 
 float MotionEventAndroidJava::GetTiltX(size_t pointer_index) const {
   DCHECK_LT(pointer_index, GetPointerCount());
-  if (pointer_index < MAX_POINTERS_TO_CACHE) {
-    return cached_pointers_[pointer_index].tilt_x;
+  if (IsPointerCacheable(pointer_index)) {
+    return GetCachedPointerTiltX(pointer_index);
   }
   if (!event_.obj()) {
     return 0.f;
@@ -248,8 +247,8 @@
 
 float MotionEventAndroidJava::GetTiltY(size_t pointer_index) const {
   DCHECK_LT(pointer_index, GetPointerCount());
-  if (pointer_index < MAX_POINTERS_TO_CACHE) {
-    return cached_pointers_[pointer_index].tilt_y;
+  if (IsPointerCacheable(pointer_index)) {
+    return GetCachedPointerTiltY(pointer_index);
   }
   if (!event_.obj()) {
     return 0.f;
@@ -296,8 +295,8 @@
 ui::MotionEvent::ToolType MotionEventAndroidJava::GetToolType(
     size_t pointer_index) const {
   DCHECK_LT(pointer_index, GetPointerCount());
-  if (pointer_index < MAX_POINTERS_TO_CACHE) {
-    return cached_pointers_[pointer_index].tool_type;
+  if (IsPointerCacheable(pointer_index)) {
+    return GetCachedPointerToolType(pointer_index);
   }
   return MotionEventAndroid::FromAndroidToolType(
       JNI_MotionEvent::Java_MotionEvent_getToolType(AttachCurrentThread(),
diff --git a/ui/events/android/motion_event_android_java.h b/ui/events/android/motion_event_android_java.h
index 05352480..d2ad18f 100644
--- a/ui/events/android/motion_event_android_java.h
+++ b/ui/events/android/motion_event_android_java.h
@@ -20,11 +20,51 @@
 
 namespace ui {
 
+class MotionEventAndroidFactory;
+
 // Implementation of ui::MotionEventAndroid wrapping a java Android MotionEvent.
 // All *input* coordinates are in device pixels (as with Android MotionEvent),
 // while all *output* coordinates are in DIPs (as with WebTouchEvent).
 class EVENTS_EXPORT MotionEventAndroidJava : public MotionEventAndroid {
  public:
+  ~MotionEventAndroidJava() override;
+  friend class MotionEventAndroidFactory;
+  // Disallow copy/assign.
+  MotionEventAndroidJava(const MotionEventAndroidJava& e) = delete;
+  void operator=(const MotionEventAndroidJava&) = delete;
+
+  // Start ui::MotionEvent overrides
+  int GetPointerId(size_t pointer_index) const override;
+  float GetX(size_t pointer_index) const override;
+  float GetY(size_t pointer_index) const override;
+  float GetTouchMajor(size_t pointer_index) const override;
+  float GetTouchMinor(size_t pointer_index) const override;
+  float GetOrientation(size_t pointer_index) const override;
+  float GetPressure(size_t pointer_index) const override;
+  float GetTiltX(size_t pointer_index) const override;
+  float GetTiltY(size_t pointer_index) const override;
+  base::TimeTicks GetHistoricalEventTime(
+      size_t historical_index) const override;
+  float GetHistoricalTouchMajor(size_t pointer_index,
+                                size_t historical_index) const override;
+  float GetHistoricalX(size_t pointer_index,
+                       size_t historical_index) const override;
+  float GetHistoricalY(size_t pointer_index,
+                       size_t historical_index) const override;
+  ToolType GetToolType(size_t pointer_index) const override;
+  bool IsLatestEventTimeResampled() const override;
+  // End ui::MotionEvent overrides
+
+  // Start MotionEventAndroid overrides
+  std::unique_ptr<MotionEventAndroid> CreateFor(
+      const gfx::PointF& point) const override;
+  float GetXPix(size_t pointer_index) const override;
+  float GetYPix(size_t pointer_index) const override;
+  int GetSource() const override;
+  base::android::ScopedJavaLocalRef<jobject> GetJavaObject() const override;
+  // End MotionEventAndroid overrides
+
+ private:
   // Forcing the caller to provide all cached values upon construction
   // eliminates the need to perform a JNI call to retrieve values individually.
   MotionEventAndroidJava(JNIEnv* env,
@@ -69,45 +109,6 @@
                          const Pointer* const pointer0,
                          const Pointer* const pointer1,
                          bool is_latest_event_time_resampled);
-
-  ~MotionEventAndroidJava() override;
-
-  // Disallow copy/assign.
-  MotionEventAndroidJava(const MotionEventAndroidJava& e) = delete;
-  void operator=(const MotionEventAndroidJava&) = delete;
-
-  // Start ui::MotionEvent overrides
-  int GetPointerId(size_t pointer_index) const override;
-  float GetX(size_t pointer_index) const override;
-  float GetY(size_t pointer_index) const override;
-  float GetTouchMajor(size_t pointer_index) const override;
-  float GetTouchMinor(size_t pointer_index) const override;
-  float GetOrientation(size_t pointer_index) const override;
-  float GetPressure(size_t pointer_index) const override;
-  float GetTiltX(size_t pointer_index) const override;
-  float GetTiltY(size_t pointer_index) const override;
-  base::TimeTicks GetHistoricalEventTime(
-      size_t historical_index) const override;
-  float GetHistoricalTouchMajor(size_t pointer_index,
-                                size_t historical_index) const override;
-  float GetHistoricalX(size_t pointer_index,
-                       size_t historical_index) const override;
-  float GetHistoricalY(size_t pointer_index,
-                       size_t historical_index) const override;
-  ToolType GetToolType(size_t pointer_index) const override;
-  bool IsLatestEventTimeResampled() const override;
-  // End ui::MotionEvent overrides
-
-  // Start MotionEventAndroid overrides
-  std::unique_ptr<MotionEventAndroid> CreateFor(
-      const gfx::PointF& point) const override;
-  float GetXPix(size_t pointer_index) const override;
-  float GetYPix(size_t pointer_index) const override;
-  int GetSource() const override;
-  base::android::ScopedJavaLocalRef<jobject> GetJavaObject() const override;
-  // End MotionEventAndroid overrides
-
- private:
   // The Java reference to the underlying MotionEvent.
   base::android::ScopedJavaGlobalRef<jobject> event_;
 
diff --git a/ui/events/android/motion_event_android_native.cc b/ui/events/android/motion_event_android_native.cc
index f6beb65..aeb7d802 100644
--- a/ui/events/android/motion_event_android_native.cc
+++ b/ui/events/android/motion_event_android_native.cc
@@ -71,100 +71,10 @@
   CHECK(native_event_);
 }
 
-// static
-std::unique_ptr<MotionEventAndroid> MotionEventAndroidNative::Create(
-    base::android::ScopedInputEvent input_event,
-    float pix_to_dip,
-    float y_offset_pix,
-    std::optional<EventTimes> event_times) {
-  const AInputEvent* event = input_event.a_input_event();
-
-  CHECK(event != nullptr);
-  CHECK(AInputEvent_getType(event) == AINPUT_EVENT_TYPE_MOTION);
-
-  const size_t history_size = AMotionEvent_getHistorySize(event);
-  // AMotionEvent_getEventTime and AMotionEvent_getHistoricalEventTime returns
-  // the time with nanoseconds precision.
-  if (!event_times) {
-    event_times = EventTimes();
-    event_times->latest =
-        base::TimeTicks::FromJavaNanoTime(AMotionEvent_getEventTime(event));
-    event_times->oldest = (history_size == 0)
-                              ? event_times->latest
-                              : base::TimeTicks::FromJavaNanoTime(
-                                    AMotionEvent_getHistoricalEventTime(
-                                        event, /* history_index= */ 0));
-  }
-  const jlong down_time_ms =
-      base::TimeTicks::FromJavaNanoTime(AMotionEvent_getDownTime(event))
-          .ToUptimeMillis();
-  // Native side doesn't have MotionEvent.getActionMasked() or
-  // MotionEvent.getActionIndex counterparts.
-  const int action = AMotionEvent_getAction(event);
-  const int masked_action = action & AMOTION_EVENT_ACTION_MASK;
-  const int action_index = (action & AMOTION_EVENT_ACTION_POINTER_INDEX_MASK) >>
-                           AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT;
-
-  const size_t pointer_count = AMotionEvent_getPointerCount(event);
-
-  const std::unique_ptr<ui::MotionEventAndroid::Pointer> pointer0 =
-      std::make_unique<ui::MotionEventAndroid::Pointer>(
-          /*id=*/AMotionEvent_getPointerId(event, 0),
-          /*pos_x_pixels=*/AMotionEvent_getX(event, 0),
-          /*pos_y_pixels=*/AMotionEvent_getY(event, 0) + y_offset_pix,
-          /*touch_major_pixels=*/AMotionEvent_getTouchMajor(event, 0),
-          /*touch_minor_pixels=*/AMotionEvent_getTouchMinor(event, 0),
-          /*pressure=*/AMotionEvent_getPressure(event, 0),
-          /*orienation_rad=*/AMotionEvent_getOrientation(event, 0),
-          /*tilt_rad=*/
-          AMotionEvent_getAxisValue(event, AMOTION_EVENT_AXIS_TILT, 0),
-          /*tool_type=*/AMotionEvent_getToolType(event, 0));
-
-  std::unique_ptr<ui::MotionEventAndroid::Pointer> pointer1 = nullptr;
-  if (pointer_count > 1) {
-    pointer1 = std::make_unique<ui::MotionEventAndroid::Pointer>(
-        /*id=*/AMotionEvent_getPointerId(event, 1),
-        /*pos_x_pixels=*/AMotionEvent_getX(event, 1),
-        /*pos_y_pixels=*/AMotionEvent_getY(event, 1) + y_offset_pix,
-        /*touch_major_pixels=*/AMotionEvent_getTouchMajor(event, 1),
-        /*touch_minor_pixels=*/AMotionEvent_getTouchMinor(event, 1),
-        /*pressure=*/AMotionEvent_getPressure(event, 1),
-        /*orientation_rad=*/AMotionEvent_getOrientation(event, 1),
-        /*tilt_rad=*/
-        AMotionEvent_getAxisValue(event, AMOTION_EVENT_AXIS_TILT, 1),
-        /*tool_type=*/AMotionEvent_getToolType(event, 1));
-  }
-
-  // TODO(crbug.com/373345667): Move this and other duplicate calculations to
-  // base class.
-  const float raw_offset_x_pixels =
-      AMotionEvent_getRawX(event, 0) - pointer0->pos_x_pixels;
-  const float raw_offset_y_pixels =
-      AMotionEvent_getRawY(event, 0) - pointer0->pos_y_pixels;
-
-  int gesture_classification = 0;
-  if (__builtin_available(android 33, *)) {
-    gesture_classification = AMotionEvent_getClassification(event);
-  }
-
-  return base::WrapUnique<MotionEventAndroid>(new MotionEventAndroidNative(
-      std::move(input_event), pix_to_dip,
-      /* ticks_x= */ 0.f,
-      /* ticks_y= */ 0.f,
-      /* tick_multiplier= */ 0.f, event_times->oldest, event_times->latest,
-      base::TimeTicks::FromUptimeMillis(down_time_ms), masked_action,
-      pointer_count, history_size, action_index,
-      /* android_action_button= */ 0, gesture_classification,
-      AMotionEvent_getButtonState(event), AMotionEvent_getMetaState(event),
-      raw_offset_x_pixels, raw_offset_y_pixels,
-      /* for_touch_handle= */ false, pointer0.get(), pointer1.get(),
-      y_offset_pix));
-}
-
 int MotionEventAndroidNative::GetPointerId(size_t pointer_index) const {
   DCHECK_LT(pointer_index, GetPointerCount());
-  if (pointer_index < MAX_POINTERS_TO_CACHE) {
-    return cached_pointers_[pointer_index].id;
+  if (IsPointerCacheable(pointer_index)) {
+    return GetCachedPointerId(pointer_index);
   }
   return AMotionEvent_getPointerId(native_event_.a_input_event(),
                                    pointer_index);
@@ -172,16 +82,16 @@
 
 float MotionEventAndroidNative::GetX(size_t pointer_index) const {
   DCHECK_LT(pointer_index, GetPointerCount());
-  if (pointer_index < MAX_POINTERS_TO_CACHE) {
-    return cached_pointers_[pointer_index].position.x();
+  if (IsPointerCacheable(pointer_index)) {
+    return GetCachedPointerPosition(pointer_index).x();
   }
   return ToDips(
       AMotionEvent_getX(native_event_.a_input_event(), pointer_index));
 }
 float MotionEventAndroidNative::GetY(size_t pointer_index) const {
   DCHECK_LT(pointer_index, GetPointerCount());
-  if (pointer_index < MAX_POINTERS_TO_CACHE) {
-    return cached_pointers_[pointer_index].position.y();
+  if (IsPointerCacheable(pointer_index)) {
+    return GetCachedPointerPosition(pointer_index).y();
   }
   return ToDips(
       AMotionEvent_getY(native_event_.a_input_event(), pointer_index) +
@@ -190,8 +100,8 @@
 
 float MotionEventAndroidNative::GetTouchMajor(size_t pointer_index) const {
   DCHECK_LT(pointer_index, GetPointerCount());
-  if (pointer_index < MAX_POINTERS_TO_CACHE) {
-    return cached_pointers_[pointer_index].touch_major;
+  if (IsPointerCacheable(pointer_index)) {
+    return GetCachedPointerTouchMajor(pointer_index);
   }
   return ToDips(
       AMotionEvent_getTouchMajor(native_event_.a_input_event(), pointer_index));
@@ -199,8 +109,8 @@
 
 float MotionEventAndroidNative::GetTouchMinor(size_t pointer_index) const {
   DCHECK_LT(pointer_index, GetPointerCount());
-  if (pointer_index < MAX_POINTERS_TO_CACHE) {
-    return cached_pointers_[pointer_index].touch_minor;
+  if (IsPointerCacheable(pointer_index)) {
+    return GetCachedPointerTouchMinor(pointer_index);
   }
   return ToDips(
       AMotionEvent_getTouchMajor(native_event_.a_input_event(), pointer_index));
@@ -208,8 +118,8 @@
 
 float MotionEventAndroidNative::GetOrientation(size_t pointer_index) const {
   DCHECK_LT(pointer_index, GetPointerCount());
-  if (pointer_index < MAX_POINTERS_TO_CACHE) {
-    return cached_pointers_[pointer_index].orientation;
+  if (IsPointerCacheable(pointer_index)) {
+    return GetCachedPointerOrientation(pointer_index);
   }
   return ToValidFloat(AMotionEvent_getOrientation(native_event_.a_input_event(),
                                                   pointer_index));
@@ -224,8 +134,8 @@
 
 float MotionEventAndroidNative::GetTiltX(size_t pointer_index) const {
   DCHECK_LT(pointer_index, GetPointerCount());
-  if (pointer_index < MAX_POINTERS_TO_CACHE) {
-    return cached_pointers_[pointer_index].tilt_x;
+  if (IsPointerCacheable(pointer_index)) {
+    return GetCachedPointerTiltX(pointer_index);
   }
   float tilt_x, tilt_y;
   float tilt_rad = ToValidFloat(AMotionEvent_getAxisValue(
@@ -237,8 +147,8 @@
 }
 
 float MotionEventAndroidNative::GetTiltY(size_t pointer_index) const {
-  if (pointer_index < MAX_POINTERS_TO_CACHE) {
-    return cached_pointers_[pointer_index].tilt_y;
+  if (IsPointerCacheable(pointer_index)) {
+    return GetCachedPointerTiltY(pointer_index);
   }
   float tilt_x, tilt_y;
   float tilt_rad = ToValidFloat(AMotionEvent_getAxisValue(
@@ -285,8 +195,8 @@
 ui::MotionEvent::ToolType MotionEventAndroidNative::GetToolType(
     size_t pointer_index) const {
   DCHECK_LT(pointer_index, GetPointerCount());
-  if (pointer_index < MAX_POINTERS_TO_CACHE) {
-    return cached_pointers_[pointer_index].tool_type;
+  if (IsPointerCacheable(pointer_index)) {
+    return GetCachedPointerToolType(pointer_index);
   }
   return FromAndroidToolType(
       AMotionEvent_getToolType(native_event_.a_input_event(), pointer_index));
diff --git a/ui/events/android/motion_event_android_native.h b/ui/events/android/motion_event_android_native.h
index db8a2ef..c7e9537 100644
--- a/ui/events/android/motion_event_android_native.h
+++ b/ui/events/android/motion_event_android_native.h
@@ -10,9 +10,6 @@
 #include <stddef.h>
 #include <stdint.h>
 
-#include <memory>
-#include <optional>
-
 #include "base/android/scoped_input_event.h"
 #include "base/android/scoped_java_ref.h"
 #include "base/memory/raw_ptr.h"
@@ -24,6 +21,8 @@
 
 namespace ui {
 
+class MotionEventAndroidFactory;
+
 class EVENTS_EXPORT MotionEventAndroidNative : public MotionEventAndroid {
  public:
   ~MotionEventAndroidNative() override;
@@ -59,17 +58,9 @@
   int GetSource() const override;
   // End MotionEventAndroid overrides
 
-  struct EventTimes {
-    base::TimeTicks oldest;
-    base::TimeTicks latest;
-  };
-  static std::unique_ptr<MotionEventAndroid> Create(
-      base::android::ScopedInputEvent input_event,
-      float pix_to_dip,
-      float y_offset_pix,
-      std::optional<EventTimes> event_times);
-
  private:
+  friend class MotionEventAndroidFactory;
+
   MotionEventAndroidNative(base::android::ScopedInputEvent input_event,
                            float pix_to_dip,
                            float ticks_x,
diff --git a/ui/events/android/motion_event_android_unittest.cc b/ui/events/android/motion_event_android_unittest.cc
index 51041d2f..04793ad5 100644
--- a/ui/events/android/motion_event_android_unittest.cc
+++ b/ui/events/android/motion_event_android_unittest.cc
@@ -15,6 +15,7 @@
 #include "base/test/scoped_feature_list.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "ui/base/ui_base_features.h"
+#include "ui/events/android/motion_event_android_factory.h"
 #include "ui/events/android/motion_event_android_java.h"
 #include "ui/events/android/motion_event_android_native.h"
 #include "ui/events/event_constants.h"
@@ -96,46 +97,63 @@
           env, /*downTime=*/0, /*eventTime=*/0, /*action=*/0, /*x=*/0, /*y=*/0,
           /*metaState=*/kAndroidAltKeyDown);
 
-  MotionEventAndroidJava event(
-      env, obj, kPixToDip, 0.f, 0.f, 0.f, oldest_event_time, latest_event_time,
-      down_time_ms, kAndroidActionDown, pointer_count, history_size,
-      action_index, kAndroidActionButton, 0, kAndroidButtonPrimary, raw_offset,
-      -raw_offset, false, &p0, &p1, false);
+  std::unique_ptr<MotionEventAndroid> event =
+      MotionEventAndroidFactory::CreateFromJava(
+          env, obj, kPixToDip,
+          /*ticks_x=*/0.f,
+          /*ticks_y=*/0.f,
+          /*tick_multiplier=*/0.f, oldest_event_time, latest_event_time,
+          down_time_ms,
+          /*android_action=*/kAndroidActionDown, pointer_count, history_size,
+          action_index,
+          /*android_action_button=*/kAndroidActionButton,
+          /*android_gesture_classification=*/0,
+          /*android_button_state=*/kAndroidButtonPrimary,
+          /*raw_offset_x_pixels=*/raw_offset,
+          /*raw_offset_y_pixels=*/-raw_offset,
+          /*for_touch_handle=*/false,
+          /*pointer0=*/&p0,
+          /*pointer1=*/&p1,
+          /*is_latest_event_time_resampled=*/false);
 
-  EXPECT_EQ(MotionEvent::Action::DOWN, event.GetAction());
-  EXPECT_EQ(oldest_event_time, event.GetEventTime());
-  EXPECT_EQ(latest_event_time, event.GetLatestEventTime());
-  EXPECT_EQ(event.GetDownTime(), down_time_ms);
-  EXPECT_EQ(p0.pos_x_pixels * kPixToDip, event.GetX(0));
-  EXPECT_EQ(p0.pos_y_pixels * kPixToDip, event.GetY(0));
-  EXPECT_EQ(p1.pos_x_pixels * kPixToDip, event.GetX(1));
-  EXPECT_EQ(p1.pos_y_pixels * kPixToDip, event.GetY(1));
-  EXPECT_FLOAT_EQ((p0.pos_x_pixels + raw_offset) * kPixToDip, event.GetRawX(0));
-  EXPECT_FLOAT_EQ((p0.pos_y_pixels - raw_offset) * kPixToDip, event.GetRawY(0));
-  EXPECT_FLOAT_EQ((p1.pos_x_pixels + raw_offset) * kPixToDip, event.GetRawX(1));
-  EXPECT_FLOAT_EQ((p1.pos_y_pixels - raw_offset) * kPixToDip, event.GetRawY(1));
-  EXPECT_EQ(p0.touch_major_pixels * kPixToDip, event.GetTouchMajor(0));
-  EXPECT_EQ(p1.touch_major_pixels * kPixToDip, event.GetTouchMajor(1));
-  EXPECT_EQ(p0.touch_minor_pixels * kPixToDip, event.GetTouchMinor(0));
-  EXPECT_EQ(p1.touch_minor_pixels * kPixToDip, event.GetTouchMinor(1));
-  EXPECT_EQ(event.GetPressure(0), pressure_0);
-  EXPECT_EQ(event.GetPressure(1), pressure_1);
-  EXPECT_EQ(p0.orientation_rad, event.GetOrientation(0));
-  EXPECT_EQ(p1.orientation_rad, event.GetOrientation(1));
+  EXPECT_EQ(MotionEvent::Action::DOWN, event->GetAction());
+  EXPECT_EQ(oldest_event_time, event->GetEventTime());
+  EXPECT_EQ(latest_event_time, event->GetLatestEventTime());
+  EXPECT_EQ(event->GetDownTime(), down_time_ms);
+  EXPECT_EQ(p0.pos_x_pixels * kPixToDip, event->GetX(0));
+  EXPECT_EQ(p0.pos_y_pixels * kPixToDip, event->GetY(0));
+  EXPECT_EQ(p1.pos_x_pixels * kPixToDip, event->GetX(1));
+  EXPECT_EQ(p1.pos_y_pixels * kPixToDip, event->GetY(1));
+  EXPECT_FLOAT_EQ((p0.pos_x_pixels + raw_offset) * kPixToDip,
+                  event->GetRawX(0));
+  EXPECT_FLOAT_EQ((p0.pos_y_pixels - raw_offset) * kPixToDip,
+                  event->GetRawY(0));
+  EXPECT_FLOAT_EQ((p1.pos_x_pixels + raw_offset) * kPixToDip,
+                  event->GetRawX(1));
+  EXPECT_FLOAT_EQ((p1.pos_y_pixels - raw_offset) * kPixToDip,
+                  event->GetRawY(1));
+  EXPECT_EQ(p0.touch_major_pixels * kPixToDip, event->GetTouchMajor(0));
+  EXPECT_EQ(p1.touch_major_pixels * kPixToDip, event->GetTouchMajor(1));
+  EXPECT_EQ(p0.touch_minor_pixels * kPixToDip, event->GetTouchMinor(0));
+  EXPECT_EQ(p1.touch_minor_pixels * kPixToDip, event->GetTouchMinor(1));
+  EXPECT_EQ(event->GetPressure(0), pressure_0);
+  EXPECT_EQ(event->GetPressure(1), pressure_1);
+  EXPECT_EQ(p0.orientation_rad, event->GetOrientation(0));
+  EXPECT_EQ(p1.orientation_rad, event->GetOrientation(1));
   EXPECT_NEAR(p0.tilt_rad,
-              ConvertToTiltRad(event.GetTiltX(0), event.GetTiltY(0)),
+              ConvertToTiltRad(event->GetTiltX(0), event->GetTiltY(0)),
               float_error);
   EXPECT_NEAR(p1.tilt_rad,
-              ConvertToTiltRad(event.GetTiltX(1), event.GetTiltY(1)),
+              ConvertToTiltRad(event->GetTiltX(1), event->GetTiltY(1)),
               float_error);
-  EXPECT_EQ(p0.id, event.GetPointerId(0));
-  EXPECT_EQ(p1.id, event.GetPointerId(1));
-  EXPECT_EQ(MotionEvent::ToolType::FINGER, event.GetToolType(0));
-  EXPECT_EQ(MotionEvent::ToolType::FINGER, event.GetToolType(1));
-  EXPECT_EQ(MotionEvent::BUTTON_PRIMARY, event.GetButtonState());
-  EXPECT_EQ(ui::EF_ALT_DOWN | ui::EF_LEFT_MOUSE_BUTTON, event.GetFlags());
-  EXPECT_EQ(static_cast<size_t>(pointer_count), event.GetPointerCount());
-  EXPECT_EQ(static_cast<size_t>(history_size), event.GetHistorySize());
+  EXPECT_EQ(p0.id, event->GetPointerId(0));
+  EXPECT_EQ(p1.id, event->GetPointerId(1));
+  EXPECT_EQ(MotionEvent::ToolType::FINGER, event->GetToolType(0));
+  EXPECT_EQ(MotionEvent::ToolType::FINGER, event->GetToolType(1));
+  EXPECT_EQ(MotionEvent::BUTTON_PRIMARY, event->GetButtonState());
+  EXPECT_EQ(ui::EF_ALT_DOWN | ui::EF_LEFT_MOUSE_BUTTON, event->GetFlags());
+  EXPECT_EQ(static_cast<size_t>(pointer_count), event->GetPointerCount());
+  EXPECT_EQ(static_cast<size_t>(history_size), event->GetHistorySize());
 }
 
 TEST(MotionEventAndroidTest, ZeroPressureOnActionUpEvent) {
@@ -151,11 +169,26 @@
       JNI_MotionEvent::Java_MotionEvent_obtain(
           env, /*downTime=*/0, /*eventTime=*/0, /*action=*/0, /*x=*/0, /*y=*/0,
           /*metaState=*/0);
-  MotionEventAndroidJava event(env, obj, kPixToDip, 0, 0, 0, base::TimeTicks(),
-                               kAndroidActionUp, 1, 0, 0, 0, 0, 0, 0, 0, false,
-                               &p0, nullptr);
+  auto event = MotionEventAndroidFactory::CreateFromJava(
+      env, obj, kPixToDip,
+      /*ticks_x=*/0,
+      /*ticks_y=*/0,
+      /*tick_multiplier=*/0,
+      /*oldest_event_time=*/base::TimeTicks(),
+      /*android_action=*/kAndroidActionUp,
+      /*pointer_count=*/1,
+      /*history_size=*/0,
+      /*action_index=*/0,
+      /*android_action_button=*/0,
+      /*android_gesture_classification=*/0,
+      /*android_button_state=*/0,
+      /*raw_offset_x_pixels=*/0,
+      /*raw_offset_y_pixels=*/0,
+      /*for_touch_handle=*/false,
+      /*pointer0=*/&p0,
+      /*pointer1=*/nullptr);
 
-  EXPECT_EQ(event.GetPressure(0), 0.f);
+  EXPECT_EQ(event->GetPressure(0), 0.f);
 }
 
 TEST(MotionEventAndroidTest, Clone) {
@@ -170,12 +203,26 @@
       JNI_MotionEvent::Java_MotionEvent_obtain(
           env, /*downTime=*/0, /*eventTime=*/0, /*action=*/0, /*x=*/0, /*y=*/0,
           /*metaState=*/0);
-  MotionEventAndroidJava event(env, obj, kPixToDip, 0, 0, 0, base::TimeTicks(),
-                               kAndroidActionDown, pointer_count, 0, 0, 0, 0, 0,
-                               0, 0, false, &p0, nullptr);
+  auto event = MotionEventAndroidFactory::CreateFromJava(
+      env, obj, kPixToDip,
+      /*ticks_x=*/0,
+      /*ticks_y=*/0,
+      /*tick_multiplier=*/0,
+      /*oldest_event_time=*/base::TimeTicks(),
+      /*android_action=*/kAndroidActionDown, pointer_count,
+      /*history_size=*/0,
+      /*action_index=*/0,
+      /*android_action_button=*/0,
+      /*android_gesture_classification=*/0,
+      /*android_button_state=*/0,
+      /*raw_offset_x_pixels=*/0,
+      /*raw_offset_y_pixels=*/0,
+      /*for_touch_handle=*/false,
+      /*pointer0=*/&p0,
+      /*pointer1=*/nullptr);
 
-  std::unique_ptr<MotionEvent> clone = event.Clone();
-  EXPECT_EQ(ui::test::ToString(event), ui::test::ToString(*clone));
+  std::unique_ptr<MotionEvent> clone = event->Clone();
+  EXPECT_EQ(ui::test::ToString(*event), ui::test::ToString(*clone));
 }
 
 TEST(MotionEventAndroidTest, Cancel) {
@@ -194,12 +241,26 @@
       JNI_MotionEvent::Java_MotionEvent_obtain(
           env, /*downTime=*/0, /*eventTime=*/0, /*action=*/0, /*x=*/0, /*y=*/0,
           /*metaState=*/0);
-  MotionEventAndroidJava event(
-      env, obj, kPixToDip, 0, 0, 0,
-      base::TimeTicks() + base::Nanoseconds(kEventTimeNS), kAndroidActionDown,
-      pointer_count, 0, 0, 0, 0, 0, 0, 0, false, &p0, nullptr);
+  auto event = MotionEventAndroidFactory::CreateFromJava(
+      env, obj, kPixToDip,
+      /*ticks_x=*/0,
+      /*ticks_y=*/0,
+      /*tick_multiplier=*/0,
+      /*oldest_event_time=*/
+      base::TimeTicks() + base::Nanoseconds(kEventTimeNS),
+      /*android_action=*/kAndroidActionDown, pointer_count,
+      /*history_size=*/0,
+      /*action_index=*/0,
+      /*android_action_button=*/0,
+      /*android_gesture_classification=*/0,
+      /*android_button_state=*/0,
+      /*raw_offset_x_pixels=*/0,
+      /*raw_offset_y_pixels=*/0,
+      /*for_touch_handle=*/false,
+      /*pointer0=*/&p0,
+      /*pointer1=*/nullptr);
 
-  std::unique_ptr<MotionEvent> cancel_event = event.Cancel();
+  std::unique_ptr<MotionEvent> cancel_event = event->Cancel();
   EXPECT_EQ(MotionEvent::Action::CANCEL, cancel_event->GetAction());
   EXPECT_EQ(event_time, cancel_event->GetEventTime());
   EXPECT_EQ(p0.pos_x_pixels * kPixToDip, cancel_event->GetX(0));
@@ -224,12 +285,26 @@
       JNI_MotionEvent::Java_MotionEvent_obtain(
           env, /*downTime=*/0, /*eventTime=*/0, /*action=*/0, /*x=*/0, /*y=*/0,
           /*metaState=*/0);
-  MotionEventAndroidJava event(env, obj, kPixToDip, 0, 0, 0, base::TimeTicks(),
-                               kAndroidActionDown, pointer_count, 0, 0, 0, 0, 0,
-                               0, 0, false, &p0, &p1);
+  auto event = MotionEventAndroidFactory::CreateFromJava(
+      env, obj, kPixToDip,
+      /*ticks_x=*/0,
+      /*ticks_y=*/0,
+      /*tick_multiplier=*/0,
+      /*oldest_event_time=*/base::TimeTicks(),
+      /*android_action=*/kAndroidActionDown, pointer_count,
+      /*history_size=*/0,
+      /*action_index=*/0,
+      /*android_action_button=*/0,
+      /*android_gesture_classification=*/0,
+      /*android_button_state=*/0,
+      /*raw_offset_x_pixels=*/0,
+      /*raw_offset_y_pixels=*/0,
+      /*for_touch_handle=*/false,
+      /*pointer0=*/&p0,
+      /*pointer1=*/&p1);
 
-  EXPECT_EQ(0.f, event.GetOrientation(0));
-  EXPECT_EQ(0.f, event.GetOrientation(1));
+  EXPECT_EQ(0.f, event->GetOrientation(0));
+  EXPECT_EQ(0.f, event->GetOrientation(1));
 }
 
 TEST(MotionEventAndroidTest, NonEmptyHistoryForNonMoveEventsSanitized) {
@@ -245,12 +320,27 @@
       JNI_MotionEvent::Java_MotionEvent_obtain(
           env, /*downTime=*/0, /*eventTime=*/0, /*action=*/0, /*x=*/0, /*y=*/0,
           /*metaState=*/0);
-  MotionEventAndroidJava event(env, obj, kPixToDip, 0, 0, 0, base::TimeTicks(),
-                               base::TimeTicks(), base::TimeTicks(),
-                               kAndroidActionDown, pointer_count, history_size,
-                               0, 0, 0, 0, 0, 0, false, &p0, nullptr, false);
+  auto event = MotionEventAndroidFactory::CreateFromJava(
+      env, obj, kPixToDip,
+      /*ticks_x=*/0,
+      /*ticks_y=*/0,
+      /*tick_multiplier=*/0,
+      /*oldest_event_time=*/base::TimeTicks(),
+      /*latest_event_time=*/base::TimeTicks(),
+      /*down_time_ms=*/base::TimeTicks(),
+      /*android_action=*/kAndroidActionDown, pointer_count, history_size,
+      /*action_index=*/0,
+      /*android_action_button=*/0,
+      /*android_gesture_classification=*/0,
+      /*android_button_state=*/0,
+      /*raw_offset_x_pixels=*/0,
+      /*raw_offset_y_pixels=*/0,
+      /*for_touch_handle=*/false,
+      /*pointer0=*/&p0,
+      /*pointer1=*/nullptr,
+      /*is_latest_event_time_resampled=*/false);
 
-  EXPECT_EQ(0U, event.GetHistorySize());
+  EXPECT_EQ(0U, event->GetHistorySize());
 }
 
 TEST(MotionEventAndroidTest, ActionIndexForPointerDown) {
@@ -270,13 +360,25 @@
       JNI_MotionEvent::Java_MotionEvent_obtain(
           env, /*downTime=*/0, /*eventTime=*/0, /*action=*/0, /*x=*/0, /*y=*/0,
           /*metaState=*/0);
-  MotionEventAndroidJava event(env, obj, kPixToDip, 0, 0, 0, base::TimeTicks(),
-                               kAndroidActionPointerDown, pointer_count,
-                               history_size, action_index, 0, 0, 0, 0, 0, false,
-                               &p0, &p1);
+  auto event = MotionEventAndroidFactory::CreateFromJava(
+      env, obj, kPixToDip,
+      /*ticks_x=*/0,
+      /*ticks_y=*/0,
+      /*tick_multiplier=*/0,
+      /*oldest_event_time=*/base::TimeTicks(),
+      /*android_action=*/kAndroidActionPointerDown, pointer_count, history_size,
+      action_index,
+      /*android_action_button=*/0,
+      /*android_gesture_classification=*/0,
+      /*android_button_state=*/0,
+      /*raw_offset_x_pixels=*/0,
+      /*raw_offset_y_pixels=*/0,
+      /*for_touch_handle=*/false,
+      /*pointer0=*/&p0,
+      /*pointer1=*/&p1);
 
-  EXPECT_EQ(MotionEvent::Action::POINTER_DOWN, event.GetAction());
-  EXPECT_EQ(action_index, event.GetActionIndex());
+  EXPECT_EQ(MotionEvent::Action::POINTER_DOWN, event->GetAction());
+  EXPECT_EQ(action_index, event->GetActionIndex());
 }
 
 TEST(MotionEventAndroidTest, NativeBackedConstructor) {
@@ -303,10 +405,11 @@
   }
   CHECK(native_event != nullptr);
 
-  std::unique_ptr<MotionEventAndroid> event = MotionEventAndroidNative::Create(
-      base::android::ScopedInputEvent(native_event), kPixToDip,
-      /* y_offset_pix= */ 0,
-      /* event_times= */ std::nullopt);
+  std::unique_ptr<MotionEventAndroid> event =
+      MotionEventAndroidFactory::CreateFromNative(
+          base::android::ScopedInputEvent(native_event), kPixToDip,
+          /* y_offset_pix= */ 0,
+          /* event_times= */ std::nullopt);
 
   EXPECT_EQ(event->GetX(0), x * kPixToDip);
   EXPECT_EQ(event->GetY(0), y * kPixToDip);
diff --git a/ui/gfx/render_text_harfbuzz.cc b/ui/gfx/render_text_harfbuzz.cc
index e72f9e7..b3647af3 100644
--- a/ui/gfx/render_text_harfbuzz.cc
+++ b/ui/gfx/render_text_harfbuzz.cc
@@ -80,6 +80,23 @@
 // character to belong to more scripts.
 const size_t kMaxScripts = 32;
 
+BASE_FEATURE(kCombiningMarkScript,
+             "CombiningMarkScript",
+             base::FEATURE_ENABLED_BY_DEFAULT);
+
+bool IsCombiningMarkScriptEnabled() {
+  static bool is_enabled = false;
+  static std::once_flag once_flag;
+  std::call_once(once_flag, [] {
+    is_enabled = base::FeatureList::IsEnabled(kCombiningMarkScript);
+  });
+  return is_enabled;
+}
+
+bool IsCombiningMarkCodepoint(UChar32 codepoint) {
+  return U_GET_GC_MASK(codepoint) & U_GC_M_MASK;
+}
+
 // Returns whether the codepoint has the 'extended pictographic' property.
 bool IsExtendedPictographicCodepoint(UChar32 codepoint) {
   return u_hasBinaryProperty(codepoint, UCHAR_EXTENDED_PICTOGRAPHIC);
@@ -117,6 +134,14 @@
 // Intersects the script extensions set of |codepoint| with |result| and writes
 // to |result|.
 void ScriptSetIntersect(UChar32 codepoint, std::vector<UScriptCode>& result) {
+  // The recommended implementation strategy is to treat all the characters of a
+  // combining character sequence, including spacing combining marks, as having
+  // the Script property value of the first character in the sequence.
+  // https://www.unicode.org/reports/tr24/#Nonspacing_Marks
+  if (IsCombiningMarkScriptEnabled() && IsCombiningMarkCodepoint(codepoint)) {
+    return;
+  }
+
   // Each codepoint has a Script property and a Script Extensions (Scx)
   // property.
   //
diff --git a/ui/message_center/message_center.cc b/ui/message_center/message_center.cc
index 7375148..624e2d8 100644
--- a/ui/message_center/message_center.cc
+++ b/ui/message_center/message_center.cc
@@ -29,6 +29,14 @@
 }
 
 // static
+void MessageCenter::InitializeForTesting(
+    std::unique_ptr<MessageCenter> message_center) {
+  DCHECK(!g_message_center);
+  DCHECK(message_center);
+  g_message_center = message_center.release();
+}
+
+// static
 MessageCenter* MessageCenter::Get() {
   return g_message_center;
 }
diff --git a/ui/message_center/message_center.h b/ui/message_center/message_center.h
index e1713f9d..e686280 100644
--- a/ui/message_center/message_center.h
+++ b/ui/message_center/message_center.h
@@ -59,6 +59,9 @@
   static void Initialize();
   // Creates the global message center object with custom LockScreenController.
   static void Initialize(std::unique_ptr<LockScreenController> controller);
+  // Sets the global message center for testing.
+  static void InitializeForTesting(
+      std::unique_ptr<MessageCenter> message_center);
 
   // Returns the global message center object. Returns null if Initialize is
   // not called.
@@ -269,6 +272,8 @@
   // Called when a message view associated with `notification_id` is hovered on.
   virtual void OnMessageViewHovered(const std::string& notification_id) = 0;
 
+  virtual ~MessageCenter();
+
  protected:
   friend class ::DownloadNotification;
   friend class ::DownloadNotificationTestBase;
@@ -283,7 +288,6 @@
   virtual void DisableTimersForTest() = 0;
 
   MessageCenter();
-  virtual ~MessageCenter();
 };
 
 }  // namespace message_center