diff --git a/DEPS b/DEPS
index 0281c6a..3da2fb8 100644
--- a/DEPS
+++ b/DEPS
@@ -299,15 +299,15 @@
   # 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': '12dbc34d742ee67f1b267dc276fe7d84631aa856',
+  'skia_revision': '0feee17aeacab6b88ac8be3d8b35ae4c940eeea4',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling V8
   # and whatever else without interference from each other.
-  'v8_revision': '577adbe0c15f8fa0643d5f91cc92a06c271c3456',
+  'v8_revision': '1beea5ff6680e267f28e29054239ebcdd4ccb861',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling ANGLE
   # and whatever else without interference from each other.
-  'angle_revision': '214b7ce20bb6a308c8e929743cd8402f3f5204dd',
+  'angle_revision': 'db33baf4eb0d7954f0110cddc30acb9cdc12e2d4',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling SwiftShader
   # and whatever else without interference from each other.
@@ -319,7 +319,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling BoringSSL
   # and whatever else without interference from each other.
-  'boringssl_revision': '45cab558a838e1a546391406e7cbe1eec9b6e643',
+  'boringssl_revision': '136284f8548bc7fb43e99e7f69e03fab57168e8b',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling Fuchsia sdk
   # and whatever else without interference from each other.
@@ -371,7 +371,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling catapult
   # and whatever else without interference from each other.
-  'catapult_revision': '5255e1a11a4d0bf747f304816a2523f952be9228',
+  'catapult_revision': '0c5acc073dd14893f2a75a51819ad81627098551',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling CrossBench
   # and whatever else without interference from each other.
@@ -391,7 +391,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling devtools-frontend
   # and whatever else without interference from each other.
-  'devtools_frontend_revision': '4cc0688b9538f497ae06e3a0f97431ffc86f4181',
+  'devtools_frontend_revision': 'c049d058ae5973f47a21fb1e4eb7a224174ef032',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling libprotobuf-mutator
   # and whatever else without interference from each other.
@@ -415,7 +415,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
-  'dawn_revision': 'f4cae4afdd4bca082d36025c66c7c284ed16a67f',
+  'dawn_revision': '606e03a22bfe5686be18fcc934555f55f987c1e9',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
@@ -519,7 +519,7 @@
   # 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':    '61d7ed30110f97d6842304bc9035ee9cdae6b5e5',
+  'llvm_libc_revision':    '4a2940b40b394ca57312aa9bbc8af430fe9a5340',
   # 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.
@@ -1486,7 +1486,7 @@
 
   'src/clank': {
     'url': Var('chrome_git') + '/clank/internal/apps.git' + '@' +
-    'af42a48a53e7717ae26d7cd6e90195216fb9fe51',
+    '687392ba3de05f6fae3002d7b7435b231d048dce',
     'condition': 'checkout_android and checkout_src_internal',
   },
 
@@ -1645,7 +1645,7 @@
     'packages': [
       {
           'package': 'chromium/third_party/androidx',
-          'version': 'HYTISQQTMh5XcYAUlGBAU6HIXb_FD0kMzljZh0v03j0C',
+          'version': '1jSxEkUFGSfsQTosCWjXFmS0h3Lk_BRPBBLwBNosOVYC',
       },
     ],
     'condition': 'checkout_android and non_git_source',
@@ -1978,7 +1978,7 @@
 
 
   'src/third_party/depot_tools':
-    Var('chromium_git') + '/chromium/tools/depot_tools.git' + '@' + 'd1f9fa6c922b20d8034fe4c7f3a62f8a824c561b',
+    Var('chromium_git') + '/chromium/tools/depot_tools.git' + '@' + '977c37458fda472d8822a8b57e4a83a7bc747471',
 
   'src/third_party/devtools-frontend/src':
     Var('chromium_git') + '/devtools/devtools-frontend' + '@' + Var('devtools_frontend_revision'),
@@ -2526,7 +2526,7 @@
     Var('pdfium_git') + '/pdfium.git' + '@' +  Var('pdfium_revision'),
 
   'src/third_party/perfetto':
-    Var('chromium_git') + '/external/github.com/google/perfetto.git' + '@' + '829564494ca9d7bc1c8b15998c5f1f3536cdacf8',
+    Var('chromium_git') + '/external/github.com/google/perfetto.git' + '@' + '6ffe623d283575c00047cf9abf90c3659505a592',
 
   'src/base/tracing/test/data': {
     'bucket': 'perfetto',
@@ -4614,7 +4614,7 @@
 
   'src/components/optimization_guide/internal': {
       'url': Var('chrome_git') + '/chrome/components/optimization_guide.git' + '@' +
-        '6032e32fa8b82048023e47709f1e5a22dd277791',
+        '3633af3203b28054ee2d0aafd69b7a687dce4d1c',
       'condition': 'checkout_src_internal',
   },
 
diff --git a/android_webview/javatests/src/org/chromium/android_webview/test/ClientOnReceivedHttpErrorTest.java b/android_webview/javatests/src/org/chromium/android_webview/test/ClientOnReceivedHttpErrorTest.java
index b482b10..71982b0 100644
--- a/android_webview/javatests/src/org/chromium/android_webview/test/ClientOnReceivedHttpErrorTest.java
+++ b/android_webview/javatests/src/org/chromium/android_webview/test/ClientOnReceivedHttpErrorTest.java
@@ -23,9 +23,11 @@
 import org.chromium.android_webview.AwWebResourceRequest;
 import org.chromium.android_webview.test.util.AwTestTouchUtils;
 import org.chromium.android_webview.test.util.CommonResources;
+import org.chromium.base.ThreadUtils;
 import org.chromium.base.test.util.Feature;
 import org.chromium.components.embedder_support.util.WebResourceResponseInfo;
 import org.chromium.content_public.browser.test.util.TestCallbackHelperContainer;
+import org.chromium.content_public.browser.test.util.WebContentsUtils;
 import org.chromium.net.test.util.TestWebServer;
 
 import java.util.ArrayList;
@@ -152,6 +154,8 @@
                         null);
         AwActivityTestRule.enableJavaScriptOnUiThread(mAwContents);
         mActivityTestRule.loadUrlAsync(mAwContents, pageWithLinkUrl);
+        ThreadUtils.runOnUiThreadBlocking(
+                () -> WebContentsUtils.simulateEndOfPaintHolding(mAwContents.getWebContents()));
         mActivityTestRule.waitForPixelColorAtCenterOfView(
                 mAwContents, mTestContainerView, CommonResources.LINK_COLOR);
 
diff --git a/android_webview/javatests/src/org/chromium/android_webview/test/ContextMenuTest.java b/android_webview/javatests/src/org/chromium/android_webview/test/ContextMenuTest.java
index 0dd432c..eed1505 100644
--- a/android_webview/javatests/src/org/chromium/android_webview/test/ContextMenuTest.java
+++ b/android_webview/javatests/src/org/chromium/android_webview/test/ContextMenuTest.java
@@ -47,6 +47,7 @@
 import org.chromium.android_webview.contextmenu.AwContextMenuItemDelegate;
 import org.chromium.android_webview.contextmenu.AwContextMenuPopulator;
 import org.chromium.android_webview.test.AwActivityTestRule.TestDependencyFactory;
+import org.chromium.base.ThreadUtils;
 import org.chromium.base.test.util.Batch;
 import org.chromium.base.test.util.CallbackHelper;
 import org.chromium.base.test.util.CriteriaHelper;
@@ -56,6 +57,7 @@
 import org.chromium.components.embedder_support.contextmenu.ContextMenuParams;
 import org.chromium.content_public.browser.test.util.DOMUtils;
 import org.chromium.content_public.browser.test.util.TestTouchUtils;
+import org.chromium.content_public.browser.test.util.WebContentsUtils;
 import org.chromium.net.test.util.TestWebServer;
 import org.chromium.ui.modelutil.MVCListAdapter.ModelList;
 import org.chromium.ui.mojom.MenuSourceType;
@@ -452,6 +454,8 @@
         mRule.loadUrlSync(
                 mTestContainerView.getAwContents(), mContentsClient.getOnPageFinishedHelper(), url);
         done.waitForCallback(callCount);
+        ThreadUtils.runOnUiThreadBlocking(
+                () -> WebContentsUtils.simulateEndOfPaintHolding(mAwContents.getWebContents()));
     }
 
     private void assertStringContains(String subString, String superString) {
diff --git a/android_webview/javatests/src/org/chromium/android_webview/test/PopupWindowTest.java b/android_webview/javatests/src/org/chromium/android_webview/test/PopupWindowTest.java
index 8a577e8..ce36fed 100644
--- a/android_webview/javatests/src/org/chromium/android_webview/test/PopupWindowTest.java
+++ b/android_webview/javatests/src/org/chromium/android_webview/test/PopupWindowTest.java
@@ -40,6 +40,7 @@
 import org.chromium.content_public.browser.SelectionPopupController;
 import org.chromium.content_public.browser.test.util.DOMUtils;
 import org.chromium.content_public.browser.test.util.TestCallbackHelperContainer;
+import org.chromium.content_public.browser.test.util.WebContentsUtils;
 import org.chromium.net.test.util.TestWebServer;
 
 import java.util.List;
@@ -179,7 +180,6 @@
                         mParentContents, mParentContentsClient, "navigator.userAgent");
 
         final String popupPath = "/popup.html";
-        final String myUserAgentString = "myUserAgent";
         final String parentPageHtml =
                 CommonResources.makeHtmlPageFrom(
                         "",
@@ -248,7 +248,6 @@
                 "tryOpenWindow()");
 
         PopupInfo popupInfo = mActivityTestRule.createPopupContents(mParentContents);
-        TestAwContentsClient popupContentsClient = popupInfo.popupContentsClient;
         final AwContents popupContents = popupInfo.popupContents;
 
         // Override the user agent string for the popup window.
@@ -436,12 +435,12 @@
                         hasOpener ? "" : "rel=\"noopener noreferrer\"");
         final String mainHtml = CommonResources.makeHtmlPageFrom("", body);
         final String openerUrl = mWebServer.setResponse("/popupOpener.html", mainHtml, null);
-        final String popupUrl =
-                mWebServer.setResponse(
-                        "/popup.html",
-                        CommonResources.makeHtmlPageFrom(
-                                "<title>" + POPUP_TITLE + "</title>", "This is a popup window"),
-                        null);
+
+        mWebServer.setResponse(
+                "/popup.html",
+                CommonResources.makeHtmlPageFrom(
+                        "<title>" + POPUP_TITLE + "</title>", "This is a popup window"),
+                null);
 
         mParentContentsClient.getOnCreateWindowHelper().setReturnValue(true);
         mActivityTestRule.loadUrlSync(
@@ -633,6 +632,10 @@
         // https://crbug.com/1334843
         mParentContentsClient.getOnPageCommitVisibleHelper().waitForOnly();
 
+        // Force an end of paint-holding which is irrelevant here and can block input events.
+        ThreadUtils.runOnUiThreadBlocking(
+                () -> WebContentsUtils.simulateEndOfPaintHolding(mParentContents.getWebContents()));
+
         // Step 4. Click iframe_link to give user gesture.
         DOMUtils.clickRect(mParentContents.getWebContents(), rect);
 
diff --git a/android_webview/javatests/src/org/chromium/android_webview/test/WebViewModalDialogOverrideTest.java b/android_webview/javatests/src/org/chromium/android_webview/test/WebViewModalDialogOverrideTest.java
index 00c5e542..06cbfc1 100644
--- a/android_webview/javatests/src/org/chromium/android_webview/test/WebViewModalDialogOverrideTest.java
+++ b/android_webview/javatests/src/org/chromium/android_webview/test/WebViewModalDialogOverrideTest.java
@@ -24,6 +24,7 @@
 import org.chromium.base.test.util.Feature;
 import org.chromium.content_public.browser.GestureListenerManager;
 import org.chromium.content_public.browser.GestureStateListener;
+import org.chromium.content_public.browser.test.util.WebContentsUtils;
 
 import java.util.concurrent.atomic.AtomicBoolean;
 
@@ -251,6 +252,9 @@
                 BEFORE_UNLOAD_URL,
                 "text/html",
                 false);
+        ThreadUtils.runOnUiThreadBlocking(
+                () -> WebContentsUtils.simulateEndOfPaintHolding(awContents.getWebContents()));
+
         AwActivityTestRule.enableJavaScriptOnUiThread(awContents);
         // JavaScript onbeforeunload dialogs require a user gesture.
         tapViewAndWait(view);
diff --git a/chrome/VERSION b/chrome/VERSION
index 72218cbc..86c1d06 100644
--- a/chrome/VERSION
+++ b/chrome/VERSION
@@ -1,4 +1,4 @@
 MAJOR=138
 MINOR=0
-BUILD=7172
+BUILD=7173
 PATCH=0
diff --git a/chrome/android/features/tab_ui/java/res/drawable/chevron_right.xml b/chrome/android/features/tab_ui/java/res/drawable/chevron_right.xml
index 1cba635d..ff032bf1 100644
--- a/chrome/android/features/tab_ui/java/res/drawable/chevron_right.xml
+++ b/chrome/android/features/tab_ui/java/res/drawable/chevron_right.xml
@@ -5,13 +5,14 @@
 found in the LICENSE file.
 -->
 
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
-android:width="24dp"
-android:height="24dp"
-android:viewportWidth="24"
-android:viewportHeight="24"
-android:tint="@macro/default_icon_color">
-<path
-    android:fillColor="@android:color/white"
-    android:pathData="M7.59,18.59L9,20l8,-8 -8,-8 -1.41,1.41L14.17,12"/>
+<vector
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24"
+    android:tint="@macro/default_icon_color">
+    <path
+        android:fillColor="@android:color/white"
+        android:pathData="M7.59,18.59L9,20l8,-8 -8,-8 -1.41,1.41L14.17,12"/>
 </vector>
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridDialogCoordinator.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridDialogCoordinator.java
index 510fb48..41d0730 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridDialogCoordinator.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridDialogCoordinator.java
@@ -218,7 +218,6 @@
                             dataSharingTabManager,
                             mComponentName,
                             showColorPickerPopupRunnable,
-                            actionConfirmationManager,
                             modalDialogManager,
                             desktopWindowStateManager,
                             tabBookmarkerSupplier,
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridDialogMediator.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridDialogMediator.java
index 86328b2..2617593f 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridDialogMediator.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridDialogMediator.java
@@ -48,7 +48,6 @@
 import org.chromium.chrome.browser.tab.TabLaunchType;
 import org.chromium.chrome.browser.tab.TabSelectionType;
 import org.chromium.chrome.browser.tab_group_sync.TabGroupSyncServiceFactory;
-import org.chromium.chrome.browser.tab_ui.ActionConfirmationManager;
 import org.chromium.chrome.browser.tab_ui.RecyclerViewPosition;
 import org.chromium.chrome.browser.tab_ui.TabUiThemeUtils;
 import org.chromium.chrome.browser.tabmodel.TabGroupModelFilter;
@@ -81,6 +80,7 @@
 import org.chromium.components.browser_ui.styles.SemanticColorUtils;
 import org.chromium.components.browser_ui.widget.gesture.BackPressHandler;
 import org.chromium.components.collaboration.CollaborationService;
+import org.chromium.components.collaboration.CollaborationServiceLeaveOrDeleteEntryPoint;
 import org.chromium.components.collaboration.CollaborationServiceShareOrManageEntryPoint;
 import org.chromium.components.collaboration.messaging.CollaborationEvent;
 import org.chromium.components.collaboration.messaging.MessageUtils;
@@ -226,8 +226,6 @@
     private final DataSharingTabManager mDataSharingTabManager;
     private final String mComponentName;
     private final Runnable mShowColorPickerPopupRunnable;
-    private final ActionConfirmationManager mActionConfirmationManager;
-    private final ModalDialogManager mModalDialogManager;
     private final Profile mOriginalProfile;
     private final @Nullable TabGroupSyncService mTabGroupSyncService;
     private final @Nullable DataSharingService mDataSharingService;
@@ -267,7 +265,6 @@
             @NonNull DataSharingTabManager dataSharingTabManager,
             String componentName,
             Runnable showColorPickerPopupRunnable,
-            @Nullable ActionConfirmationManager actionConfirmationManager,
             @Nullable ModalDialogManager modalDialogManager,
             @Nullable DesktopWindowStateManager desktopWindowStateManager,
             ObservableSupplier<TabBookmarker> tabBookmarkerSupplier,
@@ -286,8 +283,6 @@
         mDataSharingTabManager = dataSharingTabManager;
         mComponentName = componentName;
         mShowColorPickerPopupRunnable = showColorPickerPopupRunnable;
-        mActionConfirmationManager = actionConfirmationManager;
-        mModalDialogManager = modalDialogManager;
         mOriginalProfile =
                 mCurrentTabGroupModelFilterSupplier
                         .get()
@@ -1050,6 +1045,7 @@
                         mTransitiveSharedGroupObserver.getCollaborationIdSupplier().get());
 
         int tabId = mCurrentTabId;
+        EitherGroupId eitherId = EitherGroupId.createLocalId(new LocalTabGroupId(tabGroupId));
         if (tabId == Tab.INVALID_TAB_ID) return;
 
         if (menuId == R.id.ungroup_tab || menuId == R.id.select_tabs) {
@@ -1070,8 +1066,7 @@
         } else if (menuId == R.id.manage_sharing) {
             RecordUserAction.record("TabGridDialogMenu.ManageSharing");
             mDataSharingTabManager.createOrManageFlow(
-                    mActivity,
-                    EitherGroupId.createLocalId(new LocalTabGroupId(tabGroupId)),
+                    eitherId,
                     CollaborationServiceShareOrManageEntryPoint.ANDROID_TAB_GRID_DIALOG_MANAGE,
                     /* createGroupFinishedCallback= */ null);
         } else if (menuId == R.id.recent_activity) {
@@ -1093,20 +1088,14 @@
                     /* didCloseCallback= */ null);
         } else if (menuId == R.id.delete_shared_group) {
             RecordUserAction.record("TabGridDialogMenu.DeleteShared");
-            TabUiUtils.exitSharedTabGroupWithDialog(
-                    mActivity,
-                    mCurrentTabGroupModelFilterSupplier.get(),
-                    mActionConfirmationManager,
-                    mModalDialogManager,
-                    tabId);
+            mDataSharingTabManager.leaveOrDeleteFlow(
+                    eitherId,
+                    CollaborationServiceLeaveOrDeleteEntryPoint.ANDROID_TAB_GRID_DIALOG_DELETE);
         } else if (menuId == R.id.leave_group) {
             RecordUserAction.record("TabGridDialogMenu.LeaveShared");
-            TabUiUtils.exitSharedTabGroupWithDialog(
-                    mActivity,
-                    mCurrentTabGroupModelFilterSupplier.get(),
-                    mActionConfirmationManager,
-                    mModalDialogManager,
-                    tabId);
+            mDataSharingTabManager.leaveOrDeleteFlow(
+                    eitherId,
+                    CollaborationServiceLeaveOrDeleteEntryPoint.ANDROID_TAB_GRID_DIALOG_LEAVE);
         }
     }
 
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupListCoordinator.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupListCoordinator.java
index 45b46c1..736c5ff 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupListCoordinator.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupListCoordinator.java
@@ -21,6 +21,7 @@
 import org.chromium.chrome.browser.collaboration.CollaborationServiceFactory;
 import org.chromium.chrome.browser.collaboration.messaging.MessagingBackendServiceFactory;
 import org.chromium.chrome.browser.data_sharing.DataSharingServiceFactory;
+import org.chromium.chrome.browser.data_sharing.DataSharingTabManager;
 import org.chromium.chrome.browser.hub.PaneManager;
 import org.chromium.chrome.browser.profiles.Profile;
 import org.chromium.chrome.browser.profiles.ProfileProvider;
@@ -79,6 +80,7 @@
      * @param modalDialogManager Used to show confirmation dialogs.
      * @param onIsScrolledChanged To be invoked whenever the scrolled state changes.
      * @param edgeToEdgeSupplier Supplier to the {@link EdgeToEdgeController} instance.
+     * @param dataSharingTabManager The {@link} DataSharingTabManager to start collaboration flows.
      */
     public TabGroupListCoordinator(
             Context context,
@@ -88,7 +90,8 @@
             TabGroupUiActionHandler tabGroupUiActionHandler,
             ModalDialogManager modalDialogManager,
             Consumer<Boolean> onIsScrolledChanged,
-            @NonNull ObservableSupplier<EdgeToEdgeController> edgeToEdgeSupplier) {
+            @NonNull ObservableSupplier<EdgeToEdgeController> edgeToEdgeSupplier,
+            @NonNull DataSharingTabManager dataSharingTabManager) {
         ModelList modelList = new ModelList();
         mSimpleRecyclerViewAdapter =
                 new SimpleRecyclerViewAdapter(modelList) {
@@ -178,8 +181,8 @@
                         tabGroupUiActionHandler,
                         actionConfirmationManager,
                         syncService,
-                        modalDialogManager,
-                        enableContainment());
+                        enableContainment(),
+                        dataSharingTabManager);
 
         if (EdgeToEdgeUtils.isDrawKeyNativePageToEdgeEnabled()) {
             mEdgeToEdgePadAdjuster =
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupListMediator.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupListMediator.java
index 609586e..cde008d1 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupListMediator.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupListMediator.java
@@ -29,6 +29,7 @@
 import org.chromium.base.CallbackController;
 import org.chromium.base.task.TaskTraits;
 import org.chromium.chrome.browser.bookmarks.PendingRunnable;
+import org.chromium.chrome.browser.data_sharing.DataSharingTabManager;
 import org.chromium.chrome.browser.hub.PaneManager;
 import org.chromium.chrome.browser.tab.Tab;
 import org.chromium.chrome.browser.tab_ui.ActionConfirmationManager;
@@ -53,7 +54,6 @@
 import org.chromium.components.tab_group_sync.TabGroupSyncService.Observer;
 import org.chromium.components.tab_group_sync.TabGroupUiActionHandler;
 import org.chromium.components.tab_group_sync.TriggerSource;
-import org.chromium.ui.modaldialog.ModalDialogManager;
 import org.chromium.ui.modelutil.MVCListAdapter.ListItem;
 import org.chromium.ui.modelutil.MVCListAdapter.ModelList;
 import org.chromium.ui.modelutil.PropertyModel;
@@ -76,7 +76,6 @@
     private final TabGroupUiActionHandler mTabGroupUiActionHandler;
     private final ActionConfirmationManager mActionConfirmationManager;
     private final SyncService mSyncService;
-    private final ModalDialogManager mModalDialogManager;
     private final CallbackController mCallbackController = new CallbackController();
     private final @NonNull MessagingBackendService mMessagingBackendService;
     private final PendingRunnable mPendingRefresh =
@@ -84,6 +83,7 @@
                     TaskTraits.UI_DEFAULT,
                     mCallbackController.makeCancelable(this::repopulateModelList));
     private final boolean mEnableContainment;
+    private final DataSharingTabManager mDataSharingTabManager;
 
     private final TabModelObserver mTabModelObserver =
             new TabModelObserver() {
@@ -204,8 +204,8 @@
      * @param tabGroupUiActionHandler Used to open hidden tab groups.
      * @param actionConfirmationManager Used to show confirmation dialogs.
      * @param syncService Used to query active sync types.
-     * @param modalDialogManager Used to show error dialogs.
      * @param enableContainment Whether containment is enabled.
+     * @param dataSharingTabManager The {@link} DataSharingTabManager to start collaboration flows.
      */
     public TabGroupListMediator(
             Context context,
@@ -221,8 +221,8 @@
             TabGroupUiActionHandler tabGroupUiActionHandler,
             ActionConfirmationManager actionConfirmationManager,
             SyncService syncService,
-            ModalDialogManager modalDialogManager,
-            boolean enableContainment) {
+            boolean enableContainment,
+            @NonNull DataSharingTabManager dataSharingTabManager) {
         mContext = context;
         mModelList = modelList;
         mPropertyModel = propertyModel;
@@ -236,8 +236,8 @@
         mTabGroupUiActionHandler = tabGroupUiActionHandler;
         mActionConfirmationManager = actionConfirmationManager;
         mSyncService = syncService;
-        mModalDialogManager = modalDialogManager;
         mEnableContainment = enableContainment;
+        mDataSharingTabManager = dataSharingTabManager;
 
         mFilter.addObserver(mTabModelObserver);
         if (mTabGroupSyncService != null) {
@@ -291,11 +291,11 @@
                             mCollaborationService,
                             mPaneManager,
                             mTabGroupUiActionHandler,
-                            mModalDialogManager,
                             mActionConfirmationManager,
                             mFaviconResolver,
                             () -> sortUtil.getState(savedTabGroup),
-                            mEnableContainment);
+                            mEnableContainment,
+                            mDataSharingTabManager);
             ListItem listItem = new ListItem(RowType.TAB_GROUP, rowMediator.getModel());
             mModelList.add(listItem);
         }
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupListMediatorUnitTest.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupListMediatorUnitTest.java
index f30ecba..2e97c5c 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupListMediatorUnitTest.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupListMediatorUnitTest.java
@@ -57,6 +57,7 @@
 import org.chromium.base.test.util.Features.DisableFeatures;
 import org.chromium.base.test.util.Features.EnableFeatures;
 import org.chromium.chrome.browser.collaboration.messaging.MessagingBackendServiceFactory;
+import org.chromium.chrome.browser.data_sharing.DataSharingTabManager;
 import org.chromium.chrome.browser.flags.ChromeFeatureList;
 import org.chromium.chrome.browser.hub.PaneId;
 import org.chromium.chrome.browser.hub.PaneManager;
@@ -94,8 +95,6 @@
 import org.chromium.components.tab_group_sync.TriggerSource;
 import org.chromium.components.tab_groups.TabGroupColorId;
 import org.chromium.ui.modaldialog.ModalDialogManager;
-import org.chromium.ui.modaldialog.ModalDialogProperties;
-import org.chromium.ui.modaldialog.ModalDialogProperties.ButtonType;
 import org.chromium.ui.modelutil.MVCListAdapter.ModelList;
 import org.chromium.ui.modelutil.PropertyModel;
 import org.chromium.ui.test.util.MockitoHelper;
@@ -143,6 +142,7 @@
     @Mock private ModalDialogManager mModalDialogManager;
     @Mock private MessagingBackendService mMessagingBackendService;
     @Mock private Runnable mFinishBlocking;
+    @Mock private DataSharingTabManager mDataSharingTabManager;
 
     @Captor private ArgumentCaptor<TabModelObserver> mTabModelObserver;
     @Captor private ArgumentCaptor<TabGroupSyncService.Observer> mTabGroupSyncObserverCaptor;
@@ -206,8 +206,8 @@
                         mTabGroupUiActionHandler,
                         mActionConfirmationManager,
                         mSyncService,
-                        mModalDialogManager,
-                        /* enableContainment= */ true);
+                        /* enableContainment= */ true,
+                        mDataSharingTabManager);
         verify(mSyncService).addSyncStateChangedListener(mSyncStateChangedListenerCaptor.capture());
         return mediator;
     }
@@ -650,21 +650,7 @@
         assertNotNull(model.get(DELETE_RUNNABLE));
         model.get(DELETE_RUNNABLE).run();
 
-        verify(mActionConfirmationManager)
-                .processDeleteSharedGroupAttempt(
-                        any(), mMaybeBlockingResultCallbackCaptor.capture());
-        mMaybeBlockingResultCallbackCaptor
-                .getValue()
-                .onResult(
-                        new MaybeBlockingResult(
-                                ActionConfirmationResult.CONFIRMATION_POSITIVE, mFinishBlocking));
-
-        verify(mCollaborationService)
-                .deleteGroup(eq(COLLABORATION_ID1), mDeleteGroupResultCallbackCaptor.capture());
-        mDeleteGroupResultCallbackCaptor.getValue().onResult(true);
-        verify(mFinishBlocking).run();
-        verify(mModalDialogManager, never())
-                .showDialog(mModalPropertyModelCaptor.capture(), anyInt());
+        verify(mDataSharingTabManager).leaveOrDeleteFlow(any(), anyInt());
     }
 
     @Test
@@ -692,25 +678,7 @@
         assertNotNull(model.get(LEAVE_RUNNABLE));
         model.get(LEAVE_RUNNABLE).run();
 
-        verify(mActionConfirmationManager)
-                .processLeaveGroupAttempt(any(), mMaybeBlockingResultCallbackCaptor.capture());
-        mMaybeBlockingResultCallbackCaptor
-                .getValue()
-                .onResult(
-                        new MaybeBlockingResult(
-                                ActionConfirmationResult.CONFIRMATION_POSITIVE, mFinishBlocking));
-
-        verify(mCollaborationService)
-                .leaveGroup(eq(COLLABORATION_ID1), mDeleteGroupResultCallbackCaptor.capture());
-        mDeleteGroupResultCallbackCaptor.getValue().onResult(false);
-        verify(mFinishBlocking).run();
-
-        verify(mModalDialogManager).showDialog(mModalPropertyModelCaptor.capture(), anyInt());
-
-        ModalDialogProperties.Controller controller =
-                mModalPropertyModelCaptor.getValue().get(ModalDialogProperties.CONTROLLER);
-        controller.onClick(mModalPropertyModelCaptor.getValue(), ButtonType.POSITIVE);
-        verify(mModalDialogManager).dismissDialog(any(), anyInt());
+        verify(mDataSharingTabManager).leaveOrDeleteFlow(any(), anyInt());
     }
 
     @Test
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupRowMediator.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupRowMediator.java
index a434cb2..edcf1af 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupRowMediator.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupRowMediator.java
@@ -8,7 +8,6 @@
 import static org.chromium.chrome.browser.tasks.tab_management.TabGroupRowProperties.LEAVE_RUNNABLE;
 
 import android.content.Context;
-import android.text.TextUtils;
 
 import androidx.annotation.ColorInt;
 import androidx.annotation.Nullable;
@@ -18,30 +17,30 @@
 import org.chromium.base.metrics.RecordHistogram;
 import org.chromium.base.metrics.RecordUserAction;
 import org.chromium.chrome.R;
+import org.chromium.chrome.browser.data_sharing.DataSharingTabManager;
 import org.chromium.chrome.browser.data_sharing.ui.shared_image_tiles.SharedImageTilesConfig;
 import org.chromium.chrome.browser.data_sharing.ui.shared_image_tiles.SharedImageTilesCoordinator;
 import org.chromium.chrome.browser.hub.PaneId;
 import org.chromium.chrome.browser.hub.PaneManager;
 import org.chromium.chrome.browser.tab.Tab;
 import org.chromium.chrome.browser.tab_ui.ActionConfirmationManager;
-import org.chromium.chrome.browser.tab_ui.ActionConfirmationManager.MaybeBlockingResult;
 import org.chromium.chrome.browser.tabmodel.TabClosureParams;
 import org.chromium.chrome.browser.tabmodel.TabGroupModelFilter;
-import org.chromium.chrome.browser.tabmodel.TabGroupTitleUtils;
 import org.chromium.chrome.browser.tasks.tab_management.TabGroupFaviconCluster.ClusterData;
 import org.chromium.chrome.browser.tasks.tab_management.TabGroupRowView.TabGroupRowViewTitleData;
 import org.chromium.chrome.browser.tasks.tab_management.TabGroupTimeAgo.TimestampEvent;
 import org.chromium.components.browser_ui.styles.SemanticColorUtils;
 import org.chromium.components.browser_ui.widget.ActionConfirmationResult;
 import org.chromium.components.collaboration.CollaborationService;
+import org.chromium.components.collaboration.CollaborationServiceLeaveOrDeleteEntryPoint;
 import org.chromium.components.data_sharing.DataSharingService;
 import org.chromium.components.data_sharing.GroupData;
 import org.chromium.components.data_sharing.member_role.MemberRole;
+import org.chromium.components.tab_group_sync.EitherId.EitherGroupId;
 import org.chromium.components.tab_group_sync.SavedTabGroup;
 import org.chromium.components.tab_group_sync.SavedTabGroupTab;
 import org.chromium.components.tab_group_sync.TabGroupSyncService;
 import org.chromium.components.tab_group_sync.TabGroupUiActionHandler;
-import org.chromium.ui.modaldialog.ModalDialogManager;
 import org.chromium.ui.modelutil.PropertyModel;
 import org.chromium.url.GURL;
 
@@ -58,10 +57,10 @@
     private final CollaborationService mCollaborationService;
     private final PaneManager mPaneManager;
     private final TabGroupUiActionHandler mTabGroupUiActionHandler;
-    private final ModalDialogManager mModalDialogManager;
     private final ActionConfirmationManager mActionConfirmationManager;
     private final Supplier<@GroupWindowState Integer> mFetchGroupState;
     private final PropertyModel mPropertyModel;
+    private final DataSharingTabManager mDataSharingTabManager;
 
     private SharedImageTilesCoordinator mSharedImageTilesCoordinator;
 
@@ -73,11 +72,11 @@
      * @param collaborationService Used to fetch collaboration group data.
      * @param paneManager Used switch panes to show details of a group.
      * @param tabGroupUiActionHandler Used to open hidden tab groups.
-     * @param modalDialogManager Used to show error dialogs.
      * @param actionConfirmationManager Used to show confirmation dialogs.
      * @param faviconResolver Used to fetch favicon images for some tabs.
      * @param fetchGroupState Used to fetch which window the group is in.
      * @param enableContainment Whether the tab group row is in a container.
+     * @param dataSharingTabManager The {@link} DataSharingTabManager to start collaboration flows.
      */
     public TabGroupRowMediator(
             Context context,
@@ -88,11 +87,11 @@
             CollaborationService collaborationService,
             PaneManager paneManager,
             TabGroupUiActionHandler tabGroupUiActionHandler,
-            ModalDialogManager modalDialogManager,
             ActionConfirmationManager actionConfirmationManager,
             FaviconResolver faviconResolver,
             Supplier<@GroupWindowState Integer> fetchGroupState,
-            boolean enableContainment) {
+            boolean enableContainment,
+            DataSharingTabManager dataSharingTabManager) {
         mContext = context;
         mSavedTabGroup = savedTabGroup;
         mTabGroupModelFilter = tabGroupModelFilter;
@@ -101,9 +100,9 @@
         mDataSharingService = dataSharingService;
         mCollaborationService = collaborationService;
         mTabGroupUiActionHandler = tabGroupUiActionHandler;
-        mModalDialogManager = modalDialogManager;
         mActionConfirmationManager = actionConfirmationManager;
         mFetchGroupState = fetchGroupState;
+        mDataSharingTabManager = dataSharingTabManager;
 
         PropertyModel.Builder builder = new PropertyModel.Builder(TabGroupRowProperties.ALL_KEYS);
         int numberOfTabs = savedTabGroup.savedTabs.size();
@@ -135,7 +134,7 @@
             groupData = mCollaborationService.getGroupData(collaborationId);
             sharedState = TabShareUtils.discernSharedGroupState(groupData);
         }
-        setSharedProperties(sharedState, groupData, numberOfTabs, enableContainment);
+        setSharedProperties(sharedState, groupData, enableContainment, savedTabGroup);
     }
 
     /**
@@ -157,8 +156,8 @@
     private void setSharedProperties(
             @GroupSharedState int sharedState,
             @Nullable GroupData groupData,
-            int numberOfTabs,
-            boolean enableContainment) {
+            boolean enableContainment,
+            SavedTabGroup savedTabGroup) {
         if (sharedState == GroupSharedState.NOT_SHARED) {
             mPropertyModel.set(DELETE_RUNNABLE, this::processDeleteGroup);
             mPropertyModel.set(LEAVE_RUNNABLE, null);
@@ -168,19 +167,17 @@
         }
 
         String collaborationId = groupData.groupToken.collaborationId;
-        String groupTitle = groupTitleWithFallback(groupData, numberOfTabs);
         @MemberRole
         int memberRole = mCollaborationService.getCurrentUserRoleForGroup(collaborationId);
         if (memberRole == MemberRole.OWNER) {
             mPropertyModel.set(
-                    DELETE_RUNNABLE, () -> processDeleteSharedGroup(groupTitle, collaborationId));
+                    DELETE_RUNNABLE, () -> processLeaveOrDeleteShareGroup(savedTabGroup));
             mPropertyModel.set(LEAVE_RUNNABLE, null);
         } else {
             // TODO(crbug.com/365852281): Leave action should look like a delete if there are no
             // other users.
             mPropertyModel.set(DELETE_RUNNABLE, null);
-            mPropertyModel.set(
-                    LEAVE_RUNNABLE, () -> processLeaveGroup(groupTitle, collaborationId));
+            mPropertyModel.set(LEAVE_RUNNABLE, () -> processLeaveOrDeleteShareGroup(savedTabGroup));
         }
 
         if (sharedState == GroupSharedState.COLLABORATION_ONLY) {
@@ -272,46 +269,16 @@
         }
     }
 
-    private void processDeleteSharedGroup(String groupTitle, String collaborationId) {
-        // TODO(crbug.com/365852281): Confirmation should look like a non-shared delete if there are
-        // no other users.
-        mActionConfirmationManager.processDeleteSharedGroupAttempt(
-                groupTitle,
-                (result) -> {
-                    exitCollaborationWithoutWarningWrapper(
-                            collaborationId, result, MemberRole.OWNER);
-                });
-    }
-
-    private void processLeaveGroup(String groupTitle, String collaborationId) {
-        // TODO(crbug.com/365852281): Confirmation should look like a non-shared delete if there are
-        // no other users.
-        mActionConfirmationManager.processLeaveGroupAttempt(
-                groupTitle,
-                (result) -> {
-                    exitCollaborationWithoutWarningWrapper(
-                            collaborationId, result, MemberRole.MEMBER);
-                });
-    }
-
-    private void exitCollaborationWithoutWarningWrapper(
-            String collaborationId,
-            MaybeBlockingResult maybeBlockingResult,
-            @MemberRole int memberRole) {
-        if (maybeBlockingResult.result != ActionConfirmationResult.CONFIRMATION_NEGATIVE) {
-            assert maybeBlockingResult.finishBlocking != null;
-            TabUiUtils.exitCollaborationWithoutWarning(
-                    mContext,
-                    mModalDialogManager,
-                    mCollaborationService,
-                    collaborationId,
-                    memberRole,
-                    maybeBlockingResult.finishBlocking);
-        } else if (maybeBlockingResult.finishBlocking != null) {
-            assert false : "Should not be reachable.";
-            // Do the safe thing and run the runnable anyway.
-            maybeBlockingResult.finishBlocking.run();
+    private void processLeaveOrDeleteShareGroup(SavedTabGroup savedTabGroup) {
+        EitherGroupId eitherId;
+        if (savedTabGroup.syncId != null) {
+            eitherId = EitherGroupId.createSyncId(savedTabGroup.syncId);
+        } else {
+            eitherId = EitherGroupId.createLocalId(savedTabGroup.localId);
         }
+
+        mDataSharingTabManager.leaveOrDeleteFlow(
+                eitherId, CollaborationServiceLeaveOrDeleteEntryPoint.ANDROID_TAB_GROUP_ROW);
     }
 
     private void deleteGroup(boolean allowDialog) {
@@ -351,13 +318,4 @@
             mTabGroupSyncService.removeGroup(mSavedTabGroup.syncId);
         }
     }
-
-    private String groupTitleWithFallback(GroupData groupData, int numberOfTabs) {
-        String groupTitle = groupData.displayName;
-        if (TextUtils.isEmpty(groupTitle)) {
-            return TabGroupTitleUtils.getDefaultTitle(mContext, numberOfTabs);
-        } else {
-            return groupTitle;
-        }
-    }
 }
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupRowMediatorUnitTest.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupRowMediatorUnitTest.java
index 9f1a97e..c225f4e 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupRowMediatorUnitTest.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupRowMediatorUnitTest.java
@@ -51,6 +51,7 @@
 import org.chromium.base.Token;
 import org.chromium.base.test.BaseRobolectricTestRunner;
 import org.chromium.base.test.util.Features.EnableFeatures;
+import org.chromium.chrome.browser.data_sharing.DataSharingTabManager;
 import org.chromium.chrome.browser.flags.ChromeFeatureList;
 import org.chromium.chrome.browser.hub.PaneId;
 import org.chromium.chrome.browser.hub.PaneManager;
@@ -69,6 +70,7 @@
 import org.chromium.components.data_sharing.GroupMember;
 import org.chromium.components.data_sharing.SharedGroupTestHelper;
 import org.chromium.components.data_sharing.member_role.MemberRole;
+import org.chromium.components.tab_group_sync.EitherId.EitherGroupId;
 import org.chromium.components.tab_group_sync.LocalTabGroupId;
 import org.chromium.components.tab_group_sync.SavedTabGroup;
 import org.chromium.components.tab_group_sync.SavedTabGroupTab;
@@ -107,6 +109,7 @@
     @Mock private FaviconResolver mFaviconResolver;
     @Mock private Supplier<@GroupWindowState Integer> mFetchGroupState;
     @Mock private TabSwitcherPaneBase mTabSwitcherPaneBase;
+    @Mock private DataSharingTabManager mDataSharingTabManager;
 
     @Captor private ArgumentCaptor<Callback<@ActionConfirmationResult Integer>> mConfirmationCaptor;
 
@@ -179,11 +182,11 @@
                         mCollaborationService,
                         mPaneManager,
                         mTabGroupUiActionHandler,
-                        mModalDialogManager,
                         mActionConfirmationManager,
                         mFaviconResolver,
                         mFetchGroupState,
-                        /* enableContainment= */ true);
+                        /* enableContainment= */ true,
+                        mDataSharingTabManager);
         return mediator.getModel();
     }
 
@@ -434,7 +437,8 @@
         assertNotNull(propertyModel.get(DELETE_RUNNABLE));
         assertNull(propertyModel.get(LEAVE_RUNNABLE));
         propertyModel.get(DELETE_RUNNABLE).run();
-        verify(mActionConfirmationManager).processDeleteSharedGroupAttempt(eq(TITLE), any());
+        EitherGroupId eitherId = EitherGroupId.createSyncId(SYNC_GROUP_ID1);
+        verify(mDataSharingTabManager).leaveOrDeleteFlow(eq(eitherId), anyInt());
     }
 
     @Test
@@ -454,6 +458,7 @@
         assertNull(propertyModel.get(DELETE_RUNNABLE));
         assertNotNull(propertyModel.get(LEAVE_RUNNABLE));
         propertyModel.get(LEAVE_RUNNABLE).run();
-        verify(mActionConfirmationManager).processLeaveGroupAttempt(eq("2 tabs"), any());
+        EitherGroupId eitherId = EitherGroupId.createSyncId(SYNC_GROUP_ID1);
+        verify(mDataSharingTabManager).leaveOrDeleteFlow(eq(eitherId), anyInt());
     }
 }
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupsPane.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupsPane.java
index 27e8019..55ff12eb 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupsPane.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupsPane.java
@@ -18,6 +18,7 @@
 import org.chromium.base.supplier.ObservableSupplierImpl;
 import org.chromium.base.supplier.OneshotSupplier;
 import org.chromium.base.supplier.Supplier;
+import org.chromium.chrome.browser.data_sharing.DataSharingTabManager;
 import org.chromium.chrome.browser.flags.ChromeFeatureList;
 import org.chromium.chrome.browser.hub.DelegateButtonData;
 import org.chromium.chrome.browser.hub.DisplayButtonData;
@@ -61,6 +62,7 @@
             new ObservableSupplierImpl<>();
     private final ObservableSupplierImpl<Boolean> mHubSearchEnabledStateSupplier =
             new ObservableSupplierImpl<>();
+    private final DataSharingTabManager mDataSharingTabManager;
 
     private TabGroupListCoordinator mTabGroupListCoordinator;
     private final ObservableSupplier<EdgeToEdgeController> mEdgeToEdgeSupplier;
@@ -74,6 +76,7 @@
      * @param tabGroupUiActionHandlerSupplier Used to open hidden tab groups.
      * @param modalDialogManagerSupplier Used to create confirmation dialogs.
      * @param edgeToEdgeSupplier Supplier to the {@link EdgeToEdgeController} instance.
+     * @param dataSharingTabManager The {@link} DataSharingTabManager to start collaboration flows.
      */
     TabGroupsPane(
             @NonNull Context context,
@@ -83,7 +86,8 @@
             @NonNull Supplier<PaneManager> paneManagerSupplier,
             @NonNull Supplier<TabGroupUiActionHandler> tabGroupUiActionHandlerSupplier,
             @NonNull Supplier<ModalDialogManager> modalDialogManagerSupplier,
-            @NonNull ObservableSupplier<EdgeToEdgeController> edgeToEdgeSupplier) {
+            @NonNull ObservableSupplier<EdgeToEdgeController> edgeToEdgeSupplier,
+            @NonNull DataSharingTabManager dataSharingTabManager) {
         mContext = context;
         mTabGroupModelFilterSupplier = tabGroupModelFilterSupplier;
         mOnToolbarAlphaChange = onToolbarAlphaChange;
@@ -92,6 +96,7 @@
         mTabGroupUiActionHandlerSupplier = tabGroupUiActionHandlerSupplier;
         mModalDialogManagerSupplier = modalDialogManagerSupplier;
         mEdgeToEdgeSupplier = edgeToEdgeSupplier;
+        mDataSharingTabManager = dataSharingTabManager;
         if (ChromeFeatureList.sTabGroupEntryPointsAndroid.isEnabled()) {
             TabGroupCreationUiDelegate flow =
                     new TabGroupCreationUiDelegate(
@@ -168,7 +173,8 @@
                             mTabGroupUiActionHandlerSupplier.get(),
                             mModalDialogManagerSupplier.get(),
                             mHairlineVisibilitySupplier::set,
-                            mEdgeToEdgeSupplier);
+                            mEdgeToEdgeSupplier,
+                            mDataSharingTabManager);
             mRootView.addView(mTabGroupListCoordinator.getView());
         } else if (loadHint == LoadHint.COLD && mTabGroupListCoordinator != null) {
             destroy();
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 c6b44c0f..9078bf6 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
@@ -39,6 +39,7 @@
 import org.chromium.chrome.browser.collaboration.CollaborationServiceFactory;
 import org.chromium.chrome.browser.collaboration.messaging.MessagingBackendServiceFactory;
 import org.chromium.chrome.browser.data_sharing.DataSharingServiceFactory;
+import org.chromium.chrome.browser.data_sharing.DataSharingTabManager;
 import org.chromium.chrome.browser.flags.ChromeFeatureList;
 import org.chromium.chrome.browser.hub.FullButtonData;
 import org.chromium.chrome.browser.hub.LoadHint;
@@ -88,6 +89,7 @@
     @Mock private IdentityServicesProvider mIdentityServicesProvider;
     @Mock private IdentityManager mIdentityManager;
     @Mock private Supplier<PaneManager> mPaneManagerSupplier;
+    @Mock private DataSharingTabManager mDataSharingTabManager;
     @Mock Supplier<TabGroupUiActionHandler> mTabGroupUiActionHandlerSupplier;
     @Mock FaviconHelper.Natives mFaviconHelperJniMock;
     @Mock SyncService mSyncService;
@@ -135,7 +137,8 @@
                         mPaneManagerSupplier,
                         mTabGroupUiActionHandlerSupplier,
                         mModalDialogManagerSupplier,
-                        mEdgeToEdgeSupplier);
+                        mEdgeToEdgeSupplier,
+                        mDataSharingTabManager);
     }
 
     @Test
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListCoordinator.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListCoordinator.java
index a1ca12a..1ee8269 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListCoordinator.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListCoordinator.java
@@ -330,7 +330,6 @@
                         priceWelcomeMessageControllerSupplier,
                         componentName,
                         initialTabActionState,
-                        actionConfirmationManager,
                         dataSharingTabManager,
                         onTabGroupCreation);
 
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListMediator.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListMediator.java
index 7de808c..d6dc1517 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListMediator.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListMediator.java
@@ -72,7 +72,6 @@
 import org.chromium.chrome.browser.tab.state.ShoppingPersistedTabData;
 import org.chromium.chrome.browser.tab_group_sync.TabGroupSyncFeatures;
 import org.chromium.chrome.browser.tab_group_sync.TabGroupSyncServiceFactory;
-import org.chromium.chrome.browser.tab_ui.ActionConfirmationManager;
 import org.chromium.chrome.browser.tab_ui.TabListFaviconProvider;
 import org.chromium.chrome.browser.tab_ui.TabListFaviconProvider.TabFaviconFetcher;
 import org.chromium.chrome.browser.tab_ui.ThumbnailProvider;
@@ -96,6 +95,7 @@
 import org.chromium.chrome.tab_ui.R;
 import org.chromium.components.browser_ui.widget.selectable_list.SelectionDelegate;
 import org.chromium.components.collaboration.CollaborationService;
+import org.chromium.components.collaboration.CollaborationServiceLeaveOrDeleteEntryPoint;
 import org.chromium.components.collaboration.CollaborationServiceShareOrManageEntryPoint;
 import org.chromium.components.data_sharing.DataSharingService;
 import org.chromium.components.embedder_support.util.UrlUtilities;
@@ -375,7 +375,6 @@
     private final GridCardOnClickListenerProvider mGridCardOnClickListenerProvider;
     private final TabGridDialogHandler mTabGridDialogHandler;
     private final Supplier<PriceWelcomeMessageController> mPriceWelcomeMessageControllerSupplier;
-    private final @Nullable ActionConfirmationManager mActionConfirmationManager;
     private final @Nullable DataSharingTabManager mDataSharingTabManager;
     private final Runnable mOnTabGroupCreation;
     private final TabModelObserver mTabModelObserver;
@@ -945,7 +944,6 @@
      * @param componentName This is a unique string to identify different components.
      * @param initialTabActionState The initial {@link TabActionState} to use for the shown tabs.
      *     Must always be CLOSABLE for TabListMode.STRIP.
-     * @param actionConfirmationManager Used for showing confirmation dialogs.
      * @param dataSharingTabManager The service used to initiate data sharing.
      * @param onTabGroupCreation Should be run when the UI is used to create a tab group.
      */
@@ -964,7 +962,6 @@
             @NonNull Supplier<PriceWelcomeMessageController> priceWelcomeMessageControllerSupplier,
             String componentName,
             @TabActionState int initialTabActionState,
-            @Nullable ActionConfirmationManager actionConfirmationManager,
             @Nullable DataSharingTabManager dataSharingTabManager,
             @Nullable Runnable onTabGroupCreation) {
         mActivity = activity;
@@ -981,7 +978,6 @@
         mPriceWelcomeMessageControllerSupplier = priceWelcomeMessageControllerSupplier;
         mComponentName = componentName;
         mTabActionState = initialTabActionState;
-        mActionConfirmationManager = actionConfirmationManager;
         mDataSharingTabManager = dataSharingTabManager;
         mOnTabGroupCreation = onTabGroupCreation;
 
@@ -2819,6 +2815,7 @@
     void onMenuItemClicked(@IdRes int menuId, Token tabGroupId, @Nullable String collaborationId) {
         TabGroupModelFilter filter = mCurrentTabGroupModelFilterSupplier.get();
         int tabId = filter.getGroupLastShownTabId(tabGroupId);
+        EitherGroupId eitherId = EitherGroupId.createLocalId(new LocalTabGroupId(tabGroupId));
         if (tabId == Tab.INVALID_TAB_ID) return;
 
         if (menuId == R.id.close_tab_group || menuId == R.id.delete_tab_group) {
@@ -2844,25 +2841,21 @@
             TabUiUtils.ungroupTabGroup(filter, tabGroupId);
         } else if (menuId == R.id.delete_shared_group) {
             RecordUserAction.record("TabGroupItemMenu.DeleteShared");
-            TabUiUtils.exitSharedTabGroupWithDialog(
-                    mActivity, filter, mActionConfirmationManager, mModalDialogManager, tabId);
+            mDataSharingTabManager.leaveOrDeleteFlow(
+                    eitherId,
+                    CollaborationServiceLeaveOrDeleteEntryPoint.ANDROID_TAB_GROUP_ITEM_MENU_DELETE);
         } else if (menuId == R.id.leave_group) {
             RecordUserAction.record("TabGroupItemMenu.LeaveShared");
-            TabUiUtils.exitSharedTabGroupWithDialog(
-                    mActivity, filter, mActionConfirmationManager, mModalDialogManager, tabId);
+            mDataSharingTabManager.leaveOrDeleteFlow(
+                    eitherId,
+                    CollaborationServiceLeaveOrDeleteEntryPoint.ANDROID_TAB_GROUP_ITEM_MENU_LEAVE);
         } else if (menuId == R.id.share_group) {
             assert mDataSharingTabManager != null;
             RecordUserAction.record("TabGroupItemMenu.ShareGroup");
-            @Nullable PropertyModel model = mModelList.getModelFromTabId(tabId);
-            if (model == null) return;
-
-            TabUiUtils.startShareTabGroupFlow(
-                    mActivity,
-                    filter,
-                    mDataSharingTabManager,
-                    tabId,
-                    model.get(TabProperties.TITLE),
-                    CollaborationServiceShareOrManageEntryPoint.TAB_GROUP_ITEM_MENU_SHARE);
+            mDataSharingTabManager.createOrManageFlow(
+                    eitherId,
+                    CollaborationServiceShareOrManageEntryPoint.TAB_GROUP_ITEM_MENU_SHARE,
+                    /* createGroupFinishedCallback= */ null);
         }
     }
 
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabManagementDelegate.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabManagementDelegate.java
index 3e291eb3..546e38b 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabManagementDelegate.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabManagementDelegate.java
@@ -171,6 +171,7 @@
      *     groups.
      * @param modalDialogManagerSupplier Used to show confirmation dialogs.
      * @param edgeToEdgeSupplier Supplier to the {@link EdgeToEdgeController} instance.
+     * @param dataSharingTabManager The {@link} DataSharingTabManager to start collaboration flows.
      * @return The pane implementation that displays and allows interactions with tab groups.
      */
     Pane createTabGroupsPane(
@@ -181,7 +182,8 @@
             @NonNull LazyOneshotSupplier<HubManager> hubManagerSupplier,
             @NonNull Supplier<TabGroupUiActionHandler> tabGroupUiActionHandlerSupplier,
             @NonNull Supplier<ModalDialogManager> modalDialogManagerSupplier,
-            @NonNull ObservableSupplier<EdgeToEdgeController> edgeToEdgeSupplier);
+            @NonNull ObservableSupplier<EdgeToEdgeController> edgeToEdgeSupplier,
+            @NonNull DataSharingTabManager dataSharingTabManager);
 
     /**
      * Create a {@link TabGroupCreationUiDelegate} for tab group creation UI flows.
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabManagementDelegateImpl.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabManagementDelegateImpl.java
index a00ccab..5753f69 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabManagementDelegateImpl.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabManagementDelegateImpl.java
@@ -202,7 +202,8 @@
             @NonNull LazyOneshotSupplier<HubManager> hubManagerSupplier,
             @NonNull Supplier<TabGroupUiActionHandler> tabGroupUiActionHandlerSupplier,
             @NonNull Supplier<ModalDialogManager> modalDialogManagerSupplier,
-            @NonNull ObservableSupplier<EdgeToEdgeController> edgeToEdgeSupplier) {
+            @NonNull ObservableSupplier<EdgeToEdgeController> edgeToEdgeSupplier,
+            @NonNull DataSharingTabManager dataSharingTabManager) {
         LazyOneshotSupplier<TabGroupModelFilter> tabGroupModelFilterSupplier =
                 LazyOneshotSupplier.fromSupplier(
                         () ->
@@ -217,7 +218,8 @@
                 () -> hubManagerSupplier.get().getPaneManager(),
                 tabGroupUiActionHandlerSupplier,
                 modalDialogManagerSupplier,
-                edgeToEdgeSupplier);
+                edgeToEdgeSupplier,
+                dataSharingTabManager);
     }
 
     @Override
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabUiUtils.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabUiUtils.java
index 09e060d..68bd91e 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabUiUtils.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabUiUtils.java
@@ -325,7 +325,7 @@
         LocalTabGroupId localTabGroupId = TabGroupSyncUtils.getLocalTabGroupId(tab);
 
         dataSharingTabManager.createOrManageFlow(
-                activity, EitherGroupId.createLocalId(localTabGroupId), entry, (ignored) -> {});
+                EitherGroupId.createLocalId(localTabGroupId), entry, (ignored) -> {});
     }
 
     /**
diff --git a/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/ArchivedTabsDialogCoordinatorTest.java b/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/ArchivedTabsDialogCoordinatorTest.java
index 3044916..98edf322e 100644
--- a/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/ArchivedTabsDialogCoordinatorTest.java
+++ b/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/ArchivedTabsDialogCoordinatorTest.java
@@ -45,7 +45,6 @@
 import org.chromium.base.test.util.CallbackHelper;
 import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.CriteriaHelper;
-import org.chromium.base.test.util.DisabledTest;
 import org.chromium.base.test.util.DoNotBatch;
 import org.chromium.base.test.util.Feature;
 import org.chromium.base.test.util.Features.DisableFeatures;
@@ -84,7 +83,6 @@
 @DoNotBatch(reason = "TODO(crbug.com/348068134): Batch this test suite.")
 @CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE})
 @DisableFeatures("IPH_AndroidTabDeclutter")
-@DisabledTest(message = "crbug.com/397759336")
 public class ArchivedTabsDialogCoordinatorTest {
     @Rule
     public FreshCtaTransitTestRule mCtaTestRule =
diff --git a/chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/tab_management/TabGridDialogMediatorUnitTest.java b/chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/tab_management/TabGridDialogMediatorUnitTest.java
index 6388bbb..df8e0c50 100644
--- a/chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/tab_management/TabGridDialogMediatorUnitTest.java
+++ b/chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/tab_management/TabGridDialogMediatorUnitTest.java
@@ -326,11 +326,11 @@
 
         mModel.get(TabGridDialogProperties.SHARE_BUTTON_CLICK_LISTENER).onClick(null);
         verify(mDataSharingTabManager)
-                .createOrManageFlow(eq(mActivity), eq(EITHER_LOCAL_TAB_GROUP_ID), anyInt(), any());
+                .createOrManageFlow(eq(EITHER_LOCAL_TAB_GROUP_ID), anyInt(), any());
 
         mModel.get(TabGridDialogProperties.SHARE_IMAGE_TILES_CLICK_LISTENER).onClick(null);
         verify(mDataSharingTabManager, times(2))
-                .createOrManageFlow(eq(mActivity), eq(EITHER_LOCAL_TAB_GROUP_ID), anyInt(), any());
+                .createOrManageFlow(eq(EITHER_LOCAL_TAB_GROUP_ID), anyInt(), any());
 
         mModel.get(TabGridDialogProperties.SEND_FEEDBACK_RUNNABLE).run();
         ArgumentCaptor<String> categoryCaptor = ArgumentCaptor.forClass(String.class);
@@ -1526,7 +1526,7 @@
         mMediator.onToolbarMenuItemClick(R.id.manage_sharing, TAB_GROUP_ID, COLLABORATION_ID1);
         assertEquals(1, mActionTester.getActionCount("TabGridDialogMenu.ManageSharing"));
         verify(mDataSharingTabManager)
-                .createOrManageFlow(any(), eq(EITHER_LOCAL_TAB_GROUP_ID), anyInt(), eq(null));
+                .createOrManageFlow(eq(EITHER_LOCAL_TAB_GROUP_ID), anyInt(), eq(null));
     }
 
     @Test
@@ -1551,8 +1551,7 @@
                 .thenReturn(MemberRole.OWNER);
 
         mMediator.onToolbarMenuItemClick(R.id.delete_shared_group, TAB_GROUP_ID, COLLABORATION_ID1);
-        verify(mActionConfirmationManager).processDeleteSharedGroupAttempt(eq(GROUP_TITLE), any());
-        assertEquals(1, mActionTester.getActionCount("TabGridDialogMenu.DeleteShared"));
+        verify(mDataSharingTabManager).leaveOrDeleteFlow(eq(EITHER_LOCAL_TAB_GROUP_ID), anyInt());
     }
 
     @Test
@@ -1567,8 +1566,7 @@
                 .thenReturn(MemberRole.MEMBER);
 
         mMediator.onToolbarMenuItemClick(R.id.leave_group, TAB_GROUP_ID, COLLABORATION_ID1);
-        verify(mActionConfirmationManager).processLeaveGroupAttempt(eq(GROUP_TITLE), any());
-        assertEquals(1, mActionTester.getActionCount("TabGridDialogMenu.LeaveShared"));
+        verify(mDataSharingTabManager).leaveOrDeleteFlow(eq(EITHER_LOCAL_TAB_GROUP_ID), anyInt());
     }
 
     @Test
@@ -1944,7 +1942,6 @@
                         mDataSharingTabManager,
                         /* componentName= */ "",
                         mShowColorPickerPopupRunnable,
-                        mActionConfirmationManager,
                         mModalDialogManager,
                         mDesktopWindowStateManager,
                         mTabBookmarkerSupplier,
diff --git a/chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/tab_management/TabListMediatorUnitTest.java b/chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/tab_management/TabListMediatorUnitTest.java
index 2727bfa..5836166 100644
--- a/chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/tab_management/TabListMediatorUnitTest.java
+++ b/chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/tab_management/TabListMediatorUnitTest.java
@@ -130,7 +130,6 @@
 import org.chromium.chrome.browser.tab_group_sync.TabGroupSyncFeatures;
 import org.chromium.chrome.browser.tab_group_sync.TabGroupSyncFeaturesJni;
 import org.chromium.chrome.browser.tab_group_sync.TabGroupSyncServiceFactory;
-import org.chromium.chrome.browser.tab_ui.ActionConfirmationManager;
 import org.chromium.chrome.browser.tab_ui.TabContentManager;
 import org.chromium.chrome.browser.tab_ui.TabContentManagerThumbnailProvider;
 import org.chromium.chrome.browser.tab_ui.TabListFaviconProvider;
@@ -339,7 +338,6 @@
     @Mock ShoppingPersistedTabData mShoppingPersistedTabData;
     @Mock SelectionDelegate<TabListEditorItemSelectionId> mSelectionDelegate;
     @Mock ModalDialogManager mModalDialogManager;
-    @Mock ActionConfirmationManager mActionConfirmationManager;
     @Mock DataSharingTabManager mDataSharingTabManager;
     @Mock TabGroupSyncFeatures.Natives mTabGroupSyncFeaturesJniMock;
     @Mock IdentityServicesProvider mIdentityServicesProvider;
@@ -1742,7 +1740,6 @@
                         null,
                         getClass().getSimpleName(),
                         TabActionState.CLOSABLE,
-                        mActionConfirmationManager,
                         mDataSharingTabManager,
                         /* onTabGroupCreation= */ null);
         mMediator.initWithNative(mProfile);
@@ -3522,7 +3519,6 @@
                         null,
                         getClass().getSimpleName(),
                         TabProperties.TabActionState.CLOSABLE,
-                        mActionConfirmationManager,
                         mDataSharingTabManager,
                         /* onTabGroupCreation= */ null);
         mMediator.registerOrientationListener(mGridLayoutManager);
@@ -3556,7 +3552,6 @@
                         null,
                         getClass().getSimpleName(),
                         TabProperties.TabActionState.CLOSABLE,
-                        mActionConfirmationManager,
                         mDataSharingTabManager,
                         /* onTabGroupCreation= */ null);
         mMediator.registerOrientationListener(mGridLayoutManager);
@@ -4016,7 +4011,6 @@
                         null,
                         getClass().getSimpleName(),
                         TabProperties.TabActionState.SELECTABLE,
-                        mActionConfirmationManager,
                         mDataSharingTabManager,
                         /* onTabGroupCreation= */ null);
         mMediator.registerOrientationListener(mGridLayoutManager);
@@ -4063,7 +4057,6 @@
                         null,
                         getClass().getSimpleName(),
                         TabProperties.TabActionState.SELECTABLE,
-                        mActionConfirmationManager,
                         mDataSharingTabManager,
                         /* onTabGroupCreation= */ null);
         mMediator.registerOrientationListener(mGridLayoutManager);
@@ -4110,7 +4103,6 @@
                         null,
                         getClass().getSimpleName(),
                         TabProperties.TabActionState.SELECTABLE,
-                        mActionConfirmationManager,
                         mDataSharingTabManager,
                         /* onTabGroupCreation= */ null);
         mMediator.registerOrientationListener(mGridLayoutManager);
@@ -4418,7 +4410,7 @@
         assertNotNull(mModelList.get(POSITION1).model.get(TabProperties.TAB_ACTION_BUTTON_DATA));
         when(mTabGroupModelFilter.getGroupLastShownTabId(TAB_GROUP_ID)).thenReturn(TAB1_ID);
         mMediator.onMenuItemClicked(R.id.share_group, TAB_GROUP_ID, /* collaborationId= */ null);
-        verify(mDataSharingTabManager).createOrManageFlow(eq(mActivity), any(), anyInt(), any());
+        verify(mDataSharingTabManager).createOrManageFlow(any(), anyInt(), any());
     }
 
     @Test
@@ -4530,7 +4522,6 @@
                         null,
                         getClass().getSimpleName(),
                         TabProperties.TabActionState.CLOSABLE,
-                        mActionConfirmationManager,
                         mDataSharingTabManager,
                         /* onTabGroupCreation= */ null);
         mMediator.registerOrientationListener(mGridLayoutManager);
@@ -4903,7 +4894,6 @@
                         null,
                         getClass().getSimpleName(),
                         TabProperties.TabActionState.CLOSABLE,
-                        mActionConfirmationManager,
                         mDataSharingTabManager,
                         /* onTabGroupCreation= */ null);
         mMediator.registerOrientationListener(mGridLayoutManager);
@@ -5128,7 +5118,6 @@
                         null,
                         getClass().getSimpleName(),
                         tabActionState,
-                        mActionConfirmationManager,
                         mDataSharingTabManager,
                         /* onTabGroupCreation= */ null);
         TrackerFactory.setTrackerForTests(mTracker);
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 817db4f0..c55b465 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java
@@ -1060,7 +1060,8 @@
                                 ((TabbedRootUiCoordinator) mRootUiCoordinator)
                                         .getTabGroupSyncController(),
                         getModalDialogManagerSupplier(),
-                        mEdgeToEdgeControllerSupplier);
+                        mEdgeToEdgeControllerSupplier,
+                        mRootUiCoordinator.getDataSharingTabManager());
     }
 
     private Pane createHistoryPane() {
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/compositor/overlays/strip/TabGroupContextMenuCoordinator.java b/chrome/android/java/src/org/chromium/chrome/browser/compositor/overlays/strip/TabGroupContextMenuCoordinator.java
index 2a8db4f..dc605e9 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/compositor/overlays/strip/TabGroupContextMenuCoordinator.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/compositor/overlays/strip/TabGroupContextMenuCoordinator.java
@@ -49,6 +49,7 @@
 import org.chromium.chrome.tab_ui.R;
 import org.chromium.components.browser_ui.widget.BrowserUiListMenuUtils;
 import org.chromium.components.collaboration.CollaborationService;
+import org.chromium.components.collaboration.CollaborationServiceLeaveOrDeleteEntryPoint;
 import org.chromium.components.collaboration.CollaborationServiceShareOrManageEntryPoint;
 import org.chromium.components.data_sharing.member_role.MemberRole;
 import org.chromium.components.embedder_support.util.UrlConstants;
@@ -180,6 +181,8 @@
             DataSharingTabManager dataSharingTabManager) {
         return (menuId, tabGroupId, collaborationId) -> {
             int tabId = tabGroupModelFilter.getGroupLastShownTabId(tabGroupId);
+            EitherGroupId eitherId = EitherGroupId.createLocalId(new LocalTabGroupId(tabGroupId));
+
             if (tabId == Tab.INVALID_TAB_ID) return;
 
             if (menuId == org.chromium.chrome.R.id.ungroup_tab) {
@@ -209,26 +212,16 @@
                         TabLaunchType.FROM_TAB_GROUP_UI);
                 recordUserAction("NewTabInGroup");
             } else if (menuId == org.chromium.chrome.R.id.share_group) {
-                // Get user assigned group title or the default title "N tabs" if no title is
-                // assigned.
-                String tabGroupDisplayName =
-                        TabGroupTitleUtils.getDisplayableTitle(
-                                activity, tabGroupModelFilter, tabGroupId);
-
                 // Create the group share flow and display the share bottom sheet.
-                TabUiUtils.startShareTabGroupFlow(
-                        activity,
-                        tabGroupModelFilter,
-                        dataSharingTabManager,
-                        tabId,
-                        tabGroupDisplayName,
+                dataSharingTabManager.createOrManageFlow(
+                        eitherId,
                         CollaborationServiceShareOrManageEntryPoint
-                                .ANDROID_TAB_GROUP_CONTEXT_MENU_SHARE);
+                                .ANDROID_TAB_GROUP_CONTEXT_MENU_SHARE,
+                        /* createGroupFinishedCallback= */ null);
                 recordUserAction("ShareGroup");
             } else if (menuId == R.id.manage_sharing) {
                 dataSharingTabManager.createOrManageFlow(
-                        activity,
-                        EitherGroupId.createLocalId(new LocalTabGroupId(tabGroupId)),
+                        eitherId,
                         CollaborationServiceShareOrManageEntryPoint
                                 .ANDROID_TAB_GROUP_CONTEXT_MENU_MANAGE,
                         /* createGroupFinishedCallback= */ null);
@@ -237,20 +230,16 @@
                 dataSharingTabManager.showRecentActivity(activity, collaborationId);
                 recordUserAction("RecentActivity");
             } else if (menuId == R.id.delete_shared_group) {
-                TabUiUtils.exitSharedTabGroupWithDialog(
-                        activity,
-                        tabGroupModelFilter,
-                        actionConfirmationManager,
-                        modalDialogManager,
-                        tabId);
+                dataSharingTabManager.leaveOrDeleteFlow(
+                        eitherId,
+                        CollaborationServiceLeaveOrDeleteEntryPoint
+                                .ANDROID_TAB_GROUP_CONTEXT_MENU_DELETE);
                 recordUserAction("DeleteSharedGroup");
             } else if (menuId == R.id.leave_group) {
-                TabUiUtils.exitSharedTabGroupWithDialog(
-                        activity,
-                        tabGroupModelFilter,
-                        actionConfirmationManager,
-                        modalDialogManager,
-                        tabId);
+                dataSharingTabManager.leaveOrDeleteFlow(
+                        eitherId,
+                        CollaborationServiceLeaveOrDeleteEntryPoint
+                                .ANDROID_TAB_GROUP_CONTEXT_MENU_LEAVE);
                 recordUserAction("LeaveSharedGroup");
             }
         };
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ui/RootUiCoordinator.java b/chrome/android/java/src/org/chromium/chrome/browser/ui/RootUiCoordinator.java
index 32aae124..7f73d731 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ui/RootUiCoordinator.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ui/RootUiCoordinator.java
@@ -19,16 +19,20 @@
 
 import androidx.annotation.CallSuper;
 import androidx.annotation.ColorInt;
+import androidx.annotation.IntDef;
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.annotation.VisibleForTesting;
 import androidx.appcompat.app.AppCompatActivity;
+import androidx.core.graphics.Insets;
+import androidx.core.view.WindowInsetsCompat;
 
 import org.chromium.base.BuildInfo;
 import org.chromium.base.Callback;
 import org.chromium.base.CallbackController;
 import org.chromium.base.TraceEvent;
 import org.chromium.base.lifetime.Destroyable;
+import org.chromium.base.metrics.RecordHistogram;
 import org.chromium.base.metrics.RecordUserAction;
 import org.chromium.base.supplier.ObservableSupplier;
 import org.chromium.base.supplier.ObservableSupplierImpl;
@@ -95,6 +99,7 @@
 import org.chromium.chrome.browser.messages.MessagesResourceMapperInitializer;
 import org.chromium.chrome.browser.metrics.UmaSessionStats;
 import org.chromium.chrome.browser.multiwindow.MultiInstanceManager;
+import org.chromium.chrome.browser.multiwindow.MultiWindowUtils;
 import org.chromium.chrome.browser.omnibox.OmniboxFocusReason;
 import org.chromium.chrome.browser.omnibox.geo.GeolocationHeader;
 import org.chromium.chrome.browser.omnibox.suggestions.action.OmniboxActionDelegateImpl;
@@ -141,6 +146,7 @@
 import org.chromium.chrome.browser.ui.appmenu.AppMenuDelegate;
 import org.chromium.chrome.browser.ui.appmenu.AppMenuHandler;
 import org.chromium.chrome.browser.ui.appmenu.AppMenuObserver;
+import org.chromium.chrome.browser.ui.desktop_windowing.AppHeaderUtils;
 import org.chromium.chrome.browser.ui.edge_to_edge.EdgeToEdgeController;
 import org.chromium.chrome.browser.ui.edge_to_edge.EdgeToEdgeControllerFactory;
 import org.chromium.chrome.browser.ui.edge_to_edge.EdgeToEdgeUtils;
@@ -205,6 +211,22 @@
                 AppMenuBlocker,
                 ContextualSearchTabPromotionDelegate,
                 WindowFocusChangedObserver {
+    private static final String MISSING_NAVBAR_INSETS_HISTOGRAM =
+            "Android.EdgeToEdge.MissingNavbarInsets";
+
+    @IntDef({
+        MissingNavbarInsetsReason.OTHER,
+        MissingNavbarInsetsReason.IN_MULTI_WINDOW,
+        MissingNavbarInsetsReason.IN_DESKTOP_WINDOW,
+        MissingNavbarInsetsReason.NUM_ENTRIES
+    })
+    @interface MissingNavbarInsetsReason {
+        int OTHER = 0;
+        int IN_MULTI_WINDOW = 1;
+        int IN_DESKTOP_WINDOW = 2;
+        int NUM_ENTRIES = 3;
+    }
+
     protected final UnownedUserDataSupplier<TabObscuringHandler> mTabObscuringHandlerSupplier =
             new TabObscuringHandlerSupplier();
 
@@ -1831,6 +1853,34 @@
                             mFullscreenManager);
             mEdgeToEdgeControllerSupplier.set(mEdgeToEdgeController);
             mEdgeToEdgeBottomChin = createEdgeToEdgeBottomChin();
+
+            recordIfMissingNavigationBar();
+        }
+    }
+
+    private void recordIfMissingNavigationBar() {
+        var rootInsets = mActivity.getWindow().getDecorView().getRootWindowInsets();
+        assert rootInsets != null;
+        Insets navigationBarInsets =
+                WindowInsetsCompat.toWindowInsetsCompat(rootInsets)
+                        .getInsets(WindowInsetsCompat.Type.navigationBars());
+        if (!navigationBarInsets.equals(Insets.NONE)) return;
+
+        if (AppHeaderUtils.isAppInDesktopWindow(getDesktopWindowStateManager())) {
+            RecordHistogram.recordEnumeratedHistogram(
+                    MISSING_NAVBAR_INSETS_HISTOGRAM,
+                    MissingNavbarInsetsReason.IN_DESKTOP_WINDOW,
+                    MissingNavbarInsetsReason.NUM_ENTRIES);
+        } else if (MultiWindowUtils.getInstance().isInMultiWindowMode(mActivity)) {
+            RecordHistogram.recordEnumeratedHistogram(
+                    MISSING_NAVBAR_INSETS_HISTOGRAM,
+                    MissingNavbarInsetsReason.IN_MULTI_WINDOW,
+                    MissingNavbarInsetsReason.NUM_ENTRIES);
+        } else {
+            RecordHistogram.recordEnumeratedHistogram(
+                    MISSING_NAVBAR_INSETS_HISTOGRAM,
+                    MissingNavbarInsetsReason.OTHER,
+                    MissingNavbarInsetsReason.NUM_ENTRIES);
         }
     }
 
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/geo/GeolocationHeaderTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/geo/GeolocationHeaderTest.java
index 7a092d1..9404427b 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/geo/GeolocationHeaderTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/geo/GeolocationHeaderTest.java
@@ -136,6 +136,7 @@
     @Test
     @SmallTest
     @Feature({"Location"})
+    @DisabledTest(message = "https://crbug.com/416787235")
     public void testProtoEncoding() {
         setPermission(ContentSettingValues.ALLOW);
         long now = setMockLocationNow();
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/payments/WebPaymentIntentHelperTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/WebPaymentIntentHelperTest.java
index 7770e380..b4117485 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/payments/WebPaymentIntentHelperTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/WebPaymentIntentHelperTest.java
@@ -121,8 +121,8 @@
 
         Intent intent =
                 WebPaymentIntentHelper.createPayIntent(
-                        "package.name",
-                        "activity.name",
+                        "payment.app.package.name",
+                        "payment.app.activity.name",
                         "payment.request.id",
                         "merchant.name",
                         "schemeless.origin",
@@ -136,8 +136,8 @@
                         shippingOptions,
                         /* removeDeprecatedFields= */ false);
         Assert.assertEquals(WebPaymentIntentHelper.ACTION_PAY, intent.getAction());
-        Assert.assertEquals("package.name", intent.getComponent().getPackageName());
-        Assert.assertEquals("activity.name", intent.getComponent().getClassName());
+        Assert.assertEquals("payment.app.package.name", intent.getComponent().getPackageName());
+        Assert.assertEquals("payment.app.activity.name", intent.getComponent().getClassName());
         Bundle bundle = intent.getExtras();
         Assert.assertNotNull(bundle);
         Assert.assertEquals(
@@ -240,8 +240,8 @@
 
         Intent intent =
                 WebPaymentIntentHelper.createPayIntent(
-                        "package.name",
-                        "activity.name",
+                        "payment.app.package.name",
+                        "payment.app.activity.name",
                         "payment.request.id",
                         "merchant.name",
                         "schemeless.origin",
@@ -313,8 +313,8 @@
 
         Intent intent =
                 WebPaymentIntentHelper.createPayIntent(
-                        "package.name",
-                        "activity.name",
+                        "payment.app.package.name",
+                        "payment.app.activity.name",
                         "payment.request.id",
                         "merchant.name",
                         "schemeless.origin",
@@ -345,9 +345,9 @@
     @Test
     @SmallTest
     @Feature({"Payments"})
-    public void nullPackageNameExceptionTest() throws Throwable {
+    public void nullPaymentAppPackageNameExceptionTest() throws Throwable {
         thrown.expect(IllegalArgumentException.class);
-        thrown.expectMessage("packageName should not be null or empty.");
+        thrown.expectMessage("paymentAppPackageName should not be null or empty.");
 
         Map<String, PaymentMethodData> methodDataMap = new HashMap<String, PaymentMethodData>();
         PaymentMethodData bobPayMethodData = new PaymentMethodData("method", "null");
@@ -356,8 +356,8 @@
         PaymentItem total = new PaymentItem(new PaymentCurrencyAmount("CAD", "200"));
 
         WebPaymentIntentHelper.createPayIntent(
-                /* packageName= */ null,
-                "activity.name",
+                /* paymentAppPackageName= */ null,
+                "payment.app.activity.name",
                 "payment.request.id",
                 "merchant.name",
                 "schemeless.origin",
@@ -377,7 +377,7 @@
     @Feature({"Payments"})
     public void nullActivityNameExceptionTest() throws Throwable {
         thrown.expect(IllegalArgumentException.class);
-        thrown.expectMessage("activityName should not be null or empty.");
+        thrown.expectMessage("paymentAppActivityName should not be null or empty.");
 
         Map<String, PaymentMethodData> methodDataMap = new HashMap<String, PaymentMethodData>();
         PaymentMethodData bobPayMethodData = new PaymentMethodData("method", "null");
@@ -386,8 +386,8 @@
         PaymentItem total = new PaymentItem(new PaymentCurrencyAmount("CAD", "200"));
 
         WebPaymentIntentHelper.createPayIntent(
-                "package.name",
-                /* activityName= */ null,
+                "payment.app.package.name",
+                /* paymentAppActivityName= */ null,
                 "payment.request.id",
                 "merchant.name",
                 "schemeless.origin",
@@ -416,8 +416,8 @@
         PaymentItem total = new PaymentItem(new PaymentCurrencyAmount("CAD", "200"));
 
         WebPaymentIntentHelper.createPayIntent(
-                "package.name",
-                "activity.name",
+                "payment.app.package.name",
+                "payment.app.activity.name",
                 /* id= */ null,
                 "merchant.name",
                 "schemeless.origin",
@@ -446,8 +446,8 @@
         PaymentItem total = new PaymentItem(new PaymentCurrencyAmount("CAD", "200"));
 
         WebPaymentIntentHelper.createPayIntent(
-                "package.name",
-                "activity.name",
+                "payment.app.package.name",
+                "payment.app.activity.name",
                 /* id= */ "",
                 "merchant.name",
                 "schemeless.origin",
@@ -476,8 +476,8 @@
         PaymentItem total = new PaymentItem(new PaymentCurrencyAmount("CAD", "200"));
 
         WebPaymentIntentHelper.createPayIntent(
-                "package.name",
-                "activity.name",
+                "payment.app.package.name",
+                "payment.app.activity.name",
                 "id",
                 /* merchantName= */ null,
                 "schemeless.origin",
@@ -503,8 +503,8 @@
         PaymentItem total = new PaymentItem(new PaymentCurrencyAmount("CAD", "200"));
 
         WebPaymentIntentHelper.createPayIntent(
-                "package.name",
-                "activity.name",
+                "payment.app.package.name",
+                "payment.app.activity.name",
                 "id",
                 /* merchantName= */ "",
                 "schemeless.origin",
@@ -533,8 +533,8 @@
         PaymentItem total = new PaymentItem(new PaymentCurrencyAmount("CAD", "200"));
 
         WebPaymentIntentHelper.createPayIntent(
-                "package.name",
-                "activity.name",
+                "payment.app.package.name",
+                "payment.app.activity.name",
                 "id",
                 "merchant.name",
                 /* schemelessOrigin= */ null,
@@ -563,8 +563,8 @@
         PaymentItem total = new PaymentItem(new PaymentCurrencyAmount("CAD", "200"));
 
         WebPaymentIntentHelper.createPayIntent(
-                "package.name",
-                "activity.name",
+                "payment.app.package.name",
+                "payment.app.activity.name",
                 "id",
                 "merchant.name",
                 /* schemelessOrigin= */ "",
@@ -593,8 +593,8 @@
         PaymentItem total = new PaymentItem(new PaymentCurrencyAmount("CAD", "200"));
 
         WebPaymentIntentHelper.createPayIntent(
-                "package.name",
-                "activity.name",
+                "payment.app.package.name",
+                "payment.app.activity.name",
                 "id",
                 "merchant.name",
                 "schemeless.origin",
@@ -623,8 +623,8 @@
         PaymentItem total = new PaymentItem(new PaymentCurrencyAmount("CAD", "200"));
 
         WebPaymentIntentHelper.createPayIntent(
-                "package.name",
-                "activity.name",
+                "payment.app.package.name",
+                "payment.app.activity.name",
                 "id",
                 "merchant.name",
                 "schemeless.origin",
@@ -655,8 +655,8 @@
         PaymentItem total = new PaymentItem(new PaymentCurrencyAmount("CAD", "200"));
 
         WebPaymentIntentHelper.createPayIntent(
-                "package.name",
-                "activity.name",
+                "payment.app.package.name",
+                "payment.app.activity.name",
                 "id",
                 "merchant.name",
                 "schemeless.origin",
@@ -685,8 +685,8 @@
         PaymentItem total = new PaymentItem(new PaymentCurrencyAmount("CAD", "200"));
 
         WebPaymentIntentHelper.createPayIntent(
-                "package.name",
-                "activity.name",
+                "payment.app.package.name",
+                "payment.app.activity.name",
                 "payment.request.id",
                 "merchant.name",
                 "schemeless.origin",
@@ -712,8 +712,8 @@
         PaymentItem total = new PaymentItem(new PaymentCurrencyAmount("CAD", "200"));
 
         WebPaymentIntentHelper.createPayIntent(
-                "package.name",
-                "activity.name",
+                "payment.app.package.name",
+                "payment.app.activity.name",
                 "payment.request.id",
                 "merchant.name",
                 "schemeless.origin",
@@ -741,8 +741,8 @@
         PaymentItem total = new PaymentItem(new PaymentCurrencyAmount("CAD", "200"));
 
         WebPaymentIntentHelper.createPayIntent(
-                "package.name",
-                "activity.name",
+                "payment.app.package.name",
+                "payment.app.activity.name",
                 "payment.request.id",
                 "merchant.name",
                 "schemeless.origin",
@@ -769,8 +769,8 @@
         methodDataMap.put("bobPay", bobPayMethodData);
 
         WebPaymentIntentHelper.createPayIntent(
-                "package.name",
-                "activity.name",
+                "payment.app.package.name",
+                "payment.app.activity.name",
                 "id",
                 "merchant.name",
                 "schemeless.origin",
@@ -804,8 +804,8 @@
         modifiers.put("bobPay", null);
 
         WebPaymentIntentHelper.createPayIntent(
-                "package.name",
-                "activity.name",
+                "payment.app.package.name",
+                "payment.app.activity.name",
                 "payment.request.id",
                 "merchant.name",
                 "schemeless.origin",
@@ -1129,8 +1129,9 @@
 
         Intent intent =
                 WebPaymentIntentHelper.createIsReadyToPayIntent(
-                        "package.name",
-                        "service.name",
+                        "caller.package.name",
+                        "payment.app.package.name",
+                        "payment.app.service.name",
                         "schemeless.origin",
                         "schemeless.iframe.origin",
                         certificateChain,
@@ -1138,11 +1139,14 @@
                         /* clearIdFields= */ false,
                         /* removeDeprecatedFields= */ false);
         Assert.assertEquals(WebPaymentIntentHelper.ACTION_IS_READY_TO_PAY, intent.getAction());
-        Assert.assertEquals("package.name", intent.getComponent().getPackageName());
-        Assert.assertEquals("service.name", intent.getComponent().getClassName());
+        Assert.assertEquals("payment.app.package.name", intent.getComponent().getPackageName());
+        Assert.assertEquals("payment.app.service.name", intent.getComponent().getClassName());
         Bundle bundle = intent.getExtras();
         Assert.assertNotNull(bundle);
         Assert.assertEquals(
+                "caller.package.name",
+                bundle.get(WebPaymentIntentHelper.EXTRA_CALLER_PACKAGE_NAME));
+        Assert.assertEquals(
                 "schemeless.origin", bundle.get(WebPaymentIntentHelper.EXTRA_TOP_ORIGIN));
         Assert.assertEquals(
                 "schemeless.iframe.origin",
@@ -1181,8 +1185,9 @@
 
         Intent intent =
                 WebPaymentIntentHelper.createIsReadyToPayIntent(
-                        "package.name",
-                        "service.name",
+                        "caller.package.name",
+                        "payment.app.package.name",
+                        "payment.app.service.name",
                         "schemeless.origin",
                         "schemeless.iframe.origin",
                         certificateChain,
@@ -1222,8 +1227,9 @@
 
         Intent intent =
                 WebPaymentIntentHelper.createIsReadyToPayIntent(
-                        "package.name",
-                        "service.name",
+                        "caller.package.name",
+                        "payment.app.package.name",
+                        "payment.app.service.name",
                         "schemeless.origin",
                         "schemeless.iframe.origin",
                         certificateChain,
@@ -1257,8 +1263,9 @@
 
         Intent intent =
                 WebPaymentIntentHelper.createIsReadyToPayIntent(
-                        "package.name",
-                        "service.name",
+                        "caller.package.name",
+                        "payment.app.package.name",
+                        "payment.app.service.name",
                         "schemeless.origin",
                         "schemeless.iframe.origin",
                         certificateChain,
@@ -1266,8 +1273,8 @@
                         /* clearIdFields= */ true,
                         /* removeDeprecatedFields= */ false);
         Assert.assertEquals(WebPaymentIntentHelper.ACTION_IS_READY_TO_PAY, intent.getAction());
-        Assert.assertEquals("package.name", intent.getComponent().getPackageName());
-        Assert.assertEquals("service.name", intent.getComponent().getClassName());
+        Assert.assertEquals("payment.app.package.name", intent.getComponent().getPackageName());
+        Assert.assertEquals("payment.app.service.name", intent.getComponent().getClassName());
         Bundle bundle = intent.getExtras();
         Assert.assertNotNull(bundle);
         Assert.assertEquals(null, bundle.get(WebPaymentIntentHelper.EXTRA_TOP_ORIGIN));
@@ -1285,18 +1292,43 @@
     @Test
     @SmallTest
     @Feature({"Payments"})
-    public void createIsReadyToPayIntentNullPackageNameExceptionTestWithIdentity()
+    public void createIsReadyToPayIntentNullCallerPackageNameExceptionTestWithIdentity()
             throws Throwable {
         thrown.expect(IllegalArgumentException.class);
-        thrown.expectMessage("packageName should not be null or empty.");
+        thrown.expectMessage("callerPackageName should not be null or empty.");
 
         Map<String, PaymentMethodData> methodDataMap = new HashMap<String, PaymentMethodData>();
         PaymentMethodData bobPayMethodData = new PaymentMethodData("method", "null");
         methodDataMap.put("bobPay", bobPayMethodData);
 
         WebPaymentIntentHelper.createIsReadyToPayIntent(
-                /* packageName= */ null,
-                "service.name",
+                /* callerPackageName= */ null,
+                "payment.app.package.name",
+                "payment.app.service.name",
+                "schemeless.origin",
+                "schemeless.iframe.origin",
+                /* certificateChain= */ null,
+                methodDataMap,
+                /* clearIdFields= */ false,
+                /* removeDeprecatedFields= */ false);
+    }
+
+    @Test
+    @SmallTest
+    @Feature({"Payments"})
+    public void createIsReadyToPayIntentNullPaymentAppPackageNameExceptionTestWithIdentity()
+            throws Throwable {
+        thrown.expect(IllegalArgumentException.class);
+        thrown.expectMessage("paymentAppPackageName should not be null or empty.");
+
+        Map<String, PaymentMethodData> methodDataMap = new HashMap<String, PaymentMethodData>();
+        PaymentMethodData bobPayMethodData = new PaymentMethodData("method", "null");
+        methodDataMap.put("bobPay", bobPayMethodData);
+
+        WebPaymentIntentHelper.createIsReadyToPayIntent(
+                "caller.package.name",
+                /* paymentAppPackageName= */ null,
+                "payment.app.service.name",
                 "schemeless.origin",
                 "schemeless.iframe.origin",
                 /* certificateChain= */ null,
@@ -1311,15 +1343,16 @@
     public void createIsReadyToPayIntentNullPackageNameExceptionTestWithoutIdentity()
             throws Throwable {
         thrown.expect(IllegalArgumentException.class);
-        thrown.expectMessage("packageName should not be null or empty.");
+        thrown.expectMessage("paymentAppPackageName should not be null or empty.");
 
         Map<String, PaymentMethodData> methodDataMap = new HashMap<String, PaymentMethodData>();
         PaymentMethodData bobPayMethodData = new PaymentMethodData("method", "null");
         methodDataMap.put("bobPay", bobPayMethodData);
 
         WebPaymentIntentHelper.createIsReadyToPayIntent(
-                /* packageName= */ null,
-                "service.name",
+                "caller.package.name",
+                /* paymentAppPackageName= */ null,
+                "payment.app.service.name",
                 "schemeless.origin",
                 "schemeless.iframe.origin",
                 /* certificateChain= */ null,
@@ -1334,23 +1367,46 @@
     public void createPaymentDetailsUpdateServiceIntent() throws Throwable {
         Intent intent =
                 WebPaymentIntentHelper.createPaymentDetailsUpdateServiceIntent(
-                        "package.name", "service.name");
+                        "caller.package.name",
+                        "payment.app.package.name",
+                        "payment.app.service.name");
         Assert.assertEquals(
                 WebPaymentIntentHelper.ACTION_UPDATE_PAYMENT_DETAILS, intent.getAction());
-        Assert.assertEquals("package.name", intent.getComponent().getPackageName());
-        Assert.assertEquals("service.name", intent.getComponent().getClassName());
-        Assert.assertNull(intent.getExtras());
+        Assert.assertEquals("payment.app.package.name", intent.getComponent().getPackageName());
+        Assert.assertEquals("payment.app.service.name", intent.getComponent().getClassName());
+        Bundle bundle = intent.getExtras();
+        Assert.assertNotNull(bundle);
+        Assert.assertEquals(
+                "caller.package.name",
+                bundle.get(WebPaymentIntentHelper.EXTRA_CALLER_PACKAGE_NAME));
     }
 
     @Test
     @SmallTest
     @Feature({"Payments"})
-    public void createPaymentDetailsUpdateServiceIntentThrowsWithoutPackageName() throws Throwable {
+    public void createPaymentDetailsUpdateServiceIntentThrowsWithoutCallerPackageName()
+            throws Throwable {
         thrown.expect(IllegalArgumentException.class);
-        thrown.expectMessage("packageName should not be null or empty.");
+        thrown.expectMessage("callerPackageName should not be null or empty.");
 
         WebPaymentIntentHelper.createPaymentDetailsUpdateServiceIntent(
-                /* packageName= */ null, "service.name");
+                /* callerPackageName= */ null,
+                "payment.app.package.name",
+                "payment.app.service.name");
+    }
+
+    @Test
+    @SmallTest
+    @Feature({"Payments"})
+    public void createPaymentDetailsUpdateServiceIntentThrowsWithoutPaymentAppPackageName()
+            throws Throwable {
+        thrown.expect(IllegalArgumentException.class);
+        thrown.expectMessage("paymentAppPackageName should not be null or empty.");
+
+        WebPaymentIntentHelper.createPaymentDetailsUpdateServiceIntent(
+                "caller.package.name",
+                /* paymentAppPackageName= */ null,
+                "payment.app.service.name");
     }
 
     @Test
@@ -1358,9 +1414,11 @@
     @Feature({"Payments"})
     public void createPaymentDetailsUpdateServiceIntentThrowsWithoutServiceName() throws Throwable {
         thrown.expect(IllegalArgumentException.class);
-        thrown.expectMessage("serviceName should not be null or empty.");
+        thrown.expectMessage("paymentAppServiceName should not be null or empty.");
 
         WebPaymentIntentHelper.createPaymentDetailsUpdateServiceIntent(
-                "package.name", /* serviceName= */ null);
+                "caller.package.name",
+                "payment.app.package.name",
+                /* paymentAppServiceName= */ null);
     }
 }
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/tab/tab_restore/HistoricalTabSaverImplTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/tab/tab_restore/HistoricalTabSaverImplTest.java
index a001907..926e7f2 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/tab/tab_restore/HistoricalTabSaverImplTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/tab/tab_restore/HistoricalTabSaverImplTest.java
@@ -21,6 +21,7 @@
 import org.chromium.base.test.util.Batch;
 import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.CriteriaHelper;
+import org.chromium.base.test.util.DisabledTest;
 import org.chromium.chrome.browser.ChromeTabbedActivity;
 import org.chromium.chrome.browser.app.tabmodel.ArchivedTabModelOrchestrator;
 import org.chromium.chrome.browser.flags.ChromeSwitches;
@@ -119,6 +120,7 @@
      */
     @Test
     @MediumTest
+    @DisabledTest(message = "https://crbug.com/416777174")
     public void testCreateHistoricalTab_Frozen_HistoricalTabCreated() {
         final Tab tab =
                 sActivityTestRule.loadUrlInNewTab(getUrl(TEST_PAGE_1), /* incognito= */ false);
diff --git a/chrome/android/profiles/arm.newest.txt b/chrome/android/profiles/arm.newest.txt
index 9795381..e339a6c 100644
--- a/chrome/android/profiles/arm.newest.txt
+++ b/chrome/android/profiles/arm.newest.txt
@@ -1 +1 @@
-chromeos-chrome-arm-138.0.7166.0_rc-r1-merged.afdo.bz2
+chromeos-chrome-arm-138.0.7171.0_rc-r1-merged.afdo.bz2
diff --git a/chrome/android/profiles/newest.txt b/chrome/android/profiles/newest.txt
index b3e118f7..ebbf4391 100644
--- a/chrome/android/profiles/newest.txt
+++ b/chrome/android/profiles/newest.txt
@@ -1 +1 @@
-chromeos-chrome-amd64-138.0.7166.0_rc-r1-merged.afdo.bz2
+chromeos-chrome-amd64-138.0.7171.0_rc-r1-merged.afdo.bz2
diff --git a/chrome/browser/ai/ai_data_keyed_service.cc b/chrome/browser/ai/ai_data_keyed_service.cc
index f22d2e4d..1582b0f 100644
--- a/chrome/browser/ai/ai_data_keyed_service.cc
+++ b/chrome/browser/ai/ai_data_keyed_service.cc
@@ -986,20 +986,18 @@
   }
   // TODO(https://crbug.com/398271171): Remove when the actor coordinator
   // handles getting a new observation.
-  auto fetcher = std::make_unique<glic::GlicPageContextFetcher>();
+
   glic::FocusedTabData focused_tab_data{tab_->GetContents()->GetWeakPtr()};
-  fetcher->Fetch(
+  glic::GlicPageContextFetcher::Fetch(
       focused_tab_data, DefaultOptions(),
       base::BindOnce(&AiDataKeyedService::ConvertToBrowserActionResult,
-                     weak_factory_.GetWeakPtr(), std::move(callback),
-                     std::move(fetcher), task_id_, tab_id_,
-                     std::move(action_result)));
+                     weak_factory_.GetWeakPtr(), std::move(callback), task_id_,
+                     tab_id_, std::move(action_result)));
 }
 
 void AiDataKeyedService::ConvertToBrowserActionResult(
     base::OnceCallback<void(optimization_guide::proto::BrowserActionResult)>
         callback,
-    std::unique_ptr<glic::GlicPageContextFetcher> fetcher,
     int task_id,
     int tab_id,
     actor::mojom::ActionResultPtr action_result,
diff --git a/chrome/browser/ai/ai_data_keyed_service.h b/chrome/browser/ai/ai_data_keyed_service.h
index 9141b7b..287bbdaa 100644
--- a/chrome/browser/ai/ai_data_keyed_service.h
+++ b/chrome/browser/ai/ai_data_keyed_service.h
@@ -107,7 +107,6 @@
   void ConvertToBrowserActionResult(
       base::OnceCallback<void(optimization_guide::proto::BrowserActionResult)>
           callback,
-      std::unique_ptr<glic::GlicPageContextFetcher> fetcher,
       int task_id,
       int tab_id,
       actor::mojom::ActionResultPtr action_result,
diff --git a/chrome/browser/bookmarks/android/java/src/org/chromium/chrome/browser/bookmarks/ImprovedBookmarkSaveFlowView.java b/chrome/browser/bookmarks/android/java/src/org/chromium/chrome/browser/bookmarks/ImprovedBookmarkSaveFlowView.java
index 17aca55a..6dfd417 100644
--- a/chrome/browser/bookmarks/android/java/src/org/chromium/chrome/browser/bookmarks/ImprovedBookmarkSaveFlowView.java
+++ b/chrome/browser/bookmarks/android/java/src/org/chromium/chrome/browser/bookmarks/ImprovedBookmarkSaveFlowView.java
@@ -14,11 +14,13 @@
 import android.widget.TextView;
 
 import org.chromium.build.annotations.NullMarked;
+import org.chromium.ui.base.LocalizationUtils;
 
 /** Controls the bookmarks save-flow view. */
 @NullMarked
 public class ImprovedBookmarkSaveFlowView extends FrameLayout {
     private View mBookmarkContainer;
+    private ImageView mEditChevron;
     private ImageView mBookmarkImageView;
     private TextView mBookmarkTitleView;
     private TextView mBookmarkSubtitleView;
@@ -35,6 +37,7 @@
         super.onFinishInflate();
 
         mBookmarkContainer = findViewById(R.id.bookmark_container);
+        mEditChevron = findViewById(R.id.edit_chev);
         mBookmarkImageView = findViewById(R.id.bookmark_image);
         mBookmarkTitleView = findViewById(R.id.bookmark_title);
         mBookmarkSubtitleView = findViewById(R.id.bookmark_subtitle);
@@ -43,7 +46,7 @@
 
         mBookmarkContainer.setBackgroundResource(
                 R.drawable.improved_bookmark_save_flow_single_pane_background);
-
+        mEditChevron.setScaleX(LocalizationUtils.isLayoutRtl() ? -1 : 1);
         mBookmarkTitleView.setTextAppearance(R.style.TextAppearance_TextMedium_Secondary);
         mBookmarkSubtitleView.setTextAppearance(R.style.TextAppearance_TextMedium_Secondary);
     }
diff --git a/chrome/browser/bookmarks/android/javatests/src/org/chromium/chrome/browser/bookmarks/ImprovedBookmarkSaveFlowRenderTest.java b/chrome/browser/bookmarks/android/javatests/src/org/chromium/chrome/browser/bookmarks/ImprovedBookmarkSaveFlowRenderTest.java
index c8e9cdd..cea6dba 100644
--- a/chrome/browser/bookmarks/android/javatests/src/org/chromium/chrome/browser/bookmarks/ImprovedBookmarkSaveFlowRenderTest.java
+++ b/chrome/browser/bookmarks/android/javatests/src/org/chromium/chrome/browser/bookmarks/ImprovedBookmarkSaveFlowRenderTest.java
@@ -34,6 +34,7 @@
 import org.chromium.chrome.browser.bookmarks.ImprovedBookmarkSaveFlowProperties.FolderText;
 import org.chromium.chrome.test.ChromeJUnit4RunnerDelegate;
 import org.chromium.chrome.test.util.ChromeRenderTestRule;
+import org.chromium.ui.base.LocalizationUtils;
 import org.chromium.ui.modelutil.PropertyModel;
 import org.chromium.ui.modelutil.PropertyModelChangeProcessor;
 import org.chromium.ui.test.util.BlankUiTestActivity;
@@ -220,4 +221,22 @@
                 });
         mRenderTestRule.render(mContentView, "title_and_subtitle");
     }
+
+    @Test
+    @MediumTest
+    @Feature({"RenderTest"})
+    public void testRtlFlipsChevron() throws IOException {
+        ThreadUtils.runOnUiThreadBlocking(
+                () -> {
+                    LocalizationUtils.setRtlForTesting(true);
+                    mModel.set(
+                            ImprovedBookmarkSaveFlowProperties.TITLE,
+                            BookmarkSaveFlowMediator.createHighlightedCharSequence(
+                                    mActivity, new FolderText("Saved in Mobile bookmarks", 9, 16)));
+                    mModel.set(ImprovedBookmarkSaveFlowProperties.SUBTITLE, "On this device ");
+                });
+
+        mRenderTestRule.render(mContentView, "rtl_chevron");
+        ThreadUtils.runOnUiThreadBlocking(() -> LocalizationUtils.setRtlForTesting(true));
+    }
 }
diff --git a/chrome/browser/chrome_content_browser_client.cc b/chrome/browser/chrome_content_browser_client.cc
index 6f72b63..0b04c4e 100644
--- a/chrome/browser/chrome_content_browser_client.cc
+++ b/chrome/browser/chrome_content_browser_client.cc
@@ -61,7 +61,6 @@
 #include "chrome/browser/browsing_topics/browsing_topics_service_factory.h"
 #include "chrome/browser/btm/btm_browser_signin_detector.h"
 #include "chrome/browser/btm/stateful_bounce_counter.h"
-#include "chrome/browser/captive_portal/captive_portal_service_factory.h"
 #include "chrome/browser/child_process_host_flags.h"
 #include "chrome/browser/chrome_browser_main_extra_parts_nacl_deprecation.h"
 #include "chrome/browser/chrome_content_browser_client_binder_policies.h"
@@ -93,7 +92,6 @@
 #include "chrome/browser/language_detection/language_detection_model_service_factory.h"
 #include "chrome/browser/lifetime/browser_shutdown.h"
 #include "chrome/browser/loader/keep_alive_request_tracker.h"
-#include "chrome/browser/lookalikes/lookalike_url_navigation_throttle.h"
 #include "chrome/browser/media/audio_service_util.h"
 #include "chrome/browser/media/prefs/capture_device_ranking.h"
 #include "chrome/browser/media/router/media_router_feature.h"
@@ -117,7 +115,6 @@
 #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"
 #include "chrome/browser/performance_monitor/chrome_browser_main_extra_parts_performance_monitor.h"
-#include "chrome/browser/plugins/pdf_iframe_navigation_throttle.h"
 #include "chrome/browser/plugins/plugin_utils.h"
 #include "chrome/browser/policy/policy_util.h"
 #include "chrome/browser/policy/profile_policy_connector.h"
@@ -127,7 +124,6 @@
 #include "chrome/browser/preloading/prefetch/no_state_prefetch/chrome_no_state_prefetch_contents_delegate.h"
 #include "chrome/browser/preloading/prefetch/no_state_prefetch/chrome_speculation_host_delegate.h"
 #include "chrome/browser/preloading/prefetch/no_state_prefetch/no_state_prefetch_manager_factory.h"
-#include "chrome/browser/preloading/prefetch/no_state_prefetch/no_state_prefetch_navigation_throttle.h"
 #include "chrome/browser/preloading/prefetch/prefetch_service/chrome_prefetch_service_delegate.h"
 #include "chrome/browser/preloading/prefetch/search_prefetch/field_trial_settings.h"
 #include "chrome/browser/preloading/prefetch/search_prefetch/search_prefetch_url_loader.h"
@@ -162,14 +158,10 @@
 #include "chrome/browser/speech/chrome_speech_recognition_manager_delegate.h"
 #include "chrome/browser/ssl/chrome_security_blocking_page_factory.h"
 #include "chrome/browser/ssl/chrome_security_state_tab_helper.h"
-#include "chrome/browser/ssl/https_defaulted_callbacks.h"
 #include "chrome/browser/ssl/https_upgrades_interceptor.h"
 #include "chrome/browser/ssl/https_upgrades_navigation_throttle.h"
 #include "chrome/browser/ssl/sct_reporting_service.h"
 #include "chrome/browser/ssl/ssl_client_certificate_selector.h"
-#include "chrome/browser/ssl/typed_navigation_upgrade_throttle.h"
-#include "chrome/browser/supervised_user/classify_url_navigation_throttle.h"
-#include "chrome/browser/supervised_user/supervised_user_google_auth_navigation_throttle.h"
 #include "chrome/browser/tab_group_sync/tab_group_sync_utils.h"
 #include "chrome/browser/task_manager/sampling/task_manager_impl.h"
 #include "chrome/browser/task_manager/task_manager_interface.h"
@@ -179,16 +171,12 @@
 #include "chrome/browser/translate/translate_service.h"
 #include "chrome/browser/ui/blocked_content/blocked_window_params.h"
 #include "chrome/browser/ui/blocked_content/chrome_popup_navigation_delegate.h"
-#include "chrome/browser/ui/blocked_content/tab_under_navigation_throttle.h"
 #include "chrome/browser/ui/browser_navigator.h"
 #include "chrome/browser/ui/browser_navigator_params.h"
 #include "chrome/browser/ui/chrome_select_file_policy.h"
 #include "chrome/browser/ui/color/chrome_color_id.h"
 #include "chrome/browser/ui/lens/lens_overlay_side_panel_navigation_throttle.h"
 #include "chrome/browser/ui/login/http_auth_coordinator.h"
-#include "chrome/browser/ui/login/login_navigation_throttle.h"
-#include "chrome/browser/ui/passwords/password_manager_navigation_throttle.h"
-#include "chrome/browser/ui/passwords/well_known_change_password_navigation_throttle.h"
 #include "chrome/browser/ui/prefs/pref_watcher.h"
 #include "chrome/browser/ui/tab_contents/chrome_web_contents_view_delegate.h"
 #include "chrome/browser/ui/ui_features.h"
@@ -252,9 +240,6 @@
 #include "components/error_page/common/error.h"
 #include "components/error_page/common/error_page_switches.h"
 #include "components/error_page/common/localized_error.h"
-#include "components/error_page/content/browser/net_error_auto_reloader.h"
-#include "components/fingerprinting_protection_filter/browser/throttle_manager.h"
-#include "components/fingerprinting_protection_filter/common/fingerprinting_protection_filter_features.h"
 #include "components/google/core/common/google_switches.h"
 #include "components/heap_profiling/in_process/heap_profiler_controller.h"
 #include "components/history/content/browser/visited_link_navigation_throttle.h"
@@ -276,14 +261,11 @@
 #include "components/no_state_prefetch/browser/no_state_prefetch_manager.h"
 #include "components/no_state_prefetch/common/no_state_prefetch_final_status.h"
 #include "components/no_state_prefetch/common/no_state_prefetch_url_loader_throttle.h"
-#include "components/omnibox/common/omnibox_features.h"
 #include "components/page_load_metrics/browser/metrics_web_contents_observer.h"
-#include "components/payments/content/payment_handler_navigation_throttle.h"
 #include "components/payments/content/payment_request_display_manager.h"
 #include "components/payments/content/secure_payment_confirmation_service_factory.h"
 #include "components/pdf/common/pdf_util.h"
 #include "components/permissions/permission_context_base.h"
-#include "components/policy/content/policy_blocklist_navigation_throttle.h"
 #include "components/policy/content/policy_blocklist_service.h"
 #include "components/policy/core/common/management/management_service.h"
 #include "components/policy/core/common/policy_pref_names.h"
@@ -298,7 +280,6 @@
 #include "components/safe_browsing/content/browser/async_check_tracker.h"
 #include "components/safe_browsing/content/browser/browser_url_loader_throttle.h"
 #include "components/safe_browsing/content/browser/password_protection/password_protection_commit_deferring_condition.h"
-#include "components/safe_browsing/content/browser/ui_manager.h"
 #include "components/safe_browsing/core/browser/hashprefix_realtime/hash_realtime_service.h"
 #include "components/safe_browsing/core/browser/realtime/policy_engine.h"
 #include "components/safe_browsing/core/browser/realtime/url_lookup_service.h"
@@ -308,9 +289,6 @@
 #include "components/safe_browsing/core/common/hashprefix_realtime/hash_realtime_utils.h"
 #include "components/safe_browsing/core/common/safe_browsing_prefs.h"
 #include "components/search_engines/template_url_service.h"
-#include "components/security_interstitials/content/insecure_form_navigation_throttle.h"
-#include "components/security_interstitials/content/ssl_error_handler.h"
-#include "components/security_interstitials/content/ssl_error_navigation_throttle.h"
 #include "components/security_state/core/security_state.h"
 #include "components/services/on_device_translation/buildflags/buildflags.h"
 #include "components/site_isolation/pref_names.h"
@@ -452,7 +430,6 @@
 #elif BUILDFLAG(IS_MAC)
 #include "chrome/browser/browser_process_platform_part_mac.h"
 #include "chrome/browser/chrome_browser_main_mac.h"
-#include "chrome/browser/mac/auth_session_request.h"
 #include "chrome/browser/mac/chrome_browser_main_extra_parts_mac.h"
 #include "components/soda/constants.h"
 #include "sandbox/mac/sandbox_serializer.h"
@@ -473,7 +450,6 @@
 #include "chrome/app/chrome_crash_reporter_client.h"
 #include "chrome/browser/ash/arc/fileapi/arc_content_file_system_backend_delegate.h"
 #include "chrome/browser/ash/arc/fileapi/arc_documents_provider_backend_delegate.h"
-#include "chrome/browser/ash/boca/on_task/on_task_locked_session_navigation_throttle.h"
 #include "chrome/browser/ash/drive/fileapi/drivefs_file_system_backend_delegate.h"
 #include "chrome/browser/ash/file_system_provider/fileapi/backend_delegate.h"
 #include "chrome/browser/ash/fileapi/external_file_url_loader_factory.h"
@@ -487,7 +463,6 @@
 #include "chrome/browser/ash/profiles/profile_helper.h"
 #include "chrome/browser/ash/smb_client/fileapi/smbfs_file_system_backend_delegate.h"
 #include "chrome/browser/ash/system/input_device_settings.h"
-#include "chrome/browser/chromeos/app_mode/kiosk_settings_navigation_throttle.h"
 #include "chrome/browser/speech/tts_chromeos.h"
 #include "chrome/browser/speech/tts_controller_delegate_impl.h"
 #include "chrome/browser/ui/ash/main_extra_parts/chrome_browser_main_extra_parts_ash.h"
@@ -585,10 +560,7 @@
 #include "chrome/browser/ui/browser_dialogs.h"
 #include "chrome/browser/ui/browser_finder.h"
 #include "chrome/browser/ui/chrome_pages.h"
-#include "chrome/browser/ui/search/new_tab_page_navigation_throttle.h"
 #include "chrome/browser/ui/views/side_panel/read_anything/read_anything_side_panel_navigation_throttle.h"
-#include "chrome/browser/ui/web_applications/tabbed_web_app_navigation_throttle.h"
-#include "chrome/browser/ui/web_applications/webui_web_app_navigation_throttle.h"
 #include "chrome/browser/ui/webui/chrome_content_browser_client_webui_part.h"
 #include "chrome/browser/ui/webui/webui_util_desktop.h"
 #include "chrome/browser/web_applications/isolated_web_apps/isolated_web_app_error_page.h"
@@ -608,10 +580,6 @@
 #include "services/network/public/mojom/permissions_policy/permissions_policy_feature.mojom.h"
 #endif  //  !BUILDFLAG(IS_ANDROID)
 
-#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX)
-#include "chrome/browser/browser_switcher/browser_switcher_navigation_throttle.h"
-#endif
-
 #if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
 #include "components/crash/core/app/crash_switches.h"
 #include "components/crash/core/app/crashpad.h"
@@ -623,18 +591,9 @@
 
 #if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_WIN)
 #include "chrome/browser/enterprise/chrome_browser_main_extra_parts_enterprise.h"
-#include "chrome/browser/enterprise/profile_management/oidc_auth_response_capture_navigation_throttle.h"
-#include "chrome/browser/enterprise/profile_management/profile_management_navigation_throttle.h"
-#include "chrome/browser/enterprise/signin/managed_profile_required_navigation_throttle.h"
-#include "chrome/browser/enterprise/webstore/chrome_web_store_navigation_throttle.h"
-#include "chrome/browser/enterprise/webstore/features.h"
-#include "chrome/browser/ui/webui/app_settings/web_app_settings_navigation_throttle.h"
 #endif
 
 #if BUILDFLAG(ENABLE_EXTENSIONS_CORE)
-#if !BUILDFLAG(IS_ANDROID)
-#include "chrome/browser/enterprise/connectors/device_trust/navigation_throttle.h"
-#endif  // !BUILDFLAG(IS_ANDROID)
 #include "chrome/browser/enterprise/incognito/incognito_navigation_throttle.h"
 #endif  // BUILDFLAG(ENABLE_EXTENSIONS_CORE)
 
@@ -701,7 +660,6 @@
 
 #if BUILDFLAG(ENABLE_PDF)
 #include "chrome/browser/pdf/chrome_pdf_stream_delegate.h"
-#include "components/pdf/browser/pdf_navigation_throttle.h"
 #include "components/pdf/browser/pdf_url_loader_request_interceptor.h"
 #include "components/pdf/common/constants.h"
 #include "pdf/pdf_features.h"
@@ -727,10 +685,8 @@
 #include "chrome/browser/safe_browsing/chrome_password_protection_service.h"
 #include "chrome/browser/safe_browsing/chrome_ping_manager_factory.h"
 #include "chrome/browser/safe_browsing/cloud_content_scanning/deep_scanning_utils.h"
-#include "chrome/browser/safe_browsing/delayed_warning_navigation_throttle.h"
 #include "chrome/browser/safe_browsing/safe_browsing_service.h"
 #include "chrome/browser/safe_browsing/url_lookup_service_factory.h"
-#include "components/safe_browsing/content/browser/safe_browsing_navigation_throttle.h"
 #include "components/safe_browsing/core/browser/realtime/chrome_enterprise_url_lookup_service.h"
 #endif
 
@@ -857,40 +813,6 @@
   base::OnceClosure threads_ready_closure_;
 };
 
-// Wrapper for SSLErrorHandler::HandleSSLError() that supplies //chrome-level
-// parameters.
-void HandleSSLErrorWrapper(
-    content::WebContents* web_contents,
-    int cert_error,
-    const net::SSLInfo& ssl_info,
-    const GURL& request_url,
-    SSLErrorHandler::BlockingPageReadyCallback blocking_page_ready_callback) {
-  DCHECK(request_url.SchemeIsCryptographic());
-
-  Profile* profile =
-      Profile::FromBrowserContext(web_contents->GetBrowserContext());
-  // Profile should always outlive a WebContents
-  DCHECK(profile);
-
-  captive_portal::CaptivePortalService* captive_portal_service = nullptr;
-
-#if BUILDFLAG(ENABLE_CAPTIVE_PORTAL_DETECTION)
-  captive_portal_service = CaptivePortalServiceFactory::GetForProfile(profile);
-#endif
-
-  const bool is_ssl_error_override_allowed_for_origin =
-      policy::IsOriginInAllowlist(request_url, profile->GetPrefs(),
-                                  prefs::kSSLErrorOverrideAllowedForOrigins,
-                                  prefs::kSSLErrorOverrideAllowed);
-
-  SSLErrorHandler::HandleSSLError(
-      web_contents, cert_error, ssl_info, request_url,
-      std::move(blocking_page_ready_callback),
-      g_browser_process->network_time_tracker(), captive_portal_service,
-      std::make_unique<ChromeSecurityBlockingPageFactory>(),
-      is_ssl_error_override_allowed_for_origin);
-}
-
 // Cached version of the locale so we can return the locale on the I/O
 // thread.
 std::string& GetIOThreadApplicationLocale() {
@@ -1314,31 +1236,6 @@
 }
 #endif
 
-// Returns whether |web_contents| is within a hosted app.
-bool IsInHostedApp(WebContents* web_contents) {
-#if BUILDFLAG(ENABLE_EXTENSIONS)
-  Browser* browser = chrome::FindBrowserWithTab(web_contents);
-  return web_app::AppBrowserController::IsWebApp(browser);
-#else
-  return false;
-#endif
-}
-
-bool IsErrorPageAutoReloadEnabled() {
-  const base::CommandLine& command_line =
-      *base::CommandLine::ForCurrentProcess();
-  if (command_line.HasSwitch(switches::kEnableAutomation)) {
-    return false;
-  }
-  if (command_line.HasSwitch(embedder_support::kEnableAutoReload)) {
-    return true;
-  }
-  if (command_line.HasSwitch(embedder_support::kDisableAutoReload)) {
-    return false;
-  }
-  return true;
-}
-
 #if BUILDFLAG(IS_CHROMEOS)
 void NotifyMultiCaptureStarted(const std::string& label,
                                content::WebContents* web_contents,
@@ -5385,171 +5282,6 @@
   // TODO(https://crbug.com/412524375): Move the following code to
   // CreateAndAddChromeThrottlesForNavigation().
 
-#if BUILDFLAG(ENABLE_GUEST_VIEW)
-  registry.MaybeAddThrottle(
-      extensions::WebViewGuest::MaybeCreateNavigationThrottle(&handle));
-#endif
-
-  registry.MaybeAddThrottle(
-      SupervisedUserGoogleAuthNavigationThrottle::MaybeCreate(&handle));
-
-  registry.MaybeAddThrottle(
-      supervised_user::MaybeCreateClassifyUrlNavigationThrottleFor(&handle));
-
-  if (auto* throttle_manager =
-          subresource_filter::ContentSubresourceFilterThrottleManager::
-              FromNavigationHandle(handle)) {
-    throttle_manager->MaybeAppendNavigationThrottles(registry);
-  }
-
-  if (fingerprinting_protection_filter::features::
-          IsFingerprintingProtectionEnabledForIncognitoState(
-              profile ? profile->IsIncognitoProfile() : false)) {
-    if (auto* throttle_manager = fingerprinting_protection_filter::
-            ThrottleManager::FromNavigationHandle(handle)) {
-      throttle_manager->MaybeAppendNavigationThrottles(registry);
-    }
-  }
-
-  registry.MaybeAddThrottle(
-      LookalikeUrlNavigationThrottle::MaybeCreateNavigationThrottle(&handle));
-
-  registry.MaybeAddThrottle(
-      PDFIFrameNavigationThrottle::MaybeCreateThrottleFor(&handle));
-#if BUILDFLAG(ENABLE_PDF)
-  registry.AddThrottle(std::make_unique<pdf::PdfNavigationThrottle>(
-      &handle, std::make_unique<ChromePdfStreamDelegate>()));
-#endif  // BUILDFLAG(ENABLE_PDF)
-
-  registry.MaybeAddThrottle(TabUnderNavigationThrottle::MaybeCreate(&handle));
-
-  registry.MaybeAddThrottle(
-      WellKnownChangePasswordNavigationThrottle::MaybeCreateThrottleFor(
-          &handle));
-
-  registry.MaybeAddThrottle(
-      PasswordManagerNavigationThrottle::MaybeCreateThrottleFor(&handle));
-
-  registry.AddThrottle(std::make_unique<PolicyBlocklistNavigationThrottle>(
-      registry, handle.GetWebContents()->GetBrowserContext()));
-
-  // Before setting up SSL error detection, configure SSLErrorHandler to invoke
-  // the relevant extension API whenever an SSL interstitial is shown.
-  SSLErrorHandler::SetClientCallbackOnInterstitialsShown(
-      base::BindRepeating(&MaybeTriggerSecurityInterstitialShownEvent));
-  registry.AddThrottle(std::make_unique<SSLErrorNavigationThrottle>(
-      &handle, base::BindOnce(&HandleSSLErrorWrapper),
-      base::BindOnce(&IsInHostedApp),
-      base::BindOnce(
-          &ShouldIgnoreSslInterstitialBecauseNavigationDefaultedToHttps)));
-
-  registry.AddThrottle(std::make_unique<LoginNavigationThrottle>(&handle));
-
-  if (base::FeatureList::IsEnabled(omnibox::kDefaultTypedNavigationsToHttps)) {
-    registry.MaybeAddThrottle(
-        TypedNavigationUpgradeThrottle::MaybeCreateThrottleFor(&handle));
-  }
-
-#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_WIN)
-  registry.MaybeAddThrottle(
-      WebAppSettingsNavigationThrottle::MaybeCreateThrottleFor(&handle));
-  registry.MaybeAddThrottle(
-      profile_management::ProfileManagementNavigationThrottle::
-          MaybeCreateThrottleFor(&handle));
-  registry.MaybeAddThrottle(
-      profile_management::OidcAuthResponseCaptureNavigationThrottle::
-          MaybeCreateThrottleFor(&handle));
-  registry.MaybeAddThrottle(
-      ManagedProfileRequiredNavigationThrottle::MaybeCreateThrottleFor(
-          &handle));
-
-  if (base::FeatureList::IsEnabled(
-          enterprise::webstore::kChromeWebStoreNavigationThrottle)) {
-    registry.AddThrottle(
-        std::make_unique<enterprise_webstore::ChromeWebStoreNavigationThrottle>(
-            &handle));
-  }
-#endif  // BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_WIN)
-
-#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_WIN) || \
-    BUILDFLAG(IS_CHROMEOS)
-  registry.MaybeAddThrottle(
-      enterprise_connectors::DeviceTrustNavigationThrottle::
-          MaybeCreateThrottleFor(&handle));
-#endif  // BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_WIN) ||
-        // BUILDFLAG(IS_CHROMEOS)
-
-#if !BUILDFLAG(IS_ANDROID)
-  registry.MaybeAddThrottle(
-      DevToolsWindow::MaybeCreateNavigationThrottle(&handle));
-
-  registry.MaybeAddThrottle(
-      NewTabPageNavigationThrottle::MaybeCreateThrottleFor(&handle));
-
-  registry.MaybeAddThrottle(
-      web_app::TabbedWebAppNavigationThrottle::MaybeCreateThrottleFor(&handle));
-
-  registry.MaybeAddThrottle(
-      web_app::WebUIWebAppNavigationThrottle::MaybeCreateThrottleFor(&handle));
-#endif
-
-#if BUILDFLAG(SAFE_BROWSING_AVAILABLE)
-  // g_browser_process->safe_browsing_service() may be null in unittests.
-  safe_browsing::SafeBrowsingUIManager* ui_manager =
-      g_browser_process->safe_browsing_service()
-          ? g_browser_process->safe_browsing_service()->ui_manager().get()
-          : nullptr;
-  registry.MaybeAddThrottle(
-      safe_browsing::SafeBrowsingNavigationThrottle::MaybeCreateThrottleFor(
-          &handle, ui_manager));
-
-  if (base::FeatureList::IsEnabled(safe_browsing::kDelayedWarnings)) {
-    registry.AddThrottle(
-        std::make_unique<safe_browsing::DelayedWarningNavigationThrottle>(
-            &handle));
-  }
-#endif
-
-#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX)
-  browser_switcher::BrowserSwitcherNavigationThrottle::MaybeCreateAndAdd(
-      registry);
-#endif
-
-#if BUILDFLAG(IS_CHROMEOS)
-  registry.MaybeAddThrottle(
-      chromeos::KioskSettingsNavigationThrottle::MaybeCreateThrottleFor(
-          &handle));
-
-  registry.MaybeAddThrottle(
-      ash::OnTaskLockedSessionNavigationThrottle::MaybeCreateThrottleFor(
-          &handle));
-#endif
-
-#if BUILDFLAG(IS_MAC)
-  registry.MaybeAddThrottle(MaybeCreateAuthSessionThrottleFor(&handle));
-#endif
-
-  if (profile && profile->GetPrefs()) {
-    registry.MaybeAddThrottle(
-        security_interstitials::InsecureFormNavigationThrottle::
-            MaybeCreateNavigationThrottle(
-                &handle, std::make_unique<ChromeSecurityBlockingPageFactory>(),
-                profile->GetPrefs()));
-  }
-
-  if (IsErrorPageAutoReloadEnabled()) {
-    registry.MaybeAddThrottle(
-        error_page::NetErrorAutoReloader::MaybeCreateThrottleFor(&handle));
-  }
-
-  registry.MaybeAddThrottle(
-      payments::PaymentHandlerNavigationThrottle::MaybeCreateThrottleFor(
-          &handle));
-
-  registry.MaybeAddThrottle(
-      prerender::NoStatePrefetchNavigationThrottle::MaybeCreateThrottleFor(
-          &handle));
-
 #if !BUILDFLAG(IS_ANDROID)
   registry.MaybeAddThrottle(
       ReadAnythingSidePanelNavigationThrottle::CreateFor(&handle));
diff --git a/chrome/browser/chrome_content_browser_client_navigation_throttles.cc b/chrome/browser/chrome_content_browser_client_navigation_throttles.cc
index 07c231c84..ed54f03 100644
--- a/chrome/browser/chrome_content_browser_client_navigation_throttles.cc
+++ b/chrome/browser/chrome_content_browser_client_navigation_throttles.cc
@@ -4,15 +4,52 @@
 
 #include "chrome/browser/chrome_content_browser_client_navigation_throttles.h"
 
+#include "base/command_line.h"
+#include "base/feature_list.h"
+#include "build/build_config.h"
 #include "build/buildflag.h"
+#include "chrome/browser/browser_process.h"
+#include "chrome/browser/interstitials/enterprise_util.h"
+#include "chrome/browser/lookalikes/lookalike_url_navigation_throttle.h"
+#include "chrome/browser/plugins/pdf_iframe_navigation_throttle.h"
+#include "chrome/browser/policy/policy_util.h"
 #include "chrome/browser/preloading/prefetch/no_state_prefetch/chrome_no_state_prefetch_contents_delegate.h"
+#include "chrome/browser/preloading/prefetch/no_state_prefetch/no_state_prefetch_navigation_throttle.h"
 #include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/ssl/chrome_security_blocking_page_factory.h"
+#include "chrome/browser/ssl/https_defaulted_callbacks.h"
+#include "chrome/browser/ssl/typed_navigation_upgrade_throttle.h"
+#include "chrome/browser/supervised_user/classify_url_navigation_throttle.h"
+#include "chrome/browser/supervised_user/supervised_user_google_auth_navigation_throttle.h"
+#include "chrome/browser/ui/blocked_content/tab_under_navigation_throttle.h"
+#include "chrome/browser/ui/login/login_navigation_throttle.h"
+#include "chrome/browser/ui/passwords/password_manager_navigation_throttle.h"
+#include "chrome/browser/ui/passwords/well_known_change_password_navigation_throttle.h"
 #include "chrome/browser/ui/web_applications/navigation_capturing_redirection_throttle.h"
+#include "chrome/common/pref_names.h"
+#include "components/captive_portal/content/captive_portal_service.h"
+#include "components/captive_portal/core/buildflags.h"
+#include "components/embedder_support/switches.h"
+#include "components/error_page/content/browser/net_error_auto_reloader.h"
+#include "components/fingerprinting_protection_filter/browser/throttle_manager.h"
+#include "components/fingerprinting_protection_filter/common/fingerprinting_protection_filter_features.h"
+#include "components/guest_view/buildflags/buildflags.h"
+#include "components/omnibox/common/omnibox_features.h"
 #include "components/page_load_metrics/browser/metrics_navigation_throttle.h"
+#include "components/payments/content/payment_handler_navigation_throttle.h"
+#include "components/policy/content/policy_blocklist_navigation_throttle.h"
+#include "components/safe_browsing/buildflags.h"
+#include "components/security_interstitials/content/insecure_form_navigation_throttle.h"
+#include "components/security_interstitials/content/ssl_error_handler.h"
+#include "components/security_interstitials/content/ssl_error_navigation_throttle.h"
+#include "components/subresource_filter/content/browser/content_subresource_filter_throttle_manager.h"
 #include "content/public/browser/navigation_handle.h"
 #include "content/public/browser/navigation_throttle_registry.h"
 #include "content/public/browser/web_contents.h"
+#include "content/public/common/content_switches.h"
 #include "extensions/buildflags/buildflags.h"
+#include "url/gurl.h"
+#include "pdf/buildflags.h"
 
 #if BUILDFLAG(IS_ANDROID)
 #include "chrome/android/features/dev_ui/buildflags.h"
@@ -26,14 +63,20 @@
 #else  // BUILDFLAG(IS_ANDROID)
 #include "chrome/browser/apps/link_capturing/link_capturing_navigation_throttle.h"
 #include "chrome/browser/apps/link_capturing/web_app_link_capturing_delegate.h"
+#include "chrome/browser/devtools/devtools_window.h"
+#include "chrome/browser/ui/search/new_tab_page_navigation_throttle.h"
+#include "chrome/browser/ui/web_applications/tabbed_web_app_navigation_throttle.h"
+#include "chrome/browser/ui/web_applications/webui_web_app_navigation_throttle.h"
 #endif  // BUILDFLAG(IS_ANDROID)
 
 #if BUILDFLAG(IS_CHROMEOS)
 #include "chrome/browser/apps/intent_helper/chromeos_disabled_apps_throttle.h"
 #include "chrome/browser/apps/link_capturing/chromeos_link_capturing_delegate.h"
 #include "chrome/browser/apps/link_capturing/chromeos_reimpl_navigation_capturing_throttle.h"
+#include "chrome/browser/ash/boca/on_task/on_task_locked_session_navigation_throttle.h"
 #include "chrome/browser/ash/login/signin/merge_session_navigation_throttle.h"
 #include "chrome/browser/ash/login/signin/merge_session_throttling_utils.h"
+#include "chrome/browser/chromeos/app_mode/kiosk_settings_navigation_throttle.h"
 #endif  // BUILDFLAG(IS_CHROMEOS)
 
 #if BUILDFLAG(ENABLE_PLATFORM_APPS)
@@ -43,10 +86,118 @@
 #if BUILDFLAG(ENABLE_EXTENSIONS)
 #include "chrome/browser/extensions/chrome_content_browser_client_extensions_part.h"
 #include "chrome/browser/extensions/user_script_listener.h"
+#include "chrome/browser/ui/browser_finder.h"
+#include "chrome/browser/ui/web_applications/app_browser_controller.h"
 #include "extensions/browser/extension_navigation_throttle.h"
 #include "extensions/browser/extensions_browser_client.h"
 #endif  // BUILDFLAG(ENABLE_EXTENSIONS)
 
+#if BUILDFLAG(ENABLE_GUEST_VIEW)
+#include "extensions/browser/guest_view/web_view/web_view_guest.h"
+#endif
+
+#if BUILDFLAG(ENABLE_PDF)
+#include "chrome/browser/pdf/chrome_pdf_stream_delegate.h"
+#include "components/pdf/browser/pdf_navigation_throttle.h"
+#endif  // BUILDFLAG(ENABLE_PDF)
+
+#if BUILDFLAG(ENABLE_CAPTIVE_PORTAL_DETECTION)
+#include "chrome/browser/captive_portal/captive_portal_service_factory.h"
+#endif  // BUILDFLAG(ENABLE_CAPTIVE_PORTAL_DETECTION)
+
+#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_WIN)
+#include "chrome/browser/browser_switcher/browser_switcher_navigation_throttle.h"
+#include "chrome/browser/enterprise/profile_management/oidc_auth_response_capture_navigation_throttle.h"
+#include "chrome/browser/enterprise/profile_management/profile_management_navigation_throttle.h"
+#include "chrome/browser/enterprise/signin/managed_profile_required_navigation_throttle.h"
+#include "chrome/browser/enterprise/webstore/chrome_web_store_navigation_throttle.h"
+#include "chrome/browser/enterprise/webstore/features.h"
+#include "chrome/browser/ui/webui/app_settings/web_app_settings_navigation_throttle.h"
+#endif  // BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_WIN)
+
+
+#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_WIN) || \
+    BUILDFLAG(IS_CHROMEOS)
+#include "chrome/browser/enterprise/connectors/device_trust/navigation_throttle.h"
+#endif  // BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_WIN) ||
+        // BUILDFLAG(IS_CHROMEOS)
+
+#if BUILDFLAG(SAFE_BROWSING_AVAILABLE)
+#include "chrome/browser/safe_browsing/delayed_warning_navigation_throttle.h"
+#include "chrome/browser/safe_browsing/safe_browsing_service.h"
+#include "components/safe_browsing/content/browser/safe_browsing_navigation_throttle.h"
+#include "components/safe_browsing/content/browser/ui_manager.h"
+#include "components/safe_browsing/core/common/features.h"
+#endif  // BUILDFLAG(SAFE_BROWSING_AVAILABLE)
+
+#if BUILDFLAG(IS_MAC)
+#include "chrome/browser/mac/auth_session_request.h"
+#endif  // BUILDFLAG(IS_MAC)
+
+namespace {
+
+// Wrapper for SSLErrorHandler::HandleSSLError() that supplies //chrome-level
+// parameters.
+void HandleSSLErrorWrapper(
+    content::WebContents* web_contents,
+    int cert_error,
+    const net::SSLInfo& ssl_info,
+    const GURL& request_url,
+    SSLErrorHandler::BlockingPageReadyCallback blocking_page_ready_callback) {
+  DCHECK(request_url.SchemeIsCryptographic());
+
+  Profile* profile =
+      Profile::FromBrowserContext(web_contents->GetBrowserContext());
+  // Profile should always outlive a WebContents
+  DCHECK(profile);
+
+  captive_portal::CaptivePortalService* captive_portal_service = nullptr;
+
+#if BUILDFLAG(ENABLE_CAPTIVE_PORTAL_DETECTION)
+  captive_portal_service = CaptivePortalServiceFactory::GetForProfile(profile);
+#endif
+
+  const bool is_ssl_error_override_allowed_for_origin =
+      policy::IsOriginInAllowlist(request_url, profile->GetPrefs(),
+                                  prefs::kSSLErrorOverrideAllowedForOrigins,
+                                  prefs::kSSLErrorOverrideAllowed);
+
+  SSLErrorHandler::HandleSSLError(
+      web_contents, cert_error, ssl_info, request_url,
+      std::move(blocking_page_ready_callback),
+      g_browser_process->network_time_tracker(), captive_portal_service,
+      std::make_unique<ChromeSecurityBlockingPageFactory>(),
+      is_ssl_error_override_allowed_for_origin);
+}
+
+// Returns whether `web_contents` is within a hosted app.
+bool IsInHostedApp(content::WebContents* web_contents) {
+#if BUILDFLAG(ENABLE_EXTENSIONS)
+  Browser* browser = chrome::FindBrowserWithTab(web_contents);
+  return web_app::AppBrowserController::IsWebApp(browser);
+#else
+  return false;
+#endif
+}
+
+bool IsErrorPageAutoReloadEnabled() {
+  const base::CommandLine& command_line =
+      *base::CommandLine::ForCurrentProcess();
+  if (command_line.HasSwitch(switches::kEnableAutomation)) {
+    return false;
+  }
+  if (command_line.HasSwitch(embedder_support::kEnableAutoReload)) {
+    return true;
+  }
+  if (command_line.HasSwitch(embedder_support::kDisableAutoReload)) {
+    return false;
+  }
+  return true;
+}
+
+}  // namespace
+
+
 void CreateAndAddChromeThrottlesForNavigation(
     content::NavigationThrottleRegistry& registry) {
   content::NavigationHandle& handle = registry.GetNavigationHandle();
@@ -142,10 +293,10 @@
       web_app::NavigationCapturingRedirectionThrottle::MaybeCreate(&handle));
 #endif  // !BUILDFLAG(IS_ANDROID)
 
-#if BUILDFLAG(ENABLE_EXTENSIONS)
   Profile* profile =
       Profile::FromBrowserContext(handle.GetWebContents()->GetBrowserContext());
 
+#if BUILDFLAG(ENABLE_EXTENSIONS)
   if (!extensions::ChromeContentBrowserClientExtensionsPart::
           AreExtensionsDisabledForProfile(profile)) {
     registry.AddThrottle(
@@ -157,5 +308,171 @@
   }
 #endif
 
+#if BUILDFLAG(ENABLE_GUEST_VIEW)
+  registry.MaybeAddThrottle(
+      extensions::WebViewGuest::MaybeCreateNavigationThrottle(&handle));
+#endif
+
+  registry.MaybeAddThrottle(
+      SupervisedUserGoogleAuthNavigationThrottle::MaybeCreate(&handle));
+
+  registry.MaybeAddThrottle(
+      supervised_user::MaybeCreateClassifyUrlNavigationThrottleFor(&handle));
+
+  if (auto* throttle_manager =
+          subresource_filter::ContentSubresourceFilterThrottleManager::
+              FromNavigationHandle(handle)) {
+    throttle_manager->MaybeAppendNavigationThrottles(registry);
+  }
+
+  if (fingerprinting_protection_filter::features::
+          IsFingerprintingProtectionEnabledForIncognitoState(
+              profile ? profile->IsIncognitoProfile() : false)) {
+    if (auto* throttle_manager = fingerprinting_protection_filter::
+            ThrottleManager::FromNavigationHandle(handle)) {
+      throttle_manager->MaybeAppendNavigationThrottles(registry);
+    }
+  }
+
+  registry.MaybeAddThrottle(
+      LookalikeUrlNavigationThrottle::MaybeCreateNavigationThrottle(&handle));
+
+  registry.MaybeAddThrottle(
+      PDFIFrameNavigationThrottle::MaybeCreateThrottleFor(&handle));
+
+#if BUILDFLAG(ENABLE_PDF)
+  registry.AddThrottle(std::make_unique<pdf::PdfNavigationThrottle>(
+      &handle, std::make_unique<ChromePdfStreamDelegate>()));
+#endif  // BUILDFLAG(ENABLE_PDF)
+
+  registry.MaybeAddThrottle(TabUnderNavigationThrottle::MaybeCreate(&handle));
+
+  registry.MaybeAddThrottle(
+      WellKnownChangePasswordNavigationThrottle::MaybeCreateThrottleFor(
+          &handle));
+
+  registry.MaybeAddThrottle(
+      PasswordManagerNavigationThrottle::MaybeCreateThrottleFor(&handle));
+
+  registry.AddThrottle(std::make_unique<PolicyBlocklistNavigationThrottle>(
+      registry, handle.GetWebContents()->GetBrowserContext()));
+
+  // Before setting up SSL error detection, configure SSLErrorHandler to invoke
+  // the relevant extension API whenever an SSL interstitial is shown.
+  SSLErrorHandler::SetClientCallbackOnInterstitialsShown(
+      base::BindRepeating(&MaybeTriggerSecurityInterstitialShownEvent));
+  registry.AddThrottle(std::make_unique<SSLErrorNavigationThrottle>(
+      &handle, base::BindOnce(&HandleSSLErrorWrapper),
+      base::BindOnce(&IsInHostedApp),
+      base::BindOnce(
+          &ShouldIgnoreSslInterstitialBecauseNavigationDefaultedToHttps)));
+
+  registry.AddThrottle(std::make_unique<LoginNavigationThrottle>(&handle));
+
+  if (base::FeatureList::IsEnabled(omnibox::kDefaultTypedNavigationsToHttps)) {
+    registry.MaybeAddThrottle(
+        TypedNavigationUpgradeThrottle::MaybeCreateThrottleFor(&handle));
+  }
+
+#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_WIN)
+  registry.MaybeAddThrottle(
+      WebAppSettingsNavigationThrottle::MaybeCreateThrottleFor(&handle));
+  registry.MaybeAddThrottle(
+      profile_management::ProfileManagementNavigationThrottle::
+          MaybeCreateThrottleFor(&handle));
+  registry.MaybeAddThrottle(
+      profile_management::OidcAuthResponseCaptureNavigationThrottle::
+          MaybeCreateThrottleFor(&handle));
+  registry.MaybeAddThrottle(
+      ManagedProfileRequiredNavigationThrottle::MaybeCreateThrottleFor(
+          &handle));
+
+  if (base::FeatureList::IsEnabled(
+          enterprise::webstore::kChromeWebStoreNavigationThrottle)) {
+    registry.AddThrottle(
+        std::make_unique<enterprise_webstore::ChromeWebStoreNavigationThrottle>(
+            &handle));
+  }
+#endif  // BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_WIN)
+
+#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_WIN) || \
+    BUILDFLAG(IS_CHROMEOS)
+  registry.MaybeAddThrottle(
+      enterprise_connectors::DeviceTrustNavigationThrottle::
+          MaybeCreateThrottleFor(&handle));
+#endif  // BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_WIN) ||
+        // BUILDFLAG(IS_CHROMEOS)
+
+#if !BUILDFLAG(IS_ANDROID)
+  registry.MaybeAddThrottle(
+      DevToolsWindow::MaybeCreateNavigationThrottle(&handle));
+
+  registry.MaybeAddThrottle(
+      NewTabPageNavigationThrottle::MaybeCreateThrottleFor(&handle));
+
+  registry.MaybeAddThrottle(
+      web_app::TabbedWebAppNavigationThrottle::MaybeCreateThrottleFor(&handle));
+
+  registry.MaybeAddThrottle(
+      web_app::WebUIWebAppNavigationThrottle::MaybeCreateThrottleFor(&handle));
+#endif  // !BUILDFLAG(IS_ANDROID)
+
+#if BUILDFLAG(SAFE_BROWSING_AVAILABLE)
+  // g_browser_process->safe_browsing_service() may be null in unittests.
+  safe_browsing::SafeBrowsingUIManager* ui_manager =
+      g_browser_process->safe_browsing_service()
+          ? g_browser_process->safe_browsing_service()->ui_manager().get()
+          : nullptr;
+  registry.MaybeAddThrottle(
+      safe_browsing::SafeBrowsingNavigationThrottle::MaybeCreateThrottleFor(
+          &handle, ui_manager));
+
+  if (base::FeatureList::IsEnabled(safe_browsing::kDelayedWarnings)) {
+    registry.AddThrottle(
+        std::make_unique<safe_browsing::DelayedWarningNavigationThrottle>(
+            &handle));
+  }
+#endif  // BUILDFLAG(SAFE_BROWSING_AVAILABLE)
+
+#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX)
+  browser_switcher::BrowserSwitcherNavigationThrottle::MaybeCreateAndAdd(
+      registry);
+#endif  // BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX)
+
+#if BUILDFLAG(IS_CHROMEOS)
+  registry.MaybeAddThrottle(
+      chromeos::KioskSettingsNavigationThrottle::MaybeCreateThrottleFor(
+          &handle));
+
+  registry.MaybeAddThrottle(
+      ash::OnTaskLockedSessionNavigationThrottle::MaybeCreateThrottleFor(
+          &handle));
+#endif
+
+#if BUILDFLAG(IS_MAC)
+  registry.MaybeAddThrottle(MaybeCreateAuthSessionThrottleFor(&handle));
+#endif
+
+  if (profile && profile->GetPrefs()) {
+    registry.MaybeAddThrottle(
+        security_interstitials::InsecureFormNavigationThrottle::
+            MaybeCreateNavigationThrottle(
+                &handle, std::make_unique<ChromeSecurityBlockingPageFactory>(),
+                profile->GetPrefs()));
+  }
+
+  if (IsErrorPageAutoReloadEnabled()) {
+    registry.MaybeAddThrottle(
+        error_page::NetErrorAutoReloader::MaybeCreateThrottleFor(&handle));
+  }
+
+  registry.MaybeAddThrottle(
+      payments::PaymentHandlerNavigationThrottle::MaybeCreateThrottleFor(
+          &handle));
+
+  registry.MaybeAddThrottle(
+      prerender::NoStatePrefetchNavigationThrottle::MaybeCreateThrottleFor(
+          &handle));
+
   // Add new throttles here.
 }
diff --git a/chrome/browser/data_sharing/android/java/src/org/chromium/chrome/browser/data_sharing/DataSharingTabManager.java b/chrome/browser/data_sharing/android/java/src/org/chromium/chrome/browser/data_sharing/DataSharingTabManager.java
index e23095e29..f6f6ce3 100644
--- a/chrome/browser/data_sharing/android/java/src/org/chromium/chrome/browser/data_sharing/DataSharingTabManager.java
+++ b/chrome/browser/data_sharing/android/java/src/org/chromium/chrome/browser/data_sharing/DataSharingTabManager.java
@@ -45,6 +45,7 @@
 import org.chromium.components.browser_ui.share.ShareParams;
 import org.chromium.components.collaboration.CollaborationControllerDelegate;
 import org.chromium.components.collaboration.CollaborationService;
+import org.chromium.components.collaboration.CollaborationServiceLeaveOrDeleteEntryPoint;
 import org.chromium.components.collaboration.CollaborationServiceShareOrManageEntryPoint;
 import org.chromium.components.collaboration.FlowType;
 import org.chromium.components.collaboration.Outcome;
@@ -537,7 +538,6 @@
             getTabGroupModelFilter().createSingleTabGroup(tab);
         }
         createOrManageFlow(
-                activity,
                 EitherGroupId.createLocalId(
                         new LocalTabGroupId(assumeNonNull(tab.getTabGroupId()))),
                 entryPoint,
@@ -547,13 +547,11 @@
     /**
      * Creates or manage a collaboration group.
      *
-     * @param activity The activity in which the group is to be created.
      * @param eitherId The sync ID or local tab group ID of the tab group.
      * @param entry The entry point of the flow.
      * @param createGroupFinishedCallback Callback invoked when the creation flow is finished.
      */
     public void createOrManageFlow(
-            Activity activity,
             EitherGroupId eitherId,
             @CollaborationServiceShareOrManageEntryPoint int entry,
             @Nullable Callback<Boolean> createGroupFinishedCallback) {
@@ -570,6 +568,21 @@
     }
 
     /**
+     * Leave or delete a collaboration group.
+     *
+     * @param eitherId The sync ID or local tab group ID of the tab group.
+     * @param entry The entry point of the flow.
+     */
+    public void leaveOrDeleteFlow(
+            EitherGroupId eitherId, @CollaborationServiceLeaveOrDeleteEntryPoint int entry) {
+        mCurrentDelegate =
+                mCollaborationControllerDelegateFactory.create(
+                        FlowType.LEAVE_OR_DELETE, /* switchToTabSwitcherCallback= */ null);
+        assumeNonNull(mCollaborationService);
+        mCollaborationService.startLeaveOrDeleteFlow(mCurrentDelegate, eitherId, entry);
+    }
+
+    /**
      * Show the share dialog screen.
      *
      * @param activity The activity to show the UI for.
@@ -895,7 +908,6 @@
         Runnable manageSharingCallback =
                 () ->
                         createOrManageFlow(
-                                activity,
                                 EitherGroupId.createSyncId(assumeNonNull(existingGroup.syncId)),
                                 CollaborationServiceShareOrManageEntryPoint.RECENT_ACTIVITY,
                                 /* createGroupFinishedCallback= */ null);
diff --git a/chrome/browser/data_sharing/android/java/src/org/chromium/chrome/browser/data_sharing/DataSharingTabManagerUnitTest.java b/chrome/browser/data_sharing/android/java/src/org/chromium/chrome/browser/data_sharing/DataSharingTabManagerUnitTest.java
index 98bfcc0..fe84be6 100644
--- a/chrome/browser/data_sharing/android/java/src/org/chromium/chrome/browser/data_sharing/DataSharingTabManagerUnitTest.java
+++ b/chrome/browser/data_sharing/android/java/src/org/chromium/chrome/browser/data_sharing/DataSharingTabManagerUnitTest.java
@@ -241,7 +241,7 @@
         doReturn(mSavedTabGroup).when(mTabGroupSyncService).getGroup(LOCAL_ID);
         EitherGroupId either_id = EitherGroupId.createLocalId(LOCAL_ID);
         mDataSharingTabManager.createOrManageFlow(
-                mActivity, either_id, CollaborationServiceShareOrManageEntryPoint.UNKNOWN, null);
+                either_id, CollaborationServiceShareOrManageEntryPoint.UNKNOWN, null);
 
         verify(mCollaborationService).startShareOrManageFlow(any(), eq(either_id), anyInt());
     }
diff --git a/chrome/browser/data_sharing/android/java/src/org/chromium/chrome/browser/data_sharing/InstantMessageDelegateImpl.java b/chrome/browser/data_sharing/android/java/src/org/chromium/chrome/browser/data_sharing/InstantMessageDelegateImpl.java
index baf58a47..98a04a4 100644
--- a/chrome/browser/data_sharing/android/java/src/org/chromium/chrome/browser/data_sharing/InstantMessageDelegateImpl.java
+++ b/chrome/browser/data_sharing/android/java/src/org/chromium/chrome/browser/data_sharing/InstantMessageDelegateImpl.java
@@ -369,7 +369,6 @@
                     if (mTabGroupSyncService.getGroup(syncId) == null) return;
 
                     dataSharingTabManager.createOrManageFlow(
-                            activity,
                             EitherGroupId.createSyncId(syncId),
                             CollaborationServiceShareOrManageEntryPoint.ANDROID_MESSAGE,
                             /* createGroupFinishedCallback= */ null);
diff --git a/chrome/browser/data_sharing/android/java/src/org/chromium/chrome/browser/data_sharing/InstantMessageDelegateImplUnitTest.java b/chrome/browser/data_sharing/android/java/src/org/chromium/chrome/browser/data_sharing/InstantMessageDelegateImplUnitTest.java
index 2f8d23ad..ebd1cc9 100644
--- a/chrome/browser/data_sharing/android/java/src/org/chromium/chrome/browser/data_sharing/InstantMessageDelegateImplUnitTest.java
+++ b/chrome/browser/data_sharing/android/java/src/org/chromium/chrome/browser/data_sharing/InstantMessageDelegateImplUnitTest.java
@@ -314,7 +314,7 @@
         Supplier<Integer> action = propertyModel.get(ON_PRIMARY_ACTION);
         assertNotNull(action);
         assertEquals(DISMISS_IMMEDIATELY, action.get().intValue());
-        verify(mDataSharingTabManager).createOrManageFlow(any(), any(), anyInt(), any());
+        verify(mDataSharingTabManager).createOrManageFlow(any(), anyInt(), any());
     }
 
     @Test
@@ -329,7 +329,7 @@
         Supplier<Integer> action = propertyModel.get(ON_PRIMARY_ACTION);
         assertNotNull(action);
         assertEquals(DISMISS_IMMEDIATELY, action.get().intValue());
-        verify(mDataSharingTabManager, never()).createOrManageFlow(any(), any(), anyInt(), any());
+        verify(mDataSharingTabManager, never()).createOrManageFlow(any(), anyInt(), any());
     }
 
     @Test
diff --git a/chrome/browser/enterprise/connectors/analysis/content_analysis_delegate.cc b/chrome/browser/enterprise/connectors/analysis/content_analysis_delegate.cc
index bcd95d3..43f09064 100644
--- a/chrome/browser/enterprise/connectors/analysis/content_analysis_delegate.cc
+++ b/chrome/browser/enterprise/connectors/analysis/content_analysis_delegate.cc
@@ -404,7 +404,7 @@
         show_fail_closed_ui ? FinalContentAnalysisResult::FAIL_CLOSED
                             : FinalContentAnalysisResult::SUCCESS;
 
-#if BUILDFLAG(ENABLE_GLIC)
+#if BUILDFLAG(IS_WIN)
     content::WebContents* top_web_contents =
         guest_view::GuestViewBase::GetTopLevelWebContents(
             web_contents->GetResponsibleWebContents());
diff --git a/chrome/browser/enterprise/connectors/analysis/content_analysis_dialog.cc b/chrome/browser/enterprise/connectors/analysis/content_analysis_dialog.cc
index ba50a6c..ab04e8a 100644
--- a/chrome/browser/enterprise/connectors/analysis/content_analysis_dialog.cc
+++ b/chrome/browser/enterprise/connectors/analysis/content_analysis_dialog.cc
@@ -13,9 +13,11 @@
 #include "cc/paint/paint_flags.h"
 #include "chrome/browser/enterprise/connectors/analysis/content_analysis_features.h"
 #include "chrome/browser/safe_browsing/cloud_content_scanning/deep_scanning_utils.h"
+#include "chrome/common/buildflags.h"
 #include "chrome/grit/generated_resources.h"
 #include "chrome/grit/theme_resources.h"
 #include "components/constrained_window/constrained_window_views.h"
+#include "components/guest_view/browser/guest_view_base.h"
 #include "components/strings/grit/components_strings.h"
 #include "components/vector_icons/vector_icons.h"
 #include "components/web_modal/web_contents_modal_dialog_manager.h"
@@ -53,6 +55,13 @@
 
 #include "base/win/windows_h_disallowed.h"
 
+#if BUILDFLAG(ENABLE_GLIC)
+#include "base/metrics/histogram_functions.h"
+#include "chrome/browser/glic/host/guest_util.h"
+#include "chrome/browser/glic/widget/glic_widget.h"
+#include "components/guest_view/browser/guest_view_base.h"
+#endif
+
 namespace enterprise_connectors {
 
 namespace {
@@ -105,6 +114,25 @@
 
 };
 
+gfx::Rect GetDialogBounds(content::WebContents* contents,
+                          const gfx::Rect& current_widget_bounds) {
+  gfx::Rect rect = contents->GetContainerBounds();
+
+  // This will show the dialog right above the top of the contents.
+  rect.set_y(rect.y() - 40);
+#if BUILDFLAG(ENABLE_GLIC)
+  if (glic::IsGlicWebUI(contents)) {
+    // This will show the dialog right below the "header" part of Glic.
+    rect.set_y(rect.y() + 80);
+  }
+#endif  // BUILDFLAG(ENABLE_GLIC)
+
+  rect.set_x(rect.x() + (rect.width() / 2) -
+             (current_widget_bounds.width() / 2));
+
+  return rect;
+}
+
 ContentAnalysisDialog::TestObserver* observer_for_testing = nullptr;
 
 }  // namespace
@@ -246,10 +274,11 @@
     download_item_->AddObserver(this);
 
   // Because the display of the dialog is delayed, it won't block UI
-  // interaction with the tab until it is visible.  To block interaction as of
-  // now, ignore input events manually.
+  // interaction with the top level web contents until it is visible.  To block
+  // interaction as of now, ignore input events manually.
   top_level_contents_ =
       constrained_window::GetTopLevelWebContents(web_contents())->GetWeakPtr();
+
   top_level_contents_->StoreFocus();
   scoped_ignore_input_events_ =
       top_level_contents_->IgnoreInputEvents(std::nullopt);
@@ -278,6 +307,22 @@
     return;
   }
 
+// Glic port enabled for Mac only at the moment until fixed on Windows.
+// TODO(416748209): Follow up with full port of ContentAnalysisDialog to use
+// non web modals on both Mac and Windows for all sources.
+#if BUILDFLAG(IS_MAC)
+  if (glic::IsGlicWebUI(top_level_contents_.get())) {
+    // make sure only one dialog is displayed at a time. If a dialog exists we
+    // just update the view.
+    if (contents_view_) {
+      return;
+    }
+    ShowNonTabDialogNow();
+    base::UmaHistogramEnumeration("Glic.Modal.DeepScan", access_point_);
+    return;
+  }
+#endif
+
   auto* manager =
       web_modal::WebContentsModalDialogManager::FromWebContents(web_contents());
   if (!manager) {
@@ -303,6 +348,23 @@
   }
 }
 
+void ContentAnalysisDialog::ShowNonTabDialogNow() {
+  content::WebContents* top_web_contents =
+      guest_view::GuestViewBase::GetTopLevelWebContents(web_contents());
+  raw_ptr<views::Widget> dialog_widget =
+      views::DialogDelegate::CreateDialogWidget(
+          weak_ptr_factory_.GetWeakPtr().get(), gfx::NativeWindow(),
+          top_web_contents->GetNativeView());
+
+  dialog_widget->SetBounds(GetDialogBounds(
+      top_web_contents, dialog_widget->GetWindowBoundsInScreen()));
+
+  dialog_widget->Show();
+  if (observer_for_testing) {
+    observer_for_testing->ViewsFirstShown(this, first_shown_timestamp_);
+  }
+}
+
 std::u16string ContentAnalysisDialog::GetWindowTitle() const {
   return std::u16string();
 }
diff --git a/chrome/browser/enterprise/connectors/analysis/content_analysis_dialog.h b/chrome/browser/enterprise/connectors/analysis/content_analysis_dialog.h
index 2b76ff3..58649d5d 100644
--- a/chrome/browser/enterprise/connectors/analysis/content_analysis_dialog.h
+++ b/chrome/browser/enterprise/connectors/analysis/content_analysis_dialog.h
@@ -213,6 +213,7 @@
 
   // Callback function of delayed timer to make the dialog visible.
   void ShowDialogNow();
+  void ShowNonTabDialogNow();
 
   void UpdateStateFromFinalResult(FinalContentAnalysisResult final_result);
 
diff --git a/chrome/browser/extensions/service_worker_tracking_browsertest.cc b/chrome/browser/extensions/service_worker_tracking_browsertest.cc
index 76aec9e9..a03c0c6 100644
--- a/chrome/browser/extensions/service_worker_tracking_browsertest.cc
+++ b/chrome/browser/extensions/service_worker_tracking_browsertest.cc
@@ -482,10 +482,10 @@
 
 // Test that if a browser stop notification is received before the render stop
 // notification (since these things can be triggered independently) the worker's
-// browser readiness remains not ready.
+// browser and renderer state are both set to not ready.
 IN_PROC_BROWSER_TEST_F(
     ServiceWorkerStopTrackingBrowserTest,
-    OnStoppedUpdatesBrowserState_BeforeRenderStopNotification) {
+    OnStoppedUpdatesBrowserAndRendererState_BeforeRenderStopNotification) {
   ASSERT_NO_FATAL_FAILURE(LoadServiceWorkerExtension());
 
   // Get information about worker for extension that will be stopped soon.
@@ -522,6 +522,8 @@
   // notification reset it to no longer ready.
   EXPECT_EQ(worker_state->browser_state(),
             ServiceWorkerTaskQueue::BrowserState::kInitial);
+  EXPECT_EQ(worker_state->renderer_state(),
+            ServiceWorkerTaskQueue::RendererState::kNotActive);
 
   // Simulate the render stop notification arriving afterwards.
   task_queue->DidStopServiceWorkerContext(
@@ -531,17 +533,19 @@
       stopped_service_worker_id->version_id,
       stopped_service_worker_id->thread_id);
 
-  // Confirm the worker state still exists and browser state remains the same.
+  // Confirm the worker state still exists and state remains the same.
   EXPECT_EQ(worker_state->browser_state(),
             ServiceWorkerTaskQueue::BrowserState::kInitial);
+  EXPECT_EQ(worker_state->renderer_state(),
+            ServiceWorkerTaskQueue::RendererState::kNotActive);
 }
 
 // Test that if a browser stop notification is received after the render stop
 // notification (since these things can be triggered independently)
-// it updates the worker's browser readiness information to not ready.
+// the worker's browser and renderer readiness information remains not ready.
 IN_PROC_BROWSER_TEST_F(
     ServiceWorkerStopTrackingBrowserTest,
-    OnStoppedUpdatesBrowserState_AfterRenderStopNotification) {
+    OnStoppedUpdatesBrowserAndRendererState_AfterRenderStopNotification) {
   ASSERT_NO_FATAL_FAILURE(LoadServiceWorkerExtension());
 
   // Get information about worker for extension that will be stopped soon.
@@ -575,17 +579,23 @@
   ASSERT_TRUE(content::CheckServiceWorkerIsStopped(
       sw_context, stopped_service_worker_id->version_id));
 
-  // Confirm the worker state still exists and browser state is still ready.
+  // Confirm the worker state still exists and browser and renderer state are
+  // not ready.
   EXPECT_EQ(worker_state->browser_state(),
-            ServiceWorkerTaskQueue::BrowserState::kReady);
+            ServiceWorkerTaskQueue::BrowserState::kInitial);
+  EXPECT_EQ(worker_state->renderer_state(),
+            ServiceWorkerTaskQueue::RendererState::kNotActive);
 
   // Simulate browser stop notification after the render stop notification.
   ServiceWorkerTaskQueue::Get(profile())->OnStopped(
       stopped_service_worker_id->version_id, sw_info);
 
-  // Confirm the worker state still exists, but browser state is not ready.
+  // Confirm the worker state still exists, and browser and renderer state
+  // remain not ready.
   EXPECT_EQ(worker_state->browser_state(),
             ServiceWorkerTaskQueue::BrowserState::kInitial);
+  EXPECT_EQ(worker_state->renderer_state(),
+            ServiceWorkerTaskQueue::RendererState::kNotActive);
 }
 
 // Test that if a browser stop notification is received after a worker is
@@ -634,9 +644,9 @@
 // Test that if a renderer process exit notification is received before
 // a browser stop notification (since these things can be triggered
 // independently) and a context stop notification, it updates the worker's
-// renderer active state to inactive.
+// browser and renderer active state to inactive.
 IN_PROC_BROWSER_TEST_F(ServiceWorkerStopTrackingBrowserTest,
-                       RenderProcessExitedUpdatesRendererState) {
+                       RenderProcessExitedUpdatesBrowserAndRendererState) {
   ASSERT_NO_FATAL_FAILURE(LoadServiceWorkerExtension());
 
   // Get information about worker for extension that will be stopped soon.
@@ -679,8 +689,10 @@
   ASSERT_TRUE(
       content::CheckServiceWorkerIsStopped(sw_context, worker_id->version_id));
 
-  // Confirm the worker state still exists and renderer state has been set to
-  // inactive by `ServiceWorkerHost::RenderProcessForWorkerExited`.
+  // Confirm the worker state still exists and browser and renderer states have
+  // been set to inactive by `ServiceWorkerHost::RenderProcessForWorkerExited`.
+  EXPECT_EQ(worker_state->browser_state(),
+            ServiceWorkerTaskQueue::BrowserState::kInitial);
   EXPECT_EQ(worker_state->renderer_state(),
             ServiceWorkerTaskQueue::RendererState::kNotActive);
 }
diff --git a/chrome/browser/glic/glic_keyed_service.cc b/chrome/browser/glic/glic_keyed_service.cc
index 34afb46..dccf0b3 100644
--- a/chrome/browser/glic/glic_keyed_service.cc
+++ b/chrome/browser/glic/glic_keyed_service.cc
@@ -345,22 +345,8 @@
 
   metrics_->DidRequestContextFromFocusedTab();
 
-  auto fetcher = std::make_unique<glic::GlicPageContextFetcher>();
-  fetcher->Fetch(
-      GetFocusedTabData(), options,
-      base::BindOnce(
-          // Bind `fetcher` to the callback to keep it in scope until it
-          // returns.
-          // TODO(harringtond): Consider adding throttling of how often we fetch
-          // context.
-          // TODO(harringtond): Consider deleting the fetcher if the page
-          // handler is unbound before the fetch completes.
-          [](std::unique_ptr<glic::GlicPageContextFetcher> fetcher,
-             mojom::WebClientHandler::GetContextFromFocusedTabCallback callback,
-             mojom::GetContextResultPtr result) {
-            std::move(callback).Run(std::move(result));
-          },
-          std::move(fetcher), std::move(callback)));
+  GlicPageContextFetcher::Fetch(GetFocusedTabData(), options,
+                                std::move(callback));
 }
 
 void GlicKeyedService::ActInFocusedTab(
diff --git a/chrome/browser/glic/host/context/glic_page_context_fetcher.cc b/chrome/browser/glic/host/context/glic_page_context_fetcher.cc
index 704cc5bc..c529bf1c 100644
--- a/chrome/browser/glic/host/context/glic_page_context_fetcher.cc
+++ b/chrome/browser/glic/host/context/glic_page_context_fetcher.cc
@@ -124,6 +124,26 @@
     FocusedTabData focused_tab_data,
     const mojom::GetTabContextOptions& options,
     glic::mojom::WebClientHandler::GetContextFromFocusedTabCallback callback) {
+  CHECK(callback);
+  auto self = std::make_unique<GlicPageContextFetcher>();
+  auto* raw_self = self.get();
+  raw_self->FetchStart(
+      focused_tab_data, options,
+      base::BindOnce(
+          // Bind `fetcher` to the callback to keep it in scope until it
+          // returns.
+          [](std::unique_ptr<glic::GlicPageContextFetcher> fetcher,
+             mojom::WebClientHandler::GetContextFromFocusedTabCallback callback,
+             mojom::GetContextResultPtr result) {
+            std::move(callback).Run(std::move(result));
+          },
+          std::move(self), std::move(callback)));
+}
+
+void GlicPageContextFetcher::FetchStart(
+    FocusedTabData focused_tab_data,
+    const mojom::GetTabContextOptions& options,
+    glic::mojom::WebClientHandler::GetContextFromFocusedTabCallback callback) {
   base::expected<content::WebContents*, std::string_view> focus =
       focused_tab_data.GetFocus();
   if (!focus.has_value()) {
@@ -203,6 +223,9 @@
           base::BindOnce(&GlicPageContextFetcher::ReceivedContextEligibility,
                          GetWeakPtr())));
 
+  // Note: initialization_done_ guards against processing
+  // `RunCallbackIfComplete()` until we reach this point.
+  initialization_done_ = true;
   RunCallbackIfComplete();
 }
 
@@ -300,6 +323,10 @@
 }
 
 void GlicPageContextFetcher::RunCallbackIfComplete() {
+  if (!initialization_done_) {
+    return;
+  }
+
   // Continue only if the primary page changed or work is complete.
   bool work_complete =
       (screenshot_done_ && inner_text_done_ && annotated_page_content_done_ &&
diff --git a/chrome/browser/glic/host/context/glic_page_context_fetcher.h b/chrome/browser/glic/host/context/glic_page_context_fetcher.h
index 0b22f02..3937b13 100644
--- a/chrome/browser/glic/host/context/glic_page_context_fetcher.h
+++ b/chrome/browser/glic/host/context/glic_page_context_fetcher.h
@@ -32,10 +32,8 @@
   GlicPageContextFetcher();
   ~GlicPageContextFetcher() override;
 
-  // Fetches the page context. May be called at most once.
-  // TODO(harringtond): This API is error-prone, consider making this a static
-  // function so that Fetch() can't be called multiple times.
-  void Fetch(
+  // Fetches the page context.
+  static void Fetch(
       FocusedTabData focused_tab_data,
       const mojom::GetTabContextOptions& options,
       glic::mojom::WebClientHandler::GetContextFromFocusedTabCallback callback);
@@ -44,6 +42,11 @@
   void PrimaryPageChanged(content::Page& page) override;
 
  private:
+  void FetchStart(
+      FocusedTabData focused_tab_data,
+      const mojom::GetTabContextOptions& options,
+      glic::mojom::WebClientHandler::GetContextFromFocusedTabCallback callback);
+
   void GetTabScreenshot(content::WebContents& web_contents);
   void ReceivedViewportBitmap(const SkBitmap& bitmap);
   void RecievedJpegScreenshot(
@@ -69,6 +72,7 @@
   // Intermediate results:
 
   // Whether work is complete for each task, does not imply success.
+  bool initialization_done_ = false;
   bool screenshot_done_ = false;
   bool inner_text_done_ = false;
   bool pdf_done_ = false;
diff --git a/chrome/browser/glic/host/glic_actor_controller.cc b/chrome/browser/glic/host/glic_actor_controller.cc
index f8a8423..ae62d22 100644
--- a/chrome/browser/glic/host/glic_actor_controller.cc
+++ b/chrome/browser/glic/host/glic_actor_controller.cc
@@ -169,18 +169,7 @@
   // with GlicKeyedService::GetContextFromFocusedTab(). It's not clear yet if
   // the same permission checks, etc. should apply here.
 
-  auto fetcher = std::make_unique<glic::GlicPageContextFetcher>();
-  fetcher->Fetch(
-      focused_tab_data, options,
-      base::BindOnce(
-          // Bind `fetcher` to the callback to keep it in scope until it
-          // returns.
-          [](std::unique_ptr<glic::GlicPageContextFetcher> fetcher,
-             mojom::WebClientHandler::GetContextFromFocusedTabCallback callback,
-             mojom::GetContextResultPtr result) {
-            std::move(callback).Run(std::move(result));
-          },
-          std::move(fetcher), std::move(callback)));
+  GlicPageContextFetcher::Fetch(focused_tab_data, options, std::move(callback));
 }
 
 base::WeakPtr<const GlicActorController> GlicActorController::GetWeakPtr()
diff --git a/chrome/browser/glic/host/glic_annotation_manager_interactive_uitest.cc b/chrome/browser/glic/host/glic_annotation_manager_interactive_uitest.cc
index c06d0acc..8c996fd 100644
--- a/chrome/browser/glic/host/glic_annotation_manager_interactive_uitest.cc
+++ b/chrome/browser/glic/host/glic_annotation_manager_interactive_uitest.cc
@@ -145,12 +145,11 @@
       ASSERT_TRUE(glic_service);
 
       base::RunLoop run_loop(base::RunLoop::Type::kNestableTasksAllowed);
-      auto fetcher = std::make_unique<GlicPageContextFetcher>();
 
       auto options = mojom::GetTabContextOptions::New();
       options->include_annotated_page_content = true;
 
-      fetcher->Fetch(
+      GlicPageContextFetcher::Fetch(
           glic_service->GetFocusedTabData(), *options,
           base::BindLambdaForTesting([&](mojom::GetContextResultPtr result) {
             mojo_base::ProtoWrapper& serialized_apc =
diff --git a/chrome/browser/resources/glic/glic.css b/chrome/browser/resources/glic/glic.css
index 4eb5de7..0b30477 100644
--- a/chrome/browser/resources/glic/glic.css
+++ b/chrome/browser/resources/glic/glic.css
@@ -21,6 +21,7 @@
 
 body {
   overflow: hidden;
+  user-select: none;
 }
 
 webview:not([hidden]) {
diff --git a/chrome/browser/resources/pdf/ink2_manager.ts b/chrome/browser/resources/pdf/ink2_manager.ts
index 4fcf73b..cd6d5fd6 100644
--- a/chrome/browser/resources/pdf/ink2_manager.ts
+++ b/chrome/browser/resources/pdf/ink2_manager.ts
@@ -4,6 +4,7 @@
 
 import {assert} from 'chrome://resources/js/assert.js';
 import {PromiseResolver} from 'chrome://resources/js/promise_resolver.js';
+import {isRTL} from 'chrome://resources/js/util.js';
 
 import type {AnnotationBrush, Color, Point, TextAnnotation, TextAttributes, TextBoxRect, TextStyles} from './constants.js';
 import {AnnotationBrushType, TextAlignment, TextStyle, TextTypeface} from './constants.js';
@@ -68,6 +69,22 @@
   // No-op if there is no PDF page at `location`.
   initializeTextAnnotation(location: Point) {
     assert(this.viewport_);
+    // First check if the click was on a scrollbar. If so, ignore it to avoid
+    // interfering with scroll.
+    const hasScrollbars = this.viewport_.documentHasScrollbars();
+    if (hasScrollbars.vertical &&
+            (isRTL() && location.x <= this.viewport_.scrollbarWidth) ||
+        (!isRTL() &&
+         location.x >=
+             (this.viewport_.size.width - this.viewport_.scrollbarWidth))) {
+      return;
+    }
+    if (hasScrollbars.horizontal &&
+        location.y >=
+            (this.viewport_.size.height - this.viewport_.scrollbarWidth)) {
+      return;
+    }
+
     const page = this.viewport_.getPageAtPoint(location);
     if (page === -1) {
       return;
diff --git a/chrome/browser/resources/side_panel/read_anything/app.ts b/chrome/browser/resources/side_panel/read_anything/app.ts
index a89c1f24..219332b 100644
--- a/chrome/browser/resources/side_panel/read_anything/app.ts
+++ b/chrome/browser/resources/side_panel/read_anything/app.ts
@@ -150,10 +150,6 @@
 
   private accessor imagesEnabled: boolean = false;
 
-  // If the node id of the first text node that should be used by Read Aloud
-  // has been set. This is null if the id has not been set.
-  firstTextNodeSetForReadAloud: number|null = null;
-
   constructor() {
     super();
     this.constructorTime = Date.now();
@@ -197,7 +193,6 @@
       // not always reliabled called.
       this.speech_.cancel();
       this.hasContent_ = false;
-      this.firstTextNodeSetForReadAloud = null;
       this.nodeStore_.clearDomNodes();
     }
 
@@ -432,10 +427,7 @@
     // node id to call InitAXPosition in playSpeech. If it's not saved here,
     // we have to retrieve it through a DOM search such as createTreeWalker,
     // which can be computationally expensive.
-    if (!this.firstTextNodeSetForReadAloud) {
-      this.firstTextNodeSetForReadAloud = nodeId;
-      this.speechController_.initializeSpeechTree(nodeId);
-    }
+    this.speechController_.initializeSpeechTree(nodeId);
 
     const textContent = chrome.readingMode.getTextContent(nodeId);
     const textNode = document.createTextNode(textContent);
@@ -485,10 +477,6 @@
   // TODO: crbug.com/40927698 - Handle focus changes for speech, including
   // updating speech state.
   updateContent() {
-    // Each time we rebuild the subtree, we should clear the node id of the
-    // first text node.
-    this.firstTextNodeSetForReadAloud = null;
-
     // This shouldn't happen. If it does, there is likely a bug, so log it so
     // we can monitor it.
     if (this.speechController_.isSpeechActive()) {
@@ -786,16 +774,23 @@
   }
 
   protected onPlayPauseClick_() {
-    if (this.speechController_.isSpeechActive()) {
-      this.speechController_.stopSpeech(PauseActionSource.BUTTON_CLICK);
-    } else {
-      this.playSpeech();
-      this.speechController_.onPlay();
-    }
+    this.speechController_.onPlayPauseToggle(
+        this.getSelection(), this.$.container.textContent);
   }
 
   onIsSpeechActiveChange(): void {
     this.isSpeechActive_ = this.speechController_.isSpeechActive();
+    if (!chrome.readingMode.linksEnabled) {
+      return;
+    }
+
+    // Restore links if they're enabled when speech pauses via button click or
+    // when it finishes the page.
+    const pauseSource = this.speechController_.getPauseSource();
+    if ((pauseSource === PauseActionSource.BUTTON_CLICK) ||
+        pauseSource === PauseActionSource.SPEECH_FINISHED) {
+      this.updateLinks_();
+    }
   }
 
   onIsAudioCurrentlyPlayingChange(): void {
@@ -815,20 +810,6 @@
     this.resetSpeechPostSettingChange_();
   }
 
-  onStop() {
-    if (!chrome.readingMode.linksEnabled) {
-      return;
-    }
-
-    // Restore links if they're enabled when speech pauses via button click or
-    // when it finishes the page.
-    const pauseSource = this.speechController_.getPauseSource();
-    if ((pauseSource === PauseActionSource.BUTTON_CLICK) ||
-        pauseSource === PauseActionSource.SPEECH_FINISHED) {
-      this.updateLinks_();
-    }
-  }
-
   onEnabledLangsChange(): void {
     this.enabledLangs_ = this.voicePackController_.getEnabledLangs();
   }
@@ -854,83 +835,8 @@
 
   playSpeech() {
     const container = this.$.container;
-    const {anchorNode, anchorOffset, focusNode, focusOffset} =
-        this.getSelection();
-    const hasSelection =
-        anchorNode !== focusNode || anchorOffset !== focusOffset;
-    if (this.speechController_.hasSpeechBeenTriggered() &&
-        !this.speechController_.isSpeechActive()) {
-      const pausedFromButton = this.speechController_.isPausedFromButton();
-
-      let playedFromSelection = false;
-      if (hasSelection) {
-        this.speech_.cancel();
-        this.wordBoundaries_.resetToDefaultState();
-        playedFromSelection = this.playFromSelection();
-      }
-
-      if (!playedFromSelection) {
-        if (pausedFromButton && !this.wordBoundaries_.hasBoundaries()) {
-          // If word boundaries aren't supported for the given voice, we should
-          // still continue to use synth.resume, as this is preferable to
-          // restarting the current message.
-          this.speech_.resume();
-        } else {
-          this.speech_.cancel();
-          if (!this.speechController_.highlightAndPlayInterruptedMessage()) {
-            // Ensure we're updating Read Aloud state if there's no text to
-            // speak.
-            this.speechController_.onSpeechFinished();
-          }
-        }
-      }
-
-      this.speechController_.setIsSpeechActive(true);
-      this.speechController_.setIsSpeechBeingRepositioned(false);
-
-      // Hide links when speech resumes. We only hide links when the page was
-      // paused from the play/pause button.
-      if (chrome.readingMode.linksEnabled && pausedFromButton) {
-        // Toggle links and ensure that the new nodes are also highlighted.
-        this.updateLinks_(
-            /* shouldRehiglightCurrentNodes= */ !playedFromSelection);
-      }
-
-      // If the current read highlight has been cleared from a call to
-      // updateContent, such as via a preference change, rehighlight the nodes
-      // after a pause.
-      if (!playedFromSelection) {
-        this.speechController_.highlightCurrentGranularity(
-            chrome.readingMode.getCurrentText());
-      }
-
-      return;
-    }
-    if (container.textContent) {
-      // Log that we're playing speech on a new page, but not when resuming.
-      // This helps us compare how many reading mode pages are opened with
-      // speech played and without speech played. Counting resumes would
-      // inflate the speech played number.
-      this.logger_.logNewPage(/*speechPlayed=*/ true);
-      this.speechController_.setIsSpeechActive(true);
-      this.speechController_.setHasSpeechBeenTriggered(true);
-      this.speechController_.setIsSpeechBeingRepositioned(false);
-
-      // Hide links when speech begins playing.
-      if (chrome.readingMode.linksEnabled) {
-        this.updateLinks_();
-      }
-
-      const playedFromSelection = hasSelection && this.playFromSelection();
-      if (!playedFromSelection && this.firstTextNodeSetForReadAloud) {
-        this.speechController_.initializeSpeechTree(
-            this.firstTextNodeSetForReadAloud);
-        if (!this.speechController_.highlightAndPlayMessage()) {
-          // Ensure we're updating Read Aloud state if there's no text to speak.
-          this.speechController_.onSpeechFinished();
-        }
-      }
-    }
+    this.speechController_.playSpeech(
+        this.getSelection(), container.textContent);
   }
 
   private getSelectedIds(): {
@@ -961,45 +867,6 @@
     };
   }
 
-  playFromSelection(): boolean {
-    const selection = this.getSelection();
-    if (!this.firstTextNodeSetForReadAloud || !selection) {
-      return false;
-    }
-
-    const anchorNodeId = chrome.readingMode.startNodeId;
-    const anchorOffset = chrome.readingMode.startOffset;
-    const focusNodeId = chrome.readingMode.endNodeId;
-    const focusOffset = chrome.readingMode.endOffset;
-
-    // If only one of the ids is present, use that one.
-    let startingNodeId: number|undefined =
-        anchorNodeId ? anchorNodeId : focusNodeId;
-    let startingOffset = anchorNodeId ? anchorOffset : focusOffset;
-    // If both are present, start with the node that is sooner in the page.
-    if (anchorNodeId && focusNodeId) {
-      if (anchorNodeId === focusNodeId) {
-        startingOffset = Math.min(anchorOffset, focusOffset);
-      } else {
-        const pos =
-            selection.anchorNode.compareDocumentPosition(selection.focusNode);
-        const focusIsFirst = pos === Node.DOCUMENT_POSITION_PRECEDING;
-        startingNodeId = focusIsFirst ? focusNodeId : anchorNodeId;
-        startingOffset = focusIsFirst ? focusOffset : anchorOffset;
-      }
-    }
-
-    if (!startingNodeId) {
-      return false;
-    }
-
-    // Clear the selection so we don't keep trying to play from the same
-    // selection every time they press play.
-    selection.removeAllRanges();
-    this.speechController_.playFromSelection(startingNodeId, startingOffset);
-    return true;
-  }
-
   protected onSelectVoice_(
       event: CustomEvent<{selectedVoice: SpeechSynthesisVoice}>) {
     event.preventDefault();
diff --git a/chrome/browser/resources/side_panel/read_anything/read_aloud/speech_controller.ts b/chrome/browser/resources/side_panel/read_anything/read_aloud/speech_controller.ts
index 5e8a18f..ab656e71 100644
--- a/chrome/browser/resources/side_panel/read_anything/read_aloud/speech_controller.ts
+++ b/chrome/browser/resources/side_panel/read_anything/read_aloud/speech_controller.ts
@@ -22,7 +22,6 @@
 export const MAX_SPEECH_LENGTH: number = 175;
 
 export interface SpeechListener {
-  onStop(): void;
   onIsSpeechActiveChange(): void;
   onIsAudioCurrentlyPlayingChange(): void;
   onEngineStateChange(): void;
@@ -145,14 +144,19 @@
     this.listeners_.forEach(l => l.onIsAudioCurrentlyPlayingChange());
   }
 
-  initializeSpeechTree(startingNodeId: number|null) {
-    if (!startingNodeId || this.isSpeechTreeInitialized()) {
+  initializeSpeechTree(startingNodeId?: number) {
+    if (startingNodeId && !this.model_.getFirstTextNode()) {
+      this.model_.setFirstTextNode(startingNodeId);
+    }
+
+    const firstTextNode = this.model_.getFirstTextNode();
+    if (!firstTextNode || this.isSpeechTreeInitialized()) {
       return;
     }
 
     // TODO: crbug.com/40927698 - There should be a way to use AXPosition so
     // that this step can be skipped.
-    chrome.readingMode.initAxPositionWithNode(startingNodeId);
+    chrome.readingMode.initAxPositionWithNode(firstTextNode);
     this.model_.setIsSpeechTreeInitialized(true);
     chrome.readingMode.preprocessTextForSpeech();
   }
@@ -191,8 +195,21 @@
     this.logger_.logHighlightGranularity(newGranularity);
   }
 
-  onPlay() {
-    this.model_.setPlaySessionStartTime(Date.now());
+  onPlayPauseToggle(selection: Selection|null, textContent: string|null) {
+    if (this.isSpeechActive()) {
+      this.stopSpeech(PauseActionSource.BUTTON_CLICK);
+    } else {
+      this.playSpeech(selection, textContent);
+      this.model_.setPlaySessionStartTime(Date.now());
+    }
+  }
+
+  playSpeech(selection: Selection|null, textContent: string|null) {
+    if (this.hasSpeechBeenTriggered() && !this.isSpeechActive()) {
+      this.resumeSpeech_(selection);
+    } else {
+      this.playSpeechForTheFirstTime_(selection, textContent);
+    }
   }
 
   playNextGranularity() {
@@ -228,7 +245,108 @@
     }
   }
 
-  playFromSelection(startingNodeId: number, startingOffset: number) {
+  private resumeSpeech_(selection: Selection|null) {
+    let playedFromSelection = false;
+    if (this.hasSelection_(selection)) {
+      this.speech_.cancel();
+      this.wordBoundaries_.resetToDefaultState();
+      playedFromSelection = this.playFromSelection_(selection);
+    }
+
+    if (!playedFromSelection) {
+      if (this.isPausedFromButton() && !this.wordBoundaries_.hasBoundaries()) {
+        // If word boundaries aren't supported for the given voice, we should
+        // still continue to use synth.resume, as this is preferable to
+        // restarting the current message.
+        this.speech_.resume();
+      } else {
+        this.speech_.cancel();
+        if (!this.highlightAndPlayInterruptedMessage_()) {
+          // Ensure we're updating Read Aloud state if there's no text to
+          // speak.
+          this.onSpeechFinished();
+        }
+      }
+    }
+
+    this.setIsSpeechActive(true);
+    this.setIsSpeechBeingRepositioned(false);
+
+    // If the current read highlight has been cleared from a call to
+    // updateContent, such as via a preference change, rehighlight the nodes
+    // after a pause.
+    if (!playedFromSelection) {
+      this.highlightCurrentGranularity(chrome.readingMode.getCurrentText());
+    }
+  }
+
+  private playSpeechForTheFirstTime_(
+      selection: Selection|null, textContent: string|null) {
+    if (!textContent) {
+      return;
+    }
+
+    // Log that we're playing speech on a new page, but not when resuming.
+    // This helps us compare how many reading mode pages are opened with
+    // speech played and without speech played. Counting resumes would
+    // inflate the speech played number.
+    this.logger_.logNewPage(/*speechPlayed=*/ true);
+    this.setIsSpeechActive(true);
+    this.setHasSpeechBeenTriggered(true);
+    this.setIsSpeechBeingRepositioned(false);
+
+    const playedFromSelection = this.playFromSelection_(selection);
+    if (playedFromSelection) {
+      return;
+    }
+
+    this.initializeSpeechTree();
+    if (this.isSpeechTreeInitialized() && !this.highlightAndPlayMessage()) {
+      // Ensure we're updating Read Aloud state if there's no text to speak.
+      this.onSpeechFinished();
+    }
+  }
+
+  private hasSelection_(selection: Selection|null): boolean {
+    return !selection || selection.anchorNode !== selection.focusNode ||
+        selection.anchorOffset !== selection.focusOffset;
+  }
+
+  private playFromSelection_(selection: Selection|null): boolean {
+    if (!this.isSpeechTreeInitialized() || !selection ||
+        !this.hasSelection_(selection)) {
+      return false;
+    }
+
+    const anchorNodeId = chrome.readingMode.startNodeId;
+    const anchorOffset = chrome.readingMode.startOffset;
+    const focusNodeId = chrome.readingMode.endNodeId;
+    const focusOffset = chrome.readingMode.endOffset;
+
+    // If only one of the ids is present, use that one.
+    let startingNodeId: number|undefined =
+        anchorNodeId ? anchorNodeId : focusNodeId;
+    let startingOffset = anchorNodeId ? anchorOffset : focusOffset;
+    // If both are present, start with the node that is sooner in the page.
+    if (anchorNodeId && focusNodeId) {
+      if (anchorNodeId === focusNodeId) {
+        startingOffset = Math.min(anchorOffset, focusOffset);
+      } else if (selection.anchorNode && selection.focusNode) {
+        const pos =
+            selection.anchorNode.compareDocumentPosition(selection.focusNode);
+        const focusIsFirst = pos === Node.DOCUMENT_POSITION_PRECEDING;
+        startingNodeId = focusIsFirst ? focusNodeId : anchorNodeId;
+        startingOffset = focusIsFirst ? focusOffset : anchorOffset;
+      }
+    }
+
+    if (!startingNodeId) {
+      return false;
+    }
+
+    // Clear the selection so we don't keep trying to play from the same
+    // selection every time they press play.
+    selection.removeAllRanges();
     // Iterate through the page from the beginning until we get to the
     // selection. This is so clicking previous works before the selection and
     // so the previous highlights are properly set.
@@ -244,9 +362,10 @@
         this.onSpeechFinished();
       }
     }, playFromSelectionTimeout);
+    return true;
   }
 
-  highlightAndPlayInterruptedMessage(): boolean {
+  private highlightAndPlayInterruptedMessage_(): boolean {
     return this.highlightAndPlayMessage(/* isInterrupted = */ true);
   }
 
@@ -464,8 +583,6 @@
       // Canceling clears all the Utterances that are queued up via synth.play()
       this.speech_.cancel();
     }
-
-    this.listeners_.forEach(l => l.onStop());
   }
 
   setOnSpeechSynthesisUtteranceStart(message: SpeechSynthesisUtterance) {
@@ -582,7 +699,6 @@
   onSpeechFinished() {
     this.clearReadAloudState();
     this.model_.setPauseSource(PauseActionSource.SPEECH_FINISHED);
-    this.listeners_.forEach(l => l.onStop());
     this.logger_.logSpeechStopSource(
         chrome.readingMode.contentFinishedStopSource);
     this.logSpeechPlaySession_();
@@ -590,6 +706,7 @@
 
   clearReadAloudState() {
     this.reset();
+    this.model_.setFirstTextNode(null);
     this.highlighter_.clearHighlightFormatting();
     this.wordBoundaries_.resetToDefaultState();
   }
diff --git a/chrome/browser/resources/side_panel/read_anything/read_aloud/speech_model.ts b/chrome/browser/resources/side_panel/read_anything/read_aloud/speech_model.ts
index 59f0769..fccc43e3 100644
--- a/chrome/browser/resources/side_panel/read_anything/read_aloud/speech_model.ts
+++ b/chrome/browser/resources/side_panel/read_anything/read_aloud/speech_model.ts
@@ -71,6 +71,10 @@
   // Used for logging play time.
   private playSessionStartTime_: number|null = null;
 
+  // If the node id of the first text node that should be used by Read Aloud
+  // has been set. This is null if the id has not been set.
+  private firstTextNodeSetForReadAloud_: number|null = null;
+
   reset(): void {
     this.speechPlayingState_ = {
       isSpeechTreeInitialized: false,
@@ -84,6 +88,14 @@
     this.previewVoicePlaying_ = null;
   }
 
+  getFirstTextNode(): number|null {
+    return this.firstTextNodeSetForReadAloud_;
+  }
+
+  setFirstTextNode(node: number|null) {
+    this.firstTextNodeSetForReadAloud_ = node;
+  }
+
   getPlaySessionStartTime(): number|null {
     return this.playSessionStartTime_;
   }
diff --git a/chrome/browser/ui/android/edge_to_edge/internal/java/src/org/chromium/chrome/browser/ui/edge_to_edge/EdgeToEdgeControllerImpl.java b/chrome/browser/ui/android/edge_to_edge/internal/java/src/org/chromium/chrome/browser/ui/edge_to_edge/EdgeToEdgeControllerImpl.java
index d48fa99f..e30f578 100644
--- a/chrome/browser/ui/android/edge_to_edge/internal/java/src/org/chromium/chrome/browser/ui/edge_to_edge/EdgeToEdgeControllerImpl.java
+++ b/chrome/browser/ui/android/edge_to_edge/internal/java/src/org/chromium/chrome/browser/ui/edge_to_edge/EdgeToEdgeControllerImpl.java
@@ -59,6 +59,8 @@
                 LayoutStateProvider.LayoutStateObserver,
                 FullscreenManager.Observer {
     private static final String TAG = "E2E_ControllerImpl";
+    private static final String DRAW_TO_EDGE_UNSUPPORTED_CONFIG_HISTOGRAM =
+            "Android.EdgeToEdge.DrawToEdgeInUnsupportedConfiguration";
     private static final String SUPPORTED_CONFIGURATION_SWITCH_HISTOGRAM =
             "Android.EdgeToEdge.SupportedConfigurationSwitch";
 
@@ -414,6 +416,10 @@
      */
     @VisibleForTesting
     void drawToEdge(boolean pageOptedIntoEdgeToEdge, boolean changedWindowState) {
+        if (!EdgeToEdgeControllerFactory.isSupportedConfiguration(mActivity)) {
+            RecordHistogram.recordBooleanHistogram(
+                    DRAW_TO_EDGE_UNSUPPORTED_CONFIG_HISTOGRAM, changedWindowState);
+        }
         // Exit early if there is a tappable navbar (3-button) as the controller should not function
         // when 3-button nav is enabled.
         if (EdgeToEdgeUtils.hasTappableNavigationBar(mActivity.getWindow())) {
diff --git a/chrome/browser/ui/android/omnibox/BUILD.gn b/chrome/browser/ui/android/omnibox/BUILD.gn
index 54c9c58..245e534e 100644
--- a/chrome/browser/ui/android/omnibox/BUILD.gn
+++ b/chrome/browser/ui/android/omnibox/BUILD.gn
@@ -82,6 +82,7 @@
     "java/src/org/chromium/chrome/browser/omnibox/suggestions/PreWarmingRecycledViewPool.java",
     "java/src/org/chromium/chrome/browser/omnibox/suggestions/RecyclerViewSelectionController.java",
     "java/src/org/chromium/chrome/browser/omnibox/suggestions/SelectionController.java",
+    "java/src/org/chromium/chrome/browser/omnibox/suggestions/SimpleSelectionController.java",
     "java/src/org/chromium/chrome/browser/omnibox/suggestions/SuggestionCommonProperties.java",
     "java/src/org/chromium/chrome/browser/omnibox/suggestions/SuggestionHorizontalDivider.java",
     "java/src/org/chromium/chrome/browser/omnibox/suggestions/SuggestionHost.java",
@@ -426,7 +427,7 @@
     "java/src/org/chromium/chrome/browser/omnibox/suggestions/OmniboxSuggestionsDropdownUnitTest.java",
     "java/src/org/chromium/chrome/browser/omnibox/suggestions/PreWarmingRecycledViewPoolTest.java",
     "java/src/org/chromium/chrome/browser/omnibox/suggestions/RecyclerViewSelectionControllerUnitTest.java",
-    "java/src/org/chromium/chrome/browser/omnibox/suggestions/SelectionControllerUnitTest.java",
+    "java/src/org/chromium/chrome/browser/omnibox/suggestions/SimpleSelectionControllerUnitTest.java",
     "java/src/org/chromium/chrome/browser/omnibox/suggestions/SuggestionHorizontalDividerTest.java",
     "java/src/org/chromium/chrome/browser/omnibox/suggestions/SuggestionListViewBinderUnitTest.java",
     "java/src/org/chromium/chrome/browser/omnibox/suggestions/UnsyncedSuggestionsListAnimationDriverTest.java",
diff --git a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/SelectionController.java b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/SelectionController.java
index 6be37e2..3a4e769 100644
--- a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/SelectionController.java
+++ b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/SelectionController.java
@@ -16,20 +16,9 @@
 import java.lang.annotation.Target;
 import java.util.OptionalInt;
 
-/**
- * Helper class allowing advancing forward/backward while saturating outside the valid range.
- *
- * <p>TODO(344930378): Explore possibility to reconcile this with RecyclerViewSelectionController.
- * The two classes serve similar purpose, but the complexity of view recycling may make the merge
- * difficult. This controller expands RVSC capabilities, however the following aspects make
- * immediate merge difficult: - volume of items changing at runtime, - exposure triggering (to
- * ensure we can locate views for items not currently bound), - reused views propagate the selected
- * state when rebound to a different item,
- *
- * <p>Consider adding WRAPPING and WRAPPING_WITH_SENTINEL variants to allow cycling through.
- */
+/** Helper class allowing advancing forward/backward while saturating outside the valid range. */
 @NullMarked
-public class SelectionController {
+public abstract class SelectionController {
     /**
      * Operational modes of the SelectionController
      *
@@ -54,44 +43,17 @@
         int SATURATING_WITH_SENTINEL = 1;
     }
 
-    private final OnSelectionChangedListener mListener;
-    private final @Mode int mMode;
-    private final int mDefaultPosition;
-    private int mNumItems;
+    protected final @Mode int mMode;
+    protected final int mDefaultPosition;
+
     private int mPosition;
 
-    @FunctionalInterface
-    public interface OnSelectionChangedListener {
-        /**
-         * Invoked whenever selected state at specific position changed.
-         *
-         * @param position the position to apply selection change to
-         * @param isSelected whether that position should be selected
-         * @return whether selection was applied at requested position
-         */
-        boolean onSelectionChanged(int position, boolean isSelected);
-    }
-
     /**
      * SelectionController constructor.
      *
-     * @param listener the listener receiving notifications about selection changes
-     */
-    public SelectionController(OnSelectionChangedListener listener, @Mode int mode) {
-        this(listener, 0, mode);
-    }
-
-    /**
-     * SelectionController constructor.
-     *
-     * @param listener the listener receiving notifications about selection changes
-     * @param itemCount the number of valid positions [0; itemCount-1)
      * @param mode Selection mode that defines how the controller will behave
      */
-    public SelectionController(OnSelectionChangedListener listener, int itemCount, @Mode int mode) {
-        assert itemCount < Integer.MAX_VALUE;
-        assert itemCount >= 0;
-
+    public SelectionController(@Mode int mode) {
         switch (mode) {
             case Mode.SATURATING:
                 mDefaultPosition = 0;
@@ -103,29 +65,8 @@
                 break;
         }
 
-        // Initialization step only, to ensure we do not emit bogus selection change event.
         mPosition = Integer.MIN_VALUE;
-        mListener = listener;
         mMode = mode;
-        updateMaxPosition(itemCount);
-    }
-
-    /**
-     * Update range of valid positions.
-     *
-     * @param numItems total number of items, determining the upper position.
-     */
-    public void updateMaxPosition(int numItems) {
-        if (!isParkedAtSentinel()) {
-            mListener.onSelectionChanged(mPosition, false);
-        }
-
-        mNumItems = numItems;
-        mPosition = mDefaultPosition;
-
-        if (!isParkedAtSentinel()) {
-            mListener.onSelectionChanged(mPosition, true);
-        }
     }
 
     /** Resets the controller, making the current position point to default item. */
@@ -133,6 +74,9 @@
         setPosition(mDefaultPosition);
     }
 
+    /** Returns the maximum valid position the SelectionController can assume. */
+    protected abstract int getItemCount();
+
     /**
      * Advances the counter towards the maxPosition, returning false if the held value has
      * saturated.
@@ -153,7 +97,7 @@
      */
     public boolean advanceBack() {
         if (mPosition == Integer.MIN_VALUE) return false;
-        if (mPosition == Integer.MAX_VALUE) return setPosition(mNumItems - 1);
+        if (mPosition == Integer.MAX_VALUE) return setPosition(getItemCount());
         return setPosition(mPosition - 1);
     }
 
@@ -177,17 +121,18 @@
     @VisibleForTesting
     boolean setPosition(int newPosition) {
         if (!isParkedAtSentinel()) {
-            mListener.onSelectionChanged(mPosition, false);
+            setItemState(mPosition, false);
         }
 
         int oldPosition = mPosition;
+        int itemCount = getItemCount();
         mPosition = newPosition;
         switch (mMode) {
             case Mode.SATURATING:
-                if (mNumItems == 0) {
+                if (itemCount == 0) {
                     mPosition = Integer.MIN_VALUE;
                 } else {
-                    mPosition = MathUtils.clamp(mPosition, 0, mNumItems - 1);
+                    mPosition = MathUtils.clamp(mPosition, 0, itemCount - 1);
                 }
                 break;
 
@@ -195,7 +140,7 @@
                 // Park outside the valid range, keeping the information which edge we hit.
                 if (mPosition < 0) { // Underflow
                     mPosition = Integer.MIN_VALUE;
-                } else if (mPosition >= mNumItems) {
+                } else if (mPosition >= itemCount) {
                     mPosition = Integer.MAX_VALUE;
                 }
                 break;
@@ -204,13 +149,22 @@
         if (isParkedAtSentinel()) return false;
 
         // Select new item, fall back to old position if not possible.
-        if (!mListener.onSelectionChanged(mPosition, true)) {
+        if (!setItemState(mPosition, true)) {
             mPosition = oldPosition;
-            mListener.onSelectionChanged(mPosition, true);
+            setItemState(mPosition, true);
             // We failed to select the requested entry.
             return false;
         }
 
         return true;
     }
+
+    /**
+     * Applies selection change at specific position.
+     *
+     * @param position the index of an element to change the state of
+     * @param state the desired new state
+     * @return the applied state of the item at specified position.
+     */
+    protected abstract boolean setItemState(int position, boolean isSelected);
 }
diff --git a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/SimpleSelectionController.java b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/SimpleSelectionController.java
new file mode 100644
index 0000000..efd4f1d
--- /dev/null
+++ b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/SimpleSelectionController.java
@@ -0,0 +1,63 @@
+// Copyright 2025 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.chrome.browser.omnibox.suggestions;
+
+import org.chromium.build.annotations.NullMarked;
+
+/**
+ * Specialization of the SimpleSelectionController, that works with a fixed, known set of elements.
+ */
+@NullMarked
+public class SimpleSelectionController extends SelectionController {
+    private OnSelectionChangedListener mListener;
+    private int mItemCount;
+
+    @FunctionalInterface
+    public interface OnSelectionChangedListener {
+        /**
+         * Invoked whenever selected state at specific position changed.
+         *
+         * @param position the position to apply selection change to
+         * @param isSelected whether that position should be selected
+         * @return whether selection was applied at requested position
+         */
+        boolean onSelectionChanged(int position, boolean isSelected);
+    }
+
+    /**
+     * @param listener the listener receiving notifications about selection changes
+     * @param itemCount the number of items
+     * @param mode Selection mode that defines how the controller will behave
+     */
+    public SimpleSelectionController(
+            OnSelectionChangedListener listener, int itemCount, @Mode int mode) {
+        super(mode);
+
+        assert itemCount < Integer.MAX_VALUE;
+        assert itemCount >= 0;
+
+        mListener = listener;
+        mItemCount = itemCount;
+        reset();
+    }
+
+    /** Returns the number of items in the container controlled by the SelectionController. */
+    @Override
+    protected int getItemCount() {
+        return mItemCount;
+    }
+
+    /** Update the number of items SelectionController can choose from. */
+    public void setItemCount(int newItemCount) {
+        mItemCount = newItemCount;
+        // Reset position if out of range.
+        setPosition(getPosition().orElse(mDefaultPosition));
+    }
+
+    @Override
+    protected boolean setItemState(int position, boolean isSelected) {
+        return mListener.onSelectionChanged(position, isSelected);
+    }
+}
diff --git a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/SelectionControllerUnitTest.java b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/SimpleSelectionControllerUnitTest.java
similarity index 69%
rename from chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/SelectionControllerUnitTest.java
rename to chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/SimpleSelectionControllerUnitTest.java
index e5d0379..d30f68a 100644
--- a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/SelectionControllerUnitTest.java
+++ b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/SimpleSelectionControllerUnitTest.java
@@ -27,13 +27,13 @@
 
 import java.util.OptionalInt;
 
-/** Robolectric unit tests for {@link SelectionController}. */
+/** Robolectric unit tests for {@link SimpleSelectionController}. */
 @RunWith(BaseRobolectricTestRunner.class)
-public class SelectionControllerUnitTest {
+public class SimpleSelectionControllerUnitTest {
     private static final int MAX_POSITION = 3; // Items 0‒2 inclusive.
 
     public @Rule MockitoRule mMockitoRule = MockitoJUnit.rule();
-    private @Mock SelectionController.OnSelectionChangedListener mListener;
+    private @Mock SimpleSelectionController.OnSelectionChangedListener mListener;
 
     @Before
     public void setUp() {
@@ -42,7 +42,7 @@
         when(mListener.onSelectionChanged(anyInt(), eq(false))).thenReturn(true);
     }
 
-    private void verifyPositionReset(SelectionController c, int position) {
+    private void verifyPositionReset(SimpleSelectionController c, int position) {
         verify(mListener).onSelectionChanged(position, false);
         assertEquals(OptionalInt.empty(), c.getPosition());
         assertTrue(c.isParkedAtSentinel());
@@ -63,7 +63,8 @@
 
     @Test
     public void advanceForward_saturating() {
-        SelectionController c = new SelectionController(mListener, MAX_POSITION, Mode.SATURATING);
+        SimpleSelectionController c =
+                new SimpleSelectionController(mListener, MAX_POSITION, Mode.SATURATING);
         verifyPositionSet(c, 0);
 
         assertTrue(c.advanceForward());
@@ -81,8 +82,9 @@
 
     @Test
     public void advanceForward_saturatingWithSentinel() {
-        SelectionController c =
-                new SelectionController(mListener, MAX_POSITION, Mode.SATURATING_WITH_SENTINEL);
+        SimpleSelectionController c =
+                new SimpleSelectionController(
+                        mListener, MAX_POSITION, Mode.SATURATING_WITH_SENTINEL);
         assertTrue(c.isParkedAtSentinel());
 
         assertTrue(c.advanceForward());
@@ -103,7 +105,8 @@
 
     @Test
     public void advanceBack_saturating() {
-        SelectionController c = new SelectionController(mListener, MAX_POSITION, Mode.SATURATING);
+        SimpleSelectionController c =
+                new SimpleSelectionController(mListener, MAX_POSITION, Mode.SATURATING);
         c.setPosition(MAX_POSITION);
         verifyPositionChanged(c, 0, 2);
 
@@ -122,8 +125,9 @@
 
     @Test
     public void advanceBack_saturatingWithSentinel() {
-        SelectionController c =
-                new SelectionController(mListener, MAX_POSITION, Mode.SATURATING_WITH_SENTINEL);
+        SimpleSelectionController c =
+                new SimpleSelectionController(
+                        mListener, MAX_POSITION, Mode.SATURATING_WITH_SENTINEL);
         c.setPosition(MAX_POSITION - 1);
         verifyPositionSet(c, 2);
 
@@ -144,21 +148,21 @@
     public void advanceForward_saturating_listenerReturnsFalse() {
         when(mListener.onSelectionChanged(1, true)).thenReturn(false);
 
-        SelectionController c =
-                new SelectionController(
-                        mListener, MAX_POSITION, SelectionController.Mode.SATURATING);
+        SimpleSelectionController c =
+                new SimpleSelectionController(mListener, MAX_POSITION, Mode.SATURATING);
         verifyPositionSet(c, 0);
         assertFalse(c.advanceForward());
         verifyPositionChanged(c, 0, 0);
     }
 
     @Test
-    public void updateMaxPosition() {
-        SelectionController c = new SelectionController(mListener, MAX_POSITION, Mode.SATURATING);
+    public void setItemCount() {
+        SimpleSelectionController c =
+                new SimpleSelectionController(mListener, MAX_POSITION, Mode.SATURATING);
         verifyPositionSet(c, 0);
 
         // Grow list of items
-        c.updateMaxPosition(5);
+        c.setItemCount(5);
         verifyPositionSet(c, 0);
 
         assertTrue(c.advanceForward()); // Should now reach index 4 without saturating
@@ -171,13 +175,33 @@
         verifyPositionChanged(c, 3, 4);
 
         // Shrink list of items
-        c.updateMaxPosition(2);
-        verifyPositionSet(c, 0);
+        c.setItemCount(2);
+        verifyPositionSet(c, 1);
+    }
+
+    @Test
+    public void selectionControllerWithNoItems() {
+        SimpleSelectionController c = new SimpleSelectionController(mListener, 0, Mode.SATURATING);
+        // Normally, saturating controller should start at valid range, but this is an edge case.
+        assertTrue(c.isParkedAtSentinel());
+        assertEquals(OptionalInt.empty(), c.getPosition());
+
+        // Simulate we now have an item. This should make the saturating controller immediately jump
+        // to the first valid item.
+        c.setItemCount(1);
+        assertFalse(c.isParkedAtSentinel());
+        assertEquals(OptionalInt.of(0), c.getPosition());
+
+        // Simulate we lost all items. This should make the saturating controller revert to sentnel.
+        c.setItemCount(0);
+        assertTrue(c.isParkedAtSentinel());
+        assertEquals(OptionalInt.empty(), c.getPosition());
     }
 
     @Test
     public void reset_saturating() {
-        SelectionController c = new SelectionController(mListener, MAX_POSITION, Mode.SATURATING);
+        SimpleSelectionController c =
+                new SimpleSelectionController(mListener, MAX_POSITION, Mode.SATURATING);
         verifyPositionSet(c, 0);
 
         c.advanceForward(); // 1
diff --git a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/base/BaseSuggestionView.java b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/base/BaseSuggestionView.java
index 832273a..50f6167 100644
--- a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/base/BaseSuggestionView.java
+++ b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/base/BaseSuggestionView.java
@@ -20,7 +20,7 @@
 import org.chromium.build.annotations.NullMarked;
 import org.chromium.build.annotations.Nullable;
 import org.chromium.chrome.browser.omnibox.R;
-import org.chromium.chrome.browser.omnibox.suggestions.SelectionController;
+import org.chromium.chrome.browser.omnibox.suggestions.SimpleSelectionController;
 import org.chromium.chrome.browser.util.KeyNavigationUtil;
 import org.chromium.components.browser_ui.widget.RoundedCornerOutlineProvider;
 
@@ -42,7 +42,7 @@
     public final ActionChipsView actionChipsView;
     public final RoundedCornerOutlineProvider decorationIconOutline;
     private final List<ImageView> mActionButtons;
-    private final SelectionController mActionButtonsHighlighter;
+    private final SimpleSelectionController mActionButtonsHighlighter;
     private Optional<Runnable> mOnFocusViaSelectionListener = Optional.empty();
 
     /**
@@ -87,10 +87,10 @@
         addView(contentView);
 
         mActionButtonsHighlighter =
-                new SelectionController(
+                new SimpleSelectionController(
                         this::highlightActionButton,
                         0,
-                        SelectionController.Mode.SATURATING_WITH_SENTINEL);
+                        SimpleSelectionController.Mode.SATURATING_WITH_SENTINEL);
     }
 
     /**
@@ -107,7 +107,7 @@
             decreaseActionButtonsCount(desiredViewCount);
         }
 
-        mActionButtonsHighlighter.updateMaxPosition(desiredViewCount);
+        mActionButtonsHighlighter.setItemCount(desiredViewCount);
     }
 
     /**
diff --git a/chrome/browser/ui/autofill/payments/chrome_payments_autofill_client.cc b/chrome/browser/ui/autofill/payments/chrome_payments_autofill_client.cc
index 941ff22..f521b90 100644
--- a/chrome/browser/ui/autofill/payments/chrome_payments_autofill_client.cc
+++ b/chrome/browser/ui/autofill/payments/chrome_payments_autofill_client.cc
@@ -47,6 +47,7 @@
 #include "components/autofill/core/browser/payments/otp_unmask_result.h"
 #include "components/autofill/core/browser/payments/payments_autofill_client.h"
 #include "components/autofill/core/browser/payments/payments_network_interface.h"
+#include "components/autofill/core/browser/payments/save_and_fill_manager.h"
 #include "components/autofill/core/browser/payments/virtual_card_enrollment_manager.h"
 #include "components/autofill/core/browser/single_field_fillers/payments/merchant_promo_code_manager.h"
 #include "components/autofill/core/browser/suggestions/suggestion.h"
@@ -964,6 +965,18 @@
 #endif  // !BUILDFLAG(IS_ANDROID)
 }
 
+SaveAndFillManager* ChromePaymentsAutofillClient::GetSaveAndFillManager() {
+#if BUILDFLAG(IS_ANDROID)
+  return nullptr;
+#else
+  if (!save_and_fill_manager_) {
+    save_and_fill_manager_ =
+        std::make_unique<payments::SaveAndFillManager>(this);
+  }
+  return save_and_fill_manager_.get();
+#endif  // BUILDFLAG(IS_ANDROID)
+}
+
 void ChromePaymentsAutofillClient::ShowSelectBnplIssuerDialog(
     std::vector<BnplIssuerContext> bnpl_issuer_context,
     std::string app_locale,
diff --git a/chrome/browser/ui/autofill/payments/chrome_payments_autofill_client.h b/chrome/browser/ui/autofill/payments/chrome_payments_autofill_client.h
index fcc7ec3..97289113 100644
--- a/chrome/browser/ui/autofill/payments/chrome_payments_autofill_client.h
+++ b/chrome/browser/ui/autofill/payments/chrome_payments_autofill_client.h
@@ -64,6 +64,7 @@
 enum class OtpUnmaskResult;
 class PaymentsDataManager;
 class SaveAndFillDialogControllerImpl;
+class SaveAndFillManager;
 class TouchToFillDelegate;
 struct VirtualCardEnrollmentFields;
 class VirtualCardEnrollmentManager;
@@ -209,6 +210,7 @@
       override;
   PaymentsDataManager& GetPaymentsDataManager() final;
   void ShowCreditCardSaveAndFillDialog() override;
+  payments::SaveAndFillManager* GetSaveAndFillManager() override;
   void ShowSelectBnplIssuerDialog(
       std::vector<BnplIssuerContext> bnpl_issuer_context,
       std::string app_locale,
@@ -331,6 +333,8 @@
   std::unique_ptr<SaveAndFillDialogControllerImpl>
       save_and_fill_dialog_controller_;
 
+  std::unique_ptr<SaveAndFillManager> save_and_fill_manager_;
+
   std::unique_ptr<SelectBnplIssuerDialogControllerImpl>
       select_bnpl_issuer_dialog_controller_;
 
diff --git a/chrome/browser/ui/lens/lens_overlay_controller.cc b/chrome/browser/ui/lens/lens_overlay_controller.cc
index 3090b8af..7a37d26 100644
--- a/chrome/browser/ui/lens/lens_overlay_controller.cc
+++ b/chrome/browser/ui/lens/lens_overlay_controller.cc
@@ -691,6 +691,10 @@
 }
 
 void LensOverlayController::CopyImage(lens::mojom::CenterRotatedBoxPtr region) {
+  if (initialization_data_->initial_screenshot_.drawsNothing()) {
+    return;
+  }
+
   SkBitmap cropped = lens::CropBitmapToRegion(
       initialization_data_->initial_screenshot_, std::move(region));
   ui::ScopedClipboardWriter clipboard_writer(ui::ClipboardBuffer::kCopyPaste);
diff --git a/chrome/browser/ui/views/frame/browser_view.cc b/chrome/browser/ui/views/frame/browser_view.cc
index 3e97bf7c..0597ca4b 100644
--- a/chrome/browser/ui/views/frame/browser_view.cc
+++ b/chrome/browser/ui/views/frame/browser_view.cc
@@ -4346,12 +4346,15 @@
     switch (tab_data.collaboration_messaging->collaboration_event()) {
       case collaboration::messaging::CollaborationEvent::TAB_ADDED:
         title = l10n_util::GetStringFUTF16(
-            IDS_DATA_SHARING_RECENT_ACTIVITY_MEMBER_ADDED_THIS_TAB, given_name);
+                    IDS_DATA_SHARING_RECENT_ACTIVITY_MEMBER_ADDED_THIS_TAB,
+                    given_name) +
+                u", " + title;
         break;
       case collaboration::messaging::CollaborationEvent::TAB_UPDATED:
         title = l10n_util::GetStringFUTF16(
-            IDS_DATA_SHARING_RECENT_ACTIVITY_MEMBER_CHANGED_THIS_TAB,
-            given_name);
+                    IDS_DATA_SHARING_RECENT_ACTIVITY_MEMBER_CHANGED_THIS_TAB,
+                    given_name) +
+                u", " + title;
         break;
       default:
         NOTREACHED();
diff --git a/chrome/browser/ui/views/privacy_sandbox/privacy_sandbox_dialog_view_browsertest.cc b/chrome/browser/ui/views/privacy_sandbox/privacy_sandbox_dialog_view_browsertest.cc
index efa1d97..969da7d 100644
--- a/chrome/browser/ui/views/privacy_sandbox/privacy_sandbox_dialog_view_browsertest.cc
+++ b/chrome/browser/ui/views/privacy_sandbox/privacy_sandbox_dialog_view_browsertest.cc
@@ -36,8 +36,6 @@
 constexpr int kAverageBrowserHeight = 700;
 constexpr base::TimeDelta kMaxWaitTime = base::Seconds(30);
 
-// TODO(crbug.com/371041180): Refactor tests to remove these scripts and notify
-// from the notice components instead.
 std::string ScrollToBottomScript() {
   return R"```(
     (async () => {
@@ -80,35 +78,6 @@
   )```";
 }
 
-std::string WaitForPrivacyPolicyPageLoadScript() {
-  return R"(
-    (async () => {
-      return new Promise(async (resolve) => {
-        requestAnimationFrame(async () => {
-          dialogElement = document.querySelector("body > "+$1);
-          if($2 !== "") dialogElement = dialogElement.shadowRoot.querySelector($2);
-          waitForPrivacyPolicyResolve = (el) => new Promise(privacyPolicyResolve => {
-            let timeout = setTimeout(() => {
-              el.removeEventListener('privacy-policy-loaded', privacyPolicyLoadCallback);
-              privacyPolicyResolve();
-            }, 2000);
-            const privacyPolicyLoadCallback = () => {
-              clearTimeout(timeout);
-              el.removeEventListener('privacy-policy-loaded', privacyPolicyLoadCallback);
-              privacyPolicyResolve();
-            };
-            el.addEventListener('privacy-policy-loaded', privacyPolicyLoadCallback);
-          });
-          const privacyPolicyEl = dialogElement.shadowRoot.querySelector('privacy-sandbox-privacy-policy-dialog');
-          privacyPolicyEl.shadowRoot.querySelector('#privacyPolicy').src = "about:blank";
-          await waitForPrivacyPolicyResolve(privacyPolicyEl);
-          setTimeout(resolve,0);
-        });
-      });
-    })();
-  )";
-}
-
 std::string ClickLearnMoreButton3TimesScript() {
   return R"(
     (async () => {
@@ -154,6 +123,10 @@
   }
 
   void SetUpOnMainThread() override {
+    host_resolver()->AddRule("*", "127.0.0.1");
+    embedded_test_server()->ServeFilesFromSourceDirectory("content/test/data");
+
+    ASSERT_TRUE(embedded_test_server()->Start());
     mock_service_ = static_cast<MockPrivacySandboxService*>(
         PrivacySandboxServiceFactory::GetInstance()->SetTestingFactoryAndUse(
             browser()->profile(),
@@ -345,8 +318,7 @@
                   privacy_sandbox_dialog_view->GetWebContentsForTesting()),
               nullptr);
 
-    // Privacy policy is initially not visible, click expand button and trigger
-    // visibility.
+    // Click expand button.
     EXPECT_TRUE(
         content::ExecJs(privacy_sandbox_dialog_view->GetWebContentsForTesting(),
                         "document.querySelector('body > "
@@ -355,11 +327,6 @@
                         "privacy-sandbox-dialog-learn-more').shadowRoot."
                         "querySelector('div > cr-expand-button').click()"));
 
-    EXPECT_TRUE(content::ExecJs(
-        privacy_sandbox_dialog_view->GetWebContentsForTesting(),
-        content::JsReplace(WaitForPrivacyPolicyPageLoadScript(),
-                           "privacy-sandbox-combined-dialog-app", "#consent")));
-
     // Click Privacy Policy link.
     EXPECT_TRUE(
         content::ExecJs(privacy_sandbox_dialog_view->GetWebContentsForTesting(),
@@ -370,6 +337,17 @@
                             GetPrivacyPolicyLinkElementId() +
                             "')"
                             ".click()"));
+
+    // Intentionally navigate to some blocked content to avoid flakiness.
+    auto script = content::JsReplace(
+        "document.querySelector('body > "
+        "privacy-sandbox-combined-dialog-app')"
+        ".shadowRoot.querySelector('#consent')"
+        ".shadowRoot.querySelector('privacy-sandbox-privacy-policy-dialog')"
+        ".shadowRoot.querySelector('#privacyPolicy').src = $1;",
+        embedded_test_server()->GetURL("/blue.html"));
+    EXPECT_TRUE(content::ExecJs(
+        privacy_sandbox_dialog_view->GetWebContentsForTesting(), script));
   }
 
  private:
@@ -378,7 +356,7 @@
 
 // TODO(https://crbug.com/415305952): High failure rate.
 IN_PROC_BROWSER_TEST_F(PrivacySandboxDialogViewPrivacyPolicyBrowserTest,
-                       InvokeUi_PrivacyPolicy) {
+                       DISABLED_InvokeUi_PrivacyPolicy) {
   ShowAndVerifyUi();
 }
 
@@ -404,7 +382,7 @@
 // TODO(https://crbug.com/415305952): High failure rate.
 IN_PROC_BROWSER_TEST_F(
     PrivacySandboxDialogViewAdsApiUxEnhancementPrivacyPolicyBrowserTest,
-    InvokeUi_PrivacyPolicy) {
+    DISABLED_InvokeUi_PrivacyPolicy) {
   ShowAndVerifyUi();
 }
 
diff --git a/chrome/browser/ui/views/storage/storage_pressure_bubble_view.cc b/chrome/browser/ui/views/storage/storage_pressure_bubble_view.cc
index d88143d..6b9031a 100644
--- a/chrome/browser/ui/views/storage/storage_pressure_bubble_view.cc
+++ b/chrome/browser/ui/views/storage/storage_pressure_bubble_view.cc
@@ -4,6 +4,7 @@
 
 #include "chrome/browser/ui/views/storage/storage_pressure_bubble_view.h"
 
+#include "base/auto_reset.h"
 #include "base/feature_list.h"
 #include "base/metrics/histogram_functions.h"
 #include "base/metrics/histogram_macros.h"
@@ -80,18 +81,27 @@
 }
 
 StoragePressureBubbleView::~StoragePressureBubbleView() {
+  CHECK(!in_accept_);
   if (ignored_) {
     RecordBubbleHistogramValue(StoragePressureBubbleHistogramValue::kIgnored);
   }
 }
 
 void StoragePressureBubbleView::OnDialogAccepted() {
+  base::AutoReset reset_in_accept(&in_accept_, true);
+  auto weak_this = weak_ptr_factory_.GetWeakPtr();
+
   ignored_ = false;
   RecordBubbleHistogramValue(
       StoragePressureBubbleHistogramValue::kOpenedAllSites);
   // TODO(ellyjones): What is this doing here? The widget's about to close
   // anyway?
   GetWidget()->Close();
+
+  CHECK(weak_this);
+  CHECK(browser_);
+  CHECK(browser_->profile());
+
   const GURL all_sites_gurl(kAllSitesContentSettingsUrl);
   NavigateParams params(browser_, all_sites_gurl,
                         ui::PAGE_TRANSITION_AUTO_TOPLEVEL);
diff --git a/chrome/browser/ui/views/storage/storage_pressure_bubble_view.h b/chrome/browser/ui/views/storage/storage_pressure_bubble_view.h
index d439446..d17764c 100644
--- a/chrome/browser/ui/views/storage/storage_pressure_bubble_view.h
+++ b/chrome/browser/ui/views/storage/storage_pressure_bubble_view.h
@@ -6,6 +6,7 @@
 #define CHROME_BROWSER_UI_VIEWS_STORAGE_STORAGE_PRESSURE_BUBBLE_VIEW_H_
 
 #include "base/memory/raw_ptr.h"
+#include "base/memory/weak_ptr.h"
 #include "ui/base/metadata/metadata_header_macros.h"
 #include "ui/views/bubble/bubble_dialog_delegate_view.h"
 #include "url/origin.h"
@@ -35,6 +36,12 @@
   // Whether or not the user opened the all sites page from the notification
   // positive button.
   bool ignored_;
+
+  // TODO(https://crbug.com/372479681): Remove these two members and all uses of
+  // them. They are here for debugging a crash we can't reproduce under
+  // controlled conditions.
+  bool in_accept_ = false;
+  base::WeakPtrFactory<StoragePressureBubbleView> weak_ptr_factory_{this};
 };
 
 #endif  // CHROME_BROWSER_UI_VIEWS_STORAGE_STORAGE_PRESSURE_BUBBLE_VIEW_H_
diff --git a/chrome/browser/ui/views/tabs/alert_indicator_button.h b/chrome/browser/ui/views/tabs/alert_indicator_button.h
index 7cae9b22..141497c 100644
--- a/chrome/browser/ui/views/tabs/alert_indicator_button.h
+++ b/chrome/browser/ui/views/tabs/alert_indicator_button.h
@@ -88,7 +88,7 @@
   gfx::ImageSkia GetImageToPaint() override;
 
  private:
-  friend class AlertIndicatorButtonTest;
+  friend class TabContentsTest;
   friend class TabTest;
   class FadeAnimationDelegate;
 
diff --git a/chrome/browser/ui/views/tabs/tab.h b/chrome/browser/ui/views/tabs/tab.h
index 07d63054..6894d79 100644
--- a/chrome/browser/ui/views/tabs/tab.h
+++ b/chrome/browser/ui/views/tabs/tab.h
@@ -203,7 +203,7 @@
 
  private:
   class TabCloseButtonObserver;
-  friend class AlertIndicatorButtonTest;
+  friend class TabContentsTest;
   friend class TabTest;
   friend class TabStripTestBase;
 #if BUILDFLAG(IS_CHROMEOS)
@@ -213,8 +213,8 @@
   FRIEND_TEST_ALL_PREFIXES(TabTest, TitleTextHasSufficientContrast);
   FRIEND_TEST_ALL_PREFIXES(TabHoverCardInteractiveUiTest,
                            HoverCardVisibleOnTabCloseButtonFocusAfterTabFocus);
-  FRIEND_TEST_ALL_PREFIXES(AlertIndicatorButtonTest, AccessibleNameChanged);
-  FRIEND_TEST_ALL_PREFIXES(AlertIndicatorButtonTest,
+  FRIEND_TEST_ALL_PREFIXES(TabContentsTest, AccessibleNameChanged);
+  FRIEND_TEST_ALL_PREFIXES(TabContentsTest,
                            AccessibleNameChangesWithCollaborationMessages);
 
   bool ShouldUpdateAccessibleName(TabRendererData& old_data,
diff --git a/chrome/browser/ui/views/tabs/tab_unittest.cc b/chrome/browser/ui/views/tabs/tab_unittest.cc
index 91a4753..bb8ca23a 100644
--- a/chrome/browser/ui/views/tabs/tab_unittest.cc
+++ b/chrome/browser/ui/views/tabs/tab_unittest.cc
@@ -262,12 +262,12 @@
   base::SimpleTestTickClock fake_clock_;
 };
 
-class AlertIndicatorButtonTest : public ChromeViewsTestBase {
+class TabContentsTest : public ChromeViewsTestBase {
  public:
-  AlertIndicatorButtonTest() = default;
-  AlertIndicatorButtonTest(const AlertIndicatorButtonTest&) = delete;
-  AlertIndicatorButtonTest& operator=(const AlertIndicatorButtonTest&) = delete;
-  ~AlertIndicatorButtonTest() override = default;
+  TabContentsTest() = default;
+  TabContentsTest(const TabContentsTest&) = delete;
+  TabContentsTest& operator=(const TabContentsTest&) = delete;
+  ~TabContentsTest() override = default;
 
   void SetUp() override {
     ChromeViewsTestBase::SetUp();
@@ -768,7 +768,7 @@
 
 // This test verifies that the tab has its icon state updated when the alert
 // animation fade-out finishes.
-TEST_F(AlertIndicatorButtonTest, ShowsAndHidesAlertIndicator) {
+TEST_F(TabContentsTest, ShowsAndHidesAlertIndicator) {
   controller_->AddTab(0, TabActive::kInactive, TabPinned::kPinned);
   controller_->AddTab(1, TabActive::kActive);
   Tab* media_tab = tab_strip_->tab_at(0);
@@ -809,7 +809,7 @@
 
 // This test verifies that the alert indicator for a camera and/or mic is
 // visible at least for 5 seconds even if a camera/mic stopped being used.
-TEST_F(AlertIndicatorButtonTest, MinHoldDurationTest) {
+TEST_F(TabContentsTest, MinHoldDurationTest) {
   base::test::ScopedFeatureList scoped_feature_list_;
 
   controller_->AddTab(0, TabActive::kActive);
@@ -840,7 +840,7 @@
 
 // This test verifies that the alert indicator for a camera and/or mic has
 // 1-second fadeout animation after it was visible for longer than 5 seconds.
-TEST_F(AlertIndicatorButtonTest, 1SecondFadeoutAnimationTest) {
+TEST_F(TabContentsTest, 1SecondFadeoutAnimationTest) {
   base::test::ScopedFeatureList scoped_feature_list_;
 
   controller_->AddTab(0, TabActive::kActive);
@@ -908,7 +908,7 @@
   EXPECT_EQ(ax::mojom::Role::kTab, data.role);
 }
 
-TEST_F(AlertIndicatorButtonTest, AccessibleNameChanged) {
+TEST_F(TabContentsTest, AccessibleNameChanged) {
   controller_->AddTab(0, TabActive::kInactive, TabPinned::kPinned);
 
   TabRendererData old_data = tab_strip_->tab_at(0)->data();
@@ -925,8 +925,7 @@
       tab_strip_->tab_at(0)->ShouldUpdateAccessibleName(old_data, new_data));
 }
 
-TEST_F(AlertIndicatorButtonTest,
-       AccessibleNameChangesWithCollaborationMessages) {
+TEST_F(TabContentsTest, AccessibleNameChangesWithCollaborationMessages) {
   TestingProfile profile;
 
   controller_->AddTab(0, TabActive::kInactive, TabPinned::kPinned);
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 e213829..2dc647f 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
@@ -171,8 +171,10 @@
     LoadModelCallback callback,
     ml::ModelPerformanceHint performance_hint,
     on_device_model::ModelAssets assets) {
+  on_device_model::ModelFile weights = assets.weights;
+
   auto params = on_device_model::mojom::LoadModelParams::New();
-  params->assets = assets;
+  params->assets = std::move(assets);
   params->backend_type =
       optimization_guide::features::ForceCpuBackendForOnDeviceModel()
           ? ml::ModelBackendType::kCpuBackend
@@ -183,19 +185,19 @@
       std::move(params), std::move(model),
       base::BindOnce(&PageHandler::OnModelLoaded,
                      weak_ptr_factory_.GetWeakPtr(), std::move(callback),
-                     std::move(assets)));
+                     std::move(weights)));
 }
 
 void PageHandler::OnModelLoaded(
     LoadModelCallback callback,
-    on_device_model::ModelAssets assets,
+    on_device_model::ModelFile weights,
     on_device_model::mojom::LoadModelResult result) {
   if (result != on_device_model::mojom::LoadModelResult::kSuccess) {
     std::move(callback).Run(result, on_device_model::Capabilities());
     return;
   }
   GetService().GetCapabilities(
-      std::move(assets),
+      std::move(weights),
       base::BindOnce(std::move(callback),
                      on_device_model::mojom::LoadModelResult::kSuccess));
 }
diff --git a/chrome/browser/ui/webui/on_device_internals/on_device_internals_page_handler.h b/chrome/browser/ui/webui/on_device_internals/on_device_internals_page_handler.h
index b2650de..de91fa0 100644
--- a/chrome/browser/ui/webui/on_device_internals/on_device_internals_page_handler.h
+++ b/chrome/browser/ui/webui/on_device_internals/on_device_internals_page_handler.h
@@ -7,7 +7,8 @@
 
 #include "chrome/browser/ui/webui/on_device_internals/on_device_internals_page.mojom.h"
 #include "components/optimization_guide/core/optimization_guide_logger.h"
-#include "mojo/public/cpp/bindings/receiver_set.h"
+#include "mojo/public/cpp/bindings/pending_receiver.h"
+#include "mojo/public/cpp/bindings/receiver.h"
 #include "mojo/public/cpp/bindings/remote.h"
 #include "services/on_device_model/public/cpp/buildflags.h"
 #include "services/on_device_model/public/cpp/model_assets.h"
@@ -46,7 +47,7 @@
       ml::ModelPerformanceHint performance_hint,
       on_device_model::ModelAssets assets);
   void OnModelLoaded(LoadModelCallback callback,
-                     on_device_model::ModelAssets assets,
+                     on_device_model::ModelFile weights,
                      on_device_model::mojom::LoadModelResult result);
 #endif
 
diff --git a/chrome/browser/visited_url_ranking/group_suggestions_service_factory_browsertest.cc b/chrome/browser/visited_url_ranking/group_suggestions_service_factory_browsertest.cc
index 22d5ccf4..88dd42e 100644
--- a/chrome/browser/visited_url_ranking/group_suggestions_service_factory_browsertest.cc
+++ b/chrome/browser/visited_url_ranking/group_suggestions_service_factory_browsertest.cc
@@ -103,7 +103,7 @@
   base::test::ScopedFeatureList feature_list_;
 };
 
-IN_PROC_BROWSER_TEST_F(GroupSuggestionsServiceFactoryTest, SuggestionsShown) {
+IN_PROC_BROWSER_TEST_F(GroupSuggestionsServiceFactoryTest, DISABLED_SuggestionsShown) {
   TestGroupSuggestionsDelegate delegate;
   GetService()->RegisterDelegate(&delegate, GroupSuggestionsService::Scope());
   GetService()->SetConfigForTesting(base::TimeDelta());
diff --git a/chrome/browser/win/jumplist.h b/chrome/browser/win/jumplist.h
index fce59ad1..76b753c9 100644
--- a/chrome/browser/win/jumplist.h
+++ b/chrome/browser/win/jumplist.h
@@ -22,6 +22,7 @@
 #include "base/timer/timer.h"
 #include "chrome/browser/prefs/incognito_mode_prefs.h"
 #include "chrome/browser/win/jumplist_updater.h"
+#include "components/favicon_base/favicon_types.h"
 #include "components/history/core/browser/history_types.h"
 #include "components/history/core/browser/top_sites_observer.h"
 #include "components/keyed_service/core/keyed_service.h"
diff --git a/chrome/build/android-arm32.pgo.txt b/chrome/build/android-arm32.pgo.txt
index f5df6dd..d3789d2 100644
--- a/chrome/build/android-arm32.pgo.txt
+++ b/chrome/build/android-arm32.pgo.txt
@@ -1 +1 @@
-chrome-android32-main-1746813443-861fd45026df9940ca2609c2cd6eef7ab45dc762-4cac03e32f70f02cbf11d96b5da54e629616b531.profdata
+chrome-android32-main-1746853952-1bd319a1e9a5519b40c220be6ac3c6775850864a-443628585681dafc55c69a328e9f421a1c78cd58.profdata
diff --git a/chrome/build/android-arm64.pgo.txt b/chrome/build/android-arm64.pgo.txt
index 5ad0293..3905338 100644
--- a/chrome/build/android-arm64.pgo.txt
+++ b/chrome/build/android-arm64.pgo.txt
@@ -1 +1 @@
-chrome-android64-main-1746811595-150b94fdbfd212c2b14f2e8e65a20de7e3e095cd-7fe9a0e968b77a4cd55eecccb9e306b6cf4cce03.profdata
+chrome-android64-main-1746856966-bbfd9ead94ad300041664043c74a8d0fd7003e09-fd3c9752f640f74e4e050f351a86d1d24a99d465.profdata
diff --git a/chrome/build/mac-arm.pgo.txt b/chrome/build/mac-arm.pgo.txt
index d92efeec..f20f5a9 100644
--- a/chrome/build/mac-arm.pgo.txt
+++ b/chrome/build/mac-arm.pgo.txt
@@ -1 +1 @@
-chrome-mac-arm-main-1746820698-d8dfff23f0e482dea11e74b214574a058d174d1b-b1f57359eeafa0a25b962e9e274fde4973a17d5e.profdata
+chrome-mac-arm-main-1746853952-316cdb8201abcfb50caace1a04955bb0ec2f657f-443628585681dafc55c69a328e9f421a1c78cd58.profdata
diff --git a/chrome/build/win-arm64.pgo.txt b/chrome/build/win-arm64.pgo.txt
index f7214d584..08d1e0cf 100644
--- a/chrome/build/win-arm64.pgo.txt
+++ b/chrome/build/win-arm64.pgo.txt
@@ -1 +1 @@
-chrome-win-arm64-main-1746813443-6d765e1c4190ba1c54e170988acb74137dc5dc28-4cac03e32f70f02cbf11d96b5da54e629616b531.profdata
+chrome-win-arm64-main-1746834836-8f54c968cd61e3c72144519df91694e9f9b6e63e-3179e5ae5e5290f009d4ec21dba738788dc2aa8b.profdata
diff --git a/chrome/build/win32.pgo.txt b/chrome/build/win32.pgo.txt
index 2df4c3f..2680aa24 100644
--- a/chrome/build/win32.pgo.txt
+++ b/chrome/build/win32.pgo.txt
@@ -1 +1 @@
-chrome-win32-main-1746791612-db6dc6c307b34f3247a88fc82beeaadae347d690-4852d4ffeefa31265675175f00edcf84c5e30ecd.profdata
+chrome-win32-main-1746834836-e21ef60066ec33fd83f124ab95a09c9de7f0bced-3179e5ae5e5290f009d4ec21dba738788dc2aa8b.profdata
diff --git a/chrome/build/win64.pgo.txt b/chrome/build/win64.pgo.txt
index ffec439e..c5994bb0 100644
--- a/chrome/build/win64.pgo.txt
+++ b/chrome/build/win64.pgo.txt
@@ -1 +1 @@
-chrome-win64-main-1746791612-8dda5f0a63e87ebb38f287e78a6ee776d8bb236b-4852d4ffeefa31265675175f00edcf84c5e30ecd.profdata
+chrome-win64-main-1746813443-d0177f3ef39a67571bea32758731e6d8f238d388-4cac03e32f70f02cbf11d96b5da54e629616b531.profdata
diff --git a/chrome/test/data/pdf/ink2_manager_test.ts b/chrome/test/data/pdf/ink2_manager_test.ts
index 980beb6..8753340 100644
--- a/chrome/test/data/pdf/ink2_manager_test.ts
+++ b/chrome/test/data/pdf/ink2_manager_test.ts
@@ -175,6 +175,37 @@
     chrome.test.succeed();
   },
 
+  async function testNoInitializeOnScrollbar() {
+    let initEvents = 0;
+    manager.addEventListener('initialize-text-box', () => {
+      initEvents++;
+    });
+
+    // Zoom in to 2x so that there are scrollbars in both x and y.
+    let whenViewportChanged = eventToPromise('viewport-changed', manager);
+    viewport.setZoom(2.0);
+    await whenViewportChanged;
+
+    // Confirm both scrollbars and mock viewport dimensions.
+    chrome.test.assertTrue(viewport.documentHasScrollbars().vertical);
+    chrome.test.assertTrue(viewport.documentHasScrollbars().horizontal);
+    chrome.test.assertEq(100, viewport.size.width);
+    chrome.test.assertEq(100, viewport.size.height);
+    chrome.test.assertFalse(viewport.scrollbarWidth === 0);
+
+    const edge = 100 - viewport.scrollbarWidth;
+    Ink2Manager.getInstance().initializeTextAnnotation({x: edge, y: 20});
+    Ink2Manager.getInstance().initializeTextAnnotation({x: 20, y: edge});
+    chrome.test.assertEq(0, initEvents);
+
+    // Reset the zoom for the next test.
+    whenViewportChanged = eventToPromise('viewport-changed', manager);
+    viewport.setZoom(1.0);
+    await whenViewportChanged;
+
+    chrome.test.succeed();
+  },
+
   async function testInitializeTextBox() {
     // Add listeners for the expected events that fire in response to an
     // initializeTextAnnotation call.
diff --git a/chrome/test/data/pdf/ink2_test.ts b/chrome/test/data/pdf/ink2_test.ts
index 88c77fed..899494a 100644
--- a/chrome/test/data/pdf/ink2_test.ts
+++ b/chrome/test/data/pdf/ink2_test.ts
@@ -233,7 +233,7 @@
     PluginController.getInstance().getEventTarget().dispatchEvent(
         new CustomEvent(
             PluginControllerEventType.PLUGIN_MESSAGE,
-            {detail: {type: 'sendClickEvent', x: 400, y: 300}}));
+            {detail: {type: 'sendClickEvent', x: 50, y: 50}}));
     await microtasksFinished();
     chrome.test.assertTrue(isVisible(textbox));
 
diff --git a/chrome/test/data/pdf/test_util.ts b/chrome/test/data/pdf/test_util.ts
index 0556170..f7cd706 100644
--- a/chrome/test/data/pdf/test_util.ts
+++ b/chrome/test/data/pdf/test_util.ts
@@ -549,7 +549,7 @@
   const mockSizer = new MockSizer();
   const viewport = new Viewport(
       mockWindow as unknown as HTMLElement, mockSizer as unknown as HTMLElement,
-      dummyContent, /*scrollbarWidth=*/ 0, /*defaultZoom=*/ 1);
+      dummyContent, /*scrollbarWidth=*/ 5, /*defaultZoom=*/ 1);
   viewport.setZoomFactorRange([0.25, 0.4, 0.5, 1, 2]);
   const documentDimensions = new MockDocumentDimensions(0, 0);
   documentDimensions.addPage(90, 90);
diff --git a/chrome/test/data/webui/side_panel/read_anything/speech_controller_test.ts b/chrome/test/data/webui/side_panel/read_anything/speech_controller_test.ts
index b9c686a..b2223ca1 100644
--- a/chrome/test/data/webui/side_panel/read_anything/speech_controller_test.ts
+++ b/chrome/test/data/webui/side_panel/read_anything/speech_controller_test.ts
@@ -13,7 +13,6 @@
 suite('SpeechController', () => {
   let speech: TestSpeechBrowserProxy;
   let speechController: SpeechController;
-  let onStop: boolean;
   let isSpeechActiveChanged: boolean;
   let isAudioCurrentlyPlayingChanged: boolean;
   let onPreviewVoicePlaying: boolean;
@@ -31,14 +30,9 @@
     SpeechBrowserProxyImpl.setInstance(speech);
     isSpeechActiveChanged = false;
     isAudioCurrentlyPlayingChanged = false;
-    onStop = false;
     onPreviewVoicePlaying = false;
     onEngineStateChange = false;
     const speechListener = {
-      onStop() {
-        onStop = true;
-      },
-
       onIsSpeechActiveChange() {
         isSpeechActiveChanged = true;
       },
@@ -80,7 +74,6 @@
 
     assertTrue(isSpeechActiveChanged);
     assertTrue(isAudioCurrentlyPlayingChanged);
-    assertFalse(onStop);
     assertNotEquals(state, speechController.getState());
     assertTrue(speechController.isSpeechActive());
     assertTrue(speechController.isSpeechTreeInitialized());
@@ -106,7 +99,6 @@
 
     assertTrue(isSpeechActiveChanged);
     assertTrue(isAudioCurrentlyPlayingChanged);
-    assertFalse(onStop);
     assertFalse(speechController.isSpeechActive());
     assertFalse(speechController.isSpeechTreeInitialized());
     assertEquals(PauseActionSource.DEFAULT, speechController.getPauseSource());
@@ -267,7 +259,7 @@
     });
 
     test('with no node id does nothing', () => {
-      speechController.initializeSpeechTree(null);
+      speechController.initializeSpeechTree();
 
       assertFalse(!!initAxPositionWithNode);
       assertFalse(startedPreprocess);
@@ -303,7 +295,7 @@
 
     speechController.stopSpeech(source);
 
-    assertTrue(onStop);
+    assertTrue(isSpeechActiveChanged);
     assertFalse(speechController.isSpeechActive());
     assertFalse(speechController.isAudioCurrentlyPlaying());
     assertEquals(source, speechController.getPauseSource());
@@ -313,8 +305,7 @@
 
   test('stopSpeech with button click logs play session', () => {
     const source = PauseActionSource.BUTTON_CLICK;
-    speechController.onPlay();
-    speechController.setIsSpeechActive(true);
+    speechController.onPlayPauseToggle(null, 'New phone who dis?');
 
     speechController.stopSpeech(source);
     assertEquals(1, metrics.getCallCount('recordSpeechPlaybackLength'));
@@ -331,7 +322,7 @@
 
     speechController.stopSpeech(source);
 
-    assertTrue(onStop);
+    assertTrue(isSpeechActiveChanged);
     assertFalse(speechController.isSpeechActive());
     assertFalse(speechController.isAudioCurrentlyPlaying());
     assertEquals(source, speechController.getPauseSource());
@@ -347,7 +338,6 @@
 
     speechController.onSpeechInterrupted();
 
-    assertFalse(onStop);
     assertTrue(speechController.isAudioCurrentlyPlaying());
     assertTrue(speechController.isSpeechActive());
     assertTrue(speechController.isSpeechBeingRepositioned());
@@ -359,7 +349,6 @@
 
     speechController.onSpeechInterrupted();
 
-    assertTrue(onStop);
     assertEquals(
         PauseActionSource.ENGINE_INTERRUPT, speechController.getPauseSource());
     assertFalse(speechController.isAudioCurrentlyPlaying());
@@ -370,12 +359,11 @@
         await metrics.whenCalled('recordSpeechStopSource'));
   });
   test('onSpeechFinished', () => {
-    speechController.onPlay();
-    speechController.setIsSpeechActive(true);
+    speechController.onPlayPauseToggle(null, 'New phone who dis?');
 
     speechController.onSpeechFinished();
 
-    assertTrue(onStop);
+    assertTrue(isSpeechActiveChanged);
     assertEquals(1, metrics.getCallCount('recordSpeechPlaybackLength'));
     assertEquals(1, metrics.getCallCount('recordSpeechStopSource'));
     assertFalse(speechController.isSpeechActive());
diff --git a/chrome/test/data/webui/side_panel/read_anything/word_highlighting_test.ts b/chrome/test/data/webui/side_panel/read_anything/word_highlighting_test.ts
index 5ea223a..8369c1f1 100644
--- a/chrome/test/data/webui/side_panel/read_anything/word_highlighting_test.ts
+++ b/chrome/test/data/webui/side_panel/read_anything/word_highlighting_test.ts
@@ -281,7 +281,7 @@
 
     // Update the selection directly on the document.
     const spans = app.$.container.querySelectorAll('span');
-    assertEquals(spans.length, 3);
+    assertEquals(4, spans.length);
     const anchor = spans[anchorIndex]!;
     const focus = spans[focusIndex]!;
     const range = document.createRange();
diff --git a/chromecast/BUILD.gn b/chromecast/BUILD.gn
index 9939921..11caa7e 100644
--- a/chromecast/BUILD.gn
+++ b/chromecast/BUILD.gn
@@ -728,8 +728,11 @@
       "android/*",
       "androidx/*",
       "com/google/android/gms/*",
+      "com/google/androidxr/*",
+      "com/google/ar/*",
       "com/google/firebase/*",
       "com/google/protobuf/*",
+      "com/google/vr/*",
       "java/*",
       "kotlin/*",
       "kotlinx/*",
diff --git a/chromecast/media/common/media_pipeline_backend_manager.cc b/chromecast/media/common/media_pipeline_backend_manager.cc
index 312be939..b7132db 100644
--- a/chromecast/media/common/media_pipeline_backend_manager.cc
+++ b/chromecast/media/common/media_pipeline_backend_manager.cc
@@ -87,8 +87,8 @@
 std::unique_ptr<CmaBackend> MediaPipelineBackendManager::CreateBackend(
     const media::MediaPipelineDeviceParams& params) {
   DCHECK(media_task_runner_->BelongsToCurrentThread());
-  return std::make_unique<MediaPipelineBackendWrapper>(params, this,
-                                                       media_resource_tracker_);
+  return std::make_unique<MediaPipelineBackendWrapper>(
+      params, weak_factory_.GetWeakPtr(), media_resource_tracker_);
 }
 
 scoped_refptr<base::SequencedTaskRunner>
diff --git a/chromecast/media/common/media_pipeline_backend_wrapper.cc b/chromecast/media/common/media_pipeline_backend_wrapper.cc
index 22a1553..5c3535d 100644
--- a/chromecast/media/common/media_pipeline_backend_wrapper.cc
+++ b/chromecast/media/common/media_pipeline_backend_wrapper.cc
@@ -89,7 +89,7 @@
   ActiveMediaPipelineBackendWrapper(
       const media::MediaPipelineDeviceParams& params,
       MediaPipelineBackendWrapper* wrapping_backend,
-      MediaPipelineBackendManager* backend_manager,
+      base::WeakPtr<MediaPipelineBackendManager> backend_manager,
       MediaResourceTracker* media_resource_tracker);
 
   ActiveMediaPipelineBackendWrapper(const ActiveMediaPipelineBackendWrapper&) =
@@ -129,7 +129,7 @@
   bool video_decoder_created_;
   const std::unique_ptr<MediaPipelineBackend> backend_;
   MediaPipelineBackendWrapper* const wrapping_backend_;
-  MediaPipelineBackendManager* const backend_manager_;
+  const base::WeakPtr<MediaPipelineBackendManager> backend_manager_;
   const MediaPipelineDeviceParams::AudioStreamType audio_stream_type_;
   const AudioContentType content_type_;
 
@@ -143,7 +143,7 @@
 ActiveMediaPipelineBackendWrapper::ActiveMediaPipelineBackendWrapper(
     const media::MediaPipelineDeviceParams& params,
     MediaPipelineBackendWrapper* wrapping_backend,
-    MediaPipelineBackendManager* backend_manager,
+    base::WeakPtr<MediaPipelineBackendManager> backend_manager,
     MediaResourceTracker* media_resource_tracker)
     : audio_decoder_ptr_(nullptr),
       video_decoder_created_(false),
@@ -162,14 +162,14 @@
   // When the backend is revoked,  the video/audio_decoder should be considered
   // gone to |backend_manager_|. The reason is that the replacement of the
   // Audio/VideoDecoderWrapper are dummy ones that are not actually playing.
-  if (audio_decoder_ptr_) {
+  if (audio_decoder_ptr_ && backend_manager_) {
     backend_manager_->DecrementDecoderCount(
         IsSfx() ? DecoderType::SFX_DECODER : DecoderType::AUDIO_DECODER);
     if (playing_) {
       backend_manager_->UpdatePlayingAudioCount(IsSfx(), content_type_, -1);
     }
   }
-  if (video_decoder_created_) {
+  if (video_decoder_created_ && backend_manager_) {
     backend_manager_->DecrementDecoderCount(DecoderType::VIDEO_DECODER);
   }
 }
@@ -299,7 +299,7 @@
 
 MediaPipelineBackendWrapper::MediaPipelineBackendWrapper(
     const media::MediaPipelineDeviceParams& params,
-    MediaPipelineBackendManager* backend_manager,
+    base::WeakPtr<MediaPipelineBackendManager> backend_manager,
     MediaResourceTracker* media_resource_tracker)
     : revoked_(false),
       backend_manager_(backend_manager),
@@ -309,7 +309,9 @@
 }
 
 MediaPipelineBackendWrapper::~MediaPipelineBackendWrapper() {
-  backend_manager_->BackendDestroyed(this);
+  if (backend_manager_) {
+    backend_manager_->BackendDestroyed(this);
+  }
 }
 
 void MediaPipelineBackendWrapper::Revoke() {
diff --git a/chromecast/media/common/media_pipeline_backend_wrapper.h b/chromecast/media/common/media_pipeline_backend_wrapper.h
index 6ad8f433..b61917c 100644
--- a/chromecast/media/common/media_pipeline_backend_wrapper.h
+++ b/chromecast/media/common/media_pipeline_backend_wrapper.h
@@ -25,9 +25,10 @@
 
 class MediaPipelineBackendWrapper : public CmaBackend {
  public:
-  MediaPipelineBackendWrapper(const media::MediaPipelineDeviceParams& params,
-                              MediaPipelineBackendManager* backend_manager,
-                              MediaResourceTracker* media_resource_tracker);
+  MediaPipelineBackendWrapper(
+      const media::MediaPipelineDeviceParams& params,
+      base::WeakPtr<MediaPipelineBackendManager> backend_manager,
+      MediaResourceTracker* media_resource_tracker);
 
   MediaPipelineBackendWrapper(const MediaPipelineBackendWrapper&) = delete;
   MediaPipelineBackendWrapper& operator=(const MediaPipelineBackendWrapper&) =
@@ -59,7 +60,7 @@
 
   bool revoked_;
   std::unique_ptr<DecoderCreatorCmaBackend> backend_;
-  MediaPipelineBackendManager* const backend_manager_;
+  const base::WeakPtr<MediaPipelineBackendManager> backend_manager_;
   const AudioContentType content_type_;
 };
 
diff --git a/chromeos/CHROMEOS_LKGM b/chromeos/CHROMEOS_LKGM
index 1dca82b..0f49a8e 100644
--- a/chromeos/CHROMEOS_LKGM
+++ b/chromeos/CHROMEOS_LKGM
@@ -1 +1 @@
-16277.0.0-1068727
\ No newline at end of file
+16279.0.0-1068758
\ No newline at end of file
diff --git a/clank b/clank
index af42a48a..687392b 160000
--- a/clank
+++ b/clank
@@ -1 +1 @@
-Subproject commit af42a48a53e7717ae26d7cd6e90195216fb9fe51
+Subproject commit 687392ba3de05f6fae3002d7b7435b231d048dce
diff --git a/components/autofill/core/browser/BUILD.gn b/components/autofill/core/browser/BUILD.gn
index 827dae8..6b33be9 100644
--- a/components/autofill/core/browser/BUILD.gn
+++ b/components/autofill/core/browser/BUILD.gn
@@ -543,6 +543,8 @@
     "payments/payments_window_manager_util.cc",
     "payments/payments_window_manager_util.h",
     "payments/risk_data_loader.h",
+    "payments/save_and_fill_manager.cc",
+    "payments/save_and_fill_manager.h",
     "payments/virtual_card_enroll_metrics_logger.cc",
     "payments/virtual_card_enroll_metrics_logger.h",
     "payments/virtual_card_enrollment_flow.h",
diff --git a/components/autofill/core/browser/payments/payments_autofill_client.cc b/components/autofill/core/browser/payments/payments_autofill_client.cc
index 80134d4d..bf9f0c42 100644
--- a/components/autofill/core/browser/payments/payments_autofill_client.cc
+++ b/components/autofill/core/browser/payments/payments_autofill_client.cc
@@ -252,6 +252,10 @@
 
 void PaymentsAutofillClient::ShowCreditCardSaveAndFillDialog() {}
 
+payments::SaveAndFillManager* PaymentsAutofillClient::GetSaveAndFillManager() {
+  return nullptr;
+}
+
 void PaymentsAutofillClient::ShowSelectBnplIssuerDialog(
     std::vector<BnplIssuerContext> bnpl_issuer_context,
     std::string app_locale,
diff --git a/components/autofill/core/browser/payments/payments_autofill_client.h b/components/autofill/core/browser/payments/payments_autofill_client.h
index caf8d47..87915901 100644
--- a/components/autofill/core/browser/payments/payments_autofill_client.h
+++ b/components/autofill/core/browser/payments/payments_autofill_client.h
@@ -61,6 +61,7 @@
 class MandatoryReauthManager;
 class PaymentsNetworkInterface;
 class PaymentsWindowManager;
+class SaveAndFillManager;
 
 // A payments-specific client interface that handles dependency injection, and
 // its implementations serve as the integration for platform-specific code. One
@@ -574,6 +575,10 @@
   // Shows the `Save and Fill` modal dialog.
   virtual void ShowCreditCardSaveAndFillDialog();
 
+  // Gets the payments Save and Fill manager owned by the client. This will be
+  // used to handle the Save and Fill dialog.
+  virtual payments::SaveAndFillManager* GetSaveAndFillManager();
+
   // Shows the issuer selection dialog for BNPL when the BNPL suggestion is
   // selected to let users choose a BNPL issuer.
   virtual void ShowSelectBnplIssuerDialog(
diff --git a/components/autofill/core/browser/payments/save_and_fill_manager.cc b/components/autofill/core/browser/payments/save_and_fill_manager.cc
new file mode 100644
index 0000000..e747bc1
--- /dev/null
+++ b/components/autofill/core/browser/payments/save_and_fill_manager.cc
@@ -0,0 +1,17 @@
+// 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/autofill/core/browser/payments/save_and_fill_manager.h"
+
+#include "base/check_deref.h"
+
+namespace autofill::payments {
+
+SaveAndFillManager::SaveAndFillManager(
+    PaymentsAutofillClient* payments_autofill_client)
+    : payments_autofill_client_(CHECK_DEREF(payments_autofill_client)) {}
+
+SaveAndFillManager::~SaveAndFillManager() = default;
+
+}  // namespace autofill::payments
diff --git a/components/autofill/core/browser/payments/save_and_fill_manager.h b/components/autofill/core/browser/payments/save_and_fill_manager.h
new file mode 100644
index 0000000..580a2ab
--- /dev/null
+++ b/components/autofill/core/browser/payments/save_and_fill_manager.h
@@ -0,0 +1,30 @@
+// Copyright 2025 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_AUTOFILL_CORE_BROWSER_PAYMENTS_SAVE_AND_FILL_MANAGER_H_
+#define COMPONENTS_AUTOFILL_CORE_BROWSER_PAYMENTS_SAVE_AND_FILL_MANAGER_H_
+
+#include "base/memory/raw_ref.h"
+
+namespace autofill::payments {
+
+class PaymentsAutofillClient;
+
+// Owned by PaymentsAutofillClient. There is one instance of this class per Web
+// Contents. This class manages the flow for the Save and Fill dialog.
+class SaveAndFillManager {
+ public:
+  explicit SaveAndFillManager(PaymentsAutofillClient* payments_autofill_client);
+  SaveAndFillManager(const SaveAndFillManager& other) = delete;
+  SaveAndFillManager& operator=(const SaveAndFillManager& other) = delete;
+  ~SaveAndFillManager();
+
+ private:
+  // The associated payments autofill client.
+  const raw_ref<PaymentsAutofillClient> payments_autofill_client_;
+};
+
+}  // namespace autofill::payments
+
+#endif  // COMPONENTS_AUTOFILL_CORE_BROWSER_PAYMENTS_SAVE_AND_FILL_MANAGER_H_
diff --git a/components/browser_ui/widget/android/java/src/org/chromium/components/browser_ui/widget/selectable_list/SelectableListToolbar.java b/components/browser_ui/widget/android/java/src/org/chromium/components/browser_ui/widget/selectable_list/SelectableListToolbar.java
index 83ce8cc..67d27fc 100644
--- a/components/browser_ui/widget/android/java/src/org/chromium/components/browser_ui/widget/selectable_list/SelectableListToolbar.java
+++ b/components/browser_ui/widget/android/java/src/org/chromium/components/browser_ui/widget/selectable_list/SelectableListToolbar.java
@@ -281,6 +281,7 @@
                         getContext(),
                         R.drawable.ic_arrow_back_white_24dp,
                         R.color.default_icon_color_tint_list);
+        mNavigationIconDrawable.setAutoMirrored(true);
 
         mShowInfoIcon = true;
         mShowInfoStringId = R.string.show_info;
diff --git a/components/browsing_topics/util.cc b/components/browsing_topics/util.cc
index 03097a6c..c33c8924 100644
--- a/components/browsing_topics/util.cc
+++ b/components/browsing_topics/util.cc
@@ -8,8 +8,8 @@
 
 #include "base/numerics/byte_conversions.h"
 #include "base/rand_util.h"
+#include "crypto/hash.h"
 #include "crypto/hmac.h"
-#include "crypto/sha2.h"
 #include "third_party/blink/public/common/features.h"
 
 namespace browsing_topics {
@@ -122,9 +122,9 @@
 }
 
 HashedHost HashMainFrameHostForStorage(const std::string& main_frame_host) {
-  int64_t result;
-  crypto::SHA256HashString(kMainFrameHostStoragePrefix + main_frame_host,
-                           &result, sizeof(result));
+  auto hash =
+      crypto::hash::Sha256(kMainFrameHostStoragePrefix + main_frame_host);
+  int64_t result = base::I64FromNativeEndian(base::span(hash).first<8u>());
   return HashedHost(result);
 }
 
diff --git a/components/collaboration/internal/collaboration_service_impl.cc b/components/collaboration/internal/collaboration_service_impl.cc
index 02f2b83..cdf8ff2 100644
--- a/components/collaboration/internal/collaboration_service_impl.cc
+++ b/components/collaboration/internal/collaboration_service_impl.cc
@@ -134,6 +134,8 @@
     std::unique_ptr<CollaborationControllerDelegate> delegate,
     const tab_groups::EitherGroupID& either_id,
     CollaborationServiceLeaveOrDeleteEntryPoint entry) {
+  metrics::RecordLeaveOrDeleteEntryPoint(data_sharing_service_->GetLogger(),
+                                         entry);
   auto it = collaboration_controllers_.find(either_id);
   if (it != collaboration_controllers_.end()) {
     it->second->delegate()->PromoteCurrentScreen();
diff --git a/components/collaboration/internal/metrics.cc b/components/collaboration/internal/metrics.cc
index eb3069fb..2204b64 100644
--- a/components/collaboration/internal/metrics.cc
+++ b/components/collaboration/internal/metrics.cc
@@ -4,8 +4,11 @@
 
 #include "components/collaboration/internal/metrics.h"
 
+#include <string_view>
+
 #include "base/metrics/histogram_functions.h"
 #include "base/strings/stringprintf.h"
+#include "components/collaboration/public/collaboration_flow_entry_point.h"
 #include "components/data_sharing/public/logger.h"
 #include "components/data_sharing/public/logger_common.mojom.h"
 #include "components/data_sharing/public/logger_utils.h"
@@ -233,6 +236,34 @@
   }
 }
 
+std::string_view CollaborationServiceLeaveOrDeleteEntryPointToString(
+    CollaborationServiceLeaveOrDeleteEntryPoint entry) {
+  switch (entry) {
+    case CollaborationServiceLeaveOrDeleteEntryPoint::kUnknown:
+      return "Unknown";
+    case CollaborationServiceLeaveOrDeleteEntryPoint::
+        kAndroidTabGridDialogLeave:
+      return "AndroidTabGridDialogLeave";
+    case CollaborationServiceLeaveOrDeleteEntryPoint::
+        kAndroidTabGridDialogDelete:
+      return "AndroidTabGridDialogDelete";
+    case CollaborationServiceLeaveOrDeleteEntryPoint::
+        kAndroidTabGroupContextMenuLeave:
+      return "AndroidTabGroupContextMenuLeave";
+    case CollaborationServiceLeaveOrDeleteEntryPoint::
+        kAndroidTabGroupContextMenuDelete:
+      return "AndroidTabGroupContextMenuDelete";
+    case CollaborationServiceLeaveOrDeleteEntryPoint::
+        kAndroidTabGroupItemMenuLeave:
+      return "AndroidTabGroupItemMenuLeave";
+    case CollaborationServiceLeaveOrDeleteEntryPoint::
+        kAndroidTabGroupItemMenuDelete:
+      return "AndroidTabGroupItemMenuDelete";
+    case CollaborationServiceLeaveOrDeleteEntryPoint::kAndroidTabGroupRow:
+      return "AndroidTabGroupRow";
+  }
+}
+
 std::string_view CollaborationServiceStepToString(
     CollaborationServiceStep step) {
   switch (step) {
@@ -274,6 +305,12 @@
       CollaborationServiceShareOrManageEntryPointToString(entry));
 }
 
+std::string CreateLeaveOrDeleteEntryLogToString(
+    CollaborationServiceLeaveOrDeleteEntryPoint entry) {
+  return base::StringPrintf(
+      "Leave or Delete Flow Started\n  From: %s\n",
+      CollaborationServiceLeaveOrDeleteEntryPointToString(entry));
+}
 std::string CreateLatencyLogToString(CollaborationServiceStep step,
                                      base::TimeDelta duration) {
   return base::StringPrintf("Step %s took %dms to complete.",
@@ -330,6 +367,15 @@
                    logger, CreateShareOrManageEntryLogToString(entry));
 }
 
+void RecordLeaveOrDeleteEntryPoint(
+    data_sharing::Logger* logger,
+    CollaborationServiceLeaveOrDeleteEntryPoint entry) {
+  base::UmaHistogramEnumeration(
+      "CollaborationService.LeaveOrDeleteFlow.EntryPoint", entry);
+  DATA_SHARING_LOG(logger_common::mojom::LogSource::CollaborationService,
+                   logger, CreateLeaveOrDeleteEntryLogToString(entry));
+}
+
 void RecordLatency(data_sharing::Logger* logger,
                    CollaborationServiceStep step,
                    base::TimeDelta duration) {
diff --git a/components/collaboration/internal/metrics.h b/components/collaboration/internal/metrics.h
index f64c1c03..1ef7823 100644
--- a/components/collaboration/internal/metrics.h
+++ b/components/collaboration/internal/metrics.h
@@ -114,6 +114,9 @@
                      CollaborationServiceJoinEvent event);
 void RecordShareOrManageEvent(data_sharing::Logger* logger,
                               CollaborationServiceShareOrManageEvent event);
+void RecordLeaveOrDeleteEntryPoint(
+    data_sharing::Logger* logger,
+    CollaborationServiceLeaveOrDeleteEntryPoint event);
 void RecordJoinOrShareOrManageEvent(
     data_sharing::Logger* logger,
     FlowType type,
diff --git a/components/collaboration/public/collaboration_flow_entry_point.h b/components/collaboration/public/collaboration_flow_entry_point.h
index 1e942ef..ae56915 100644
--- a/components/collaboration/public/collaboration_flow_entry_point.h
+++ b/components/collaboration/public/collaboration_flow_entry_point.h
@@ -34,16 +34,20 @@
 //   org.chromium.components.collaboration)
 enum class CollaborationServiceShareOrManageEntryPoint {
   kUnknown = 0,
+  kDialogToolbarButton = 10,
+  kRecentActivity = 3,
+  kNotification = 6,
+  kTabGroupItemMenuShare = 8,
+
+  // Android specific entry points.
   kAndroidTabGridDialogShare = 1,
   kAndroidTabGridDialogManage = 2,
-  kRecentActivity = 3,
   kAndroidTabGroupContextMenuShare = 4,
   kAndroidTabGroupContextMenuManage = 5,
-  kNotification = 6,
   kAndroidMessage = 7,
-  kTabGroupItemMenuShare = 8,
   kAndroidShareSheetExtra = 9,
-  kDialogToolbarButton = 10,
+
+  // IOS specific entry points.
   kiOSTabGroupIndicatorShare = 11,
   kiOSTabGroupIndicatorManage = 12,
   kiOSTabGridShare = 13,
@@ -52,10 +56,13 @@
   kiOSTabStripManage = 16,
   kiOSTabGroupViewShare = 17,
   kiOSTabGroupViewManage = 18,
+  kiOSMessage = 22,
+
+  // Desktop specific entry points.
   kDesktopGroupEditorShareOrManageButton = 19,
   kDesktopNotification = 20,
   kDesktopRecentActivity = 21,
-  kiOSMessage = 22,
+
   kMaxValue = kiOSMessage,
 };
 // LINT.ThenChange(//tools/metrics/histograms/metadata/collaboration_service/enums.xml:CollaborationServiceShareOrManageEntryPoint)
@@ -68,7 +75,16 @@
 //   org.chromium.components.collaboration)
 enum class CollaborationServiceLeaveOrDeleteEntryPoint {
   kUnknown = 0,
-  kMaxValue = kUnknown,
+
+  // Android specific entry points.
+  kAndroidTabGridDialogLeave = 1,
+  kAndroidTabGridDialogDelete = 2,
+  kAndroidTabGroupContextMenuLeave = 3,
+  kAndroidTabGroupContextMenuDelete = 4,
+  kAndroidTabGroupItemMenuLeave = 5,
+  kAndroidTabGroupItemMenuDelete = 6,
+  kAndroidTabGroupRow = 7,
+  kMaxValue = kAndroidTabGroupRow,
 };
 // LINT.ThenChange(//tools/metrics/histograms/metadata/collaboration_service/enums.xml:CollaborationServiceLeaveOrDeleteEntryPoint)
 
diff --git a/components/history/core/browser/expire_history_backend.h b/components/history/core/browser/expire_history_backend.h
index 3f30a43..a127595 100644
--- a/components/history/core/browser/expire_history_backend.h
+++ b/components/history/core/browser/expire_history_backend.h
@@ -16,6 +16,7 @@
 #include "base/memory/raw_ptr.h"
 #include "base/memory/weak_ptr.h"
 #include "base/time/time.h"
+#include "components/favicon_base/favicon_types.h"
 #include "components/history/core/browser/history_types.h"
 
 class GURL;
diff --git a/components/history/core/browser/history_service.h b/components/history/core/browser/history_service.h
index 208f579..205d0cc 100644
--- a/components/history/core/browser/history_service.h
+++ b/components/history/core/browser/history_service.h
@@ -34,6 +34,7 @@
 #include "base/time/time.h"
 #include "build/build_config.h"
 #include "components/favicon_base/favicon_callback.h"
+#include "components/favicon_base/favicon_types.h"
 #include "components/favicon_base/favicon_usage_data.h"
 #include "components/history/core/browser/history_types.h"
 #include "components/history/core/browser/keyword_id.h"
diff --git a/components/history/core/browser/history_types.h b/components/history/core/browser/history_types.h
index c18f798a..c9835a5 100644
--- a/components/history/core/browser/history_types.h
+++ b/components/history/core/browser/history_types.h
@@ -19,7 +19,6 @@
 #include "base/containers/flat_map.h"
 #include "base/functional/callback_forward.h"
 #include "base/time/time.h"
-#include "components/favicon_base/favicon_types.h"
 #include "components/history/core/browser/history_context.h"
 #include "components/history/core/browser/url_row.h"
 #include "components/query_parser/query_parser.h"
diff --git a/components/history_clusters/core/history_clusters_util.h b/components/history_clusters/core/history_clusters_util.h
index 4114b32..2cda495 100644
--- a/components/history_clusters/core/history_clusters_util.h
+++ b/components/history_clusters/core/history_clusters_util.h
@@ -9,6 +9,7 @@
 #include <string>
 #include <vector>
 
+#include "base/containers/flat_set.h"
 #include "components/history/core/browser/history_types.h"
 #include "url/gurl.h"
 
diff --git a/components/omnibox/browser/autocomplete_controller_metrics.cc b/components/omnibox/browser/autocomplete_controller_metrics.cc
index df72368..70cd8f6 100644
--- a/components/omnibox/browser/autocomplete_controller_metrics.cc
+++ b/components/omnibox/browser/autocomplete_controller_metrics.cc
@@ -5,13 +5,147 @@
 #include "autocomplete_controller_metrics.h"
 
 #include <string>
+#include <string_view>
 
 #include "base/metrics/histogram_functions.h"
 #include "base/metrics/histogram_macros.h"
+#include "base/strings/strcat.h"
 #include "base/time/time.h"
 #include "components/omnibox/browser/autocomplete_controller.h"
 #include "components/omnibox/browser/autocomplete_provider.h"
 #include "components/omnibox/browser/autocomplete_result.h"
+#include "components/omnibox/common/omnibox_feature_configs.h"
+#include "third_party/metrics_proto/omnibox_event.pb.h"
+
+namespace {
+
+using omnibox_feature_configs::AutocompleteControllerMetricsOptimization;
+
+enum class MetricNameSuffix {
+  kDone,
+  kLastChange,
+  kLastDefaultChange,
+  kProvider,
+  kMaxValue = kProvider
+};
+
+constexpr std::string_view ToString(MetricNameSuffix name_suffix) {
+  using enum MetricNameSuffix;
+  switch (name_suffix) {
+    case kDone:
+      return "Done";
+    case kLastChange:
+      return "LastChange";
+    case kLastDefaultChange:
+      return "LastDefaultChange";
+    case kProvider:
+      return "Provider";
+  }
+  NOTREACHED();
+}
+
+std::string GetMetricName(MetricNameSuffix name_suffix,
+                          const AutocompleteProvider* provider,
+                          std::string_view completion_suffix = "") {
+  return base::StrCat(
+      {"Omnibox.AsyncAutocompletionTime2.", ToString(name_suffix),
+       (provider ? "." : ""), (provider ? provider->GetName() : ""),
+       (completion_suffix.empty() ? "" : "."), completion_suffix});
+}
+
+void LogAsyncAutocompletionTimeMetricsImpl(MetricNameSuffix name_suffix,
+                                           const AutocompleteProvider* provider,
+                                           bool completed,
+                                           base::TimeDelta elapsed_time) {
+  // This may be called up to 40 times per omnibox key-stroke. Cache the
+  // histograms in a lookup table keyed  by name_suffix + provider_number
+  // (where provider_number is 0 unless name_suffix == kProvider) so that
+  // we can avoid having to construct their names and look them up each time.
+
+  // The max size of each of the histogram tables.
+  constexpr int kMaxHistogramIndex =
+      (static_cast<int>(MetricNameSuffix::kMaxValue) +
+       metrics::OmniboxEventProto_ProviderType_ProviderType_MAX);
+
+  // Validate the histogram lookup parameters:
+  // * name_suffix is in [0..kMaxValue]
+  // * `provider` is non-null iff name_suffix == kProvider, and vice versa.
+  // * if non-null, provider yields a value <= ProviderType_MAX
+  DCHECK_GE(static_cast<int>(name_suffix), 0);
+  DCHECK_LE(static_cast<int>(name_suffix),
+            static_cast<int>(MetricNameSuffix::kMaxValue));
+  DCHECK_EQ(name_suffix == MetricNameSuffix::kProvider, !!provider);
+  DCHECK(!provider ||
+         provider->AsOmniboxEventProviderType() <=
+             metrics::OmniboxEventProto_ProviderType_ProviderType_MAX);
+
+  // Each histogram is at the same index in its respective table.
+  const int histogram_index =
+      static_cast<int>(name_suffix) +
+      (provider ? provider->AsOmniboxEventProviderType() : 0);
+
+  // Use the `STATIC_HISTOGRAM_POINTER_GROUP` macro to define a static table of
+  // atomic histogram pointers which is indexed by `histogram_index`.
+  //
+  // I.e., the histograms are ordered as:
+  //   Done, LastChange, DefaultChange, Provider-0, Provider-1, ...
+#define STATIC_HISTOGRAM_TIMES_POINTER_GROUP(name, sample)    \
+  STATIC_HISTOGRAM_POINTER_GROUP(                             \
+      name, histogram_index, kMaxHistogramIndex,              \
+      AddTimeMillisecondsGranularity(sample),                 \
+      base::Histogram::FactoryTimeGet(                        \
+          name, base::Milliseconds(1), base::Seconds(10), 50, \
+          base::HistogramBase::kUmaTargetedHistogramFlag))
+
+  // These metrics are logged up to about 40 times each per omnibox keystroke.
+  // `GetMetricName()` is deterministic for any given set of parameters, so each
+  // histogram name is a run-time constant and a pointer to the corresponding
+  // histogram object will be cached on first use in a static table.
+  STATIC_HISTOGRAM_TIMES_POINTER_GROUP(GetMetricName(name_suffix, provider),
+                                       elapsed_time);
+  if (completed) {
+    STATIC_HISTOGRAM_TIMES_POINTER_GROUP(
+        GetMetricName(name_suffix, provider, "Completed"), elapsed_time);
+  } else {
+    STATIC_HISTOGRAM_TIMES_POINTER_GROUP(
+        GetMetricName(name_suffix, provider, "Interrupted"), elapsed_time);
+  }
+#undef STATIC_HISTOGRAM_TIMES_POINTER_GROUP
+}
+
+inline void LogAsyncAutocompletionTimeMetrics(MetricNameSuffix metric,
+                                              bool completed,
+                                              base::TimeDelta elapsed_time) {
+  DCHECK_NE(metric, MetricNameSuffix::kProvider);
+  LogAsyncAutocompletionTimeMetricsImpl(metric, nullptr, completed,
+                                        elapsed_time);
+}
+
+inline void LogAsyncAutocompletionTimeMetrics(
+    const AutocompleteProvider& provider,
+    base::TimeDelta elapsed_time) {
+  LogAsyncAutocompletionTimeMetricsImpl(MetricNameSuffix::kProvider, &provider,
+                                        provider.done(), elapsed_time);
+}
+
+void OldLogAsyncAutocompletionTimeMetrics(const std::string& name,
+                                          bool completed,
+                                          base::TimeDelta elapsed_time) {
+  const auto name_prefix = "Omnibox.AsyncAutocompletionTime2." + name;
+
+  // These metrics are logged up to about 40 times per omnibox keystroke. Use
+  // the, less efficient, UMA histogram functions because the names are dynamic.
+  // Each histogram function invocation results in a string alloc and a
+  // histogram lookup => ~120 allocs and 120 lookups per omnibox keystroke.
+  base::UmaHistogramTimes(name_prefix, elapsed_time);
+  if (completed) {
+    base::UmaHistogramTimes(name_prefix + ".Completed", elapsed_time);
+  } else {
+    base::UmaHistogramTimes(name_prefix + ".Interrupted", elapsed_time);
+  }
+}
+
+}  // namespace
 
 AutocompleteControllerMetrics::AutocompleteControllerMetrics(
     const AutocompleteController& controller)
@@ -29,14 +163,16 @@
     std::vector<AutocompleteResult::MatchDedupComparator> last_result,
     std::vector<AutocompleteResult::MatchDedupComparator> new_result) {
   // Only log metrics for async requests.
-  if (controller_->input().omit_asynchronous_matches())
+  if (controller_->input().omit_asynchronous_matches()) {
     return;
+  }
 
   // If results are empty then the omnibox is likely closed, and clearing old
   // results won't be user visible. E.g., this occurs when opening a new tab
   // while the popup was open.
-  if (new_result.empty())
+  if (new_result.empty()) {
     return;
+  }
 
   // Log suggestion changes.
 
@@ -51,25 +187,26 @@
   }
   LogSuggestionChangeInAnyPositionMetrics(any_match_changed_or_removed);
 
-  // Log suggestion finalization times.
-  // This handles logging as soon as the final update occurs, while `OnStop()`
-  // handles the case where the final update never occurs because of
-  // interruptions.
+  // Log suggestion finalization times. This handles logging as soon as the
+  // final update occurs, while `OnStop()` handles the case where the final
+  // update never occurs because of interruptions.
 
   // E.g., suggestion deletion can call `OnNotifyChanged()` after the controller
-  // is done and finalization metrics have been logged. They shouldn't be
-  // re-logged.
+  // is done and finalization metrics have been logged. They should not be
+  // logged again.
   if (controller_->last_update_type() ==
-      AutocompleteController::UpdateType::kMatchDeletion)
+      AutocompleteController::UpdateType::kMatchDeletion) {
     return;
+  }
 
   const bool any_match_changed_or_removed_or_added =
       any_match_changed_or_removed || last_result.size() != new_result.size();
   const bool default_match_changed_or_removed_or_added =
       last_result.empty() || last_result[0] != new_result[0];
 
-  if (any_match_changed_or_removed_or_added)
+  if (any_match_changed_or_removed_or_added) {
     last_change_time_ = base::TimeTicks::Now();
+  }
   if (default_match_changed_or_removed_or_added) {
     DCHECK(any_match_changed_or_removed_or_added);
     last_default_change_time_ = last_change_time_;
@@ -78,55 +215,60 @@
   // update.
   // TODO(crbug.com/364303536): `logged_finalization_metrics_` should be
   //   guaranteed false here (hence the DCHECK in
-  //   `LogSuggestionFinalizationMetrics()`. But because of a temporary bandaid
+  //   `LogSuggestionFinalizationMetrics()`. But because of a temporary band-aid
   //   to allow history embedding answers and unscoped extension suggestions to
   //   ignore the stop timer, we need to check it anyways.
-  if (controller_->done() && !logged_finalization_metrics_)
+  if (controller_->done() && !logged_finalization_metrics_) {
     LogSuggestionFinalizationMetrics();
+  }
 }
 
 void AutocompleteControllerMetrics::OnProviderUpdate(
     const AutocompleteProvider& provider) const {
   // Only log metrics for async requests. This will likely never happen, since
   // `OnProviderUpdate()` is only called by async providers (but not necessarily
-  // async'ly, see the comments in
-  // `AutocompleteController::OnProviderUpdate()`).
-  if (controller_->input().omit_asynchronous_matches())
+  // async'ly, see the comments in `AutocompleteController::OnProviderUpdate`).
+  if (controller_->input().omit_asynchronous_matches()) {
     return;
+  }
 
   // Some async providers may produce multiple updates. Only log the final async
   // update.
-  if (provider.done())
+  if (provider.done()) {
     LogProviderTimeMetrics(provider);
+  }
 }
 
 void AutocompleteControllerMetrics::OnStop() {
   // Only log metrics for async requests.
-  if (controller_->input().omit_asynchronous_matches())
+  if (controller_->input().omit_asynchronous_matches()) {
     return;
+  }
 
   // Done providers should already be logged by `OnProviderUpdate()`.
   for (const auto& provider : controller_->providers()) {
-    if (!provider->done())
+    if (!provider->done()) {
       LogProviderTimeMetrics(*provider);
+    }
   }
 
-  // If the controller is done, `OnNotifyChanged()` should have already logged
-  // finalization metrics. This case, i.e. `OnStop()` invoked even though the
-  // controller is done, is possible because `OnStart()` calls `OnStop()`.
+  // If the controller is done, `OnNotifyChanged()` should have already
+  // logged finalization metrics. This case, i.e. `OnStop()` invoked even
+  // though the controller is done, is possible because `OnStart()` calls
+  // `OnStop()`.
   // TODO(crbug.com/364303536): `logged_finalization_metrics_` should be
   //   guaranteed false here (hence the DCHECK in
-  //   `LogSuggestionFinalizationMetrics()`. But because of a temporary bandaid
-  //   to allow history embedding answers and unscoped extension answers to
-  //   ignore the stop timer, we need to check it anyways.
+  //   `LogSuggestionFinalizationMetrics()`. But because of a temporary
+  //   bandaid to allow history embedding answers and unscoped extension
+  //   answers to ignore the stop timer, we need to check it anyways.
   if (!controller_->done() && !logged_finalization_metrics_) {
     LogSuggestionFinalizationMetrics();
   }
 }
 
 void AutocompleteControllerMetrics::LogSuggestionFinalizationMetrics() {
-  // Finalization metrics should be logged once only, either when all async
-  // providers complete or they're interrupted before completion.
+  // Finalization metrics should be logged once only, either when all
+  // async providers complete or they're interrupted before completion.
 #if BUILDFLAG(IS_IOS)
   // iOS is weird in that it sometimes calls `InjectAdHocMatch()` when the user
   // selects a suggestion, thus changing the results when autocompletion is done
@@ -140,65 +282,96 @@
              controller_->last_update_type());
   logged_finalization_metrics_ = true;
 
-  LogAsyncAutocompletionTimeMetrics("Done", controller_->done(),
-                                    base::TimeTicks::Now());
-  LogAsyncAutocompletionTimeMetrics("LastChange", controller_->done(),
-                                    last_change_time_);
-  LogAsyncAutocompletionTimeMetrics("LastDefaultChange", controller_->done(),
-                                    last_default_change_time_);
+  const auto done_elapsed_time = base::TimeTicks::Now() - start_time_;
+  const auto last_change_elapsed_time = last_change_time_ - start_time_;
+  const auto last_default_change_elapsed_time =
+      last_default_change_time_ - start_time_;
+  const bool is_completed = controller_->done();
+
+  if (AutocompleteControllerMetricsOptimization::Get().enabled) {
+    using enum MetricNameSuffix;
+    LogAsyncAutocompletionTimeMetrics(kDone, is_completed, done_elapsed_time);
+    LogAsyncAutocompletionTimeMetrics(kLastChange, is_completed,
+                                      last_change_elapsed_time);
+    LogAsyncAutocompletionTimeMetrics(kLastDefaultChange, is_completed,
+                                      last_default_change_elapsed_time);
+  } else {
+    OldLogAsyncAutocompletionTimeMetrics("Done", is_completed,
+                                         done_elapsed_time);
+    OldLogAsyncAutocompletionTimeMetrics("LastChange", is_completed,
+                                         last_change_elapsed_time);
+    OldLogAsyncAutocompletionTimeMetrics("LastDefaultChange", is_completed,
+                                         last_default_change_elapsed_time);
+  }
 }
 
 void AutocompleteControllerMetrics::LogProviderTimeMetrics(
     const AutocompleteProvider& provider) const {
-  LogAsyncAutocompletionTimeMetrics(
-      std::string("Provider.") + provider.GetName(), provider.done(),
-      base::TimeTicks::Now());
-}
-
-void AutocompleteControllerMetrics::LogAsyncAutocompletionTimeMetrics(
-    const std::string& name,
-    bool completed,
-    const base::TimeTicks end_time) const {
-  const auto name_prefix = "Omnibox.AsyncAutocompletionTime2." + name;
-  const auto elapsed_time = end_time - start_time_;
-  // These metrics are logged up to about 40 times per omnibox keystroke. But
-  // use UMA functions as the names are dynamic.
-  base::UmaHistogramTimes(name_prefix, elapsed_time);
-  if (completed)
-    base::UmaHistogramTimes(name_prefix + ".Completed", elapsed_time);
-  else
-    base::UmaHistogramTimes(name_prefix + ".Interrupted", elapsed_time);
+  const auto elapsed_time = base::TimeTicks::Now() - start_time_;
+  if (AutocompleteControllerMetricsOptimization::Get().enabled) {
+    LogAsyncAutocompletionTimeMetrics(provider, elapsed_time);
+  } else {
+    OldLogAsyncAutocompletionTimeMetrics(
+        std::string("Provider.") + provider.GetName(), provider.done(),
+        elapsed_time);
+  }
 }
 
 void AutocompleteControllerMetrics::LogSuggestionChangeIndexMetrics(
     size_t change_index) const {
-  std::string name = "Omnibox.MatchStability2.MatchChangeIndex";
-  size_t max = AutocompleteResult::kMaxAutocompletePositionValue;
   // These metrics are logged up to about 50 times per omnibox keystroke, so use
-  // UMA macros for efficiency.
-  if (controller_->last_update_type() ==
-          AutocompleteController::UpdateType::kSyncPass ||
-      controller_->last_update_type() ==
-          AutocompleteController::UpdateType::kSyncPassOnly) {
-    UMA_HISTOGRAM_EXACT_LINEAR(name + ".CrossInput", change_index, max);
+  // the UMA macros (which cache the histogram pointer) for efficiency.
+  static constexpr char kName[] = "Omnibox.MatchStability2.MatchChangeIndex";
+  constexpr size_t max = AutocompleteResult::kMaxAutocompletePositionValue;
+  const bool is_sync = (controller_->last_update_type() ==
+                            AutocompleteController::UpdateType::kSyncPass ||
+                        controller_->last_update_type() ==
+                            AutocompleteController::UpdateType::kSyncPassOnly);
+
+  if (AutocompleteControllerMetricsOptimization::Get().enabled) {
+    UMA_HISTOGRAM_EXACT_LINEAR(kName, change_index, max);
+    if (is_sync) {
+      UMA_HISTOGRAM_EXACT_LINEAR(base::StrCat({kName, ".CrossInput"}),
+                                 change_index, max);
+    } else {
+      UMA_HISTOGRAM_EXACT_LINEAR(base::StrCat({kName, ".Async"}), change_index,
+                                 max);
+    }
   } else {
-    UMA_HISTOGRAM_EXACT_LINEAR(name + ".Async", change_index, max);
+    const std::string name = kName;  // Unnecessary string alloc!
+    UMA_HISTOGRAM_EXACT_LINEAR(name, change_index, max);
+    if (is_sync) {
+      UMA_HISTOGRAM_EXACT_LINEAR(name + ".CrossInput", change_index, max);
+    } else {
+      UMA_HISTOGRAM_EXACT_LINEAR(name + ".Async", change_index, max);
+    }
   }
-  UMA_HISTOGRAM_EXACT_LINEAR(name, change_index, max);
 }
 
 void AutocompleteControllerMetrics::LogSuggestionChangeInAnyPositionMetrics(
     bool changed) const {
-  std::string name = "Omnibox.MatchStability2.MatchChangeInAnyPosition";
-  // These metrics are logged up to about 5 times per omnibox keystroke, so
-  // use UMA macros for efficiency.
-  if (controller_->last_update_type() ==
-          AutocompleteController::UpdateType::kSyncPass ||
-      controller_->last_update_type() ==
-          AutocompleteController::UpdateType::kSyncPassOnly) {
-    UMA_HISTOGRAM_BOOLEAN(name + ".CrossInput", changed);
+  // These metrics are logged up to about 5 times per omnibox keystroke, so use
+  // the UMA macros (which cache the histogram pointer) for efficiency.
+  static constexpr char kName[] =
+      "Omnibox.MatchStability2.MatchChangeInAnyPosition";
+  const bool is_sync = (controller_->last_update_type() ==
+                            AutocompleteController::UpdateType::kSyncPass ||
+                        controller_->last_update_type() ==
+                            AutocompleteController::UpdateType::kSyncPassOnly);
+  if (AutocompleteControllerMetricsOptimization::Get().enabled) {
+    UMA_HISTOGRAM_BOOLEAN(kName, changed);
+    if (is_sync) {
+      UMA_HISTOGRAM_BOOLEAN(base::StrCat({kName, ".CrossInput"}), changed);
+    } else {
+      UMA_HISTOGRAM_BOOLEAN(base::StrCat({kName, ".Async"}), changed);
+    }
   } else {
-    UMA_HISTOGRAM_BOOLEAN(name + ".Async", changed);
+    const std::string name = kName;  // Unnecessary string alloc!
+    UMA_HISTOGRAM_BOOLEAN(name, changed);
+    if (is_sync) {
+      UMA_HISTOGRAM_BOOLEAN(name + ".CrossInput", changed);
+    } else {
+      UMA_HISTOGRAM_BOOLEAN(name + ".Async", changed);
+    }
   }
-  UMA_HISTOGRAM_BOOLEAN(name, changed);
 }
diff --git a/components/omnibox/browser/autocomplete_controller_metrics.h b/components/omnibox/browser/autocomplete_controller_metrics.h
index 6a537d2..8b18ac2 100644
--- a/components/omnibox/browser/autocomplete_controller_metrics.h
+++ b/components/omnibox/browser/autocomplete_controller_metrics.h
@@ -86,13 +86,6 @@
   // whether the provider completed or was interrupted.
   void LogProviderTimeMetrics(const AutocompleteProvider& provider) const;
 
-  // Helper for the above 2 logging methods. Logs
-  // 'Omnibox.AsyncAutocompletionTime.<name>'. Additionally logs either
-  // '*.Completed' or '*.Interrupted' depending on `completed`.
-  void LogAsyncAutocompletionTimeMetrics(const std::string& name,
-                                         bool completed,
-                                         const base::TimeTicks end_time) const;
-
   // Logs 'Omnibox.MatchStability.MatchChangeIndex'. Additionally logs
   // '*.CrossInput' or '*.Async' depending on `controller_.in_start()`.
   void LogSuggestionChangeIndexMetrics(size_t change_index) const;
diff --git a/components/omnibox/browser/autocomplete_controller_metrics_unittest.cc b/components/omnibox/browser/autocomplete_controller_metrics_unittest.cc
index 7d39bf4..eb637bc6 100644
--- a/components/omnibox/browser/autocomplete_controller_metrics_unittest.cc
+++ b/components/omnibox/browser/autocomplete_controller_metrics_unittest.cc
@@ -21,6 +21,7 @@
 #include "components/omnibox/browser/autocomplete_result.h"
 #include "components/omnibox/browser/fake_autocomplete_controller.h"
 #include "components/omnibox/browser/fake_autocomplete_provider.h"
+#include "components/omnibox/common/omnibox_feature_configs.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
@@ -58,7 +59,7 @@
 };
 }  // namespace
 
-class AutocompleteControllerMetricsTest : public testing::Test {
+class AutocompleteControllerMetricsTest : public testing::TestWithParam<bool> {
  public:
   AutocompleteControllerMetricsTest()
       : controller_(&task_environment_),
@@ -67,6 +68,8 @@
         base::MakeRefCounted<FakeAutocompleteProviderDelayed>(
             AutocompleteProvider::Type::TYPE_BOOKMARK, &task_environment_)};
 
+    scoped_optimization_config_.Get().enabled = GetParam();
+
     // Allow tests to simulate an initial update with no changes. Since the
     // 0-matches cases is special handled, tests can't simply do
     // `Simulate(true|false, {})` to simulate an initial update with no changes;
@@ -211,13 +214,16 @@
 
   // Used to control time passed between calls. Many metrics tested are timing
   // metrics.
+  omnibox_feature_configs::ScopedConfigForTesting<
+      omnibox_feature_configs::AutocompleteControllerMetricsOptimization>
+      scoped_optimization_config_;
   base::test::SingleThreadTaskEnvironment task_environment_{
       base::test::TaskEnvironment::TimeSource::MOCK_TIME};
   FakeAutocompleteController controller_;
   std::unique_ptr<base::HistogramTester> histogram_tester_;
 };
 
-TEST_F(AutocompleteControllerMetricsTest, SuggestionFinalization_SyncInput) {
+TEST_P(AutocompleteControllerMetricsTest, SuggestionFinalization_SyncInput) {
   // Sync inputs should not log metrics.
   SetInputSync(true);
   SimulateStart(true, {CreateMatch(1)});
@@ -225,7 +231,7 @@
   ExpectNoSuggestionFinalizationMetrics();
 }
 
-TEST_F(AutocompleteControllerMetricsTest,
+TEST_P(AutocompleteControllerMetricsTest,
        SuggestionFinalization_OnlySyncUpdate) {
   // Sync updates should log metrics.
   SimulateStart(true, {CreateMatch(1)});
@@ -233,7 +239,7 @@
   StopAndExpectNoSuggestionFinalizationMetrics();
 }
 
-TEST_F(AutocompleteControllerMetricsTest,
+TEST_P(AutocompleteControllerMetricsTest,
        SuggestionFinalization_OnlySyncUpdateWithNoChanges) {
   // Sync updates without changes should log metrics.
   SimulateStart(true, {CreateMatch(0)});
@@ -241,7 +247,7 @@
   StopAndExpectNoSuggestionFinalizationMetrics();
 }
 
-TEST_F(AutocompleteControllerMetricsTest,
+TEST_P(AutocompleteControllerMetricsTest,
        SuggestionFinalization_SyncAnd3AsyncUpdate) {
   // This is the typical flow: 1 sync update followed by multiple async updates.
   SimulateStart(false, {CreateMatch(1)});
@@ -255,7 +261,7 @@
   StopAndExpectNoSuggestionFinalizationMetrics();
 }
 
-TEST_F(AutocompleteControllerMetricsTest,
+TEST_P(AutocompleteControllerMetricsTest,
        SuggestionFinalization_SyncAnd3AsyncUpdateWithNoChanges) {
   // 1 sync and 3 async updates, none of the 4 has a change.
   SimulateStart(false, {CreateMatch(0)});
@@ -269,7 +275,7 @@
   StopAndExpectNoSuggestionFinalizationMetrics();
 }
 
-TEST_F(
+TEST_P(
     AutocompleteControllerMetricsTest,
     SuggestionFinalization_UnchangedSyncAnd2UnchangedAnd1ChangedAsyncUpdates) {
   // 1 sync and 3 async updates, only the last of the 4 has a change.
@@ -284,7 +290,7 @@
   StopAndExpectNoSuggestionFinalizationMetrics();
 }
 
-TEST_F(
+TEST_P(
     AutocompleteControllerMetricsTest,
     SuggestionFinalization_UnchangedSyncAnd1ChangedAnd2UnchangedAsyncUpdates) {
   // 1 sync and 3 async updates, only the 2nd of the 4 has a change. Because of
@@ -300,7 +306,7 @@
   StopAndExpectNoSuggestionFinalizationMetrics();
 }
 
-TEST_F(
+TEST_P(
     AutocompleteControllerMetricsTest,
     SuggestionFinalization_UnchangedSyncAnd1ChangedAnd2UnchangedAsyncUpdates_ChangeAppliedBeforeDone) {
   // 1 sync and 3 async updates, only the 2nd of the 4 has a change. The
@@ -317,7 +323,7 @@
   StopAndExpectNoSuggestionFinalizationMetrics();
 }
 
-TEST_F(AutocompleteControllerMetricsTest,
+TEST_P(AutocompleteControllerMetricsTest,
        SuggestionFinalization_ChangedSyncAnd3UnchangedAsyncUpdates) {
   // 1 sync and 3 async updates, only the 1st of the 4 has a change.
   SimulateStart(false, {CreateMatch(1)});
@@ -331,7 +337,7 @@
   StopAndExpectNoSuggestionFinalizationMetrics();
 }
 
-TEST_F(AutocompleteControllerMetricsTest,
+TEST_P(AutocompleteControllerMetricsTest,
        SuggestionFinalization_StopTimerReached) {
   // Simulates the case where the async updates take longer than the 1.5s stop
   // timer. It's not possible for the sync update to take longer, as the stop
@@ -346,7 +352,7 @@
   StopAndExpectNoSuggestionFinalizationMetrics();
 }
 
-TEST_F(AutocompleteControllerMetricsTest, SuggestionFinalization_Interrupted) {
+TEST_P(AutocompleteControllerMetricsTest, SuggestionFinalization_Interrupted) {
   // Start 1st input.
   SimulateStart(false, {CreateMatch(1)});
   // 1 async update for 1st input.
@@ -372,7 +378,7 @@
   StopAndExpectNoSuggestionFinalizationMetrics();
 }
 
-TEST_F(AutocompleteControllerMetricsTest, SuggestionFinalization_ExpireTimer) {
+TEST_P(AutocompleteControllerMetricsTest, SuggestionFinalization_ExpireTimer) {
   SimulateStart(true, {CreateMatch(0), CreateMatch(1)});
   ResetHistogramTester();
   // A sync update without matches. The transferred match should remain.
@@ -387,7 +393,7 @@
   StopAndExpectNoSuggestionFinalizationMetrics();
 }
 
-TEST_F(AutocompleteControllerMetricsTest,
+TEST_P(AutocompleteControllerMetricsTest,
        SuggestionFinalization_MatchDeletion) {
   SimulateStart(true, {CreateMatch(0), CreateMatch(1)});
   ResetHistogramTester();
@@ -407,7 +413,7 @@
   StopAndExpectNoSuggestionFinalizationMetrics();
 }
 
-TEST_F(AutocompleteControllerMetricsTest,
+TEST_P(AutocompleteControllerMetricsTest,
        SuggestionFinalization_DefaultUnchanged) {
   // Sync update with a default match change.
   SimulateStart(false, {CreateMatch(1)});
@@ -421,7 +427,7 @@
   StopAndExpectNoSuggestionFinalizationMetrics();
 }
 
-TEST_F(AutocompleteControllerMetricsTest, Provider_SyncAndAsyncCompletion) {
+TEST_P(AutocompleteControllerMetricsTest, Provider_SyncAndAsyncCompletion) {
   controller_.providers_ = {
       base::MakeRefCounted<FakeAutocompleteProviderDelayed>(
           AutocompleteProvider::Type::TYPE_BOOKMARK, &task_environment_, true),
@@ -472,7 +478,7 @@
   }
 }
 
-TEST_F(AutocompleteControllerMetricsTest,
+TEST_P(AutocompleteControllerMetricsTest,
        Provider_1ProviderWithMultipleUpdates) {
   // Sync update without completion.
   controller_.GetFakeProvider().done_ = true;
@@ -489,7 +495,7 @@
   ExpectNoProviderMetrics(controller_.GetFakeProvider().GetName());
 }
 
-TEST_F(AutocompleteControllerMetricsTest, Provider_Interrupted) {
+TEST_P(AutocompleteControllerMetricsTest, Provider_Interrupted) {
   controller_.providers_ = {
       base::MakeRefCounted<FakeAutocompleteProviderDelayed>(
           AutocompleteProvider::Type::TYPE_BOOKMARK, &task_environment_),
@@ -514,7 +520,7 @@
   ExpectSingleCountSuggestionFinalizationMetrics(3, 0, 0, false);
 }
 
-TEST_F(AutocompleteControllerMetricsTest, MatchStability) {
+TEST_P(AutocompleteControllerMetricsTest, MatchStability) {
   auto create_result = [&](std::vector<int> ids) {
     std::vector<AutocompleteMatch> matches;
     std::ranges::transform(ids, std::back_inserter(matches), [&](int id) {
@@ -656,3 +662,7 @@
               testing::ElementsAre(base::Bucket(0, 1)));
   ResetHistogramTester();
 }
+
+INSTANTIATE_TEST_SUITE_P(All,
+                         AutocompleteControllerMetricsTest,
+                         ::testing::Values(false, true));
diff --git a/components/omnibox/browser/clipboard_provider.h b/components/omnibox/browser/clipboard_provider.h
index 9daf7ebf..2458b07 100644
--- a/components/omnibox/browser/clipboard_provider.h
+++ b/components/omnibox/browser/clipboard_provider.h
@@ -9,6 +9,7 @@
 #include "base/memory/raw_ptr.h"
 #include "components/omnibox/browser/autocomplete_enums.h"
 #include "components/omnibox/browser/autocomplete_provider.h"
+#include "ui/gfx/image/image.h"
 
 class AutocompleteProviderClient;
 class AutocompleteProviderListener;
diff --git a/components/omnibox/common/omnibox_feature_configs.cc b/components/omnibox/common/omnibox_feature_configs.cc
index 2f212475..befcf448 100644
--- a/components/omnibox/common/omnibox_feature_configs.cc
+++ b/components/omnibox/common/omnibox_feature_configs.cc
@@ -20,6 +20,16 @@
     base::FEATURE_ENABLED_BY_DEFAULT;
 #endif
 
+BASE_FEATURE(AutocompleteControllerMetricsOptimization::
+                 kAutocompleteControllerMetricsOptimization,
+             "AutocompleteControlMetricsOptimization",
+             base::FEATURE_ENABLED_BY_DEFAULT);
+AutocompleteControllerMetricsOptimization::
+    AutocompleteControllerMetricsOptimization() {
+  enabled =
+      base::FeatureList::IsEnabled(kAutocompleteControllerMetricsOptimization);
+}
+
 // TODO(manukh): Enabled by default in m120. Clean up 12/5 when after m121
 //   branch cut.
 // static
diff --git a/components/omnibox/common/omnibox_feature_configs.h b/components/omnibox/common/omnibox_feature_configs.h
index fb3b3777..b8c3569 100644
--- a/components/omnibox/common/omnibox_feature_configs.h
+++ b/components/omnibox/common/omnibox_feature_configs.h
@@ -104,6 +104,14 @@
 
 // Add new configs below, ordered alphabetically.
 
+// If enabled, use more efficient codepaths when capturing autocomplete metrics.
+struct AutocompleteControllerMetricsOptimization
+    : Config<AutocompleteControllerMetricsOptimization> {
+  DECLARE_FEATURE(kAutocompleteControllerMetricsOptimization);
+  AutocompleteControllerMetricsOptimization();
+  bool enabled;
+};
+
 // If enabled, adds recent calc suggestions.
 struct CalcProvider : Config<CalcProvider> {
   DECLARE_FEATURE(kCalcProvider);
diff --git a/components/optimization_guide/core/BUILD.gn b/components/optimization_guide/core/BUILD.gn
index e7ddf75..3e61523 100644
--- a/components/optimization_guide/core/BUILD.gn
+++ b/components/optimization_guide/core/BUILD.gn
@@ -473,6 +473,8 @@
       "model_execution/test/request_builder.h",
       "model_execution/test/response_holder.cc",
       "model_execution/test/response_holder.h",
+      "model_execution/test/substitution_builder.cc",
+      "model_execution/test/substitution_builder.h",
       "model_execution/test/test_on_device_model_component_state_manager.cc",
       "model_execution/test/test_on_device_model_component_state_manager.h",
     ]
diff --git a/components/optimization_guide/core/model_execution/multimodal_message.cc b/components/optimization_guide/core/model_execution/multimodal_message.cc
index 7cf9c89..b173c8b 100644
--- a/components/optimization_guide/core/model_execution/multimodal_message.cc
+++ b/components/optimization_guide/core/model_execution/multimodal_message.cc
@@ -51,6 +51,14 @@
 
 MultimodalMessageEditView::~MultimodalMessageEditView() = default;
 
+void MultimodalMessageEditView::MarkPending(int tag, bool pending) {
+  if (pending) {
+    overlay_->pending.insert(tag);
+  } else {
+    overlay_->pending.erase(tag);
+  }
+}
+
 void MultimodalMessageEditView::Set(int tag, const std::string& v) {
   ProtoStatus status = SetProtoField(&message_.get(), tag, v);
   CHECK_EQ(status, ProtoStatus::kOk);
@@ -97,6 +105,28 @@
 
 MultimodalMessageReadView::~MultimodalMessageReadView() = default;
 
+bool MultimodalMessageReadView::IsPending(
+    const proto::ProtoField& proto_field) const {
+  std::optional<MultimodalMessageReadView> parent = *this;
+  for (int i = 0; i < proto_field.proto_descriptors_size() - 1; i++) {
+    if (!parent->overlay_) {
+      return false;
+    }
+    int32_t tag = proto_field.proto_descriptors(i).tag_number();
+    if (parent->overlay_->pending.contains(tag)) {
+      return true;
+    }
+    parent = parent->GetNested(proto_field.proto_descriptors(i).tag_number());
+    if (!parent) {
+      return false;
+    }
+  }
+  int32_t tag =
+      proto_field.proto_descriptors(proto_field.proto_descriptors_size() - 1)
+          .tag_number();
+  return parent->overlay_ && parent->overlay_->pending.contains(tag);
+}
+
 // Get the type of multimodal content for a field.
 MultimodalType MultimodalMessageReadView::GetMultimodalType(
     const proto::ProtoField& proto_field) const {
@@ -255,6 +285,10 @@
   return MultimodalMessageEditView(*message, overlay_->overlays[n]);
 }
 
+void RepeatedMultimodalMessageEditView::MarkIncomplete(bool incomplete) {
+  overlay_->incomplete = incomplete;
+}
+
 RepeatedMultimodalMessageReadView::RepeatedMultimodalMessageReadView(
     const MessageLite& parent,
     int32_t tag,
@@ -280,6 +314,10 @@
   return MultimodalMessageReadView(*message, &overlay_->overlays[n]);
 }
 
+bool RepeatedMultimodalMessageReadView::IsIncomplete() const {
+  return overlay_ && overlay_->incomplete;
+}
+
 MultimodalMessage::MultimodalMessage() = default;
 MultimodalMessage::MultimodalMessage(const MessageLite& initial_message)
     : message_(initial_message.New()) {
diff --git a/components/optimization_guide/core/model_execution/multimodal_message.h b/components/optimization_guide/core/model_execution/multimodal_message.h
index 4a56337..2af2320 100644
--- a/components/optimization_guide/core/model_execution/multimodal_message.h
+++ b/components/optimization_guide/core/model_execution/multimodal_message.h
@@ -27,6 +27,7 @@
 #include <cstddef>
 #include <map>
 #include <optional>
+#include <set>
 #include <vector>
 
 #include "base/memory/raw_ptr.h"
@@ -56,6 +57,9 @@
   MultimodalMessageData& operator=(MultimodalMessageData&&);
   ~MultimodalMessageData();
 
+  // Which fields are currently marked pending.
+  std::set<int> pending;
+
   // Images stored for fields of the message.
   std::map<int, SkBitmap> images;
 
@@ -89,6 +93,9 @@
   // effectively have no overlay, and default initialized values can be created
   // for them lazily.
   std::vector<MultimodalMessageData> overlays;
+
+  // If this field is expected to have more data added later.
+  bool incomplete = false;
 };
 
 // A mutable view of a MultimodalMessage (or a submessage of it).
@@ -101,6 +108,10 @@
                             MultimodalMessageData& overlay);
   ~MultimodalMessageEditView();
 
+  // Marks a field as pending (or clears it if pending = false).
+  // Substitutions will truncate where they would depend on this field.
+  void MarkPending(int tag, bool pending = true);
+
   // Sets a string field value.
   void Set(int tag, const std::string& v);
 
@@ -150,6 +161,9 @@
     return std::string(message_->GetTypeName());
   }
 
+  // Returns true iff the field or any parent has been marked pending.
+  bool IsPending(const proto::ProtoField& proto_field) const;
+
   // Get the type of multimodal content for a field.
   MultimodalType GetMultimodalType(const proto::ProtoField& proto_field) const;
 
@@ -199,6 +213,11 @@
   // Get a previously added message.
   MultimodalMessageEditView Get(int n);
 
+  // Indicate that more content may be appended to this field later.
+  // Substitutions will truncate where they would depend on whether more
+  // messages are present.
+  void MarkIncomplete(bool incomplete = true);
+
  private:
   // The underlying message that owns the repeated field.
   const raw_ref<google::protobuf::MessageLite> parent_;
@@ -227,6 +246,9 @@
   // Will crash on out of bounds access.
   MultimodalMessageReadView Get(int n) const;
 
+  // Return whether this list was marked incomplete.
+  bool IsIncomplete() const;
+
  private:
   // The underlying message that owns the repeated field.
   const raw_ref<const google::protobuf::MessageLite> parent_;
diff --git a/components/optimization_guide/core/model_execution/substitution.cc b/components/optimization_guide/core/model_execution/substitution.cc
index 4d4a743..b12907b 100644
--- a/components/optimization_guide/core/model_execution/substitution.cc
+++ b/components/optimization_guide/core/model_execution/substitution.cc
@@ -45,51 +45,64 @@
   int offset = 0;
 };
 
+enum class ConditionResult { kFalse, kTrue, kStop };
+
+ConditionResult AsResult(bool value) {
+  return value ? ConditionResult::kTrue : ConditionResult::kFalse;
+}
+
 // Returns whether `condition` applies based on `message`.
-bool EvaluateCondition(const ResolutionContext& ctx,
-                       const proto::Condition& condition) {
+ConditionResult EvaluateCondition(const ResolutionContext& ctx,
+                                  const proto::Condition& condition) {
+  if (ctx.view.IsPending(condition.proto_field())) {
+    return ConditionResult::kStop;
+  }
   std::optional<proto::Value> proto_value =
       ctx.view.GetValue(condition.proto_field());
   if (!proto_value) {
-    return false;
+    return AsResult(false);
   }
 
   switch (condition.operator_type()) {
     case proto::OPERATOR_TYPE_EQUAL_TO:
-      return AreValuesEqual(*proto_value, condition.value());
+      return AsResult(AreValuesEqual(*proto_value, condition.value()));
     case proto::OPERATOR_TYPE_NOT_EQUAL_TO:
-      return !AreValuesEqual(*proto_value, condition.value());
+      return AsResult(!AreValuesEqual(*proto_value, condition.value()));
     default:
       base::debug::DumpWithoutCrashing();
-      return false;
+      return AsResult(false);
   }
 }
 
-bool AndConditions(const ResolutionContext& ctx,
-                   const RepeatedPtrField<proto::Condition>& conditions) {
+ConditionResult AndConditions(
+    const ResolutionContext& ctx,
+    const RepeatedPtrField<proto::Condition>& conditions) {
   for (const auto& condition : conditions) {
-    if (!EvaluateCondition(ctx, condition)) {
-      return false;
+    ConditionResult result = EvaluateCondition(ctx, condition);
+    if (result != ConditionResult::kTrue) {
+      return result;
     }
   }
-  return true;
+  return ConditionResult::kTrue;
 }
 
-bool OrConditions(const ResolutionContext& ctx,
-                  const RepeatedPtrField<proto::Condition>& conditions) {
+ConditionResult OrConditions(
+    const ResolutionContext& ctx,
+    const RepeatedPtrField<proto::Condition>& conditions) {
   for (const auto& condition : conditions) {
-    if (EvaluateCondition(ctx, condition)) {
-      return true;
+    ConditionResult result = EvaluateCondition(ctx, condition);
+    if (result != ConditionResult::kFalse) {
+      return result;
     }
   }
-  return false;
+  return ConditionResult::kFalse;
 }
 
 // Returns whether `conditions` apply based on `message`.
-bool DoConditionsApply(const ResolutionContext& ctx,
-                       const proto::ConditionList& conditions) {
+ConditionResult DoConditionsApply(const ResolutionContext& ctx,
+                                  const proto::ConditionList& conditions) {
   if (conditions.conditions_size() == 0) {
-    return true;
+    return ConditionResult::kTrue;
   }
 
   switch (conditions.condition_evaluation_type()) {
@@ -99,7 +112,7 @@
       return AndConditions(ctx, conditions.conditions());
     default:
       base::debug::DumpWithoutCrashing();
-      return false;
+      return ConditionResult::kFalse;
   }
 }
 
@@ -109,8 +122,9 @@
 class InputBuilder final {
  public:
   enum class Error {
-    OK = 0,
-    FAILED = 1,
+    kOk = 0,
+    kFailed = 1,  // The config is not valid over this input.
+    kStop = 2,    // Terminate early due to a pending field.
   };
   InputBuilder() : out_(Input::New()) {}
   Error ResolveSubstitutedString(const ResolutionContext& ctx,
@@ -155,40 +169,49 @@
 InputBuilder::Error InputBuilder::ResolveProtoField(
     const ResolutionContext& ctx,
     const proto::ProtoField& field) {
+  if (ctx.view.IsPending(field)) {
+    return Error::kStop;
+  }
   std::optional<proto::Value> value = ctx.view.GetValue(field);
   if (!value) {
     DVLOG(1) << "Invalid proto field of " << ctx.view.GetTypeName();
-    return Error::FAILED;
+    return Error::kFailed;
   }
   AddString(GetStringFromValue(*value));
-  return Error::OK;
+  return Error::kOk;
 }
 
 InputBuilder::Error InputBuilder::ResolveRangeExpr(
     const ResolutionContext& ctx,
     const proto::RangeExpr& expr) {
+  if (ctx.view.IsPending(expr.proto_field())) {
+    return Error::kStop;
+  }
   auto repeated = ctx.view.GetRepeated(expr.proto_field());
   if (!repeated) {
     DVLOG(1) << "Invalid proto field for RangeExpr over "
              << ctx.view.GetTypeName();
-    return Error::FAILED;
+    return Error::kFailed;
   }
   int repeated_size = repeated->Size();
   for (int i = 0; i < repeated_size; i++) {
     Error error = ResolveSubstitutedString(
         ResolutionContext{repeated->Get(i), i}, expr.expr());
-    if (error != Error::OK) {
+    if (error != Error::kOk) {
       return error;
     }
   }
-  return Error::OK;
+  if (repeated->IsIncomplete()) {
+    return Error::kStop;
+  }
+  return Error::kOk;
 }
 
 InputBuilder::Error InputBuilder::ResolveIndexExpr(
     const ResolutionContext& ctx,
     const proto::IndexExpr& expr) {
   AddString(base::NumberToString(ctx.offset + expr.one_based()));
-  return Error::OK;
+  return Error::kOk;
 }
 
 InputBuilder::Error InputBuilder::ResolveControlToken(
@@ -208,24 +231,27 @@
       AddToken(ml::Token::kEnd);
       break;
     default:
-      return Error::FAILED;
+      return Error::kFailed;
   }
-  return Error::OK;
+  return Error::kOk;
 }
 
 InputBuilder::Error InputBuilder::ResolveMediaField(
     const ResolutionContext& ctx,
     proto::MediaField field) {
+  if (ctx.view.IsPending(field.proto_field())) {
+    return Error::kStop;
+  }
   MultimodalType mtype = ctx.view.GetMultimodalType(field.proto_field());
   switch (mtype) {
     case MultimodalType::kAudio:
       out_->pieces.emplace_back(*ctx.view.GetAudio(field.proto_field()));
-      return Error::OK;
+      return Error::kOk;
     case MultimodalType::kImage:
       out_->pieces.emplace_back(*ctx.view.GetImage(field.proto_field()));
-      return Error::OK;
+      return Error::kOk;
     case MultimodalType::kNone:
-      return Error::OK;
+      return Error::kOk;
   }
 }
 
@@ -235,7 +261,7 @@
   switch (candidate.arg_case()) {
     case proto::StringArg::kRawString:
       AddString(candidate.raw_string());
-      return Error::OK;
+      return Error::kOk;
     case proto::StringArg::kProtoField:
       return ResolveProtoField(ctx, candidate.proto_field());
     case proto::StringArg::kRangeExpr:
@@ -248,7 +274,7 @@
       return ResolveMediaField(ctx, candidate.media_field());
     case proto::StringArg::ARG_NOT_SET:
       DVLOG(1) << "StringArg is incomplete.";
-      return Error::FAILED;
+      return Error::kFailed;
   }
 }
 
@@ -256,18 +282,28 @@
     const ResolutionContext& ctx,
     const proto::StringSubstitution& arg) {
   for (const auto& candidate : arg.candidates()) {
-    if (DoConditionsApply(ctx, candidate.conditions())) {
-      return ResolveStringArg(ctx, candidate);
+    switch (DoConditionsApply(ctx, candidate.conditions())) {
+      case ConditionResult::kFalse:
+        continue;
+      case ConditionResult::kStop:
+        return Error::kStop;
+      case ConditionResult::kTrue:
+        return ResolveStringArg(ctx, candidate);
     }
   }
-  return Error::OK;
+  return Error::kOk;
 }
 
 InputBuilder::Error InputBuilder::ResolveSubstitutedString(
     const ResolutionContext& ctx,
     const proto::SubstitutedString& substitution) {
-  if (!DoConditionsApply(ctx, substitution.conditions())) {
-    return Error::OK;
+  switch (DoConditionsApply(ctx, substitution.conditions())) {
+    case ConditionResult::kFalse:
+      return Error::kOk;
+    case ConditionResult::kStop:
+      return Error::kStop;
+    case ConditionResult::kTrue:
+      break;
   }
   if (substitution.should_ignore_input_context()) {
     should_ignore_input_context_ = true;
@@ -286,15 +322,15 @@
     }
     if (token != "%s") {
       DVLOG(1) << "Invalid Token";
-      return Error::FAILED;  // Invalid token
+      return Error::kFailed;  // Invalid token
     }
     if (substitution_idx >= substitution.substitutions_size()) {
       DVLOG(1) << "Too many substitutions";
-      return Error::FAILED;
+      return Error::kFailed;
     }
     Error error =
         ResolveSubstitution(ctx, substitution.substitutions(substitution_idx));
-    if (error != Error::OK) {
+    if (error != Error::kOk) {
       return error;
     }
     ++substitution_idx;
@@ -302,9 +338,9 @@
   AddString(templ.substr(template_idx, std::string_view::npos));
   if (substitution_idx != substitution.substitutions_size()) {
     DVLOG(1) << "Missing substitutions";
-    return Error::FAILED;
+    return Error::kFailed;
   }
-  return Error::OK;
+  return Error::kOk;
 }
 
 // Placeholder strings for a control token in MQLS logs / display.
@@ -359,7 +395,10 @@
   for (const auto& substitution : config_substitutions) {
     auto error = builder.ResolveSubstitutedString(ResolutionContext{request, 0},
                                                   substitution);
-    if (error != InputBuilder::Error::OK) {
+    if (error == InputBuilder::Error::kStop) {
+      break;
+    }
+    if (error != InputBuilder::Error::kOk) {
       return std::nullopt;
     }
   }
diff --git a/components/optimization_guide/core/model_execution/substitution_unittest.cc b/components/optimization_guide/core/model_execution/substitution_unittest.cc
index d3c937a..e455b0a9 100644
--- a/components/optimization_guide/core/model_execution/substitution_unittest.cc
+++ b/components/optimization_guide/core/model_execution/substitution_unittest.cc
@@ -14,6 +14,7 @@
 #include "components/optimization_guide/core/model_execution/on_device_model_execution_proto_descriptors.h"
 #include "components/optimization_guide/core/model_execution/test/feature_config_builder.h"
 #include "components/optimization_guide/core/model_execution/test/request_builder.h"
+#include "components/optimization_guide/core/model_execution/test/substitution_builder.h"
 #include "components/optimization_guide/proto/descriptors.pb.h"
 #include "components/optimization_guide/proto/features/compose.pb.h"
 #include "components/optimization_guide/proto/features/example_for_testing.pb.h"
@@ -584,6 +585,140 @@
   EXPECT_EQ(result->input->pieces.size(), 0u);
 }
 
+Substitutions BlockCheck(proto::StringSubstitution sub) {
+  Substitutions result;
+  auto* expr1 = result.Add();
+  expr1->set_string_template("%s for...%s...%s");
+  expr1->add_substitutions()->add_candidates()->set_raw_string("waiting");
+  *expr1->add_substitutions() = std::move(sub);
+  expr1->add_substitutions()->add_candidates()->set_raw_string("complete");
+  return result;
+}
+
+TEST_F(SubstitutionTest, BlockOnPendingField) {
+  using RequestProto = ::optimization_guide::proto::ExampleForTestingRequest;
+
+  Substitutions substitutions = BlockCheck(
+      Always(StringArg(ProtoField({RequestProto::kStringValueFieldNumber}))));
+
+  MultimodalMessage request{proto::ExampleForTestingRequest()};
+  request.edit().MarkPending(RequestProto::kStringValueFieldNumber);
+
+  std::optional<SubstitutionResult> result =
+      CreateSubstitutions(request.read(), substitutions);
+  ASSERT_TRUE(result.has_value());
+  EXPECT_EQ(result->ToString(), "waiting for...");
+}
+
+TEST_F(SubstitutionTest, BlockOnPendingMediaField) {
+  using RequestProto = ::optimization_guide::proto::ExampleForTestingRequest;
+  using Msg = ::optimization_guide::proto::ExampleForTestingMessage;
+
+  Substitutions substitutions = BlockCheck(Always(MediaFieldArg(
+      {RequestProto::kNested1FieldNumber, Msg::kMediaFieldNumber})));
+
+  MultimodalMessage request{proto::ExampleForTestingRequest()};
+  request.edit().MarkPending(RequestProto::kNested1FieldNumber);
+
+  std::optional<SubstitutionResult> result =
+      CreateSubstitutions(request.read(), substitutions);
+  ASSERT_TRUE(result.has_value());
+  EXPECT_EQ(result->ToString(), "waiting for...");
+}
+
+TEST_F(SubstitutionTest, BlockOnPendingFieldInCondition) {
+  using RequestProto = ::optimization_guide::proto::ExampleForTestingRequest;
+  using Msg = ::optimization_guide::proto::ExampleForTestingMessage;
+
+  Substitutions substitutions = BlockCheck(
+      Candidates({EnumCase(ProtoField({RequestProto::kEnumValueFieldNumber}),
+                           Msg::VALUE0, StringArg("maybe_value")),
+                  StringArg("fallback_value")}));
+
+  MultimodalMessage request{proto::ExampleForTestingRequest()};
+  request.edit().MarkPending(RequestProto::kEnumValueFieldNumber);
+
+  std::optional<SubstitutionResult> result =
+      CreateSubstitutions(request.read(), substitutions);
+  ASSERT_TRUE(result.has_value());
+  EXPECT_EQ(result->ToString(), "waiting for...");
+}
+
+Substitutions BlockCheckRepeatedSubstitution() {
+  using RequestProto = ::optimization_guide::proto::ExampleForTestingRequest;
+  using Msg = ::optimization_guide::proto::ExampleForTestingMessage;
+  return BlockCheck(Always(RangeExprArg(
+      ProtoField({RequestProto::kRepeatedFieldFieldNumber}),
+      Just(StringArg(ProtoField({Msg::kStringValueFieldNumber}))))));
+}
+
+MultimodalMessage RepeatedMessage() {
+  using RequestProto = ::optimization_guide::proto::ExampleForTestingRequest;
+  using Msg = ::optimization_guide::proto::ExampleForTestingMessage;
+  MultimodalMessage msg{proto::ExampleForTestingRequest()};
+  msg.edit()
+      .MutableRepeatedField(RequestProto::kRepeatedFieldFieldNumber)
+      .Add()
+      .Set(Msg::kStringValueFieldNumber, "A");
+  msg.edit()
+      .MutableRepeatedField(RequestProto::kRepeatedFieldFieldNumber)
+      .Add()
+      .Set(Msg::kStringValueFieldNumber, "B");
+  msg.edit()
+      .MutableRepeatedField(RequestProto::kRepeatedFieldFieldNumber)
+      .Add()
+      .Set(Msg::kStringValueFieldNumber, "C");
+  return msg;
+}
+
+TEST_F(SubstitutionTest, BlockOnPendingRepeated) {
+  using RequestProto = ::optimization_guide::proto::ExampleForTestingRequest;
+
+  Substitutions substitutions = BlockCheckRepeatedSubstitution();
+
+  MultimodalMessage request = RepeatedMessage();
+  request.edit().MarkPending(RequestProto::kRepeatedFieldFieldNumber);
+
+  std::optional<SubstitutionResult> result =
+      CreateSubstitutions(request.read(), substitutions);
+  ASSERT_TRUE(result.has_value());
+  EXPECT_EQ(result->ToString(), "waiting for...");
+}
+
+TEST_F(SubstitutionTest, BlockOnPendingFieldInRepeated) {
+  using RequestProto = ::optimization_guide::proto::ExampleForTestingRequest;
+  using Msg = ::optimization_guide::proto::ExampleForTestingMessage;
+
+  Substitutions substitutions = BlockCheckRepeatedSubstitution();
+
+  MultimodalMessage request = RepeatedMessage();
+  request.edit()
+      .MutableRepeatedField(RequestProto::kRepeatedFieldFieldNumber)
+      .Get(1)
+      .MarkPending(Msg::kStringValueFieldNumber);
+
+  std::optional<SubstitutionResult> result =
+      CreateSubstitutions(request.read(), substitutions);
+  ASSERT_TRUE(result.has_value());
+  EXPECT_EQ(result->ToString(), "waiting for...A");
+}
+
+TEST_F(SubstitutionTest, BlockOnIncompleteRepeated) {
+  using RequestProto = ::optimization_guide::proto::ExampleForTestingRequest;
+
+  Substitutions substitutions = BlockCheckRepeatedSubstitution();
+
+  MultimodalMessage request = RepeatedMessage();
+  request.edit()
+      .MutableRepeatedField(RequestProto::kRepeatedFieldFieldNumber)
+      .MarkIncomplete(true);
+
+  std::optional<SubstitutionResult> result =
+      CreateSubstitutions(request.read(), substitutions);
+  ASSERT_TRUE(result.has_value());
+  EXPECT_EQ(result->ToString(), "waiting for...ABC");
+}
+
 }  // namespace
 
 }  // namespace optimization_guide
diff --git a/components/optimization_guide/core/model_execution/test/feature_config_builder.cc b/components/optimization_guide/core/model_execution/test/feature_config_builder.cc
index 401c6b7..d35fa2d 100644
--- a/components/optimization_guide/core/model_execution/test/feature_config_builder.cc
+++ b/components/optimization_guide/core/model_execution/test/feature_config_builder.cc
@@ -8,6 +8,7 @@
 
 #include "base/strings/string_util.h"
 #include "components/optimization_guide/core/model_execution/feature_keys.h"
+#include "components/optimization_guide/core/model_execution/test/substitution_builder.h"
 #include "components/optimization_guide/proto/descriptors.pb.h"
 #include "components/optimization_guide/proto/features/compose.pb.h"
 #include "components/optimization_guide/proto/features/example_for_testing.pb.h"
@@ -32,14 +33,6 @@
   return result;
 }
 
-proto::ProtoField ProtoField(std::initializer_list<int32_t> tags) {
-  proto::ProtoField f;
-  for (int32_t tag : tags) {
-    f.add_proto_descriptors()->set_tag_number(tag);
-  }
-  return f;
-}
-
 proto::ProtoField PageUrlField() {
   return ProtoField({3, 1});
 }
@@ -60,32 +53,6 @@
   return ProtoField({1});
 }
 
-proto::RangeExpr RangeExpr(proto::ProtoField repeated_field,
-                           proto::SubstitutedString expr) {
-  proto::RangeExpr result;
-  *result.mutable_proto_field() = std::move(repeated_field);
-  *result.mutable_expr() = std::move(expr);
-  return result;
-}
-
-proto::SubstitutedString FieldSubstitution(const std::string& tmpl,
-                                           proto::ProtoField field) {
-  proto::SubstitutedString result;
-  result.set_string_template(tmpl);
-  *result.add_substitutions()->add_candidates()->mutable_proto_field() =
-      std::move(field);
-  return result;
-}
-
-proto::SubstitutedString ForEachSubstitution(proto::ProtoField repeated_field,
-                                             proto::SubstitutedString expr) {
-  proto::SubstitutedString result;
-  result.set_string_template("%s");
-  *result.add_substitutions()->add_candidates()->mutable_range_expr() =
-      RangeExpr(std::move(repeated_field), std::move(expr));
-  return result;
-}
-
 proto::SubstitutedString PageUrlSubstitution() {
   return FieldSubstitution("url: %s", PageUrlField());
 }
diff --git a/components/optimization_guide/core/model_execution/test/feature_config_builder.h b/components/optimization_guide/core/model_execution/test/feature_config_builder.h
index a86a430..fc0bba98 100644
--- a/components/optimization_guide/core/model_execution/test/feature_config_builder.h
+++ b/components/optimization_guide/core/model_execution/test/feature_config_builder.h
@@ -9,6 +9,7 @@
 #include <optional>
 #include <string>
 
+#include "components/optimization_guide/core/model_execution/test/substitution_builder.h"
 #include "components/optimization_guide/proto/descriptors.pb.h"
 #include "components/optimization_guide/proto/features/example_for_testing.pb.h"
 #include "components/optimization_guide/proto/on_device_model_execution_config.pb.h"
@@ -26,9 +27,6 @@
 // FakeOnDeviceModel::ClassifyTextSafety.
 proto::SafetyCategoryThreshold RequireReasonable();
 
-// Construct a ProtoField with the given tags.
-proto::ProtoField ProtoField(std::initializer_list<int32_t> tags);
-
 // Reference ComposeRequest::page_metadata.page_url
 proto::ProtoField PageUrlField();
 
@@ -44,18 +42,6 @@
 // Reference StringValue::value
 proto::ProtoField StringValueField();
 
-// Construct a RangeExpr.
-proto::RangeExpr RangeExpr(proto::ProtoField repeated_field,
-                           proto::SubstitutedString expr);
-
-// Make Substitution putting 'field' in 'tmpl'.
-proto::SubstitutedString FieldSubstitution(const std::string& tmpl,
-                                           proto::ProtoField field);
-
-// Make a Substitution that formats a repeated field.
-proto::SubstitutedString ForEachSubstitution(proto::ProtoField repeated_field,
-                                             proto::SubstitutedString expr);
-
 // Make a template for "url: {page_url}".
 proto::SubstitutedString PageUrlSubstitution();
 
@@ -110,18 +96,6 @@
   return cfg;
 }
 
-inline auto Int32Proto(int32_t value) {
-  proto::Value v;
-  v.set_int32_value(value);
-  return v;
-}
-
-inline auto Int64Proto(int64_t value) {
-  proto::Value v;
-  v.set_int64_value(value);
-  return v;
-}
-
 // Construct an InputConfig that formats a proto::ExampleForTestingRequest.
 proto::OnDeviceModelExecutionInputConfig TestInputConfig(
     proto::SubstitutedString context_template,
diff --git a/components/optimization_guide/core/model_execution/test/substitution_builder.cc b/components/optimization_guide/core/model_execution/test/substitution_builder.cc
new file mode 100644
index 0000000..3ed0bcd
--- /dev/null
+++ b/components/optimization_guide/core/model_execution/test/substitution_builder.cc
@@ -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.
+
+#include "components/optimization_guide/core/model_execution/test/substitution_builder.h"
+
+#include "components/optimization_guide/proto/substitution.pb.h"
+
+namespace optimization_guide {
+
+proto::ProtoField ProtoField(std::initializer_list<int32_t> tags) {
+  proto::ProtoField f;
+  for (int32_t tag : tags) {
+    f.add_proto_descriptors()->set_tag_number(tag);
+  }
+  return f;
+}
+
+proto::StringSubstitution Candidates(
+    std::initializer_list<proto::StringArg> candidates) {
+  proto::StringSubstitution result;
+  for (auto& candidate : candidates) {
+    *result.add_candidates() = candidate;
+  }
+  return result;
+}
+
+proto::ConditionList All(std::initializer_list<proto::Condition> conditions) {
+  proto::ConditionList result;
+  result.set_condition_evaluation_type(proto::CONDITION_EVALUATION_TYPE_AND);
+  for (auto& condition : conditions) {
+    *result.add_conditions() = condition;
+  }
+  return result;
+}
+
+proto::ConditionList Any(std::initializer_list<proto::Condition> conditions) {
+  proto::ConditionList result;
+  result.set_condition_evaluation_type(proto::CONDITION_EVALUATION_TYPE_OR);
+  for (auto& condition : conditions) {
+    *result.add_conditions() = condition;
+  }
+  return result;
+}
+
+proto::SubstitutedString FieldSubstitution(const std::string& tmpl,
+                                           proto::ProtoField field) {
+  proto::SubstitutedString result;
+  result.set_string_template(tmpl);
+  *result.add_substitutions()->add_candidates()->mutable_proto_field() =
+      std::move(field);
+  return result;
+}
+
+proto::SubstitutedString ForEachSubstitution(proto::ProtoField repeated_field,
+                                             proto::SubstitutedString expr) {
+  proto::SubstitutedString result;
+  result.set_string_template("%s");
+  *result.add_substitutions()->add_candidates() =
+      RangeExprArg(std::move(repeated_field), std::move(expr));
+  return result;
+}
+
+}  // namespace optimization_guide
diff --git a/components/optimization_guide/core/model_execution/test/substitution_builder.h b/components/optimization_guide/core/model_execution/test/substitution_builder.h
new file mode 100644
index 0000000..0ca40ec
--- /dev/null
+++ b/components/optimization_guide/core/model_execution/test/substitution_builder.h
@@ -0,0 +1,160 @@
+// 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_OPTIMIZATION_GUIDE_CORE_MODEL_EXECUTION_TEST_SUBSTITUTION_BUILDER_H_
+#define COMPONENTS_OPTIMIZATION_GUIDE_CORE_MODEL_EXECUTION_TEST_SUBSTITUTION_BUILDER_H_
+
+#include <initializer_list>
+#include <optional>
+#include <string>
+#include <type_traits>
+
+#include "components/optimization_guide/proto/descriptors.pb.h"
+#include "components/optimization_guide/proto/substitution.pb.h"
+
+namespace optimization_guide {
+
+// Construct a ProtoField with the given tags.
+proto::ProtoField ProtoField(std::initializer_list<int32_t> tags);
+
+inline proto::Value StrProto(std::string value) {
+  proto::Value v;
+  v.set_string_value(std::move(value));
+  return v;
+}
+
+inline proto::Value Int32Proto(int32_t value) {
+  proto::Value v;
+  v.set_int32_value(value);
+  return v;
+}
+
+inline proto::Value Int64Proto(int64_t value) {
+  proto::Value v;
+  v.set_int64_value(value);
+  return v;
+}
+
+inline proto::Value BoolProto(bool value) {
+  proto::Value v;
+  v.set_boolean_value(value);
+  return v;
+}
+
+inline proto::Condition Eq(proto::ProtoField field, proto::Value value) {
+  proto::Condition result;
+  *result.mutable_proto_field() = std::move(field);
+  result.set_operator_type(proto::OPERATOR_TYPE_EQUAL_TO);
+  *result.mutable_value() = std::move(value);
+  return result;
+}
+
+inline proto::Condition Neq(proto::ProtoField field, proto::Value value) {
+  proto::Condition result;
+  *result.mutable_proto_field() = std::move(field);
+  result.set_operator_type(proto::OPERATOR_TYPE_NOT_EQUAL_TO);
+  *result.mutable_value() = std::move(value);
+  return result;
+}
+
+// Construct AND-joined conditions.
+proto::ConditionList All(std::initializer_list<proto::Condition>);
+
+// Construct OR-joined conditions.
+proto::ConditionList Any(std::initializer_list<proto::Condition>);
+
+inline proto::StringArg StringArg(std::string raw_string) {
+  proto::StringArg result;
+  result.set_raw_string(std::move(raw_string));
+  return result;
+}
+
+inline proto::StringArg StringArg(proto::ProtoField field) {
+  proto::StringArg result;
+  *result.mutable_proto_field() = std::move(field);
+  return result;
+}
+
+inline proto::StringArg StringArg(proto::ControlToken token) {
+  proto::StringArg result;
+  result.set_control_token(token);
+  return result;
+}
+
+inline proto::StringArg RangeExprArg(proto::ProtoField repeated_field,
+                                     proto::SubstitutedString expr) {
+  proto::StringArg result;
+  *result.mutable_range_expr()->mutable_proto_field() =
+      std::move(repeated_field);
+  *result.mutable_range_expr()->mutable_expr() = std::move(expr);
+  return result;
+}
+
+// Construct an IndexExpr in a StringArg.
+inline proto::StringArg IndexExprArg(bool one_based) {
+  proto::StringArg result;
+  result.mutable_index_expr()->set_one_based(one_based);
+  return result;
+}
+
+// Construct an MediaField in a StringArg.
+inline proto::StringArg MediaFieldArg(std::initializer_list<int32_t> tags) {
+  proto::StringArg result;
+  *result.mutable_media_field()->mutable_proto_field() = ProtoField(tags);
+  return result;
+}
+
+// Add a condition to a StringArg.
+inline proto::StringArg If(proto::ConditionList conditions,
+                           proto::StringArg arg) {
+  *arg.mutable_conditions() = std::move(conditions);
+  return arg;
+}
+
+// If(field=value) -> Arg
+template <typename Enum>
+  requires(std::is_enum_v<Enum>)
+proto::StringArg EnumCase(proto::ProtoField field,
+                          Enum value,
+                          proto::StringArg arg) {
+  return If(All({Eq(std::move(field), Int32Proto(value))}), std::move(arg));
+}
+
+// A StringSubstitution with only one candidate.
+inline proto::StringSubstitution Always(proto::StringArg arg) {
+  proto::StringSubstitution result;
+  *result.add_candidates() = arg;
+  return result;
+}
+
+// A SubstitutedString that contains one element.
+inline proto::SubstitutedString Just(proto::StringSubstitution sub) {
+  proto::SubstitutedString result;
+  result.set_string_template("%s");
+  *result.add_substitutions() = std::move(sub);
+  return result;
+}
+
+inline proto::SubstitutedString Just(proto::StringArg arg) {
+  return Just(Always(arg));
+}
+
+// Constructs a StringSubstitution object with provided candidates.
+proto::StringSubstitution Candidates(
+    std::initializer_list<proto::StringArg> candidates);
+
+proto::SubstitutedString Concatenated(
+    std::initializer_list<proto::StringSubstitution> substitutions);
+
+// Make Substitution putting 'field' in 'tmpl'.
+proto::SubstitutedString FieldSubstitution(const std::string& tmpl,
+                                           proto::ProtoField field);
+
+// Make a Substitution that formats a repeated field.
+proto::SubstitutedString ForEachSubstitution(proto::ProtoField repeated_field,
+                                             proto::SubstitutedString expr);
+
+}  // namespace optimization_guide
+
+#endif  // COMPONENTS_OPTIMIZATION_GUIDE_CORE_MODEL_EXECUTION_TEST_SUBSTITUTION_BUILDER_H_
diff --git a/components/optimization_guide/internal b/components/optimization_guide/internal
index 6032e32..3633af32 160000
--- a/components/optimization_guide/internal
+++ b/components/optimization_guide/internal
@@ -1 +1 @@
-Subproject commit 6032e32fa8b82048023e47709f1e5a22dd277791
+Subproject commit 3633af3203b28054ee2d0aafd69b7a687dce4d1c
diff --git a/components/payments/content/android/java/src/org/chromium/components/payments/AndroidPaymentApp.java b/components/payments/content/android/java/src/org/chromium/components/payments/AndroidPaymentApp.java
index 5213b3482..f7ae74c 100644
--- a/components/payments/content/android/java/src/org/chromium/components/payments/AndroidPaymentApp.java
+++ b/components/payments/content/android/java/src/org/chromium/components/payments/AndroidPaymentApp.java
@@ -209,8 +209,10 @@
 
         Intent isReadyToPayIntent =
                 WebPaymentIntentHelper.createIsReadyToPayIntent(
-                        /* packageName= */ mPackageName,
-                        /* serviceName= */ mIsReadyToPayServiceName,
+                        /* callerPackageName= */ ContextUtils.getApplicationContext()
+                                .getPackageName(),
+                        /* paymentAppPackageName= */ mPackageName,
+                        /* paymentAppServiceName= */ mIsReadyToPayServiceName,
                         removeUrlScheme(origin),
                         removeUrlScheme(iframeOrigin),
                         certificateChain,
@@ -389,7 +391,10 @@
                     new PaymentDetailsUpdateConnection(
                             ContextUtils.getApplicationContext(),
                             WebPaymentIntentHelper.createPaymentDetailsUpdateServiceIntent(
-                                    mPackageName, mPaymentDetailsUpdateServiceName),
+                                    /* callerPackageName= */ ContextUtils.getApplicationContext()
+                                            .getPackageName(),
+                                    mPackageName,
+                                    mPaymentDetailsUpdateServiceName),
                             new PaymentDetailsUpdateService().getBinder());
             mPaymentDetailsUpdateConnection.connectToService();
         }
diff --git a/components/payments/content/android/java/src/org/chromium/components/payments/intent/WebPaymentIntentHelper.java b/components/payments/content/android/java/src/org/chromium/components/payments/intent/WebPaymentIntentHelper.java
index b8171373..8f8dc46 100644
--- a/components/payments/content/android/java/src/org/chromium/components/payments/intent/WebPaymentIntentHelper.java
+++ b/components/payments/content/android/java/src/org/chromium/components/payments/intent/WebPaymentIntentHelper.java
@@ -64,6 +64,7 @@
     public static final String EXTRA_PAYMENT_OPTIONS_REQUEST_SHIPPING = "requestShipping";
     public static final String EXTRA_PAYMENT_OPTIONS_SHIPPING_TYPE = "shippingType";
     public static final String EXTRA_SHIPPING_OPTIONS = "shippingOptions";
+    public static final String EXTRA_CALLER_PACKAGE_NAME = "packageName";
 
     // Deprecated parameters sent to the payment app for backward compatibility.
     // TODO(crbug.com/40849135): Remove these parameters.
@@ -238,36 +239,36 @@
      * Create an intent to invoke a native payment app. This method throws IllegalArgumentException
      * for invalid arguments.
      *
-     * @param packageName The name of the package of the payment app. Only non-empty string is
-     *         allowed.
-     * @param activityName The name of the payment activity in the payment app. Only non-empty
-     *         string is allowed.
+     * @param paymentAppPackageName The name of the package of the payment app. Only non-empty
+     *     string is allowed.
+     * @param paymentAppActivityName The name of the payment activity in the payment app. Only
+     *     non-empty string is allowed.
      * @param id The unique identifier of the PaymentRequest. Only non-empty string is allowed.
      * @param merchantName The name of the merchant. Cannot be null..
      * @param schemelessOrigin The schemeless origin of this merchant. Only non-empty string is
-     *         allowed.
+     *     allowed.
      * @param schemelessIframeOrigin The schemeless origin of the iframe that invoked
-     *         PaymentRequest. Only non-empty string is allowed.
+     *     PaymentRequest. Only non-empty string is allowed.
      * @param certificateChain The site certificate chain of the merchant. Can be null when
-     *         ANDROID_PAYMENT_INTENTS_OMIT_DEPRECATED_PARAMETERS is enabled or for localhost or
-     *         local file, which are secure contexts without SSL. Each byte array cannot be null.
+     *     ANDROID_PAYMENT_INTENTS_OMIT_DEPRECATED_PARAMETERS is enabled or for localhost or local
+     *     file, which are secure contexts without SSL. Each byte array cannot be null.
      * @param methodDataMap The payment-method specific data for all applicable payment methods,
-     *         e.g., whether the app should be invoked in test or production, a merchant identifier,
-     *         or a public key. The map and its values cannot be null. The map should have at
-     *         least one entry.
+     *     e.g., whether the app should be invoked in test or production, a merchant identifier, or
+     *     a public key. The map and its values cannot be null. The map should have at least one
+     *     entry.
      * @param total The total amount. Cannot be null..
      * @param displayItems The shopping cart items. OK to be null.
      * @param modifiers The relevant payment details modifiers. OK to be null.
      * @param paymentOptions The relevant merchant requested payment options. OK to be null.
      * @param shippingOptions Merchant specified available shipping options. Should be non-empty
-     *          when paymentOptions.requestShipping is true.
+     *     when paymentOptions.requestShipping is true.
      * @param removeDeprecatedFields Whether the deprecated fields should be omitted from the
-     *          intent.
+     *     intent.
      * @return The intent to invoke the payment app.
      */
     public static Intent createPayIntent(
-            String packageName,
-            String activityName,
+            String paymentAppPackageName,
+            String paymentAppActivityName,
             String id,
             String merchantName,
             String schemelessOrigin,
@@ -281,9 +282,9 @@
             @Nullable List<PaymentShippingOption> shippingOptions,
             boolean removeDeprecatedFields) {
         Intent payIntent = new Intent();
-        checkStringNotEmpty(activityName, "activityName");
-        checkStringNotEmpty(packageName, "packageName");
-        payIntent.setClassName(packageName, activityName);
+        checkStringNotEmpty(paymentAppActivityName, "paymentAppActivityName");
+        checkStringNotEmpty(paymentAppPackageName, "paymentAppPackageName");
+        payIntent.setClassName(paymentAppPackageName, paymentAppActivityName);
         payIntent.setAction(ACTION_PAY);
         payIntent.putExtras(
                 buildPayIntentExtras(
@@ -306,27 +307,38 @@
      * Create an intent for the service that dynamically updates the payment details (e.g., total
      * price) based on user's payment method, shipping address, or shipping option.
      *
-     * @param packageName The name of the package of the payment app. Only non-empty string is
-     *     allowed.
-     * @param serviceName The name of the service. Only non-empty string is allowed.
+     * @param callerPackageName The name of the package of the calling code, e.g.,
+     *     "com.android.chrome".
+     * @param paymentAppPackageName The name of the package of the payment app. Only non-empty
+     *     string is allowed.
+     * @param paymentAppServiceName The name of the service. Only non-empty string is allowed.
      * @return The intent to invoke the service.
      */
     public static Intent createPaymentDetailsUpdateServiceIntent(
-            String packageName, String serviceName) {
+            String callerPackageName, String paymentAppPackageName, String paymentAppServiceName) {
+        checkStringNotEmpty(callerPackageName, "callerPackageName");
+        checkStringNotEmpty(paymentAppPackageName, "paymentAppPackageName");
+        checkStringNotEmpty(paymentAppServiceName, "paymentAppServiceName");
+
         Intent intent = new Intent();
-        checkStringNotEmpty(packageName, "packageName");
-        checkStringNotEmpty(serviceName, "serviceName");
-        intent.setClassName(packageName, serviceName);
+        intent.setClassName(paymentAppPackageName, paymentAppServiceName);
         intent.setAction(ACTION_UPDATE_PAYMENT_DETAILS);
+
+        Bundle extras = new Bundle();
+        extras.putString(EXTRA_CALLER_PACKAGE_NAME, callerPackageName);
+        intent.putExtras(extras);
+
         return intent;
     }
 
     /**
      * Create an intent to invoke a service that can answer "is ready to pay" query.
      *
-     * @param packageName The name of the package of the payment app. Only non-empty string is
-     *     allowed.
-     * @param serviceName The name of the service. Only non-empty string is allowed.
+     * @param callerPackageName The name of the package of the calling code, e.g.,
+     *     "com.android.chrome".
+     * @param paymentAppPackageName The name of the package of the payment app. Only non-empty
+     *     string is allowed.
+     * @param paymentAppServiceName The name of the service. Only non-empty string is allowed.
      * @param schemelessOrigin The schemeless origin of this merchant. Only non-empty string is
      *     allowed.
      * @param schemelessIframeOrigin The schemeless origin of the iframe that invoked
@@ -344,8 +356,9 @@
      * @return The intent to invoke the service.
      */
     public static Intent createIsReadyToPayIntent(
-            String packageName,
-            String serviceName,
+            String callerPackageName,
+            String paymentAppPackageName,
+            String paymentAppServiceName,
             String schemelessOrigin,
             String schemelessIframeOrigin,
             byte @Nullable [][] certificateChain,
@@ -353,11 +366,13 @@
             boolean clearIdFields,
             boolean removeDeprecatedFields) {
         Intent isReadyToPayIntent = new Intent();
-        checkStringNotEmpty(serviceName, "serviceName");
-        checkStringNotEmpty(packageName, "packageName");
-        isReadyToPayIntent.setClassName(packageName, serviceName);
+        checkStringNotEmpty(callerPackageName, "callerPackageName");
+        checkStringNotEmpty(paymentAppServiceName, "paymentAppServiceName");
+        checkStringNotEmpty(paymentAppPackageName, "paymentAppPackageName");
+        isReadyToPayIntent.setClassName(paymentAppPackageName, paymentAppServiceName);
         isReadyToPayIntent.setAction(ACTION_IS_READY_TO_PAY);
         Bundle extras = new Bundle();
+        extras.putString(EXTRA_CALLER_PACKAGE_NAME, callerPackageName);
         if (!clearIdFields) {
             addCommonExtrasWithIdentity(
                     schemelessOrigin,
@@ -401,6 +416,7 @@
             @Nullable List<PaymentShippingOption> shippingOptions,
             boolean removeDeprecatedFields) {
         Bundle extras = new Bundle();
+
         checkStringNotEmpty(id, "id");
         extras.putString(EXTRA_PAYMENT_REQUEST_ID, id);
 
diff --git a/content/browser/indexed_db/instance/bucket_context.cc b/content/browser/indexed_db/instance/bucket_context.cc
index e517d5c..92bc6ac 100644
--- a/content/browser/indexed_db/instance/bucket_context.cc
+++ b/content/browser/indexed_db/instance/bucket_context.cc
@@ -26,6 +26,7 @@
 #include "base/check_op.h"
 #include "base/containers/contains.h"
 #include "base/containers/map_util.h"
+#include "base/feature_list.h"
 #include "base/files/file_path.h"
 #include "base/files/file_util.h"
 #include "base/functional/bind.h"
@@ -67,6 +68,7 @@
 #include "content/browser/indexed_db/instance/database_callbacks.h"
 #include "content/browser/indexed_db/instance/leveldb/backing_store.h"
 #include "content/browser/indexed_db/instance/pending_connection.h"
+#include "content/browser/indexed_db/instance/sqlite/backing_store_impl.h"
 #include "content/browser/indexed_db/status.h"
 #include "mojo/public/cpp/bindings/pending_associated_receiver.h"
 #include "mojo/public/cpp/bindings/pending_associated_remote.h"
@@ -194,6 +196,11 @@
 
 }  // namespace
 
+// TODO(crbug.com/40253999): Move to blink when needed there.
+BASE_FEATURE(kSqliteBackingStore,
+             "IdbSqliteBackingStore",
+             base::FEATURE_DISABLED_BY_DEFAULT);
+
 BucketContext::Delegate::Delegate()
     : on_ready_for_destruction(base::DoNothing()),
       on_receiver_bounced(base::DoNothing()),
@@ -876,6 +883,11 @@
   return sanitized_message;
 }
 
+bool BucketContext::ShouldUseSqliteBackingStore() {
+  // Additional checks may be added subsequently.
+  return base::FeatureList::IsEnabled(kSqliteBackingStore);
+}
+
 void BucketContext::HandleBackingStoreCorruption(
     const std::string& error_message) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
@@ -962,9 +974,11 @@
   for (int i = 0; i < kNumOpenTries; ++i) {
     const bool is_first_attempt = i == 0;
     std::tie(backing_store, status, data_loss_info, disk_full) =
-        level_db::BackingStore::OpenAndVerify(
-            *this, data_path_, database_path, blob_path, lock_manager.get(),
-            is_first_attempt, create_if_missing);
+        ShouldUseSqliteBackingStore()
+            ? sqlite::BackingStoreImpl::OpenAndVerify(data_path_)
+            : level_db::BackingStore::OpenAndVerify(
+                  *this, data_path_, database_path, blob_path,
+                  lock_manager.get(), is_first_attempt, create_if_missing);
     if (is_first_attempt) [[likely]] {
       first_try_status = status;
     }
diff --git a/content/browser/indexed_db/instance/bucket_context.h b/content/browser/indexed_db/instance/bucket_context.h
index 15c5034..6586d31 100644
--- a/content/browser/indexed_db/instance/bucket_context.h
+++ b/content/browser/indexed_db/instance/bucket_context.h
@@ -382,11 +382,7 @@
 
   std::string SanitizeErrorMessage(const std::string& message);
 
-  // This only exists to ease the transition to a swappable backing store. It
-  // should be removed.
-  level_db::BackingStore* leveldb_backing_store() {
-    return reinterpret_cast<level_db::BackingStore*>(backing_store_.get());
-  }
+  bool ShouldUseSqliteBackingStore();
 
   SEQUENCE_CHECKER(sequence_checker_);
 
diff --git a/content/gpu/BUILD.gn b/content/gpu/BUILD.gn
index 5a5b7c3e..c6a90ab 100644
--- a/content/gpu/BUILD.gn
+++ b/content/gpu/BUILD.gn
@@ -99,6 +99,7 @@
     deps += [
       "//components/tracing:graphics_provider",
       "//media",
+      "//sandbox/linux:sandbox_services",
     ]
   }
 
diff --git a/content/gpu/gpu_main.cc b/content/gpu/gpu_main.cc
index cadb96f..73be627 100644
--- a/content/gpu/gpu_main.cc
+++ b/content/gpu/gpu_main.cc
@@ -12,6 +12,7 @@
 #include "base/allocator/partition_alloc_support.h"
 #include "base/check.h"
 #include "base/command_line.h"
+#include "base/files/scoped_file.h"
 #include "base/feature_list.h"
 #include "base/functional/bind.h"
 #include "base/memory/raw_ptr.h"
@@ -85,8 +86,13 @@
 
 #if BUILDFLAG(IS_ANDROID)
 #include "base/android/meminfo_dump_provider.h"
+#include "base/posix/eintr_wrapper.h"
 #include "base/trace_event/memory_dump_manager.h"
 #include "components/tracing/common/graphics_memory_dump_provider_android.h"
+#include "sandbox/linux/services/thread_helpers.h" // nogncheck
+#include "sandbox/policy/features.h"
+#include "sandbox/policy/linux/landlock_gpu_policy_android.h"
+#include "sandbox/policy/sandbox_type.h"
 #endif
 
 #if BUILDFLAG(IS_WIN)
@@ -123,6 +129,8 @@
 bool StartSandboxLinux(gpu::GpuWatchdogThread*,
                        const gpu::GPUInfo*,
                        const gpu::GpuPreferences&);
+#elif BUILDFLAG(IS_ANDROID)
+bool StartSandboxAndroid(gpu::GpuWatchdogThread*);
 #elif BUILDFLAG(IS_WIN)
 bool StartSandboxWindows(const sandbox::SandboxInterfaceInfo*);
 #endif
@@ -185,6 +193,12 @@
     return StartSandboxWindows(sandbox_info_);
 #elif BUILDFLAG(IS_MAC)
     return sandbox::Seatbelt::IsSandboxed();
+#elif BUILDFLAG(IS_ANDROID)
+    if (base::FeatureList::IsEnabled(
+            sandbox::policy::features::kAndroidGpuSandbox)) {
+      return StartSandboxAndroid(watchdog_thread);
+    }
+    return false;
 #else
     return false;
 #endif
@@ -523,6 +537,29 @@
 }
 #endif  // BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
 
+#if BUILDFLAG(IS_ANDROID)
+bool StartSandboxAndroid(gpu::GpuWatchdogThread* watchdog_thread) {
+  if (watchdog_thread) {
+    // Stop the watchdog thread temporarily.
+    base::ScopedFD proc_fd(
+        HANDLE_EINTR(open("/proc", O_DIRECTORY | O_RDONLY | O_CLOEXEC)));
+
+    sandbox::ThreadHelpers::StopThreadAndWatchProcFS(proc_fd.get(),
+                                                     watchdog_thread);
+  }
+
+  bool res = sandbox::landlock::ApplyLandlock(
+      sandbox::policy::SandboxTypeFromCommandLine(
+          *base::CommandLine::ForCurrentProcess()));
+
+  if (watchdog_thread) {
+    watchdog_thread->Start();
+  }
+
+  return res;
+}
+#endif  // BUILDFLAG(IS_ANDROID)
+
 #if BUILDFLAG(IS_WIN)
 bool StartSandboxWindows(const sandbox::SandboxInterfaceInfo* sandbox_info) {
   GPU_STARTUP_TRACE_EVENT("Lower token");
diff --git a/content/test/gpu/gpu_tests/test_expectations/expected_color_expectations.txt b/content/test/gpu/gpu_tests/test_expectations/expected_color_expectations.txt
index e63345d..44764738 100644
--- a/content/test/gpu/gpu_tests/test_expectations/expected_color_expectations.txt
+++ b/content/test/gpu/gpu_tests/test_expectations/expected_color_expectations.txt
@@ -117,6 +117,10 @@
 # Longstanding flaking test - page reported failure
 crbug.com/40877266 [ android android-shield-android-tv ] ExpectedColor_MediaRecorderFromVideoElement [ RetryOnFailure ]
 
+# Flakes on M1 machines when upgrading to MacOS 15.4.
+crbug.com/416712848 [ sequoia apple-angle-metal-renderer:-apple-m1 ] ExpectedColor_MediaRecorderFrom2DCanvas [ RetryOnFailure ]
+crbug.com/416712848 [ sequoia apple-angle-metal-renderer:-apple-m1 ] ExpectedColor_maps [ RetryOnFailure ]
+
 
 #######################################################################
 # Automated Entries After This Point - Do Not Manually Add Below Here #
diff --git a/extensions/browser/service_worker/service_worker_task_queue.cc b/extensions/browser/service_worker/service_worker_task_queue.cc
index 0c296f5b..a9aa97f2 100644
--- a/extensions/browser/service_worker/service_worker_task_queue.cc
+++ b/extensions/browser/service_worker/service_worker_task_queue.cc
@@ -287,19 +287,8 @@
                               service_worker_version_id, thread_id};
   WorkerState* worker_state = GetWorkerState(context_id);
   DCHECK(worker_state);
-  // If |worker_state| had a worker running previously, for which we didn't
-  // see DidStopServiceWorkerContext notification (typically happens on render
-  // process shutdown), then we'd preserve stale state in |renderer_state_|.
-  //
-  // This isn't a problem because the next browser process readiness
-  // (DidStartWorkerForScope) or the next renderer process readiness
-  // (DidStartServiceWorkerContext) will clear the state, whichever happens
-  // first.
-  //
-  // TODO(lazyboy): Update the renderer state in RenderProcessExited() and
-  // uncomment the following DCHECK:
-  // DCHECK_NE(RendererState::kActive, worker_state->renderer_state_)
-  //    << "Worker already started";
+  DCHECK_NE(RendererState::kActive, worker_state->renderer_state())
+      << "Worker already started";
   worker_state->SetWorkerId(worker_id, ProcessManager::Get(browser_context_));
   worker_state->SetRendererState(RendererState::kActive);
 
@@ -319,9 +308,7 @@
   WorkerState* worker_state = GetWorkerState(context_id);
   // If the extension is still activated, worker state should still exist.
   CHECK(worker_state);
-
-  worker_state->SetRendererState(RendererState::kNotActive);
-  worker_state->ResetWorkerId();
+  worker_state->Reset();
 }
 
 void ServiceWorkerTaskQueue::DidStopServiceWorkerContext(
@@ -353,8 +340,7 @@
   }
 
   DCHECK_NE(RendererState::kNotActive, worker_state->renderer_state());
-  worker_state->SetRendererState(RendererState::kNotActive);
-  worker_state->ResetWorkerId();
+  worker_state->Reset();
 
   if (g_test_observer) {
     g_test_observer->DidStopServiceWorkerContext(extension_id);
@@ -569,13 +555,7 @@
   // registered for subscopes via `navigation.serviceWorker.register()` rather
   // than being declared in the manifest's background section are not allowed
   // to use extensions API, and should be ignored here. See crbug.com/395536907.
-  // NOTE: We may have already reset worker ID and renderer state, but not
-  // browser state, if `DidStopServiceWorkerContext()` was called first.
-  // In that case, we reset browser state here.
-  bool has_already_reset_renderer_state =
-      !worker_state->worker_id() &&
-      worker_state->renderer_state() == RendererState::kNotActive;
-  if (has_already_reset_renderer_state ||
+  if (worker_state->worker_id() &&
       worker_state->worker_id()->version_id == version_id) {
     // Stop tracking the worker for extension API purposes.
     ProcessManager::Get(browser_context_)
@@ -584,9 +564,7 @@
     // stops, a new instance must start before the worker can be considered
     // ready to receive tasks/events again and the renderer stop notifications
     // are not 100% reliable.
-    worker_state->SetBrowserState(BrowserState::kInitial);
-    worker_state->SetRendererState(RendererState::kNotActive);
-    worker_state->ResetWorkerId();
+    worker_state->Reset();
   }
 
   if (g_test_observer) {
diff --git a/extensions/browser/service_worker/service_worker_task_queue.h b/extensions/browser/service_worker/service_worker_task_queue.h
index a892a08..4ee0444 100644
--- a/extensions/browser/service_worker/service_worker_task_queue.h
+++ b/extensions/browser/service_worker/service_worker_task_queue.h
@@ -185,13 +185,17 @@
 
     void SetWorkerId(const WorkerId& worker_id,
                      ProcessManager* process_manager);
-    void ResetWorkerId() { worker_id_.reset(); }
     void SetBrowserState(BrowserState browser_state) {
       browser_state_ = browser_state;
     }
     void SetRendererState(RendererState renderer_state) {
       renderer_state_ = renderer_state;
     }
+    void Reset() {
+      worker_id_.reset();
+      browser_state_ = BrowserState::kInitial;
+      renderer_state_ = RendererState::kNotActive;
+    }
 
     bool ready() const;
 
diff --git a/gpu/config/gpu_driver_bug_list.json b/gpu/config/gpu_driver_bug_list.json
index 1c53f7f1..0bb9cc6 100644
--- a/gpu/config/gpu_driver_bug_list.json
+++ b/gpu/config/gpu_driver_bug_list.json
@@ -3445,6 +3445,21 @@
       "features": [
         "force_rgb10a2_overlay_support"
       ]
+    },
+    {
+      "id": 443,
+      "cr_bugs": [385039726],
+      "description": "Disable DComp texture support on older Windows versions",
+      "os": {
+        "type": "win",
+        "version": {
+          "op": "<",
+          "value": "10.0.26100.3624"
+        }
+      },
+      "features": [
+        "disable_dcomp_texture"
+      ]
     }
   ]
 }
diff --git a/gpu/config/gpu_workaround_list.txt b/gpu/config/gpu_workaround_list.txt
index 6ff6822..5b969607 100644
--- a/gpu/config/gpu_workaround_list.txt
+++ b/gpu/config/gpu_workaround_list.txt
@@ -22,6 +22,7 @@
 disable_d3d11_update_subresource1
 disable_d3d11_video_decoder
 disable_d3d11_vp9_ksvc_decoding
+disable_dcomp_texture
 disable_decode_swap_chain
 disable_depth_texture
 disable_direct_composition_sw_video_overlays
diff --git a/gpu/ipc/service/gpu_init.cc b/gpu/ipc/service/gpu_init.cc
index 59f4341..d30729c 100644
--- a/gpu/ipc/service/gpu_init.cc
+++ b/gpu/ipc/service/gpu_init.cc
@@ -139,7 +139,10 @@
           gpu::FORCE_RGB10A2_OVERLAY_SUPPORT),
       .check_ycbcr_studio_g22_left_p709_for_nv12_support =
           gpu_feature_info.IsWorkaroundEnabled(
-              gpu::CHECK_YCBCR_STUDIO_G22_LEFT_P709_FOR_NV12_SUPPORT)};
+              gpu::CHECK_YCBCR_STUDIO_G22_LEFT_P709_FOR_NV12_SUPPORT),
+      .disable_dcomp_texture =
+          gpu_feature_info.IsWorkaroundEnabled(gpu::DISABLE_DCOMP_TEXTURE),
+  };
   SetDirectCompositionOverlayWorkarounds(workarounds);
 
   DCHECK(gpu_info);
diff --git a/ios/chrome/browser/authentication/ui_bundled/signin/signin_utils.mm b/ios/chrome/browser/authentication/ui_bundled/signin/signin_utils.mm
index 3c7777cf..d37a2df 100644
--- a/ios/chrome/browser/authentication/ui_bundled/signin/signin_utils.mm
+++ b/ios/chrome/browser/authentication/ui_bundled/signin/signin_utils.mm
@@ -284,33 +284,13 @@
     return false;
   }
 
-  if (IsFullscreenSigninPromoManagerMigrationEnabled()) {
-    feature_engagement::Tracker* tracker =
-        feature_engagement::TrackerFactory::GetForProfile(profile);
-    unsigned int interactions = 0;
-    std::vector<std::pair<feature_engagement::EventConfig, int>> events =
-        tracker->ListEvents(
-            feature_engagement::kIPHiOSPromoSigninFullscreenFeature);
-    for (const auto& event : events) {
-      if (event.first.name ==
-          feature_engagement::events::kIOSSigninFullscreenPromoTrigger) {
-        interactions = event.second;
-        break;
-      }
-    }
-
-    if (interactions <= 1) {
-      return true;
-    }
-
-  } else {
-    // The sign-in promo should be shown twice, even if no account has been
-    // added.
-    NSInteger display_count =
-        [defaults integerForKey:kSigninPromoViewDisplayCountKey];
-    if (display_count <= 1) {
-      return true;
-    }
+  // TODO(crbug.com/416634715): Replace user defaults interaction count with FET
+  // event count. The sign-in promo should be shown twice, even if no account
+  // has been added.
+  NSInteger display_count =
+      [defaults integerForKey:kSigninPromoViewDisplayCountKey];
+  if (display_count <= 1) {
+    return true;
   }
 
   // Otherwise, it can be shown only if a new account has been added.
diff --git a/ios/chrome/browser/push_notification/model/push_notification_delegate.mm b/ios/chrome/browser/push_notification/model/push_notification_delegate.mm
index 8d106ec..6ae19061 100644
--- a/ios/chrome/browser/push_notification/model/push_notification_delegate.mm
+++ b/ios/chrome/browser/push_notification/model/push_notification_delegate.mm
@@ -1160,6 +1160,14 @@
   CHECK(sceneState);
   Browser* browser =
       sceneState.browserProviderInterface.mainBrowserProvider.browser;
+  ProfileIOS* profile = browser->GetProfile();
+
+  // Get the clientID for the client that is associated with this notification.
+  PushNotificationClient* client = [self clientForNotification:notification
+                                                       profile:profile];
+  std::optional<PushNotificationClientId> clientID =
+      (client) ? std::make_optional(client->GetClientId()) : std::nullopt;
+
   CommandDispatcher* dispatcher = browser->GetCommandDispatcher();
   id<ApplicationCommands> applicationHandler =
       HandlerForProtocol(dispatcher, ApplicationCommands);
@@ -1167,11 +1175,31 @@
       HandlerForProtocol(dispatcher, SettingsCommands);
   __block base::OnceClosure completion2 = std::move(completion);
   [applicationHandler prepareToPresentModal:^{
-    [settingsHandler showNotificationsSettings];
+    [settingsHandler showNotificationsSettingsAndHighlightClient:clientID];
     std::move(completion2).Run();
   }];
 }
 
+// Returns the client to handle the given `notification`. The client can be
+// either profile-scoped (and associated with the given `profile`), or
+// app-scoped.
+- (PushNotificationClient*)clientForNotification:(UNNotification*)notification
+                                         profile:(ProfileIOS*)profile {
+  if (notification == nil) {
+    return nullptr;
+  }
+  PushNotificationClient* client = nullptr;
+  if (IsMultiProfilePushNotificationHandlingEnabled()) {
+    PushNotificationClientManager* clientManager =
+        GetClientManagerForProfile(profile);
+    client = clientManager->GetClientForNotification(notification);
+  }
+  if (!client) {
+    client = self.appWideClientManager->GetClientForNotification(notification);
+  }
+  return client;
+}
+
 // Returns the `SceneState` matching the notification response's target scene,
 // if any.
 - (SceneState*)notificationTargetSceneStateForResponse:
diff --git a/ios/chrome/browser/settings/ui_bundled/notifications/notifications_banner_view_controller.mm b/ios/chrome/browser/settings/ui_bundled/notifications/notifications_banner_view_controller.mm
index 596de1e0..f093d24b 100644
--- a/ios/chrome/browser/settings/ui_bundled/notifications/notifications_banner_view_controller.mm
+++ b/ios/chrome/browser/settings/ui_bundled/notifications/notifications_banner_view_controller.mm
@@ -79,6 +79,8 @@
     TableViewHeaderFooterItem* tipsNotificationsFooterItem;
 // All the items for the send tab notifications section received by mediator.
 @property(nonatomic, strong) TableViewSwitchItem* sendTabNotificationsItem;
+// The item with this identifier will be visually highlighted.
+@property(nonatomic, assign) NotificationsItemIdentifier highlightedItem;
 
 @end
 
@@ -88,6 +90,7 @@
   UITableViewDiffableDataSource<NSNumber*, NSNumber*>* _dataSource;
   NSDiffableDataSourceSnapshot* _snapshot;
   ChromeTableViewStyler* _tableViewStyler;
+  ChromeTableViewStyler* _highlightTableViewStyler;
   // The `viewWillLayoutSubviews` is invoked on creation, dismissal, and
   // backward navigation of the NotificationsBannerViewController. To prevent
   // the view controller styling aspects of the view that will be carried over
@@ -190,7 +193,7 @@
       if (cell) {
         TableViewCell* tableViewCell =
             base::apple::ObjCCastStrict<TableViewCell>(cell);
-        [item configureCell:tableViewCell withStyler:[self tableViewStyler]];
+        [self configureCell:tableViewCell item:item identifier:itemIdentifier];
       }
     }
   }
@@ -328,6 +331,7 @@
       return self.safetyCheckItem;
     case ItemIdentifierSendTab:
       return self.sendTabNotificationsItem;
+    case ItemIdentifierNone:
     case ItemIdentifierTipsNotificationsFooter:
       NOTREACHED();
   }
@@ -368,7 +372,7 @@
       DequeueTableViewCell<TableViewSwitchCell>(tableView);
   TableViewSwitchItem* switchItem =
       base::apple::ObjCCastStrict<TableViewSwitchItem>(item);
-  [switchItem configureCell:cell withStyler:[self tableViewStyler]];
+  [self configureCell:cell item:switchItem identifier:itemIdentifier];
   cell.switchView.tag = itemIdentifier;
   [cell.switchView addTarget:self
                       action:@selector(switchAction:)
@@ -419,6 +423,16 @@
   return _tableViewStyler;
 }
 
+// Styler for highlighted cells.
+- (ChromeTableViewStyler*)highlightTableViewStyler {
+  if (!_highlightTableViewStyler) {
+    _highlightTableViewStyler = [[ChromeTableViewStyler alloc] init];
+    _highlightTableViewStyler.cellBackgroundColor =
+        [UIColor colorNamed:kBlueHaloColor];
+  }
+  return _highlightTableViewStyler;
+}
+
 // Configures the banner based on the view's size.
 - (void)configureBanner {
   if (IsCompactHeight(self.traitCollection) || TooNarrowForBanner(self.view)) {
@@ -438,4 +452,15 @@
       self.shouldHideBanner ? nil : UIColor.whiteColor;
 }
 
+// Configures the `cell` for the `item` with the given `identifier`. A styler
+// is chosed depending on whether the item should be highlighted or not.
+- (void)configureCell:(TableViewCell*)cell
+                 item:(TableViewItem*)item
+           identifier:(NotificationsItemIdentifier)identifier {
+  ChromeTableViewStyler* styler = identifier == self.highlightedItem
+                                      ? [self highlightTableViewStyler]
+                                      : [self tableViewStyler];
+  [item configureCell:cell withStyler:styler];
+}
+
 @end
diff --git a/ios/chrome/browser/settings/ui_bundled/notifications/notifications_consumer.h b/ios/chrome/browser/settings/ui_bundled/notifications/notifications_consumer.h
index 56bed502..c6ba132 100644
--- a/ios/chrome/browser/settings/ui_bundled/notifications/notifications_consumer.h
+++ b/ios/chrome/browser/settings/ui_bundled/notifications/notifications_consumer.h
@@ -7,6 +7,7 @@
 
 #import <UIKit/UIKit.h>
 
+#import "ios/chrome/browser/settings/ui_bundled/notifications/notifications_item_identifier.h"
 #import "ios/chrome/browser/shared/ui/table_view/legacy_chrome_table_view_consumer.h"
 
 @class TableViewItem;
@@ -34,6 +35,9 @@
 // Initializes the send tab notifications item.
 - (void)setSendTabNotificationsItem:(TableViewItem*)sendTabNotificationsItem;
 
+// Visually highlights the item with the given `identifier`.
+- (void)setHighlightedItem:(NotificationsItemIdentifier)identifier;
+
 // Called when an item is updated and needs to be reloaded.
 - (void)reloadData;
 
diff --git a/ios/chrome/browser/settings/ui_bundled/notifications/notifications_coordinator.h b/ios/chrome/browser/settings/ui_bundled/notifications/notifications_coordinator.h
index 96a74c3..67be279 100644
--- a/ios/chrome/browser/settings/ui_bundled/notifications/notifications_coordinator.h
+++ b/ios/chrome/browser/settings/ui_bundled/notifications/notifications_coordinator.h
@@ -38,6 +38,9 @@
 // Show Price Trackinhg Notifications settings.
 - (void)showTrackingPrice;
 
+// Visually highlights the table view row for the given `clientID`.
+- (void)highlightClient:(PushNotificationClientId)clientID;
+
 @end
 
 #endif  // IOS_CHROME_BROWSER_SETTINGS_UI_BUNDLED_NOTIFICATIONS_NOTIFICATIONS_COORDINATOR_H_
diff --git a/ios/chrome/browser/settings/ui_bundled/notifications/notifications_coordinator.mm b/ios/chrome/browser/settings/ui_bundled/notifications/notifications_coordinator.mm
index 5c8f08f2..87e2b60 100644
--- a/ios/chrome/browser/settings/ui_bundled/notifications/notifications_coordinator.mm
+++ b/ios/chrome/browser/settings/ui_bundled/notifications/notifications_coordinator.mm
@@ -119,6 +119,30 @@
   [_optInAlertCoordinator stop];
 }
 
+- (void)highlightClient:(PushNotificationClientId)clientID {
+  self.viewController.highlightedItem = [self itemForClient:clientID];
+  [self.viewController reloadData];
+}
+
+// Returns the table view item identifier for the given `clientID`.
+- (NotificationsItemIdentifier)itemForClient:
+    (PushNotificationClientId)clientID {
+  switch (clientID) {
+    case PushNotificationClientId::kCommerce:
+      return ItemIdentifierPriceTracking;
+    case PushNotificationClientId::kContent:
+    case PushNotificationClientId::kSports:
+      return ItemIdentifierContent;
+    case PushNotificationClientId::kTips:
+      return ItemIdentifierTips;
+    case PushNotificationClientId::kSafetyCheck:
+      return ItemIdentifierSafetyCheck;
+    case PushNotificationClientId::kSendTab:
+    case PushNotificationClientId::kReminders:
+      return ItemIdentifierSendTab;
+  }
+}
+
 #pragma mark - NotificationsAlertPresenter
 
 - (void)presentPushNotificationPermissionAlert {
diff --git a/ios/chrome/browser/settings/ui_bundled/notifications/notifications_item_identifier.h b/ios/chrome/browser/settings/ui_bundled/notifications/notifications_item_identifier.h
index 2bf0094de..faacd13 100644
--- a/ios/chrome/browser/settings/ui_bundled/notifications/notifications_item_identifier.h
+++ b/ios/chrome/browser/settings/ui_bundled/notifications/notifications_item_identifier.h
@@ -10,7 +10,8 @@
 // Enum representing the different types of notifications in the notifications
 // settings page.
 enum NotificationsItemIdentifier {
-  ItemIdentifierContent = kItemTypeEnumZero,
+  ItemIdentifierNone = kItemTypeEnumZero,
+  ItemIdentifierContent,
   ItemIdentifierTips,
   ItemIdentifierTipsNotificationsFooter,
   ItemIdentifierPriceTracking,
diff --git a/ios/chrome/browser/settings/ui_bundled/settings_navigation_controller.h b/ios/chrome/browser/settings/ui_bundled/settings_navigation_controller.h
index cf77aa9..12e7a3d0 100644
--- a/ios/chrome/browser/settings/ui_bundled/settings_navigation_controller.h
+++ b/ios/chrome/browser/settings/ui_bundled/settings_navigation_controller.h
@@ -251,6 +251,9 @@
 // `delegate` may be nil.
 + (instancetype)
     notificationsSettingsControllerForBrowser:(Browser*)browser
+                                       client:(std::optional<
+                                                  PushNotificationClientId>)
+                                                  clientID
                                      delegate:
                                          (id<SettingsNavigationControllerDelegate>)
                                              delegate;
diff --git a/ios/chrome/browser/settings/ui_bundled/settings_navigation_controller.mm b/ios/chrome/browser/settings/ui_bundled/settings_navigation_controller.mm
index a34e168..7d85d32 100644
--- a/ios/chrome/browser/settings/ui_bundled/settings_navigation_controller.mm
+++ b/ios/chrome/browser/settings/ui_bundled/settings_navigation_controller.mm
@@ -578,6 +578,9 @@
 
 + (instancetype)
     notificationsSettingsControllerForBrowser:(Browser*)browser
+                                       client:(std::optional<
+                                                  PushNotificationClientId>)
+                                                  clientID
                                      delegate:
                                          (id<SettingsNavigationControllerDelegate>)
                                              delegate {
@@ -586,7 +589,7 @@
           initWithRootViewController:nil
                              browser:browser
                             delegate:delegate];
-  [navigationController showNotificationsSettings];
+  [navigationController showNotificationsSettingsAndHighlightClient:clientID];
   return navigationController;
 }
 
@@ -1254,6 +1257,14 @@
   [self.notificationsCoordinator start];
 }
 
+- (void)showNotificationsSettingsAndHighlightClient:
+    (std::optional<PushNotificationClientId>)clientID {
+  [self showNotificationsSettings];
+  if (clientID.has_value()) {
+    [self.notificationsCoordinator highlightClient:clientID.value()];
+  }
+}
+
 - (void)showPriceNotificationsSettings {
   [self stopNotificationsCoordinator];
   self.notificationsCoordinator = [[NotificationsCoordinator alloc]
diff --git a/ios/chrome/browser/shared/coordinator/scene/scene_controller.mm b/ios/chrome/browser/shared/coordinator/scene/scene_controller.mm
index 2c426194..7612806 100644
--- a/ios/chrome/browser/shared/coordinator/scene/scene_controller.mm
+++ b/ios/chrome/browser/shared/coordinator/scene/scene_controller.mm
@@ -2826,15 +2826,22 @@
 }
 
 - (void)showNotificationsSettings {
+  [self showNotificationsSettingsAndHighlightClient:std::nullopt];
+}
+
+- (void)showNotificationsSettingsAndHighlightClient:
+    (std::optional<PushNotificationClientId>)clientID {
   UIViewController* baseViewController = self.currentInterface.viewController;
   if (self.settingsNavigationController) {
-    [self.settingsNavigationController showNotificationsSettings];
+    [self.settingsNavigationController
+        showNotificationsSettingsAndHighlightClient:clientID];
     return;
   }
 
   Browser* browser = self.mainInterface.browser;
   self.settingsNavigationController = [SettingsNavigationController
       notificationsSettingsControllerForBrowser:browser
+                                         client:clientID
                                        delegate:self];
   [baseViewController presentViewController:self.settingsNavigationController
                                    animated:YES
diff --git a/ios/chrome/browser/shared/public/commands/settings_commands.h b/ios/chrome/browser/shared/public/commands/settings_commands.h
index 9a8bff1f..51002de 100644
--- a/ios/chrome/browser/shared/public/commands/settings_commands.h
+++ b/ios/chrome/browser/shared/public/commands/settings_commands.h
@@ -7,6 +7,8 @@
 
 #import <UIKit/UIKit.h>
 
+#import <optional>
+
 namespace autofill {
 class AutofillProfile;
 class CreditCard;
@@ -16,6 +18,7 @@
 struct CredentialUIEntry;
 enum class PasswordCheckReferrer;
 }  // namespace password_manager
+enum class PushNotificationClientId;
 
 @protocol SettingsCommands
 
@@ -108,6 +111,11 @@
 // Shows the Notifications Settings page in the settings.
 - (void)showNotificationsSettings;
 
+// Shows the Notification Settings page and highlights the row for the push
+// notification client with the given `clientID`.
+- (void)showNotificationsSettingsAndHighlightClient:
+    (std::optional<PushNotificationClientId>)clientID;
+
 @end
 
 #endif  // IOS_CHROME_BROWSER_SHARED_PUBLIC_COMMANDS_SETTINGS_COMMANDS_H_
diff --git a/pdf/pdf_ink_module.cc b/pdf/pdf_ink_module.cc
index 0b80ebcf..77b5caa 100644
--- a/pdf/pdf_ink_module.cc
+++ b/pdf/pdf_ink_module.cc
@@ -78,14 +78,13 @@
     bool is_ink,
     std::vector<uint8_t> image_data,
     const gfx::Size& thumbnail_size) {
-  base::Value::Dict message;
-  message.Set("type", "updateInk2Thumbnail");
-  message.Set("pageNumber", page_index + 1);
-  message.Set("isInk", is_ink);
-  message.Set("imageData", std::move(image_data));
-  message.Set("width", thumbnail_size.width());
-  message.Set("height", thumbnail_size.height());
-  return message;
+  return base::Value::Dict()
+      .Set("type", "updateInk2Thumbnail")
+      .Set("pageNumber", page_index + 1)
+      .Set("isInk", is_ink)
+      .Set("imageData", std::move(image_data))
+      .Set("width", thumbnail_size.width())
+      .Set("height", thumbnail_size.height());
 }
 
 ink::StrokeInput::ToolType GetToolTypeFromTouchEvent(
@@ -304,9 +303,7 @@
 }
 
 void PdfInkModule::SendContentFocusedMessage() {
-  base::Value::Dict message;
-  message.Set("type", "contentFocused");
-  client_->PostMessage(std::move(message));
+  client_->PostMessage(base::Value::Dict().Set("type", "contentFocused"));
 }
 
 PdfInkModule::PageInkStrokeIterator PdfInkModule::GetVisibleStrokesIterator() {
@@ -1203,11 +1200,10 @@
   data.Set("size", ink_brush.GetSize());
 
   SkColor color = GetSkColorFromInkBrush(ink_brush);
-  base::Value::Dict color_reply;
-  color_reply.Set("r", static_cast<int>(SkColorGetR(color)));
-  color_reply.Set("g", static_cast<int>(SkColorGetG(color)));
-  color_reply.Set("b", static_cast<int>(SkColorGetB(color)));
-  data.Set("color", std::move(color_reply));
+  data.Set("color", base::Value::Dict()
+                        .Set("r", static_cast<int>(SkColorGetR(color)))
+                        .Set("g", static_cast<int>(SkColorGetG(color)))
+                        .Set("b", static_cast<int>(SkColorGetB(color))));
 
   reply.Set("data", std::move(data));
   client_->PostMessage(std::move(reply));
diff --git a/pdf/pdf_ink_module_unittest.cc b/pdf/pdf_ink_module_unittest.cc
index 67e30cc..1670ca6f 100644
--- a/pdf/pdf_ink_module_unittest.cc
+++ b/pdf/pdf_ink_module_unittest.cc
@@ -228,9 +228,9 @@
 
 base::Value::Dict CreateGetAnnotationBrushMessageForTesting(
     const std::string& brush_type) {
-  base::Value::Dict message;
-  message.Set("type", "getAnnotationBrush");
-  message.Set("messageId", "foo");
+  auto message = base::Value::Dict()
+                     .Set("type", "getAnnotationBrush")
+                     .Set("messageId", "foo");
   if (!brush_type.empty()) {
     message.Set("brushType", brush_type);
   }
@@ -415,6 +415,21 @@
   std::vector<gfx::Rect> invalidations_;
 };
 
+class PdfInkModuleMetricsTestBase {
+ protected:
+  static constexpr char kHighlighterColorMetric[] =
+      "PDF.Ink2StrokeHighlighterColor";
+  static constexpr char kHighlighterSizeMetric[] =
+      "PDF.Ink2StrokeHighlighterSize";
+  static constexpr char kInputDeviceMetric[] = "PDF.Ink2StrokeInputDeviceType";
+  static constexpr char kTypeMetric[] = "PDF.Ink2StrokeBrushType";
+
+  base::HistogramTester& histograms() { return histograms_; }
+
+ private:
+  base::HistogramTester histograms_;
+};
+
 class PdfInkModuleTest : public testing::TestWithParam<InkTestVariation> {
  public:
   void SetUp() override {
@@ -451,9 +466,8 @@
 }  // namespace
 
 TEST_P(PdfInkModuleTest, UnknownMessage) {
-  base::Value::Dict message;
-  message.Set("type", "nonInkMessage");
-  EXPECT_FALSE(ink_module().OnMessage(message));
+  EXPECT_FALSE(
+      ink_module().OnMessage(base::Value::Dict().Set("type", "nonInkMessage")));
 }
 
 // Verify that a get eraser message gets the eraser parameters.
@@ -2779,137 +2793,129 @@
                             {expected_page1_horz_line_input_batch.value()}))));
 }
 
-class PdfInkModuleMetricsTest : public PdfInkModuleUndoRedoTest {
+class PdfInkModuleMetricsTest : public PdfInkModuleMetricsTestBase,
+                                public PdfInkModuleUndoRedoTest {
  protected:
   static constexpr char kPenColorMetric[] = "PDF.Ink2StrokePenColor";
-  static constexpr char kHighlighterColorMetric[] =
-      "PDF.Ink2StrokeHighlighterColor";
-  static constexpr char kInputDeviceMetric[] = "PDF.Ink2StrokeInputDeviceType";
   static constexpr char kPenSizeMetric[] = "PDF.Ink2StrokePenSize";
-  static constexpr char kHighlighterSizeMetric[] =
-      "PDF.Ink2StrokeHighlighterSize";
-  static constexpr char kTypeMetric[] = "PDF.Ink2StrokeBrushType";
 };
 
 TEST_P(PdfInkModuleMetricsTest, StrokeUndoRedoDoesNotAffectMetrics) {
   InitializeSimpleSinglePageBasicLayout();
-  base::HistogramTester histograms;
 
   // Draw a pen stroke.
   RunStrokeCheckTest(/*annotation_mode_enabled=*/true);
 
-  histograms.ExpectUniqueSample(kTypeMetric, StrokeMetricBrushType::kPen, 1);
-  histograms.ExpectUniqueSample(kInputDeviceMetric,
-                                StrokeMetricInputDeviceType::kMouse, 1);
-  histograms.ExpectUniqueSample(kPenSizeMetric, StrokeMetricBrushSize::kMedium,
-                                1);
-  histograms.ExpectUniqueSample(kPenColorMetric, StrokeMetricPenColor::kBlack,
-                                1);
+  histograms().ExpectUniqueSample(kTypeMetric, StrokeMetricBrushType::kPen, 1);
+  histograms().ExpectUniqueSample(kInputDeviceMetric,
+                                  StrokeMetricInputDeviceType::kMouse, 1);
+  histograms().ExpectUniqueSample(kPenSizeMetric,
+                                  StrokeMetricBrushSize::kMedium, 1);
+  histograms().ExpectUniqueSample(kPenColorMetric, StrokeMetricPenColor::kBlack,
+                                  1);
 
   // Undo and redo.
   PerformUndo();
   PerformRedo();
 
   // The metrics should stay the same.
-  histograms.ExpectUniqueSample(kTypeMetric, StrokeMetricBrushType::kPen, 1);
-  histograms.ExpectUniqueSample(kInputDeviceMetric,
-                                StrokeMetricInputDeviceType::kMouse, 1);
-  histograms.ExpectUniqueSample(kPenSizeMetric, StrokeMetricBrushSize::kMedium,
-                                1);
-  histograms.ExpectUniqueSample(kPenColorMetric, StrokeMetricPenColor::kBlack,
-                                1);
+  histograms().ExpectUniqueSample(kTypeMetric, StrokeMetricBrushType::kPen, 1);
+  histograms().ExpectUniqueSample(kInputDeviceMetric,
+                                  StrokeMetricInputDeviceType::kMouse, 1);
+  histograms().ExpectUniqueSample(kPenSizeMetric,
+                                  StrokeMetricBrushSize::kMedium, 1);
+  histograms().ExpectUniqueSample(kPenColorMetric, StrokeMetricPenColor::kBlack,
+                                  1);
 }
 
 TEST_P(PdfInkModuleMetricsTest, StrokeBrushColorPen) {
   InitializeSimpleSinglePageBasicLayout();
-  base::HistogramTester histograms;
 
   RunStrokeCheckTest(/*annotation_mode_enabled=*/false);
 
-  histograms.ExpectTotalCount(kPenColorMetric, 0);
+  histograms().ExpectTotalCount(kPenColorMetric, 0);
 
   // Draw a stroke with the default black color.
   RunStrokeCheckTest(/*annotation_mode_enabled=*/true);
 
-  histograms.ExpectUniqueSample(kPenColorMetric, StrokeMetricPenColor::kBlack,
-                                1);
+  histograms().ExpectUniqueSample(kPenColorMetric, StrokeMetricPenColor::kBlack,
+                                  1);
 
   // Draw a stroke with "Red 1" color.
   TestAnnotationBrushMessageParams params = kRedBrushParams;
   SelectBrushTool(PdfInkBrush::Type::kPen, params);
   ApplyStrokeWithMouseAtMouseDownPoint();
 
-  histograms.ExpectBucketCount(kPenColorMetric, StrokeMetricPenColor::kRed1, 1);
-  histograms.ExpectTotalCount(kPenColorMetric, 2);
+  histograms().ExpectBucketCount(kPenColorMetric, StrokeMetricPenColor::kRed1,
+                                 1);
+  histograms().ExpectTotalCount(kPenColorMetric, 2);
 
   // Draw a stroke with "Tan 3" color.
   params.color = SkColorSetRGB(0x88, 0x59, 0x45);
   SelectBrushTool(PdfInkBrush::Type::kPen, params);
   ApplyStrokeWithMouseAtMouseDownPoint();
 
-  histograms.ExpectBucketCount(kPenColorMetric, StrokeMetricPenColor::kTan3, 1);
-  histograms.ExpectTotalCount(kPenColorMetric, 3);
-  histograms.ExpectTotalCount(kHighlighterColorMetric, 0);
+  histograms().ExpectBucketCount(kPenColorMetric, StrokeMetricPenColor::kTan3,
+                                 1);
+  histograms().ExpectTotalCount(kPenColorMetric, 3);
+  histograms().ExpectTotalCount(kHighlighterColorMetric, 0);
 }
 
 TEST_P(PdfInkModuleMetricsTest, StrokeBrushColorHighlighter) {
   EnableAnnotationMode();
   InitializeSimpleSinglePageBasicLayout();
-  base::HistogramTester histograms;
 
   // Draw a stroke with "Light Red" color.
   TestAnnotationBrushMessageParams params = kRedBrushParams;
   SelectBrushTool(PdfInkBrush::Type::kHighlighter, params);
   ApplyStrokeWithMouseAtMouseDownPoint();
 
-  histograms.ExpectBucketCount(kHighlighterColorMetric,
-                               StrokeMetricHighlighterColor::kLightRed, 1);
-  histograms.ExpectTotalCount(kHighlighterColorMetric, 1);
+  histograms().ExpectBucketCount(kHighlighterColorMetric,
+                                 StrokeMetricHighlighterColor::kLightRed, 1);
+  histograms().ExpectTotalCount(kHighlighterColorMetric, 1);
 
   // Draw a stroke with "Orange" color.
   params.color = SkColorSetRGB(0xFF, 0x63, 0x0C);
   SelectBrushTool(PdfInkBrush::Type::kHighlighter, params);
   ApplyStrokeWithMouseAtMouseDownPoint();
 
-  histograms.ExpectBucketCount(kHighlighterColorMetric,
-                               StrokeMetricHighlighterColor::kOrange, 1);
-  histograms.ExpectTotalCount(kHighlighterColorMetric, 2);
-  histograms.ExpectTotalCount(kPenColorMetric, 0);
+  histograms().ExpectBucketCount(kHighlighterColorMetric,
+                                 StrokeMetricHighlighterColor::kOrange, 1);
+  histograms().ExpectTotalCount(kHighlighterColorMetric, 2);
+  histograms().ExpectTotalCount(kPenColorMetric, 0);
 }
 
 TEST_P(PdfInkModuleMetricsTest, StrokeBrushSizePen) {
   InitializeSimpleSinglePageBasicLayout();
-  base::HistogramTester histograms;
 
   // Draw a stroke.
   RunStrokeCheckTest(/*annotation_mode_enabled=*/true);
 
-  histograms.ExpectUniqueSample(kPenSizeMetric, StrokeMetricBrushSize::kMedium,
-                                1);
+  histograms().ExpectUniqueSample(kPenSizeMetric,
+                                  StrokeMetricBrushSize::kMedium, 1);
 
   TestAnnotationBrushMessageParams params = {SkColorSetRGB(0xF2, 0x8B, 0x82),
                                              /*size=*/1.0};
   SelectBrushTool(PdfInkBrush::Type::kPen, params);
   ApplyStrokeWithMouseAtMouseDownPoint();
 
-  histograms.ExpectBucketCount(kPenSizeMetric,
-                               StrokeMetricBrushSize::kExtraThin, 1);
-  histograms.ExpectTotalCount(kPenSizeMetric, 2);
+  histograms().ExpectBucketCount(kPenSizeMetric,
+                                 StrokeMetricBrushSize::kExtraThin, 1);
+  histograms().ExpectTotalCount(kPenSizeMetric, 2);
 
   params.size = 8.0f;
   SelectBrushTool(PdfInkBrush::Type::kPen, params);
   ApplyStrokeWithMouseAtMouseDownPoint();
 
-  histograms.ExpectBucketCount(kPenSizeMetric,
-                               StrokeMetricBrushSize::kExtraThick, 1);
-  histograms.ExpectTotalCount(kPenSizeMetric, 3);
-  histograms.ExpectTotalCount(kHighlighterSizeMetric, 0);
+  histograms().ExpectBucketCount(kPenSizeMetric,
+                                 StrokeMetricBrushSize::kExtraThick, 1);
+  histograms().ExpectTotalCount(kPenSizeMetric, 3);
+  histograms().ExpectTotalCount(kHighlighterSizeMetric, 0);
 }
 
 TEST_P(PdfInkModuleMetricsTest, StrokeBrushSizeHighlighter) {
   EnableAnnotationMode();
   InitializeSimpleSinglePageBasicLayout();
-  base::HistogramTester histograms;
 
   // Draw a stroke with medium size.
   TestAnnotationBrushMessageParams params = {SkColorSetRGB(0xF2, 0x8B, 0x82),
@@ -2917,116 +2923,115 @@
   SelectBrushTool(PdfInkBrush::Type::kHighlighter, params);
   ApplyStrokeWithMouseAtMouseDownPoint();
 
-  histograms.ExpectUniqueSample(kHighlighterSizeMetric,
-                                StrokeMetricBrushSize::kMedium, 1);
+  histograms().ExpectUniqueSample(kHighlighterSizeMetric,
+                                  StrokeMetricBrushSize::kMedium, 1);
 
   // Draw a stroke with extra thin size.
   params.size = 4.0f;
   SelectBrushTool(PdfInkBrush::Type::kHighlighter, params);
   ApplyStrokeWithMouseAtMouseDownPoint();
 
-  histograms.ExpectBucketCount(kHighlighterSizeMetric,
-                               StrokeMetricBrushSize::kExtraThin, 1);
-  histograms.ExpectTotalCount(kHighlighterSizeMetric, 2);
+  histograms().ExpectBucketCount(kHighlighterSizeMetric,
+                                 StrokeMetricBrushSize::kExtraThin, 1);
+  histograms().ExpectTotalCount(kHighlighterSizeMetric, 2);
 
   // Draw a stroke with extra thick size.
   params.size = 16.0f;
   SelectBrushTool(PdfInkBrush::Type::kHighlighter, params);
   ApplyStrokeWithMouseAtMouseDownPoint();
 
-  histograms.ExpectBucketCount(kHighlighterSizeMetric,
-                               StrokeMetricBrushSize::kExtraThick, 1);
-  histograms.ExpectTotalCount(kPenSizeMetric, 0);
-  histograms.ExpectTotalCount(kHighlighterSizeMetric, 3);
+  histograms().ExpectBucketCount(kHighlighterSizeMetric,
+                                 StrokeMetricBrushSize::kExtraThick, 1);
+  histograms().ExpectTotalCount(kPenSizeMetric, 0);
+  histograms().ExpectTotalCount(kHighlighterSizeMetric, 3);
 }
 
 TEST_P(PdfInkModuleMetricsTest, StrokeBrushType) {
   InitializeSimpleSinglePageBasicLayout();
-  base::HistogramTester histograms;
 
   RunStrokeCheckTest(/*annotation_mode_enabled=*/false);
 
-  histograms.ExpectTotalCount(kTypeMetric, 0);
+  histograms().ExpectTotalCount(kTypeMetric, 0);
 
   // Draw a pen stroke.
   RunStrokeCheckTest(/*annotation_mode_enabled=*/true);
 
-  histograms.ExpectUniqueSample(kTypeMetric, StrokeMetricBrushType::kPen, 1);
+  histograms().ExpectUniqueSample(kTypeMetric, StrokeMetricBrushType::kPen, 1);
 
   // Draw a highlighter stroke.
   TestAnnotationBrushMessageParams params = kRedBrushParams;
   SelectBrushTool(PdfInkBrush::Type::kHighlighter, params);
   ApplyStrokeWithMouseAtMouseDownPoint();
 
-  histograms.ExpectBucketCount(kTypeMetric, StrokeMetricBrushType::kHighlighter,
-                               1);
-  histograms.ExpectTotalCount(kTypeMetric, 2);
+  histograms().ExpectBucketCount(kTypeMetric,
+                                 StrokeMetricBrushType::kHighlighter, 1);
+  histograms().ExpectTotalCount(kTypeMetric, 2);
 
   // Draw an eraser stroke.
   SelectEraserTool();
   ApplyStrokeWithMouseAtMouseDownPoint();
 
-  histograms.ExpectBucketCount(kTypeMetric, StrokeMetricBrushType::kEraser, 1);
-  histograms.ExpectTotalCount(kTypeMetric, 3);
+  histograms().ExpectBucketCount(kTypeMetric, StrokeMetricBrushType::kEraser,
+                                 1);
+  histograms().ExpectTotalCount(kTypeMetric, 3);
 
   // Draw an eraser stroke at a different point that does not erase any other
   // strokes. The metric should stay the same.
   ApplyStrokeWithMouseAtPoints(
       kMouseUpPoint, base::span_from_ref(kMouseUpPoint), kMouseUpPoint);
 
-  histograms.ExpectBucketCount(kTypeMetric, StrokeMetricBrushType::kEraser, 1);
-  histograms.ExpectTotalCount(kTypeMetric, 3);
+  histograms().ExpectBucketCount(kTypeMetric, StrokeMetricBrushType::kEraser,
+                                 1);
+  histograms().ExpectTotalCount(kTypeMetric, 3);
 
   // Draw another pen stroke.
   params.size = 3.0f;
   SelectBrushTool(PdfInkBrush::Type::kPen, params);
   ApplyStrokeWithMouseAtMouseDownPoint();
 
-  histograms.ExpectBucketCount(kTypeMetric, StrokeMetricBrushType::kPen, 2);
-  histograms.ExpectTotalCount(kTypeMetric, 4);
+  histograms().ExpectBucketCount(kTypeMetric, StrokeMetricBrushType::kPen, 2);
+  histograms().ExpectTotalCount(kTypeMetric, 4);
 }
 
 TEST_P(PdfInkModuleMetricsTest, StrokeInputDeviceMouse) {
   InitializeSimpleSinglePageBasicLayout();
-  base::HistogramTester histograms;
 
   RunStrokeCheckTest(/*annotation_mode_enabled=*/false);
 
-  histograms.ExpectTotalCount(kInputDeviceMetric, 0);
+  histograms().ExpectTotalCount(kInputDeviceMetric, 0);
 
   // Draw a stroke with a mouse.
   RunStrokeCheckTest(/*annotation_mode_enabled=*/true);
 
-  histograms.ExpectUniqueSample(kInputDeviceMetric,
-                                StrokeMetricInputDeviceType::kMouse, 1);
+  histograms().ExpectUniqueSample(kInputDeviceMetric,
+                                  StrokeMetricInputDeviceType::kMouse, 1);
 
   // Draw an eraser stroke with a mouse that erases the first stroke.
   SelectEraserTool();
   ApplyStrokeWithMouseAtMouseDownPoint();
 
-  histograms.ExpectUniqueSample(kInputDeviceMetric,
-                                StrokeMetricInputDeviceType::kMouse, 2);
+  histograms().ExpectUniqueSample(kInputDeviceMetric,
+                                  StrokeMetricInputDeviceType::kMouse, 2);
 
   // Draw another eraser stroke with a mouse that erases nothing.
   ApplyStrokeWithMouseAtMouseDownPoint();
 
-  histograms.ExpectUniqueSample(kInputDeviceMetric,
-                                StrokeMetricInputDeviceType::kMouse, 2);
+  histograms().ExpectUniqueSample(kInputDeviceMetric,
+                                  StrokeMetricInputDeviceType::kMouse, 2);
 }
 
 TEST_P(PdfInkModuleMetricsTest, StrokeInputDeviceTouch) {
   InitializeSimpleSinglePageBasicLayout();
-  base::HistogramTester histograms;
 
   RunStrokeTouchCheckTest(/*annotation_mode_enabled=*/false);
 
-  histograms.ExpectTotalCount(kInputDeviceMetric, 0);
+  histograms().ExpectTotalCount(kInputDeviceMetric, 0);
 
   // Draw a stroke with touch.
   RunStrokeTouchCheckTest(/*annotation_mode_enabled=*/true);
 
-  histograms.ExpectUniqueSample(kInputDeviceMetric,
-                                StrokeMetricInputDeviceType::kTouch, 1);
+  histograms().ExpectUniqueSample(kInputDeviceMetric,
+                                  StrokeMetricInputDeviceType::kTouch, 1);
 
   // Draw an eraser stroke with touch that erases the first stroke.
   SelectEraserTool();
@@ -3036,30 +3041,29 @@
   ApplyStrokeWithTouchAtPoints(base::span_from_ref(kMouseDownPoint), move_point,
                                base::span_from_ref(kMouseDownPoint));
 
-  histograms.ExpectUniqueSample(kInputDeviceMetric,
-                                StrokeMetricInputDeviceType::kTouch, 2);
+  histograms().ExpectUniqueSample(kInputDeviceMetric,
+                                  StrokeMetricInputDeviceType::kTouch, 2);
 
   // Draw another eraser stroke with touch that erases nothing.
   ApplyStrokeWithTouchAtPoints(base::span_from_ref(kMouseDownPoint), move_point,
                                base::span_from_ref(kMouseDownPoint));
 
-  histograms.ExpectUniqueSample(kInputDeviceMetric,
-                                StrokeMetricInputDeviceType::kTouch, 2);
+  histograms().ExpectUniqueSample(kInputDeviceMetric,
+                                  StrokeMetricInputDeviceType::kTouch, 2);
 }
 
 TEST_P(PdfInkModuleMetricsTest, StrokeInputDevicePen) {
   InitializeSimpleSinglePageBasicLayout();
-  base::HistogramTester histograms;
 
   RunStrokePenCheckTest(/*annotation_mode_enabled=*/false);
 
-  histograms.ExpectTotalCount(kInputDeviceMetric, 0);
+  histograms().ExpectTotalCount(kInputDeviceMetric, 0);
 
   // Draw a stroke with a pen.
   RunStrokePenCheckTest(/*annotation_mode_enabled=*/true);
 
-  histograms.ExpectUniqueSample(kInputDeviceMetric,
-                                StrokeMetricInputDeviceType::kPen, 1);
+  histograms().ExpectUniqueSample(kInputDeviceMetric,
+                                  StrokeMetricInputDeviceType::kPen, 1);
 
   // Draw an eraser stroke with a pen that erases the first stroke.
   SelectEraserTool();
@@ -3069,15 +3073,15 @@
   ApplyStrokeWithPenAtPoints(base::span_from_ref(kMouseDownPoint), move_point,
                              base::span_from_ref(kMouseDownPoint));
 
-  histograms.ExpectUniqueSample(kInputDeviceMetric,
-                                StrokeMetricInputDeviceType::kPen, 2);
+  histograms().ExpectUniqueSample(kInputDeviceMetric,
+                                  StrokeMetricInputDeviceType::kPen, 2);
 
   // Draw another eraser stroke with a pen that erases nothing.
   ApplyStrokeWithPenAtPoints(base::span_from_ref(kMouseDownPoint), move_point,
                              base::span_from_ref(kMouseDownPoint));
 
-  histograms.ExpectUniqueSample(kInputDeviceMetric,
-                                StrokeMetricInputDeviceType::kPen, 2);
+  histograms().ExpectUniqueSample(kInputDeviceMetric,
+                                  StrokeMetricInputDeviceType::kPen, 2);
 }
 
 class PdfInkModuleTextHighlightTest : public PdfInkModuleStrokeTest {
diff --git a/pdf/pdf_view_web_plugin.cc b/pdf/pdf_view_web_plugin.cc
index a51357c..d221559 100644
--- a/pdf/pdf_view_web_plugin.cc
+++ b/pdf/pdf_view_web_plugin.cc
@@ -232,12 +232,11 @@
 };
 
 base::Value::Dict DictFromRect(const gfx::Rect& rect) {
-  base::Value::Dict dict;
-  dict.Set("x", rect.x());
-  dict.Set("y", rect.y());
-  dict.Set("width", rect.width());
-  dict.Set("height", rect.height());
-  return dict;
+  return base::Value::Dict()
+      .Set("x", rect.x())
+      .Set("y", rect.y())
+      .Set("width", rect.width())
+      .Set("height", rect.height());
 }
 
 bool IsPrintPreviewUrl(std::string_view url) {
@@ -273,12 +272,11 @@
 base::Value::Dict CreateSaveDataBlockMessage(
     const std::string& token,
     PdfViewWebPlugin::SaveDataBlock data) {
-  base::Value::Dict message;
-  message.Set("type", "saveDataBlock");
-  message.Set("token", token);
-  message.Set("dataToSave", std::move(data.block));
-  message.Set("totalFileSize", base::checked_cast<int>(data.total_file_size));
-  return message;
+  return base::Value::Dict()
+      .Set("type", "saveDataBlock")
+      .Set("token", token)
+      .Set("dataToSave", std::move(data.block))
+      .Set("totalFileSize", base::checked_cast<int>(data.total_file_size));
 }
 
 }  // namespace
@@ -404,9 +402,8 @@
   }
 
   void StrokeFinished() override {
-    base::Value::Dict message;
-    message.Set("type", "finishInkStroke");
-    plugin_->client_->PostMessage(std::move(message));
+    plugin_->client_->PostMessage(
+        base::Value::Dict().Set("type", "finishInkStroke"));
     plugin_->SetPluginCanSave(true);
   }
 
@@ -571,11 +568,11 @@
 }
 
 void PdfViewWebPlugin::SendSetSmoothScrolling() {
-  base::Value::Dict message;
-  message.Set("type", "setSmoothScrolling");
-  message.Set("smoothScrolling",
-              blink::Platform::Current()->IsScrollAnimatorEnabled());
-  client_->PostMessage(std::move(message));
+  client_->PostMessage(
+      base::Value::Dict()
+          .Set("type", "setSmoothScrolling")
+          .Set("smoothScrolling",
+               blink::Platform::Current()->IsScrollAnimatorEnabled()));
 }
 
 void PdfViewWebPlugin::DidOpen(std::unique_ptr<UrlLoader> loader,
@@ -1024,16 +1021,17 @@
 }
 
 void PdfViewWebPlugin::ProposeDocumentLayout(const DocumentLayout& layout) {
-  base::Value::Dict message;
-  message.Set("type", "documentDimensions");
-  message.Set("width", layout.size().width());
-  message.Set("height", layout.size().height());
-  message.Set("layoutOptions", layout.options().ToValue());
   base::Value::List page_dimensions;
-  for (size_t i = 0; i < layout.page_count(); ++i)
+  page_dimensions.reserve(layout.page_count());
+  for (size_t i = 0; i < layout.page_count(); ++i) {
     page_dimensions.Append(DictFromRect(layout.page_rect(i)));
-  message.Set("pageDimensions", std::move(page_dimensions));
-  client_->PostMessage(std::move(message));
+  }
+  client_->PostMessage(base::Value::Dict()
+                           .Set("type", "documentDimensions")
+                           .Set("width", layout.size().width())
+                           .Set("height", layout.size().height())
+                           .Set("layoutOptions", layout.options().ToValue())
+                           .Set("pageDimensions", std::move(page_dimensions)));
 
   // Reload the accessibility tree on layout changes because the relative page
   // bounds are no longer valid.
@@ -1059,64 +1057,61 @@
 void PdfViewWebPlugin::ScrollToX(int x_screen_coords) {
   const float x_scroll_pos = x_screen_coords / device_scale_;
 
-  base::Value::Dict message;
-  message.Set("type", "setScrollPosition");
-  message.Set("x", static_cast<double>(x_scroll_pos));
-  client_->PostMessage(std::move(message));
+  client_->PostMessage(base::Value::Dict()
+                           .Set("type", "setScrollPosition")
+                           .Set("x", static_cast<double>(x_scroll_pos)));
 }
 
 void PdfViewWebPlugin::ScrollToY(int y_screen_coords) {
   const float y_scroll_pos = y_screen_coords / device_scale_;
 
-  base::Value::Dict message;
-  message.Set("type", "setScrollPosition");
-  message.Set("y", static_cast<double>(y_scroll_pos));
-  client_->PostMessage(std::move(message));
+  client_->PostMessage(base::Value::Dict()
+                           .Set("type", "setScrollPosition")
+                           .Set("y", static_cast<double>(y_scroll_pos)));
 }
 
 void PdfViewWebPlugin::ScrollBy(const gfx::Vector2d& delta) {
   const float x_delta = delta.x() / device_scale_;
   const float y_delta = delta.y() / device_scale_;
 
-  base::Value::Dict message;
-  message.Set("type", "scrollBy");
-  message.Set("x", static_cast<double>(x_delta));
-  message.Set("y", static_cast<double>(y_delta));
-  client_->PostMessage(std::move(message));
+  client_->PostMessage(base::Value::Dict()
+                           .Set("type", "scrollBy")
+                           .Set("x", static_cast<double>(x_delta))
+                           .Set("y", static_cast<double>(y_delta)));
 }
 
 void PdfViewWebPlugin::ScrollToPage(int page) {
   if (!engine_ || engine_->GetNumberOfPages() == 0)
     return;
 
-  base::Value::Dict message;
-  message.Set("type", "goToPage");
-  message.Set("page", page);
-  client_->PostMessage(std::move(message));
+  client_->PostMessage(
+      base::Value::Dict().Set("type", "goToPage").Set("page", page));
 }
 
 void PdfViewWebPlugin::NavigateTo(const std::string& url,
                                   WindowOpenDisposition disposition) {
-  base::Value::Dict message;
-  message.Set("type", "navigate");
-  message.Set("url", url);
-  message.Set("disposition", static_cast<int>(disposition));
-  client_->PostMessage(std::move(message));
+  client_->PostMessage(base::Value::Dict()
+                           .Set("type", "navigate")
+                           .Set("url", url)
+                           .Set("disposition", static_cast<int>(disposition)));
 }
 
 void PdfViewWebPlugin::NavigateToDestination(int page,
                                              const float* x,
                                              const float* y,
                                              const float* zoom) {
-  base::Value::Dict message;
-  message.Set("type", "navigateToDestination");
-  message.Set("page", page);
-  if (x)
+  auto message = base::Value::Dict()
+                     .Set("type", "navigateToDestination")
+                     .Set("page", page);
+  if (x) {
     message.Set("x", static_cast<double>(*x));
-  if (y)
+  }
+  if (y) {
     message.Set("y", static_cast<double>(*y));
-  if (zoom)
+  }
+  if (zoom) {
     message.Set("zoom", static_cast<double>(*zoom));
+  }
   client_->PostMessage(std::move(message));
 }
 
@@ -1176,9 +1171,8 @@
 }
 
 void PdfViewWebPlugin::NotifyTouchSelectionOccurred() {
-  base::Value::Dict message;
-  message.Set("type", "touchSelectionOccurred");
-  client_->PostMessage(std::move(message));
+  client_->PostMessage(
+      base::Value::Dict().Set("type", "touchSelectionOccurred"));
 }
 
 void PdfViewWebPlugin::CaretChanged(const gfx::Rect& caret_rect) {
@@ -1190,15 +1184,11 @@
   DCHECK(password_callback_.is_null());
   password_callback_ = std::move(callback);
 
-  base::Value::Dict message;
-  message.Set("type", "getPassword");
-  client_->PostMessage(std::move(message));
+  client_->PostMessage(base::Value::Dict().Set("type", "getPassword"));
 }
 
 void PdfViewWebPlugin::Beep() {
-  base::Value::Dict message;
-  message.Set("type", "beep");
-  client_->PostMessage(std::move(message));
+  client_->PostMessage(base::Value::Dict().Set("type", "beep"));
 }
 
 void PdfViewWebPlugin::Alert(const std::string& message) {
@@ -1238,14 +1228,14 @@
                              const std::string& bcc,
                              const std::string& subject,
                              const std::string& body) {
-  base::Value::Dict message;
-  message.Set("type", "email");
-  message.Set("to", base::EscapeUrlEncodedData(to, false));
-  message.Set("cc", base::EscapeUrlEncodedData(cc, false));
-  message.Set("bcc", base::EscapeUrlEncodedData(bcc, false));
-  message.Set("subject", base::EscapeUrlEncodedData(subject, false));
-  message.Set("body", base::EscapeUrlEncodedData(body, false));
-  client_->PostMessage(std::move(message));
+  client_->PostMessage(
+      base::Value::Dict()
+          .Set("type", "email")
+          .Set("to", base::EscapeUrlEncodedData(to, false))
+          .Set("cc", base::EscapeUrlEncodedData(cc, false))
+          .Set("bcc", base::EscapeUrlEncodedData(bcc, false))
+          .Set("subject", base::EscapeUrlEncodedData(subject, false))
+          .Set("body", base::EscapeUrlEncodedData(body, false)));
 }
 
 void PdfViewWebPlugin::Print() {
@@ -1446,8 +1436,6 @@
 
 void PdfViewWebPlugin::FormFieldFocusChange(
     PDFiumEngineClient::FocusFieldType type) {
-  base::Value::Dict message;
-  message.Set("type", "formFocusChange");
   std::string field_type;
   // LINT.IfChange(FocusFieldTypes)
   switch (type) {
@@ -1462,8 +1450,10 @@
       break;
   }
   // LINT.ThenChange(//chrome/browser/resources/pdf/constants.ts:FocusFieldTypes)
-  message.Set("focused", field_type);
-  client_->PostMessage(std::move(message));
+
+  client_->PostMessage(base::Value::Dict()
+                           .Set("type", "formFocusChange")
+                           .Set("focused", std::move(field_type)));
 
   text_input_type_ = type == PDFiumEngineClient::FocusFieldType::kText
                          ? blink::WebTextInputType::kWebTextInputTypeText
@@ -1508,16 +1498,13 @@
   edit_mode_ = true;
   SetPluginCanSave(true);
 
-  base::Value::Dict message;
-  message.Set("type", "setIsEditing");
-  client_->PostMessage(std::move(message));
+  client_->PostMessage(base::Value::Dict().Set("type", "setIsEditing"));
 }
 
 void PdfViewWebPlugin::DocumentFocusChanged(bool document_has_focus) {
-  base::Value::Dict message;
-  message.Set("type", "documentFocusChanged");
-  message.Set("hasFocus", document_has_focus);
-  client_->PostMessage(std::move(message));
+  client_->PostMessage(base::Value::Dict()
+                           .Set("type", "documentFocusChanged")
+                           .Set("hasFocus", document_has_focus));
 }
 
 void PdfViewWebPlugin::SetSelectedText(const std::string& selected_text) {
@@ -1591,16 +1578,14 @@
     return;
   }
 
-  base::Value::Dict message;
-  message.Set("type", "showSearchifyInProgress");
-  message.Set("show", show);
-  client_->PostMessage(std::move(message));
+  client_->PostMessage(base::Value::Dict()
+                           .Set("type", "showSearchifyInProgress")
+                           .Set("show", show));
 }
 
 void PdfViewWebPlugin::OnHasSearchifyText() {
-  base::Value::Dict message;
-  message.Set("type", "setHasSearchifyText");
-  client_->PostMessage(std::move(message));
+  client_->PostMessage(base::Value::Dict().Set("type", "setHasSearchifyText"));
+
   pdf_accessibility_data_handler_->OnHasSearchifyText();
   if (chrome_pdf::features::IsPdfSearchifySaveEnabled()) {
     SetPluginCanSave(true);
@@ -1772,8 +1757,6 @@
 void PdfViewWebPlugin::HandleGetPageBoundingBoxMessage(
     const base::Value::Dict& message) {
   const int page_index = message.FindInt("page").value();
-  base::Value::Dict reply = PrepareReplyMessage(message);
-
   PDFiumPage* page = engine_->GetPage(page_index);
   CHECK(page);
   gfx::RectF bounding_box = page->GetBoundingBox();
@@ -1782,12 +1765,12 @@
   // Flip the origin from bottom-left to top-left.
   bounding_box.set_y(static_cast<float>(page_bounds.height()) -
                      bounding_box.bottom());
-  reply.Set("x", bounding_box.x());
-  reply.Set("y", bounding_box.y());
-  reply.Set("width", bounding_box.width());
-  reply.Set("height", bounding_box.height());
 
-  client_->PostMessage(std::move(reply));
+  client_->PostMessage(PrepareReplyMessage(message)
+                           .Set("x", bounding_box.x())
+                           .Set("y", bounding_box.y())
+                           .Set("width", bounding_box.width())
+                           .Set("height", bounding_box.height()));
 }
 
 void PdfViewWebPlugin::HandleGetPasswordCompleteMessage(
@@ -1802,9 +1785,8 @@
   std::string selected_text;
   base::RemoveChars(engine_->GetSelectedText(), "\r", &selected_text);
 
-  base::Value::Dict reply = PrepareReplyMessage(message);
-  reply.Set("selectedText", selected_text);
-  client_->PostMessage(std::move(reply));
+  client_->PostMessage(PrepareReplyMessage(message).Set(
+      "selectedText", std::move(selected_text)));
 }
 
 void PdfViewWebPlugin::HandleGetSaveDataBlockMessage(
@@ -1822,9 +1804,8 @@
 
 void PdfViewWebPlugin::HandleGetSuggestedFileName(
     const base::Value::Dict& message) {
-  base::Value::Dict reply = PrepareReplyMessage(message);
-  reply.Set("fileName", GetFileNameForSaveFromUrl(url_));
-  client_->PostMessage(std::move(reply));
+  client_->PostMessage(PrepareReplyMessage(message).Set(
+      "fileName", GetFileNameForSaveFromUrl(url_)));
 }
 
 void PdfViewWebPlugin::HandleGetThumbnailMessage(
@@ -1879,9 +1860,8 @@
   base::Value data_to_save(
       IsSaveDataSizeValid(data.size()) ? data : std::vector<uint8_t>());
 
-  base::Value::Dict reply = PrepareReplyMessage(message);
-  reply.Set("dataToSave", std::move(data_to_save));
-  client_->PostMessage(std::move(reply));
+  client_->PostMessage(
+      PrepareReplyMessage(message).Set("dataToSave", std::move(data_to_save)));
 }
 
 void PdfViewWebPlugin::HandleSaveMessage(const base::Value::Dict& message) {
@@ -2096,10 +2076,10 @@
 
   engine_->KillFormFocus();
 
-  base::Value::Dict message;
-  message.Set("type", "saveData");
-  message.Set("token", token);
-  message.Set("fileName", GetFileNameForSaveFromUrl(url_));
+  auto message = base::Value::Dict()
+                     .Set("type", "saveData")
+                     .Set("token", token)
+                     .Set("fileName", GetFileNameForSaveFromUrl(url_));
 
   // Expose `edit_mode_` state for integration testing.
   message.Set("editModeForTesting", edit_mode_);
@@ -2208,10 +2188,8 @@
 void PdfViewWebPlugin::SaveToFile(const std::string& token) {
   engine_->KillFormFocus();
 
-  base::Value::Dict message;
-  message.Set("type", "consumeSaveToken");
-  message.Set("token", token);
-  client_->PostMessage(std::move(message));
+  client_->PostMessage(
+      base::Value::Dict().Set("type", "consumeSaveToken").Set("token", token));
 
   pdf_host_->SaveUrlAs(GURL(url_), network::mojom::ReferrerPolicy::kDefault);
 }
@@ -2694,6 +2672,7 @@
     return;
 
   base::Value::List attachments;
+  attachments.reserve(attachment_infos.size());
   for (const DocumentAttachmentInfo& attachment_info : attachment_infos) {
     // Send `size` as -1 to indicate that the attachment is too large to be
     // downloaded.
@@ -2701,41 +2680,36 @@
                          ? static_cast<int>(attachment_info.size_bytes)
                          : -1;
 
-    base::Value::Dict attachment;
-    attachment.Set("name", attachment_info.name);
-    attachment.Set("size", size);
-    attachment.Set("readable", attachment_info.is_readable);
-    attachments.Append(std::move(attachment));
+    attachments.Append(base::Value::Dict()
+                           .Set("name", attachment_info.name)
+                           .Set("size", size)
+                           .Set("readable", attachment_info.is_readable));
   }
 
-  base::Value::Dict message;
-  message.Set("type", "attachments");
-  message.Set("attachmentsData", std::move(attachments));
-  client_->PostMessage(std::move(message));
+  client_->PostMessage(base::Value::Dict()
+                           .Set("type", "attachments")
+                           .Set("attachmentsData", std::move(attachments)));
 }
 
 void PdfViewWebPlugin::SendBookmarks() {
   base::Value::List bookmarks = engine_->GetBookmarks();
-  if (bookmarks.empty())
+  if (bookmarks.empty()) {
     return;
+  }
 
-  base::Value::Dict message;
-  message.Set("type", "bookmarks");
-  message.Set("bookmarksData", std::move(bookmarks));
-  client_->PostMessage(std::move(message));
+  client_->PostMessage(base::Value::Dict()
+                           .Set("type", "bookmarks")
+                           .Set("bookmarksData", std::move(bookmarks)));
 }
 
 void PdfViewWebPlugin::SendExecutedEditCommand(std::string_view edit_command) {
-  base::Value::Dict message;
-  message.Set("type", "executedEditCommand");
-  message.Set("editCommand", edit_command);
-  client_->PostMessage(std::move(message));
+  client_->PostMessage(base::Value::Dict()
+                           .Set("type", "executedEditCommand")
+                           .Set("editCommand", edit_command));
 }
 
 void PdfViewWebPlugin::SendStartedFindInPage() {
-  base::Value::Dict message;
-  message.Set("type", "startedFindInPage");
-  client_->PostMessage(std::move(message));
+  client_->PostMessage(base::Value::Dict().Set("type", "startedFindInPage"));
 }
 
 void PdfViewWebPlugin::SendMetadata() {
@@ -2783,20 +2757,18 @@
   metadata.Set("canSerializeDocument",
                IsSaveDataSizeValid(engine_->GetLoadedByteSize()));
 
-  base::Value::Dict message;
-  message.Set("type", "metadata");
-  message.Set("metadataData", std::move(metadata));
-  client_->PostMessage(std::move(message));
+  client_->PostMessage(base::Value::Dict()
+                           .Set("type", "metadata")
+                           .Set("metadataData", std::move(metadata)));
 }
 
 void PdfViewWebPlugin::SendLoadingProgress(double percentage) {
   DCHECK(percentage == -1 || (percentage >= 0 && percentage <= 100));
   last_progress_sent_ = percentage;
 
-  base::Value::Dict message;
-  message.Set("type", "loadProgress");
-  message.Set("progress", percentage);
-  client_->PostMessage(std::move(message));
+  client_->PostMessage(base::Value::Dict()
+                           .Set("type", "loadProgress")
+                           .Set("progress", percentage));
 }
 
 void PdfViewWebPlugin::HandleReleaseSaveInBlockBuffers(
@@ -2944,9 +2916,7 @@
 }
 
 void PdfViewWebPlugin::SendPrintPreviewLoadedNotification() {
-  base::Value::Dict message;
-  message.Set("type", "printPreviewLoaded");
-  client_->PostMessage(std::move(message));
+  client_->PostMessage(base::Value::Dict().Set("type", "printPreviewLoaded"));
 }
 
 void PdfViewWebPlugin::SendThumbnailForTesting(base::Value::Dict reply,
diff --git a/pdf/pdf_view_web_plugin_unittest.cc b/pdf/pdf_view_web_plugin_unittest.cc
index 767da03..29d8049 100644
--- a/pdf/pdf_view_web_plugin_unittest.cc
+++ b/pdf/pdf_view_web_plugin_unittest.cc
@@ -199,10 +199,9 @@
 }
 
 base::Value::Dict GenerateShowSearchifyInProgressMessage(bool show) {
-  base::Value::Dict message;
-  message.Set("type", "showSearchifyInProgress");
-  message.Set("show", show);
-  return message;
+  return base::Value::Dict()
+      .Set("type", "showSearchifyInProgress")
+      .Set("show", show);
 }
 
 class MockHeaderVisitor : public blink::WebHTTPHeaderVisitor {
@@ -1499,10 +1498,9 @@
 TEST_F(PdfViewWebPluginTest, HandleSetBackgroundColorMessage) {
   ASSERT_NE(SK_ColorGREEN, plugin_->GetBackgroundColor());
 
-  base::Value::Dict message;
-  message.Set("type", "setBackgroundColor");
-  message.Set("color", static_cast<double>(SK_ColorGREEN));
-  plugin_->OnMessage(message);
+  plugin_->OnMessage(base::Value::Dict()
+                         .Set("type", "setBackgroundColor")
+                         .Set("color", static_cast<double>(SK_ColorGREEN)));
 
   EXPECT_EQ(SK_ColorGREEN, plugin_->GetBackgroundColor());
 }
@@ -1511,9 +1509,9 @@
   EXPECT_FALSE(engine_ptr_->IsReadOnly());
   plugin_->set_cursor_type_for_testing(ui::mojom::CursorType::kIBeam);
 
-  base::Value::Dict message;
-  message.Set("type", "setPresentationMode");
-  message.Set("enablePresentationMode", true);
+  auto message = base::Value::Dict()
+                     .Set("type", "setPresentationMode")
+                     .Set("enablePresentationMode", true);
   plugin_->OnMessage(message);
 
   // After entering presentation mode, PDFiumEngine is read-only and the cursor
@@ -1837,15 +1835,14 @@
 }
 
 TEST_F(PdfViewWebPluginTest, OnDocumentLoadComplete) {
-  base::Value::Dict metadata;
-  metadata.Set("fileSize", "0 B");
-  metadata.Set("linearized", false);
-  metadata.Set("pageSize", "Varies");
-  metadata.Set("canSerializeDocument", true);
-
-  base::Value::Dict message;
-  message.Set("type", "metadata");
-  message.Set("metadataData", std::move(metadata));
+  auto message =
+      base::Value::Dict()
+          .Set("type", "metadata")
+          .Set("metadataData", base::Value::Dict()
+                                   .Set("fileSize", "0 B")
+                                   .Set("linearized", false)
+                                   .Set("pageSize", "Varies")
+                                   .Set("canSerializeDocument", true));
 
   EXPECT_CALL(*client_ptr_, PostMessage);
   EXPECT_CALL(*client_ptr_, PostMessage(Eq(std::ref(message))));
@@ -1935,25 +1932,21 @@
 }
 
 TEST_F(PdfViewWebPluginTest, OnHasSearchifyText) {
-  base::Value::Dict message;
-  message.Set("type", "setHasSearchifyText");
+  auto message = base::Value::Dict().Set("type", "setHasSearchifyText");
 
   EXPECT_CALL(*client_ptr_, PostMessage(Eq(std::ref(message))));
   plugin_->OnHasSearchifyText();
 }
 
 TEST_F(PdfViewWebPluginTest, HighlightTextFragments) {
-  base::Value::List fragments;
-  fragments.Append("hello-,world");
-  fragments.Append("world,-hello");
-
-  base::Value::Dict message;
-  message.Set("type", "highlightTextFragments");
-  message.Set("textFragments", std::move(fragments));
-
   EXPECT_CALL(*engine_ptr_, HighlightTextFragments(
                                 ElementsAre("hello-,world", "world,-hello")));
-  plugin_->OnMessage(message);
+
+  plugin_->OnMessage(base::Value::Dict()
+                         .Set("type", "highlightTextFragments")
+                         .Set("textFragments", base::Value::List()
+                                                   .Append("hello-,world")
+                                                   .Append("world,-hello")));
 }
 
 class PdfViewWebPluginWithDocInfoTest
@@ -1979,29 +1972,25 @@
     }
 
     base::Value::List GetBookmarks() override {
-      // Create `bookmark1` which navigates to an in-doc position. This bookmark
-      // will be in the top-level bookmark list.
-      base::Value::Dict bookmark1;
-      bookmark1.Set("title", "Bookmark 1");
-      bookmark1.Set("page", 2);
-      bookmark1.Set("x", 10);
-      bookmark1.Set("y", 20);
-      bookmark1.Set("zoom", 2.0);
-
       // Create `bookmark2` which navigates to a web page. This bookmark will be
       // a child of `bookmark1`.
-      base::Value::Dict bookmark2;
-      bookmark2.Set("title", "Bookmark 2");
-      bookmark2.Set("uri", "test.com");
+      auto bookmark2 =
+          base::Value::Dict().Set("title", "Bookmark 2").Set("uri", "test.com");
 
-      base::Value::List children_of_bookmark1;
-      children_of_bookmark1.Append(std::move(bookmark2));
-      bookmark1.Set("children", std::move(children_of_bookmark1));
+      // Create `bookmark1` which navigates to an in-doc position. This bookmark
+      // will be in the top-level bookmark list.
+      auto bookmark1 =
+          base::Value::Dict()
+              .Set("title", "Bookmark 1")
+              .Set("page", 2)
+              .Set("x", 10)
+              .Set("y", 20)
+              .Set("zoom", 2.0)
+              .Set("children",
+                   base::Value::List().Append(std::move(bookmark2)));
 
       // Create the top-level bookmark list.
-      base::Value::List bookmarks;
-      bookmarks.Append(std::move(bookmark1));
-      return bookmarks;
+      return base::Value::List().Append(std::move(bookmark1));
     }
 
     std::optional<gfx::Size> GetUniformPageSizePoints() override {
@@ -2051,68 +2040,51 @@
   };
 
   static base::Value::Dict CreateExpectedAttachmentsResponse() {
-    base::Value::List attachments;
-    {
-      base::Value::Dict attachment;
-      attachment.Set("name", "attachment1.txt");
-      attachment.Set("size", 13);
-      attachment.Set("readable", true);
-      attachments.Append(std::move(attachment));
-    }
-    {
-      base::Value::Dict attachment;
-      attachment.Set("name", "attachment2.pdf");
-      attachment.Set("size", 0);
-      attachment.Set("readable", false);
-      attachments.Append(std::move(attachment));
-    }
-    {
-      base::Value::Dict attachment;
-      attachment.Set("name", "attachment3.mov");
-      attachment.Set("size", -1);
-      attachment.Set("readable", true);
-      attachments.Append(std::move(attachment));
-    }
-
-    base::Value::Dict message;
-    message.Set("type", "attachments");
-    message.Set("attachmentsData", std::move(attachments));
-    return message;
+    return base::Value::Dict()
+        .Set("type", "attachments")
+        .Set("attachmentsData", base::Value::List()
+                                    .Append(base::Value::Dict()
+                                                .Set("name", "attachment1.txt")
+                                                .Set("size", 13)
+                                                .Set("readable", true))
+                                    .Append(base::Value::Dict()
+                                                .Set("name", "attachment2.pdf")
+                                                .Set("size", 0)
+                                                .Set("readable", false))
+                                    .Append(base::Value::Dict()
+                                                .Set("name", "attachment3.mov")
+                                                .Set("size", -1)
+                                                .Set("readable", true)));
   }
 
   static base::Value::Dict CreateExpectedBookmarksResponse(
       base::Value::List bookmarks) {
-    base::Value::Dict message;
-    message.Set("type", "bookmarks");
-    message.Set("bookmarksData", std::move(bookmarks));
-    return message;
+    return base::Value::Dict()
+        .Set("type", "bookmarks")
+        .Set("bookmarksData", std::move(bookmarks));
   }
 
   static base::Value::Dict CreateExpectedMetadataResponse() {
-    base::Value::Dict metadata;
-    metadata.Set("version", "1.7");
-    metadata.Set("fileSize", "13 B");
-    metadata.Set("linearized", true);
-
-    metadata.Set("title", "Title");
-    metadata.Set("author", "Author");
-    metadata.Set("subject", "Subject");
-    metadata.Set("keywords", "Keywords");
-    metadata.Set("creator", "Creator");
-    metadata.Set("producer", "Producer");
-    metadata.Set("creationDate",
-                 "5/4/21, 4:12:13\xE2\x80\xAF"
-                 "AM");
-    metadata.Set("modDate",
-                 "6/4/21, 8:16:17\xE2\x80\xAF"
-                 "AM");
-    metadata.Set("pageSize", "13.89 × 16.67 in (portrait)");
-    metadata.Set("canSerializeDocument", true);
-
-    base::Value::Dict message;
-    message.Set("type", "metadata");
-    message.Set("metadataData", std::move(metadata));
-    return message;
+    return base::Value::Dict()
+        .Set("type", "metadata")
+        .Set("metadataData", base::Value::Dict()
+                                 .Set("version", "1.7")
+                                 .Set("fileSize", "13 B")
+                                 .Set("linearized", true)
+                                 .Set("title", "Title")
+                                 .Set("author", "Author")
+                                 .Set("subject", "Subject")
+                                 .Set("keywords", "Keywords")
+                                 .Set("creator", "Creator")
+                                 .Set("producer", "Producer")
+                                 .Set("creationDate",
+                                      "5/4/21, 4:12:13\xE2\x80\xAF"
+                                      "AM")
+                                 .Set("modDate",
+                                      "6/4/21, 8:16:17\xE2\x80\xAF"
+                                      "AM")
+                                 .Set("pageSize", "13.89 × 16.67 in (portrait)")
+                                 .Set("canSerializeDocument", true));
   }
 
   void SetUpClient() override {
@@ -2308,27 +2280,27 @@
       uint32_t offset,
       uint32_t block_size,
       std::string token) {
-    base::Value::Dict dict;
-    dict.Set("type", "getSaveDataBlock");
-    dict.Set("saveRequestType", static_cast<int>(request_type));
-    dict.Set("offset", static_cast<int>(offset));
-    dict.Set("blockSize", static_cast<int>(block_size));
-    dict.Set("token", token);
-    return dict;
+    return base::Value::Dict()
+        .Set("type", "getSaveDataBlock")
+        .Set("saveRequestType", static_cast<int>(request_type))
+        .Set("offset", static_cast<int>(offset))
+        .Set("blockSize", static_cast<int>(block_size))
+        .Set("token", token);
   }
 
   void ExpectResponse(base::span<const uint8_t> data,
                       uint32_t offset,
                       uint32_t block_size,
                       std::string token) {
-    base::Value value(base::Value::Type::DICT);
-    value.GetDict().Set("type", "saveDataBlock");
-    value.GetDict().Set("token", token);
-    value.GetDict().Set("dataToSave",
-                        base::Value(data.subspan(offset, block_size)));
-    value.GetDict().Set("totalFileSize",
-                        base::Value(static_cast<int>(data.size())));
-    EXPECT_CALL(*client_ptr_, PostMessage(base::test::IsJson(value)));
+    auto data_to_save = data.subspan(offset, block_size);
+    base::BlobStorage data_to_save_blob(data_to_save.begin(),
+                                        data_to_save.end());
+    auto dict = base::Value::Dict()
+                    .Set("type", "saveDataBlock")
+                    .Set("token", std::move(token))
+                    .Set("dataToSave", std::move(data_to_save_blob))
+                    .Set("totalFileSize", static_cast<int>(data.size()));
+    EXPECT_CALL(*client_ptr_, PostMessage(base::test::IsJson(dict)));
   }
 
   void SetUpClient() override {
@@ -2422,9 +2394,8 @@
                                    0, 0, "token-1"));
   EXPECT_FALSE(plugin_->IsSaveDataBufferEmptyForTesting());
 
-  base::Value::Dict message;
-  message.Set("type", "releaseSaveInBlockBuffers");
-  plugin_->OnMessage(message);
+  plugin_->OnMessage(
+      base::Value::Dict().Set("type", "releaseSaveInBlockBuffers"));
   EXPECT_TRUE(plugin_->IsSaveDataBufferEmptyForTesting());
 
   pdf_receiver_.FlushForTesting();
@@ -2839,9 +2810,9 @@
   }
 
   void SendThumbnail(std::string_view message_id, const gfx::SizeF& page_size) {
-    base::Value::Dict reply;
-    reply.Set("type", "getThumbnailReply");
-    reply.Set("messageId", message_id);
+    auto reply = base::Value::Dict()
+                     .Set("type", "getThumbnailReply")
+                     .Set("messageId", message_id);
     plugin_->SendThumbnailForTesting(
         std::move(reply), /*page_index=*/0,
         Thumbnail(page_size, /*device_pixel_ratio=*/1));
diff --git a/pdf/test/pdf_ink_test_helpers.cc b/pdf/test/pdf_ink_test_helpers.cc
index 3971287..d16b7fe 100644
--- a/pdf/test/pdf_ink_test_helpers.cc
+++ b/pdf/test/pdf_ink_test_helpers.cc
@@ -58,9 +58,6 @@
 base::Value::Dict CreateSetAnnotationBrushMessageForTesting(
     std::string_view type,
     const TestAnnotationBrushMessageParams* params) {
-  base::Value::Dict message;
-  message.Set("type", "setAnnotationBrush");
-
   base::Value::Dict data;
   data.Set("type", type);
   if (params) {
@@ -71,15 +68,16 @@
                  .Set("b", static_cast<int>(SkColorGetB(params->color))));
     data.Set("size", params->size);
   }
-  message.Set("data", std::move(data));
-  return message;
+
+  return base::Value::Dict()
+      .Set("type", "setAnnotationBrush")
+      .Set("data", std::move(data));
 }
 
 base::Value::Dict CreateSetAnnotationModeMessageForTesting(bool enable) {
-  base::Value::Dict message;
-  message.Set("type", "setAnnotationMode");
-  message.Set("mode", enable ? "draw" : "off");
-  return message;
+  return base::Value::Dict()
+      .Set("type", "setAnnotationMode")
+      .Set("mode", enable ? "draw" : "off");
 }
 
 base::Value::Dict CreateSetAnnotationUndoRedoMessageForTesting(
@@ -87,11 +85,9 @@
   base::Value::Dict message;
   switch (type) {
     case TestAnnotationUndoRedoMessageType::kUndo:
-      message.Set("type", "annotationUndo");
-      return message;
+      return base::Value::Dict().Set("type", "annotationUndo");
     case TestAnnotationUndoRedoMessageType::kRedo:
-      message.Set("type", "annotationRedo");
-      return message;
+      return base::Value::Dict().Set("type", "annotationRedo");
   }
   NOTREACHED();
 }
diff --git a/sandbox/linux/BUILD.gn b/sandbox/linux/BUILD.gn
index d642c7d..e56c1423 100644
--- a/sandbox/linux/BUILD.gn
+++ b/sandbox/linux/BUILD.gn
@@ -377,9 +377,9 @@
     "system_headers/arm_linux_syscalls.h",
     "system_headers/arm_linux_ucontext.h",
     "system_headers/i386_linux_ucontext.h",
-    "system_headers/landlock.h",
     "system_headers/linux_filter.h",
     "system_headers/linux_futex.h",
+    "system_headers/linux_landlock.h",
     "system_headers/linux_prctl.h",
     "system_headers/linux_ptrace.h",
     "system_headers/linux_seccomp.h",
diff --git a/sandbox/linux/services/syscall_wrappers.cc b/sandbox/linux/services/syscall_wrappers.cc
index df4a991..4d12e51 100644
--- a/sandbox/linux/services/syscall_wrappers.cc
+++ b/sandbox/linux/services/syscall_wrappers.cc
@@ -215,4 +215,16 @@
   return syscall(__NR_landlock_create_ruleset, attr, size, flags);
 }
 
+int landlock_add_rule(const int ruleset_fd,
+                      const int rule_type,
+                      const void* const rule_attr,
+                      const uint32_t flags) {
+  return syscall(__NR_landlock_add_rule, ruleset_fd, rule_type, rule_attr,
+                 flags);
+}
+
+int landlock_restrict_self(const int ruleset_fd, const uint32_t flags) {
+  return syscall(__NR_landlock_restrict_self, ruleset_fd, flags);
+}
+
 }  // namespace sandbox
diff --git a/sandbox/linux/services/syscall_wrappers.h b/sandbox/linux/services/syscall_wrappers.h
index a6b22eb..d591dbc 100644
--- a/sandbox/linux/services/syscall_wrappers.h
+++ b/sandbox/linux/services/syscall_wrappers.h
@@ -105,6 +105,12 @@
     const struct landlock_ruleset_attr* const attr,
     const size_t size,
     const uint32_t flags);
+SANDBOX_EXPORT int landlock_add_rule(const int ruleset_fd,
+                                     const int rule_type,
+                                     const void* const rule_attr,
+                                     const uint32_t flags);
+SANDBOX_EXPORT int landlock_restrict_self(const int ruleset_fd,
+                                          const uint32_t flags);
 
 }  // namespace sandbox
 
diff --git a/sandbox/linux/system_headers/landlock.h b/sandbox/linux/system_headers/landlock.h
deleted file mode 100644
index 76c24b4..0000000
--- a/sandbox/linux/system_headers/landlock.h
+++ /dev/null
@@ -1,51 +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.
- */
-
-/*
- * Landlock system definitions.
- *
- * These definitions are based on <linux/landlock.h>. However, because we
- * can't guarantee that header will be available on all systems, they are
- * extracted here. We only include definitions needed for checking the Landlock
- * version, as we just need to determine if the system supports Landlock.
- */
-
-#ifndef SANDBOX_LINUX_SYSTEM_HEADERS_LANDLOCK_H_
-#define SANDBOX_LINUX_SYSTEM_HEADERS_LANDLOCK_H_
-
-#include <stddef.h>
-#include <stdint.h>
-
-/**
- * struct landlock_ruleset_attr - Ruleset definition
- *
- * Argument of sys_landlock_create_ruleset().
- */
-struct landlock_ruleset_attr {
-  /**
-   * @handled_access_fs: Bitmask of actions (cf. `Filesystem flags`_)
-   * that is handled by this ruleset and should then be forbidden if no
-   * rule explicitly allow them.  This is needed for backward
-   * compatibility reasons.
-   */
-  uint64_t handled_access_fs;
-};
-
-/*
- * sys_landlock_create_ruleset() flags:
- *
- * - %LANDLOCK_CREATE_RULESET_VERSION: Get the highest supported Landlock ABI
- *   version.
- */
-#ifndef LANDLOCK_CREATE_RULESET_VERSION
-#define LANDLOCK_CREATE_RULESET_VERSION (1U << 0)
-#endif
-
-// Syscall number for landlock_create_ruleset taken from <asm-generic/unistd.h>.
-#ifndef __NR_landlock_create_ruleset
-#define __NR_landlock_create_ruleset 444
-#endif
-
-#endif  // SANDBOX_LINUX_SYSTEM_HEADERS_LANDLOCK_H_
diff --git a/sandbox/linux/system_headers/linux_landlock.h b/sandbox/linux/system_headers/linux_landlock.h
new file mode 100644
index 0000000..32ef65e
--- /dev/null
+++ b/sandbox/linux/system_headers/linux_landlock.h
@@ -0,0 +1,113 @@
+// 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.
+
+// Landlock system definitions.
+//
+// These definitions are based on <linux/landlock.h>. However, because we
+// can't guarantee that header will be available on all systems, they are
+// extracted here. We only include definitions needed for checking the Landlock
+// version, as we just need to determine if the system supports Landlock.
+
+#ifndef SANDBOX_LINUX_SYSTEM_HEADERS_LINUX_LANDLOCK_H_
+#define SANDBOX_LINUX_SYSTEM_HEADERS_LINUX_LANDLOCK_H_
+
+#include <stddef.h>
+#include <stdint.h>
+
+// struct landlock_ruleset_attr - Ruleset definition
+//
+//  Argument of sys_landlock_create_ruleset().
+struct landlock_ruleset_attr {
+  // @handled_access_fs: Bitmask of actions (cf. `Filesystem flags`_)
+  // that is handled by this ruleset and should then be forbidden if no
+  // rule explicitly allow them.  This is needed for backward
+  // compatibility reasons.
+  uint64_t handled_access_fs;
+};
+
+// sys_landlock_create_ruleset() flags:
+//
+// - %LANDLOCK_CREATE_RULESET_VERSION: Get the highest supported Landlock ABI
+//   version.
+#ifndef LANDLOCK_CREATE_RULESET_VERSION
+#define LANDLOCK_CREATE_RULESET_VERSION (1U << 0)
+#endif
+
+#ifndef LANDLOCK_RULE_PATH_BENEATH
+#define LANDLOCK_RULE_PATH_BENEATH 1
+#endif
+
+// struct landlock_path_beneath_attr - Path hierarchy definition
+//
+// Argument of sys_landlock_add_rule().
+struct landlock_path_beneath_attr {
+  // @allowed_access: Bitmask of allowed actions for this file hierarchy
+  // (cf. `Filesystem flags`_).
+  uint64_t allowed_access;
+  // @parent_fd: File descriptor, open with ``O_PATH``, which identifies
+  // the parent directory of a file hierarchy, or just a file.
+  uint32_t parent_fd;
+};
+
+#ifndef LANDLOCK_ACCESS_FS_EXECUTE
+#define LANDLOCK_ACCESS_FS_EXECUTE (1ULL << 0)
+#endif
+
+#ifndef LANDLOCK_ACCESS_FS_WRITE_FILE
+#define LANDLOCK_ACCESS_FS_WRITE_FILE (1ULL << 1)
+#endif
+
+#ifndef LANDLOCK_ACCESS_FS_READ_FILE
+#define LANDLOCK_ACCESS_FS_READ_FILE (1ULL << 2)
+#endif
+
+#ifndef LANDLOCK_ACCESS_FS_READ_DIR
+#define LANDLOCK_ACCESS_FS_READ_DIR (1ULL << 3)
+#endif
+
+#ifndef LANDLOCK_ACCESS_FS_REMOVE_DIR
+#define LANDLOCK_ACCESS_FS_REMOVE_DIR (1ULL << 4)
+#endif
+
+#ifndef LANDLOCK_ACCESS_FS_REMOVE_FILE
+#define LANDLOCK_ACCESS_FS_REMOVE_FILE (1ULL << 5)
+#endif
+
+#ifndef LANDLOCK_ACCESS_FS_MAKE_CHAR
+#define LANDLOCK_ACCESS_FS_MAKE_CHAR (1ULL << 6)
+#endif
+
+#ifndef LANDLOCK_ACCESS_FS_MAKE_DIR
+#define LANDLOCK_ACCESS_FS_MAKE_DIR (1ULL << 7)
+#endif
+
+#ifndef LANDLOCK_ACCESS_FS_MAKE_REG
+#define LANDLOCK_ACCESS_FS_MAKE_REG (1ULL << 8)
+#endif
+
+#ifndef LANDLOCK_ACCESS_FS_MAKE_SOCK
+#define LANDLOCK_ACCESS_FS_MAKE_SOCK (1ULL << 9)
+#endif
+
+#ifndef LANDLOCK_ACCESS_FS_MAKE_FIFO
+#define LANDLOCK_ACCESS_FS_MAKE_FIFO (1ULL << 10)
+#endif
+
+#ifndef LANDLOCK_ACCESS_FS_MAKE_BLOCK
+#define LANDLOCK_ACCESS_FS_MAKE_BLOCK (1ULL << 11)
+#endif
+
+#ifndef LANDLOCK_ACCESS_FS_MAKE_SYM
+#define LANDLOCK_ACCESS_FS_MAKE_SYM (1ULL << 12)
+#endif
+
+#ifndef LANDLOCK_ACCESS_FS_REFER
+#define LANDLOCK_ACCESS_FS_REFER (1ULL << 13)
+#endif
+
+#ifndef LANDLOCK_ACCESS_FS_TRUNCATE
+#define LANDLOCK_ACCESS_FS_TRUNCATE (1ULL << 14)
+#endif
+
+#endif  // SANDBOX_LINUX_SYSTEM_HEADERS_LINUX_LANDLOCK_H_
diff --git a/sandbox/policy/BUILD.gn b/sandbox/policy/BUILD.gn
index d8b04f5..7d206b8 100644
--- a/sandbox/policy/BUILD.gn
+++ b/sandbox/policy/BUILD.gn
@@ -36,6 +36,8 @@
       "linux/bpf_base_policy_linux.h",
       "linux/bpf_renderer_policy_linux.cc",
       "linux/bpf_renderer_policy_linux.h",
+      "linux/landlock_gpu_policy_android.cc",
+      "linux/landlock_gpu_policy_android.h",
     ]
     deps += [
       "//sandbox:sandbox_buildflags",
diff --git a/sandbox/policy/features.cc b/sandbox/policy/features.cc
index 579745a..b2e59a2 100644
--- a/sandbox/policy/features.cc
+++ b/sandbox/policy/features.cc
@@ -156,6 +156,10 @@
 #endif  // BUILDFLAG(IS_MAC)
 
 #if BUILDFLAG(IS_ANDROID)
+// Enables the experimental Android GPU sandbox using Landlock.
+BASE_FEATURE(kAndroidGpuSandbox,
+             "AndroidGpuSandbox",
+             base::FEATURE_DISABLED_BY_DEFAULT);
 // Enables the renderer on Android to use a separate seccomp policy.
 BASE_FEATURE(kUseRendererProcessPolicy,
              "UseRendererProcessPolicy",
diff --git a/sandbox/policy/features.h b/sandbox/policy/features.h
index 1cc13f0..905de65f 100644
--- a/sandbox/policy/features.h
+++ b/sandbox/policy/features.h
@@ -53,6 +53,7 @@
 #endif  // BUILDFLAG(IS_MAC)
 
 #if BUILDFLAG(IS_ANDROID)
+SANDBOX_POLICY_EXPORT BASE_DECLARE_FEATURE(kAndroidGpuSandbox);
 SANDBOX_POLICY_EXPORT BASE_DECLARE_FEATURE(kUseRendererProcessPolicy);
 SANDBOX_POLICY_EXPORT BASE_DECLARE_FEATURE(kRestrictRendererPoliciesInBaseline);
 SANDBOX_POLICY_EXPORT BASE_DECLARE_FEATURE(kRestrictCloneParameters);
diff --git a/sandbox/policy/linux/landlock_gpu_policy_android.cc b/sandbox/policy/linux/landlock_gpu_policy_android.cc
new file mode 100644
index 0000000..75d921c8
--- /dev/null
+++ b/sandbox/policy/linux/landlock_gpu_policy_android.cc
@@ -0,0 +1,114 @@
+// 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 "sandbox/policy/linux/landlock_gpu_policy_android.h"
+
+#include <string>
+#include <vector>
+
+#include "base/logging.h"
+#include "base/files/scoped_file.h"
+#include "base/threading/thread_id_name_manager.h"
+#include "build/build_config.h"
+#include "sandbox/linux/services/thread_helpers.h"
+
+#if BUILDFLAG(IS_ANDROID)
+#include <fcntl.h>
+#include <sys/prctl.h>
+#include <unistd.h>
+
+namespace sandbox::landlock {
+
+bool AddRulesToPolicy(int ruleset_fd,
+                      const std::vector<std::string>& paths,
+                      uint64_t allowed_access) {
+  bool success = true;
+  for (const auto& path : paths) {
+    base::ScopedFD parent_fd(open(path.c_str(), O_PATH | O_CLOEXEC));
+    if (!parent_fd.is_valid()) {
+      PLOG(ERROR) << "open failed for " << path;
+      continue;
+    }
+    struct landlock_path_beneath_attr path_beneath = {
+        .allowed_access = allowed_access,
+        .parent_fd = static_cast<uint32_t>(parent_fd.get()),
+    };
+
+    if (landlock_add_rule(ruleset_fd, LANDLOCK_RULE_PATH_BENEATH, &path_beneath,
+                          0)) {
+      PLOG(ERROR) << "landlock_add_rule() failed for " << path;
+      success = false;
+    }
+  }
+  return success;
+}
+#endif
+
+bool ApplyLandlock(sandbox::mojom::Sandbox sandbox_type) {
+#if BUILDFLAG(IS_ANDROID)
+  if (sandbox_type != sandbox::mojom::Sandbox::kGpu) {
+    LOG(ERROR) << "Sandbox type not GPU, skipping Landlock";
+    return false;
+  }
+
+  if (!sandbox::ThreadHelpers::IsSingleThreaded()) {
+    LOG(ERROR) << "Not single threaded, skipping Landlock";
+    for (const auto& id : base::ThreadIdNameManager::GetInstance()->GetIds()) {
+      LOG(ERROR) << "ThreadId=" << id << " name:"
+                 << base::ThreadIdNameManager::GetInstance()->GetName(id);
+    }
+    return false;
+  }
+
+  struct landlock_ruleset_attr ruleset_attr = {
+      .handled_access_fs = LANDLOCK_HANDLED_ACCESS_TYPES};
+  base::ScopedFD ruleset_fd(
+      landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0));
+  if (!ruleset_fd.is_valid()) {
+    PLOG(ERROR) << "Ruleset creation failed";
+    return false;
+  }
+
+  std::vector<std::string> allowed_ro_paths = {
+      // RO access to /data because there may be sub-directories that don't
+      // exist yet at policy creation.
+      "/data", "/proc", "/sys", "/var"};
+  uint64_t ro_access =
+      LANDLOCK_ACCESS_FS_READ_FILE | LANDLOCK_ACCESS_FS_READ_DIR;
+  if (!AddRulesToPolicy(ruleset_fd.get(), allowed_ro_paths, ro_access)) {
+    LOG(ERROR) << "Adding Landlock RO rules failed";
+  }
+
+  std::vector<std::string> allowed_rw_paths = {
+      "/data/cache/com.android.chrome",
+      // We need to allowlist /dev, because ashmem creates dynamically named
+      // directories, such as /dev/ashmemc4072f6e-da0f-447e-98d4-b6497e5f57af.
+      // TODO(crbug.com/40215931): Move away from ashmem and remove this.
+      "/dev",
+  };
+  uint64_t rw_access =
+      LANDLOCK_ACCESS_FS_READ_FILE | LANDLOCK_ACCESS_FS_READ_DIR |
+      LANDLOCK_ACCESS_FS_WRITE_FILE | LANDLOCK_ACCESS_FS_REMOVE_FILE;
+  if (!AddRulesToPolicy(ruleset_fd.get(), allowed_rw_paths, rw_access)) {
+    LOG(ERROR) << "Adding Landlock RW rules failed";
+  }
+  // Landlock requires no_new_privs.
+  if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) < 0) {
+    PLOG(ERROR) << "SET_NO_NEW_PRIVS failed";
+    return false;
+  }
+  int result = landlock_restrict_self(ruleset_fd.get(), 0);
+  if (result != 0) {
+    PLOG(ERROR) << "landlock_restrict_self() failed";
+    return false;
+  }
+
+  return true;
+#else  // !BUILDFLAG(IS_ANDROID)
+  // Landlock is not applicable on non-Android platforms.
+  return false;
+#endif
+}
+
+}  // namespace sandbox::landlock
diff --git a/sandbox/policy/linux/landlock_gpu_policy_android.h b/sandbox/policy/linux/landlock_gpu_policy_android.h
new file mode 100644
index 0000000..16ad33ec
--- /dev/null
+++ b/sandbox/policy/linux/landlock_gpu_policy_android.h
@@ -0,0 +1,55 @@
+// Copyright 2025 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Landlock functions and constants.
+
+#ifndef SANDBOX_POLICY_LINUX_LANDLOCK_GPU_POLICY_ANDROID_H_
+#define SANDBOX_POLICY_LINUX_LANDLOCK_GPU_POLICY_ANDROID_H_
+
+#include "sandbox/linux/services/syscall_wrappers.h"
+#include "sandbox/linux/system_headers/linux_landlock.h"
+#include "sandbox/policy/export.h"
+#include "sandbox/policy/mojom/sandbox.mojom.h"
+
+namespace sandbox::landlock {
+
+#define LANDLOCK_ACCESS_FS_ROUGHLY_READ \
+  (LANDLOCK_ACCESS_FS_READ_FILE | LANDLOCK_ACCESS_FS_READ_DIR)
+
+#define LANDLOCK_ACCESS_FS_ROUGHLY_READ_EXECUTE                \
+  (LANDLOCK_ACCESS_FS_EXECUTE | LANDLOCK_ACCESS_FS_READ_FILE | \
+   LANDLOCK_ACCESS_FS_READ_DIR)
+
+#define LANDLOCK_ACCESS_FS_ROUGHLY_BASIC_WRITE                     \
+  (LANDLOCK_ACCESS_FS_WRITE_FILE | LANDLOCK_ACCESS_FS_REMOVE_DIR | \
+   LANDLOCK_ACCESS_FS_REMOVE_FILE | LANDLOCK_ACCESS_FS_MAKE_DIR |  \
+   LANDLOCK_ACCESS_FS_MAKE_REG)
+
+#define LANDLOCK_ACCESS_FS_ROUGHLY_EDIT                            \
+  (LANDLOCK_ACCESS_FS_WRITE_FILE | LANDLOCK_ACCESS_FS_REMOVE_DIR | \
+   LANDLOCK_ACCESS_FS_REMOVE_FILE)
+
+#define LANDLOCK_ACCESS_FS_ROUGHLY_FULL_WRITE                      \
+  (LANDLOCK_ACCESS_FS_WRITE_FILE | LANDLOCK_ACCESS_FS_REMOVE_DIR | \
+   LANDLOCK_ACCESS_FS_REMOVE_FILE | LANDLOCK_ACCESS_FS_MAKE_CHAR | \
+   LANDLOCK_ACCESS_FS_MAKE_DIR | LANDLOCK_ACCESS_FS_MAKE_REG |     \
+   LANDLOCK_ACCESS_FS_MAKE_SOCK | LANDLOCK_ACCESS_FS_MAKE_FIFO |   \
+   LANDLOCK_ACCESS_FS_MAKE_BLOCK | LANDLOCK_ACCESS_FS_MAKE_SYM)
+
+#define LANDLOCK_ACCESS_FILE                                    \
+  (LANDLOCK_ACCESS_FS_EXECUTE | LANDLOCK_ACCESS_FS_WRITE_FILE | \
+   LANDLOCK_ACCESS_FS_READ_FILE)
+
+#define LANDLOCK_HANDLED_ACCESS_TYPES        \
+  (LANDLOCK_ACCESS_FS_ROUGHLY_READ_EXECUTE | \
+   LANDLOCK_ACCESS_FS_ROUGHLY_FULL_WRITE)
+
+// Applies a basic Landlock sandbox policy to the current process.
+// Returns true if the policy was applied successfully, false otherwise.
+// This function is a no-op and returns false on non-Android platforms.
+SANDBOX_POLICY_EXPORT bool ApplyLandlock(mojom::Sandbox sandbox_type);
+
+}  // namespace sandbox::landlock
+
+#endif  // SANDBOX_POLICY_LINUX_LANDLOCK_GPU_POLICY_ANDROID_H_
diff --git a/sandbox/policy/linux/sandbox_linux.cc b/sandbox/policy/linux/sandbox_linux.cc
index 427604b2..ac0c77b 100644
--- a/sandbox/policy/linux/sandbox_linux.cc
+++ b/sandbox/policy/linux/sandbox_linux.cc
@@ -47,7 +47,7 @@
 #include "sandbox/linux/syscall_broker/broker_client.h"
 #include "sandbox/linux/syscall_broker/broker_command.h"
 #include "sandbox/linux/syscall_broker/broker_process.h"
-#include "sandbox/linux/system_headers/landlock.h"
+#include "sandbox/linux/system_headers/linux_landlock.h"
 #include "sandbox/linux/system_headers/linux_stat.h"
 #include "sandbox/policy/features.h"
 #include "sandbox/policy/linux/bpf_broker_policy_linux.h"
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 9ccd457..421beb7 100644
--- a/services/on_device_model/ml/on_device_model_executor.cc
+++ b/services/on_device_model/ml/on_device_model_executor.cc
@@ -410,17 +410,17 @@
 DISABLE_CFI_DLSYM
 on_device_model::Capabilities OnDeviceModelExecutor::GetCapabilities(
     const ChromeML& chrome_ml,
-    on_device_model::ModelAssets assets) {
+    on_device_model::ModelFile model_file) {
   on_device_model::Capabilities result;
   if (!chrome_ml.api().GetCapabilities) {
     return result;
   }
 
   PlatformFile platform_file;
-  if (assets.weights.IsValid()) {
-    platform_file = assets.weights.TakePlatformFile();
+  if (model_file.IsFile()) {
+    platform_file = model_file.file().TakePlatformFile();
   } else {
-    base::File file(assets.weights_path,
+    base::File file(model_file.path(),
                     base::File::FLAG_OPEN | base::File::FLAG_READ);
     platform_file = file.TakePlatformFile();
   }
@@ -531,12 +531,13 @@
   max_tokens_ = std::max(params->max_tokens, kReserveTokensForSafety);
 
   ChromeMLModelData data;
-  std::string weights_path_str = assets.weights_path.AsUTF8Unsafe();
+  std::string weights_path_str;
   std::string sp_model_path_str = assets.sp_model_path.AsUTF8Unsafe();
   // Prefer to load the model from a file descriptor if possible.
-  if (assets.weights.IsValid()) {
-    data.weights_file = assets.weights.TakePlatformFile();
+  if (assets.weights.IsFile()) {
+    data.weights_file = assets.weights.file().TakePlatformFile();
   } else {
+    weights_path_str = assets.weights.path().AsUTF8Unsafe();
     data.model_path = weights_path_str.data();
     data.sentencepiece_model_path = sp_model_path_str.data();
   }
diff --git a/services/on_device_model/ml/on_device_model_executor.h b/services/on_device_model/ml/on_device_model_executor.h
index d06638b..94b251e5 100644
--- a/services/on_device_model/ml/on_device_model_executor.h
+++ b/services/on_device_model/ml/on_device_model_executor.h
@@ -105,7 +105,7 @@
 
   static on_device_model::Capabilities GetCapabilities(
       const ChromeML& chrome_ml,
-      on_device_model::ModelAssets assets);
+      on_device_model::ModelFile model_file);
 
   std::unique_ptr<SessionImpl> CreateSession(
       const ScopedAdaptation* adaptation,
diff --git a/services/on_device_model/on_device_model_service.cc b/services/on_device_model/on_device_model_service.cc
index 2d1044b..d91b04c 100644
--- a/services/on_device_model/on_device_model_service.cc
+++ b/services/on_device_model/on_device_model_service.cc
@@ -445,10 +445,10 @@
   std::move(callback).Run(mojom::LoadModelResult::kSuccess);
 }
 
-void OnDeviceModelService::GetCapabilities(ModelAssets assets,
+void OnDeviceModelService::GetCapabilities(ModelFile model_file,
                                            GetCapabilitiesCallback callback) {
   std::move(callback).Run(ml::OnDeviceModelExecutor::GetCapabilities(
-      *chrome_ml_, std::move(assets)));
+      *chrome_ml_, std::move(model_file)));
 }
 
 void OnDeviceModelService::GetEstimatedPerformanceClass(
diff --git a/services/on_device_model/on_device_model_service.h b/services/on_device_model/on_device_model_service.h
index 0fbc32ce..68afc21 100644
--- a/services/on_device_model/on_device_model_service.h
+++ b/services/on_device_model/on_device_model_service.h
@@ -71,7 +71,7 @@
   void LoadModel(mojom::LoadModelParamsPtr params,
                  mojo::PendingReceiver<mojom::OnDeviceModel> model,
                  LoadModelCallback callback) override;
-  void GetCapabilities(ModelAssets assets,
+  void GetCapabilities(ModelFile model_file,
                        GetCapabilitiesCallback callback) override;
   void LoadTextSafetyModel(
       mojom::TextSafetyModelParamsPtr params,
diff --git a/services/on_device_model/on_device_model_service_unittest.cc b/services/on_device_model/on_device_model_service_unittest.cc
index bd8a90c8..5c38a1549 100644
--- a/services/on_device_model/on_device_model_service_unittest.cc
+++ b/services/on_device_model/on_device_model_service_unittest.cc
@@ -92,6 +92,7 @@
     params->backend_type = backend_type;
     params->performance_hint = performance_hint;
     params->max_tokens = 8000;
+    params->assets = ModelAssets::FromPath(base::FilePath());
     base::test::TestFuture<mojom::LoadModelResult> future;
     service()->LoadModel(std::move(params), remote.BindNewPipeAndPassReceiver(),
                          future.GetCallback());
@@ -647,10 +648,9 @@
   auto expect_capabilities = [&](const std::string& data,
                                  const Capabilities& expected) {
     FakeFile file(data);
-    ModelAssets assets;
-    assets.weights = file.Open();
+    ModelFile model_file(file.Open());
     base::test::TestFuture<const Capabilities&> future;
-    service()->GetCapabilities(std::move(assets), future.GetCallback());
+    service()->GetCapabilities(std::move(model_file), future.GetCallback());
     EXPECT_EQ(expected, future.Take());
   };
   expect_capabilities("none", {});
@@ -664,10 +664,9 @@
   auto expect_capabilities = [&](const std::string& data,
                                  const Capabilities& expected) {
     FakeFile file(data);
-    ModelAssets assets;
-    assets.weights_path = file.Path();
+    ModelFile model_file(file.Path());
     base::test::TestFuture<const Capabilities&> future;
-    service()->GetCapabilities(std::move(assets), future.GetCallback());
+    service()->GetCapabilities(std::move(model_file), future.GetCallback());
     EXPECT_EQ(expected, future.Take());
   };
   expect_capabilities("none", {});
diff --git a/services/on_device_model/public/cpp/BUILD.gn b/services/on_device_model/public/cpp/BUILD.gn
index 62f5dda..c8b5eb4f 100644
--- a/services/on_device_model/public/cpp/BUILD.gn
+++ b/services/on_device_model/public/cpp/BUILD.gn
@@ -33,6 +33,8 @@
     "model_assets.h",
     "model_assets_mojom_traits.cc",
     "model_assets_mojom_traits.h",
+    "model_file_mojom_traits.cc",
+    "model_file_mojom_traits.h",
   ]
   public_deps = [
     "//base",
diff --git a/services/on_device_model/public/cpp/model_assets.cc b/services/on_device_model/public/cpp/model_assets.cc
index 5f25f7d..eb3ed72c 100644
--- a/services/on_device_model/public/cpp/model_assets.cc
+++ b/services/on_device_model/public/cpp/model_assets.cc
@@ -5,13 +5,21 @@
 #include "services/on_device_model/public/cpp/model_assets.h"
 
 #include <cstdint>
-#include <string_view>
+#include <optional>
+#include <utility>
+#include <variant>
 
+#include "base/check.h"
 #include "base/feature_list.h"
 #include "base/files/file.h"
+#include "base/files/file_path.h"
 #include "base/files/file_util.h"
-#include "base/task/thread_pool.h"
 #include "build/build_config.h"
+#include "mojo/public/cpp/bindings/default_construct_tag.h"
+
+#if BUILDFLAG(IS_WIN)
+#include "base/task/thread_pool.h"
+#endif
 
 namespace on_device_model {
 namespace {
@@ -67,20 +75,70 @@
 ModelAssetPaths::ModelAssetPaths(const ModelAssetPaths&) = default;
 ModelAssetPaths::~ModelAssetPaths() = default;
 
-ModelAssets::ModelAssets() = default;
+ModelFile::ModelFile(base::File file) : file_(std::move(file)) {}
 
-ModelAssets::ModelAssets(const ModelAssets& other)
-    : weights(other.weights.Duplicate()),
-      weights_path(other.weights_path),
-      sp_model_path(other.sp_model_path) {}
+ModelFile::ModelFile(base::FilePath path) : file_(std::move(path)) {}
 
-ModelAssets& ModelAssets::operator=(const ModelAssets& other) {
-  weights = other.weights.Duplicate();
-  weights_path = other.weights_path;
-  sp_model_path = other.sp_model_path;
+ModelFile::ModelFile(mojo::DefaultConstruct::Tag) {}
+
+ModelFile::ModelFile(const ModelFile& other) {
+  if (other.IsFile()) {
+    file_ = other.file().Duplicate();
+  } else {
+    file_ = other.path();
+  }
+}
+
+ModelFile& ModelFile::operator=(const ModelFile& other) {
+  if (other.IsFile()) {
+    file_ = other.file().Duplicate();
+  } else {
+    file_ = other.path();
+  }
   return *this;
 }
 
+ModelFile::ModelFile(ModelFile&&) = default;
+ModelFile& ModelFile::operator=(ModelFile&&) = default;
+ModelFile::~ModelFile() = default;
+
+base::File& ModelFile::file() {
+  CHECK(std::holds_alternative<base::File>(file_));
+  return std::get<base::File>(file_);
+}
+
+const base::File& ModelFile::file() const {
+  CHECK(std::holds_alternative<base::File>(file_));
+  return std::get<base::File>(file_);
+}
+
+const base::FilePath& ModelFile::path() const {
+  CHECK(std::holds_alternative<base::FilePath>(file_));
+  return std::get<base::FilePath>(file_);
+}
+
+bool ModelFile::IsFile() const {
+  return std::holds_alternative<base::File>(file_);
+}
+
+// static
+ModelAssets ModelAssets::FromFile(base::File file) {
+  return ModelAssets(ModelFile(std::move(file)));
+}
+
+// static
+ModelAssets ModelAssets::FromPath(base::FilePath path) {
+  return ModelAssets(ModelFile(std::move(path)));
+}
+
+ModelAssets::ModelAssets(ModelFile weights) : weights(std::move(weights)) {}
+
+ModelAssets::ModelAssets(mojo::DefaultConstruct::Tag tag) : weights(tag) {}
+
+ModelAssets::ModelAssets(const ModelAssets& other) = default;
+
+ModelAssets& ModelAssets::operator=(const ModelAssets& other) = default;
+
 ModelAssets::ModelAssets(ModelAssets&&) = default;
 ModelAssets& ModelAssets::operator=(ModelAssets&&) = default;
 ModelAssets::~ModelAssets() = default;
@@ -90,16 +148,13 @@
     PrefetchFile(paths.weights);
   }
 
-  ModelAssets assets;
-  if (!paths.weights.empty()) {
-    if (base::FeatureList::IsEnabled(
-            kForceLoadOnDeviceModelFromFilePathForTesting)) {
-      assets.weights_path = paths.weights;
-    } else {
-      assets.weights = base::File(paths.weights, kWeightsFlags);
-    }
+  if (paths.weights.empty() ||
+      base::FeatureList::IsEnabled(
+          kForceLoadOnDeviceModelFromFilePathForTesting)) {
+    return ModelAssets::FromPath(std::move(paths.weights));
   }
-  return assets;
+
+  return ModelAssets::FromFile(base::File(paths.weights, kWeightsFlags));
 }
 
 AdaptationAssetPaths::AdaptationAssetPaths() = default;
diff --git a/services/on_device_model/public/cpp/model_assets.h b/services/on_device_model/public/cpp/model_assets.h
index 4ef85fb4..71f2788 100644
--- a/services/on_device_model/public/cpp/model_assets.h
+++ b/services/on_device_model/public/cpp/model_assets.h
@@ -5,9 +5,15 @@
 #ifndef SERVICES_ON_DEVICE_MODEL_PUBLIC_CPP_MODEL_ASSETS_H_
 #define SERVICES_ON_DEVICE_MODEL_PUBLIC_CPP_MODEL_ASSETS_H_
 
+#include <optional>
+#include <variant>
+
 #include "base/component_export.h"
 #include "base/files/file.h"
 #include "base/files/file_path.h"
+#include "mojo/public/cpp/bindings/default_construct_tag.h"
+#include "mojo/public/cpp/bindings/union_traits.h"
+#include "services/on_device_model/public/mojom/on_device_model_service.mojom-data-view.h"
 
 namespace on_device_model {
 
@@ -21,18 +27,48 @@
   base::FilePath sp_model;
 };
 
+class COMPONENT_EXPORT(ON_DEVICE_MODEL_CPP) ModelFile {
+ public:
+  explicit ModelFile(base::File file);
+  explicit ModelFile(base::FilePath path);
+  explicit ModelFile(mojo::DefaultConstruct::Tag);
+  ModelFile(const ModelFile&);
+  ModelFile& operator=(const ModelFile&);
+  ModelFile(ModelFile&&);
+  ModelFile& operator=(ModelFile&&);
+  ~ModelFile();
+
+  base::File& file();
+  const base::File& file() const;
+
+  const base::FilePath& path() const;
+
+  bool IsFile() const;
+
+ private:
+  friend struct mojo::UnionTraits<on_device_model::mojom::ModelFileDataView,
+                                  on_device_model::ModelFile>;
+
+  std::variant<base::File, base::FilePath> file_;
+};
+
 // A bundle of opened file assets comprising model description to use for
 // execution.
 struct COMPONENT_EXPORT(ON_DEVICE_MODEL_CPP) ModelAssets {
-  ModelAssets();
+  // Convenience methods which construct `weights` from the given arg.
+  static ModelAssets FromFile(base::File file);
+  static ModelAssets FromPath(base::FilePath path);
+
+  explicit ModelAssets(ModelFile weights);
+
+  explicit ModelAssets(mojo::DefaultConstruct::Tag);
   ModelAssets(const ModelAssets&);
   ModelAssets& operator=(const ModelAssets&);
   ModelAssets(ModelAssets&&);
   ModelAssets& operator=(ModelAssets&&);
   ~ModelAssets();
 
-  base::File weights;
-  base::FilePath weights_path;
+  ModelFile weights;
   base::FilePath sp_model_path;
 };
 
@@ -63,6 +99,7 @@
   AdaptationAssets& operator=(AdaptationAssets&&);
   ~AdaptationAssets();
 
+  // TODO(crbug.com/401011041): Use a ModelFile to represent these members.
   base::File weights;
   base::FilePath weights_path;
 };
diff --git a/services/on_device_model/public/cpp/model_assets_mojom_traits.cc b/services/on_device_model/public/cpp/model_assets_mojom_traits.cc
index aa47d4f5..48537d9f 100644
--- a/services/on_device_model/public/cpp/model_assets_mojom_traits.cc
+++ b/services/on_device_model/public/cpp/model_assets_mojom_traits.cc
@@ -4,6 +4,11 @@
 
 #include "services/on_device_model/public/cpp/model_assets_mojom_traits.h"
 
+#include <optional>
+
+#include "base/files/file_path.h"
+#include "mojo/public/cpp/base/file_path_mojom_traits.h"
+#include "services/on_device_model/public/cpp/model_assets.h"
 #include "services/on_device_model/public/mojom/on_device_model_service.mojom-shared.h"
 
 namespace mojo {
@@ -15,16 +20,12 @@
          on_device_model::ModelAssets* assets) {
   // base::FilePath doesn't have nullable StructTraits, so we need to use
   // optional.
-  std::optional<base::FilePath> weights_path, sp_model_path;
+  std::optional<base::FilePath> sp_model_path;
   bool ok = data.ReadWeights(&assets->weights) &&
-            data.ReadWeightsPath(&weights_path) &&
             data.ReadSpModelPath(&sp_model_path);
   if (!ok) {
     return false;
   }
-  if (weights_path.has_value()) {
-    assets->weights_path = *weights_path;
-  }
   if (sp_model_path.has_value()) {
     assets->sp_model_path = *sp_model_path;
   }
diff --git a/services/on_device_model/public/cpp/model_assets_mojom_traits.h b/services/on_device_model/public/cpp/model_assets_mojom_traits.h
index 142f1c8..2565feb0 100644
--- a/services/on_device_model/public/cpp/model_assets_mojom_traits.h
+++ b/services/on_device_model/public/cpp/model_assets_mojom_traits.h
@@ -5,13 +5,13 @@
 #ifndef SERVICES_ON_DEVICE_MODEL_PUBLIC_CPP_MODEL_ASSETS_MOJOM_TRAITS_H_
 #define SERVICES_ON_DEVICE_MODEL_PUBLIC_CPP_MODEL_ASSETS_MOJOM_TRAITS_H_
 
+#include <utility>
+
 #include "base/component_export.h"
-#include "base/files/file.h"
 #include "base/files/file_path.h"
-#include "mojo/public/cpp/base/file_mojom_traits.h"
-#include "mojo/public/cpp/base/file_path_mojom_traits.h"
-#include "mojo/public/cpp/base/read_only_file_mojom_traits.h"
+#include "mojo/public/cpp/bindings/struct_traits.h"
 #include "services/on_device_model/public/cpp/model_assets.h"
+#include "services/on_device_model/public/cpp/model_file_mojom_traits.h"
 #include "services/on_device_model/public/mojom/on_device_model_service.mojom-shared.h"
 
 namespace mojo {
@@ -20,14 +20,11 @@
 struct COMPONENT_EXPORT(ON_DEVICE_MODEL_CPP)
     StructTraits<on_device_model::mojom::ModelAssetsDataView,
                  on_device_model::ModelAssets> {
-  static base::File weights(on_device_model::ModelAssets& assets) {
+  static on_device_model::ModelFile weights(
+      on_device_model::ModelAssets& assets) {
     return std::move(assets.weights);
   }
 
-  static base::FilePath weights_path(on_device_model::ModelAssets& assets) {
-    return std::move(assets.weights_path);
-  }
-
   static base::FilePath sp_model_path(on_device_model::ModelAssets& assets) {
     return std::move(assets.sp_model_path);
   }
diff --git a/services/on_device_model/public/cpp/model_file_mojom_traits.cc b/services/on_device_model/public/cpp/model_file_mojom_traits.cc
new file mode 100644
index 0000000..e2254c3
--- /dev/null
+++ b/services/on_device_model/public/cpp/model_file_mojom_traits.cc
@@ -0,0 +1,82 @@
+// Copyright 2025 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "services/on_device_model/public/cpp/model_file_mojom_traits.h"
+
+#include <optional>
+#include <utility>
+#include <variant>
+
+#include "base/files/file.h"
+#include "base/files/file_path.h"
+#include "base/functional/overloaded.h"
+#include "base/notreached.h"
+#include "mojo/public/cpp/base/file_mojom_traits.h"
+#include "mojo/public/cpp/base/file_path_mojom_traits.h"
+#include "services/on_device_model/public/cpp/model_assets.h"
+#include "services/on_device_model/public/mojom/on_device_model_service.mojom-shared.h"
+
+namespace mojo {
+
+// static
+base::File UnionTraits<
+    on_device_model::mojom::ModelFileDataView,
+    on_device_model::ModelFile>::file(on_device_model::ModelFile& file) {
+  return std::get<base::File>(std::move(file.file_));
+}
+
+// static
+base::FilePath UnionTraits<
+    on_device_model::mojom::ModelFileDataView,
+    on_device_model::ModelFile>::path(on_device_model::ModelFile& file) {
+  return std::get<base::FilePath>(std::move(file.file_));
+}
+
+// static
+bool UnionTraits<on_device_model::mojom::ModelFileDataView,
+                 on_device_model::ModelFile>::
+    Read(on_device_model::mojom::ModelFileDataView data,
+         on_device_model::ModelFile* out) {
+  switch (data.tag()) {
+    case on_device_model::mojom::ModelFileDataView::Tag::kFile: {
+      base::File file;
+      if (!data.ReadFile(&file)) {
+        return false;
+      }
+      *out = on_device_model::ModelFile(std::move(file));
+      return true;
+    }
+    case on_device_model::mojom::ModelFileDataView::Tag::kPath: {
+      // base::FilePath doesn't have nullable StructTraits, so we need to use
+      // optional.
+      std::optional<base::FilePath> path;
+      if (!data.ReadPath(&path)) {
+        return false;
+      }
+      *out = on_device_model::ModelFile(
+          std::move(path).value_or(base::FilePath()));
+      return true;
+    }
+    default:
+      return false;
+  }
+}
+
+// static
+on_device_model::mojom::ModelFileDataView::Tag UnionTraits<
+    on_device_model::mojom::ModelFileDataView,
+    on_device_model::ModelFile>::GetTag(const on_device_model::ModelFile&
+                                            file) {
+  return std::visit(
+      base::Overloaded{
+          [](const base::File& file) {
+            return on_device_model::mojom::ModelFileDataView::Tag::kFile;
+          },
+          [](const base::FilePath& path) {
+            return on_device_model::mojom::ModelFileDataView::Tag::kPath;
+          }},
+      file.file_);
+}
+
+}  // namespace mojo
diff --git a/services/on_device_model/public/cpp/model_file_mojom_traits.h b/services/on_device_model/public/cpp/model_file_mojom_traits.h
new file mode 100644
index 0000000..3d33f985
--- /dev/null
+++ b/services/on_device_model/public/cpp/model_file_mojom_traits.h
@@ -0,0 +1,34 @@
+// Copyright 2025 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef SERVICES_ON_DEVICE_MODEL_PUBLIC_CPP_MODEL_FILE_MOJOM_TRAITS_H_
+#define SERVICES_ON_DEVICE_MODEL_PUBLIC_CPP_MODEL_FILE_MOJOM_TRAITS_H_
+
+#include "base/component_export.h"
+#include "base/files/file.h"
+#include "base/files/file_path.h"
+#include "mojo/public/cpp/bindings/union_traits.h"
+#include "services/on_device_model/public/cpp/model_assets.h"
+#include "services/on_device_model/public/mojom/on_device_model_service.mojom-shared.h"
+
+namespace mojo {
+
+template <>
+class COMPONENT_EXPORT(ON_DEVICE_MODEL_CPP)
+    UnionTraits<on_device_model::mojom::ModelFileDataView,
+                on_device_model::ModelFile> {
+ public:
+  static base::File file(on_device_model::ModelFile& file);
+  static base::FilePath path(on_device_model::ModelFile& file);
+
+  static bool Read(on_device_model::mojom::ModelFileDataView data,
+                   on_device_model::ModelFile* weights);
+
+  static on_device_model::mojom::ModelFileDataView::Tag GetTag(
+      const on_device_model::ModelFile& weights);
+};
+
+}  // namespace mojo
+
+#endif  // SERVICES_ON_DEVICE_MODEL_PUBLIC_CPP_MODEL_FILE_MOJOM_TRAITS_H_
diff --git a/services/on_device_model/public/cpp/test_support/fake_service.cc b/services/on_device_model/public/cpp/test_support/fake_service.cc
index f64fd34..7b2ebeb 100644
--- a/services/on_device_model/public/cpp/test_support/fake_service.cc
+++ b/services/on_device_model/public/cpp/test_support/fake_service.cc
@@ -388,7 +388,7 @@
     return;
   }
   FakeOnDeviceModel::Data data;
-  data.base_weight = ReadFile(params->assets.weights);
+  data.base_weight = ReadFile(params->assets.weights.file());
   auto test_model = std::make_unique<FakeOnDeviceModel>(
       settings_, std::move(data), params->performance_hint);
   model_receivers_.Add(std::move(test_model), std::move(model));
@@ -396,9 +396,9 @@
 }
 
 void FakeOnDeviceModelService::GetCapabilities(
-    ModelAssets assets,
+    ModelFile model_file,
     GetCapabilitiesCallback callback) {
-  std::string contents = ReadFile(assets.weights);
+  std::string contents = ReadFile(model_file.file());
   Capabilities capabilities;
   if (contents.find("image") != std::string::npos) {
     capabilities.Put(CapabilityFlags::kImageInput);
diff --git a/services/on_device_model/public/cpp/test_support/fake_service.h b/services/on_device_model/public/cpp/test_support/fake_service.h
index 2496132..c797250f 100644
--- a/services/on_device_model/public/cpp/test_support/fake_service.h
+++ b/services/on_device_model/public/cpp/test_support/fake_service.h
@@ -212,7 +212,7 @@
   void LoadModel(mojom::LoadModelParamsPtr params,
                  mojo::PendingReceiver<mojom::OnDeviceModel> model,
                  LoadModelCallback callback) override;
-  void GetCapabilities(ModelAssets assets,
+  void GetCapabilities(ModelFile model_file,
                        GetCapabilitiesCallback callback) override;
   void LoadTextSafetyModel(
       mojom::TextSafetyModelParamsPtr params,
diff --git a/services/on_device_model/public/mojom/BUILD.gn b/services/on_device_model/public/mojom/BUILD.gn
index 7f61f2b..0d7be5f 100644
--- a/services/on_device_model/public/mojom/BUILD.gn
+++ b/services/on_device_model/public/mojom/BUILD.gn
@@ -32,6 +32,13 @@
         {
           mojom = "on_device_model.mojom.ModelAssets"
           cpp = "::on_device_model::ModelAssets"
+          default_constructible = false
+          move_only = true
+        },
+        {
+          mojom = "on_device_model.mojom.ModelFile"
+          cpp = "::on_device_model::ModelFile"
+          default_constructible = false
           move_only = true
         },
         {
@@ -61,6 +68,7 @@
         "//services/on_device_model/public/cpp/adaptation_assets_mojom_traits.h",
         "//services/on_device_model/public/cpp/capabilities_mojom_traits.h",
         "//services/on_device_model/public/cpp/model_assets_mojom_traits.h",
+        "//services/on_device_model/public/cpp/model_file_mojom_traits.h",
       ]
       traits_sources =
           [ "//services/on_device_model/ml/chrome_ml_types_traits.cc" ]
diff --git a/services/on_device_model/public/mojom/on_device_model_service.mojom b/services/on_device_model/public/mojom/on_device_model_service.mojom
index 75961c59..7758a2ef 100644
--- a/services/on_device_model/public/mojom/on_device_model_service.mojom
+++ b/services/on_device_model/public/mojom/on_device_model_service.mojom
@@ -11,17 +11,21 @@
 import "sandbox/policy/mojom/sandbox.mojom";
 import "services/on_device_model/public/mojom/on_device_model.mojom";
 
+// Model weights could be passed as an opened file or a file path.
+union ModelFile {
+  // This should be used with the GPU backend.
+  //
+  // TODO(b/313919363): This should be a ReadOnlyFile.
+  mojo_base.mojom.File file;
+
+  // This should be used with the APU backend.
+  mojo_base.mojom.FilePath path;
+};
+
 // Opened file resources needed to define the model.
 struct ModelAssets {
-  // Model weights could be passed as an opened file or a file path.
-  // The backend type will decide which one should be used, or
-  // which one is preferred if both are passed. If both are unset,
-  // usually the operation should fail.
-  // APU backend: weights_path should be used.
-  // GPU backend: weights should be used.
-  // TODO(b/313919363): This should also be a ReadOnlyFile.
-  mojo_base.mojom.File? weights;
-  mojo_base.mojom.FilePath? weights_path;
+  // Model weights. The backend may prefer these to be passed a certain way.
+  ModelFile weights;
 
   // Currently the only usage of sp model will be passed by file path.
   mojo_base.mojom.FilePath? sp_model_path;
@@ -128,9 +132,9 @@
   LoadModel(LoadModelParams params, pending_receiver<OnDeviceModel> model)
       => (LoadModelResult result);
 
-  // Returns the capabilities for a model specified by `params`. Note that this
+  // Returns the capabilities for a model specified by `weights`. Note that this
   // will not load the model, so is much cheaper than calling LoadModel().
-  GetCapabilities(ModelAssets assets) => (Capabilities capabilities);
+  GetCapabilities(ModelFile weights) => (Capabilities capabilities);
 
   // Initializes a new TextSafetyModel with the provided params.
   // The model is disconnected on any errors with it.
diff --git a/services/preferences/tracked/OWNERS b/services/preferences/tracked/OWNERS
index f1e6849..8550559 100644
--- a/services/preferences/tracked/OWNERS
+++ b/services/preferences/tracked/OWNERS
@@ -1,2 +1,3 @@
 gab@chromium.org
 proberge@chromium.org
+wfh@chromium.org
diff --git a/services/screen_ai/screen_ai_ocr_perf_test.cc b/services/screen_ai/screen_ai_ocr_perf_test.cc
index 7ee0092..36c7443 100644
--- a/services/screen_ai/screen_ai_ocr_perf_test.cc
+++ b/services/screen_ai/screen_ai_ocr_perf_test.cc
@@ -17,6 +17,7 @@
 #include "base/json/json_file_value_serializer.h"
 #include "base/logging.h"
 #include "base/strings/string_split.h"
+#include "base/system/sys_info.h"
 #include "base/test/bind.h"
 #include "base/time/time.h"
 #include "base/timer/lap_timer.h"
@@ -113,8 +114,6 @@
 
     library_->SetFileContentFunctions(&OcrTestEnvironment::GetDataSize,
                                       &OcrTestEnvironment::CopyData);
-
-    InitOcr();
   }
 
   void InitOcr() {
@@ -151,12 +150,26 @@
 
   void Benchmark(const std::string& metrics_name,
                  base::RepeatingClosure target_ops) {
+    // Records the available memory before library initialization.
+    int base_mem = static_cast<int>(
+        (base::SysInfo::AmountOfAvailablePhysicalMemory() / (1024 * 1024)));
+    InitOcr();
+
     base::LapTimer lap_timer(kWarmUpIterationCount, base::Seconds(10), 1);
     do {
       target_ops.Run();
       lap_timer.NextLap();
     } while (lap_timer.NumLaps() < kActualIterationCount);
 
+    // Records the memory difference after `PerformOcr()`.
+    // TODO(crbug.com/415902702): Considers to put memory into a separate test.
+    int mem_diff =
+        base_mem -
+        static_cast<int>(base::SysInfo::AmountOfAvailablePhysicalMemory() /
+                         (1024 * 1024));
+    perf_values_.Set(metrics_name + "_mem", mem_diff);
+    LOG(INFO) << "Perf: " << metrics_name << "_mem => " << mem_diff << " mb";
+
     int avg_duration = lap_timer.TimePerLap().InMilliseconds();
     perf_values_.Set(metrics_name, avg_duration);
     LOG(INFO) << "Perf: " << metrics_name << " => " << avg_duration << " ms";
diff --git a/testing/variations/fieldtrial_testing_config.json b/testing/variations/fieldtrial_testing_config.json
index e969e8d..4e8cd2c 100644
--- a/testing/variations/fieldtrial_testing_config.json
+++ b/testing/variations/fieldtrial_testing_config.json
@@ -440,22 +440,6 @@
             ]
         }
     ],
-    "AndroidAutofillLazyFrameworkWrapper": [
-        {
-            "platforms": [
-                "android",
-                "android_webview"
-            ],
-            "experiments": [
-                {
-                    "name": "Enabled",
-                    "enable_features": [
-                        "AndroidAutofillLazyFrameworkWrapper"
-                    ]
-                }
-            ]
-        }
-    ],
     "AndroidBookmarkBar": [
         {
             "platforms": [
@@ -10006,6 +9990,7 @@
                         "GlicDevelopmentCookies",
                         "GlicFreURLConfig",
                         "GlicKeyboardShortcutNewBadge",
+                        "GlicLearnMoreURLConfig",
                         "GlicURLConfig",
                         "IPH_GlicPromo"
                     ]
@@ -10013,6 +9998,22 @@
             ]
         }
     ],
+    "GlicTieredRollout": [
+        {
+            "platforms": [
+                "mac",
+                "windows"
+            ],
+            "experiments": [
+                {
+                    "name": "Enabled",
+                    "enable_features": [
+                        "GlicTieredRollout"
+                    ]
+                }
+            ]
+        }
+    ],
     "GlobalVaapiLock": [
         {
             "platforms": [
diff --git a/third_party/androidx/build.gradle b/third_party/androidx/build.gradle
index a71ddc0..17d7c9e6 100644
--- a/third_party/androidx/build.gradle
+++ b/third_party/androidx/build.gradle
@@ -304,7 +304,7 @@
     google()
     maven {
         // This URL is generated by the fetch_all_androidx.py script.
-        url 'https://androidx.dev/snapshots/builds/13471930/artifacts/repository'
+        url 'https://androidx.dev/snapshots/builds/13474078/artifacts/repository'
     }
     mavenCentral()
 }
diff --git a/third_party/angle b/third_party/angle
index 214b7ce..db33baf 160000
--- a/third_party/angle
+++ b/third_party/angle
@@ -1 +1 @@
-Subproject commit 214b7ce20bb6a308c8e929743cd8402f3f5204dd
+Subproject commit db33baf4eb0d7954f0110cddc30acb9cdc12e2d4
diff --git a/third_party/blink/renderer/bindings/generated_in_core.gni b/third_party/blink/renderer/bindings/generated_in_core.gni
index fb18143b..e0b3fe84 100644
--- a/third_party/blink/renderer/bindings/generated_in_core.gni
+++ b/third_party/blink/renderer/bindings/generated_in_core.gni
@@ -637,6 +637,8 @@
   "$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_scroll_axis.h",
   "$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_scroll_behavior.cc",
   "$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_scroll_behavior.h",
+  "$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_scroll_container.cc",
+  "$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_scroll_container.h",
   "$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_scroll_logical_position.cc",
   "$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_scroll_logical_position.h",
   "$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_scroll_restoration.cc",
diff --git a/third_party/blink/renderer/core/dom/element.cc b/third_party/blink/renderer/core/dom/element.cc
index eb2604a..9168f578 100644
--- a/third_party/blink/renderer/core/dom/element.cc
+++ b/third_party/blink/renderer/core/dom/element.cc
@@ -46,6 +46,7 @@
 #include "third_party/blink/renderer/bindings/core/v8/v8_aria_notification_options.h"
 #include "third_party/blink/renderer/bindings/core/v8/v8_check_visibility_options.h"
 #include "third_party/blink/renderer/bindings/core/v8/v8_pointer_lock_options.h"
+#include "third_party/blink/renderer/bindings/core/v8/v8_scroll_container.h"
 #include "third_party/blink/renderer/bindings/core/v8/v8_scroll_into_view_options.h"
 #include "third_party/blink/renderer/bindings/core/v8/v8_scroll_to_options.h"
 #include "third_party/blink/renderer/bindings/core/v8/v8_shadow_root_init.h"
@@ -1856,8 +1857,13 @@
   mojom::blink::ScrollIntoViewParamsPtr params =
       scroll_into_view_util::CreateScrollIntoViewParams(*options,
                                                         *GetComputedStyle());
+  Element* container = nullptr;
+  if (options->hasContainer() &&
+      options->container() == V8ScrollContainer::Enum::kNearest) {
+    container = this;
+  }
 
-  ScrollIntoViewNoVisualUpdate(std::move(params));
+  ScrollIntoViewNoVisualUpdate(std::move(params), container);
 }
 
 // TODO(crbug.com/385129957): This only searches up to the nearest scroll
diff --git a/third_party/blink/renderer/core/frame/scroll_into_view_options.idl b/third_party/blink/renderer/core/frame/scroll_into_view_options.idl
index 117a7aeb..3ff462c1 100644
--- a/third_party/blink/renderer/core/frame/scroll_into_view_options.idl
+++ b/third_party/blink/renderer/core/frame/scroll_into_view_options.idl
@@ -1,6 +1,8 @@
 enum ScrollLogicalPosition { "start", "center", "end", "nearest" };
+enum ScrollContainer { "all", "nearest" };
 
 dictionary ScrollIntoViewOptions : ScrollOptions {
     ScrollLogicalPosition block = "start";
     [ImplementedAs=inlinePosition] ScrollLogicalPosition inline = "nearest";
-};
\ No newline at end of file
+    [RuntimeEnabled=ScrollIntoViewNearest] ScrollContainer container = "all";
+};
diff --git a/third_party/blink/renderer/core/frame/web_frame_widget_test.cc b/third_party/blink/renderer/core/frame/web_frame_widget_test.cc
index 0976cac7..57ba4690 100644
--- a/third_party/blink/renderer/core/frame/web_frame_widget_test.cc
+++ b/third_party/blink/renderer/core/frame/web_frame_widget_test.cc
@@ -23,6 +23,7 @@
 #include "third_party/blink/renderer/core/dom/events/add_event_listener_options_resolved.h"
 #include "third_party/blink/renderer/core/dom/events/native_event_listener.h"
 #include "third_party/blink/renderer/core/editing/editing_utilities.h"
+#include "third_party/blink/renderer/core/execution_context/agent.h"
 #include "third_party/blink/renderer/core/frame/local_dom_window.h"
 #include "third_party/blink/renderer/core/frame/web_frame_widget_impl.h"
 #include "third_party/blink/renderer/core/frame/web_local_frame_impl.h"
@@ -32,6 +33,7 @@
 #include "third_party/blink/renderer/core/html/html_div_element.h"
 #include "third_party/blink/renderer/core/html/html_image_element.h"
 #include "third_party/blink/renderer/core/input/event_handler.h"
+#include "third_party/blink/renderer/core/layout/layout_image.h"
 #include "third_party/blink/renderer/core/paint/paint_layer_scrollable_area.h"
 #include "third_party/blink/renderer/core/testing/sim/sim_request.h"
 #include "third_party/blink/renderer/core/testing/sim/sim_test.h"
@@ -692,6 +694,41 @@
   }
 }
 
+TEST_F(WebFrameWidgetImplSimTest, SpeculativeImageDecodeBeforeLayout) {
+  // Check that a speculative decode can start as soon as an img element gets a
+  // src based on prior layout information, without waiting for a subsequent
+  // layout to happen.
+  base::test::ScopedFeatureList feature_list(
+      features::kSpeculativeImageDecodes);
+  SimRequest request("https://example.com/test.html", "text/html");
+  LoadURL("https://example.com/test.html");
+  request.Complete(R"HTML(
+      <!DOCTYPE html>
+      <html><body><img width=13 height=17/></body></html>
+  )HTML");
+  Compositor().BeginFrame();
+  HTMLImageElement* image =
+      To<HTMLImageElement>(GetDocument().QuerySelector(AtomicString("img")));
+  LayoutImage* layout_image = To<LayoutImage>(image->GetLayoutObject());
+  EXPECT_EQ(layout_image->CachedResourcePriority().visibility,
+            ResourcePriority::kVisible);
+  // Decode size should be based on layout size (note that this does not
+  // actually match the intrinsic size of the data URL below.
+  EXPECT_EQ(layout_image->CachedSpeculativeDecodeSize(), gfx::Size(13, 17));
+
+  image->setAttribute(
+      html_names::kSrcAttr,
+      AtomicString("data:image/"
+                   "png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+"
+                   "9AAAAAXNSR0IArs4c6QAAABhJREFUKFNjbGj48J+BCMA4qhBfKFE/"
+                   "eACQKR1hvTllHQAAAABJRU5ErkJggg=="));
+  // The fetch is initiated synchronously from a microtask after src is set. For
+  // a data URL the load will also finish synchronously, and the speculative
+  // decode should have been triggered, based on pre-computed visibility.
+  EXPECT_CALL(*MockMainFrameWidget(), RequestDecode(_, _, true)).Times(1);
+  GetDocument().GetAgent().PerformMicrotaskCheckpoint();
+}
+
 #if BUILDFLAG(IS_WIN)
 struct ProximateBoundsCollectionArgs final {
   base::RepeatingCallback<gfx::Rect(const Document&)>
diff --git a/third_party/blink/renderer/core/html/html_image_element.cc b/third_party/blink/renderer/core/html/html_image_element.cc
index c742a6a7..11bab51 100644
--- a/third_party/blink/renderer/core/html/html_image_element.cc
+++ b/third_party/blink/renderer/core/html/html_image_element.cc
@@ -520,6 +520,9 @@
       LayoutImage* image = MakeGarbageCollected<LayoutImage>(this);
       image->SetImageResource(MakeGarbageCollected<LayoutImageResource>());
       image->SetImageDevicePixelRatio(image_device_pixel_ratio_);
+      if (base::FeatureList::IsEnabled(features::kSpeculativeImageDecodes)) {
+        GetDocument().View()->RegisterForLifecycleNotifications(this);
+      }
       return image;
     }
     case LayoutDisposition::kCollapsed:  // Falls through.
@@ -760,9 +763,27 @@
   is_ad_related_ = true;
 }
 
+void HTMLImageElement::DidFinishLayout() {
+  if (base::FeatureList::IsEnabled(features::kSpeculativeImageDecodes)) {
+    if (LayoutImage* layout_image = DynamicTo<LayoutImage>(GetLayoutObject())) {
+      // Populate cached values for load priority and speculative decode
+      // parameters.
+      layout_image->ComputeResourcePriority();
+      layout_image->ComputeSpeculativeDecodeSize();
+      layout_image->ComputeSpeculativeDecodeQuality();
+      // Once the image has a source ResourceFetcher will take over the updates.
+      if (!is_ad_related_ && GetImageLoader().GetContent()) {
+        GetDocument().View()->UnregisterFromLifecycleNotifications(this);
+      }
+    }
+  }
+}
+
 void HTMLImageElement::DidFinishLifecycleUpdate(
     const LocalFrameView& local_frame_view) {
-  DCHECK(is_ad_related_);
+  if (!is_ad_related_) {
+    return;
+  }
 
   // Scope to the outermost frame to avoid counting image ads that are (likely)
   // already in ad iframes.
diff --git a/third_party/blink/renderer/core/html/html_image_element.h b/third_party/blink/renderer/core/html/html_image_element.h
index 7e6ed80..e8f5f43 100644
--- a/third_party/blink/renderer/core/html/html_image_element.h
+++ b/third_party/blink/renderer/core/html/html_image_element.h
@@ -261,6 +261,7 @@
   void CreateMediaQueryListIfDoesNotExist();
 
   // LocalFrameView::LifecycleNotificationObserver
+  void DidFinishLayout() override;
   void DidFinishLifecycleUpdate(const LocalFrameView&) override;
 
   Member<HTMLImageLoader> image_loader_;
diff --git a/third_party/blink/renderer/core/layout/layout_box.h b/third_party/blink/renderer/core/layout/layout_box.h
index 70bf1252..40c10b80 100644
--- a/third_party/blink/renderer/core/layout/layout_box.h
+++ b/third_party/blink/renderer/core/layout/layout_box.h
@@ -854,7 +854,7 @@
   PhysicalRect ClippingRect(const PhysicalOffset& location) const;
 
   void ImageChanged(WrappedImagePtr, CanDeferInvalidation) override;
-  ResourcePriority ComputeResourcePriority() const final;
+  ResourcePriority ComputeResourcePriority() const override;
 
   PositionWithAffinity PositionForPointInFragments(const PhysicalOffset&) const;
 
diff --git a/third_party/blink/renderer/core/layout/layout_image.cc b/third_party/blink/renderer/core/layout/layout_image.cc
index 1a22f77..10eb7ce 100644
--- a/third_party/blink/renderer/core/layout/layout_image.cc
+++ b/third_party/blink/renderer/core/layout/layout_image.cc
@@ -217,6 +217,36 @@
   return !is_fixed_sized;
 }
 
+ResourcePriority LayoutImage::ComputeResourcePriority() const {
+  speculative_decode_parameters_.cached_resource_priority =
+      LayoutReplaced::ComputeResourcePriority();
+  return speculative_decode_parameters_.cached_resource_priority;
+}
+
+ResourcePriority LayoutImage::CachedResourcePriority() const {
+  return speculative_decode_parameters_.cached_resource_priority;
+}
+
+gfx::Size LayoutImage::ComputeSpeculativeDecodeSize() const {
+  speculative_decode_parameters_.cached_speculative_decode_size =
+      LayoutReplaced::ComputeSpeculativeDecodeSize();
+  return speculative_decode_parameters_.cached_speculative_decode_size;
+}
+
+gfx::Size LayoutImage::CachedSpeculativeDecodeSize() const {
+  return speculative_decode_parameters_.cached_speculative_decode_size;
+}
+
+InterpolationQuality LayoutImage::ComputeSpeculativeDecodeQuality() const {
+  speculative_decode_parameters_.cached_speculative_decode_quality =
+      LayoutReplaced::ComputeSpeculativeDecodeQuality();
+  return speculative_decode_parameters_.cached_speculative_decode_quality;
+}
+
+InterpolationQuality LayoutImage::CachedSpeculativeDecodeQuality() const {
+  return speculative_decode_parameters_.cached_speculative_decode_quality;
+}
+
 bool LayoutImage::InvalidateLayoutOnNaturalSizeChange() {
   SetIntrinsicLogicalWidthsDirty();
 
diff --git a/third_party/blink/renderer/core/layout/layout_image.h b/third_party/blink/renderer/core/layout/layout_image.h
index b0efc66a..ef6e86d 100644
--- a/third_party/blink/renderer/core/layout/layout_image.h
+++ b/third_party/blink/renderer/core/layout/layout_image.h
@@ -101,6 +101,13 @@
       ImageChanged(image_resource_->ImagePtr(), CanDeferInvalidation::kNo);
   }
 
+  ResourcePriority ComputeResourcePriority() const final;
+  ResourcePriority CachedResourcePriority() const final;
+  gfx::Size ComputeSpeculativeDecodeSize() const final;
+  gfx::Size CachedSpeculativeDecodeSize() const final;
+  InterpolationQuality ComputeSpeculativeDecodeQuality() const final;
+  InterpolationQuality CachedSpeculativeDecodeQuality() const final;
+
   const char* GetName() const override {
     NOT_DESTROYED();
     return "LayoutImage";
@@ -191,6 +198,12 @@
 
   friend class MutableForPainting;
   PhysicalRect last_paint_rect_;
+
+  mutable struct {
+    ResourcePriority cached_resource_priority;
+    gfx::Size cached_speculative_decode_size;
+    InterpolationQuality cached_speculative_decode_quality;
+  } speculative_decode_parameters_;
 };
 
 template <>
diff --git a/third_party/blink/renderer/core/layout/layout_object.cc b/third_party/blink/renderer/core/layout/layout_object.cc
index 7ebadec3..4b290c86 100644
--- a/third_party/blink/renderer/core/layout/layout_object.cc
+++ b/third_party/blink/renderer/core/layout/layout_object.cc
@@ -4408,7 +4408,7 @@
   return true;
 }
 
-InterpolationQuality LayoutObject::GetSpeculativeDecodeQuality() const {
+InterpolationQuality LayoutObject::ComputeSpeculativeDecodeQuality() const {
   NOT_DESTROYED();
   return StyleRef().GetInterpolationQuality();
 }
diff --git a/third_party/blink/renderer/core/layout/layout_object.h b/third_party/blink/renderer/core/layout/layout_object.h
index 6b637b7a..cd0e0a7 100644
--- a/third_party/blink/renderer/core/layout/layout_object.h
+++ b/third_party/blink/renderer/core/layout/layout_object.h
@@ -2665,7 +2665,7 @@
   void NotifyImageFullyRemoved(ImageResourceContent*) override;
   bool WillRenderImage() final;
   bool GetImageAnimationPolicy(mojom::blink::ImageAnimationPolicy&) final;
-  InterpolationQuality GetSpeculativeDecodeQuality() const final;
+  InterpolationQuality ComputeSpeculativeDecodeQuality() const override;
 
   void Remove() {
     NOT_DESTROYED();
diff --git a/third_party/blink/renderer/core/layout/layout_replaced.cc b/third_party/blink/renderer/core/layout/layout_replaced.cc
index acaa092b..dcd39dca 100644
--- a/third_party/blink/renderer/core/layout/layout_replaced.cc
+++ b/third_party/blink/renderer/core/layout/layout_replaced.cc
@@ -435,7 +435,7 @@
   return LayoutBox::PositionForPoint(point);
 }
 
-gfx::Size LayoutReplaced::GetSpeculativeDecodeSize() const {
+gfx::Size LayoutReplaced::ComputeSpeculativeDecodeSize() const {
   NOT_DESTROYED();
   return ReplacedContentRect().PixelSnappedSize();
 }
diff --git a/third_party/blink/renderer/core/layout/layout_replaced.h b/third_party/blink/renderer/core/layout/layout_replaced.h
index 7a0cc07..5f88674e 100644
--- a/third_party/blink/renderer/core/layout/layout_replaced.h
+++ b/third_party/blink/renderer/core/layout/layout_replaced.h
@@ -159,7 +159,7 @@
   }
 
   // ImageResourceObserver
-  gfx::Size GetSpeculativeDecodeSize() const override;
+  gfx::Size ComputeSpeculativeDecodeSize() const override;
 
  private:
   // Computes a rect, relative to the element's content's natural size, that
diff --git a/third_party/blink/renderer/core/loader/image_loader.cc b/third_party/blink/renderer/core/loader/image_loader.cc
index 74dc54f..f072abe 100644
--- a/third_party/blink/renderer/core/loader/image_loader.cc
+++ b/third_party/blink/renderer/core/loader/image_loader.cc
@@ -617,6 +617,7 @@
     // dispatched.
     if (new_image_content) {
       new_image_content->AddObserver(this);
+      document.Fetcher()->MaybeStartSpeculativeImageDecode();
     }
     if (old_image_content) {
       old_image_content->RemoveObserver(this);
diff --git a/third_party/blink/renderer/core/loader/resource/image_resource_content.cc b/third_party/blink/renderer/core/loader/resource/image_resource_content.cc
index fa680b5..43aac37 100644
--- a/third_party/blink/renderer/core/loader/resource/image_resource_content.cc
+++ b/third_party/blink/renderer/core/loader/resource/image_resource_content.cc
@@ -151,6 +151,10 @@
     ProhibitAddRemoveObserverInScope prohibit_add_remove_observer_in_scope(
         this);
     observers_.insert(observer);
+    ApplyPriorityAndSpeculativeDecodeParams(
+        observer->CachedResourcePriority(),
+        observer->CachedSpeculativeDecodeSize(),
+        observer->CachedSpeculativeDecodeQuality());
   }
 
   if (info_->IsCacheValidator())
@@ -188,30 +192,6 @@
   info_->DidRemoveClientOrObserver();
 }
 
-static void PriorityFromObserver(
-    const ImageResourceObserver* observer,
-    ResourcePriority& priority,
-    ResourcePriority& priority_excluding_image_loader) {
-  ResourcePriority next_priority = observer->ComputeResourcePriority();
-  if (next_priority.is_lcp_resource) {
-    // Mark the resource as predicted LCP despite its visibility.
-    priority.is_lcp_resource = true;
-    priority_excluding_image_loader.is_lcp_resource = true;
-  }
-
-  if (next_priority.visibility == ResourcePriority::kNotVisible)
-    return;
-
-  priority.visibility = ResourcePriority::kVisible;
-  priority.intra_priority_value += next_priority.intra_priority_value;
-
-  if (next_priority.source != ResourcePriority::Source::kImageLoader) {
-    priority_excluding_image_loader.visibility = ResourcePriority::kVisible;
-    priority_excluding_image_loader.intra_priority_value +=
-        next_priority.intra_priority_value;
-  }
-}
-
 void ImageResourceContent::UpdateResourceInfoFromObservers() {
   ProhibitAddRemoveObserverInScope prohibit_add_remove_observer_in_scope(this);
 
@@ -221,12 +201,10 @@
   cached_info_.max_interpolation_quality_ = kInterpolationNone;
 
   auto update = [this](const ImageResourceObserver* observer) -> void {
-    PriorityFromObserver(observer, cached_info_.priority_,
-                         cached_info_.priority_excluding_image_loader_);
-    cached_info_.max_size_.SetToMax(observer->GetSpeculativeDecodeSize());
-    cached_info_.max_interpolation_quality_ =
-        std::max(cached_info_.max_interpolation_quality_,
-                 observer->GetSpeculativeDecodeQuality());
+    ApplyPriorityAndSpeculativeDecodeParams(
+        observer->ComputeResourcePriority(),
+        observer->ComputeSpeculativeDecodeSize(),
+        observer->ComputeSpeculativeDecodeQuality());
   };
 
   for (const auto& it : finished_observers_) {
@@ -237,6 +215,35 @@
   }
 }
 
+void ImageResourceContent::ApplyPriorityAndSpeculativeDecodeParams(
+    const ResourcePriority& new_priority,
+    const gfx::Size& new_size,
+    InterpolationQuality new_quality) {
+  if (new_priority.is_lcp_resource) {
+    // Mark the resource as predicted LCP despite its visibility.
+    cached_info_.priority_.is_lcp_resource = true;
+    cached_info_.priority_excluding_image_loader_.is_lcp_resource = true;
+  }
+
+  if (new_priority.visibility == ResourcePriority::kNotVisible) {
+    return;
+  }
+
+  cached_info_.priority_.visibility = ResourcePriority::kVisible;
+  cached_info_.priority_.intra_priority_value +=
+      new_priority.intra_priority_value;
+
+  if (new_priority.source != ResourcePriority::Source::kImageLoader) {
+    cached_info_.priority_excluding_image_loader_.visibility =
+        ResourcePriority::kVisible;
+    cached_info_.priority_excluding_image_loader_.intra_priority_value +=
+        new_priority.intra_priority_value;
+  }
+  cached_info_.max_size_.SetToMax(new_size);
+  cached_info_.max_interpolation_quality_ =
+      std::max(cached_info_.max_interpolation_quality_, new_quality);
+}
+
 bool ImageResourceContent::CanBeSpeculativelyDecoded() const {
   for (const auto& it : finished_observers_) {
     if (!it.key->CanBeSpeculativelyDecoded()) {
diff --git a/third_party/blink/renderer/core/loader/resource/image_resource_content.h b/third_party/blink/renderer/core/loader/resource/image_resource_content.h
index b7e3ae8..23c67ac 100644
--- a/third_party/blink/renderer/core/loader/resource/image_resource_content.h
+++ b/third_party/blink/renderer/core/loader/resource/image_resource_content.h
@@ -252,6 +252,10 @@
   void HandleObserverFinished(ImageResourceObserver*);
   void UpdateToLoadedContentStatus(ResourceStatus);
   void UpdateImageAnimationPolicy();
+  void ApplyPriorityAndSpeculativeDecodeParams(
+      const ResourcePriority& new_priority,
+      const gfx::Size& new_size,
+      InterpolationQuality new_quality);
 
   class ProhibitAddRemoveObserverInScope : public base::AutoReset<bool> {
    public:
diff --git a/third_party/blink/renderer/core/loader/resource/image_resource_observer.h b/third_party/blink/renderer/core/loader/resource/image_resource_observer.h
index 05b9b03..cf28f201 100644
--- a/third_party/blink/renderer/core/loader/resource/image_resource_observer.h
+++ b/third_party/blink/renderer/core/loader/resource/image_resource_observer.h
@@ -82,14 +82,24 @@
     return false;
   }
 
-  // Return the observer's requested resource priority. An implementation of
-  // this method is not allowed to add or remove ImageResource observers.
+  // Compute and return the observer's requested resource priority. An
+  // implementation of this method is not allowed to add or remove ImageResource
+  // observers.
   virtual ResourcePriority ComputeResourcePriority() const {
     return ResourcePriority();
   }
+  // Return the last computed ResourcePriority, if available.
+  virtual ResourcePriority CachedResourcePriority() const {
+    return ResourcePriority();
+  }
+
   virtual bool CanBeSpeculativelyDecoded() const { return true; }
-  virtual gfx::Size GetSpeculativeDecodeSize() const { return gfx::Size(); }
-  virtual InterpolationQuality GetSpeculativeDecodeQuality() const {
+  virtual gfx::Size ComputeSpeculativeDecodeSize() const { return gfx::Size(); }
+  virtual gfx::Size CachedSpeculativeDecodeSize() const { return gfx::Size(); }
+  virtual InterpolationQuality ComputeSpeculativeDecodeQuality() const {
+    return kInterpolationNone;
+  }
+  virtual InterpolationQuality CachedSpeculativeDecodeQuality() const {
     return kInterpolationNone;
   }
 
diff --git a/third_party/blink/renderer/core/scroll/scroll_into_view_util.cc b/third_party/blink/renderer/core/scroll/scroll_into_view_util.cc
index 342cbe48..20974abc 100644
--- a/third_party/blink/renderer/core/scroll/scroll_into_view_util.cc
+++ b/third_party/blink/renderer/core/scroll/scroll_into_view_util.cc
@@ -282,10 +282,6 @@
       break;
     }
 
-    if (stop_at.Contains(current_box)) {
-      break;
-    }
-
     // If the scroll was stopped prior to reaching the local root, we cannot
     // return a rect since the caller cannot know which frame it's relative to.
     std::optional<LayoutBox*> next_box_opt =
@@ -296,13 +292,21 @@
 
     LayoutBox* next_box = *next_box_opt;
 
-    // TODO(https://crbug.com/391627364): for now, we should not leave the frame
-    // for scroll-marker-group containers, and `container` check indicates this
-    // case. But later we will support more cases where `container` is not
-    // nullptr.
-    if (container && next_box &&
-        next_box->GetFrame() != current_box->GetFrame()) {
-      break;
+    if (container) {
+      // If we just found a scrolling container that is or contains the
+      // requested container, stop scrolling.
+      // Additionally stop scrolling if we would continue on to scroll a
+      // different frame.
+      // TODO(crbug.com/365913982): We shouldn't scroll the scroll container
+      // on which scrollIntoView was called, which would obviate the need
+      // for the check that area_to_scroll is not the target.
+      // TODO(crbug.com/416730010): Revisit this if we allow passing a
+      // container from a different document.
+      if ((area_to_scroll && area_to_scroll->GetLayoutBox() != &box &&
+           stop_at.Contains(current_box)) ||
+          (next_box && next_box->GetFrame() != current_box->GetFrame())) {
+        return std::nullopt;
+      }
     }
 
     AdjustRectAndParamsForParentFrame(*current_box, next_box,
diff --git a/third_party/blink/renderer/core/style/computed_style.h b/third_party/blink/renderer/core/style/computed_style.h
index 6f329ce..37275b2e 100644
--- a/third_party/blink/renderer/core/style/computed_style.h
+++ b/third_party/blink/renderer/core/style/computed_style.h
@@ -585,9 +585,22 @@
 
   // column-rule-width
   GapDataList<int> ColumnRuleWidth() const {
-    if (!BorderStyleIsVisible(ColumnRuleStyle())) {
+    // The legacy version of 'column-rule-width' behaved such that if
+    // 'column-rule-style' was not visible, we'd treat the width as 0. We will
+    // continue to apply this rule for 'column-rule-width' if a single value is
+    // provided for 'column-rule-width' and 'column-rule-style' for backwards
+    // compat. However, if one of the properties is a list of values, we will
+    // return the true computed value of the width as specified by the author
+    // (per CSSWG resolution [1]).
+    //
+    // [1]: https://github.com/w3c/csswg-drafts/issues/11494
+    const GapDataList<EBorderStyle> rule_style = ColumnRuleStyle();
+    if (rule_style.HasSingleValue() &&
+        ColumnRuleWidthInternal().HasSingleValue() &&
+        !BorderStyleIsVisible(rule_style)) {
       return GapDataList<int>(0);
     }
+
     return ColumnRuleWidthInternal();
   }
 
diff --git a/third_party/blink/renderer/platform/loader/fetch/resource_fetcher.cc b/third_party/blink/renderer/platform/loader/fetch/resource_fetcher.cc
index 3152c3b..7c3c219 100644
--- a/third_party/blink/renderer/platform/loader/fetch/resource_fetcher.cc
+++ b/third_party/blink/renderer/platform/loader/fetch/resource_fetcher.cc
@@ -1496,7 +1496,6 @@
           resource->GetContentStatus() == ResourceStatus::kCached &&
           base::FeatureList::IsEnabled(features::kSpeculativeImageDecodes)) {
         speculative_decode_candidate_images_.insert(resource);
-        MaybeStartSpeculativeImageDecode();
       }
       break;
   }
@@ -2538,7 +2537,6 @@
         resource->GetContentStatus() == ResourceStatus::kCached &&
         base::FeatureList::IsEnabled(features::kSpeculativeImageDecodes)) {
       speculative_decode_candidate_images_.insert(resource);
-      MaybeStartSpeculativeImageDecode();
     }
 
     // Since this resource came from the network stack we only schedule a stale
diff --git a/third_party/blink/renderer/platform/loader/fetch/resource_fetcher.h b/third_party/blink/renderer/platform/loader/fetch/resource_fetcher.h
index 26c2401..91136ce 100644
--- a/third_party/blink/renderer/platform/loader/fetch/resource_fetcher.h
+++ b/third_party/blink/renderer/platform/loader/fetch/resource_fetcher.h
@@ -379,6 +379,8 @@
   // changed such that the load should no longer be deferred.
   void ReloadImagesIfNotDeferred();
 
+  void MaybeStartSpeculativeImageDecode();
+
   // Populates the provided request's permissions policy.
   void PopulateResourceRequestPermissionsPolicy(
       network::ResourceRequest* request);
@@ -488,7 +490,6 @@
 
   void MaybeSaveResourceToStrongReference(Resource* resource);
 
-  void MaybeStartSpeculativeImageDecode();
   void SpeculativeImageDecodeFinished();
 
   enum class RevalidationPolicy {
diff --git a/third_party/blink/renderer/platform/runtime_enabled_features.json5 b/third_party/blink/renderer/platform/runtime_enabled_features.json5
index cf649153..3cfbfea9 100644
--- a/third_party/blink/renderer/platform/runtime_enabled_features.json5
+++ b/third_party/blink/renderer/platform/runtime_enabled_features.json5
@@ -4112,6 +4112,10 @@
       name: "ScrollEndEvents",
       status: "stable",
     },
+    {
+      name: "ScrollIntoViewNearest",
+      status: "test"
+    },
     // TODO(355460994): Remove after M129.
     // scroll into root frame didn't take scrollbar and borders into account,
     // while this is fixed, to avoid any web compat issues we have this killswitch.
diff --git a/third_party/blink/web_tests/FlagExpectations/disable-site-isolation-trials b/third_party/blink/web_tests/FlagExpectations/disable-site-isolation-trials
index 1ff99026..48efd7b 100644
--- a/third_party/blink/web_tests/FlagExpectations/disable-site-isolation-trials
+++ b/third_party/blink/web_tests/FlagExpectations/disable-site-isolation-trials
@@ -76,6 +76,7 @@
 crbug.com/1209223 [ Linux ] external/wpt/html/browsers/browsing-the-web/navigating-across-documents/javascript-url-security-check-same-origin-domain.sub.html [ Failure ]
 
 # ====== New tests from wpt-importer added here ======
+external/wpt/html/cross-origin-opener-policy/iframe-popup-same-origin-to-unsafe-none.https.html?3-4 [ Crash Pass ]
 crbug.com/415690774 external/wpt/html/cross-origin-opener-policy/iframe-popup-same-origin-allow-popups-to-same-origin.https.html?5-6 [ Crash ]
 crbug.com/415690774 external/wpt/html/cross-origin-opener-policy/iframe-popup-same-origin-to-same-origin.https.html?5-6 [ Crash ]
 crbug.com/407618687 external/wpt/html/cross-origin-opener-policy/iframe-popup-same-origin-to-same-origin.https.html?3-4 [ Crash ]
diff --git a/third_party/blink/web_tests/FlagExpectations/force-renderer-accessibility b/third_party/blink/web_tests/FlagExpectations/force-renderer-accessibility
index 109d48e8..a24deeba 100644
--- a/third_party/blink/web_tests/FlagExpectations/force-renderer-accessibility
+++ b/third_party/blink/web_tests/FlagExpectations/force-renderer-accessibility
@@ -59,6 +59,3 @@
 # realistic scenario in the wild so we deem it safe to simply skip this test
 # when accessibility is enabled.
 fast/events/touch/gesture/gesture-tap-frame-move.html [ Skip ]
-
-# Undiagnosed failure:
-crbug.com/1503980 fast/events/scroll-event-handler-count.html [ Skip ]
diff --git a/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_8.json b/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_8.json
index 3eb77f29..58b296c 100644
--- a/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_8.json
+++ b/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_8.json
@@ -26480,6 +26480,19 @@
       {}
      ]
     ],
+    "webkit-box-ignores-flex-wrap.tentative.html": [
+     "98086f83570d4c5b4c6251f31093744442a907ca",
+     [
+      null,
+      [
+       [
+        "/compat/green-ref.html",
+        "=="
+       ]
+      ],
+      {}
+     ]
+    ],
     "webkit-box-rtl-flex.html": [
      "d20b3a0a9dbab237a799f62504b9b1ea7b90a564",
      [
@@ -387638,16 +387651,12 @@
       []
      ],
      "resources": {
-      "fetch-private-permission-denied.html": [
-       "7368cf5fcb0b854d4f0eaaed95fa5c3169d572c7",
-       []
-      ],
       "fetch-private.html": [
-       "ede16badf338dc50defad0251f83f8b02f1d3fec",
+       "b96a207ec33a13e5dd4c53083ac3d73123b23cbb",
        []
       ],
       "support.sub.js": [
-       "ef343b956fe2007089acccc4c6bffe5876ad364a",
+       "774e34d0a6fe59bab19aca14cb71b6e520acb798",
        []
       ],
       "target.py": [
@@ -411353,7 +411362,7 @@
      []
     ],
     "largest-contentful-paint.idl": [
-     "872ba552b0d3b0398be533beb578fdedd260baea",
+     "d1630cc7daae802bf27107ce0ab07185d0746d7f",
      []
     ],
     "layout-instability.idl": [
@@ -411677,7 +411686,7 @@
      []
     ],
     "service-workers.idl": [
-     "d9ff2f651f8235968dd29bd6d7f86e9e8fdcb4cf",
+     "34af3372401eed53328b8bc5e7ea42b87ddd9b20",
      []
     ],
     "shape-detection-api.idl": [
@@ -411829,7 +411838,7 @@
      []
     ],
     "webauthn.idl": [
-     "a33c85e7bad86753211fa7aa9270abac18b1e54e",
+     "7fbe55e67652b3fd35079d1a06423bb441657bd5",
      []
     ],
     "webcodecs-aac-codec-registration.idl": [
@@ -487480,6 +487489,13 @@
        {}
       ]
      ],
+     "style-src-inline-style-with-csstext.html": [
+      "5e812b4aee9d0d081673a0f333f8b29187619c3d",
+      [
+       null,
+       {}
+      ]
+     ],
      "style-src-multiple-policies-multiple-hashing-algorithms.html": [
       "027c61d8c632f2387408b8fb6869dee69bb8913d",
       [
@@ -529096,7 +529112,7 @@
        ]
       ],
       "sibling-function-descriptors.tentative.html": [
-       "4987eff9b8d4ce6b662cabd0b7729499d7b3e704",
+       "76d2ff8ee4db660e41ede9b188465a6c31843e3b",
        [
         null,
         {}
@@ -578355,7 +578371,7 @@
     },
     "local-network-access": {
      "fetch.tentative.https.html": [
-      "649dd2309596fe38e650aa9dbaf661c845dc7f9c",
+      "9c591f309b75af6731c6d6be9c0907d3b8c22618",
       [
        null,
        {
@@ -665515,7 +665531,7 @@
      ]
     ],
     "idlharness.html": [
-     "5f5d286b356cf5dcffd76f2ed6f689804e8426ba",
+     "0dd006e711c6d7b02fcbcd4d31a5c11381fe8d69",
      [
       null,
       {}
@@ -711863,6 +711879,19 @@
       }
      ]
     ],
+    "smoke": {
+     "tentative": {
+      "basic.html": [
+       "9bfedf09b1934e00a85cd030388ed470f575f7b0",
+       [
+        null,
+        {
+         "testdriver": true
+        }
+       ]
+      ]
+     }
+    },
     "soft-navigation-detection-main-descendent.tentative.html": [
      "96ff55260c3b9553ac5735cc5fe209ee7cfd5f37",
      [
@@ -714828,10 +714857,12 @@
      ]
     ],
     "SpeechRecognition-installOnDevice.https.html": [
-     "1d1dd35edc26d75419a49120d8a76c4ea7c84233",
+     "2f5b359c571d6d9cc97815ae6ba46e4249ec569e",
      [
       null,
-      {}
+      {
+       "testdriver": true
+      }
      ]
     ],
     "SpeechRecognition-onerror.https.html": [
@@ -773239,7 +773270,7 @@
       ]
      ],
      "qdq_subgraph.https.any.js": [
-      "5ec9ff80a22ce74bb9492b5bca747ce717c1ba46",
+      "aa816cce7fb2cd3d0a3f81d0453c8c199bf052fe",
       [
        "webnn/conformance_tests/qdq_subgraph.https.any.html?cpu",
        {
diff --git a/third_party/blink/web_tests/external/wpt/content-security-policy/style-src/style-src-inline-style-with-csstext.html b/third_party/blink/web_tests/external/wpt/content-security-policy/style-src/style-src-inline-style-with-csstext.html
new file mode 100644
index 0000000..5e812b4
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/content-security-policy/style-src/style-src-inline-style-with-csstext.html
@@ -0,0 +1,29 @@
+<!doctype html>
+<html>
+<head>
+    <meta http-equiv="Content-Security-Policy" content="style-src 'self';">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+
+    <script>
+      var t = async_test("Manipulating cssText should be allowed with 'self'");
+      document.addEventListener("securitypolicyviolation", t.unreached_func("Should not trigger a security policy violation"));
+    </script>
+</head>
+<body>
+    <div id='log'></div>
+
+    <div id="content">Lorem ipsum</div>
+
+    <script>
+      t.step(function() {
+        var contentEl = document.getElementById("content");
+        contentEl.style.cssText = 'margin-left: 2px;';
+        var marginLeftVal = getComputedStyle(contentEl).getPropertyValue('margin-left');
+        assert_equals(marginLeftVal, "2px");
+        t.done();
+      });
+    </script>
+
+</body>
+</html>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-gaps/parsing/gap-decorations-col-rule-width.html b/third_party/blink/web_tests/external/wpt/css/css-gaps/parsing/gap-decorations-col-rule-width.html
new file mode 100644
index 0000000..db7b97e
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-gaps/parsing/gap-decorations-col-rule-width.html
@@ -0,0 +1,51 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>CSS Gap Decorations: Ensure getComputedStyle for column-rule-width is as specified with multiple values</title>
+<link rel="help" href="https://drafts.csswg.org/css-gaps-1/#column-row-rule-width">
+<link rel="author" title="Sam Davis Omekara Jr." href="mailto:samomekarajr@microsoft.com">
+<script src="/resources/testharness.js" type="text/javascript"></script>
+<script src="/resources/testharnessreport.js" type="text/javascript"></script>
+</head>
+<body>
+<div id="target1"></div>
+<div id="target2"></div>
+<div id="target3"></div>
+<style>
+  #target1 {
+    column-rule-width: thin;
+  }
+
+  #target2 {
+    column-rule-width: 5px 10px 15px;
+  }
+
+  #target3 {
+    column-rule-width: repeat(auto, 5px);
+  }
+</style>
+<script>
+  test(function() {
+    const containerStyle = window.getComputedStyle(document.querySelector('#target1'));
+    const columnRuleWidth = containerStyle.getPropertyValue('column-rule-width');
+    assert_equals(columnRuleWidth, '0px');
+
+  }, "`column-rule-width` should be `0px` when `column-rule-style` is `none` with single value");
+
+  test(function() {
+    const containerStyle = window.getComputedStyle(document.querySelector('#target2'));
+    const columnRuleWidth = containerStyle.getPropertyValue('column-rule-width');
+    assert_equals(columnRuleWidth, '5px 10px 15px');
+
+  }, "`column-rule-width` should be as specified regardless of `column-rule-style` with multiple values");
+
+  test(function() {
+    const containerStyle = window.getComputedStyle(document.querySelector('#target3'));
+    const columnRuleWidth = containerStyle.getPropertyValue('column-rule-width');
+    assert_equals(columnRuleWidth, 'repeat(auto, 5px)');
+
+  }, "`column-rule-width` should be as specified regardless of `column-rule-style` with multiple (repeat) values");
+</script>
+</body>
+</html>
diff --git a/third_party/blink/web_tests/external/wpt/css/cssom-view/resources/scrollIntoView-frame.html b/third_party/blink/web_tests/external/wpt/css/cssom-view/resources/scrollIntoView-frame.html
new file mode 100644
index 0000000..ee2be2f
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/cssom-view/resources/scrollIntoView-frame.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<meta name="viewport" content="initial-scale=1">
+<style>
+body {
+  height: 600px;
+}
+#target {
+  position: absolute;
+  top: 400px;
+  height: 200px;
+}
+</style>
+<body>
+<div id="target"></div>
+</body>
diff --git a/third_party/blink/web_tests/external/wpt/css/cssom-view/scrollIntoView-container.html b/third_party/blink/web_tests/external/wpt/css/cssom-view/scrollIntoView-container.html
new file mode 100644
index 0000000..3b241ea
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/cssom-view/scrollIntoView-container.html
@@ -0,0 +1,84 @@
+<!DOCTYPE html>
+<title>CSSOM View - scrollIntoView container option</title>
+<meta charset="utf-8">
+<meta name="viewport" content="initial-scale=1">
+<link rel="author" title="Rob Flack" href="mailto:flackr@chromium.org">
+<link rel="help" href="https://drafts.csswg.org/cssom-view/#dom-element-scrollintoview">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<style>
+.scroller {
+  overflow: auto;
+  height: 200px;
+}
+.spacer {
+  height: 400px;
+}
+#target {
+  height: 200px;
+}
+</style>
+<script>
+let setFrameLoaded = null;
+let frameLoaded = new Promise(resolve => {
+  setFrameLoaded = resolve;
+});
+</script>
+<div id="outer" class="scroller">
+  <div class="spacer"></div>
+  <div id="inner" class="scroller">
+    <div class="spacer"></div>
+    <div id="target"></div>
+    <iframe id="frame" height="200" src="resources/scrollIntoView-frame.html" onload="setFrameLoaded()"></iframe>
+  </div>
+</div>
+<script>
+const outer = document.getElementById('outer');
+const inner = document.getElementById('inner');
+const target = document.getElementById('target');
+
+function reset() {
+  outer.scrollTop = 0;
+  inner.scrollTop = 0;
+}
+
+test(() => {
+  reset();
+  target.scrollIntoView();
+  assert_equals(inner.scrollTop, 400, '#inner scrollTop');
+  assert_equals(outer.scrollTop, 400, '#outer scrollTop');
+}, `scrollIntoView() defaults to scrolling ancestors`);
+
+test(() => {
+  reset();
+  target.scrollIntoView({container: 'all'});
+  assert_equals(inner.scrollTop, 400, '#inner scrollTop');
+  assert_equals(outer.scrollTop, 400, '#outer scrollTop');
+}, `scrollIntoView({container: 'all'}) scrolls ancestors`);
+
+test(() => {
+  reset();
+  target.scrollIntoView({container: 'nearest'});
+  assert_equals(inner.scrollTop, 400, '#inner scrollTop');
+  assert_equals(outer.scrollTop, 0, '#outer scrollTop');
+}, `scrollIntoView({container: 'nearest'}) only scrolls nearest scroll container`);
+
+test(() => {
+  reset();
+  inner.scrollIntoView({container: 'nearest'});
+  assert_equals(outer.scrollTop, 400, '#outer scrollTop');
+  assert_equals(inner.scrollTop, 0, '#inner scrollTop');
+}, `scrollIntoView({container: 'nearest'}) doesn't stop at itself`);
+
+promise_test(async () => {
+  reset();
+  await frameLoaded;
+  const frameDoc = document.getElementById("frame").contentDocument;
+  const frameTarget = frameDoc.getElementById("target");
+  frameTarget.scrollIntoView({container: 'nearest'});
+  assert_equals(frameDoc.scrollingElement.scrollTop, 400, 'frame scrollingElement scrollTop');
+  assert_equals(inner.scrollTop, 0, '#inner scrollTop');
+  assert_equals(outer.scrollTop, 0, '#outer scrollTop');
+}, `scrollIntoView({container: 'nearest'}) doesn't propagate to outer frames`);
+
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/interfaces/largest-contentful-paint.idl b/third_party/blink/web_tests/external/wpt/interfaces/largest-contentful-paint.idl
index 872ba55..d1630cc 100644
--- a/third_party/blink/web_tests/external/wpt/interfaces/largest-contentful-paint.idl
+++ b/third_party/blink/web_tests/external/wpt/interfaces/largest-contentful-paint.idl
@@ -5,7 +5,6 @@
 
 [Exposed=Window]
 interface LargestContentfulPaint : PerformanceEntry {
-    readonly attribute DOMHighResTimeStamp renderTime;
     readonly attribute DOMHighResTimeStamp loadTime;
     readonly attribute unsigned long size;
     readonly attribute DOMString id;
@@ -13,3 +12,5 @@
     readonly attribute Element? element;
     [Default] object toJSON();
 };
+
+LargestContentfulPaint includes PaintTimingMixin;
diff --git a/third_party/blink/web_tests/external/wpt/interfaces/service-workers.idl b/third_party/blink/web_tests/external/wpt/interfaces/service-workers.idl
index d9ff2f6..34af337 100644
--- a/third_party/blink/web_tests/external/wpt/interfaces/service-workers.idl
+++ b/third_party/blink/web_tests/external/wpt/interfaces/service-workers.idl
@@ -34,7 +34,7 @@
   readonly attribute USVString scope;
   readonly attribute ServiceWorkerUpdateViaCache updateViaCache;
 
-  [NewObject] Promise<undefined> update();
+  [NewObject] Promise<ServiceWorkerRegistration> update();
   [NewObject] Promise<boolean> unregister();
 
   // event
diff --git a/third_party/blink/web_tests/external/wpt/interfaces/webauthn.idl b/third_party/blink/web_tests/external/wpt/interfaces/webauthn.idl
index a33c85e..7fbe55e6 100644
--- a/third_party/blink/web_tests/external/wpt/interfaces/webauthn.idl
+++ b/third_party/blink/web_tests/external/wpt/interfaces/webauthn.idl
@@ -311,22 +311,37 @@
 partial dictionary AuthenticationExtensionsClientInputs {
   DOMString appid;
 };
+partial dictionary AuthenticationExtensionsClientInputsJSON {
+  DOMString appid;
+};
 
 partial dictionary AuthenticationExtensionsClientOutputs {
   boolean appid;
 };
+partial dictionary AuthenticationExtensionsClientOutputsJSON {
+  boolean appid;
+};
 
 partial dictionary AuthenticationExtensionsClientInputs {
   DOMString appidExclude;
 };
+partial dictionary AuthenticationExtensionsClientInputsJSON {
+  DOMString appidExclude;
+};
 
 partial dictionary AuthenticationExtensionsClientOutputs {
   boolean appidExclude;
 };
+partial dictionary AuthenticationExtensionsClientOutputsJSON {
+  boolean appidExclude;
+};
 
 partial dictionary AuthenticationExtensionsClientInputs {
     boolean credProps;
 };
+partial dictionary AuthenticationExtensionsClientInputsJSON {
+    boolean credProps;
+};
 
 dictionary CredentialPropertiesOutput {
     boolean rk;
@@ -335,33 +350,57 @@
 partial dictionary AuthenticationExtensionsClientOutputs {
     CredentialPropertiesOutput credProps;
 };
+partial dictionary AuthenticationExtensionsClientOutputsJSON {
+    CredentialPropertiesOutput credProps;
+};
 
 dictionary AuthenticationExtensionsPRFValues {
     required BufferSource first;
     BufferSource second;
 };
+dictionary AuthenticationExtensionsPRFValuesJSON {
+    required Base64URLString first;
+    Base64URLString second;
+};
 
 dictionary AuthenticationExtensionsPRFInputs {
     AuthenticationExtensionsPRFValues eval;
     record<DOMString, AuthenticationExtensionsPRFValues> evalByCredential;
 };
+dictionary AuthenticationExtensionsPRFInputsJSON {
+    AuthenticationExtensionsPRFValuesJSON eval;
+    record<DOMString, AuthenticationExtensionsPRFValuesJSON> evalByCredential;
+};
 
 partial dictionary AuthenticationExtensionsClientInputs {
     AuthenticationExtensionsPRFInputs prf;
 };
+partial dictionary AuthenticationExtensionsClientInputsJSON {
+    AuthenticationExtensionsPRFInputsJSON prf;
+};
 
 dictionary AuthenticationExtensionsPRFOutputs {
     boolean enabled;
     AuthenticationExtensionsPRFValues results;
 };
+dictionary AuthenticationExtensionsPRFOutputsJSON {
+    boolean enabled;
+    AuthenticationExtensionsPRFValuesJSON results;
+};
 
 partial dictionary AuthenticationExtensionsClientOutputs {
     AuthenticationExtensionsPRFOutputs prf;
 };
+partial dictionary AuthenticationExtensionsClientOutputsJSON {
+    AuthenticationExtensionsPRFOutputsJSON prf;
+};
 
 partial dictionary AuthenticationExtensionsClientInputs {
     AuthenticationExtensionsLargeBlobInputs largeBlob;
 };
+partial dictionary AuthenticationExtensionsClientInputsJSON {
+    AuthenticationExtensionsLargeBlobInputsJSON largeBlob;
+};
 
 enum LargeBlobSupport {
   "required",
@@ -373,13 +412,26 @@
     boolean read;
     BufferSource write;
 };
+dictionary AuthenticationExtensionsLargeBlobInputsJSON {
+    DOMString support;
+    boolean read;
+    Base64URLString write;
+};
 
 partial dictionary AuthenticationExtensionsClientOutputs {
     AuthenticationExtensionsLargeBlobOutputs largeBlob;
 };
+partial dictionary AuthenticationExtensionsClientOutputsJSON {
+    AuthenticationExtensionsLargeBlobOutputsJSON largeBlob;
+};
 
 dictionary AuthenticationExtensionsLargeBlobOutputs {
     boolean supported;
     ArrayBuffer blob;
     boolean written;
 };
+dictionary AuthenticationExtensionsLargeBlobOutputsJSON {
+    boolean supported;
+    Base64URLString blob;
+    boolean written;
+};
diff --git a/third_party/blink/web_tests/external/wpt/largest-contentful-paint/idlharness.html b/third_party/blink/web_tests/external/wpt/largest-contentful-paint/idlharness.html
index 5f5d286b..0dd006e 100644
--- a/third_party/blink/web_tests/external/wpt/largest-contentful-paint/idlharness.html
+++ b/third_party/blink/web_tests/external/wpt/largest-contentful-paint/idlharness.html
@@ -10,7 +10,7 @@
 
 idl_test(
   ['largest-contentful-paint'],
-  ['performance-timeline', 'dom', 'hr-time'],
+  ['performance-timeline', 'dom', 'hr-time', 'paint-timing'],
   async (idl_array, t) => {
     idl_array.add_objects({
       LargestContentfulPaint: ['lcp']
diff --git a/third_party/blink/web_tests/external/wpt/speculation-rules/prefetch/resources/executor.sub.html b/third_party/blink/web_tests/external/wpt/speculation-rules/prefetch/resources/executor.sub.html
index d27acfe..975a3e5 100644
--- a/third_party/blink/web_tests/external/wpt/speculation-rules/prefetch/resources/executor.sub.html
+++ b/third_party/blink/web_tests/external/wpt/speculation-rules/prefetch/resources/executor.sub.html
@@ -1,5 +1,8 @@
 <!DOCTYPE html>
 <script src="/common/dispatcher/dispatcher.js" nonce="abc"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="/resources/testdriver-actions.js"></script>
 <script src="utils.sub.js" nonce="abc"></script>
 <script nonce="abc">
 // For a given string `str` that is escaped by WPT's `.sub.html` or
@@ -45,6 +48,39 @@
   }
 });
 
+// Add a link to the page in order to use during the test
+function add_link(id, url) {
+  const link_element = document.createElement("a");
+  const link_text = document.createTextNode(url);
+  link_element.setAttribute("href", url);
+  link_element.setAttribute("id", id);
+  link_element.appendChild(link_text);
+  document.body.appendChild(link_element);
+}
+
+// "id" is the id of the link that we need to hover on in order
+// to start the prefetch
+async function start_non_eager_prefetch_on_hover(id) {
+  let target = document.getElementById(id);
+
+  test_driver.set_test_context(window.opener);
+  // Inject the inputs to run this test.
+  await new test_driver.Actions().addPointer("mouse").pointerMove(0, 0, {origin: target}).send();
+}
+
+// "id" is the id of the link that we need to press on in order
+// to start the prefetch
+async function start_non_eager_prefetch_on_pointerdown(id) {
+  let target = document.getElementById(id);
+
+  test_driver.set_test_context(window.opener);
+  // Inject the inputs to run this test.
+  // Move mouse pointer outside of the anchor so that we don't start the
+  // navigation before making sure the prefetch request started server-side.
+  await new test_driver.Actions().addPointer("mouse").pointerMove(0, 0, {origin: target}).pointerDown().pointerMove(0, 0).pointerUp().send();
+}
+
+
 // The fetch request's URL sent to the server.
 window.requestUrl = reverse_html_escape(
     "{{location[server]}}{{location[path]}}{{location[query]}}");
diff --git a/third_party/blink/web_tests/wpt_internal/speculation-rules/speculation-tags/prefetch-eagerness-pointer-down.https.html b/third_party/blink/web_tests/external/wpt/speculation-rules/speculation-tags/prefetch-eagerness-pointer-down.https.html
similarity index 91%
rename from third_party/blink/web_tests/wpt_internal/speculation-rules/speculation-tags/prefetch-eagerness-pointer-down.https.html
rename to third_party/blink/web_tests/external/wpt/speculation-rules/speculation-tags/prefetch-eagerness-pointer-down.https.html
index 4809189..cbf69ce 100644
--- a/third_party/blink/web_tests/wpt_internal/speculation-rules/speculation-tags/prefetch-eagerness-pointer-down.https.html
+++ b/third_party/blink/web_tests/external/wpt/speculation-rules/speculation-tags/prefetch-eagerness-pointer-down.https.html
@@ -3,6 +3,8 @@
 <meta name="timeout" content="long">
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
 <script src="/common/dispatcher/dispatcher.js"></script>
 <script src="/common/utils.js"></script>
 <script src="/html/browsers/browsing-the-web/remote-context-helper/resources/remote-context-helper.js"></script>
@@ -15,7 +17,7 @@
 setup(() => assertSpeculationRulesIsSupported());
 
 promise_test(async t => {
-  const agent = await spawnWindow(t, {executor:new URL("../prefetch/resources/executor-non-eager.html", document.baseURI)});
+  const agent = await spawnWindow(t);
   const nextUrl = agent.getExecutorURL({ page: 2 });
   await agent.forceSpeculationRules({
       prefetch: [
diff --git a/third_party/blink/web_tests/wpt_internal/speculation-rules/speculation-tags/prefetch-eagerness-pointer-hover.https.html b/third_party/blink/web_tests/external/wpt/speculation-rules/speculation-tags/prefetch-eagerness-pointer-hover.https.html
similarity index 87%
rename from third_party/blink/web_tests/wpt_internal/speculation-rules/speculation-tags/prefetch-eagerness-pointer-hover.https.html
rename to third_party/blink/web_tests/external/wpt/speculation-rules/speculation-tags/prefetch-eagerness-pointer-hover.https.html
index 54e288ef..f5e749b 100644
--- a/third_party/blink/web_tests/wpt_internal/speculation-rules/speculation-tags/prefetch-eagerness-pointer-hover.https.html
+++ b/third_party/blink/web_tests/external/wpt/speculation-rules/speculation-tags/prefetch-eagerness-pointer-hover.https.html
@@ -3,6 +3,8 @@
 <meta name="timeout" content="long">
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
 <script src="/common/dispatcher/dispatcher.js"></script>
 <script src="/common/utils.js"></script>
 <script src="/html/browsers/browsing-the-web/remote-context-helper/resources/remote-context-helper.js"></script>
@@ -15,7 +17,7 @@
 setup(() => assertSpeculationRulesIsSupported());
 
 promise_test(async t => {
-  const agent = await spawnWindow(t, {executor:new URL("../prefetch/resources/executor-non-eager.html", document.baseURI)});
+  const agent = await spawnWindow(t);
   const nextUrl = agent.getExecutorURL({ page: 2 });
   await agent.forceSpeculationRules({
       prefetch: [
@@ -35,7 +37,7 @@
     await start_non_eager_prefetch_on_hover(linkId);
   }, [linkId]);
 
-  // TODO(crbug.com/381687257):  This is the chromium-specific behavior. Revisit this wait when upstreaming this test.
+  // TODO(crbug.com/381687257): Remove this when 0ms hover trigger is supported.
   // Wait for longer than 200 ms for the onhover trigger to fire.
   await new Promise(resolve => t.step_timeout(resolve, 500));
   await agent.navigate(nextUrl);
diff --git a/third_party/boringssl/src b/third_party/boringssl/src
index 45cab55..136284f 160000
--- a/third_party/boringssl/src
+++ b/third_party/boringssl/src
@@ -1 +1 @@
-Subproject commit 45cab558a838e1a546391406e7cbe1eec9b6e643
+Subproject commit 136284f8548bc7fb43e99e7f69e03fab57168e8b
diff --git a/third_party/catapult b/third_party/catapult
index 5255e1a..0c5acc0 160000
--- a/third_party/catapult
+++ b/third_party/catapult
@@ -1 +1 @@
-Subproject commit 5255e1a11a4d0bf747f304816a2523f952be9228
+Subproject commit 0c5acc073dd14893f2a75a51819ad81627098551
diff --git a/third_party/dawn b/third_party/dawn
index f4cae4a..606e03a 160000
--- a/third_party/dawn
+++ b/third_party/dawn
@@ -1 +1 @@
-Subproject commit f4cae4afdd4bca082d36025c66c7c284ed16a67f
+Subproject commit 606e03a22bfe5686be18fcc934555f55f987c1e9
diff --git a/third_party/depot_tools b/third_party/depot_tools
index d1f9fa6..977c374 160000
--- a/third_party/depot_tools
+++ b/third_party/depot_tools
@@ -1 +1 @@
-Subproject commit d1f9fa6c922b20d8034fe4c7f3a62f8a824c561b
+Subproject commit 977c37458fda472d8822a8b57e4a83a7bc747471
diff --git a/third_party/devtools-frontend/src b/third_party/devtools-frontend/src
index 4cc0688..c049d05 160000
--- a/third_party/devtools-frontend/src
+++ b/third_party/devtools-frontend/src
@@ -1 +1 @@
-Subproject commit 4cc0688b9538f497ae06e3a0f97431ffc86f4181
+Subproject commit c049d058ae5973f47a21fb1e4eb7a224174ef032
diff --git a/third_party/llvm-libc/src b/third_party/llvm-libc/src
index 61d7ed3..4a2940b4 160000
--- a/third_party/llvm-libc/src
+++ b/third_party/llvm-libc/src
@@ -1 +1 @@
-Subproject commit 61d7ed30110f97d6842304bc9035ee9cdae6b5e5
+Subproject commit 4a2940b40b394ca57312aa9bbc8af430fe9a5340
diff --git a/third_party/perfetto b/third_party/perfetto
index 8295644..6ffe623 160000
--- a/third_party/perfetto
+++ b/third_party/perfetto
@@ -1 +1 @@
-Subproject commit 829564494ca9d7bc1c8b15998c5f1f3536cdacf8
+Subproject commit 6ffe623d283575c00047cf9abf90c3659505a592
diff --git a/third_party/skia b/third_party/skia
index 12dbc34d..0feee17 160000
--- a/third_party/skia
+++ b/third_party/skia
@@ -1 +1 @@
-Subproject commit 12dbc34d742ee67f1b267dc276fe7d84631aa856
+Subproject commit 0feee17aeacab6b88ac8be3d8b35ae4c940eeea4
diff --git a/tools/metrics/histograms/metadata/android/enums.xml b/tools/metrics/histograms/metadata/android/enums.xml
index a566906..e10afa1b 100644
--- a/tools/metrics/histograms/metadata/android/enums.xml
+++ b/tools/metrics/histograms/metadata/android/enums.xml
@@ -1209,6 +1209,12 @@
   <int value="2" label="Device lock restored"/>
 </enum>
 
+<enum name="MissingNavbarInsetsReason">
+  <int value="0" label="Other"/>
+  <int value="1" label="InMultiWindow"/>
+  <int value="2" label="InDesktopWindow"/>
+</enum>
+
 <enum name="MotionEventAction">
   <int value="0" label="NONE"/>
   <int value="1" label="DOWN"/>
diff --git a/tools/metrics/histograms/metadata/android/histograms.xml b/tools/metrics/histograms/metadata/android/histograms.xml
index 704cd94..f92c4521 100644
--- a/tools/metrics/histograms/metadata/android/histograms.xml
+++ b/tools/metrics/histograms/metadata/android/histograms.xml
@@ -1922,6 +1922,18 @@
   </summary>
 </histogram>
 
+<histogram name="Android.EdgeToEdge.DrawToEdgeInUnsupportedConfiguration"
+    enum="Boolean" expires_after="2025-08-24">
+  <owner>clhager@google.com</owner>
+  <owner>wenyufu@chromium.org</owner>
+  <owner>edge-to-edge@chromium.org</owner>
+  <summary>
+    Records when EdgeToEdgeControllerImpl#drawToEdge gets called on a device
+    whose configuration does not support edge-to-edge, with the boolean record
+    indicating whether drawToEdge was called due to a change in window state.
+  </summary>
+</histogram>
+
 <histogram name="Android.EdgeToEdge.Eligible" enum="Boolean"
     expires_after="2025-08-24">
   <owner>lazzzis@google.com</owner>
@@ -1943,6 +1955,18 @@
   </summary>
 </histogram>
 
+<histogram name="Android.EdgeToEdge.MissingNavbarInsets"
+    enum="MissingNavbarInsetsReason" expires_after="2025-12-01">
+  <owner>clhager@google.com</owner>
+  <owner>wenyufu@chromium.org</owner>
+  <owner>edge-to-edge@chromium.org</owner>
+  <summary>
+    Records once during the initialization of the EdgeToEdgeControllerImpl
+    whether there were no navigation bar insets at all, which may indicate an
+    incorrect value from the OS.
+  </summary>
+</histogram>
+
 <histogram name="Android.EdgeToEdge.SupportedConfigurationSwitch"
     enum="SupportedConfigurationSwitch" expires_after="2025-12-01">
   <owner>clhager@google.com</owner>
diff --git a/tools/metrics/histograms/metadata/collaboration_service/enums.xml b/tools/metrics/histograms/metadata/collaboration_service/enums.xml
index 860054f..0403b0d 100644
--- a/tools/metrics/histograms/metadata/collaboration_service/enums.xml
+++ b/tools/metrics/histograms/metadata/collaboration_service/enums.xml
@@ -84,6 +84,13 @@
 
 <enum name="CollaborationServiceLeaveOrDeleteEntryPoint">
   <int value="0" label="Unknown"/>
+  <int value="1" label="AndroidTabGroupContextMenuLeave"/>
+  <int value="2" label="AndroidTabGroupContextMenuDelete"/>
+  <int value="3" label="AndroidTabGroupItemMenuLeave"/>
+  <int value="4" label="AndroidTabGroupItemMenuDelete"/>
+  <int value="5" label="AndroidTabGroupRow"/>
+  <int value="6" label="AndroidTabGridDialogLeave"/>
+  <int value="7" label="AndroidTabGridDialogDelete"/>
 </enum>
 
 <!-- LINT.ThenChange(//components/collaboration/public/collaboration_flow_entry_point.h:CollaborationServiceLeaveOrDeleteEntryPoint) -->
diff --git a/tools/perf/core/perfetto_binary_roller/binary_deps.json b/tools/perf/core/perfetto_binary_roller/binary_deps.json
index 49aa728..ee377702 100644
--- a/tools/perf/core/perfetto_binary_roller/binary_deps.json
+++ b/tools/perf/core/perfetto_binary_roller/binary_deps.json
@@ -5,8 +5,8 @@
             "full_remote_path": "perfetto-luci-artifacts/v50.1/linux-arm64/trace_processor_shell"
         },
         "win": {
-            "hash": "b3d955460721019e770ca39601f366f2cf357bff",
-            "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/win/f2e70843f73b1e17fea3de511344f65b4bc8ad9c/trace_processor_shell.exe"
+            "hash": "b85dcca74e1212a0cc61c2b770824bef6a716fef",
+            "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/win/6ffe623d283575c00047cf9abf90c3659505a592/trace_processor_shell.exe"
         },
         "linux_arm": {
             "hash": "99f971ca131f6d11c73f4b918099d434bdd8093c",
@@ -21,8 +21,8 @@
             "full_remote_path": "perfetto-luci-artifacts/v50.1/mac-arm64/trace_processor_shell"
         },
         "linux": {
-            "hash": "4026dea19671293170bc7f302463b54f0e32b393",
-            "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/linux/f2e70843f73b1e17fea3de511344f65b4bc8ad9c/trace_processor_shell"
+            "hash": "6a2c70dd8ecf91307dc9f297bcb297794666935f",
+            "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/linux/6ffe623d283575c00047cf9abf90c3659505a592/trace_processor_shell"
         }
     },
     "power_profile.sql": {
diff --git a/ui/gl/direct_composition_support.cc b/ui/gl/direct_composition_support.cc
index 737f1e1..44cf34d4 100644
--- a/ui/gl/direct_composition_support.cc
+++ b/ui/gl/direct_composition_support.cc
@@ -1109,6 +1109,8 @@
       features::kDXGIWaitableSwapChainMaxQueuedFrames.Get());
 }
 
+std::optional<bool> g_direct_composition_texture_supported;
+
 void SetDirectCompositionOverlayWorkarounds(
     const DirectCompositionOverlayWorkarounds& workarounds) {
   // This has to be set before initializing overlay caps.
@@ -1121,6 +1123,10 @@
   g_force_rgb10a2_overlay_support = workarounds.force_rgb10a2_overlay_support;
   g_check_ycbcr_studio_g22_left_p709_for_nv12_support =
       workarounds.check_ycbcr_studio_g22_left_p709_for_nv12_support;
+  CHECK(!g_direct_composition_texture_supported.has_value());
+  if (workarounds.disable_dcomp_texture) {
+    g_direct_composition_texture_supported = false;
+  }
 }
 
 void SetDirectCompositionMonitorInfoForTesting(
@@ -1130,7 +1136,6 @@
   g_primary_monitor_size = primary_monitor_size;
 }
 
-std::optional<bool> g_direct_composition_texture_supported;
 
 bool DirectCompositionTextureSupported() {
   if (g_direct_composition_texture_supported.has_value()) {
diff --git a/ui/gl/direct_composition_support.h b/ui/gl/direct_composition_support.h
index b4b8188b..afc2b71 100644
--- a/ui/gl/direct_composition_support.h
+++ b/ui/gl/direct_composition_support.h
@@ -149,6 +149,15 @@
   // Enable NV12 overlay support only when
   // DXGI_COLOR_SPACE_YCBCR_STUDIO_G22_LEFT_P709 is supported.
   bool check_ycbcr_studio_g22_left_p709_for_nv12_support = false;
+
+  // Before 10.0.26100.3624, Windows could return PRESENTATION_ERROR_LOST in
+  // some cases that are potentially recoverable by destroying all the DComp
+  // textures associated with our DComp device. However, Viz is not
+  // well-equipped to do this since most DComp textures are owned by pools in
+  // the renderer processes. This version and beyond, Windows has a fix to only
+  // return PRESENTATION_ERROR_LOST in truly unrecoverable cases, which we will
+  // treat the same as context loss.
+  bool disable_dcomp_texture = false;
 };
 GL_EXPORT void SetDirectCompositionOverlayWorkarounds(
     const DirectCompositionOverlayWorkarounds& workarounds);
diff --git a/v8 b/v8
index 577adbe..1beea5f 160000
--- a/v8
+++ b/v8
@@ -1 +1 @@
-Subproject commit 577adbe0c15f8fa0643d5f91cc92a06c271c3456
+Subproject commit 1beea5ff6680e267f28e29054239ebcdd4ccb861