diff --git a/.gemini/commands/cr/test/silence-test-ios.toml b/.gemini/commands/cr/test/silence-test-ios.toml
index be9522263..1f02813 100644
--- a/.gemini/commands/cr/test/silence-test-ios.toml
+++ b/.gemini/commands/cr/test/silence-test-ios.toml
@@ -18,53 +18,79 @@
     *   Identify the required `<test_name>`, required `<silencing_type>`
         (`DISABLED` or `FLAKY`), and optional `<conditions>` from the user's
         input.
-    *   Example input: `/silence-test-ios testMyFeature FLAKY only on iphone
-        device`
+    *   Example input: `/cr:test:silence-test-ios testMyFeature FLAKY only on
+        iphone device`
     *   **If you can't parse all the required arguments:** Do not proceed with
         this task and print out the missing arguments to the user.
 
 2.  **Locate and read the Test File:**
 
-    *   Use codesearch to find the file containing the `<test_name>`.
-    *   Separate the test suite name from the test suite name when doing your
-        search. Example: TestSuite/testCase => TestSuite testCase.
+    *   Use codesearch to find the file containing the `<test_name>`. Do not use
+        any language filter which are too strict.
+    *   Separate the test suite name in 2 distinct search items from the test
+        suite name when doing your search. Example: TestSuite/testCase =>
+        "TestSuite", "testCase".
 
 3.  **Determine and Apply Silencing Strategy:**
 
     *   Based on the file content and user-provided `<conditions>`, choose the
         correct silencing method.
     *   **For Objective-C/C++ (`.mm`, `.cc`):**
+
         *   **No Conditions:** Rename the test by adding the `<silencing_type>_`
             prefix.
             *   `TEST_F(MySuite, testMyFeature)` -> `TEST_F(MySuite,
                 <silencing_type>_testMyFeature)`
             *   `- (void)testMyFeature` -> `-
                 (void)<silencing_type>_testMyFeature`
+            *   **Important for Objective-C instance methods:** If the test
+                method is referenced by a `@selector`, you MUST also update the
+                selector to match the new test name (e.g.,
+                `@selector(testMyFeature)` ->
+                `@selector(<silencing_type>_testMyFeature)`). Search for usages
+                of the selector in the same file.
         *   **With Conditions (e.g., "iphone device", "ipad simulator", "no
             multiwindow"):**
+
             *   **Compile-time conditions for tests on iOS (e.g., simulator vs.
                 device):** Rename the test to `MAYBE_testMyFeature` and add a
                 preprocessor block. For "iphone device": `objectivec #if
                 !TARGET_OS_SIMULATOR #define MAYBE_testMyFeature
                 DISABLED_testMyFeature #else #define MAYBE_testMyFeature
-                testMyFeature #endif`
+                testMyFeature #endif`.
             *   **Runtime conditions (e.g., OS version, form factor,
                 multi-window):** Add a guard at the beginning of the test
                 method.
+
                 *   **OS Version:** `if (@available(iOS <os_version>, *)) {
                     EARL_GREY_TEST_SKIPPED(@"Reason"); }`
+                *   **Starting from an iOS version and beyond:**
+                    `base::ios::IsRunningOnIOS<os_version>OrLater() {
+                    EARL_GREY_TEST_SKIPPED(@"Reason");}`.
+
+                    Pick one of the helpers in @base/ios/ios_util.mm. Replace
+                    the <os_version> by the OS version to start silencing from
+                    (e.g. IsRunningOnIOS26OrLater() for silencing the test when
+                    running on iOS 26 or later). Commit
+                    afc85ffeb6f263217e983d0c5cf2f4e2d4af50d5 shows an example of
+                    that.
+
                 *   **Form Factor:** `if ([ChromeEarlGrey isCompactWidth]) {
                     EARL_GREY_TEST_SKIPPED(@"Reason"); }`
+
                 *   **Multi-window:** `if (![ChromeEarlGrey
                     areMultipleWindowsSupported]) {
                     EARL_GREY_TEST_DISABLED(@"Reason"); }`
+
     *   **For Java (`.java`):**
+
         *   Add the `@DisabledTest(message = "crbug.com/...")` annotation above
             the test method. You will need to create a bug first to get the bug
             number.
 
-4.  **Create a Bug:**
+4.  **Create a Bug iff needed:**
 
+    *   Search for an existing bug for the test case before creating a new one.
     *   Create a bug base on this template:
         *   **Title:** `[iOS Gardener] Test <test_name> failing on waterfall
             <bot_name>`
@@ -91,6 +117,8 @@
         freeze` to temporarily commit those changes in the current git branch
         before switching.
     *   Apply the silencing change to the test file.
+    *   Make sure that the needed headers are included.
+    *   If there are missing dependencies in the BUILD.gn file, add them.
     *   Add the file to git and commit the change. The commit message should be:
         ``` [iOS][Gardener] Disable flaky test <test_name>
 
diff --git a/DEPS b/DEPS
index aa71c000..f90f1a3d 100644
--- a/DEPS
+++ b/DEPS
@@ -240,7 +240,7 @@
   # luci-go CIPD package version.
   # Make sure the revision is uploaded by infra-packagers builder.
   # https://ci.chromium.org/p/infra-internal/g/infra-packagers/console
-  'luci_go': 'git_revision:0ff6e38043daec0a3e3bea265820a63eab6bf21d',
+  'luci_go': 'git_revision:f58a984f19eeed333afe8ec2e167cb7197dca7f5',
 
   # This can be overridden, e.g. with custom_vars, to build clang from HEAD
   # instead of downloading the prebuilt pinned revision.
@@ -305,11 +305,11 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling V8
   # and whatever else without interference from each other.
-  'src_internal_revision': '3ddd5788bd026e3ed8a3f674c273a6030f54e656',
+  'src_internal_revision': '603b258ff4ab92ff387e07b3f47c447a52595968',
   # 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': '359f3d7cc7edfcb93e99ab5ed7e9f2f5fdd8ef85',
+  'skia_revision': '7c2f502e3304f4c76413696a9035470c32ac6dde',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling V8
   # and whatever else without interference from each other.
@@ -397,7 +397,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': '5c0782b1ec316a185616cd76223d66c8b2b4dbbb',
+  'devtools_frontend_revision': '6f55537aa2a782d9e8093896c857add3a88d01f3',
   # 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.
@@ -457,7 +457,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling crabbyavif
   # and whatever else without interference from each other.
-  'crabbyavif_revision': '4c70b98d1ebc8a210f2919be7ccabbcf23061cb5',
+  'crabbyavif_revision': 'f96d484aa93776f076a2949805fd29bc4888b1c4',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling Speedometer main
   # and whatever else without interference from each other.
@@ -1212,7 +1212,7 @@
       'packages': [
           {
               'package': 'chromium/chrome/android/orderfiles/arm64',
-              'version': 'Y0AWgaivgW6UpW6RIrsVLJG2p5zUsR7Hrx1g6tkWnN0C',
+              'version': 'RoROg2UqfYdfWdYUSX5JU9ETgBOP1MPow11HloSRmwQC',
           },
       ],
       'condition': 'checkout_android',
@@ -1618,7 +1618,7 @@
   },
 
   'src/docs/website': {
-    'url': Var('chromium_git') + '/website.git' + '@' + 'c6edf98e7fab2385c90caac6211f00b62e9b773d',
+    'url': Var('chromium_git') + '/website.git' + '@' + '4f4404f68b21aefa24a86abad8fb0cf01a0db7fc',
   },
 
   'src/ios/third_party/earl_grey2/src': {
@@ -2096,7 +2096,7 @@
     Var('chromium_git') + '/chromium/web-tests.git' + '@' + Var('crossbench_web_tests_revision'),
 
   'src/third_party/depot_tools':
-    Var('chromium_git') + '/chromium/tools/depot_tools.git' + '@' + '83ad19235f633ab5ac843b3e977d53f4b7b042a5',
+    Var('chromium_git') + '/chromium/tools/depot_tools.git' + '@' + '0e9c00fe9f9783104836bd68870b5253c8c76121',
 
   'src/third_party/devtools-frontend/src':
     Var('chromium_git') + '/devtools/devtools-frontend' + '@' + Var('devtools_frontend_revision'),
@@ -2641,7 +2641,7 @@
     Var('pdfium_git') + '/pdfium.git' + '@' +  Var('pdfium_revision'),
 
   'src/third_party/perfetto':
-    Var('chromium_git') + '/external/github.com/google/perfetto.git' + '@' + 'd17b40b3b5e36f3744f1d010fe3ba2d3c55559c0',
+    Var('chromium_git') + '/external/github.com/google/perfetto.git' + '@' + '49af9ecad055e597f1cc7d57677454b72606e96d',
 
   'src/base/tracing/test/data': {
     'bucket': 'perfetto',
@@ -3012,7 +3012,7 @@
     Var('chromium_git') + '/webpagereplay.git' + '@' + Var('webpagereplay_revision'),
 
   'src/third_party/webrtc':
-    Var('webrtc_git') + '/src.git' + '@' + 'db0e64c5e80168839d74ae0cc6173ccd537ae211',
+    Var('webrtc_git') + '/src.git' + '@' + '35d9a8f86a90ddfebd9c8b5f6ddce14b68a1a879',
 
   # Wuffs' canonical repository is at github.com/google/wuffs, but we use
   # Skia's mirror of Wuffs, the same as in upstream Skia's DEPS file.
@@ -3793,7 +3793,7 @@
 
   'src/ios_internal':  {
       'url': Var('chrome_git') + '/chrome/ios_internal.git' + '@' +
-        '684211821a75efa2da3fad3310385a55308a3da6',
+        '30018259e2511654b23013f465ac3d119a7e4a35',
       'condition': 'checkout_ios and checkout_src_internal',
   },
 
diff --git a/ash/hud_display/fps_graph_page_view.cc b/ash/hud_display/fps_graph_page_view.cc
index 85a5612..5bda265 100644
--- a/ash/hud_display/fps_graph_page_view.cc
+++ b/ash/hud_display/fps_graph_page_view.cc
@@ -118,6 +118,7 @@
 }
 
 void FPSGraphPageView::OnDidPresentCompositorFrame(
+    ui::Compositor* compositor,
     uint32_t frame_token,
     const gfx::PresentationFeedback& feedback) {
   UpdateStats(feedback);
diff --git a/ash/hud_display/fps_graph_page_view.h b/ash/hud_display/fps_graph_page_view.h
index c9e7176..217d0e0 100644
--- a/ash/hud_display/fps_graph_page_view.h
+++ b/ash/hud_display/fps_graph_page_view.h
@@ -47,6 +47,7 @@
 
   // ui::CompositorObserver:
   void OnDidPresentCompositorFrame(
+      ui::Compositor* compositor,
       uint32_t frame_token,
       const gfx::PresentationFeedback& feedback) override;
 
diff --git a/ash/system/power/power_event_observer.cc b/ash/system/power/power_event_observer.cc
index 8694320..7efb610 100644
--- a/ash/system/power/power_event_observer.cc
+++ b/ash/system/power/power_event_observer.cc
@@ -32,6 +32,7 @@
 #include "ui/compositor/compositor.h"
 #include "ui/compositor/compositor_observer.h"
 #include "ui/display/manager/display_configurator.h"
+#include "ui/gfx/presentation_feedback.h"
 
 // TODO(b/248107965): Remove after figuring out the root cause of the bug
 #undef ENABLED_VLOG_LEVEL
@@ -100,7 +101,10 @@
     }
     pending_compositing_[compositor].state = CompositingState::kWaitingForEnded;
   }
-  void OnCompositingAckDeprecated(ui::Compositor* compositor) override {
+  void OnDidPresentCompositorFrame(
+      ui::Compositor* compositor,
+      uint32_t frame_token,
+      const gfx::PresentationFeedback& feedback) override {
     if (!pending_compositing_.count(compositor))
       return;
     CompositorInfo& compositor_info = pending_compositing_[compositor];
@@ -119,6 +123,7 @@
 
     RunCallbackIfAllCompositingEnded();
   }
+
   void OnCompositingShuttingDown(ui::Compositor* compositor) override {
     compositor_observations_.RemoveObservation(compositor);
     pending_compositing_.erase(compositor);
diff --git a/ash/system/power/power_event_observer_test_api.cc b/ash/system/power/power_event_observer_test_api.cc
index 8f7b944a7..cad59faf 100644
--- a/ash/system/power/power_event_observer_test_api.cc
+++ b/ash/system/power/power_event_observer_test_api.cc
@@ -7,6 +7,7 @@
 #include "ash/system/power/power_event_observer.h"
 #include "base/time/time.h"
 #include "ui/compositor/compositor_observer.h"
+#include "ui/gfx/presentation_feedback.h"
 
 namespace ash {
 
@@ -40,8 +41,8 @@
     ui::Compositor* compositor) {
   if (!power_event_observer_->compositor_watcher_.get())
     return;
-  power_event_observer_->compositor_watcher_->OnCompositingAckDeprecated(
-      compositor);
+  power_event_observer_->compositor_watcher_->OnDidPresentCompositorFrame(
+      compositor, /*frame_token=*/0, gfx::PresentationFeedback());
 }
 
 void PowerEventObserverTestApi::CompositeFrame(ui::Compositor* compositor) {
@@ -51,8 +52,8 @@
       compositor);
   power_event_observer_->compositor_watcher_->OnCompositingStarted(
       compositor, base::TimeTicks());
-  power_event_observer_->compositor_watcher_->OnCompositingAckDeprecated(
-      compositor);
+  power_event_observer_->compositor_watcher_->OnDidPresentCompositorFrame(
+      compositor, /*frame_token=*/0, gfx::PresentationFeedback());
 }
 
 bool PowerEventObserverTestApi::SimulateCompositorsReadyForSuspend() {
diff --git a/base/android/java/src/org/chromium/base/AconfigFlaggedApiDelegate.java b/base/android/java/src/org/chromium/base/AconfigFlaggedApiDelegate.java
index 1d398ec..feb8b032 100644
--- a/base/android/java/src/org/chromium/base/AconfigFlaggedApiDelegate.java
+++ b/base/android/java/src/org/chromium/base/AconfigFlaggedApiDelegate.java
@@ -50,16 +50,6 @@
     }
 
     /**
-     * Checks if the display topology is available, based on the API level and Aconfig flags.
-     *
-     * @deprecated Use {@link #isDisplayTopologyAvailable(DisplayManager)} instead.
-     */
-    @Deprecated
-    default boolean isDisplayTopologyAvailable() {
-        return false;
-    }
-
-    /**
      * Checks if the display topology is available, based on the API level, Aconfig flags and
      * Display Topology state.
      *
diff --git a/build/android/gyp/lint.py b/build/android/gyp/lint.py
index c718887..61986747e 100755
--- a/build/android/gyp/lint.py
+++ b/build/android/gyp/lint.py
@@ -333,9 +333,6 @@
         # If all the warnings are filtered, we should not fail on the final
         # summary line.
         r'\d+ errors?, \d+ warnings?',
-        # Not sure why, these were new starting 32.0.0-alpha01. Remove when
-        # https://crbug.com/439854682 is fixed.
-        'WARNING: skipping method null',
     ]
     return build_utils.FilterLines(output, '|'.join(filter_patterns))
 
diff --git a/build/config/locales.gni b/build/config/locales.gni
index 37632eb..4d503e1 100644
--- a/build/config/locales.gni
+++ b/build/config/locales.gni
@@ -16,7 +16,7 @@
   # pass the "--translate-genders" flag into GRIT to generate 4 translation
   # files per locale ("", "_FEMININE", "_MASCULINE", "_NEUTER") instead of
   # one.
-  translate_genders = true
+  translate_genders = !is_ios
 }
 
 # The following additional platform specific lists are created:
diff --git a/chrome/android/java/res/layout/incognito_indicator.xml b/chrome/android/java/res/layout/incognito_indicator.xml
index e4a7a801..afa3a963 100644
--- a/chrome/android/java/res/layout/incognito_indicator.xml
+++ b/chrome/android/java/res/layout/incognito_indicator.xml
@@ -4,27 +4,34 @@
 Use of this source code is governed by a BSD-style license that can be
 found in the LICENSE file.
 -->
-<LinearLayout
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:orientation="horizontal"
-    android:layout_height="match_parent"
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
     android:layout_width="wrap_content"
-    android:clickable="false"
-    android:focusable="true"
+    android:layout_height="match_parent"
     android:visibility="gone"
-    android:gravity="center_vertical"
-    android:paddingStart="@dimen/incognito_indicator_lateral_start_padding"
-    android:paddingEnd="@dimen/incognito_indicator_lateral_end_padding"
-    android:background="@drawable/incognito_indicator_background">
-  <ImageView
+    android:paddingVertical="@dimen/location_bar_vertical_margin"
+    android:paddingHorizontal="@dimen/incognito_indicator_lateral_margin">
+  <LinearLayout
+      android:orientation="horizontal"
+      android:layout_height="match_parent"
       android:layout_width="wrap_content"
-      android:layout_height="wrap_content"
-      android:contentDescription="@string/accessibility_incognito_badge"
-      android:src="@drawable/ic_incognito"/>
-  <TextView
-      android:layout_width="wrap_content"
-      android:layout_height="wrap_content"
-      android:layout_marginStart="@dimen/incognito_indicator_text_lateral_start_margin"
-      android:textAppearance="@style/TextAppearance.TextLarge.Secondary.Baseline.Light"
-      android:text="@string/incognito_badge" />
-</LinearLayout>
+      android:clickable="false"
+      android:focusable="true"
+      android:gravity="center_vertical"
+      android:paddingStart="@dimen/incognito_indicator_lateral_start_padding"
+      android:paddingEnd="@dimen/incognito_indicator_lateral_end_padding"
+      android:background="@drawable/incognito_indicator_background"
+      tools:ignore="UselessParent">
+    <ImageView
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:contentDescription="@string/accessibility_incognito_badge"
+        android:src="@drawable/ic_incognito"/>
+    <TextView
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="@dimen/incognito_indicator_text_lateral_start_margin"
+        android:textAppearance="@style/TextAppearance.TextLarge.Secondary.Baseline.Light"
+        android:text="@string/incognito_badge" />
+  </LinearLayout>
+</FrameLayout>
\ No newline at end of file
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/appearance/settings/AppearanceSettingsFragment.java b/chrome/android/java/src/org/chromium/chrome/browser/appearance/settings/AppearanceSettingsFragment.java
index 8aa952d8..efd54ab0 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/appearance/settings/AppearanceSettingsFragment.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/appearance/settings/AppearanceSettingsFragment.java
@@ -5,13 +5,17 @@
 package org.chromium.chrome.browser.appearance.settings;
 
 import android.content.Context;
+import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
 import android.os.Bundle;
 
+import org.chromium.base.ContextUtils;
+import org.chromium.base.DeviceInfo;
 import org.chromium.base.supplier.ObservableSupplier;
 import org.chromium.base.supplier.ObservableSupplierImpl;
 import org.chromium.build.annotations.NullMarked;
 import org.chromium.build.annotations.Nullable;
 import org.chromium.chrome.R;
+import org.chromium.chrome.browser.bookmarks.bar.BookmarkBarConstants;
 import org.chromium.chrome.browser.bookmarks.bar.BookmarkBarUtils;
 import org.chromium.chrome.browser.night_mode.NightModeMetrics.ThemeSettingsEntry;
 import org.chromium.chrome.browser.night_mode.NightModeUtils;
@@ -36,15 +40,20 @@
     public static final String PREF_UI_THEME = "ui_theme";
 
     private final ObservableSupplierImpl<String> mPageTitle = new ObservableSupplierImpl<>();
+    private boolean mUseProfileUserPrefs;
 
     private @Nullable PrefChangeRegistrar mPrefChangeRegistrar;
     private @Nullable PrefObserver mPrefObserver;
+    private @Nullable OnSharedPreferenceChangeListener mDevicePrefsListener;
 
     @Override
     public void onCreatePreferences(@Nullable Bundle savedInstanceState, @Nullable String rootKey) {
         mPageTitle.set(getTitle(getContext()));
         SettingsUtils.addPreferencesFromResource(this, R.xml.appearance_preferences);
 
+        // This fragment may be used on Desktop or tablets. For Desktop we use the current Profile's
+        // UserPrefs. For tablets, we use the local device preference.
+        mUseProfileUserPrefs = DeviceInfo.isDesktop();
         initBookmarkBarPref();
         initToolbarShortcutPref();
         initUiThemePref();
@@ -59,6 +68,11 @@
             mPrefChangeRegistrar.destroy();
             mPrefChangeRegistrar = null;
         }
+        if (mDevicePrefsListener != null) {
+            ContextUtils.getAppSharedPreferences()
+                    .unregisterOnSharedPreferenceChangeListener(mDevicePrefsListener);
+            mDevicePrefsListener = null;
+        }
     }
 
     @Override
@@ -92,6 +106,14 @@
             return;
         }
 
+        if (mUseProfileUserPrefs) {
+            initBookmarkBarPrefForUserPrefs();
+        } else {
+            initBookmarkBarPrefForDevicePreference();
+        }
+    }
+
+    private void initBookmarkBarPrefForUserPrefs() {
         mPrefChangeRegistrar = PrefServiceUtil.createFor(getProfile());
         mPrefObserver =
                 new PrefObserver() {
@@ -113,6 +135,28 @@
                         });
     }
 
+    private void initBookmarkBarPrefForDevicePreference() {
+        // Similar to UserPrefs above, we must have both an observer of changes to the device prefs,
+        // as well as the ability to set the device prefs via the toggle, since the value can be
+        // toggled by another window.
+        mDevicePrefsListener =
+                (sharedPreferences, key) -> {
+                    if (key != null
+                            && key.equals(BookmarkBarConstants.BOOKMARK_BAR_SHOW_BOOKMARK_BAR)) {
+                        updateBookmarkBarPref();
+                    }
+                };
+        ContextUtils.getAppSharedPreferences()
+                .registerOnSharedPreferenceChangeListener(mDevicePrefsListener);
+
+        ((ChromeSwitchPreference) findPreference(PREF_BOOKMARK_BAR))
+                .setOnPreferenceChangeListener(
+                        (pref, newValue) -> {
+                            BookmarkBarUtils.setDevicePrefShowBookmarksBar((boolean) newValue);
+                            return true;
+                        });
+    }
+
     private void initToolbarShortcutPref() {
         // LINT.IfChange(InitPrefToolbarShortcut)
         new AdaptiveToolbarStatePredictor(
@@ -143,9 +187,16 @@
     }
 
     private void updateBookmarkBarPref() {
-        if (BookmarkBarUtils.isDeviceBookmarkBarCompatible(getContext())) {
+        if (!BookmarkBarUtils.isDeviceBookmarkBarCompatible(getContext())) {
+            return;
+        }
+
+        if (mUseProfileUserPrefs) {
             ((ChromeSwitchPreference) findPreference(PREF_BOOKMARK_BAR))
                     .setChecked(BookmarkBarUtils.isUserPrefsShowBookmarksBarEnabled(getProfile()));
+        } else {
+            ((ChromeSwitchPreference) findPreference(PREF_BOOKMARK_BAR))
+                    .setChecked(BookmarkBarUtils.isDevicePrefShowBookmarksBarEnabled());
         }
     }
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/compositor/overlays/strip/reorder/TabStripDragHandler.java b/chrome/android/java/src/org/chromium/chrome/browser/compositor/overlays/strip/reorder/TabStripDragHandler.java
index 8787a830..bd0f780a 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/compositor/overlays/strip/reorder/TabStripDragHandler.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/compositor/overlays/strip/reorder/TabStripDragHandler.java
@@ -37,6 +37,7 @@
 import org.chromium.chrome.browser.dragdrop.ChromeDragDropUtils;
 import org.chromium.chrome.browser.dragdrop.ChromeDropDataAndroid;
 import org.chromium.chrome.browser.dragdrop.ChromeTabDropDataAndroid;
+import org.chromium.chrome.browser.flags.ChromeFeatureList;
 import org.chromium.chrome.browser.multiwindow.MultiInstanceManager;
 import org.chromium.chrome.browser.tab.Tab;
 import org.chromium.chrome.browser.tab_ui.TabContentManager;
@@ -599,6 +600,8 @@
     }
 
     public static boolean isDraggedItemPinned() {
+        if (!ChromeFeatureList.sAndroidPinnedTabs.isEnabled()) return false;
+
         @Nullable Tab tab =
                 ChromeDragDropUtils.getTabFromGlobalState(
                         getDragDropGlobalState(/* dragEvent= */ null));
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/settings/FragmentDependencyProvider.java b/chrome/android/java/src/org/chromium/chrome/browser/settings/FragmentDependencyProvider.java
index 1e46a04..c04a6e21 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/settings/FragmentDependencyProvider.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/settings/FragmentDependencyProvider.java
@@ -57,7 +57,6 @@
 import org.chromium.components.privacy_sandbox.IncognitoTrackingProtectionsFragment;
 import org.chromium.components.privacy_sandbox.IpProtectionSettingsFragment;
 import org.chromium.components.privacy_sandbox.TrackingProtectionSettings;
-import org.chromium.components.user_prefs.UserPrefs;
 import org.chromium.ui.modaldialog.ModalDialogManager;
 
 /** Provides dependencies to fragments in the settings activity. */
@@ -123,7 +122,6 @@
                     new SafetyCheckBridge(mProfile),
                     mModalDialogManagerSupplier,
                     SyncServiceFactory.getForProfile(mProfile),
-                    UserPrefs.get(mProfile),
                     new PasswordStoreBridge(mProfile),
                     PasswordManagerHelper.getForProfile(mProfile),
                     new SettingsCustomTabLauncherImpl());
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tabbed_mode/KeyboardFocusRowManager.java b/chrome/android/java/src/org/chromium/chrome/browser/tabbed_mode/KeyboardFocusRowManager.java
index 7a636eca..5c24deb8 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/tabbed_mode/KeyboardFocusRowManager.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/tabbed_mode/KeyboardFocusRowManager.java
@@ -10,6 +10,7 @@
 import org.chromium.chrome.browser.bookmarks.bar.BookmarkBarCoordinator;
 import org.chromium.chrome.browser.compositor.CompositorViewHolder;
 import org.chromium.chrome.browser.compositor.overlays.strip.StripLayoutHelperManager;
+import org.chromium.chrome.browser.flags.ChromeFeatureList;
 import org.chromium.chrome.browser.tab.TabObscuringHandler;
 import org.chromium.chrome.browser.toolbar.ToolbarManager;
 import org.chromium.chrome.browser.toolbar.top.tab_strip.StripVisibilityState;
@@ -150,9 +151,11 @@
         }
 
         // The next item in the focus cycle order is BOOKMARKS_BAR, if it is present.
-        var bookmarkBarCoordinator = mBookmarkBarCoordinatorSupplier.get();
-        if (bookmarkBarCoordinator != null && bookmarkBarCoordinator.isVisible()) {
-            keyboardFocusRows.add(KeyboardFocusRow.BOOKMARKS_BAR);
+        if (ChromeFeatureList.sAndroidBookmarkBar.isEnabled()) {
+            var bookmarkBarCoordinator = mBookmarkBarCoordinatorSupplier.get();
+            if (bookmarkBarCoordinator != null && bookmarkBarCoordinator.isVisible()) {
+                keyboardFocusRows.add(KeyboardFocusRow.BOOKMARKS_BAR);
+            }
         }
 
         int currentFocusIndex = keyboardFocusRows.indexOf(oldKeyboardFocusRow);
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tabbed_mode/TabbedAppMenuPropertiesDelegate.java b/chrome/android/java/src/org/chromium/chrome/browser/tabbed_mode/TabbedAppMenuPropertiesDelegate.java
index 2beb693..5a90f67 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/tabbed_mode/TabbedAppMenuPropertiesDelegate.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/tabbed_mode/TabbedAppMenuPropertiesDelegate.java
@@ -282,7 +282,10 @@
                         buildModelForDivider(R.id.divider_line_id)));
 
         // Page Zoom
-        if (shouldShowPageZoomItem(currentTab)) modelList.add(buildPageZoomItem(currentTab));
+        // Disable page zoom menu item on Reading Mode pages.
+        if (shouldShowPageZoomItem(currentTab) && !shouldShowReaderModePrefs(currentTab)) {
+            modelList.add(buildPageZoomItem(currentTab));
+        }
 
         // Share
         if (ShareUtils.shouldEnableShare(currentTab)) {
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tabbed_mode/TabbedRootUiCoordinator.java b/chrome/android/java/src/org/chromium/chrome/browser/tabbed_mode/TabbedRootUiCoordinator.java
index b1f280e..0d1b4b9 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/tabbed_mode/TabbedRootUiCoordinator.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/tabbed_mode/TabbedRootUiCoordinator.java
@@ -25,6 +25,7 @@
 import org.chromium.base.ApplicationStatus;
 import org.chromium.base.Callback;
 import org.chromium.base.CommandLine;
+import org.chromium.base.DeviceInfo;
 import org.chromium.base.Log;
 import org.chromium.base.ResettersForTesting;
 import org.chromium.base.Token;
@@ -1896,7 +1897,11 @@
             return true;
         } else if (id == R.id.toggle_bookmark_bar) {
             if (BookmarkBarUtils.isActivityStateBookmarkBarCompatible(mActivity)) {
-                BookmarkBarUtils.toggleUserPrefsShowBookmarksBar(mProfileSupplier.get());
+                if (DeviceInfo.isDesktop()) {
+                    BookmarkBarUtils.toggleUserPrefsShowBookmarksBar(mProfileSupplier.get());
+                } else {
+                    BookmarkBarUtils.toggleDevicePrefShowBookmarksBar();
+                }
                 return true;
             }
         }
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/appearance/settings/AppearanceSettingsFragmentTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/appearance/settings/AppearanceSettingsFragmentTest.java
index 1d3ac37c..08af55c 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/appearance/settings/AppearanceSettingsFragmentTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/appearance/settings/AppearanceSettingsFragmentTest.java
@@ -33,11 +33,13 @@
 import org.mockito.stubbing.Answer;
 
 import org.chromium.base.Callback;
+import org.chromium.base.FeatureOverrides;
 import org.chromium.base.ThreadUtils;
 import org.chromium.base.supplier.ObservableSupplierImpl;
 import org.chromium.base.test.BaseJUnit4ClassRunner;
 import org.chromium.base.test.util.Batch;
 import org.chromium.chrome.browser.bookmarks.bar.BookmarkBarUtils;
+import org.chromium.chrome.browser.flags.ChromeFeatureList;
 import org.chromium.chrome.browser.night_mode.NightModeMetrics.ThemeSettingsEntry;
 import org.chromium.chrome.browser.night_mode.NightModeUtils;
 import org.chromium.chrome.browser.night_mode.ThemeType;
@@ -47,6 +49,7 @@
 import org.chromium.chrome.browser.profiles.Profile;
 import org.chromium.chrome.browser.toolbar.adaptive.AdaptiveToolbarStatePredictor;
 import org.chromium.chrome.browser.toolbar.adaptive.settings.AdaptiveToolbarSettingsFragment;
+import org.chromium.chrome.test.OverrideContextWrapperTestRule;
 import org.chromium.components.browser_ui.settings.BlankUiTestActivitySettingsTestRule;
 import org.chromium.components.browser_ui.settings.ChromeSwitchPreference;
 import org.chromium.components.prefs.PrefChangeRegistrar;
@@ -68,6 +71,10 @@
     public final BlankUiTestActivitySettingsTestRule mSettingsTestRule =
             new BlankUiTestActivitySettingsTestRule();
 
+    @Rule
+    public OverrideContextWrapperTestRule mOverrideContextRule =
+            new OverrideContextWrapperTestRule();
+
     @Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule();
 
     @Mock private PrefChangeRegistrar.Natives mPrefChangeRegistrarJni;
@@ -107,6 +114,12 @@
         doAnswer(runCallbackWithValueAtIndex(mBookmarkBarSettingSupplier::set, 1))
                 .when(mPrefService)
                 .setBoolean(eq(Pref.SHOW_BOOKMARK_BAR), anyBoolean());
+
+        // Explicitly override FeatureParam for consistency.
+        FeatureOverrides.Builder overrides = FeatureOverrides.newBuilder();
+        overrides =
+                overrides.param(ChromeFeatureList.ANDROID_BOOKMARK_BAR, "show_bookmark_bar", true);
+        overrides.apply();
     }
 
     @AfterClass
@@ -133,7 +146,9 @@
 
     @Test
     @SmallTest
-    public void testBookmarkBarPreferenceUpdatesSettingWhenChanged() {
+    public void testBookmarkBarPreferenceUpdatesSettingWhenChanged_Desktop() {
+        mOverrideContextRule.setIsDesktop(true);
+
         ThreadUtils.runOnUiThreadBlocking(() -> mBookmarkBarSettingSupplier.set(true));
         BookmarkBarUtils.setDeviceBookmarkBarCompatibleForTesting(true);
         launchSettings();
@@ -152,7 +167,9 @@
 
     @Test
     @SmallTest
-    public void testBookmarkBarPreferenceIsUpdatedWhenSettingChanges() {
+    public void testBookmarkBarPreferenceIsUpdatedWhenSettingChanges_Desktop() {
+        mOverrideContextRule.setIsDesktop(true);
+
         ThreadUtils.runOnUiThreadBlocking(() -> mBookmarkBarSettingSupplier.set(true));
         BookmarkBarUtils.setDeviceBookmarkBarCompatibleForTesting(true);
         launchSettings();
@@ -169,6 +186,48 @@
 
     @Test
     @SmallTest
+    public void testBookmarkBarPreferenceUpdatesSettingWhenChanged_Tablet() {
+        mOverrideContextRule.setIsDesktop(false);
+
+        BookmarkBarUtils.setDeviceBookmarkBarCompatibleForTesting(true);
+        launchSettings();
+
+        final var bookmarkBarPref = assertSwitchExists(PREF_BOOKMARK_BAR);
+        Assert.assertTrue(bookmarkBarPref.isChecked());
+
+        ThreadUtils.runOnUiThreadBlocking(bookmarkBarPref::performClick);
+        Assert.assertFalse(bookmarkBarPref.isChecked());
+        Assert.assertFalse(BookmarkBarUtils.isDevicePrefShowBookmarksBarEnabled());
+        Assert.assertTrue(BookmarkBarUtils.hasUserSetDevicePrefShowBookmarksBar());
+
+        ThreadUtils.runOnUiThreadBlocking(bookmarkBarPref::performClick);
+        Assert.assertTrue(bookmarkBarPref.isChecked());
+        Assert.assertTrue(BookmarkBarUtils.isDevicePrefShowBookmarksBarEnabled());
+        Assert.assertTrue(BookmarkBarUtils.hasUserSetDevicePrefShowBookmarksBar());
+    }
+
+    @Test
+    @SmallTest
+    public void testBookmarkBarPreferenceIsUpdatedWhenSettingChanges_Tablet() {
+        mOverrideContextRule.setIsDesktop(false);
+
+        BookmarkBarUtils.setDeviceBookmarkBarCompatibleForTesting(true);
+        launchSettings();
+
+        final var bookmarkBarPref = assertSwitchExists(PREF_BOOKMARK_BAR);
+        Assert.assertTrue(bookmarkBarPref.isChecked());
+
+        ThreadUtils.runOnUiThreadBlocking(
+                () -> BookmarkBarUtils.setDevicePrefShowBookmarksBar(false));
+        Assert.assertFalse(bookmarkBarPref.isChecked());
+
+        ThreadUtils.runOnUiThreadBlocking(
+                () -> BookmarkBarUtils.setDevicePrefShowBookmarksBar(true));
+        Assert.assertTrue(bookmarkBarPref.isChecked());
+    }
+
+    @Test
+    @SmallTest
     public void testToolbarShortcutPreferenceIsAbsentWhenDisabled() {
         AdaptiveToolbarStatePredictor.setToolbarStateForTesting(NONE);
         launchSettings();
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/password_manager/settings/PasswordsPreferenceTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/password_manager/settings/PasswordsPreferenceTest.java
index 5202de11..84c7ef3 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/password_manager/settings/PasswordsPreferenceTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/password_manager/settings/PasswordsPreferenceTest.java
@@ -4,8 +4,7 @@
 
 package org.chromium.chrome.browser.password_manager.settings;
 
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.when;
 
 import androidx.test.filters.MediumTest;
@@ -30,7 +29,6 @@
 import org.chromium.chrome.browser.password_manager.PasswordManagerTestHelper;
 import org.chromium.chrome.browser.password_manager.PasswordManagerUtilBridge;
 import org.chromium.chrome.browser.password_manager.PasswordManagerUtilBridgeJni;
-import org.chromium.chrome.browser.preferences.Pref;
 import org.chromium.chrome.browser.settings.MainSettings;
 import org.chromium.chrome.browser.settings.SettingsActivityTestRule;
 import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
@@ -38,7 +36,6 @@
 import org.chromium.chrome.test.transit.settings.SettingsActivityPublicTransitEntryPoints;
 import org.chromium.chrome.test.transit.settings.SettingsStation;
 import org.chromium.chrome.test.util.ChromeRenderTestRule;
-import org.chromium.components.prefs.PrefService;
 import org.chromium.ui.test.util.DeviceRestriction;
 import org.chromium.ui.test.util.RenderTestRule.Component;
 
@@ -66,8 +63,6 @@
 
     @Rule public MockitoRule mMockitoRule = MockitoJUnit.rule();
 
-    @Mock PrefService mPrefService;
-
     @Mock private PasswordManagerUtilBridge.Natives mPasswordManagerUtilBridgeJniMock;
     @Mock private LoginDbDeprecationUtilBridge.Natives mLoginDbDeprecationUtilBridgeJniMock;
 
@@ -79,17 +74,13 @@
         PasswordManagerUtilBridgeJni.setInstanceForTesting(mPasswordManagerUtilBridgeJniMock);
         LoginDbDeprecationUtilBridgeJni.setInstanceForTesting(mLoginDbDeprecationUtilBridgeJniMock);
         PasswordManagerTestHelper.setUpGmsCoreFakeBackends();
-
-        PasswordsPreference.setPrefServiceForTesting(mPrefService);
     }
 
     @Test
     @MediumTest
     @Feature({"RenderTest"})
     public void testPwmStoppedWorkingSubtitle() throws IOException {
-        when(mPasswordManagerUtilBridgeJniMock.isPasswordManagerAvailable(any(), eq(true)))
-                .thenReturn(false);
-        when(mPrefService.getBoolean(Pref.UPM_UNMIGRATED_PASSWORDS_EXPORTED)).thenReturn(true);
+        when(mPasswordManagerUtilBridgeJniMock.isPasswordManagerAvailable(true)).thenReturn(false);
         when(mLoginDbDeprecationUtilBridgeJniMock.getAutoExportCsvFilePath(any()))
                 .thenReturn("random/file/path");
 
@@ -106,9 +97,7 @@
     @Feature({"RenderTest"})
     @Restriction({DeviceRestriction.RESTRICTION_TYPE_NON_AUTO})
     public void testSomePasswordsNotAccessibleSubtitle() throws IOException {
-        when(mPasswordManagerUtilBridgeJniMock.isPasswordManagerAvailable(any(), eq(true)))
-                .thenReturn(true);
-        when(mPrefService.getBoolean(Pref.UPM_UNMIGRATED_PASSWORDS_EXPORTED)).thenReturn(true);
+        when(mPasswordManagerUtilBridgeJniMock.isPasswordManagerAvailable(true)).thenReturn(true);
         File fakeCsv = File.createTempFile("passwords", null, null);
         fakeCsv.deleteOnExit();
         when(mLoginDbDeprecationUtilBridgeJniMock.getAutoExportCsvFilePath(any()))
@@ -127,9 +116,7 @@
     @Feature({"RenderTest"})
     @Restriction({DeviceRestriction.RESTRICTION_TYPE_AUTO})
     public void testSomePasswordsNotAccessibleSubtitleNotDisplayedOnAuto() throws IOException {
-        when(mPasswordManagerUtilBridgeJniMock.isPasswordManagerAvailable(any(), eq(true)))
-                .thenReturn(true);
-        when(mPrefService.getBoolean(Pref.UPM_UNMIGRATED_PASSWORDS_EXPORTED)).thenReturn(true);
+        when(mPasswordManagerUtilBridgeJniMock.isPasswordManagerAvailable(true)).thenReturn(true);
         File fakeCsv = File.createTempFile("passwords", null, null);
         fakeCsv.deleteOnExit();
         when(mLoginDbDeprecationUtilBridgeJniMock.getAutoExportCsvFilePath(any()))
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/tabbed_mode/KeyboardFocusRowManagerTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/tabbed_mode/KeyboardFocusRowManagerTest.java
index 978e92344..065801b 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/tabbed_mode/KeyboardFocusRowManagerTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/tabbed_mode/KeyboardFocusRowManagerTest.java
@@ -17,6 +17,7 @@
 import androidx.test.filters.SmallTest;
 import androidx.test.platform.app.InstrumentationRegistry;
 
+import org.junit.After;
 import org.junit.Before;
 import org.junit.BeforeClass;
 import org.junit.Rule;
@@ -25,19 +26,23 @@
 import org.mockito.junit.MockitoJUnit;
 import org.mockito.junit.MockitoRule;
 
+import org.chromium.base.FeatureOverrides;
 import org.chromium.base.ThreadUtils;
 import org.chromium.base.test.util.Batch;
 import org.chromium.base.test.util.CallbackHelper;
 import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.Feature;
+import org.chromium.base.test.util.Features.DisableFeatures;
 import org.chromium.base.test.util.Features.EnableFeatures;
 import org.chromium.base.test.util.Restriction;
 import org.chromium.chrome.R;
 import org.chromium.chrome.browser.ChromeTabbedActivity;
+import org.chromium.chrome.browser.bookmarks.bar.BookmarkBarUtils;
 import org.chromium.chrome.browser.flags.ChromeFeatureList;
 import org.chromium.chrome.browser.flags.ChromeSwitches;
 import org.chromium.chrome.browser.tab.TabObscuringHandler;
 import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
+import org.chromium.chrome.test.OverrideContextWrapperTestRule;
 import org.chromium.chrome.test.transit.ChromeTransitTestRules;
 import org.chromium.chrome.test.transit.ReusedCtaTransitTestRule;
 import org.chromium.chrome.test.transit.page.WebPageStation;
@@ -55,12 +60,17 @@
 @RunWith(ChromeJUnit4ClassRunner.class)
 @Batch(Batch.PER_CLASS)
 @CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE})
+@DisableFeatures(ChromeFeatureList.ANDROID_BOOKMARK_BAR)
 public class KeyboardFocusRowManagerTest {
 
     @Rule
     public ReusedCtaTransitTestRule<WebPageStation> mActivityTestRule =
             ChromeTransitTestRules.blankPageStartReusedActivityRule();
 
+    @Rule
+    public OverrideContextWrapperTestRule mOverrideContextRule =
+            new OverrideContextWrapperTestRule();
+
     @Rule public MockitoRule mockito = MockitoJUnit.rule(); // todo delete if not needed
 
     private WebPageStation mPage;
@@ -71,6 +81,12 @@
     @BeforeClass
     public static void setUpClass() {
         TabbedRootUiCoordinator.setDisableTopControlsAnimationsForTesting(true);
+
+        // Explicitly override FeatureParam for consistency.
+        FeatureOverrides.Builder overrides = FeatureOverrides.newBuilder();
+        overrides =
+                overrides.param(ChromeFeatureList.ANDROID_BOOKMARK_BAR, "show_bookmark_bar", true);
+        overrides.apply();
     }
 
     @Before
@@ -80,6 +96,13 @@
         mTabbedRootUiCoordinator =
                 (TabbedRootUiCoordinator) mActivity.getRootUiCoordinatorForTesting();
         mKeyboardFocusRowManager = mTabbedRootUiCoordinator.getKeyboardFocusRowManagerForTesting();
+        mOverrideContextRule.setIsDesktop(true);
+    }
+
+    @After
+    public void tearDown() {
+        setUserPrefsShowBookmarksBar(false);
+        setBookmarkBarFeatureParam(false);
     }
 
     @Test
@@ -128,6 +151,9 @@
     @Feature("KeyboardShortcuts")
     @EnableFeatures(ChromeFeatureList.ANDROID_BOOKMARK_BAR)
     public void testSwitchKeyboardFocusRow_withBookmarksBar() {
+        setBookmarkBarFeatureParam(true);
+        setUserPrefsShowBookmarksBar(true);
+
         // Put something in the content view so we can focus on it.
         ChromeTabUtils.newTabFromMenu(
                 InstrumentationRegistry.getInstrumentation(), mActivity, false, true);
@@ -155,6 +181,9 @@
     @Restriction(DeviceFormFactor.TABLET_OR_DESKTOP)
     @EnableFeatures(ChromeFeatureList.ANDROID_BOOKMARK_BAR)
     public void testSwitchKeyboardFocusRow_withBookmarkBarFocus() {
+        setBookmarkBarFeatureParam(true);
+        setUserPrefsShowBookmarksBar(true);
+
         ThreadUtils.runOnUiThreadBlocking(
                 mTabbedRootUiCoordinator::initializeBookmarkBarCoordinatorForTesting);
 
@@ -306,4 +335,19 @@
                 KeyboardFocusRow.NONE,
                 mKeyboardFocusRowManager.getKeyboardFocusRowForTesting());
     }
+
+    private void setUserPrefsShowBookmarksBar(boolean showBookmarksBar) {
+        ThreadUtils.runOnUiThreadBlocking(
+                () ->
+                        BookmarkBarUtils.setUserPrefsShowBookmarksBar(
+                                mActivity.getProfileProviderSupplier().get().getOriginalProfile(),
+                                showBookmarksBar));
+    }
+
+    private void setBookmarkBarFeatureParam(boolean param) {
+        FeatureOverrides.Builder overrides = FeatureOverrides.newBuilder();
+        overrides =
+                overrides.param(ChromeFeatureList.ANDROID_BOOKMARK_BAR, "show_bookmark_bar", param);
+        overrides.apply();
+    }
 }
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/password_manager/PasswordCheckupLauncherTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/password_manager/PasswordCheckupLauncherTest.java
index f5369c8..6a39577 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/password_manager/PasswordCheckupLauncherTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/password_manager/PasswordCheckupLauncherTest.java
@@ -6,7 +6,6 @@
 
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.Matchers.is;
-import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.isNull;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.times;
@@ -39,15 +38,12 @@
 import org.chromium.chrome.browser.safety_hub.SafetyHubFragment;
 import org.chromium.chrome.browser.settings.SettingsActivity;
 import org.chromium.chrome.browser.sync.SyncServiceFactory;
-import org.chromium.components.prefs.PrefService;
 import org.chromium.components.signin.AccountManagerFacadeProvider;
 import org.chromium.components.signin.base.AccountInfo;
 import org.chromium.components.signin.test.util.FakeAccountManagerFacade;
 import org.chromium.components.signin.test.util.TestAccounts;
 import org.chromium.components.sync.SyncService;
 import org.chromium.components.sync.UserSelectableType;
-import org.chromium.components.user_prefs.UserPrefs;
-import org.chromium.components.user_prefs.UserPrefsJni;
 import org.chromium.ui.base.WindowAndroid;
 import org.chromium.ui.modaldialog.ModalDialogManager;
 
@@ -71,10 +67,6 @@
 
     @Mock private Profile mProfile;
 
-    @Mock private PrefService mPrefService;
-
-    @Mock private UserPrefs.Natives mMockUserPrefsJni;
-
     @Mock private PasswordManagerUtilBridge.Natives mMockPasswordManagerUtilBridgeJni;
 
     @Mock private SyncService mMockSyncService;
@@ -101,11 +93,9 @@
 
     @Before
     public void setUp() {
-        UserPrefsJni.setInstanceForTesting(mMockUserPrefsJni);
         PasswordManagerUtilBridgeJni.setInstanceForTesting(mMockPasswordManagerUtilBridgeJni);
 
         when(mProfile.getOriginalProfile()).thenReturn(mProfile);
-        when(mMockUserPrefsJni.get(mProfile)).thenReturn(mPrefService);
 
         SyncServiceFactory.setInstanceForTesting(mMockSyncService);
         when(mMockSyncService.isSyncFeatureEnabled()).thenReturn(true);
@@ -141,9 +131,7 @@
     public void testLaunchCheckupOnDeviceShowsAccountCheckup()
             throws PendingIntent.CanceledException {
         when(mMockSyncService.getSelectedTypes()).thenReturn(Set.of(UserSelectableType.PASSWORDS));
-        when(mMockPasswordManagerUtilBridgeJni.isPasswordManagerAvailable(
-                        eq(mPrefService), eq(true)))
-                .thenReturn(true);
+        when(mMockPasswordManagerUtilBridgeJni.isPasswordManagerAvailable(true)).thenReturn(true);
 
         PasswordCheckupLauncher.launchCheckupOnDevice(
                 mProfile, mMockWindowAndroid, LEAK_DIALOG, TestAccounts.ACCOUNT1.getEmail());
@@ -155,9 +143,7 @@
     public void testLaunchCheckupOnDeviceShowsLocalCheckup()
             throws PendingIntent.CanceledException {
         when(mMockSyncService.getSelectedTypes()).thenReturn(Collections.emptySet());
-        when(mMockPasswordManagerUtilBridgeJni.isPasswordManagerAvailable(
-                        eq(mPrefService), eq(true)))
-                .thenReturn(true);
+        when(mMockPasswordManagerUtilBridgeJni.isPasswordManagerAvailable(true)).thenReturn(true);
 
         PasswordCheckupLauncher.launchCheckupOnDevice(
                 mProfile, mMockWindowAndroid, LEAK_DIALOG, TEST_NO_EMAIL_ADDRESS);
@@ -171,9 +157,7 @@
         // Local checkup will be launched from the leak detection dialog if the leaked credential is
         // stored only in the local store, even though the user is syncing passwords.
         when(mMockSyncService.getSelectedTypes()).thenReturn(Set.of(UserSelectableType.PASSWORDS));
-        when(mMockPasswordManagerUtilBridgeJni.isPasswordManagerAvailable(
-                        eq(mPrefService), eq(true)))
-                .thenReturn(true);
+        when(mMockPasswordManagerUtilBridgeJni.isPasswordManagerAvailable(true)).thenReturn(true);
 
         PasswordCheckupLauncher.launchCheckupOnDevice(
                 mProfile, mMockWindowAndroid, LEAK_DIALOG, TEST_NO_EMAIL_ADDRESS);
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/tabbed_mode/TabbedAppMenuPropertiesDelegateUnitTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/tabbed_mode/TabbedAppMenuPropertiesDelegateUnitTest.java
index 63f9762..c18d28f 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/tabbed_mode/TabbedAppMenuPropertiesDelegateUnitTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/tabbed_mode/TabbedAppMenuPropertiesDelegateUnitTest.java
@@ -1584,6 +1584,20 @@
                         context.getString(R.string.hide_reading_mode_text)));
     }
 
+    @Test
+    public void pageZoomMenuOption_NotVisibleInReadingMode() {
+        setUpMocksForPageMenu();
+        PageZoomCoordinator.setShouldShowMenuItemForTesting(true);
+        doReturn(true)
+                .when(mTabbedAppMenuPropertiesDelegate)
+                .shouldShowReaderModePrefs(any(Tab.class));
+        when(mTab.getUrl()).thenReturn(JUnitTestGURLs.CHROME_DISTILLER_EXAMPLE_URL);
+
+        MVCListAdapter.ModelList modelList = mTabbedAppMenuPropertiesDelegate.getMenuItems();
+
+        assertFalse(isMenuVisible(modelList, R.id.page_zoom_id));
+    }
+
     private MVCListAdapter.ModelList setUpMenuWithIncognitoReauthPage(boolean isShowing) {
         setUpMocksForOverviewMenu();
         when(mTabModelSelector.getCurrentModel()).thenReturn(mIncognitoTabModel);
diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn
index ac05a7553..4a071e70 100644
--- a/chrome/browser/BUILD.gn
+++ b/chrome/browser/BUILD.gn
@@ -1462,6 +1462,8 @@
     "site_isolation/site_details.cc",
     "site_isolation/site_details.h",
     "site_protection/site_familiarity_heuristic_name.h",
+    "site_protection/site_familiarity_utils.cc",
+    "site_protection/site_familiarity_utils.h",
     "site_protection/site_protection_metrics.h",
     "site_protection/site_protection_metrics_observer.cc",
     "site_protection/site_protection_metrics_observer.h",
@@ -3814,8 +3816,6 @@
       "download/download_dir_policy_handler.h",
       "download/download_dir_util.cc",
       "download/download_dir_util.h",
-      "download/download_item_web_app_data.cc",
-      "download/download_item_web_app_data.h",
       "download/download_open_prompt.cc",
       "download/download_open_prompt.h",
       "download/download_ui_enterprise_util.cc",
diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc
index eb424480..eea1c10 100644
--- a/chrome/browser/about_flags.cc
+++ b/chrome/browser/about_flags.cc
@@ -13269,7 +13269,7 @@
      flag_descriptions::kAndroidAnimatedProgressBarInBrowserName,
      flag_descriptions::kAndroidAnimatedProgressBarInBrowserDescription,
      kOsAndroid,
-     FEATURE_VALUE_TYPE(chrome::android::kAndroidAnimatedProgressBarInBrowser)},
+     FEATURE_VALUE_TYPE(features::kAndroidAnimatedProgressBarInBrowser)},
 #endif  // BUILDFLAG(IS_ANDROID)
 
     {"enable-cross-device-pref-tracker",
diff --git a/chrome/browser/actor/actor_keyed_service_fake.cc b/chrome/browser/actor/actor_keyed_service_fake.cc
index 590d358..ef11c6f 100644
--- a/chrome/browser/actor/actor_keyed_service_fake.cc
+++ b/chrome/browser/actor/actor_keyed_service_fake.cc
@@ -13,7 +13,6 @@
 
 namespace actor {
 using ::testing::_;
-using ::testing::Invoke;
 
 ActorKeyedServiceFake::ActorKeyedServiceFake(Profile* profile)
     : ActorKeyedService(profile) {}
@@ -33,15 +32,15 @@
 
   for (auto& mock : {mock_ui_dispatcher, mock_task_ui_dispatcher}) {
     ON_CALL(*mock, OnPreTool(_, _))
-        .WillByDefault(Invoke(UiEventDispatcherCallback<ToolRequest>(
-            base::BindRepeating(MakeOkResult))));
+        .WillByDefault(UiEventDispatcherCallback<ToolRequest>(
+            base::BindRepeating(MakeOkResult)));
     ON_CALL(*mock, OnPostTool(_, _))
-        .WillByDefault(Invoke(UiEventDispatcherCallback<ToolRequest>(
-            base::BindRepeating(MakeOkResult))));
+        .WillByDefault(UiEventDispatcherCallback<ToolRequest>(
+            base::BindRepeating(MakeOkResult)));
     ON_CALL(*mock, OnActorTaskAsyncChange(_, _))
-        .WillByDefault(Invoke(UiEventDispatcherCallback<
-                              ui::UiEventDispatcher::ActorTaskAsyncChange>(
-            base::BindRepeating(MakeOkResult))));
+        .WillByDefault(UiEventDispatcherCallback<
+                       ui::UiEventDispatcher::ActorTaskAsyncChange>(
+            base::BindRepeating(MakeOkResult)));
   }
   auto execution_engine = ExecutionEngine::CreateForTesting(
       GetProfile(), std::move(ui_event_dispatcher));
diff --git a/chrome/browser/actor/execution_engine_unittest.cc b/chrome/browser/actor/execution_engine_unittest.cc
index fa71409a..d74145b06 100644
--- a/chrome/browser/actor/execution_engine_unittest.cc
+++ b/chrome/browser/actor/execution_engine_unittest.cc
@@ -43,7 +43,6 @@
 using testing::_;
 using testing::Eq;
 using testing::Field;
-using testing::Invoke;
 using testing::Property;
 using testing::VariantWith;
 using ChangeTaskState = ui::UiEventDispatcher::ChangeTaskState;
@@ -152,15 +151,15 @@
     for (auto& mock :
          {mock_ui_event_dispatcher_, task_mock_ui_event_dispatcher_}) {
       ON_CALL(*mock, OnPreTool(_, _))
-          .WillByDefault(Invoke(UiEventDispatcherCallback<ToolRequest>(
-              base::BindRepeating(MakeOkResult))));
+          .WillByDefault(UiEventDispatcherCallback<ToolRequest>(
+              base::BindRepeating(MakeOkResult)));
       ON_CALL(*mock, OnPostTool(_, _))
-          .WillByDefault(Invoke(UiEventDispatcherCallback<ToolRequest>(
-              base::BindRepeating(MakeOkResult))));
+          .WillByDefault(UiEventDispatcherCallback<ToolRequest>(
+              base::BindRepeating(MakeOkResult)));
       ON_CALL(*mock, OnActorTaskAsyncChange(_, _))
-          .WillByDefault(Invoke(UiEventDispatcherCallback<
-                                ui::UiEventDispatcher::ActorTaskAsyncChange>(
-              base::BindRepeating(MakeOkResult))));
+          .WillByDefault(UiEventDispatcherCallback<
+                         ui::UiEventDispatcher::ActorTaskAsyncChange>(
+              base::BindRepeating(MakeOkResult)));
     }
   }
 
@@ -289,8 +288,8 @@
 
 TEST_F(ExecutionEngineTest, UiOnPreToolFails) {
   EXPECT_CALL(*mock_ui_event_dispatcher_, OnPreTool(_, _))
-      .WillOnce(Invoke(UiEventDispatcherCallback<ToolRequest>(
-          base::BindRepeating(MakeErrorResult))));
+      .WillOnce(UiEventDispatcherCallback<ToolRequest>(
+          base::BindRepeating(MakeErrorResult)));
   EXPECT_CALL(*mock_ui_event_dispatcher_, OnPostTool(_, _)).Times(0);
   EXPECT_FALSE(
       Act(GURL("http://localhost/"), MakeClickCallback(kFakeContentNodeId)));
@@ -301,8 +300,8 @@
 TEST_F(ExecutionEngineTest, UiOnPostToolFails) {
   EXPECT_CALL(*mock_ui_event_dispatcher_, OnPreTool(_, _)).Times(1);
   EXPECT_CALL(*mock_ui_event_dispatcher_, OnPostTool(_, _))
-      .WillOnce(Invoke(UiEventDispatcherCallback<ToolRequest>(
-          base::BindRepeating(MakeErrorResult))));
+      .WillOnce(UiEventDispatcherCallback<ToolRequest>(
+          base::BindRepeating(MakeErrorResult)));
   EXPECT_FALSE(
       Act(GURL("http://localhost/"), MakeClickCallback(kFakeContentNodeId)));
   histograms_.ExpectUniqueSample(kActionResultHistogram,
@@ -312,9 +311,9 @@
 TEST_F(ExecutionEngineTest, ActFailsWhenAddTabFails) {
   EXPECT_CALL(*task_mock_ui_event_dispatcher_,
               OnActorTaskAsyncChange(VariantWith<AddTab>(_), _))
-      .WillOnce(Invoke(UiEventDispatcherCallback<
-                       ui::UiEventDispatcher::ActorTaskAsyncChange>(
-          base::BindRepeating(MakeErrorResult))));
+      .WillOnce(UiEventDispatcherCallback<
+                ui::UiEventDispatcher::ActorTaskAsyncChange>(
+          base::BindRepeating(MakeErrorResult)));
   EXPECT_FALSE(
       Act(GURL("http://localhost/"), MakeClickCallback(kFakeContentNodeId)));
   histograms_.ExpectUniqueSample(kActionResultHistogram,
diff --git a/chrome/browser/actor/ui/actor_ui_state_manager_unittest.cc b/chrome/browser/actor/ui/actor_ui_state_manager_unittest.cc
index 3f88dd3..d817a91 100644
--- a/chrome/browser/actor/ui/actor_ui_state_manager_unittest.cc
+++ b/chrome/browser/actor/ui/actor_ui_state_manager_unittest.cc
@@ -34,7 +34,6 @@
 using ::tabs::TabFeatures;
 using ::tabs::TabInterface;
 using ::testing::_;
-using ::testing::Invoke;
 using ::testing::NiceMock;
 using ::testing::Return;
 using ::testing::ValuesIn;
@@ -98,11 +97,11 @@
 
   void ExpectUiTabStateChange(const UiTabState& expected_state) {
     ON_CALL(*mock_actor_ui_tab_controller(), OnUiTabStateChange(_, _))
-        .WillByDefault(Invoke(
+        .WillByDefault(
             [&](UiTabState state, base::OnceCallback<void(bool)> callback) {
               EXPECT_EQ(state, expected_state);
               std::move(callback).Run(true);
-            }));
+            });
   }
 
   ActorUiStateManager* actor_ui_state_manager() {
diff --git a/chrome/browser/actor/ui/actor_ui_tab_controller_unittest.cc b/chrome/browser/actor/ui/actor_ui_tab_controller_unittest.cc
index b8b62c8..0fe0c06 100644
--- a/chrome/browser/actor/ui/actor_ui_tab_controller_unittest.cc
+++ b/chrome/browser/actor/ui/actor_ui_tab_controller_unittest.cc
@@ -39,7 +39,6 @@
 namespace {
 using ::tabs::MockTabInterface;
 using ::testing::_;
-using ::testing::Invoke;
 using ::testing::MockFunction;
 using ::testing::Return;
 using ::testing::ReturnRef;
diff --git a/chrome/browser/android/compositor/layer/toolbar_layer.cc b/chrome/browser/android/compositor/layer/toolbar_layer.cc
index 945f6ee..1e26ad9 100644
--- a/chrome/browser/android/compositor/layer/toolbar_layer.cc
+++ b/chrome/browser/android/compositor/layer/toolbar_layer.cc
@@ -12,6 +12,7 @@
 #include "cc/slim/ui_resource_layer.h"
 #include "chrome/browser/android/compositor/resources/toolbar_resource.h"
 #include "chrome/browser/flags/android/chrome_feature_list.h"
+#include "chrome/browser/ui/ui_features.h"
 #include "components/viz/common/features.h"
 #include "components/viz/common/quads/offset_tag.h"
 #include "third_party/skia/include/core/SkColor.h"
@@ -164,9 +165,13 @@
                                      bool visible,
                                      const viz::OffsetTag& offset_tag) {
   bool is_progress_bar_visible = SkColorGetA(progress_bar_background_color);
-  if (features::IsAndroidAnimatedProgressBarInVizEnabled()) {
+  if (features::IsAndroidAnimatedProgressBarInVizEnabled() ||
+      features::IsAndroidAnimatedProgressBarInBrowserEnabled()) {
     is_progress_bar_visible = visible;
-    progress_bar_layers_->SetOffsetTag(offset_tag);
+
+    if (features::IsAndroidAnimatedProgressBarInVizEnabled()) {
+      progress_bar_layers_->SetOffsetTag(offset_tag);
+    }
   }
 
   progress_bar_background_layer_->SetHideLayerAndSubtree(!is_progress_bar_visible);
diff --git a/chrome/browser/bookmarks/android/BUILD.gn b/chrome/browser/bookmarks/android/BUILD.gn
index cbce98f..8bbce27 100644
--- a/chrome/browser/bookmarks/android/BUILD.gn
+++ b/chrome/browser/bookmarks/android/BUILD.gn
@@ -92,6 +92,7 @@
     "java/src/org/chromium/chrome/browser/bookmarks/bar/BookmarkBarButton.java",
     "java/src/org/chromium/chrome/browser/bookmarks/bar/BookmarkBarButtonProperties.java",
     "java/src/org/chromium/chrome/browser/bookmarks/bar/BookmarkBarButtonViewBinder.java",
+    "java/src/org/chromium/chrome/browser/bookmarks/bar/BookmarkBarConstants.java",
     "java/src/org/chromium/chrome/browser/bookmarks/bar/BookmarkBarItemsLayoutManager.java",
     "java/src/org/chromium/chrome/browser/bookmarks/bar/BookmarkBarItemsProvider.java",
     "java/src/org/chromium/chrome/browser/bookmarks/bar/BookmarkBarProperties.java",
diff --git a/chrome/browser/bookmarks/android/java/src/org/chromium/chrome/browser/bookmarks/bar/BookmarkBarConstants.java b/chrome/browser/bookmarks/android/java/src/org/chromium/chrome/browser/bookmarks/bar/BookmarkBarConstants.java
new file mode 100644
index 0000000..93f4305
--- /dev/null
+++ b/chrome/browser/bookmarks/android/java/src/org/chromium/chrome/browser/bookmarks/bar/BookmarkBarConstants.java
@@ -0,0 +1,19 @@
+// Copyright 2025 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.chrome.browser.bookmarks.bar;
+
+import org.chromium.build.annotations.NullMarked;
+
+/** Constants for the Bookmark Bar feature. */
+@NullMarked
+public class BookmarkBarConstants {
+
+    /**
+     * Bookmark Bar preference, tracks whether or not the user wants to show the bookmark bar on the
+     * current device.
+     */
+    public static final String BOOKMARK_BAR_SHOW_BOOKMARK_BAR =
+            "Chrome.BookmarkBar.ShowBookmarkBar";
+}
diff --git a/chrome/browser/bookmarks/android/java/src/org/chromium/chrome/browser/bookmarks/bar/BookmarkBarUtils.java b/chrome/browser/bookmarks/android/java/src/org/chromium/chrome/browser/bookmarks/bar/BookmarkBarUtils.java
index c5fdda4..a395849 100644
--- a/chrome/browser/bookmarks/android/java/src/org/chromium/chrome/browser/bookmarks/bar/BookmarkBarUtils.java
+++ b/chrome/browser/bookmarks/android/java/src/org/chromium/chrome/browser/bookmarks/bar/BookmarkBarUtils.java
@@ -13,6 +13,8 @@
 import androidx.annotation.IntDef;
 import androidx.appcompat.content.res.AppCompatResources;
 
+import org.chromium.base.ContextUtils;
+import org.chromium.base.DeviceInfo;
 import org.chromium.base.ResettersForTesting;
 import org.chromium.base.supplier.LazyOneshotSupplier;
 import org.chromium.base.supplier.LazyOneshotSupplierImpl;
@@ -131,6 +133,7 @@
     /**
      * Returns true if the Bookmark Bar currently visible. The feature is visible when it is allowed
      * in the given context, and the show bookmark bar UserPref is enabled for the current user.
+     * When on tablets, we do not use the UserPref and instead use the device preference.
      *
      * @param context The context in which compatibility should be assessed.
      * @param profile The profile for which the user UserPref should be assessed.
@@ -145,9 +148,14 @@
             return false;
         }
 
-        return isUserPrefsShowBookmarksBarEnabled(profile);
+        // On Desktop, we sync with the UserPrefs, but on tablets we use a local device preference.
+        return DeviceInfo.isDesktop()
+                ? isUserPrefsShowBookmarksBarEnabled(profile)
+                : isDevicePrefShowBookmarksBarEnabled();
     }
 
+    // UserPrefs methods - used on Desktop.
+
     /**
      * Returns whether the bookmark bar should be shown based on the current user's UserPrefs. Note:
      * This is synced across devices for the user's profile.
@@ -183,6 +191,64 @@
                 Pref.SHOW_BOOKMARK_BAR, !prefService.getBoolean(Pref.SHOW_BOOKMARK_BAR));
     }
 
+    // Device preferences methods - used on tablets.
+
+    /**
+     * Returns whether or not the bookmark bar should be shown based on the local device
+     * preferences. This is only used on tablets, where bookmarks bar does not sync with the user's
+     * desktop preference, but is instead stored locally on device.
+     *
+     * <p>Note: When a user has not previously set the device preference, the default return value
+     * is currently controlled by a FeatureParam for testing.
+     *
+     * @return Whether or not the bookmarks bar should be shown based on device preference.
+     */
+    public static boolean isDevicePrefShowBookmarksBarEnabled() {
+        // If a user has set the show bookmarks bar setting explicitly, then we will use that value.
+        // If the user has never set the preference, then we will return a default, which is
+        // currently controlled with a FeatureParam.
+        return hasUserSetDevicePrefShowBookmarksBar()
+                ? ContextUtils.getAppSharedPreferences()
+                        .getBoolean(BookmarkBarConstants.BOOKMARK_BAR_SHOW_BOOKMARK_BAR, false)
+                : ChromeFeatureList.sAndroidBookmarkBarShowBookmarkBar.getValue();
+    }
+
+    /**
+     * Set whether the bookmark bar should be shown at a device preferences level. This is only used
+     * on tablets, where bookmarks bar does not sync with the user's desktop preference, but is
+     * instead stored locally on the device.
+     *
+     * @param enabled The new device preference for enabling the bookmark bar.
+     */
+    public static void setDevicePrefShowBookmarksBar(boolean enabled) {
+        ContextUtils.getAppSharedPreferences()
+                .edit()
+                .putBoolean(BookmarkBarConstants.BOOKMARK_BAR_SHOW_BOOKMARK_BAR, enabled)
+                .apply();
+    }
+
+    /**
+     * Returns true when the user has previously set the visibility of the bookmarks bar explicitly
+     * at the device preference level. This is only used on tablets, where bookmarks bar does not
+     * sync with the user's desktop preference, but is instead stored locally on the device.
+     *
+     * @return Whether the user has set show bookmarks bar device preference manually.
+     */
+    public static boolean hasUserSetDevicePrefShowBookmarksBar() {
+        return ContextUtils.getAppSharedPreferences()
+                .contains(BookmarkBarConstants.BOOKMARK_BAR_SHOW_BOOKMARK_BAR);
+    }
+
+    /**
+     * Toggles the value of the show bookmarks bar device preference, this is stored locally and
+     * only used on tablets.
+     */
+    public static void toggleDevicePrefShowBookmarksBar() {
+        setDevicePrefShowBookmarksBar(!isDevicePrefShowBookmarksBarEnabled());
+    }
+
+    // Helper methods.
+
     /**
      * Creates a list item to render in the bookmark bar for the specified bookmark item.
      *
@@ -254,6 +320,8 @@
         return UserPrefs.get(profile.getOriginalProfile());
     }
 
+    // ForTesting methods.
+
     /**
      * Sets whether the bookmark bar feature is forcibly allowed/disallowed for testing.
      *
diff --git a/chrome/browser/bookmarks/android/java/src/org/chromium/chrome/browser/bookmarks/bar/BookmarkBarVisibilityProvider.java b/chrome/browser/bookmarks/android/java/src/org/chromium/chrome/browser/bookmarks/bar/BookmarkBarVisibilityProvider.java
index 54e0305..99e7d53 100644
--- a/chrome/browser/bookmarks/android/java/src/org/chromium/chrome/browser/bookmarks/bar/BookmarkBarVisibilityProvider.java
+++ b/chrome/browser/bookmarks/android/java/src/org/chromium/chrome/browser/bookmarks/bar/BookmarkBarVisibilityProvider.java
@@ -5,11 +5,14 @@
 package org.chromium.chrome.browser.bookmarks.bar;
 
 import android.app.Activity;
+import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
 import android.content.res.Configuration;
 
 import androidx.annotation.NonNull;
 
 import org.chromium.base.Callback;
+import org.chromium.base.ContextUtils;
+import org.chromium.base.DeviceInfo;
 import org.chromium.base.ObserverList;
 import org.chromium.base.supplier.ObservableSupplier;
 import org.chromium.build.annotations.NullMarked;
@@ -62,7 +65,7 @@
     private final ObserverList<BookmarkBarVisibilityObserver> mObservers;
 
     private @Nullable PrefChangeRegistrar mPrefChangeRegistrar;
-    private final @Nullable PrefObserver mPrefObserver;
+    private @Nullable OnSharedPreferenceChangeListener mDevicePrefsListener;
 
     /**
      * Constructor.
@@ -87,13 +90,19 @@
         mProfileSupplierObserver = this::processProfileChange;
         mProfileSupplier.addObserver(mProfileSupplierObserver);
 
-        mPrefObserver =
-                new PrefObserver() {
-                    @Override
-                    public void onPreferenceChange() {
-                        processPrefChange();
-                    }
-                };
+        // On tablets we use local device prefs.
+        if (!DeviceInfo.isDesktop()) {
+            mDevicePrefsListener =
+                    (sharedPreferences, key) -> {
+                        if (key != null
+                                && key.equals(
+                                        BookmarkBarConstants.BOOKMARK_BAR_SHOW_BOOKMARK_BAR)) {
+                            processPrefChange();
+                        }
+                    };
+            ContextUtils.getAppSharedPreferences()
+                    .registerOnSharedPreferenceChangeListener(mDevicePrefsListener);
+        }
     }
 
     /**
@@ -120,6 +129,7 @@
         mActivityLifecycleDispatcher.unregister(mConfigurationChangedListener);
         mProfileSupplier.removeObserver(mProfileSupplierObserver);
         destroyPrefChangeRegistrar();
+        destroySharedPrefListener();
         mObservers.clear();
     }
 
@@ -170,7 +180,20 @@
         }
     }
 
+    private void destroySharedPrefListener() {
+        if (mDevicePrefsListener != null) {
+            ContextUtils.getAppSharedPreferences()
+                    .unregisterOnSharedPreferenceChangeListener(mDevicePrefsListener);
+            mDevicePrefsListener = null;
+        }
+    }
+
     @Nullable PrefObserver getPrefObserverForTesting() {
-        return mPrefObserver;
+        return new PrefObserver() {
+            @Override
+            public void onPreferenceChange() {
+                processPrefChange();
+            }
+        };
     }
 }
diff --git a/chrome/browser/bookmarks/android/javatests/src/org/chromium/chrome/browser/bookmarks/bar/BookmarkBarTest.java b/chrome/browser/bookmarks/android/javatests/src/org/chromium/chrome/browser/bookmarks/bar/BookmarkBarTest.java
index f004a7b..d26cb814 100644
--- a/chrome/browser/bookmarks/android/javatests/src/org/chromium/chrome/browser/bookmarks/bar/BookmarkBarTest.java
+++ b/chrome/browser/bookmarks/android/javatests/src/org/chromium/chrome/browser/bookmarks/bar/BookmarkBarTest.java
@@ -48,10 +48,12 @@
 import org.hamcrest.Matcher;
 import org.junit.After;
 import org.junit.Before;
+import org.junit.BeforeClass;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import org.chromium.base.FeatureOverrides;
 import org.chromium.base.ThreadUtils;
 import org.chromium.base.test.util.Batch;
 import org.chromium.base.test.util.CommandLineFlags;
@@ -65,6 +67,7 @@
 import org.chromium.chrome.browser.flags.ChromeSwitches;
 import org.chromium.chrome.browser.tab.Tab;
 import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
+import org.chromium.chrome.test.OverrideContextWrapperTestRule;
 import org.chromium.chrome.test.transit.AutoResetCtaTransitTestRule;
 import org.chromium.chrome.test.transit.ChromeTransitTestRules;
 import org.chromium.chrome.test.util.BookmarkTestUtil;
@@ -93,19 +96,32 @@
     public AutoResetCtaTransitTestRule mCtaTestRule =
             ChromeTransitTestRules.autoResetCtaActivityRule();
 
+    @Rule
+    public OverrideContextWrapperTestRule mOverrideContextRule =
+            new OverrideContextWrapperTestRule();
+
     private BookmarkModel mModel;
     private BookmarkId mDesktopFolderId;
     private @Nullable List<BookmarkId> mItemIds;
 
+    @BeforeClass
+    public static void classSetUp() {
+        // Explicitly override FeatureParam for consistency.
+        FeatureOverrides.Builder overrides = FeatureOverrides.newBuilder();
+        overrides =
+                overrides.param(ChromeFeatureList.ANDROID_BOOKMARK_BAR, "show_bookmark_bar", true);
+        overrides.apply();
+    }
+
     @Before
     public void setUp() {
-        mCtaTestRule.startOnBlankPage();
+        mOverrideContextRule.setIsDesktop(true);
 
+        mCtaTestRule.startOnBlankPage();
         BookmarkBarUtils.setActivityStateBookmarkBarCompatibleForTesting(true);
         ThreadUtils.runOnUiThreadBlocking(() -> setBookmarkBarSetting(/* enabled= */ true));
         waitForBookmarkBarVisibility(/* visible= */ true);
         BookmarkTestUtil.waitForBookmarkModelLoaded();
-
         ThreadUtils.runOnUiThreadBlocking(
                 () -> {
                     mModel = mCtaTestRule.getActivity().getBookmarkModelForTesting();
diff --git a/chrome/browser/bookmarks/android/junit/src/org/chromium/chrome/browser/bookmarks/bar/BookmarkBarUtilsTest.java b/chrome/browser/bookmarks/android/junit/src/org/chromium/chrome/browser/bookmarks/bar/BookmarkBarUtilsTest.java
index b234e6e9..8d78d1a 100644
--- a/chrome/browser/bookmarks/android/junit/src/org/chromium/chrome/browser/bookmarks/bar/BookmarkBarUtilsTest.java
+++ b/chrome/browser/bookmarks/android/junit/src/org/chromium/chrome/browser/bookmarks/bar/BookmarkBarUtilsTest.java
@@ -43,6 +43,7 @@
 import org.robolectric.shadows.ShadowDrawable;
 
 import org.chromium.base.Callback;
+import org.chromium.base.FeatureOverrides;
 import org.chromium.base.supplier.ObservableSupplierImpl;
 import org.chromium.base.test.BaseRobolectricTestRunner;
 import org.chromium.base.test.util.Features.DisableFeatures;
@@ -53,6 +54,7 @@
 import org.chromium.chrome.browser.preferences.Pref;
 import org.chromium.chrome.browser.profiles.Profile;
 import org.chromium.chrome.browser.profiles.ProfileProvider;
+import org.chromium.chrome.test.OverrideContextWrapperTestRule;
 import org.chromium.components.bookmarks.BookmarkItem;
 import org.chromium.components.prefs.PrefService;
 import org.chromium.components.user_prefs.UserPrefsJni;
@@ -67,6 +69,7 @@
 
 /** Unit tests for {@link BookmarkBarUtils}. */
 @RunWith(BaseRobolectricTestRunner.class)
+@EnableFeatures(ChromeFeatureList.ANDROID_BOOKMARK_BAR)
 public class BookmarkBarUtilsTest {
 
     private static final String PHONE_QUALIFIER =
@@ -78,6 +81,10 @@
     public final ActivityScenarioRule<TestActivity> mActivityScenarioRule =
             new ActivityScenarioRule<>(TestActivity.class);
 
+    @Rule
+    public OverrideContextWrapperTestRule mOverrideContextRule =
+            new OverrideContextWrapperTestRule();
+
     @Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule();
 
     @Mock private BiConsumer<BookmarkItem, Integer> mClickCallback;
@@ -107,11 +114,18 @@
         UserPrefsJni.setInstanceForTesting(mUserPrefsJni);
 
         mProfileProviderSupplier = new ObservableSupplierImpl<>(mProfileProvider);
+
+        // Explicitly override FeatureParam for consistency.
+        FeatureOverrides.Builder overrides = FeatureOverrides.newBuilder();
+        overrides =
+                overrides.param(ChromeFeatureList.ANDROID_BOOKMARK_BAR, "show_bookmark_bar", true);
+        overrides.apply();
     }
 
     @After
     public void tearDown() {
         UserPrefsJni.setInstanceForTesting(null);
+        mOverrideContextRule.setIsDesktop(false);
     }
 
     @Test
@@ -165,7 +179,6 @@
     @Test
     @SmallTest
     @Config(qualifiers = PHONE_QUALIFIER)
-    @EnableFeatures(ChromeFeatureList.ANDROID_BOOKMARK_BAR)
     public void testIsFeatureEnabledWhenFlagIsEnabledOnPhone() {
         mActivityScenarioRule
                 .getScenario()
@@ -191,7 +204,6 @@
     @Test
     @SmallTest
     @Config(qualifiers = TABLET_QUALIFIER)
-    @EnableFeatures(ChromeFeatureList.ANDROID_BOOKMARK_BAR)
     public void testIsFeatureEnabledWhenFlagIsEnabledOnTablet() {
         mActivityScenarioRule
                 .getScenario()
@@ -203,7 +215,8 @@
 
     @Test
     @SmallTest
-    public void testIsBookmarkBarVisible() {
+    public void testIsBookmarkBarVisible_Desktop() {
+        mOverrideContextRule.setIsDesktop(true);
         mActivityScenarioRule
                 .getScenario()
                 .onActivity(
@@ -230,7 +243,45 @@
 
     @Test
     @SmallTest
+    public void testIsBookmarkBarVisible_Tablet() {
+        mOverrideContextRule.setIsDesktop(false);
+        mActivityScenarioRule
+                .getScenario()
+                .onActivity(
+                        activity -> {
+                            // Case: feature disallowed.
+                            BookmarkBarUtils.setActivityStateBookmarkBarCompatibleForTesting(false);
+                            assertFalse(BookmarkBarUtils.isBookmarkBarVisible(activity, mProfile));
+
+                            // Case: feature allowed no device pref (FeatureParam = true).
+                            BookmarkBarUtils.setActivityStateBookmarkBarCompatibleForTesting(true);
+                            assertTrue(BookmarkBarUtils.isBookmarkBarVisible(activity, mProfile));
+
+                            // Apply new FeatureParam override.
+                            FeatureOverrides.Builder overrides = FeatureOverrides.newBuilder();
+                            overrides =
+                                    overrides.param(
+                                            ChromeFeatureList.ANDROID_BOOKMARK_BAR,
+                                            "show_bookmark_bar",
+                                            false);
+                            overrides.apply();
+
+                            // Case: feature allowed no device pref (FeatureParam = false).
+                            assertFalse(BookmarkBarUtils.isBookmarkBarVisible(activity, mProfile));
+
+                            // Case: feature allowed explicit device pref
+                            BookmarkBarUtils.setDevicePrefShowBookmarksBar(true);
+                            assertTrue(BookmarkBarUtils.isBookmarkBarVisible(activity, mProfile));
+                        });
+    }
+
+    // Test UserPrefs - only on Desktop
+
+    @Test
+    @SmallTest
     public void testIsUserPrefsShowBookmarksBarEnabled() {
+        mOverrideContextRule.setIsDesktop(true);
+
         mSetting.set(false);
         assertFalse(BookmarkBarUtils.isUserPrefsShowBookmarksBarEnabled(mProfile));
         assertFalse(BookmarkBarUtils.isUserPrefsShowBookmarksBarEnabled(null));
@@ -243,6 +294,8 @@
     @Test
     @SmallTest
     public void testSetUserPrefsShowBookmarksBar() {
+        mOverrideContextRule.setIsDesktop(true);
+
         mSetting.set(false);
         assertFalse(BookmarkBarUtils.isUserPrefsShowBookmarksBarEnabled(mProfile));
 
@@ -256,6 +309,8 @@
     @Test
     @SmallTest
     public void testToggleUserPrefsShowBookmarksBar() {
+        mOverrideContextRule.setIsDesktop(true);
+
         mSetting.set(false);
         assertFalse(BookmarkBarUtils.isUserPrefsShowBookmarksBarEnabled(mProfile));
 
@@ -266,6 +321,68 @@
         assertFalse(BookmarkBarUtils.isUserPrefsShowBookmarksBarEnabled(mProfile));
     }
 
+    // Test device prefs - only on Tablet
+
+    @Test
+    @SmallTest
+    public void testIsDevicePrefShowBookmarksBarEnabled() {
+        mOverrideContextRule.setIsDesktop(false);
+
+        // User should not have set any preference yet.
+        assertFalse(BookmarkBarUtils.hasUserSetDevicePrefShowBookmarksBar());
+
+        // Even though user has not set a device preference, the FeatureParam will make it true.
+        assertTrue(BookmarkBarUtils.isDevicePrefShowBookmarksBarEnabled());
+
+        // Apply new FeatureParam override.
+        FeatureOverrides.Builder overrides = FeatureOverrides.newBuilder();
+        overrides =
+                overrides.param(ChromeFeatureList.ANDROID_BOOKMARK_BAR, "show_bookmark_bar", false);
+        overrides.apply();
+
+        assertFalse(BookmarkBarUtils.isDevicePrefShowBookmarksBarEnabled());
+    }
+
+    @Test
+    @SmallTest
+    public void testSetDevicePrefShowBookmarksBar() {
+        mOverrideContextRule.setIsDesktop(false);
+
+        // User should not have set any preference yet.
+        assertFalse(BookmarkBarUtils.hasUserSetDevicePrefShowBookmarksBar());
+
+        // Even though user has not set a device preference, the FeatureParam will make it true.
+        assertTrue(BookmarkBarUtils.isDevicePrefShowBookmarksBarEnabled());
+
+        BookmarkBarUtils.setDevicePrefShowBookmarksBar(true);
+        assertTrue(BookmarkBarUtils.isDevicePrefShowBookmarksBarEnabled());
+        assertTrue(BookmarkBarUtils.hasUserSetDevicePrefShowBookmarksBar());
+
+        BookmarkBarUtils.setDevicePrefShowBookmarksBar(false);
+        assertFalse(BookmarkBarUtils.isDevicePrefShowBookmarksBarEnabled());
+        assertTrue(BookmarkBarUtils.hasUserSetDevicePrefShowBookmarksBar());
+    }
+
+    @Test
+    @SmallTest
+    public void testToggleDevicePrefShowBookmarksBar() {
+        mOverrideContextRule.setIsDesktop(false);
+
+        // User should not have set any preference yet.
+        assertFalse(BookmarkBarUtils.hasUserSetDevicePrefShowBookmarksBar());
+
+        // Even though user has not set a device preference, the FeatureParam will make it true.
+        assertTrue(BookmarkBarUtils.isDevicePrefShowBookmarksBarEnabled());
+
+        BookmarkBarUtils.toggleDevicePrefShowBookmarksBar();
+        assertFalse(BookmarkBarUtils.isDevicePrefShowBookmarksBarEnabled());
+        assertTrue(BookmarkBarUtils.hasUserSetDevicePrefShowBookmarksBar());
+
+        BookmarkBarUtils.toggleDevicePrefShowBookmarksBar();
+        assertTrue(BookmarkBarUtils.isDevicePrefShowBookmarksBarEnabled());
+        assertTrue(BookmarkBarUtils.hasUserSetDevicePrefShowBookmarksBar());
+    }
+
     @Test
     @SmallTest
     public void testCreateListItem() {
diff --git a/chrome/browser/bookmarks/android/junit/src/org/chromium/chrome/browser/bookmarks/bar/BookmarkBarVisibilityProviderTest.java b/chrome/browser/bookmarks/android/junit/src/org/chromium/chrome/browser/bookmarks/bar/BookmarkBarVisibilityProviderTest.java
index bd087afd..62eb2df 100644
--- a/chrome/browser/bookmarks/android/junit/src/org/chromium/chrome/browser/bookmarks/bar/BookmarkBarVisibilityProviderTest.java
+++ b/chrome/browser/bookmarks/android/junit/src/org/chromium/chrome/browser/bookmarks/bar/BookmarkBarVisibilityProviderTest.java
@@ -38,6 +38,7 @@
 import org.chromium.chrome.browser.lifecycle.ActivityLifecycleDispatcher;
 import org.chromium.chrome.browser.lifecycle.ConfigurationChangedObserver;
 import org.chromium.chrome.browser.profiles.Profile;
+import org.chromium.chrome.test.OverrideContextWrapperTestRule;
 import org.chromium.components.prefs.PrefChangeRegistrar;
 import org.chromium.components.prefs.PrefChangeRegistrar.PrefObserver;
 import org.chromium.components.prefs.PrefChangeRegistrarJni;
@@ -54,6 +55,10 @@
 
     @Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule();
 
+    @Rule
+    public OverrideContextWrapperTestRule mOverrideContextRule =
+            new OverrideContextWrapperTestRule();
+
     @Mock private Activity mActivity;
     @Mock private Resources mResources;
     @Mock private ActivityLifecycleDispatcher mActivityLifecycleDispatcher;
@@ -71,6 +76,7 @@
     @Before
     public void setUp() {
         mProfileSupplier.set(mProfile);
+        mOverrideContextRule.setIsDesktop(true);
 
         // Set up mocks.
         when(mProfile.getOriginalProfile()).thenReturn(mProfile);
diff --git a/chrome/browser/browsing_data/chrome_browsing_data_lifetime_manager_browsertest.cc b/chrome/browser/browsing_data/chrome_browsing_data_lifetime_manager_browsertest.cc
index b3273d5..fc46dd5 100644
--- a/chrome/browser/browsing_data/chrome_browsing_data_lifetime_manager_browsertest.cc
+++ b/chrome/browser/browsing_data/chrome_browsing_data_lifetime_manager_browsertest.cc
@@ -530,7 +530,7 @@
   autofill::AutofillProfile profile(
       "01234567-89ab-cdef-fedc-ba9876543210",
       autofill::AutofillProfile::RecordType::kLocalOrSyncable,
-      AddressCountryCode("US"));
+      autofill::AddressCountryCode("US"));
   autofill::test::SetProfileInfo(
       &profile, "Marion", "Mitchell", "Morrison", "johnwayne@me.xyz", "Fox",
       "123 Zoo St.", "unit 5", "Hollywood", "CA", "91601", "US", "12345678910");
diff --git a/chrome/browser/download/BUILD.gn b/chrome/browser/download/BUILD.gn
index d129f5b6e..f7b590a7 100644
--- a/chrome/browser/download/BUILD.gn
+++ b/chrome/browser/download/BUILD.gn
@@ -38,7 +38,19 @@
   ]
 
   if (!is_android) {
-    public += [ "download_commands.h" ]
+    public += [
+      "download_commands.h",
+      "download_item_web_app_data.h",
+    ]
+
+    sources += [ "download_item_web_app_data.cc" ]
+
+    public_deps += [ "//components/webapps/common" ]
+
+    deps += [
+      "//chrome/browser/policy:path_parser",
+      "//chrome/common:chrome_features",
+    ]
   }
 
   if (safe_browsing_mode != 0) {
diff --git a/chrome/browser/extensions/api/autofill_private/autofill_private_api.cc b/chrome/browser/extensions/api/autofill_private/autofill_private_api.cc
index 761487a..cae67ea9 100644
--- a/chrome/browser/extensions/api/autofill_private/autofill_private_api.cc
+++ b/chrome/browser/extensions/api/autofill_private/autofill_private_api.cc
@@ -182,9 +182,9 @@
       adm.IsEligibleForAddressAccountStorage()
           ? autofill::AutofillProfile::RecordType::kAccount
           : autofill::AutofillProfile::RecordType::kLocalOrSyncable;
-  AddressCountryCode address_country_code =
+  autofill::AddressCountryCode address_country_code =
       country_code.has_value()
-          ? AddressCountryCode(std::string(*country_code))
+          ? autofill::AddressCountryCode(std::string(*country_code))
           : autofill::i18n_model_definition::kLegacyHierarchyCountryCode;
   return autofill::AutofillProfile(record_type, address_country_code);
 }
diff --git a/chrome/browser/extensions/api/autofill_private/autofill_util.cc b/chrome/browser/extensions/api/autofill_private/autofill_util.cc
index 76265713..ee0e765 100644
--- a/chrome/browser/extensions/api/autofill_private/autofill_util.cc
+++ b/chrome/browser/extensions/api/autofill_private/autofill_util.cc
@@ -290,9 +290,9 @@
   const variations::VariationsService* variations_service =
       g_browser_process->variations_service();
   model.SetCountries(
-      GeoIpCountryCode(variations_service
-                           ? variations_service->GetLatestCountry()
-                           : std::string()),
+      autofill::GeoIpCountryCode(variations_service
+                                     ? variations_service->GetLatestCountry()
+                                     : std::string()),
       extensions::ExtensionsBrowserClient::Get()->GetApplicationLocale());
   const std::vector<std::unique_ptr<autofill::AutofillCountry>>& countries =
       model.countries();
diff --git a/chrome/browser/extensions/api/experimental_actor/experimental_actor_api.cc b/chrome/browser/extensions/api/experimental_actor/experimental_actor_api.cc
index 23ab2f4..e31ddd68 100644
--- a/chrome/browser/extensions/api/experimental_actor/experimental_actor_api.cc
+++ b/chrome/browser/extensions/api/experimental_actor/experimental_actor_api.cc
@@ -25,8 +25,6 @@
 #include "chrome/browser/extensions/chrome_extension_function_details.h"
 #include "chrome/browser/extensions/extension_tab_util.h"
 #include "chrome/browser/profiles/profile.h"
-#include "chrome/browser/ui/browser.h"
-#include "chrome/browser/ui/browser_finder.h"
 #include "chrome/common/actor/action_result.h"
 #include "chrome/common/extensions/api/experimental_actor.h"
 #include "chrome/common/extensions/api/tabs.h"
@@ -132,94 +130,6 @@
   return true;
 }
 
-ExperimentalActorStartTaskFunction::ExperimentalActorStartTaskFunction() =
-    default;
-
-ExperimentalActorStartTaskFunction::~ExperimentalActorStartTaskFunction() =
-    default;
-
-ExtensionFunction::ResponseAction ExperimentalActorStartTaskFunction::Run() {
-  auto params = api::experimental_actor::StartTask::Params::Create(args());
-  EXTENSION_FUNCTION_VALIDATE(params);
-  optimization_guide::proto::BrowserStartTask task;
-  if (!task.ParseFromArray(params->start_task_proto.data(),
-                           params->start_task_proto.size())) {
-    return RespondNow(
-        Error("Parsing optimization_guide::proto::BrowserStartTask failed."));
-  }
-
-  // Convert from extension tab ids to TabHandles.
-  int32_t tab_handle =
-      ConvertSessionTabIdToTabHandle(task.tab_id(), browser_context());
-
-  auto* actor_service = actor::ActorKeyedService::Get(browser_context());
-
-  actor::TaskId task_id = actor_service->CreateTask();
-
-  // If a tab_id wasn't specified, create a new one.
-  // TODO(crbug.com/411462297): The client of this API should create a new tab
-  // themselves using the CreateTabAction and this code can be removed.
-  if (!tab_handle) {
-    // Get the most recently active browser for this profile.
-    Browser* browser = chrome::FindTabbedBrowser(
-        Profile::FromBrowserContext(browser_context()),
-        /*match_original_profiles=*/false);
-    // If no browser exists create one.
-    if (!browser) {
-      browser = Browser::Create(
-          Browser::CreateParams(Profile::FromBrowserContext(browser_context()),
-                                /*user_gesture=*/false));
-    }
-
-    std::unique_ptr<actor::ToolRequest> create_tab =
-        std::make_unique<actor::CreateTabToolRequest>(
-            browser->session_id().id(),
-            WindowOpenDisposition::NEW_FOREGROUND_TAB);
-    std::vector<std::unique_ptr<actor::ToolRequest>> actions;
-    actions.push_back(std::move(create_tab));
-    actor_service->PerformActions(
-        task_id, std::move(actions),
-        base::BindOnce(&ExperimentalActorStartTaskFunction::OnTabCreated, this,
-                       browser->AsWeakPtr(), task_id));
-  } else {
-    base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
-        FROM_HERE,
-        base::BindOnce(&ExperimentalActorStartTaskFunction::OnTaskStarted, this,
-                       task_id, tab_handle));
-  }
-
-  return RespondLater();
-}
-
-void ExperimentalActorStartTaskFunction::OnTaskStarted(actor::TaskId task_id,
-                                                       int32_t tab_id) {
-  optimization_guide::proto::BrowserStartTaskResult result;
-  result.set_task_id(task_id.value());
-  result.set_tab_id(tab_id);
-  result.set_status(optimization_guide::proto::BrowserStartTaskResult::SUCCESS);
-
-  std::vector<uint8_t> data_buffer(result.ByteSizeLong());
-  result.SerializeToArray(&data_buffer[0], result.ByteSizeLong());
-  Respond(ArgumentList(api::experimental_actor::StartTask::Results::Create(
-      std::move(data_buffer))));
-}
-
-void ExperimentalActorStartTaskFunction::OnTabCreated(
-    base::WeakPtr<Browser> browser,
-    actor::TaskId task_id,
-    actor::mojom::ActionResultCode result_code,
-    std::optional<size_t> index_of_failed_action,
-    std::vector<actor::ActionResultWithLatencyInfo> action_results) {
-  int32_t tab_id = 0;
-  // CreateTask assumes it always succeeds but we won't have a tab if the
-  // browser is closed during creation.
-  if (browser) {
-    tab_id =
-        browser->tab_strip_model()->GetActiveTab()->GetHandle().raw_value();
-  }
-  OnTaskStarted(actor::TaskId(task_id), tab_id);
-}
-
 ExperimentalActorStopTaskFunction::ExperimentalActorStopTaskFunction() =
     default;
 
@@ -237,80 +147,6 @@
       ArgumentList(api::experimental_actor::StopTask::Results::Create()));
 }
 
-ExperimentalActorExecuteActionFunction::
-    ExperimentalActorExecuteActionFunction() = default;
-
-ExperimentalActorExecuteActionFunction::
-    ~ExperimentalActorExecuteActionFunction() = default;
-
-ExtensionFunction::ResponseAction
-ExperimentalActorExecuteActionFunction::Run() {
-#if !BUILDFLAG(ENABLE_GLIC)
-  return RespondNow(
-      Error("Execute action not supported for this build configuration."));
-#else
-  auto params = api::experimental_actor::ExecuteAction::Params::Create(args());
-  EXTENSION_FUNCTION_VALIDATE(params);
-  optimization_guide::proto::BrowserAction action;
-  if (!action.ParseFromArray(params->browser_action_proto.data(),
-                             params->browser_action_proto.size())) {
-    return RespondNow(
-        Error("Parsing optimization_guide::proto::BrowserAction failed."));
-  }
-
-  int32_t tab_handle =
-      ConvertSessionTabIdToTabHandle(action.tab_id(), browser_context());
-  action.set_tab_id(tab_handle);
-
-  auto* actor_service =
-      actor::ActorKeyedServiceFactory::GetActorKeyedService(browser_context());
-
-  actor_service->GetJournal().Log(
-      GURL(), actor::TaskId(action.task_id()),
-      actor::mojom::JournalTrack::kActor, "ExperimentalActorExecutAction",
-      absl::StrFormat("Proto: %s", actor::ToBase64(action)));
-
-  // BuildToolRequest looks for tab_ids on the individual action structs since
-  // that's where Glic puts them. However, the extension puts the tab_id on the
-  // BrowserAction itself. Use the BrowserAction's tab_id as the fallback tab so
-  // that, if Action doesn't provide a tab_id we'll use the
-  // BrowserAction.tab_id. This path should go away once extension clients are
-  // migrated to PerformActions.
-  tabs::TabInterface* browser_action_tab =
-      action.has_tab_id() ? tabs::TabHandle(action.tab_id()).Get() : nullptr;
-
-  actor::BuildToolRequestResult requests =
-      actor::BuildToolRequest(action, browser_action_tab);
-
-  if (!requests.has_value()) {
-    return RespondNow(
-        Error("Failed to convert BrowserAction to ToolRequests."));
-  }
-
-  actor_service->ExecuteAction(
-      actor::TaskId(action.task_id()), std::move(requests.value()),
-      base::BindOnce(
-          &ExperimentalActorExecuteActionFunction::OnResponseReceived, this));
-
-  return RespondLater();
-#endif
-}
-
-void ExperimentalActorExecuteActionFunction::OnResponseReceived(
-    optimization_guide::proto::BrowserActionResult response) {
-  // Convert from tab handle to session tab id.
-  int32_t session_tab_id =
-      ConvertTabHandleToSessionTabId(response.tab_id(), browser_context());
-  response.set_tab_id(session_tab_id);
-
-  std::vector<uint8_t> data_buffer(response.ByteSizeLong());
-  if (!data_buffer.empty()) {
-    response.SerializeToArray(&data_buffer[0], response.ByteSizeLong());
-  }
-  Respond(ArgumentList(api::experimental_actor::ExecuteAction::Results::Create(
-      std::move(data_buffer))));
-}
-
 ExperimentalActorCreateTaskFunction::ExperimentalActorCreateTaskFunction() =
     default;
 ExperimentalActorCreateTaskFunction::~ExperimentalActorCreateTaskFunction() =
diff --git a/chrome/browser/extensions/api/experimental_actor/experimental_actor_api.h b/chrome/browser/extensions/api/experimental_actor/experimental_actor_api.h
index 16f3096..aac32cdd 100644
--- a/chrome/browser/extensions/api/experimental_actor/experimental_actor_api.h
+++ b/chrome/browser/extensions/api/experimental_actor/experimental_actor_api.h
@@ -13,8 +13,6 @@
 #include "components/optimization_guide/proto/features/actions_data.pb.h"
 #include "extensions/browser/extension_function.h"
 
-class Browser;
-
 namespace extensions {
 
 // Base class for all experimental actor functions. See idl for more details.
@@ -31,31 +29,6 @@
   bool PreRunValidation(std::string* error) override;
 };
 
-// Starts an actor task.
-class ExperimentalActorStartTaskFunction : public ExperimentalActorApiFunction {
- public:
-  ExperimentalActorStartTaskFunction();
-
-  ExperimentalActorStartTaskFunction(
-      const ExperimentalActorStartTaskFunction&) = delete;
-  ExperimentalActorStartTaskFunction& operator=(
-      const ExperimentalActorStartTaskFunction&) = delete;
-
- protected:
-  ~ExperimentalActorStartTaskFunction() override;
-  ResponseAction Run() override;
-  void OnTaskStarted(actor::TaskId task_id, int32_t tab_id);
-  void OnTabCreated(
-      base::WeakPtr<Browser> browser,
-      actor::TaskId task_id,
-      actor::mojom::ActionResultCode result_code,
-      std::optional<size_t> index_of_failed_action,
-      std::vector<actor::ActionResultWithLatencyInfo> action_results);
-
-  DECLARE_EXTENSION_FUNCTION("experimentalActor.startTask",
-                             EXPERIMENTALACTOR_STARTTASK)
-};
-
 // Stops an actor task.
 class ExperimentalActorStopTaskFunction : public ExperimentalActorApiFunction {
  public:
@@ -74,27 +47,6 @@
                              EXPERIMENTALACTOR_STOPTASK)
 };
 
-// Executes an actor action.
-class ExperimentalActorExecuteActionFunction
-    : public ExperimentalActorApiFunction {
- public:
-  ExperimentalActorExecuteActionFunction();
-
-  ExperimentalActorExecuteActionFunction(
-      const ExperimentalActorExecuteActionFunction&) = delete;
-  ExperimentalActorExecuteActionFunction& operator=(
-      const ExperimentalActorExecuteActionFunction&) = delete;
-
- protected:
-  ~ExperimentalActorExecuteActionFunction() override;
-  ResponseAction Run() override;
-  void OnResponseReceived(
-      optimization_guide::proto::BrowserActionResult response);
-
-  DECLARE_EXTENSION_FUNCTION("experimentalActor.executeAction",
-                             EXPERIMENTALACTOR_EXECUTEACTION)
-};
-
 class ExperimentalActorCreateTaskFunction
     : public ExperimentalActorApiFunction {
  public:
diff --git a/chrome/browser/fast_checkout/fast_checkout_trigger_validator_impl.cc b/chrome/browser/fast_checkout/fast_checkout_trigger_validator_impl.cc
index bddac15..0fa4d91 100644
--- a/chrome/browser/fast_checkout/fast_checkout_trigger_validator_impl.cc
+++ b/chrome/browser/fast_checkout/fast_checkout_trigger_validator_impl.cc
@@ -54,7 +54,7 @@
   }
 
   if (autofill_client_->GetVariationConfigCountryCode() !=
-      GeoIpCountryCode("US")) {
+      autofill::GeoIpCountryCode("US")) {
     return FastCheckoutTriggerOutcome::kUnsupportedCountry;
   }
 
diff --git a/chrome/browser/fast_checkout/fast_checkout_trigger_validator_impl_unittest.cc b/chrome/browser/fast_checkout/fast_checkout_trigger_validator_impl_unittest.cc
index dbadd74..05225c38 100644
--- a/chrome/browser/fast_checkout/fast_checkout_trigger_validator_impl_unittest.cc
+++ b/chrome/browser/fast_checkout/fast_checkout_trigger_validator_impl_unittest.cc
@@ -37,7 +37,7 @@
   using autofill::TestContentAutofillClient::TestContentAutofillClient;
   MOCK_METHOD(autofill::LogManager*, GetCurrentLogManager, (), (override));
   MOCK_METHOD(bool, IsContextSecure, (), (const override));
-  MOCK_METHOD(GeoIpCountryCode,
+  MOCK_METHOD(autofill::GeoIpCountryCode,
               GetVariationConfigCountryCode,
               (),
               (const override));
@@ -102,7 +102,7 @@
         .WillByDefault(Return(&pdm()));
     ON_CALL(*autofill_client(), IsContextSecure).WillByDefault(Return(true));
     ON_CALL(*autofill_client(), GetVariationConfigCountryCode)
-        .WillByDefault(Return(GeoIpCountryCode("US")));
+        .WillByDefault(Return(autofill::GeoIpCountryCode("US")));
 
     pdm().test_address_data_manager().SetAutofillProfileEnabled(true);
     pdm().test_payments_data_manager().SetAutofillPaymentMethodsEnabled(true);
diff --git a/chrome/browser/first_run/first_run.cc b/chrome/browser/first_run/first_run.cc
index 71c9500..b4112ce 100644
--- a/chrome/browser/first_run/first_run.cc
+++ b/chrome/browser/first_run/first_run.cc
@@ -431,9 +431,7 @@
     return;
   }
 
-  // Safe to remove const as ScopedProfileKeepAlive just wraps a non-const
-  // Profile* as const in its constructor.
-  Profile* profile = const_cast<Profile*>(scoped_profile->profile());
+  Profile* profile = scoped_profile->profile();
 
   bookmark_model->BeginExtensiveChanges();
   absl::Cleanup end_changes = [bookmark_model] {
diff --git a/chrome/browser/flag-metadata.json b/chrome/browser/flag-metadata.json
index 52c6c57..a289e401 100644
--- a/chrome/browser/flag-metadata.json
+++ b/chrome/browser/flag-metadata.json
@@ -3340,11 +3340,6 @@
     "expiry_milestone": 150
   },
   {
-    "name": "enable-desktop-pwas-sync-changes",
-    "owners": [ "mek@google.com", "pwa-team@google.com" ],
-    "expiry_milestone": 135
-  },
-  {
     "name": "enable-desktop-pwas-tab-strip",
     "owners": [ "pwa-team@google.com" ],
     "expiry_milestone": 150
@@ -5972,7 +5967,7 @@
   {
     "name": "instance-switcher-v2",
     "owners": [ "aishwaryarj@google.com", "rathomas@google.com", "clank-large-form-factors@google.com" ],
-    "expiry_milestone": 142
+    "expiry_milestone": 144
   },
   {
     "name": "instant-hotspot-on-nearby",
@@ -6059,7 +6054,7 @@
   {
     "name": "ios-expanded-tips",
     "owners": ["scottyoder@google.com", "bwwilliams@google.com", "bling-pandamonium@google.com"],
-    "expiry_milestone": 142
+    "expiry_milestone": 145
   },
   {
     "name": "ios-fill-recovery-password",
@@ -6151,7 +6146,7 @@
   {
     "name": "ios-provides-app-notification-settings",
     "owners": ["scottyoder@google.com", "bling-pandamonium@google.com"],
-    "expiry_milestone": 141
+    "expiry_milestone": 148
   },
   {
     "name": "ios-provisional-notification-alert",
@@ -6622,7 +6617,7 @@
       "hchao@chromium.org",
       "chrome-secure-web-and-net@chromium.org"
     ],
-    "expiry_milestone": 142
+    "expiry_milestone": 146
   },
   {
     "name": "local-network-access-check-webrtc",
@@ -8248,7 +8243,7 @@
       "kmg@google.com",
       "koilos@google.com"
     ],
-    "expiry_milestone": 141
+    "expiry_milestone": 150
   },
   {
     "name": "process-rank-policy-android",
@@ -9936,7 +9931,7 @@
   {
     "name": "tracking-protection-3pcd",
     "owners": ["fmacintosh@google.com", "koilos@google.com"],
-    "expiry_milestone": 142
+    "expiry_milestone": 146
   },
   {
     "name": "traffic-counters",
@@ -10231,7 +10226,7 @@
   {
     "name": "use-winrt-midi-api",
     "owners": [ "//third_party/blink/renderer/modules/webaudio/OWNERS", "toyoshim@chromium.org", "mjwilson@chromium.org" ],
-    "expiry_milestone": 142
+    "expiry_milestone": 154
   },
   {
     "name": "use-xps-for-printing",
diff --git a/chrome/browser/flags/android/chrome_feature_list.cc b/chrome/browser/flags/android/chrome_feature_list.cc
index e66a728..f2c0ed0 100644
--- a/chrome/browser/flags/android/chrome_feature_list.cc
+++ b/chrome/browser/flags/android/chrome_feature_list.cc
@@ -139,6 +139,7 @@
     &download::features::kSmartSuggestionForLargeDownloads,
     &base::features::kCollectAndroidFrameTimelineMetrics,
     &download::features::kDownloadNotificationServiceUnifiedAPI,
+    &features::kAndroidAnimatedProgressBarInBrowser,
     &features::kAndroidAnimatedProgressBarInViz,
     &features::kAndroidBcivBottomControls,
     &features::kAndroidBrowserControlsInViz,
@@ -196,7 +197,6 @@
     &kAdaptiveButtonInTopToolbarCustomizationV2,
     &kAdaptiveButtonInTopToolbarPageSummary,
     &kAllowTabClosingUponMinimization,
-    &kAndroidAnimatedProgressBarInBrowser,
     &kAndroidAppIntegration,
     &kAndroidAppIntegrationV2,
     &kNewTabPageCustomization,
@@ -541,10 +541,6 @@
              "AllowTabClosingUponMinimization",
              base::FEATURE_ENABLED_BY_DEFAULT);
 
-BASE_FEATURE(kAndroidAnimatedProgressBarInBrowser,
-             "AndroidAnimatedProgressBarInBrowser",
-             base::FEATURE_DISABLED_BY_DEFAULT);
-
 BASE_FEATURE(kAndroidAppIntegration,
              "AndroidAppIntegration",
              base::FEATURE_ENABLED_BY_DEFAULT);
diff --git a/chrome/browser/flags/android/chrome_feature_list.h b/chrome/browser/flags/android/chrome_feature_list.h
index 4c00f8ce..8e55912 100644
--- a/chrome/browser/flags/android/chrome_feature_list.h
+++ b/chrome/browser/flags/android/chrome_feature_list.h
@@ -24,7 +24,6 @@
 BASE_DECLARE_FEATURE(kNewTabPageCustomizationV2);
 BASE_DECLARE_FEATURE(kNewTabPageCustomizationToolbarButton);
 BASE_DECLARE_FEATURE(kNewTabPageCustomizationForMvt);
-BASE_DECLARE_FEATURE(kAndroidAnimatedProgressBarInBrowser);
 BASE_DECLARE_FEATURE(kAndroidAnimatedProgressBarInViz);
 BASE_DECLARE_FEATURE(kAndroidAppIntegrationWithFavicon);
 BASE_DECLARE_FEATURE(kAndroidAppIntegrationMultiDataSource);
diff --git a/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java b/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java
index 4d29c91..22a21d1 100644
--- a/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java
+++ b/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java
@@ -1455,6 +1455,9 @@
                             "multi_data_source_skip_device_check",
                             false);
 
+    public static final BooleanCachedFeatureParam sAndroidBookmarkBarShowBookmarkBar =
+            newBooleanCachedFeatureParam(ANDROID_BOOKMARK_BAR, "show_bookmark_bar", false);
+
     public static final BooleanCachedFeatureParam sAndroidComposeplateSkipLocaleCheck =
             newBooleanCachedFeatureParam(ANDROID_COMPOSEPLATE, "skip_locale_check", false);
 
@@ -1795,6 +1798,7 @@
                     sAndroidAppIntegrationWithFaviconSkipSchemaCheck,
                     sAndroidAppIntegrationWithFaviconUseLargeFavicon,
                     sAndroidAppIntegrationWithFaviconZeroStateFaviconNumber,
+                    sAndroidBookmarkBarShowBookmarkBar,
                     sAndroidBottomToolbarDefaultToTop,
                     sAndroidComposeplateHideIncognitoButton,
                     sAndroidComposeplateSkipLocaleCheck,
diff --git a/chrome/browser/glic/BUILD.gn b/chrome/browser/glic/BUILD.gn
index 6d07956..034e72e 100644
--- a/chrome/browser/glic/BUILD.gn
+++ b/chrome/browser/glic/BUILD.gn
@@ -52,7 +52,6 @@
     "public/glic_enabling.h",
     "public/glic_keyed_service.h",
     "public/glic_keyed_service_factory.h",
-    "widget/glic_side_panel_coordinator.h",
     "widget/glic_view.h",
     "widget/glic_window_controller.h",
     "widget/local_hotkey_manager.h",
@@ -162,7 +161,6 @@
     "widget/glic_floating_ui.h",
     "widget/glic_panel_hotkey_delegate.cc",
     "widget/glic_panel_hotkey_delegate.h",
-    "widget/glic_side_panel_coordinator.cc",
     "widget/glic_side_panel_ui.cc",
     "widget/glic_side_panel_ui.h",
     "widget/glic_view.cc",
diff --git a/chrome/browser/glic/widget/glic_side_panel_coordinator.h b/chrome/browser/glic/widget/glic_side_panel_coordinator.h
deleted file mode 100644
index 5538b9da..0000000
--- a/chrome/browser/glic/widget/glic_side_panel_coordinator.h
+++ /dev/null
@@ -1,36 +0,0 @@
-// Copyright 2025 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef CHROME_BROWSER_GLIC_WIDGET_GLIC_SIDE_PANEL_COORDINATOR_H_
-#define CHROME_BROWSER_GLIC_WIDGET_GLIC_SIDE_PANEL_COORDINATOR_H_
-
-#include <memory>
-
-class SidePanelEntryScope;
-class SidePanelRegistry;
-
-namespace views {
-class View;
-}  // namespace views
-
-namespace glic {
-
-// GlicSidePanelCoordinator handles the creation and registration of the
-// glic SidePanelEntry.
-class GlicSidePanelCoordinator {
- public:
-  GlicSidePanelCoordinator();
-  ~GlicSidePanelCoordinator() = default;
-
-  // Create and register the Glic side panel entry.
-  void CreateAndRegisterEntry(SidePanelRegistry* global_registry);
-
- private:
-  // Gets the Glic WebView from the Glic service.
-  std::unique_ptr<views::View> CreateGlicWebView(SidePanelEntryScope& scope);
-};
-
-}  // namespace glic
-
-#endif  // CHROME_BROWSER_GLIC_WIDGET_GLIC_SIDE_PANEL_COORDINATOR_H_
diff --git a/chrome/browser/history/BUILD.gn b/chrome/browser/history/BUILD.gn
index 2bf9957..0fd18cab 100644
--- a/chrome/browser/history/BUILD.gn
+++ b/chrome/browser/history/BUILD.gn
@@ -2,77 +2,78 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
-import("//build/config/android/config.gni")
-import("//build/config/android/rules.gni")
-import("//chrome/browser/buildflags.gni")
-import("//third_party/jni_zero/jni_zero.gni")
+if (is_android) {
+  import("//build/config/android/config.gni")
+  import("//build/config/android/rules.gni")
+  import("//chrome/browser/buildflags.gni")
+  import("//third_party/jni_zero/jni_zero.gni")
 
-android_library("java") {
-  srcjar_deps = [ ":jni_headers" ]
-  sources = [
-    "java/src/org/chromium/chrome/browser/history/AppFilterCoordinator.java",
-    "java/src/org/chromium/chrome/browser/history/AppFilterMediator.java",
-    "java/src/org/chromium/chrome/browser/history/AppFilterProperties.java",
-    "java/src/org/chromium/chrome/browser/history/AppFilterSheetContent.java",
-    "java/src/org/chromium/chrome/browser/history/AppFilterViewBinder.java",
-    "java/src/org/chromium/chrome/browser/history/HistoryTabHelper.java",
-  ]
+  android_library("java") {
+    srcjar_deps = [ ":jni_headers" ]
+    sources = [
+      "java/src/org/chromium/chrome/browser/history/AppFilterCoordinator.java",
+      "java/src/org/chromium/chrome/browser/history/AppFilterMediator.java",
+      "java/src/org/chromium/chrome/browser/history/AppFilterProperties.java",
+      "java/src/org/chromium/chrome/browser/history/AppFilterSheetContent.java",
+      "java/src/org/chromium/chrome/browser/history/AppFilterViewBinder.java",
+      "java/src/org/chromium/chrome/browser/history/HistoryTabHelper.java",
+    ]
 
-  deps = [
-    ":java_resources",
-    "//base:base_java",
-    "//chrome/browser/tab:java",
-    "//components/browser_ui/bottomsheet/android:java",
-    "//content/public/android:content_java",
-    "//third_party/androidx:androidx_annotation_annotation_java",
-    "//third_party/androidx:androidx_annotation_annotation_java",
-    "//third_party/androidx:androidx_recyclerview_recyclerview_java",
-    "//third_party/jni_zero:jni_zero_java",
-    "//ui/android:ui_full_java",
-  ]
-  resources_package = "org.chromium.chrome.browser.history"
-}
+    deps = [
+      ":java_resources",
+      "//base:base_java",
+      "//chrome/browser/tab:java",
+      "//components/browser_ui/bottomsheet/android:java",
+      "//content/public/android:content_java",
+      "//third_party/androidx:androidx_annotation_annotation_java",
+      "//third_party/androidx:androidx_recyclerview_recyclerview_java",
+      "//third_party/jni_zero:jni_zero_java",
+      "//ui/android:ui_full_java",
+    ]
+    resources_package = "org.chromium.chrome.browser.history"
+  }
 
-generate_jni("jni_headers") {
-  sources =
-      [ "java/src/org/chromium/chrome/browser/history/HistoryTabHelper.java" ]
-}
+  generate_jni("jni_headers") {
+    sources =
+        [ "java/src/org/chromium/chrome/browser/history/HistoryTabHelper.java" ]
+  }
 
-android_resources("java_resources") {
-  sources = [
-    "java/res/layout/appfilter_content.xml",
-    "java/res/layout/appfilter_header.xml",
-  ]
-  deps = [
-    "//chrome/browser/ui/android/strings:ui_strings_grd",
-    "//components/browser_ui/widget/android:java_resources",
-  ]
-}
+  android_resources("java_resources") {
+    sources = [
+      "java/res/layout/appfilter_content.xml",
+      "java/res/layout/appfilter_header.xml",
+    ]
+    deps = [
+      "//chrome/browser/ui/android/strings:ui_strings_grd",
+      "//components/browser_ui/widget/android:java_resources",
+    ]
+  }
 
-android_library("unit_device_javatests") {
-  testonly = true
-  resources_package = "org.chromium.chrome.browser.history"
+  android_library("unit_device_javatests") {
+    testonly = true
+    resources_package = "org.chromium.chrome.browser.history"
 
-  sources = [ "java/src/org/chromium/chrome/browser/history/AppFilterCoordinatorTest.java" ]
+    sources = [ "java/src/org/chromium/chrome/browser/history/AppFilterCoordinatorTest.java" ]
 
-  deps = [
-    ":java",
-    ":java_resources",
-    "//base:base_java",
-    "//base:base_java_test_support",
-    "//chrome/browser/flags:java",
-    "//chrome/test/android:chrome_java_unit_test_support",
-    "//components/browser_ui/bottomsheet/android:factory_java",
-    "//components/browser_ui/bottomsheet/android:java",
-    "//components/browser_ui/bottomsheet/android:manager_java",
-    "//components/browser_ui/desktop_windowing/android:java",
-    "//components/browser_ui/widget/android:java",
-    "//content/public/test/android:content_java_test_support",
-    "//third_party/androidx:androidx_test_monitor_java",
-    "//third_party/androidx:androidx_test_rules_java",
-    "//third_party/androidx:androidx_test_runner_java",
-    "//third_party/junit",
-    "//ui/android:ui_java",
-    "//ui/android:ui_java_test_support",
-  ]
+    deps = [
+      ":java",
+      ":java_resources",
+      "//base:base_java",
+      "//base:base_java_test_support",
+      "//chrome/browser/flags:java",
+      "//chrome/test/android:chrome_java_unit_test_support",
+      "//components/browser_ui/bottomsheet/android:factory_java",
+      "//components/browser_ui/bottomsheet/android:java",
+      "//components/browser_ui/bottomsheet/android:manager_java",
+      "//components/browser_ui/desktop_windowing/android:java",
+      "//components/browser_ui/widget/android:java",
+      "//content/public/test/android:content_java_test_support",
+      "//third_party/androidx:androidx_test_monitor_java",
+      "//third_party/androidx:androidx_test_rules_java",
+      "//third_party/androidx:androidx_test_runner_java",
+      "//third_party/junit",
+      "//ui/android:ui_java",
+      "//ui/android:ui_java_test_support",
+    ]
+  }
 }
diff --git a/chrome/browser/long_screenshots/long_screenshots_tab_service_unittest.cc b/chrome/browser/long_screenshots/long_screenshots_tab_service_unittest.cc
index 78f911a..923049fb 100644
--- a/chrome/browser/long_screenshots/long_screenshots_tab_service_unittest.cc
+++ b/chrome/browser/long_screenshots/long_screenshots_tab_service_unittest.cc
@@ -117,7 +117,7 @@
   const int kTabId = 1U;
 
   LaxMockPaintPreviewRecorder recorder;
-  recorder.SetResponse(paint_preview::mojom::PaintPreviewStatus::kOk);
+  recorder.SetResponse();
   OverrideInterface(&recorder);
 
   auto* service = GetService();
@@ -155,8 +155,7 @@
   response->geometry_metadata =
       paint_preview::mojom::GeometryMetadataResponse::New();
   response->skp.emplace(mojo_base::BigBuffer());
-  recorder.SetResponse(paint_preview::mojom::PaintPreviewStatus::kOk,
-                       std::move(response));
+  recorder.SetResponse(std::move(response));
   OverrideInterface(&recorder);
 
   auto* service = GetService();
@@ -182,7 +181,7 @@
   const int kTabId = 1U;
 
   LaxMockPaintPreviewRecorder recorder;
-  recorder.SetResponse(paint_preview::mojom::PaintPreviewStatus::kOk);
+  recorder.SetResponse();
   OverrideInterface(&recorder);
 
   auto* service = GetService();
@@ -214,7 +213,7 @@
   auto files_1 = ListDir(path_1);
   ASSERT_EQ(1U, files_1.size());
 
-  recorder.SetResponse(paint_preview::mojom::PaintPreviewStatus::kOk);
+  recorder.SetResponse();
   service->CaptureTab(kTabId, GURL::EmptyGURL(), web_contents(), 1000, 1000,
                       2000, 2000, false,
                       paint_preview::mojom::ClipCoordOverride::kNone,
@@ -259,7 +258,8 @@
   const int kTabId = 1U;
 
   LaxMockPaintPreviewRecorder recorder;
-  recorder.SetResponse(paint_preview::mojom::PaintPreviewStatus::kFailed);
+  recorder.SetResponse(
+      base::unexpected(paint_preview::mojom::PaintPreviewStatus::kFailed));
   OverrideInterface(&recorder);
 
   auto* service = GetService();
diff --git a/chrome/browser/new_tab_page/modules/file_suggestion/microsoft_files_page_handler.cc b/chrome/browser/new_tab_page/modules/file_suggestion/microsoft_files_page_handler.cc
index bb5dc7d..b9f889e 100644
--- a/chrome/browser/new_tab_page/modules/file_suggestion/microsoft_files_page_handler.cc
+++ b/chrome/browser/new_tab_page/modules/file_suggestion/microsoft_files_page_handler.cc
@@ -11,6 +11,7 @@
 #include "chrome/browser/new_tab_page/microsoft_auth/microsoft_auth_service_factory.h"
 #include "chrome/browser/new_tab_page/modules/file_suggestion/file_suggestion.mojom.h"
 #include "chrome/browser/new_tab_page/modules/microsoft_modules_helper.h"
+#include "chrome/browser/new_tab_page/new_tab_page_util.h"
 #include "chrome/grit/generated_resources.h"
 #include "net/http/http_response_headers.h"
 #include "net/http/http_status_code.h"
@@ -313,6 +314,10 @@
   return TimeFormatAsIso8601(base::Time::Now());
 }
 
+std::string GetBoolAsString(bool x) {
+  return x ? "true" : "false";
+}
+
 // The number of responses that should be found in the response body of JSON
 // batch requests.
 constexpr size_t kNonInsightsRequiredResponseSize = 2;
@@ -540,6 +545,8 @@
 MicrosoftFilesPageHandler::GetTrendingFiles(base::Value::Dict result) {
   auto* suggestions = result.FindList("value");
   if (!suggestions) {
+    LogModuleError(ntp_features::kNtpSharepointModule,
+                   "Content Error: Trending Files missing 'value' field");
     request_result_ = MicrosoftFilesRequestResult::kContentError;
     return std::vector<file_suggestion::mojom::FilePtr>();
   }
@@ -565,6 +572,15 @@
         "resourceVisualization.mediaType");
 
     if (!id || !title || !url || !mime_type) {
+      LogModuleError(
+          ntp_features::kNtpSharepointModule,
+          base::StringPrintf(
+              "Content Error: Trending File missing field ('id': %s, "
+              "'resourceVisualization.title': %s, 'resourceReference.webUrl': "
+              "%s, "
+              "'resourceVisualization.mediaType': %s)",
+              GetBoolAsString(!id), GetBoolAsString(!title),
+              GetBoolAsString(!url), GetBoolAsString(!mime_type)));
       request_result_ = MicrosoftFilesRequestResult::kContentError;
       return std::vector<file_suggestion::mojom::FilePtr>();
     }
@@ -666,6 +682,21 @@
                                             &sort_time);
     if (!id || !title || !item_url || !last_modified_time_str ||
         !suggestion_has_formatted_time) {
+      LogModuleError(
+          ntp_features::kNtpSharepointModule,
+          base::StringPrintf(
+              "Content Error: Recent/Shared File ('response_id': %s) missing "
+              "field "
+              "('id': %s, 'name': %s, 'webUrl': %s, 'lastModifiedDateTime': "
+              "%s, "
+              "'fileSystemInfo.lastAccessedDateTime': %s, "
+              "'remoteItem.shared.sharedBy.user.displayName': %s, "
+              "'remoteItem.shared.sharedDateTime': %s)",
+              response_id, GetBoolAsString(!id), GetBoolAsString(!title),
+              GetBoolAsString(!item_url),
+              GetBoolAsString(!last_modified_time_str),
+              GetBoolAsString(!last_opened_time_str),
+              GetBoolAsString(!shared_by), GetBoolAsString(!shared_time_str)));
       request_result_ = MicrosoftFilesRequestResult::kContentError;
       return std::vector<
           std::pair<base::Time, file_suggestion::mojom::FilePtr>>();
@@ -729,6 +760,9 @@
     base::Value::Dict result) {
   auto* responses = result.FindList("responses");
   if (!responses) {
+    LogModuleError(
+        ntp_features::kNtpSharepointModule,
+        "Content Error: Recent/Shared response body missing 'responses' field");
     request_result_ = MicrosoftFilesRequestResult::kContentError;
     return std::vector<file_suggestion::mojom::FilePtr>();
   }
@@ -736,6 +770,9 @@
   // The response body should contain a list that has 2 dictionaries - one for
   // each request, with their own lists containing file data.
   if (responses->size() != kNonInsightsRequiredResponseSize) {
+    LogModuleError(ntp_features::kNtpSharepointModule,
+                   "Content Error: Recent/Shared response body has incorrect "
+                   "'responses' length");
     request_result_ = MicrosoftFilesRequestResult::kContentError;
     return std::vector<file_suggestion::mojom::FilePtr>();
   }
@@ -748,6 +785,9 @@
     const std::string* response_id = response_dict.FindString("id");
     auto* suggestions = response_dict.FindListByDottedPath("body.value");
     if (!response_id || !suggestions) {
+      LogModuleError(ntp_features::kNtpSharepointModule,
+                     "Content Error: Recent/Shared response missing 'id' or "
+                     "'body.value' field");
       request_result_ = MicrosoftFilesRequestResult::kContentError;
       return std::vector<file_suggestion::mojom::FilePtr>();
     }
@@ -847,6 +887,9 @@
     base::Value::Dict result) {
   auto* responses = result.FindList("responses");
   if (!responses) {
+    LogModuleError(
+        ntp_features::kNtpSharepointModule,
+        "Content Error: Aggrefated response body missing 'responses' field");
     request_result_ = MicrosoftFilesRequestResult::kContentError;
     return std::vector<file_suggestion::mojom::FilePtr>();
   }
@@ -854,6 +897,9 @@
   // The response body should contain a list that has a dictionary for each
   // request, with their own lists containing file data.
   if (responses->size() != kCombinedSuggestionsRequiredResponseSize) {
+    LogModuleError(ntp_features::kNtpSharepointModule,
+                   "Content Error: Aggregated response body 'responses' field "
+                   "incorrect length");
     request_result_ = MicrosoftFilesRequestResult::kContentError;
     return std::vector<file_suggestion::mojom::FilePtr>();
   }
@@ -867,6 +913,8 @@
     auto response_body = response_dict.FindDict("body")->Clone();
     auto* body_value = response_dict.FindListByDottedPath("body.value");
     if (!response_id) {
+      LogModuleError(ntp_features::kNtpSharepointModule,
+                     "Content Error: Aggregated response missing 'id' field");
       request_result_ = MicrosoftFilesRequestResult::kContentError;
       return std::vector<file_suggestion::mojom::FilePtr>();
     } else if (*response_id == "trending") {
diff --git a/chrome/browser/paint_preview/services/paint_preview_tab_service_unittest.cc b/chrome/browser/paint_preview/services/paint_preview_tab_service_unittest.cc
index 5d6578c..dc13150 100644
--- a/chrome/browser/paint_preview/services/paint_preview_tab_service_unittest.cc
+++ b/chrome/browser/paint_preview/services/paint_preview_tab_service_unittest.cc
@@ -126,7 +126,7 @@
   const int kTabId = 1U;
 
   LaxMockPaintPreviewRecorder recorder;
-  recorder.SetResponse(mojom::PaintPreviewStatus::kOk);
+  recorder.SetResponse();
   OverrideInterface(&recorder);
 
   auto* service = GetService();
@@ -160,7 +160,7 @@
   const int kTabId = 1U;
 
   LaxMockPaintPreviewRecorder recorder;
-  recorder.SetResponse(mojom::PaintPreviewStatus::kFailed);
+  recorder.SetResponse(base::unexpected(mojom::PaintPreviewStatus::kFailed));
   OverrideInterface(&recorder);
 
   auto* service = GetService();
@@ -194,7 +194,7 @@
   const int kTabId = 1U;
 
   LaxMockPaintPreviewRecorder recorder;
-  recorder.SetResponse(mojom::PaintPreviewStatus::kOk);
+  recorder.SetResponse();
   OverrideInterface(&recorder);
 
   auto* service = GetService();
@@ -225,6 +225,7 @@
   auto files_1 = ListDir(path_1);
   ASSERT_EQ(1U, files_1.size());
 
+  recorder.SetResponse();
   service->CaptureTab(kTabId, web_contents(), false, 1.0, 10, 20,
                       base::BindOnce([](PaintPreviewTabService::Status status) {
                         EXPECT_EQ(status, PaintPreviewTabService::Status::kOk);
@@ -380,7 +381,7 @@
   const int kTabId = 1U;
 
   LaxMockPaintPreviewRecorder recorder;
-  recorder.SetResponse(mojom::PaintPreviewStatus::kOk);
+  recorder.SetResponse();
   OverrideInterface(&recorder);
 
   auto service = BuildServiceWithCache({});
@@ -414,7 +415,7 @@
   const int kTabId = 1U;
 
   LaxMockPaintPreviewRecorder recorder;
-  recorder.SetResponse(mojom::PaintPreviewStatus::kOk);
+  recorder.SetResponse();
   OverrideInterface(&recorder);
 
   auto service = BuildServiceWithCache({kTabId + 1});
diff --git a/chrome/browser/password_manager/android/BUILD.gn b/chrome/browser/password_manager/android/BUILD.gn
index 26a76938..67872a7 100644
--- a/chrome/browser/password_manager/android/BUILD.gn
+++ b/chrome/browser/password_manager/android/BUILD.gn
@@ -20,8 +20,6 @@
 
 source_set("backend_public") {
   sources = [
-    "login_db_deprecation_runner_factory.cc",
-    "login_db_deprecation_runner_factory.h",
     "password_manager_settings_service_android_impl.cc",
     "password_manager_settings_service_android_impl.h",
   ]
@@ -548,7 +546,6 @@
     "fake_password_manager_lifecycle_helper.h",
     "first_cct_page_load_marker_unittest.cc",
     "generated_password_saved_message_delegate_unittest.cc",
-    "login_db_deprecation_runner_factory_unittest.cc",
     "mock_password_checkup_launcher_helper.cc",
     "mock_password_checkup_launcher_helper.h",
     "mock_password_manager_error_message_helper_bridge.cc",
diff --git a/chrome/browser/password_manager/android/credential_leak_controller_android.cc b/chrome/browser/password_manager/android/credential_leak_controller_android.cc
index 3f2d65b..75ac004 100644
--- a/chrome/browser/password_manager/android/credential_leak_controller_android.cc
+++ b/chrome/browser/password_manager/android/credential_leak_controller_android.cc
@@ -23,23 +23,6 @@
 using password_manager::metrics_util::LeakDialogMetricsRecorder;
 using password_manager::metrics_util::LeakDialogType;
 
-namespace {
-CredentialLeakType AdjustLeakTypeForLoginDbDeprecation(
-    CredentialLeakType leak_type,
-    Profile* profile) {
-  if (!password_manager_android_util::LoginDbDeprecationReady(
-          profile->GetPrefs())) {
-    // While the login DB deprecation is being prepared (passwords are
-    // exported), saved passwords from the DB are still visible to the leak
-    // service (until the export finishes). Resetting the leak type to 0,
-    // ensures we don't show a dialog with options for saved passwords
-    // (i.e. Password Checkup), because those are not available at this time.
-    return 0;
-  }
-  return leak_type;
-}
-
-}  // namespace
 CredentialLeakControllerAndroid::CredentialLeakControllerAndroid(
     password_manager::CredentialLeakType leak_type,
     const GURL& origin,
@@ -49,7 +32,7 @@
     std::unique_ptr<PasswordCheckupLauncherHelper> checkup_launcher,
     std::unique_ptr<LeakDialogMetricsRecorder> metrics_recorder,
     std::string account_email)
-    : leak_type_(AdjustLeakTypeForLoginDbDeprecation(leak_type, profile)),
+    : leak_type_(leak_type),
       origin_(origin),
       username_(username),
       profile_(profile),
diff --git a/chrome/browser/password_manager/android/credential_leak_controller_android_unittest.cc b/chrome/browser/password_manager/android/credential_leak_controller_android_unittest.cc
index 0d634d3..f3c48e68 100644
--- a/chrome/browser/password_manager/android/credential_leak_controller_android_unittest.cc
+++ b/chrome/browser/password_manager/android/credential_leak_controller_android_unittest.cc
@@ -18,7 +18,6 @@
 #include "components/password_manager/core/browser/features/password_features.h"
 #include "components/password_manager/core/browser/leak_detection_dialog_utils.h"
 #include "components/password_manager/core/browser/password_manager_metrics_util.h"
-#include "components/password_manager/core/browser/split_stores_and_local_upm.h"
 #include "components/password_manager/core/common/password_manager_pref_names.h"
 #include "components/prefs/pref_service.h"
 #include "components/ukm/test_ukm_recorder.h"
@@ -227,14 +226,12 @@
                               LeakDialogDismissalReason::kNoDirectInteraction);
 }
 
-// The following tests are specific to the login DB deprecation.
-TEST_F(CredentialLeakControllerAndroidTest,
-       LeakTypeResetToChangeIfLoginDbDeprecationNotReady) {
-  // The export state is only valid for users who are not enrolled in UPM.
-  password_manager::SetLegacySplitStoresPrefForTest(profile()->GetPrefs(),
-                                                    false);
-  profile()->GetPrefs()->SetBoolean(
-      password_manager::prefs::kUpmUnmigratedPasswordsExported, false);
+TEST_F(CredentialLeakControllerAndroidTest, LeakType) {
+  if (base::android::device_info::is_automotive()) {
+    // Automotive only uses the base leak type and doesn't display the
+    // "Check passwords" button.
+    GTEST_SKIP() << "This test should not run on automotive.";
+  }
 
   std::unique_ptr<MockPasswordCheckupLauncherHelper> mock_launcher =
       std::make_unique<MockPasswordCheckupLauncherHelper>();
@@ -254,75 +251,6 @@
       LaunchCheckupOnDevice(
           _, profile(), _,
           password_manager::PasswordCheckReferrerAndroid::kLeakDialog, _))
-      .Times(0);
-  controller->OnAcceptDialog();
-}
-
-TEST_F(CredentialLeakControllerAndroidTest,
-       LeakTypeNotResetIfPasswordsExported) {
-  if (base::android::device_info::is_automotive()) {
-    // Automotive only uses the base leak type and doesn't display the
-    // "Check passwords" button.
-    GTEST_SKIP() << "This test should not run on automotive.";
-  }
-
-  // The export state is only valid for users who are not enrolled in UPM.
-  password_manager::SetLegacySplitStoresPrefForTest(profile()->GetPrefs(),
-                                                    false);
-  profile()->GetPrefs()->SetBoolean(
-      password_manager::prefs::kUpmUnmigratedPasswordsExported, true);
-
-  std::unique_ptr<MockPasswordCheckupLauncherHelper> mock_launcher =
-      std::make_unique<MockPasswordCheckupLauncherHelper>();
-  MockPasswordCheckupLauncherHelper* weak_mock_launcher = mock_launcher.get();
-
-  // Setting `isReused` to true will normally cause the leak type to be
-  // `kCheckup`.
-  CredentialLeakControllerAndroid* controller =
-      MakeController(profile(), std::move(mock_launcher), IsSaved(false),
-                     IsReused(true), IsSyncing(false),
-                     /* account_email_ = */ "");
-
-  // Expect that despite the original leak being of type `kCheckup` the positive
-  // button is not the "Check" one.
-  EXPECT_CALL(
-      *weak_mock_launcher,
-      LaunchCheckupOnDevice(
-          _, profile(), _,
-          password_manager::PasswordCheckReferrerAndroid::kLeakDialog, _))
-      .Times(1);
-  controller->OnAcceptDialog();
-}
-
-TEST_F(CredentialLeakControllerAndroidTest,
-       LeakTypeNotResetIfUPMAlreadyActive) {
-  if (base::android::device_info::is_automotive()) {
-    // Automotive only uses the base leak type and doesn't display the
-    // "Check passwords" button.
-    GTEST_SKIP() << "This test should not run on automotive.";
-  }
-
-  password_manager::SetLegacySplitStoresPrefForTest(profile()->GetPrefs(),
-                                                    true);
-  profile()->GetPrefs()->SetBoolean(
-      password_manager::prefs::kUpmUnmigratedPasswordsExported, false);
-
-  std::unique_ptr<MockPasswordCheckupLauncherHelper> mock_launcher =
-      std::make_unique<MockPasswordCheckupLauncherHelper>();
-  MockPasswordCheckupLauncherHelper* weak_mock_launcher = mock_launcher.get();
-
-  // Setting `isReused` to true will cause the leak type to be `kCheckup`.
-  CredentialLeakControllerAndroid* controller =
-      MakeController(profile(), std::move(mock_launcher), IsSaved(false),
-                     IsReused(true), IsSyncing(false),
-                     /* account_email_ = */ "");
-
-  // Check that the checkup option is presented to the user if UPM is active.
-  EXPECT_CALL(
-      *weak_mock_launcher,
-      LaunchCheckupOnDevice(
-          _, profile(), _,
-          password_manager::PasswordCheckReferrerAndroid::kLeakDialog, _))
       .Times(1);
   controller->OnAcceptDialog();
 }
diff --git a/chrome/browser/password_manager/android/java/src/org/chromium/chrome/browser/password_manager/PasswordManagerHelper.java b/chrome/browser/password_manager/android/java/src/org/chromium/chrome/browser/password_manager/PasswordManagerHelper.java
index 4be6b10..15a9e9e 100644
--- a/chrome/browser/password_manager/android/java/src/org/chromium/chrome/browser/password_manager/PasswordManagerHelper.java
+++ b/chrome/browser/password_manager/android/java/src/org/chromium/chrome/browser/password_manager/PasswordManagerHelper.java
@@ -34,16 +34,13 @@
 import org.chromium.chrome.browser.password_manager.CredentialManagerLauncher.CredentialManagerBackendException;
 import org.chromium.chrome.browser.password_manager.CredentialManagerLauncher.CredentialManagerError;
 import org.chromium.chrome.browser.password_manager.PasswordCheckupClientHelper.PasswordManagerUnavailableException;
-import org.chromium.chrome.browser.preferences.Pref;
 import org.chromium.chrome.browser.profiles.Profile;
 import org.chromium.chrome.browser.profiles.ProfileKeyedMap;
 import org.chromium.chrome.browser.pwm_disabled.PasswordCsvDownloadFlowControllerFactory;
 import org.chromium.chrome.browser.pwm_disabled.PasswordManagerUnavailableDialogCoordinator;
 import org.chromium.chrome.browser.sync.SyncServiceFactory;
 import org.chromium.components.browser_ui.settings.SettingsCustomTabLauncher;
-import org.chromium.components.prefs.PrefService;
 import org.chromium.components.sync.SyncService;
-import org.chromium.components.user_prefs.UserPrefs;
 import org.chromium.ui.modaldialog.ModalDialogManager;
 
 import java.lang.annotation.Retention;
@@ -136,15 +133,6 @@
                 ManagePasswordsReferrer.MAX_VALUE);
         SyncService syncService = SyncServiceFactory.getForProfile(mProfile);
 
-        PrefService prefService = UserPrefs.get(mProfile);
-
-        if (!PasswordManagerUtilBridge.isPasswordManagerAvailable(prefService)
-                && !prefService.getBoolean(Pref.UPM_UNMIGRATED_PASSWORDS_EXPORTED)) {
-            // The automatic export is ongoing. Usually a dialog offering the user to download
-            // the auto-exported CSV would be shown, but until the CSV is written, the dialog
-            // can't be shown. This is a rare corner-case.
-            return;
-        }
         if (!showPwmUnavailableOrDownloadCsvDialog(
                 context, modalDialogManagerSupplier, settingsCustomTabLauncher)) {
             LoadingModalDialogCoordinator loadingDialogCoordinator =
@@ -152,7 +140,6 @@
                             () -> assertNonNull(modalDialogManagerSupplier.get()), context);
             launchTheCredentialManager(
                     referrer,
-                    prefService,
                     syncService,
                     loadingDialogCoordinator,
                     modalDialogManagerSupplier,
@@ -178,8 +165,7 @@
                         activity,
                         mProfile,
                         PasswordManagerUtilBridge.isGooglePlayServicesUpdatable(),
-                        PasswordManagerUtilBridge.isPasswordManagerAvailable(
-                                UserPrefs.get(mProfile)),
+                        PasswordManagerUtilBridge.isPasswordManagerAvailable(),
                         settingsCustomTabLauncher);
     }
 
@@ -194,7 +180,7 @@
             return true;
         }
 
-        if (!PasswordManagerUtilBridge.isPasswordManagerAvailable(UserPrefs.get(mProfile))) {
+        if (!PasswordManagerUtilBridge.isPasswordManagerAvailable()) {
             new PasswordManagerUnavailableDialogCoordinator()
                     .showDialog(
                             context,
@@ -426,14 +412,13 @@
     @VisibleForTesting
     void launchTheCredentialManager(
             @ManagePasswordsReferrer int referrer,
-            PrefService prefService,
             @Nullable SyncService syncService,
             LoadingModalDialogCoordinator loadingDialogCoordinator,
             Supplier<@Nullable ModalDialogManager> modalDialogManagerSupplier,
             Context context,
             @Nullable String account) {
         assert syncService != null;
-        assert PasswordManagerUtilBridge.isPasswordManagerAvailable(prefService);
+        assert PasswordManagerUtilBridge.isPasswordManagerAvailable();
         CredentialManagerLauncher credentialManagerLauncher =
                 CredentialManagerLauncherFactory.getInstance().createLauncher();
         assert credentialManagerLauncher != null;
@@ -477,16 +462,6 @@
         try {
             checkupClient = getPasswordCheckupClientHelper();
         } catch (PasswordManagerUnavailableException exception) {
-            // This is slightly different than the access to the management UI where if there
-            // is an auto-exported CSV, a dialog shown even if the password manager is available
-            // If a checkup is possible, the results of the checkup are more important than
-            // the CSV. In addition, the CSV issue is being prominently presented on the
-            // main settings view.
-            if (!UserPrefs.get(mProfile).getBoolean(Pref.UPM_UNMIGRATED_PASSWORDS_EXPORTED)) {
-                // The auto-exported file is not ready, so there it's not possible to show a
-                // dialog to download the CSV.
-                return;
-            }
             assert settingsCustomTabLauncher != null;
             showPwmUnavailableOrDownloadCsvDialog(
                     context, modalDialogManagerSupplier, settingsCustomTabLauncher);
@@ -673,7 +648,7 @@
         // Callers shouldn't need to distinguish between the errors anymore, since there will be no
         // more partial support, so PASSWORD_MANGER_NOT_AVAILABLE will replace and include all the
         // errors.
-        if (!PasswordManagerUtilBridge.isPasswordManagerAvailable(UserPrefs.get(mProfile))) {
+        if (!PasswordManagerUtilBridge.isPasswordManagerAvailable()) {
             throw new PasswordManagerUnavailableException();
         }
 
diff --git a/chrome/browser/password_manager/android/java/src/org/chromium/chrome/browser/password_manager/PasswordManagerUtilBridge.java b/chrome/browser/password_manager/android/java/src/org/chromium/chrome/browser/password_manager/PasswordManagerUtilBridge.java
index c15c06e..7d902ff0 100644
--- a/chrome/browser/password_manager/android/java/src/org/chromium/chrome/browser/password_manager/PasswordManagerUtilBridge.java
+++ b/chrome/browser/password_manager/android/java/src/org/chromium/chrome/browser/password_manager/PasswordManagerUtilBridge.java
@@ -5,12 +5,10 @@
 package org.chromium.chrome.browser.password_manager;
 
 import org.jni_zero.CalledByNative;
-import org.jni_zero.JniType;
 import org.jni_zero.NativeMethods;
 
 import org.chromium.base.PackageUtils;
 import org.chromium.build.annotations.NullMarked;
-import org.chromium.components.prefs.PrefService;
 
 /** Wrapper for utilities in password_manager_util. */
 @NullMarked
@@ -23,9 +21,9 @@
      *
      * @return whether password manager functionality is available.
      */
-    public static boolean isPasswordManagerAvailable(PrefService prefService) {
+    public static boolean isPasswordManagerAvailable() {
         return PasswordManagerUtilBridgeJni.get()
-                .isPasswordManagerAvailable(prefService, isInternalBackendPresent());
+                .isPasswordManagerAvailable(isInternalBackendPresent());
     }
 
     @CalledByNative
@@ -47,7 +45,6 @@
 
     @NativeMethods
     public interface Natives {
-        boolean isPasswordManagerAvailable(
-                @JniType("PrefService*") PrefService prefService, boolean isInternalBackendPresent);
+        boolean isPasswordManagerAvailable(boolean isInternalBackendPresent);
     }
 }
diff --git a/chrome/browser/password_manager/android/java/src/org/chromium/chrome/browser/password_manager/settings/PasswordsPreference.java b/chrome/browser/password_manager/android/java/src/org/chromium/chrome/browser/password_manager/settings/PasswordsPreference.java
index 7875ef2..1836b36e 100644
--- a/chrome/browser/password_manager/android/java/src/org/chromium/chrome/browser/password_manager/settings/PasswordsPreference.java
+++ b/chrome/browser/password_manager/android/java/src/org/chromium/chrome/browser/password_manager/settings/PasswordsPreference.java
@@ -68,7 +68,7 @@
         }
 
         // This warning should override the managed text if any is present.
-        setUpPostDeprecationWarning(holder, prefService);
+        setUpPostDeprecationWarning(holder);
     }
 
     private void setUpManagedDisclaimerView(PreferenceViewHolder holder, PrefService prefService) {
@@ -86,11 +86,10 @@
                                 .password_saving_off_by_administrator);
     }
 
-    private void setUpPostDeprecationWarning(PreferenceViewHolder holder, PrefService prefService) {
+    private void setUpPostDeprecationWarning(PreferenceViewHolder holder) {
         assert mProfile != null : "Profile is not set!";
 
-        boolean isPasswordManagerAvailable =
-                PasswordManagerUtilBridge.isPasswordManagerAvailable(prefService);
+        boolean isPasswordManagerAvailable = PasswordManagerUtilBridge.isPasswordManagerAvailable();
         boolean hasPasswordsInCsv = LoginDbDeprecationUtilBridge.hasPasswordsInCsv(mProfile);
 
         // If there are no unmigrated passwords left in Chrome and the password manager is available
@@ -99,14 +98,6 @@
             return;
         }
 
-        // If the password manager is not available, but the auto-export hasn't finished yet
-        // don't show any subtitle either, because clicking the button will have no effect anyway
-        // and the version of the subtitle cannot be accurately determined.
-        if (!isPasswordManagerAvailable
-                && !prefService.getBoolean(Pref.UPM_UNMIGRATED_PASSWORDS_EXPORTED)) {
-            return;
-        }
-
         // If either the password manager is not available or it is available but there are
         // unmigrated passwords left in Chrome, show a subtitle notifying the user of that.
         // Automotive doesn't support the export flow, so only the "stopped working"
diff --git a/chrome/browser/password_manager/android/junit/src/org/chromium/chrome/browser/password_manager/PasswordManagerCheckupHelperTest.java b/chrome/browser/password_manager/android/junit/src/org/chromium/chrome/browser/password_manager/PasswordManagerCheckupHelperTest.java
index 17ba893..e3ca992 100644
--- a/chrome/browser/password_manager/android/junit/src/org/chromium/chrome/browser/password_manager/PasswordManagerCheckupHelperTest.java
+++ b/chrome/browser/password_manager/android/junit/src/org/chromium/chrome/browser/password_manager/PasswordManagerCheckupHelperTest.java
@@ -55,19 +55,15 @@
 import org.chromium.chrome.browser.password_manager.CredentialManagerLauncher.CredentialManagerError;
 import org.chromium.chrome.browser.password_manager.PasswordCheckupClientHelper.PasswordManagerUnavailableException;
 import org.chromium.chrome.browser.password_manager.PasswordManagerHelper.PasswordCheckOperation;
-import org.chromium.chrome.browser.preferences.Pref;
 import org.chromium.chrome.browser.profiles.Profile;
 import org.chromium.chrome.browser.pwm_disabled.PasswordCsvDownloadFlowController;
 import org.chromium.chrome.browser.pwm_disabled.PasswordCsvDownloadFlowControllerFactory;
 import org.chromium.chrome.browser.sync.SyncServiceFactory;
 import org.chromium.components.browser_ui.settings.SettingsCustomTabLauncher;
 import org.chromium.components.browser_ui.test.BrowserUiTestFragmentActivity;
-import org.chromium.components.prefs.PrefService;
 import org.chromium.components.signin.base.CoreAccountInfo;
 import org.chromium.components.sync.SyncService;
 import org.chromium.components.sync.UserSelectableType;
-import org.chromium.components.user_prefs.UserPrefs;
-import org.chromium.components.user_prefs.UserPrefsJni;
 import org.chromium.google_apis.gaia.GaiaId;
 import org.chromium.ui.modaldialog.ModalDialogManager;
 import org.chromium.ui.modaldialog.ModalDialogProperties;
@@ -95,10 +91,6 @@
 
     @Mock private Profile mProfile;
 
-    @Mock private PrefService mPrefService;
-
-    @Mock private UserPrefs.Natives mUserPrefsJniMock;
-
     @Mock private PasswordManagerUtilBridge.Natives mPasswordManagerUtilBridgeJniMock;
 
     @Mock private SyncService mSyncServiceMock;
@@ -121,10 +113,8 @@
 
     @Before
     public void setUp() {
-        UserPrefsJni.setInstanceForTesting(mUserPrefsJniMock);
         PasswordManagerUtilBridgeJni.setInstanceForTesting(mPasswordManagerUtilBridgeJniMock);
         mPasswordManagerHelper = new PasswordManagerHelper(mProfile);
-        when(mUserPrefsJniMock.get(mProfile)).thenReturn(mPrefService);
         SyncServiceFactory.setInstanceForTesting(mSyncServiceMock);
         when(mLoadingModalDialogCoordinator.getState())
                 .thenReturn(LoadingModalDialogCoordinator.State.PENDING);
@@ -142,9 +132,7 @@
                 .addObserver(any(LoadingModalDialogCoordinator.Observer.class));
         PasswordManagerBackendSupportHelper.setInstanceForTesting(mBackendSupportHelperMock);
         when(mBackendSupportHelperMock.isBackendPresent()).thenReturn(true);
-        when(mPasswordManagerUtilBridgeJniMock.isPasswordManagerAvailable(
-                        eq(mPrefService), eq(true)))
-                .thenReturn(true);
+        when(mPasswordManagerUtilBridgeJniMock.isPasswordManagerAvailable(true)).thenReturn(true);
         when(mPasswordCheckupClientHelperFactoryMock.createHelper())
                 .thenReturn(mPasswordCheckupClientHelperMock);
         PasswordCheckupClientHelperFactory.setFactoryForTesting(
@@ -154,9 +142,7 @@
 
     @Test
     public void testThrowsPasswordManagerNotAvailableException() {
-        when(mPasswordManagerUtilBridgeJniMock.isPasswordManagerAvailable(
-                        eq(mPrefService), eq(true)))
-                .thenReturn(false);
+        when(mPasswordManagerUtilBridgeJniMock.isPasswordManagerAvailable(true)).thenReturn(false);
         chooseToSyncPasswords();
         setUpSuccessfulRunPasswordCheckup();
 
@@ -1043,10 +1029,7 @@
     @Test
     public void testShowDownloadCsvDialogIfCsvIsPresentAndPwmNotAvailable() {
         when(mBackendSupportHelperMock.isBackendPresent()).thenReturn(true);
-        when(mPasswordManagerUtilBridgeJniMock.isPasswordManagerAvailable(
-                        eq(mPrefService), eq(true)))
-                .thenReturn(false);
-        when(mPrefService.getBoolean(Pref.UPM_UNMIGRATED_PASSWORDS_EXPORTED)).thenReturn(true);
+        when(mPasswordManagerUtilBridgeJniMock.isPasswordManagerAvailable(true)).thenReturn(false);
         LoginDbDeprecationUtilBridge.setHasCsvFileForTesting(true);
 
         FragmentActivity testActivity =
@@ -1075,10 +1058,7 @@
     @Test
     public void testShowDownloadCsvDialogIfCsvIsPresentAndNoGms() {
         when(mBackendSupportHelperMock.isBackendPresent()).thenReturn(true);
-        when(mPasswordManagerUtilBridgeJniMock.isPasswordManagerAvailable(
-                        eq(mPrefService), eq(true)))
-                .thenReturn(false);
-        when(mPrefService.getBoolean(Pref.UPM_UNMIGRATED_PASSWORDS_EXPORTED)).thenReturn(true);
+        when(mPasswordManagerUtilBridgeJniMock.isPasswordManagerAvailable(true)).thenReturn(false);
         LoginDbDeprecationUtilBridge.setHasCsvFileForTesting(true);
 
         FragmentActivity testActivity =
@@ -1106,10 +1086,7 @@
     @Test
     public void testShowPwmUnavailableDialogNoCsvNoGms() {
         when(mBackendSupportHelperMock.isBackendPresent()).thenReturn(true);
-        when(mPasswordManagerUtilBridgeJniMock.isPasswordManagerAvailable(
-                        eq(mPrefService), eq(true)))
-                .thenReturn(false);
-        when(mPrefService.getBoolean(Pref.UPM_UNMIGRATED_PASSWORDS_EXPORTED)).thenReturn(true);
+        when(mPasswordManagerUtilBridgeJniMock.isPasswordManagerAvailable(true)).thenReturn(false);
         LoginDbDeprecationUtilBridge.setHasCsvFileForTesting(false);
 
         FragmentActivity testActivity =
@@ -1131,10 +1108,7 @@
     @Test
     public void testShowPwmUnavailableDialogNoCsvUpdatableGms() {
         when(mBackendSupportHelperMock.isBackendPresent()).thenReturn(true);
-        when(mPasswordManagerUtilBridgeJniMock.isPasswordManagerAvailable(
-                        eq(mPrefService), eq(true)))
-                .thenReturn(false);
-        when(mPrefService.getBoolean(Pref.UPM_UNMIGRATED_PASSWORDS_EXPORTED)).thenReturn(true);
+        when(mPasswordManagerUtilBridgeJniMock.isPasswordManagerAvailable(true)).thenReturn(false);
         LoginDbDeprecationUtilBridge.setHasCsvFileForTesting(false);
 
         FragmentActivity testActivity =
diff --git a/chrome/browser/password_manager/android/junit/src/org/chromium/chrome/browser/password_manager/PasswordManagerHelperTest.java b/chrome/browser/password_manager/android/junit/src/org/chromium/chrome/browser/password_manager/PasswordManagerHelperTest.java
index 607e53b..746e9a7 100644
--- a/chrome/browser/password_manager/android/junit/src/org/chromium/chrome/browser/password_manager/PasswordManagerHelperTest.java
+++ b/chrome/browser/password_manager/android/junit/src/org/chromium/chrome/browser/password_manager/PasswordManagerHelperTest.java
@@ -6,10 +6,8 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
-import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.doThrow;
@@ -54,7 +52,6 @@
 import org.chromium.chrome.browser.loading_modal.LoadingModalDialogCoordinator;
 import org.chromium.chrome.browser.password_manager.CredentialManagerLauncher.CredentialManagerBackendException;
 import org.chromium.chrome.browser.password_manager.CredentialManagerLauncher.CredentialManagerError;
-import org.chromium.chrome.browser.preferences.Pref;
 import org.chromium.chrome.browser.profiles.Profile;
 import org.chromium.chrome.browser.pwm_disabled.PasswordCsvDownloadFlowController;
 import org.chromium.chrome.browser.pwm_disabled.PasswordCsvDownloadFlowControllerFactory;
@@ -63,12 +60,9 @@
 import org.chromium.components.browser_ui.settings.SettingsCustomTabLauncher;
 import org.chromium.components.browser_ui.settings.SettingsNavigation;
 import org.chromium.components.browser_ui.test.BrowserUiTestFragmentActivity;
-import org.chromium.components.prefs.PrefService;
 import org.chromium.components.signin.base.CoreAccountInfo;
 import org.chromium.components.sync.SyncService;
 import org.chromium.components.sync.UserSelectableType;
-import org.chromium.components.user_prefs.UserPrefs;
-import org.chromium.components.user_prefs.UserPrefsJni;
 import org.chromium.google_apis.gaia.GaiaId;
 import org.chromium.google_apis.gaia.GoogleServiceAuthError;
 import org.chromium.google_apis.gaia.GoogleServiceAuthErrorState;
@@ -100,10 +94,6 @@
 
     @Mock private Profile mProfile;
 
-    @Mock private PrefService mPrefService;
-
-    @Mock private UserPrefs.Natives mUserPrefsJniMock;
-
     @Mock private PasswordManagerUtilBridge.Natives mPasswordManagerUtilBridgeJniMock;
 
     @Mock private SyncService mSyncServiceMock;
@@ -129,13 +119,10 @@
     @Before
     public void setUp() throws CredentialManagerBackendException {
         // TODO(crbug.com/40940922): Parametrise the tests for local and account.
-        UserPrefsJni.setInstanceForTesting(mUserPrefsJniMock);
         PasswordManagerUtilBridgeJni.setInstanceForTesting(mPasswordManagerUtilBridgeJniMock);
-        when(mPasswordManagerUtilBridgeJniMock.isPasswordManagerAvailable(
-                        eq(mPrefService), anyBoolean()))
+        when(mPasswordManagerUtilBridgeJniMock.isPasswordManagerAvailable(anyBoolean()))
                 .thenReturn(true);
         mPasswordManagerHelper = new PasswordManagerHelper(mProfile);
-        when(mUserPrefsJniMock.get(mProfile)).thenReturn(mPrefService);
         SyncServiceFactory.setInstanceForTesting(mSyncServiceMock);
         when(mSyncServiceMock.isEngineInitialized()).thenReturn(true);
         when(mSyncServiceMock.getAuthError())
@@ -281,7 +268,6 @@
 
         mPasswordManagerHelper.launchTheCredentialManager(
                 ManagePasswordsReferrer.CHROME_SETTINGS,
-                mPrefService,
                 mSyncServiceMock,
                 mLoadingModalDialogCoordinator,
                 mModalDialogManagerSupplier,
@@ -299,7 +285,6 @@
 
         mPasswordManagerHelper.launchTheCredentialManager(
                 ManagePasswordsReferrer.CHROME_SETTINGS,
-                mPrefService,
                 mSyncServiceMock,
                 mLoadingModalDialogCoordinator,
                 mModalDialogManagerSupplier,
@@ -318,7 +303,6 @@
 
         mPasswordManagerHelper.launchTheCredentialManager(
                 ManagePasswordsReferrer.CHROME_SETTINGS,
-                mPrefService,
                 mSyncServiceMock,
                 mLoadingModalDialogCoordinator,
                 mModalDialogManagerSupplier,
@@ -336,7 +320,6 @@
 
         mPasswordManagerHelper.launchTheCredentialManager(
                 ManagePasswordsReferrer.CHROME_SETTINGS,
-                mPrefService,
                 mSyncServiceMock,
                 mLoadingModalDialogCoordinator,
                 mModalDialogManagerSupplier,
@@ -356,7 +339,6 @@
 
         mPasswordManagerHelper.launchTheCredentialManager(
                 ManagePasswordsReferrer.CHROME_SETTINGS,
-                mPrefService,
                 mSyncServiceMock,
                 mLoadingModalDialogCoordinator,
                 mModalDialogManagerSupplier,
@@ -376,7 +358,6 @@
 
         mPasswordManagerHelper.launchTheCredentialManager(
                 ManagePasswordsReferrer.CHROME_SETTINGS,
-                mPrefService,
                 mSyncServiceMock,
                 mLoadingModalDialogCoordinator,
                 mModalDialogManagerSupplier,
@@ -395,7 +376,6 @@
 
         mPasswordManagerHelper.launchTheCredentialManager(
                 ManagePasswordsReferrer.CHROME_SETTINGS,
-                mPrefService,
                 mSyncServiceMock,
                 mLoadingModalDialogCoordinator,
                 mModalDialogManagerSupplier,
@@ -417,7 +397,6 @@
 
         mPasswordManagerHelper.launchTheCredentialManager(
                 ManagePasswordsReferrer.CHROME_SETTINGS,
-                mPrefService,
                 mSyncServiceMock,
                 mLoadingModalDialogCoordinator,
                 mModalDialogManagerSupplier,
@@ -438,7 +417,6 @@
 
         mPasswordManagerHelper.launchTheCredentialManager(
                 ManagePasswordsReferrer.CHROME_SETTINGS,
-                mPrefService,
                 mSyncServiceMock,
                 mLoadingModalDialogCoordinator,
                 mModalDialogManagerSupplier,
@@ -460,7 +438,6 @@
 
         mPasswordManagerHelper.launchTheCredentialManager(
                 ManagePasswordsReferrer.CHROME_SETTINGS,
-                mPrefService,
                 mSyncServiceMock,
                 mLoadingModalDialogCoordinator,
                 mModalDialogManagerSupplier,
@@ -483,7 +460,6 @@
 
         mPasswordManagerHelper.launchTheCredentialManager(
                 ManagePasswordsReferrer.CHROME_SETTINGS,
-                mPrefService,
                 mSyncServiceMock,
                 mLoadingModalDialogCoordinator,
                 mModalDialogManagerSupplier,
@@ -501,7 +477,6 @@
 
         mPasswordManagerHelper.launchTheCredentialManager(
                 ManagePasswordsReferrer.CHROME_SETTINGS,
-                mPrefService,
                 mSyncServiceMock,
                 mLoadingModalDialogCoordinator,
                 mModalDialogManagerSupplier,
@@ -523,7 +498,6 @@
 
         mPasswordManagerHelper.launchTheCredentialManager(
                 ManagePasswordsReferrer.CHROME_SETTINGS,
-                mPrefService,
                 mSyncServiceMock,
                 mLoadingModalDialogCoordinator,
                 mModalDialogManagerSupplier,
@@ -541,7 +515,6 @@
 
         mPasswordManagerHelper.launchTheCredentialManager(
                 ManagePasswordsReferrer.CHROME_SETTINGS,
-                mPrefService,
                 mSyncServiceMock,
                 mLoadingModalDialogCoordinator,
                 mModalDialogManagerSupplier,
@@ -564,7 +537,6 @@
 
         mPasswordManagerHelper.launchTheCredentialManager(
                 ManagePasswordsReferrer.CHROME_SETTINGS,
-                mPrefService,
                 mSyncServiceMock,
                 mLoadingModalDialogCoordinator,
                 mModalDialogManagerSupplier,
@@ -585,7 +557,6 @@
 
         mPasswordManagerHelper.launchTheCredentialManager(
                 ManagePasswordsReferrer.CHROME_SETTINGS,
-                mPrefService,
                 mSyncServiceMock,
                 mLoadingModalDialogCoordinator,
                 mModalDialogManagerSupplier,
@@ -699,10 +670,7 @@
     @Test
     public void testShowDownloadCsvDialogIfCsvIsPresentAndPwmNotAvailable() {
         when(mBackendSupportHelperMock.isBackendPresent()).thenReturn(true);
-        when(mPasswordManagerUtilBridgeJniMock.isPasswordManagerAvailable(
-                        eq(mPrefService), eq(true)))
-                .thenReturn(false);
-        when(mPrefService.getBoolean(Pref.UPM_UNMIGRATED_PASSWORDS_EXPORTED)).thenReturn(true);
+        when(mPasswordManagerUtilBridgeJniMock.isPasswordManagerAvailable(true)).thenReturn(false);
         LoginDbDeprecationUtilBridge.setHasCsvFileForTesting(true);
 
         FragmentActivity testActivity =
@@ -732,9 +700,7 @@
     @Test
     public void testShowDownloadCsvDialogIfCsvIsPresentAndPwmAvailable() {
         when(mBackendSupportHelperMock.isBackendPresent()).thenReturn(true);
-        when(mPasswordManagerUtilBridgeJniMock.isPasswordManagerAvailable(
-                        eq(mPrefService), eq(true)))
-                .thenReturn(true);
+        when(mPasswordManagerUtilBridgeJniMock.isPasswordManagerAvailable(true)).thenReturn(true);
         LoginDbDeprecationUtilBridge.setHasCsvFileForTesting(true);
 
         FragmentActivity testActivity =
@@ -764,10 +730,7 @@
     @Test
     public void testShowDownloadCsvDialogIfCsvIsPresentAndNoGms() {
         when(mBackendSupportHelperMock.isBackendPresent()).thenReturn(true);
-        when(mPasswordManagerUtilBridgeJniMock.isPasswordManagerAvailable(
-                        eq(mPrefService), eq(true)))
-                .thenReturn(false);
-        when(mPrefService.getBoolean(Pref.UPM_UNMIGRATED_PASSWORDS_EXPORTED)).thenReturn(true);
+        when(mPasswordManagerUtilBridgeJniMock.isPasswordManagerAvailable(true)).thenReturn(false);
         LoginDbDeprecationUtilBridge.setHasCsvFileForTesting(true);
 
         FragmentActivity testActivity =
@@ -796,10 +759,7 @@
     @Test
     public void testShowPwmUnavailableDialogNoCsvNoGms() {
         when(mBackendSupportHelperMock.isBackendPresent()).thenReturn(true);
-        when(mPasswordManagerUtilBridgeJniMock.isPasswordManagerAvailable(
-                        eq(mPrefService), eq(true)))
-                .thenReturn(false);
-        when(mPrefService.getBoolean(Pref.UPM_UNMIGRATED_PASSWORDS_EXPORTED)).thenReturn(true);
+        when(mPasswordManagerUtilBridgeJniMock.isPasswordManagerAvailable(true)).thenReturn(false);
         LoginDbDeprecationUtilBridge.setHasCsvFileForTesting(false);
 
         FragmentActivity testActivity =
@@ -822,10 +782,7 @@
     @Test
     public void testShowPwmUnavailableDialogNoCsvUpdatableGms() {
         when(mBackendSupportHelperMock.isBackendPresent()).thenReturn(true);
-        when(mPasswordManagerUtilBridgeJniMock.isPasswordManagerAvailable(
-                        eq(mPrefService), eq(true)))
-                .thenReturn(false);
-        when(mPrefService.getBoolean(Pref.UPM_UNMIGRATED_PASSWORDS_EXPORTED)).thenReturn(true);
+        when(mPasswordManagerUtilBridgeJniMock.isPasswordManagerAvailable(true)).thenReturn(false);
         LoginDbDeprecationUtilBridge.setHasCsvFileForTesting(false);
 
         FragmentActivity testActivity =
@@ -845,48 +802,9 @@
                 dialogModel.get(ModalDialogProperties.TITLE));
     }
 
-    @Test
-    public void testDoesntShowDialogIfExportNotFinished() {
-        when(mBackendSupportHelperMock.isBackendPresent()).thenReturn(true);
-        when(mPasswordManagerUtilBridgeJniMock.isPasswordManagerAvailable(
-                        eq(mPrefService), eq(true)))
-                .thenReturn(false);
-        when(mPrefService.getBoolean(Pref.UPM_UNMIGRATED_PASSWORDS_EXPORTED)).thenReturn(false);
-        LoginDbDeprecationUtilBridge.setHasCsvFileForTesting(false);
-
-        FragmentActivity testActivity =
-                Robolectric.buildActivity(BrowserUiTestFragmentActivity.class).setup().get();
-        setUpUpdatableGmsCore(testActivity);
-        mPasswordManagerHelper.showPasswordSettings(
-                testActivity,
-                ManagePasswordsReferrer.CHROME_SETTINGS,
-                mModalDialogManagerSupplier,
-                /* managePasskeys= */ false,
-                TEST_NO_EMAIL_ADDRESS,
-                mSettingsCustomTabLauncher);
-
-        // Check that the unavailability dialog is not shown.
-        PropertyModel dialogModel = mModalDialogManager.getCurrentDialogForTest();
-        assertNull(dialogModel);
-
-        // Check that the download CSV dialog is not shown.
-        PasswordCsvDownloadFlowController mockController =
-                mock(PasswordCsvDownloadFlowController.class);
-        PasswordCsvDownloadFlowControllerFactory.setControllerForTesting(mockController);
-        verify(mockController, never())
-                .showDialogAndStartFlow(
-                        any(), any(), anyBoolean(), anyBoolean(), eq(mSettingsCustomTabLauncher));
-
-        // Check that the management UI is not shown (the pwm is unavailable).
-        verify(mCredentialManagerLauncherMock, never())
-                .getLocalCredentialManagerIntent(anyInt(), any(), any());
-    }
-
     private void setUpPwmAvailableWithoutUnmigratedPasswords() {
         when(mBackendSupportHelperMock.isBackendPresent()).thenReturn(true);
-        when(mPasswordManagerUtilBridgeJniMock.isPasswordManagerAvailable(
-                        eq(mPrefService), eq(true)))
-                .thenReturn(true);
+        when(mPasswordManagerUtilBridgeJniMock.isPasswordManagerAvailable(true)).thenReturn(true);
         LoginDbDeprecationUtilBridge.setHasCsvFileForTesting(false);
     }
 
diff --git a/chrome/browser/password_manager/android/login_db_deprecation_runner_factory.cc b/chrome/browser/password_manager/android/login_db_deprecation_runner_factory.cc
deleted file mode 100644
index e641e6c6..0000000
--- a/chrome/browser/password_manager/android/login_db_deprecation_runner_factory.cc
+++ /dev/null
@@ -1,63 +0,0 @@
-// Copyright 2024 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "chrome/browser/password_manager/android/login_db_deprecation_runner_factory.h"
-
-#include "base/feature_list.h"
-#include "base/no_destructor.h"
-#include "build/build_config.h"
-#include "chrome/browser/password_manager/android/password_manager_util_bridge.h"
-#include "chrome/browser/profiles/profile.h"
-#include "components/password_manager/core/browser/export/login_db_deprecation_password_exporter.h"
-#include "components/password_manager/core/browser/export/login_db_deprecation_runner.h"
-#include "components/password_manager/core/browser/features/password_features.h"
-#include "components/password_manager/core/browser/password_manager_buildflags.h"
-#include "components/password_manager/core/browser/split_stores_and_local_upm.h"
-#include "components/password_manager/core/common/password_manager_pref_names.h"
-#include "components/prefs/pref_service.h"
-#include "content/public/browser/browser_context.h"
-
-LoginDbDeprecationRunnerFactory::LoginDbDeprecationRunnerFactory()
-    : ProfileKeyedServiceFactory("LoginDbDeprecationRunnerFactory",
-                                 ProfileSelections::BuildForRegularProfile()) {}
-LoginDbDeprecationRunnerFactory::~LoginDbDeprecationRunnerFactory() = default;
-
-LoginDbDeprecationRunnerFactory*
-LoginDbDeprecationRunnerFactory::GetInstance() {
-  static base::NoDestructor<LoginDbDeprecationRunnerFactory> instance;
-  return instance.get();
-}
-
-password_manager::LoginDbDeprecationRunner*
-LoginDbDeprecationRunnerFactory::GetForProfile(Profile* profile) {
-  return static_cast<password_manager::LoginDbDeprecationRunner*>(
-      GetInstance()->GetServiceForBrowserContext(profile, true));
-}
-
-std::unique_ptr<KeyedService>
-LoginDbDeprecationRunnerFactory::BuildServiceInstanceForBrowserContext(
-    content::BrowserContext* context) const {
-#if BUILDFLAG(USE_LOGIN_DATABASE_AS_BACKEND)
-  return nullptr;
-#else
-  Profile* profile = Profile::FromBrowserContext(context);
-  PrefService* prefs = profile->GetPrefs();
-
-  // If the client is already migrated there is no need for export.
-  if (password_manager::GetLegacySplitStoresPref(prefs)) {
-    return nullptr;
-  }
-
-  if (prefs->GetBoolean(
-          password_manager::prefs::kUpmUnmigratedPasswordsExported)) {
-    // Since saving new passwords is disabled, one export is enough to guarantee
-    // that all passwords have been preserved outside of the login database.
-    return nullptr;
-  }
-
-  return std::make_unique<password_manager::LoginDbDeprecationRunner>(
-      std::make_unique<password_manager::LoginDbDeprecationPasswordExporter>(
-          profile->GetPrefs(), profile->GetPath()));
-#endif  // BUILDFLAG(USE_LOGIN_DATABASE_AS_BACKEND)
-}
diff --git a/chrome/browser/password_manager/android/login_db_deprecation_runner_factory.h b/chrome/browser/password_manager/android/login_db_deprecation_runner_factory.h
deleted file mode 100644
index d68a9af..0000000
--- a/chrome/browser/password_manager/android/login_db_deprecation_runner_factory.h
+++ /dev/null
@@ -1,34 +0,0 @@
-// Copyright 2024 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef CHROME_BROWSER_PASSWORD_MANAGER_ANDROID_LOGIN_DB_DEPRECATION_RUNNER_FACTORY_H_
-#define CHROME_BROWSER_PASSWORD_MANAGER_ANDROID_LOGIN_DB_DEPRECATION_RUNNER_FACTORY_H_
-
-#include "base/no_destructor.h"
-#include "base/types/pass_key.h"
-#include "chrome/browser/profiles/profile_keyed_service_factory.h"
-#include "components/password_manager/core/browser/export/login_db_deprecation_runner.h"
-#include "content/public/browser/browser_context.h"
-
-// Creates the `LoginDbDeprecationRunner` which runs the pre-deprecation
-// export for unmigrated passwords on Android.
-class LoginDbDeprecationRunnerFactory : public ProfileKeyedServiceFactory {
- public:
-  static LoginDbDeprecationRunnerFactory* GetInstance();
-  static password_manager::LoginDbDeprecationRunner* GetForProfile(
-      Profile* profile);
-
- private:
-  friend class base::NoDestructor<LoginDbDeprecationRunnerFactory>;
-
-  LoginDbDeprecationRunnerFactory();
-  ~LoginDbDeprecationRunnerFactory() override;
-
-  std::unique_ptr<KeyedService> BuildServiceInstanceForBrowserContext(
-      content::BrowserContext* context) const override;
-
-  base::RepeatingCallback<bool()> internal_backend_checker_;
-};
-
-#endif  // CHROME_BROWSER_PASSWORD_MANAGER_ANDROID_LOGIN_DB_DEPRECATION_RUNNER_FACTORY_H_
diff --git a/chrome/browser/password_manager/android/login_db_deprecation_runner_factory_unittest.cc b/chrome/browser/password_manager/android/login_db_deprecation_runner_factory_unittest.cc
deleted file mode 100644
index 45cd857b..0000000
--- a/chrome/browser/password_manager/android/login_db_deprecation_runner_factory_unittest.cc
+++ /dev/null
@@ -1,50 +0,0 @@
-// Copyright 2024 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "chrome/browser/password_manager/android/login_db_deprecation_runner_factory.h"
-
-#include "base/test/scoped_feature_list.h"
-#include "chrome/test/base/testing_profile.h"
-#include "components/password_manager/core/browser/features/password_features.h"
-#include "components/password_manager/core/browser/split_stores_and_local_upm.h"
-#include "components/password_manager/core/common/password_manager_pref_names.h"
-#include "components/prefs/pref_service.h"
-#include "components/prefs/testing_pref_service.h"
-#include "content/public/test/browser_task_environment.h"
-#include "testing/gtest/include/gtest/gtest.h"
-
-class LoginDbDeprecationRunnerFactoryTest : public testing::Test {
- public:
-  LoginDbDeprecationRunnerFactoryTest() = default;
-
- protected:
-  content::BrowserTaskEnvironment task_env_;
-  TestingProfile testing_profile_;
-};
-
-TEST_F(LoginDbDeprecationRunnerFactoryTest, NullServiceIfMigrated) {
-  PrefService* prefs = testing_profile_.GetPrefs();
-  password_manager::SetLegacySplitStoresPrefForTest(prefs, true);
-  EXPECT_FALSE(
-      LoginDbDeprecationRunnerFactory::GetForProfile(&testing_profile_));
-}
-
-TEST_F(LoginDbDeprecationRunnerFactoryTest, NullIfAlreadyExported) {
-  PrefService* prefs = testing_profile_.GetPrefs();
-  password_manager::SetLegacySplitStoresPrefForTest(prefs, false);
-  prefs->SetBoolean(password_manager::prefs::kUpmUnmigratedPasswordsExported,
-                    true);
-  EXPECT_FALSE(
-      LoginDbDeprecationRunnerFactory::GetForProfile(&testing_profile_));
-}
-
-TEST_F(LoginDbDeprecationRunnerFactoryTest,
-       NonNullServiceIfNotEligibleForMigration) {
-  PrefService* prefs = testing_profile_.GetPrefs();
-  password_manager::SetLegacySplitStoresPrefForTest(prefs, false);
-  prefs->SetBoolean(password_manager::prefs::kUpmUnmigratedPasswordsExported,
-                    false);
-  EXPECT_TRUE(
-      LoginDbDeprecationRunnerFactory::GetForProfile(&testing_profile_));
-}
diff --git a/chrome/browser/password_manager/android/login_db_deprecation_util_bridge.cc b/chrome/browser/password_manager/android/login_db_deprecation_util_bridge.cc
index 4cb7710..5837c03 100644
--- a/chrome/browser/password_manager/android/login_db_deprecation_util_bridge.cc
+++ b/chrome/browser/password_manager/android/login_db_deprecation_util_bridge.cc
@@ -6,8 +6,8 @@
 #include "base/android/jni_android.h"
 #include "base/android/jni_string.h"
 #include "base/files/file_path.h"
+#include "chrome/browser/password_manager/android/password_manager_android_util.h"
 #include "chrome/browser/profiles/profile.h"
-#include "components/password_manager/core/browser/export/login_db_deprecation_password_exporter.h"
 
 // Must come after all headers that specialize FromJniType() / ToJniType().
 #include "chrome/browser/password_manager/android/utils_jni_headers/LoginDbDeprecationUtilBridge_jni.h"
@@ -18,6 +18,6 @@
   return base::android::ConvertUTF8ToJavaString(
       env, profile->GetPath()
                .Append(FILE_PATH_LITERAL(
-                   password_manager::kExportedPasswordsFileName))
+                   password_manager_android_util::kExportedPasswordsFileName))
                .value());
 }
diff --git a/chrome/browser/password_manager/android/password_manager_android_util.cc b/chrome/browser/password_manager/android/password_manager_android_util.cc
index 6c96878..0fe7b2a 100644
--- a/chrome/browser/password_manager/android/password_manager_android_util.cc
+++ b/chrome/browser/password_manager/android/password_manager_android_util.cc
@@ -15,16 +15,11 @@
 #include "base/strings/string_number_conversions.h"
 #include "chrome/browser/password_manager/android/password_manager_util_bridge.h"
 #include "chrome/browser/password_manager/android/password_manager_util_bridge_interface.h"
-#include "components/password_manager/core/browser/export/login_db_deprecation_password_exporter.h"
 #include "components/password_manager/core/browser/password_manager_buildflags.h"
 #include "components/password_manager/core/browser/password_manager_constants.h"
-#include "components/password_manager/core/browser/password_manager_util.h"
-#include "components/password_manager/core/browser/password_sync_util.h"
 #include "components/password_manager/core/browser/split_stores_and_local_upm.h"
 #include "components/password_manager/core/common/password_manager_pref_names.h"
 #include "components/prefs/pref_service.h"
-#include "components/sync/base/pref_names.h"
-#include "components/version_info/android/channel_getter.h"
 
 namespace password_manager_android_util {
 
@@ -40,20 +35,8 @@
 }
 
 #if !BUILDFLAG(USE_LOGIN_DATABASE_AS_BACKEND)
-// Called on startup to delete the login data files for users migrated to UPM
-// or for users who had all the unmigrated passwords auto-exported.
-// Must only be called if the value of the state pref
-// `PasswordsUseUPMLocalAndSeparateStores` is `On` and there
-// is no need for deactivation of local UPM or either UPM is already active or
-// unmigrated passwords have already been auto-exported.
-void MaybeDeleteLoginDataFiles(PrefService* prefs,
-                               const base::FilePath& login_db_directory) {
-  bool already_active_in_upm =
-      password_manager::GetLegacySplitStoresPref(prefs);
-  bool login_db_ready_for_deprecation =
-      LoginDbDeprecationReady(prefs);
-  CHECK(already_active_in_upm || login_db_ready_for_deprecation);
-
+// Called on startup to delete the login data files.
+void DeleteLoginDataFiles(const base::FilePath& login_db_directory) {
   base::FilePath profile_db_path =
       login_db_directory.Append(password_manager::kLoginDataForProfileFileName);
   base::FilePath account_db_path =
@@ -67,10 +50,6 @@
     bool success = base::DeleteFile(profile_db_path);
     base::UmaHistogramBoolean("PasswordManager.ProfileLoginData.RemovalStatus",
                               success);
-    if (success) {
-      prefs->SetBoolean(
-          password_manager::prefs::kEmptyProfileStoreLoginDatabase, true);
-    }
   }
   base::DeleteFile(profile_db_journal_path);
 
@@ -84,8 +63,8 @@
 
 void DeleteAutoExportedCsv(PrefService* prefs,
                            const base::FilePath& login_db_directory) {
-  base::FilePath csv_path = login_db_directory.Append(
-      FILE_PATH_LITERAL(password_manager::kExportedPasswordsFileName));
+  base::FilePath csv_path =
+      login_db_directory.Append(FILE_PATH_LITERAL(kExportedPasswordsFileName));
   if (base::PathExists(csv_path)) {
     bool success = base::DeleteFile(csv_path);
     if (success) {
@@ -99,8 +78,9 @@
 
 #endif  // !BUILDFLAG(USE_LOGIN_DATABASE_AS_BACKEND)
 
+}  // namespace
+
 PasswordManagerNotAvailableReason GetPasswordManagerNotActiveReason(
-    PrefService* pref_service,
     PasswordManagerUtilBridgeInterface* util_bridge,
     bool is_internal_backend_present) {
   if (!is_internal_backend_present) {
@@ -114,58 +94,30 @@
     return PasswordManagerNotAvailableReason::kOutdatedGmsCore;
   }
 
-  CHECK(!pref_service->GetBoolean(
-      password_manager::prefs::kUpmUnmigratedPasswordsExported));
-  return PasswordManagerNotAvailableReason::kAutoExportPending;
+  NOTREACHED();
 }
 
 void RecordLocalUpmActivationMetrics(
-    PrefService* pref_service,
     PasswordManagerUtilBridgeInterface* util_bridge) {
   bool is_internal_backend_present = util_bridge->IsInternalBackendPresent();
   bool is_pwm_available =
-      IsPasswordManagerAvailable(pref_service, is_internal_backend_present);
+      IsPasswordManagerAvailable(is_internal_backend_present);
   base::UmaHistogramBoolean("PasswordManager.LocalUpmActivated",
                             is_pwm_available);
   if (!is_pwm_available) {
     base::UmaHistogramEnumeration(
         "PasswordManager.Android.NotAvailableReason",
-        GetPasswordManagerNotActiveReason(pref_service, util_bridge,
+        GetPasswordManagerNotActiveReason(util_bridge,
                                           is_internal_backend_present));
   }
 }
 
-void InitializeUpmUnmigratedPasswordsExportPref(
-    PrefService* prefs,
-    const base::FilePath& login_db_directory) {
-  // The umigrated passwords export pref should only be set for users who aren't
-  // already part of UPM.
-  if (password_manager::GetLegacySplitStoresPref(prefs)) {
-    return;
-  }
-
-  // If there are no passwords saved, there is nothing to export prior to
-  // deprecation, so mark the export as done already.
-  if (prefs->GetBoolean(
-          password_manager::prefs::kEmptyProfileStoreLoginDatabase) ||
-      !base::PathExists(login_db_directory.Append(
-          password_manager::kLoginDataForProfileFileName))) {
-    prefs->SetBoolean(password_manager::prefs::kUpmUnmigratedPasswordsExported,
-                      true);
-  }
-}
-
-}  // namespace
-
 bool IsPasswordManagerAvailable(
-    const PrefService* prefs,
     std::unique_ptr<PasswordManagerUtilBridgeInterface> util_bridge) {
-  return IsPasswordManagerAvailable(prefs,
-                                    util_bridge->IsInternalBackendPresent());
+  return IsPasswordManagerAvailable(util_bridge->IsInternalBackendPresent());
 }
 
-bool IsPasswordManagerAvailable(const PrefService* prefs,
-                                bool is_internal_backend_present) {
+bool IsPasswordManagerAvailable(bool is_internal_backend_present) {
   if (!is_internal_backend_present) {
     return false;
   }
@@ -173,35 +125,17 @@
   if (!HasMinGmsVersionForFullUpmSupport()) {
     return false;
   }
-  bool upm_already_active = password_manager::GetLegacySplitStoresPref(prefs);
-  bool exported_umigrated_passwords = prefs->GetBoolean(
-      password_manager::prefs::kUpmUnmigratedPasswordsExported);
-  return upm_already_active || exported_umigrated_passwords;
-}
 
-bool LoginDbDeprecationReady(PrefService* prefs) {
-  bool upm_already_active = password_manager::GetLegacySplitStoresPref(prefs);
-  bool exported_umigrated_passwords = prefs->GetBoolean(
-      password_manager::prefs::kUpmUnmigratedPasswordsExported);
-  return upm_already_active || exported_umigrated_passwords;
+  return true;
 }
 
 void MaybeDeleteLoginDatabases(
     PrefService* pref_service,
     const base::FilePath& login_db_directory,
     std::unique_ptr<PasswordManagerUtilBridgeInterface> util_bridge) {
-  // For fresh installs in particular, it's important to do this before
-  // the backend creation, so that the Android backends are directly wired
-  // without requiring another restart.
-  password_manager_android_util::InitializeUpmUnmigratedPasswordsExportPref(
-      pref_service, login_db_directory);
-  // If the login DB is being deprecated, only record metrics and do not
-  // perform the activation algorithm.
-  RecordLocalUpmActivationMetrics(pref_service, util_bridge.get());
+  RecordLocalUpmActivationMetrics(util_bridge.get());
 #if !BUILDFLAG(USE_LOGIN_DATABASE_AS_BACKEND)
-  if (LoginDbDeprecationReady(pref_service)) {
-    MaybeDeleteLoginDataFiles(pref_service, login_db_directory);
-  }
+  DeleteLoginDataFiles(login_db_directory);
   if (pref_service->GetBoolean(
           password_manager::prefs::kUpmAutoExportCsvNeedsDeletion)) {
     DeleteAutoExportedCsv(pref_service, login_db_directory);
diff --git a/chrome/browser/password_manager/android/password_manager_android_util.h b/chrome/browser/password_manager/android/password_manager_android_util.h
index d0cd7ee7..43edd4c 100644
--- a/chrome/browser/password_manager/android/password_manager_android_util.h
+++ b/chrome/browser/password_manager/android/password_manager_android_util.h
@@ -5,9 +5,10 @@
 #ifndef CHROME_BROWSER_PASSWORD_MANAGER_ANDROID_PASSWORD_MANAGER_ANDROID_UTIL_H_
 #define CHROME_BROWSER_PASSWORD_MANAGER_ANDROID_PASSWORD_MANAGER_ANDROID_UTIL_H_
 
+#include <memory>
+#include <string_view>
+
 #include "chrome/browser/password_manager/android/password_manager_util_bridge_interface.h"
-#include "components/password_manager/core/browser/password_manager_metrics_util.h"
-#include "components/password_manager/core/common/password_manager_pref_names.h"
 
 class PrefService;
 
@@ -17,6 +18,9 @@
 
 namespace password_manager_android_util {
 
+inline constexpr std::string_view kExportedPasswordsFileName =
+    "ChromePasswords.csv";
+
 // These values are persisted to logs. Entries should not be renumbered and
 // numeric values should never be reused.
 //
@@ -31,40 +35,25 @@
   // GmsCore version doesn't support UPM at all, or not fully.
   kOutdatedGmsCore = 2,
   // GmsCore version supports UPM, but there are still unmigrated passwords.
-  kAutoExportPending = 3,
+  // Deprecated: kAutoExportPending = 3,
 
-  kMaxValue = kAutoExportPending
+  kMaxValue = kOutdatedGmsCore
 };
 
 // LINT.ThenChange(/tools/metrics/histograms/metadata/password/enums.xml:PasswordManagerNotAvailableReason)
 
 // Checks whether the password manager can be used on Android.
-// Once the login db is deprecated, for clients not fulfilling the criteria
-// for talking to the Android backend, the password manager will no longer
-// be available.
 // The criteria are:
 // - access to the internal backend
 // - GMS Core version with full UPM support
-// - passwords were either migrated or exported
 bool IsPasswordManagerAvailable(
-    const PrefService* prefs,
     std::unique_ptr<PasswordManagerUtilBridgeInterface> util_bridge);
 
 // As above, except the caller already knows whether the internal backend
 // is present, probably because the call originates in Java.
-bool IsPasswordManagerAvailable(const PrefService* prefs,
-                                bool is_internal_backend_present);
+bool IsPasswordManagerAvailable(bool is_internal_backend_present);
 
-// The login DB is ready to be deprecated when all the passwords have either
-// been already migrated to UPM or exported.
-//
-// Note: This should only be used if looking to identify whether deprecation
-// is ongoing or not. For most other  purposes `IsPasswordManagerAvailable` is
-// the correct util to check.
-bool LoginDbDeprecationReady(PrefService* pref_service);
-
-// The login database is deprecated on Android. This function deletes the data
-// if the user already exported any leftover data.
+// The login database is deprecated on Android. This function deletes the data.
 void MaybeDeleteLoginDatabases(
     PrefService* pref_service,
     const base::FilePath& login_db_directory,
diff --git a/chrome/browser/password_manager/android/password_manager_android_util_unittest.cc b/chrome/browser/password_manager/android/password_manager_android_util_unittest.cc
index 7c6b722b..57d0c64 100644
--- a/chrome/browser/password_manager/android/password_manager_android_util_unittest.cc
+++ b/chrome/browser/password_manager/android/password_manager_android_util_unittest.cc
@@ -18,19 +18,16 @@
 #include "base/test/test_file_util.h"
 #include "build/build_config.h"
 #include "chrome/browser/password_manager/android/mock_password_manager_util_bridge.h"
-#include "components/password_manager/core/browser/export/login_db_deprecation_password_exporter.h"
 #include "components/password_manager/core/browser/password_manager_constants.h"
 #include "components/password_manager/core/browser/split_stores_and_local_upm.h"
 #include "components/password_manager/core/common/password_manager_pref_names.h"
 #include "components/prefs/pref_registry_simple.h"
 #include "components/prefs/testing_pref_service.h"
-#include "components/sync/test/test_sync_service.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 using password_manager::GetSplitStoresUpmMinVersion;
 using password_manager::prefs::kUpmAutoExportCsvNeedsDeletion;
-using password_manager::prefs::kUpmUnmigratedPasswordsExported;
 using testing::Return;
 
 namespace password_manager_android_util {
@@ -39,11 +36,6 @@
 class PasswordManagerAndroidUtilTest : public testing::Test {
  public:
   PasswordManagerAndroidUtilTest() {
-    password_manager::RegisterLegacySplitStoresPref(pref_service_.registry());
-    pref_service_.registry()->RegisterBooleanPref(
-        password_manager::prefs::kEmptyProfileStoreLoginDatabase, false);
-    pref_service_.registry()->RegisterBooleanPref(
-        password_manager::prefs::kUpmUnmigratedPasswordsExported, false);
     pref_service_.registry()->RegisterBooleanPref(
         password_manager::prefs::kUpmAutoExportCsvNeedsDeletion, false);
     base::WriteFile(login_db_directory_.Append(
@@ -77,15 +69,12 @@
   // Make sure all the other criteria are fulfilled.
   base::android::device_info::set_gms_version_code_for_test(
       base::NumberToString(GetSplitStoresUpmMinVersion()));
-  password_manager::SetLegacySplitStoresPrefForTest(pref_service(), true);
-  pref_service()->SetBoolean(kUpmUnmigratedPasswordsExported, false);
 
   std::unique_ptr<MockPasswordManagerUtilBridge> mock_util_bridge =
       std::make_unique<MockPasswordManagerUtilBridge>();
   EXPECT_CALL(*mock_util_bridge, IsInternalBackendPresent)
       .WillOnce(Return(false));
-  EXPECT_FALSE(
-      IsPasswordManagerAvailable(pref_service(), std::move(mock_util_bridge)));
+  EXPECT_FALSE(IsPasswordManagerAvailable(std::move(mock_util_bridge)));
 }
 
 TEST_F(PasswordManagerAndroidUtilTest,
@@ -94,19 +83,15 @@
       std::make_unique<MockPasswordManagerUtilBridge>();
   EXPECT_CALL(*mock_util_bridge, IsInternalBackendPresent)
       .WillOnce(Return(true));
-  password_manager::SetLegacySplitStoresPrefForTest(pref_service(), true);
-  pref_service()->SetBoolean(kUpmUnmigratedPasswordsExported, false);
 
   // Set a GMS Core version that is lower than the min required version.
   base::android::device_info::set_gms_version_code_for_test(
       base::NumberToString(GetSplitStoresUpmMinVersion() - 1));
 
-  EXPECT_FALSE(
-      IsPasswordManagerAvailable(pref_service(), std::move(mock_util_bridge)));
+  EXPECT_FALSE(IsPasswordManagerAvailable(std::move(mock_util_bridge)));
 }
 
-TEST_F(PasswordManagerAndroidUtilTest,
-       PasswordManagerNotAvailablePasswordsUnmigratedPasswords) {
+TEST_F(PasswordManagerAndroidUtilTest, PasswordManagerAvailable) {
   std::unique_ptr<MockPasswordManagerUtilBridge> mock_util_bridge =
       std::make_unique<MockPasswordManagerUtilBridge>();
   EXPECT_CALL(*mock_util_bridge, IsInternalBackendPresent)
@@ -115,43 +100,7 @@
   base::android::device_info::set_gms_version_code_for_test(
       base::NumberToString(GetSplitStoresUpmMinVersion()));
 
-  password_manager::SetLegacySplitStoresPrefForTest(pref_service(), false);
-  pref_service()->SetBoolean(kUpmUnmigratedPasswordsExported, false);
-
-  EXPECT_FALSE(
-      IsPasswordManagerAvailable(pref_service(), std::move(mock_util_bridge)));
-}
-
-TEST_F(PasswordManagerAndroidUtilTest, PasswordManagerAvailableNoUpmMigration) {
-  std::unique_ptr<MockPasswordManagerUtilBridge> mock_util_bridge =
-      std::make_unique<MockPasswordManagerUtilBridge>();
-  EXPECT_CALL(*mock_util_bridge, IsInternalBackendPresent)
-      .WillOnce(Return(true));
-
-  base::android::device_info::set_gms_version_code_for_test(
-      base::NumberToString(GetSplitStoresUpmMinVersion()));
-
-  password_manager::SetLegacySplitStoresPrefForTest(pref_service(), false);
-  pref_service()->SetBoolean(kUpmUnmigratedPasswordsExported, true);
-
-  EXPECT_TRUE(
-      IsPasswordManagerAvailable(pref_service(), std::move(mock_util_bridge)));
-}
-
-TEST_F(PasswordManagerAndroidUtilTest, PasswordManagerAvailableUpmMigration) {
-  std::unique_ptr<MockPasswordManagerUtilBridge> mock_util_bridge =
-      std::make_unique<MockPasswordManagerUtilBridge>();
-  EXPECT_CALL(*mock_util_bridge, IsInternalBackendPresent)
-      .WillOnce(Return(true));
-
-  base::android::device_info::set_gms_version_code_for_test(
-      base::NumberToString(GetSplitStoresUpmMinVersion()));
-
-  password_manager::SetLegacySplitStoresPrefForTest(pref_service(), true);
-  pref_service()->SetBoolean(kUpmUnmigratedPasswordsExported, false);
-
-  EXPECT_TRUE(
-      IsPasswordManagerAvailable(pref_service(), std::move(mock_util_bridge)));
+  EXPECT_TRUE(IsPasswordManagerAvailable(std::move(mock_util_bridge)));
 }
 
 TEST_F(PasswordManagerAndroidUtilTest, TestRecordsUpmNotActiveWhenNoGms) {
@@ -190,31 +139,10 @@
       PasswordManagerNotAvailableReason::kOutdatedGmsCore, 1);
 }
 
-TEST_F(PasswordManagerAndroidUtilTest,
-       TestRecordsUpmNotActivateBeforeAutoExport) {
+TEST_F(PasswordManagerAndroidUtilTest, TestRecordsUpmActive) {
   base::android::device_info::set_gms_version_code_for_test(
       base::NumberToString(GetSplitStoresUpmMinVersion()));
 
-  password_manager::SetLegacySplitStoresPrefForTest(pref_service(), false);
-  pref_service()->SetBoolean(
-      password_manager::prefs::kUpmUnmigratedPasswordsExported, false);
-  base::HistogramTester histogram_tester;
-  MaybeDeleteLoginDatabases(pref_service(), login_db_directory(),
-                            GetMockBridgeWithBackendPresent());
-  histogram_tester.ExpectUniqueSample("PasswordManager.LocalUpmActivated",
-                                      false, 1);
-  histogram_tester.ExpectUniqueSample(
-      "PasswordManager.Android.NotAvailableReason",
-      PasswordManagerNotAvailableReason::kAutoExportPending, 1);
-}
-
-TEST_F(PasswordManagerAndroidUtilTest, TestRecordsUpmActiveIfExported) {
-  base::android::device_info::set_gms_version_code_for_test(
-      base::NumberToString(GetSplitStoresUpmMinVersion()));
-
-  password_manager::SetLegacySplitStoresPrefForTest(pref_service(), false);
-  pref_service()->SetBoolean(
-      password_manager::prefs::kUpmUnmigratedPasswordsExported, true);
   base::HistogramTester histogram_tester;
   MaybeDeleteLoginDatabases(pref_service(), login_db_directory(),
                             GetMockBridgeWithBackendPresent());
@@ -224,63 +152,13 @@
       "PasswordManager.Android.NotAvailableReason", 0);
 }
 
-TEST_F(PasswordManagerAndroidUtilTest, TestRecordsUpmActiveIfAlreadyActive) {
-  base::android::device_info::set_gms_version_code_for_test(
-      base::NumberToString(GetSplitStoresUpmMinVersion()));
-
-  password_manager::SetLegacySplitStoresPrefForTest(pref_service(), true);
-  pref_service()->SetBoolean(
-      password_manager::prefs::kUpmUnmigratedPasswordsExported, false);
-  base::HistogramTester histogram_tester;
-  MaybeDeleteLoginDatabases(pref_service(), login_db_directory(),
-                            GetMockBridgeWithBackendPresent());
-  histogram_tester.ExpectUniqueSample("PasswordManager.LocalUpmActivated", true,
-                                      1);
-  histogram_tester.ExpectTotalCount(
-      "PasswordManager.Android.NotAvailableReason", 0);
-}
-
-TEST_F(PasswordManagerAndroidUtilTest,
-       InitUnmigratedExportUnchangedIfMigrated) {
-  password_manager::SetLegacySplitStoresPrefForTest(pref_service(), true);
-  MaybeDeleteLoginDatabases(pref_service(), login_db_directory(),
-                            GetMockBridgeWithBackendPresent());
-  EXPECT_TRUE(pref_service()
-                  ->FindPreference(
-                      password_manager::prefs::kUpmUnmigratedPasswordsExported)
-                  ->IsDefaultValue());
-}
-
-TEST_F(PasswordManagerAndroidUtilTest, InitUnmigratedExportPrefTrueEmptyDb) {
-  password_manager::SetLegacySplitStoresPrefForTest(pref_service(), false);
-  pref_service()->SetBoolean(
-      password_manager::prefs::kEmptyProfileStoreLoginDatabase, true);
-  pref_service()->SetBoolean(
-      password_manager::prefs::kUpmUnmigratedPasswordsExported, false);
-  MaybeDeleteLoginDatabases(pref_service(), login_db_directory(),
-                            GetMockBridgeWithBackendPresent());
-  EXPECT_TRUE(pref_service()->GetBoolean(
-      password_manager::prefs::kUpmUnmigratedPasswordsExported));
-}
-
-TEST_F(PasswordManagerAndroidUtilTest,
-       DeletesLoginDataFilesAfterUnmigratedPasswordsExported) {
+TEST_F(PasswordManagerAndroidUtilTest, DeletesLoginDataFiles) {
   base::HistogramTester histogram_tester;
   const char kRemovalStatusProfileMetric[] =
       "PasswordManager.ProfileLoginData.RemovalStatus";
   const char kRemovalStatusAccountMetric[] =
       "PasswordManager.AccountLoginData.RemovalStatus";
 
-  // Assume an unmigrated user.
-  password_manager::SetLegacySplitStoresPrefForTest(pref_service(), false);
-  // With unmigrated passwords exported.
-  pref_service()->SetBoolean(kUpmUnmigratedPasswordsExported, true);
-
-  // And for whom the initial passwords deletion failed, so they still have
-  // passwords in the db.
-  pref_service()->SetBoolean(
-      password_manager::prefs::kEmptyProfileStoreLoginDatabase, false);
-
   // Creating the login data files for testing.
   base::FilePath profile_db_path = login_db_directory().Append(
       password_manager::kLoginDataForProfileFileName);
@@ -308,60 +186,6 @@
   EXPECT_FALSE(PathExists(account_db_path));
   EXPECT_FALSE(PathExists(profile_db_journal_path));
   EXPECT_FALSE(PathExists(account_db_journal_path));
-  EXPECT_TRUE(pref_service()->GetBoolean(
-      password_manager::prefs::kEmptyProfileStoreLoginDatabase));
-
-  histogram_tester.ExpectUniqueSample(kRemovalStatusProfileMetric, true, 1);
-  histogram_tester.ExpectUniqueSample(kRemovalStatusAccountMetric, true, 1);
-}
-
-// This test is relevant for users for whom prior db deletion attempts failed.
-TEST_F(PasswordManagerAndroidUtilTest,
-       DeletesLoginDataFilesForAlreadyMigratedUser) {
-  base::HistogramTester histogram_tester;
-  const char kRemovalStatusProfileMetric[] =
-      "PasswordManager.ProfileLoginData.RemovalStatus";
-  const char kRemovalStatusAccountMetric[] =
-      "PasswordManager.AccountLoginData.RemovalStatus";
-
-  // Assume a migrated user.
-  password_manager::SetLegacySplitStoresPrefForTest(pref_service(), true);
-  // No unmigrated passwords, so nothing was exported.
-  pref_service()->SetBoolean(kUpmUnmigratedPasswordsExported, false);
-  // And for whom the initial passwords deletion failed, so they still have
-  // passwords in the db.
-  pref_service()->SetBoolean(
-      password_manager::prefs::kEmptyProfileStoreLoginDatabase, false);
-
-  // Creating the login data files for testing.
-  base::FilePath profile_db_path = login_db_directory().Append(
-      password_manager::kLoginDataForProfileFileName);
-  base::FilePath account_db_path = login_db_directory().Append(
-      password_manager::kLoginDataForAccountFileName);
-  base::FilePath profile_db_journal_path = login_db_directory().Append(
-      password_manager::kLoginDataJournalForProfileFileName);
-  base::FilePath account_db_journal_path = login_db_directory().Append(
-      password_manager::kLoginDataJournalForAccountFileName);
-
-  base::WriteFile(profile_db_path, "Test content");
-  base::WriteFile(account_db_path, "Test content");
-  base::WriteFile(profile_db_journal_path, "Test content");
-  base::WriteFile(account_db_journal_path, "Test content");
-
-  EXPECT_TRUE(PathExists(profile_db_path));
-  EXPECT_TRUE(PathExists(account_db_path));
-  EXPECT_TRUE(PathExists(profile_db_journal_path));
-  EXPECT_TRUE(PathExists(account_db_journal_path));
-
-  MaybeDeleteLoginDatabases(pref_service(), login_db_directory(),
-                            GetMockBridgeWithBackendPresent());
-
-  EXPECT_FALSE(PathExists(profile_db_path));
-  EXPECT_FALSE(PathExists(account_db_path));
-  EXPECT_FALSE(PathExists(profile_db_journal_path));
-  EXPECT_FALSE(PathExists(account_db_journal_path));
-  EXPECT_TRUE(pref_service()->GetBoolean(
-      password_manager::prefs::kEmptyProfileStoreLoginDatabase));
 
   histogram_tester.ExpectUniqueSample(kRemovalStatusProfileMetric, true, 1);
   histogram_tester.ExpectUniqueSample(kRemovalStatusAccountMetric, true, 1);
@@ -375,7 +199,7 @@
 
   // Creating the csv for testing.
   base::FilePath csv_path =
-      login_db_directory().Append(password_manager::kExportedPasswordsFileName);
+      login_db_directory().Append(kExportedPasswordsFileName);
 
   base::WriteFile(csv_path, "Test content");
 
@@ -398,7 +222,7 @@
 
   // Creating the csv for testing.
   base::FilePath csv_path =
-      login_db_directory().Append(password_manager::kExportedPasswordsFileName);
+      login_db_directory().Append(kExportedPasswordsFileName);
 
   base::WriteFile(csv_path, "Test content");
 
diff --git a/chrome/browser/password_manager/android/password_manager_util_bridge.cc b/chrome/browser/password_manager/android/password_manager_util_bridge.cc
index 1dfdf400..af8ceb1 100644
--- a/chrome/browser/password_manager/android/password_manager_util_bridge.cc
+++ b/chrome/browser/password_manager/android/password_manager_util_bridge.cc
@@ -21,10 +21,9 @@
 
 jboolean JNI_PasswordManagerUtilBridge_IsPasswordManagerAvailable(
     JNIEnv* env,
-    PrefService* pref_service,
     jboolean is_internal_backend_present) {
   return password_manager_android_util::IsPasswordManagerAvailable(
-      pref_service, is_internal_backend_present);
+      is_internal_backend_present);
 }
 
 namespace password_manager_android_util {
diff --git a/chrome/browser/password_manager/android/pwd_check_wrapper/java/src/org/chromium/chrome/browser/pwd_check_wrapper/FakePasswordCheckControllerFactory.java b/chrome/browser/password_manager/android/pwd_check_wrapper/java/src/org/chromium/chrome/browser/pwd_check_wrapper/FakePasswordCheckControllerFactory.java
index b079e77..fb6da2f 100644
--- a/chrome/browser/password_manager/android/pwd_check_wrapper/java/src/org/chromium/chrome/browser/pwd_check_wrapper/FakePasswordCheckControllerFactory.java
+++ b/chrome/browser/password_manager/android/pwd_check_wrapper/java/src/org/chromium/chrome/browser/pwd_check_wrapper/FakePasswordCheckControllerFactory.java
@@ -6,7 +6,6 @@
 
 import org.chromium.chrome.browser.password_manager.PasswordManagerHelper;
 import org.chromium.chrome.browser.password_manager.PasswordStoreBridge;
-import org.chromium.components.prefs.PrefService;
 import org.chromium.components.sync.SyncService;
 
 public class FakePasswordCheckControllerFactory extends PasswordCheckControllerFactory {
@@ -15,7 +14,6 @@
     @Override
     public PasswordCheckController create(
             SyncService syncService,
-            PrefService prefService,
             PasswordStoreBridge passwordStoreBridge,
             PasswordManagerHelper passwordManagerHelper) {
         mPasswordCheckController = new FakePasswordCheckController();
diff --git a/chrome/browser/password_manager/android/pwd_check_wrapper/java/src/org/chromium/chrome/browser/pwd_check_wrapper/GmsCorePasswordCheckControllerTest.java b/chrome/browser/password_manager/android/pwd_check_wrapper/java/src/org/chromium/chrome/browser/pwd_check_wrapper/GmsCorePasswordCheckControllerTest.java
index 6889887..47c018e 100644
--- a/chrome/browser/password_manager/android/pwd_check_wrapper/java/src/org/chromium/chrome/browser/pwd_check_wrapper/GmsCorePasswordCheckControllerTest.java
+++ b/chrome/browser/password_manager/android/pwd_check_wrapper/java/src/org/chromium/chrome/browser/pwd_check_wrapper/GmsCorePasswordCheckControllerTest.java
@@ -32,12 +32,9 @@
 import org.chromium.chrome.browser.pwd_check_wrapper.PasswordCheckController.PasswordCheckResult;
 import org.chromium.chrome.browser.pwd_check_wrapper.PasswordCheckController.PasswordStorageType;
 import org.chromium.chrome.browser.sync.SyncServiceFactory;
-import org.chromium.components.prefs.PrefService;
 import org.chromium.components.signin.base.CoreAccountInfo;
 import org.chromium.components.sync.SyncService;
 import org.chromium.components.sync.UserSelectableType;
-import org.chromium.components.user_prefs.UserPrefs;
-import org.chromium.components.user_prefs.UserPrefsJni;
 import org.chromium.google_apis.gaia.GaiaId;
 
 import java.util.OptionalInt;
@@ -57,8 +54,6 @@
     @Mock private SyncService mSyncService;
     @Mock private PasswordStoreBridge mPasswordStoreBridge;
     @Mock private Profile mProfile;
-    @Mock private UserPrefs.Natives mUserPrefsJniMock;
-    @Mock private PrefService mPrefService;
     @Mock private PasswordManagerUtilBridge.Natives mPasswordManagerUtilBridgeNativeMock;
     @Mock private PasswordManagerHelper.Natives mPasswordManagerHelperNativeMock;
     FakePasswordCheckupClientHelper mPasswordCheckupClientHelper;
@@ -67,7 +62,7 @@
 
     @Before
     public void setUp() {
-        setupUserProfileWithMockPrefService();
+        when(mProfile.getOriginalProfile()).thenReturn(mProfile);
         configureMockSyncServiceToSyncPasswords();
         configurePasswordManagerBackendSupport();
         setFakePasswordCheckupClientHelper();
@@ -81,7 +76,7 @@
     private void configurePasswordManagerBackendSupport() {
         PasswordManagerUtilBridgeJni.setInstanceForTesting(mPasswordManagerUtilBridgeNativeMock);
         PasswordManagerHelperJni.setInstanceForTesting(mPasswordManagerHelperNativeMock);
-        when(mPasswordManagerUtilBridgeNativeMock.isPasswordManagerAvailable(mPrefService, true))
+        when(mPasswordManagerUtilBridgeNativeMock.isPasswordManagerAvailable(true))
                 .thenReturn(true);
 
         FakePasswordManagerBackendSupportHelper helper =
@@ -110,12 +105,6 @@
         PasswordCheckupClientHelperFactory.setFactoryForTesting(passwordCheckupClientHelperFactory);
     }
 
-    private void setupUserProfileWithMockPrefService() {
-        when(mProfile.getOriginalProfile()).thenReturn(mProfile);
-        UserPrefsJni.setInstanceForTesting(mUserPrefsJniMock);
-        when(mUserPrefsJniMock.get(mProfile)).thenReturn(mPrefService);
-    }
-
     /**
      * The flow: checkPasswords is called -> as a result of password check 0 breached credentials
      * are obtained -> 10 passwords overall have been loaded.
diff --git a/chrome/browser/password_manager/android/pwd_check_wrapper/java/src/org/chromium/chrome/browser/pwd_check_wrapper/PasswordCheckControllerFactory.java b/chrome/browser/password_manager/android/pwd_check_wrapper/java/src/org/chromium/chrome/browser/pwd_check_wrapper/PasswordCheckControllerFactory.java
index c8549d8..22ed97b 100644
--- a/chrome/browser/password_manager/android/pwd_check_wrapper/java/src/org/chromium/chrome/browser/pwd_check_wrapper/PasswordCheckControllerFactory.java
+++ b/chrome/browser/password_manager/android/pwd_check_wrapper/java/src/org/chromium/chrome/browser/pwd_check_wrapper/PasswordCheckControllerFactory.java
@@ -9,20 +9,18 @@
 import org.chromium.chrome.browser.password_manager.PasswordManagerHelper;
 import org.chromium.chrome.browser.password_manager.PasswordManagerUtilBridge;
 import org.chromium.chrome.browser.password_manager.PasswordStoreBridge;
-import org.chromium.components.prefs.PrefService;
 import org.chromium.components.sync.SyncService;
 
 @NullMarked
 public class PasswordCheckControllerFactory {
     public PasswordCheckController create(
             @Nullable SyncService syncService,
-            PrefService prefService,
             PasswordStoreBridge passwordStoreBridge,
             PasswordManagerHelper passwordManagerHelper) {
         // This is only used by the old Safety Check, which is only opened from the PhishGuard
         // dialog and only if the phished credential is saved in both local and account stores.
         // This means that UPM is completely available.
-        assert PasswordManagerUtilBridge.isPasswordManagerAvailable(prefService);
+        assert PasswordManagerUtilBridge.isPasswordManagerAvailable();
         return new GmsCorePasswordCheckController(
                 syncService, passwordStoreBridge, passwordManagerHelper);
     }
diff --git a/chrome/browser/password_manager/factories/password_store_backend_factory.cc b/chrome/browser/password_manager/factories/password_store_backend_factory.cc
index 768d11c..6f864da 100644
--- a/chrome/browser/password_manager/factories/password_store_backend_factory.cc
+++ b/chrome/browser/password_manager/factories/password_store_backend_factory.cc
@@ -47,6 +47,7 @@
 using password_manager_android_util::PasswordManagerUtilBridge;
 #endif
 
+#if BUILDFLAG(USE_LOGIN_DATABASE_AS_BACKEND)
 std::unique_ptr<PasswordStoreBackend> CreateProfilePasswordStoreBuiltInBackend(
     const base::FilePath& login_db_directory,
     PrefService* prefs,
@@ -54,57 +55,35 @@
   std::unique_ptr<password_manager::LoginDatabase> login_db(
       password_manager::CreateLoginDatabaseForProfileStorage(login_db_directory,
                                                              prefs));
-#if BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC)
+#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC)
   password_manager::LoginDatabase* login_db_ptr = login_db.get();
-#endif  // BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC)
+#endif  // BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC)
   std::unique_ptr<PasswordStoreBackend> backend =
       std::make_unique<PasswordStoreBuiltInBackend>(
           std::move(login_db),
           syncer::WipeModelUponSyncDisabledBehavior::kNever, prefs,
           os_crypt_async);
 
-#if BUILDFLAG(IS_ANDROID)
-  // base::Unretained() is safe because `prefs` lives as long as the profile
-  // and IntermediateCallbackForSettingPrefs() ensures the callback isn't
-  // invoked if the profile is being destroyed.
-  auto is_profile_db_empty_cb =
-      base::BindPostTaskToCurrentDefault(base::BindRepeating(
-          &password_manager::IntermediateCallbackForSettingPrefs,
-          backend->AsWeakPtr(),
-          base::BindRepeating(
-              &PrefService::SetBoolean, base::Unretained(prefs),
-              password_manager::prefs::kEmptyProfileStoreLoginDatabase)));
-  login_db_ptr->SetIsEmptyCb(std::move(is_profile_db_empty_cb));
-#endif  // BUILDFLAG(IS_ANDROID)
-
 #if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC)
   base::FilePath user_data_dir;
   policy::path_parser::CheckUserDataDirPolicy(&user_data_dir);
   // If `user_data_dir` is empty it means that policy did not set it.
   login_db_ptr->SetIsUserDataDirPolicySet(!user_data_dir.empty());
-#endif
+#endif  //  BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC)
   return backend;
 }
-
-#if !BUILDFLAG(USE_LOGIN_DATABASE_AS_BACKEND)
-
+#else   // BUILDFLAG(USE_LOGIN_DATABASE_AS_BACKEND)
 // Creates the backend for the profile `PasswordStore` on Android, after
 // login db deprecation.
 std::unique_ptr<PasswordStoreBackend> CreateProfilePasswordStoreBackendAndroid(
     PrefService* prefs,
     const base::FilePath& login_db_directory,
     os_crypt_async::OSCryptAsync* os_crypt_async) {
-  if (!password_manager_android_util::LoginDbDeprecationReady(prefs)) {
-    // There are still passwords that need exporting, so instantiate the
-    // backend that connects to the login DB.
-    return CreateProfilePasswordStoreBuiltInBackend(login_db_directory, prefs,
-                                                    os_crypt_async);
-  }
   // Once the login DB is deprecated, there are only 2 options for
   // the backend: an empty one if the Android backend isn't supported,
   // or the Android backend.
   if (password_manager_android_util::IsPasswordManagerAvailable(
-          prefs, std::make_unique<PasswordManagerUtilBridge>())) {
+          std::make_unique<PasswordManagerUtilBridge>())) {
     return std::make_unique<
         password_manager::PasswordStoreAndroidLocalBackend>();
   }
@@ -119,15 +98,14 @@
   // The account store shouldn't have an associated login DB with existing
   // passwords, so no pre-export step is required.
   if (password_manager_android_util::IsPasswordManagerAvailable(
-          prefs, std::make_unique<PasswordManagerUtilBridge>())) {
+          std::make_unique<PasswordManagerUtilBridge>())) {
     return std::make_unique<
         password_manager::PasswordStoreAndroidAccountBackend>(
         password_manager::kAccountStore);
   }
   return std::make_unique<password_manager::PasswordStoreEmptyBackend>();
 }
-
-#endif  // !BUILDFLAG(USE_LOGIN_DATABASE_AS_BACKEND)
+#endif  // BUILDFLAG(USE_LOGIN_DATABASE_AS_BACKEND)
 }  // namespace
 
 std::unique_ptr<PasswordStoreBackend> CreateProfilePasswordStoreBackend(
diff --git a/chrome/browser/password_manager/password_manager_settings_service_factory.cc b/chrome/browser/password_manager/password_manager_settings_service_factory.cc
index aa47f80..27b4691 100644
--- a/chrome/browser/password_manager/password_manager_settings_service_factory.cc
+++ b/chrome/browser/password_manager/password_manager_settings_service_factory.cc
@@ -84,11 +84,7 @@
 std::unique_ptr<password_manager::PasswordManagerSettingsService>
 PasswordManagerSettingsServiceFactory::CreateService(Profile* profile) const {
 #if BUILDFLAG(IS_ANDROID)
-  // For the first run after the feature is enabled, before the unmigrated
-  // passwords are exported, `IsPasswordManagerAvailable` can return false.
-  // However, password saving isn't possible in that run anyway.
   if (password_manager_android_util::IsPasswordManagerAvailable(
-          profile->GetPrefs(),
           std::make_unique<
               password_manager_android_util::PasswordManagerUtilBridge>())) {
     return std::make_unique<PasswordManagerSettingsServiceAndroidImpl>(
diff --git a/chrome/browser/password_manager/profile_password_store_factory.cc b/chrome/browser/password_manager/profile_password_store_factory.cc
index 18aed654..354c6b6 100644
--- a/chrome/browser/password_manager/profile_password_store_factory.cc
+++ b/chrome/browser/password_manager/profile_password_store_factory.cc
@@ -30,7 +30,6 @@
 #include "services/network/public/cpp/shared_url_loader_factory.h"
 
 #if BUILDFLAG(IS_ANDROID)
-#include "chrome/browser/password_manager/android/login_db_deprecation_runner_factory.h"
 #include "chrome/browser/password_manager/android/password_manager_util_bridge.h"
 #endif  // BUILDFLAG(IS_ANDROID)
 
@@ -91,14 +90,6 @@
   affiliation_service->RegisterSource(std::move(password_affiliation_adapter));
 #endif
 
-#if BUILDFLAG(IS_ANDROID) && !BUILDFLAG(USE_LOGIN_DATABASE_AS_BACKEND)
-  CHECK(util_bridge.IsInternalBackendPresent());
-  password_manager::LoginDbDeprecationRunner* login_db_deprecation_runner =
-      LoginDbDeprecationRunnerFactory::GetForProfile(profile);
-  if (login_db_deprecation_runner) {
-    login_db_deprecation_runner->StartExportWithDelay(ps);
-  }
-#endif
   DelayReportingPasswordStoreMetrics(profile);
 
   return ps;
@@ -142,9 +133,6 @@
               .Build()) {
   DependsOn(AffiliationServiceFactory::GetInstance());
   DependsOn(CredentialsCleanerRunnerFactory::GetInstance());
-#if BUILDFLAG(IS_ANDROID)
-  DependsOn(LoginDbDeprecationRunnerFactory::GetInstance());
-#endif
 }
 
 ProfilePasswordStoreFactory::~ProfilePasswordStoreFactory() = default;
diff --git a/chrome/browser/prefs/browser_prefs.cc b/chrome/browser/prefs/browser_prefs.cc
index 581384fc..8305005 100644
--- a/chrome/browser/prefs/browser_prefs.cc
+++ b/chrome/browser/prefs/browser_prefs.cc
@@ -1143,6 +1143,16 @@
     "ash.desks.desks_lacros_profile_id_list";
 #endif  // BUILDFLAG(IS_CHROMEOS)
 
+#if BUILDFLAG(IS_ANDROID)
+// Deprecated 09/2025.
+constexpr char kObsoleteUpmUnmigratedPasswordsExported[] =
+    "profile.upm_unmigrated_passwords_exported";
+constexpr char kObsoletePasswordsUseUPMLocalAndSeparateStores[] =
+    "passwords_use_upm_local_and_separate_stores";
+constexpr char kObsoleteEmptyProfileStoreLoginDatabase[] =
+    "password_manager.empty_profile_store_login_database";
+#endif  // BUILDFLAG(IS_ANDROID)
+
 // Register local state used only for migration (clearing or moving to a new
 // key).
 void RegisterLocalStatePrefsForMigration(PrefRegistrySimple* registry) {
@@ -1648,6 +1658,14 @@
   // Deprecated 08/2025.
   registry->RegisterListPref(kDesksLacrosProfileIdList);
 #endif  // BUILDFLAG(IS_CHROMEOS)
+
+#if BUILDFLAG(IS_ANDROID)
+  // Deprecated 09/2025.
+  registry->RegisterBooleanPref(kObsoleteUpmUnmigratedPasswordsExported, false);
+  registry->RegisterIntegerPref(kObsoletePasswordsUseUPMLocalAndSeparateStores,
+                                0);
+  registry->RegisterBooleanPref(kObsoleteEmptyProfileStoreLoginDatabase, false);
+#endif  // BUILDFLAG(IS_ANDROID)
 }
 
 }  // namespace
@@ -2993,6 +3011,13 @@
   profile_prefs->ClearPref(kDesksLacrosProfileIdList);
 #endif  // BUILDFLAG(IS_CHROMEOS)
 
+#if BUILDFLAG(IS_ANDROID)
+  // Added 09/2025.
+  profile_prefs->ClearPref(kObsoleteUpmUnmigratedPasswordsExported);
+  profile_prefs->ClearPref(kObsoletePasswordsUseUPMLocalAndSeparateStores);
+  profile_prefs->ClearPref(kObsoleteEmptyProfileStoreLoginDatabase);
+#endif  // BUILDFLAG(IS_ANDROID)
+
   // Please don't delete the following line. It is used by PRESUBMIT.py.
   // END_MIGRATE_OBSOLETE_PROFILE_PREFS
 
diff --git a/chrome/browser/profiles/keep_alive/scoped_profile_keep_alive.cc b/chrome/browser/profiles/keep_alive/scoped_profile_keep_alive.cc
index a37cef1a..8c5e41a 100644
--- a/chrome/browser/profiles/keep_alive/scoped_profile_keep_alive.cc
+++ b/chrome/browser/profiles/keep_alive/scoped_profile_keep_alive.cc
@@ -27,7 +27,7 @@
 
 }  // namespace
 
-ScopedProfileKeepAlive::ScopedProfileKeepAlive(const Profile* profile,
+ScopedProfileKeepAlive::ScopedProfileKeepAlive(Profile* profile,
                                                ProfileKeepAliveOrigin origin)
     : profile_(profile->GetWeakPtr()), origin_(origin) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
@@ -65,7 +65,7 @@
 
 // static
 void ScopedProfileKeepAlive::RemoveKeepAliveOnUIThread(
-    base::WeakPtr<const Profile> profile,
+    base::WeakPtr<Profile> profile,
     ProfileKeepAliveOrigin origin) {
   SCOPED_CRASH_KEY_STRING32("ProfileKeepAlive", "origin",
                             GetOriginName(origin));
diff --git a/chrome/browser/profiles/keep_alive/scoped_profile_keep_alive.h b/chrome/browser/profiles/keep_alive/scoped_profile_keep_alive.h
index 9ec47174..2e75741 100644
--- a/chrome/browser/profiles/keep_alive/scoped_profile_keep_alive.h
+++ b/chrome/browser/profiles/keep_alive/scoped_profile_keep_alive.h
@@ -25,22 +25,22 @@
 // off-the-record Profile triggers a DCHECK.
 class ScopedProfileKeepAlive {
  public:
-  ScopedProfileKeepAlive(const Profile* profile, ProfileKeepAliveOrigin origin);
+  ScopedProfileKeepAlive(Profile* profile, ProfileKeepAliveOrigin origin);
   ~ScopedProfileKeepAlive();
 
   ScopedProfileKeepAlive(const ScopedProfileKeepAlive&) = delete;
   ScopedProfileKeepAlive& operator=(const ScopedProfileKeepAlive&) = delete;
 
-  const Profile* profile() { return profile_.get(); }
+  Profile* profile() { return profile_.get(); }
   ProfileKeepAliveOrigin origin() { return origin_; }
 
  private:
   // Called after the ScopedProfileKeepAlive has been deleted, so this is a
   // static method where we pass parameters manually.
-  static void RemoveKeepAliveOnUIThread(base::WeakPtr<const Profile> profile,
+  static void RemoveKeepAliveOnUIThread(base::WeakPtr<Profile> profile,
                                         ProfileKeepAliveOrigin origin);
 
-  const base::WeakPtr<const Profile> profile_;
+  const base::WeakPtr<Profile> profile_;
   const ProfileKeepAliveOrigin origin_;
 };
 
diff --git a/chrome/browser/profiles/profile.cc b/chrome/browser/profiles/profile.cc
index f5e973b..546c9c27 100644
--- a/chrome/browser/profiles/profile.cc
+++ b/chrome/browser/profiles/profile.cc
@@ -593,10 +593,6 @@
   return chrome_variations_client_.get();
 }
 
-base::WeakPtr<const Profile> Profile::GetWeakPtr() const {
-  return weak_factory_.GetWeakPtr();
-}
-
 base::WeakPtr<Profile> Profile::GetWeakPtr() {
   return weak_factory_.GetWeakPtr();
 }
diff --git a/chrome/browser/profiles/profile.h b/chrome/browser/profiles/profile.h
index 25c4d29..806ecfcc 100644
--- a/chrome/browser/profiles/profile.h
+++ b/chrome/browser/profiles/profile.h
@@ -477,7 +477,6 @@
 
   virtual void RecordPrimaryMainFrameNavigation() = 0;
 
-  base::WeakPtr<const Profile> GetWeakPtr() const;
   base::WeakPtr<Profile> GetWeakPtr();
 
   // Experimental getters/setters to gauge the performance of caching
diff --git a/chrome/browser/profiles/profile_manager.cc b/chrome/browser/profiles/profile_manager.cc
index ebefa6a0..60faa5d 100644
--- a/chrome/browser/profiles/profile_manager.cc
+++ b/chrome/browser/profiles/profile_manager.cc
@@ -1214,7 +1214,7 @@
   return Profile::CreateProfile(path, this, Profile::CreateMode::kAsynchronous);
 }
 
-bool ProfileManager::HasKeepAliveForTesting(const Profile* profile,
+bool ProfileManager::HasKeepAliveForTesting(Profile* profile,
                                             ProfileKeepAliveOrigin origin) {
   DCHECK(profile);
   ProfileInfo* info = GetProfileInfoByPath(profile->GetPath());
@@ -1243,7 +1243,7 @@
   base::UmaHistogramCounts100("Profile.ZombieProfileCount", zombie_count);
 }
 
-void ProfileManager::AddKeepAlive(const Profile* profile,
+void ProfileManager::AddKeepAlive(Profile* profile,
                                   ProfileKeepAliveOrigin origin) {
   DCHECK_NE(ProfileKeepAliveOrigin::kWaitingForFirstBrowserWindow, origin);
 
@@ -1286,7 +1286,7 @@
   }
 }
 
-void ProfileManager::RemoveKeepAlive(const Profile* profile,
+void ProfileManager::RemoveKeepAlive(Profile* profile,
                                      ProfileKeepAliveOrigin origin) {
   DCHECK_NE(ProfileKeepAliveOrigin::kWaitingForFirstBrowserWindow, origin);
 
@@ -1340,7 +1340,7 @@
   UnloadProfileIfNoKeepAlive(info);
 }
 
-void ProfileManager::ClearFirstBrowserWindowKeepAlive(const Profile* profile) {
+void ProfileManager::ClearFirstBrowserWindowKeepAlive(Profile* profile) {
   CHECK(profile);
   CHECK(!profile->IsOffTheRecord());
 
diff --git a/chrome/browser/profiles/profile_manager.h b/chrome/browser/profiles/profile_manager.h
index 364e68d..7f7a93d 100644
--- a/chrome/browser/profiles/profile_manager.h
+++ b/chrome/browser/profiles/profile_manager.h
@@ -310,8 +310,7 @@
 
   // Used for testing. Returns true if |profile| has at least one ref of type
   // |origin|.
-  bool HasKeepAliveForTesting(const Profile* profile,
-                              ProfileKeepAliveOrigin origin);
+  bool HasKeepAliveForTesting(Profile* profile, ProfileKeepAliveOrigin origin);
 
   // Disables the periodic reporting of profile metrics, as this is causing
   // tests to time out.
@@ -338,7 +337,7 @@
   // Removes the kWaitingForFirstBrowserWindow keepalive. This allows a
   // Profile* to be deleted from now on, even if it never had a visible
   // browser window.
-  void ClearFirstBrowserWindowKeepAlive(const Profile* profile);
+  void ClearFirstBrowserWindowKeepAlive(Profile* profile);
 
   // Returns whether |path| is allowed for profile creation.
   bool IsAllowedProfilePath(const base::FilePath& path) const;
@@ -442,8 +441,8 @@
 
   // Increments/decrements the refcount on a |profile|. (it must not be an
   // off-the-record profile)
-  void AddKeepAlive(const Profile* profile, ProfileKeepAliveOrigin origin);
-  void RemoveKeepAlive(const Profile* profile, ProfileKeepAliveOrigin origin);
+  void AddKeepAlive(Profile* profile, ProfileKeepAliveOrigin origin);
+  void RemoveKeepAlive(Profile* profile, ProfileKeepAliveOrigin origin);
 
   void RecordZombieMetrics();
 
diff --git a/chrome/browser/resources/autofill_ml_internals/log_details.css b/chrome/browser/resources/autofill_ml_internals/log_details.css
index e3c6a79b..1b44e337 100644
--- a/chrome/browser/resources/autofill_ml_internals/log_details.css
+++ b/chrome/browser/resources/autofill_ml_internals/log_details.css
@@ -26,7 +26,6 @@
   border-radius: 4px;
   padding: 8px;
   margin-bottom: 16px;
-  display: flex;
   gap: 8px;
 }
 
@@ -34,6 +33,12 @@
   width: 300px;
 }
 
+.field-details-row {
+  display: flex;
+  gap: 8px;
+  margin-bottom: 8px;
+}
+
 .histogram {
   width: 500px;
 }
diff --git a/chrome/browser/resources/autofill_ml_internals/log_details.html.ts b/chrome/browser/resources/autofill_ml_internals/log_details.html.ts
index 982e3c4..54644da 100644
--- a/chrome/browser/resources/autofill_ml_internals/log_details.html.ts
+++ b/chrome/browser/resources/autofill_ml_internals/log_details.html.ts
@@ -32,46 +32,54 @@
     <h3>Fields</h3>
     ${this.log.fieldPredictions.map((field: MlFieldPredictionLog) => html`
       <div class="field">
-        <div class="field-properties">
-          <div class="row">
-            <span class="label">Label</span>
-            <span>${field.label}</span>
+        <div class="field-details-row">
+          <div class="field-properties">
+            <div class="row">
+              <span class="label">Label</span>
+              <span>${field.label}</span>
+            </div>
+            <div class="row">
+              <span class="label">Placeholder</span>
+              <span>${field.placeholder}</span>
+            </div>
+            <div class="row">
+              <span class="label">Name</span>
+              <span>${field.name}</span>
+            </div>
+            <div class="row">
+              <span class="label">ID</span>
+              <span>${field.id}</span>
+            </div>
+            <div class="row">
+              <span class="label">Autocomplete</span>
+              <span>${field.autocomplete}</span>
+            </div>
+            <div class="row">
+              <span class="label">Form Control Type</span>
+              <span>${field.formControlType}</span>
+            </div>
           </div>
-          <div class="row">
-            <span class="label">Placeholder</span>
-            <span>${field.placeholder}</span>
-          </div>
-          <div class="row">
-            <span class="label">Name</span>
-            <span>${field.name}</span>
-          </div>
-          <div class="row">
-            <span class="label">ID</span>
-            <span>${field.id}</span>
-          </div>
-          <div class="row">
-            <span class="label">Autocomplete</span>
-            <span>${field.autocomplete}</span>
-          </div>
-          <div class="row">
-            <span class="label">Form Control Type</span>
-            <span>${field.formControlType}</span>
+          <div class="histogram">
+            ${this.getTopPredictions_(field.probabilities).map(p => html`
+              <div class="histogram-row">
+                <div class="histogram-label" title="${p.type}">
+                  ${p.type}
+                </div>
+                <div class="histogram-bar-container">
+                  <div class="histogram-bar"
+                      style="width: ${p.percentage}">
+                  </div>
+                </div>
+                <div class="histogram-value">${p.percentage}</div>
+              </div>
+            `)}
           </div>
         </div>
-        <div class="histogram">
-          ${this.getTopPredictions_(field.probabilities).map(prediction => html`
-            <div class="histogram-row">
-              <div class="histogram-label" title="${prediction.type}">
-                ${prediction.type}
-              </div>
-              <div class="histogram-bar-container">
-                <div class="histogram-bar"
-                    style="width: ${prediction.percentage}">
-                </div>
-              </div>
-              <div class="histogram-value">${prediction.percentage}</div>
-            </div>
-          `)}
+        <div class="row">
+          <span class="label">Tokens</span>
+          <span>
+            ${this.formatFieldTokens_(field.tokenizedFieldRepresentation)}
+          </span>
         </div>
       `)}
     </div>
diff --git a/chrome/browser/resources/autofill_ml_internals/log_details.ts b/chrome/browser/resources/autofill_ml_internals/log_details.ts
index a0eb362..cd18379 100644
--- a/chrome/browser/resources/autofill_ml_internals/log_details.ts
+++ b/chrome/browser/resources/autofill_ml_internals/log_details.ts
@@ -68,6 +68,10 @@
         .sort((a, b) => b.probability - a.probability)
         .slice(0, LogDetailsElement.TOP_PREDICTIONS_COUNT);
   }
+
+  protected formatFieldTokens_(tokens: string[]): string {
+    return tokens.join(' / ');
+  }
 }
 
 declare global {
diff --git a/chrome/browser/resources/history/BUILD.gn b/chrome/browser/resources/history/BUILD.gn
index 7404dd3..8f86b23 100644
--- a/chrome/browser/resources/history/BUILD.gn
+++ b/chrome/browser/resources/history/BUILD.gn
@@ -23,27 +23,31 @@
     "images/avatar_surrounding_illustration_dark.svg",
   ]
 
-  # Files holding a Polymer element definition and have an equivalent .html file.
-  web_component_files = [
-    "app.ts",
-    "history_embeddings_promo.ts",
-    "history_item.ts",
-    "history_list.ts",
-    "history_toolbar.ts",
-    "side_bar.ts",
-    "synced_device_card.ts",
-    "synced_device_manager.ts",
-  ]
-
   ts_files = [
+    "app.html.ts",
+    "app.ts",
     "browser_service.ts",
     "constants.ts",
     "externs.ts",
     "history.ts",
+    "history_embeddings_promo.html.ts",
+    "history_embeddings_promo.ts",
+    "history_item.html.ts",
+    "history_item.ts",
+    "history_list.html.ts",
+    "history_list.ts",
+    "history_toolbar.html.ts",
+    "history_toolbar.ts",
     "lazy_load.ts",
     "query_manager.ts",
     "router.ts",
     "searched_label.ts",
+    "side_bar.html.ts",
+    "side_bar.ts",
+    "synced_device_card.html.ts",
+    "synced_device_card.ts",
+    "synced_device_manager.html.ts",
+    "synced_device_manager.ts",
   ]
 
   # Files that are passed as input to css_to_wrapper().
diff --git a/chrome/browser/resources/history/app.html b/chrome/browser/resources/history/app.html.ts
similarity index 93%
rename from chrome/browser/resources/history/app.html
rename to chrome/browser/resources/history/app.html.ts
index 1552fc9..ab3721d 100644
--- a/chrome/browser/resources/history/app.html
+++ b/chrome/browser/resources/history/app.html.ts
@@ -1,4 +1,14 @@
+// Copyright 2025 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
 
+import {html} from '//resources/lit/v3_0/lit.rollup.js';
+
+import type {HistoryAppElement} from './app.js';
+
+export function getHtml(this: HistoryAppElement) {
+  // clang-format off
+  return html`<!--_html_template_start_-->
     <history-query-manager
         .queryResult="${this.queryResult_}"
         @query-finished="${this.onQueryFinished_}"
@@ -101,7 +111,7 @@
                   .scrollTarget="${this.scrollTarget_}"
                   .scrollOffset="${this.tabContentScrollOffset_}">
               </history-list>
-             ${this.historyClustersSelected_() ? html`
+              ${this.historyClustersSelected_() ? html`
                 <history-clusters id="history-clusters"
                     ?is-active="${this.getShowResultsByGroup_()}"
                     .query="${this.queryState_.searchTerm}"
@@ -139,3 +149,6 @@
         </history-side-bar>
       </cr-drawer>`}'>
     </cr-lazy-render-lit>
+<!--_html_template_end_-->`;
+  // clang-format on
+}
diff --git a/chrome/browser/resources/history/app.ts b/chrome/browser/resources/history/app.ts
index 1e4c6602..e1f67f4 100644
--- a/chrome/browser/resources/history/app.ts
+++ b/chrome/browser/resources/history/app.ts
@@ -872,9 +872,6 @@
   }
 }
 
-// Exported to be used in the autogenerated Lit template file
-export type AppElement = HistoryAppElement;
-
 declare global {
   interface HTMLElementTagNameMap {
     'history-app': HistoryAppElement;
diff --git a/chrome/browser/resources/history/history_embeddings_promo.html b/chrome/browser/resources/history/history_embeddings_promo.html.ts
similarity index 70%
rename from chrome/browser/resources/history/history_embeddings_promo.html
rename to chrome/browser/resources/history/history_embeddings_promo.html.ts
index 21d38c7..1948b83 100644
--- a/chrome/browser/resources/history/history_embeddings_promo.html
+++ b/chrome/browser/resources/history/history_embeddings_promo.html.ts
@@ -1,5 +1,14 @@
+// Copyright 2025 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
 
+import {html} from '//resources/lit/v3_0/lit.rollup.js';
 
+import type {HistoryEmbeddingsPromoElement} from './history_embeddings_promo.js';
+
+export function getHtml(this: HistoryEmbeddingsPromoElement) {
+  // clang-format off
+  return html`<!--_html_template_start_-->
 <div id="promo" role="dialog" aria-label="$i18n{historyEmbeddingsPromoLabel}"
     ?hidden="${!this.shown_}">
   <cr-icon-button id="close" iron-icon="cr:close"
@@ -31,3 +40,6 @@
     </div>
   </div>
 </div>
+<!--_html_template_end_-->`;
+  // clang-format on
+}
diff --git a/chrome/browser/resources/history/history_item.html b/chrome/browser/resources/history/history_item.html.ts
similarity index 90%
rename from chrome/browser/resources/history/history_item.html
rename to chrome/browser/resources/history/history_item.html.ts
index bb84668..ce6a732e 100644
--- a/chrome/browser/resources/history/history_item.html
+++ b/chrome/browser/resources/history/history_item.html.ts
@@ -1,3 +1,14 @@
+// Copyright 2025 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import {html} from '//resources/lit/v3_0/lit.rollup.js';
+
+import type {HistoryItemElement} from './history_item.js';
+
+export function getHtml(this: HistoryItemElement) {
+  // clang-format off
+  return html`<!--_html_template_start_-->
     <div id="main-container">
       <div id="background-clip" aria-hidden="true">
         <div id="background"></div>
@@ -88,3 +99,6 @@
         <div id="time-gap-separator" ?hidden="${!this.hasTimeGap}"></div>
       </div>
     </div>
+<!--_html_template_end_-->`;
+  // clang-format on
+}
diff --git a/chrome/browser/resources/history/history_list.html b/chrome/browser/resources/history/history_list.html.ts
similarity index 86%
rename from chrome/browser/resources/history/history_list.html
rename to chrome/browser/resources/history/history_list.html.ts
index 3cedc81..742f790d 100644
--- a/chrome/browser/resources/history/history_list.html
+++ b/chrome/browser/resources/history/history_list.html.ts
@@ -1,3 +1,14 @@
+// Copyright 2025 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import {html} from '//resources/lit/v3_0/lit.rollup.js';
+
+import type {HistoryListElement} from './history_list.js';
+
+export function getHtml(this: HistoryListElement) {
+  // clang-format off
+  return html`<!--_html_template_start_-->
     <div id="noResults" class="centered-message"
         ?hidden="${this.hasResults_()}">
       ${this.noResultsMessage_()}
@@ -63,4 +74,7 @@
             $i18n{removeBookmark}
           </button>
         </cr-action-menu>`}'>
-      </cr-lazy-render-lit>
+    </cr-lazy-render-lit>
+<!--_html_template_end_-->`;
+  // clang-format on
+}
diff --git a/chrome/browser/resources/history/history_toolbar.html b/chrome/browser/resources/history/history_toolbar.html.ts
similarity index 71%
rename from chrome/browser/resources/history/history_toolbar.html
rename to chrome/browser/resources/history/history_toolbar.html.ts
index fadb3ee..43e543a 100644
--- a/chrome/browser/resources/history/history_toolbar.html
+++ b/chrome/browser/resources/history/history_toolbar.html.ts
@@ -1,3 +1,14 @@
+// Copyright 2025 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import {html} from '//resources/lit/v3_0/lit.rollup.js';
+
+import type {HistoryToolbarElement} from './history_toolbar.js';
+
+export function getHtml(this: HistoryToolbarElement) {
+  // clang-format off
+  return html`<!--_html_template_start_-->
 <cr-toolbar id="mainToolbar"
     disable-right-content-grow
     ?has-overlay="${this.itemsSelected_}"
@@ -29,3 +40,6 @@
   </cr-button>
 
 </cr-toolbar-selection-overlay>
+<!--_html_template_end_-->`;
+  // clang-format on
+}
diff --git a/chrome/browser/resources/history/side_bar.html b/chrome/browser/resources/history/side_bar.html.ts
similarity index 80%
rename from chrome/browser/resources/history/side_bar.html
rename to chrome/browser/resources/history/side_bar.html.ts
index f877875..40aebfd9 100644
--- a/chrome/browser/resources/history/side_bar.html
+++ b/chrome/browser/resources/history/side_bar.html.ts
@@ -1,3 +1,14 @@
+// Copyright 2025 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import {html} from '//resources/lit/v3_0/lit.rollup.js';
+
+import type {HistorySideBarElement} from './side_bar.js';
+
+export function getHtml(this: HistorySideBarElement) {
+  // clang-format off
+  return html`<!--_html_template_start_-->
 <cr-menu-selector id="menu" selected="${this.selectedPage}"
     @selected-changed="${this.onSelectorSelectedChanged_}"
     selectable=".page-item" attr-for-selected="path"
@@ -43,3 +54,6 @@
     <div ?hidden="${!this.showGMAAndGAA_}">$i18nRaw{sidebarFooterGMAAndGAA}</div>
   </div>
 </div>
+<!--_html_template_end_-->`;
+  // clang-format on
+}
diff --git a/chrome/browser/resources/history/side_bar.ts b/chrome/browser/resources/history/side_bar.ts
index 7dd5ac8..1aebcba 100644
--- a/chrome/browser/resources/history/side_bar.ts
+++ b/chrome/browser/resources/history/side_bar.ts
@@ -241,9 +241,6 @@
   }
 }
 
-// Exported to be used in the autogenerated Lit template file
-export type SideBarElement = HistorySideBarElement;
-
 declare global {
   interface HTMLElementTagNameMap {
     'history-side-bar': HistorySideBarElement;
diff --git a/chrome/browser/resources/history/synced_device_card.html b/chrome/browser/resources/history/synced_device_card.html.ts
similarity index 77%
rename from chrome/browser/resources/history/synced_device_card.html
rename to chrome/browser/resources/history/synced_device_card.html.ts
index 15a7d8b..6756ae09 100644
--- a/chrome/browser/resources/history/synced_device_card.html
+++ b/chrome/browser/resources/history/synced_device_card.html.ts
@@ -1,3 +1,14 @@
+// Copyright 2025 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import {html} from '//resources/lit/v3_0/lit.rollup.js';
+
+import type {HistorySyncedDeviceCardElement} from './synced_device_card.js';
+
+export function getHtml(this: HistorySyncedDeviceCardElement) {
+  // clang-format off
+  return html`<!--_html_template_start_-->
 <div id="history-item-container">
   <div class="card-title" id="card-heading" aria-expanded="${this.opened}"
       aria-controls="collapse" @click="${this.toggleTabCard}">
@@ -40,3 +51,6 @@
     </div>
   </cr-collapse>
 </div>
+<!--_html_template_end_-->`;
+  // clang-format on
+}
diff --git a/chrome/browser/resources/history/synced_device_manager.html b/chrome/browser/resources/history/synced_device_manager.html.ts
similarity index 84%
rename from chrome/browser/resources/history/synced_device_manager.html
rename to chrome/browser/resources/history/synced_device_manager.html.ts
index 78c196e..ce340126 100644
--- a/chrome/browser/resources/history/synced_device_manager.html
+++ b/chrome/browser/resources/history/synced_device_manager.html.ts
@@ -1,3 +1,14 @@
+// Copyright 2025 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import {html} from '//resources/lit/v3_0/lit.rollup.js';
+
+import type {HistorySyncedDeviceManagerElement} from './synced_device_manager.js';
+
+export function getHtml(this: HistorySyncedDeviceManagerElement) {
+  // clang-format off
+  return html`<!--_html_template_start_-->
 <div id="synced-device-list" class="history-cards"
     ?hidden="${!this.syncedDevices_.length}">
   ${this.syncedDevices_.map((syncedDevice, index) => html`
@@ -70,3 +81,6 @@
   </cr-action-menu>
 `}'>
 </cr-lazy-render-lit>
+<!--_html_template_end_-->`;
+  // clang-format on
+}
diff --git a/chrome/browser/resources/settings/privacy_page/privacy_page.html b/chrome/browser/resources/settings/privacy_page/privacy_page.html
index ad1e708..e8e57eac 100644
--- a/chrome/browser/resources/settings/privacy_page/privacy_page.html
+++ b/chrome/browser/resources/settings/privacy_page/privacy_page.html
@@ -701,94 +701,7 @@
           </settings-subpage>
        </template>
       </template>
-      <template is="dom-if" if="[[enableExperimentalWebPlatformFeatures_]]">
-        <template is="dom-if" route-path="/content/bluetoothScanning" no-search>
-          <settings-subpage page-title="$i18n{siteSettingsBluetoothScanning}"
-              search-label="$i18n{siteSettingsAllSitesSearch}"
-              search-term="{{searchFilter_}}">
-            <settings-bluetooth-scanning-page search-term="[[searchFilter_]]">
-            </settings-bluetooth-scanning-page>
-          </settings-subpage>
-        </template>
-      </template>
-      <template is="dom-if" route-path="/content/vr" no-search>
-        <settings-subpage page-title="$i18n{siteSettingsVr}"
-            search-label="$i18n{siteSettingsAllSitesSearch}"
-            search-term="{{searchFilter_}}">
-          <settings-vr-page search-term="[[searchFilter_]]"></settings-vr-page>
-        </settings-subpage>
-      </template>
-      <template is="dom-if" route-path="/content/ar" no-search>
-        <settings-subpage page-title="$i18n{siteSettingsAr}"
-            search-label="$i18n{siteSettingsAllSitesSearch}"
-            search-term="{{searchFilter_}}">
-          <settings-ar-page search-term="[[searchFilter_]]"></settings-ar-page>
-        </settings-subpage>
-      </template>
-      <template is="dom-if" if="[[enableHandTrackingContentSetting_]]">
-        <template is="dom-if" route-path="/content/handTracking" no-search>
-        <settings-subpage page-title="$i18n{siteSettingsHandTracking}"
-            search-label="$i18n{siteSettingsAllSitesSearch}"
-            search-term="{{searchFilter_}}">
-          <settings-hand-tracking-page search-term="[[searchFilter_]]">
-          </settings-hand-tracking-page>
-        </settings-subpage>
-      </template>
-      </template>
-      <template is="dom-if" route-path="/content/idleDetection" no-search>
-        <settings-subpage page-title="$i18n{siteSettingsIdleDetection}"
-            search-label="$i18n{siteSettingsAllSitesSearch}"
-            search-term="{{searchFilter_}}">
-          <settings-idle-detection-page search-term="[[searchFilter_]]">
-          </settings-idle-detection-page>
-        </settings-subpage>
-      </template>
-      <template is="dom-if" route-path="/content/windowManagement" no-search>
-        <settings-subpage page-title="$i18n{siteSettingsWindowManagement}"
-            search-label="$i18n{siteSettingsAllSitesSearch}"
-            search-term="{{searchFilter_}}">
-          <settings-window-management-page search-term="[[searchFilter_]]">
-          </settings-window-management-page>
-        </settings-subpage>
-      </template>
-      <template is="dom-if" route-path="/content/localFonts" no-search>
-        <settings-subpage page-title="$i18n{fonts}"
-            search-label="$i18n{siteSettingsAllSitesSearch}"
-            search-term="{{searchFilter_}}">
-          <settings-local-fonts-page search-term="[[searchFilter_]]">
-          </settings-local-fonts-page>
-        </settings-subpage>
-      </template>
-      <template is="dom-if" route-path="/content/storageAccess" no-search>
-        <settings-subpage page-title="$i18n{siteSettingsStorageAccess}"
-            search-label="$i18n{siteSettingsAllSitesSearch}"
-            search-term="{{searchFilter_}}">
-          <settings-storage-access-page search-term="[[searchFilter_]]">
-          </settings-storage-access-page>
-        </settings-subpage>
-      </template>
-      <template is="dom-if" if="[[autoPictureInPictureEnabled_]]">
-        <template is="dom-if" route-path="/content/autoPictureInPicture" no-search>
-          <settings-subpage page-title="$i18n{siteSettingsAutoPictureInPicture}"
-              search-label="$i18n{siteSettingsAllSitesSearch}"
-              search-term="{{searchFilter_}}">
-            <settings-auto-picture-in-picture-page
-                search-term="[[searchFilter_]]">
-            </settings-auto-picture-in-picture-page>
-          </settings-subpage>
-       </template>
-      </template>
-      <template is="dom-if" if="[[capturedSurfaceControlEnabled_]]">
-        <template is="dom-if" route-path="/content/capturedSurfaceControl" no-search>
-          <settings-subpage page-title="$i18n{siteSettingsCapturedSurfaceControl}"
-              search-label="$i18n{siteSettingsAllSitesSearch}"
-              search-term="{{searchFilter_}}">
-            <settings-captured-surface-control-page
-                search-term="[[searchFilter_]]">
-            </settings-captured-surface-control-page>
-          </settings-subpage>
-       </template>
-      </template>
+
       <if expr="is_chromeos">
         <template is="dom-if" if="[[enableSmartCardReadersContentSetting_]]">
           <template is="dom-if"
diff --git a/chrome/browser/resources/settings/privacy_page/privacy_page.ts b/chrome/browser/resources/settings/privacy_page/privacy_page.ts
index b45a26e..702382ac 100644
--- a/chrome/browser/resources/settings/privacy_page/privacy_page.ts
+++ b/chrome/browser/resources/settings/privacy_page/privacy_page.ts
@@ -126,13 +126,6 @@
         },
       },
 
-      enableHandTrackingContentSetting_: {
-        type: Boolean,
-        value() {
-          return loadTimeData.getBoolean('enableHandTrackingContentSetting');
-        },
-      },
-
       enableFederatedIdentityApiContentSetting_: {
         type: Boolean,
         value() {
@@ -181,16 +174,6 @@
             loadTimeData.getBoolean('isPrivacySandboxRestrictedNoticeEnabled'),
       },
 
-      autoPictureInPictureEnabled_: {
-        type: Boolean,
-        value: () => loadTimeData.getBoolean('autoPictureInPictureEnabled'),
-      },
-
-      capturedSurfaceControlEnabled_: {
-        type: Boolean,
-        value: () => loadTimeData.getBoolean('capturedSurfaceControlEnabled'),
-      },
-
       /**
        * Whether the File System Access Persistent Permissions UI should be
        * displayed.
@@ -296,7 +279,6 @@
   declare private enableDeleteBrowsingDataRevamp_: boolean;
   declare private enableFederatedIdentityApiContentSetting_: boolean;
   declare private enablePaymentHandlerContentSetting_: boolean;
-  declare private enableHandTrackingContentSetting_: boolean;
   declare private enableExperimentalWebPlatformFeatures_: boolean;
   // <if expr="is_chromeos">
   declare private enableSmartCardReadersContentSetting_: boolean;
@@ -306,8 +288,6 @@
   declare private isPrivacySandboxRestricted_: boolean;
   declare private isPrivacySandboxRestrictedNoticeEnabled_: boolean;
   private privateStateTokensEnabled_: boolean;
-  declare private autoPictureInPictureEnabled_: boolean;
-  declare private capturedSurfaceControlEnabled_: boolean;
   declare private enableWebAppInstallation_: boolean;
   declare private enableLocalNetworkAccessSetting_: boolean;
   declare private focusConfig_: FocusConfig;
@@ -506,15 +486,25 @@
         triggerId = 'securityLinkRow';
         break;
       case 'siteSettings':
+      case 'siteSettingsAr':
       case 'siteSettingsAutomaticFullscreen':
+      case 'siteSettingsAutoPictureInPicture':
+      case 'siteSettingsBluetoothScanning':
+      case 'siteSettingsCapturedSurfaceControl':
       case 'siteSettingsHandlers':
+      case 'siteSettingsHandTracking':
+      case 'siteSettingsIdleDetection':
       case 'siteSettingsKeyboardLock':
+      case 'siteSettingsLocalFonts':
       case 'siteSettingsLocalNetworkAccess':
       case 'siteSettingsLocation':
       case 'siteSettingsNotifications':
       case 'siteSettingsPdfDocuments':
       case 'siteSettingsSiteData':
+      case 'siteSettingsStorageAccess':
+      case 'siteSettingsVr':
       case 'siteSettingsWebAppInstallation':
+      case 'siteSettingsWindowManagement':
       case 'siteSettingsZoomLevels':
         triggerId = 'permissionsLinkRow';
         break;
diff --git a/chrome/browser/resources/settings/privacy_page/privacy_page_index.html b/chrome/browser/resources/settings/privacy_page/privacy_page_index.html
index fb981e1..85dcbb7 100644
--- a/chrome/browser/resources/settings/privacy_page/privacy_page_index.html
+++ b/chrome/browser/resources/settings/privacy_page/privacy_page_index.html
@@ -124,6 +124,21 @@
   </template>
 
   <template is="dom-if" if="[[renderView_(
+      routes_.SITE_SETTINGS_AR, currentRoute, inSearchMode)]]">
+    <settings-ar-page slot="view" id="siteSettingsAr" data-parent-view-id="old">
+    </settings-ar-page>
+  </template>
+
+  <template is="dom-if" if="[[autoPictureInPictureEnabled_]]">
+    <template is="dom-if" if="[[renderView_(
+        routes_.SITE_SETTINGS_AUTO_PICTURE_IN_PICTURE, currentRoute, inSearchMode)]]">
+      <settings-auto-picture-in-picture-page slot="view"
+          id="siteSettingsAutoPictureInPicture" data-parent-view-id="old">
+      </settings-auto-picture-in-picture-page>
+    </template>
+  </template>
+
+  <template is="dom-if" if="[[renderView_(
       routes_.SITE_SETTINGS_AUTOMATIC_FULLSCREEN, currentRoute,
       inSearchMode)]]">
     <settings-automatic-full-screen-page slot="view"
@@ -132,6 +147,33 @@
     </settings-automatic-full-screen-page>
   </template>
 
+  <template is="dom-if" if="[[enableExperimentalWebPlatformFeatures_]]">
+    <template is="dom-if" if="[[renderView_(
+        routes_.SITE_SETTINGS_BLUETOOTH_SCANNING, currentRoute, inSearchMode)]]">
+      <settings-bluetooth-scanning-page slot="view"
+          id="siteSettingsBluetoothScanning" data-parent-view-id="old">
+      </settings-bluetooth-scanning-page>
+    </template>
+  </template>
+
+  <template is="dom-if" if="[[capturedSurfaceControlEnabled_]]">
+    <template is="dom-if" if="[[renderView_(
+        routes_.SITE_SETTINGS_CAPTURED_SURFACE_CONTROL, currentRoute, inSearchMode)]]">
+      <settings-captured-surface-control-page slot="view"
+          id="siteSettingsCapturedSurfaceControl" data-parent-view-id="old">
+      </settings-captured-surface-control-page>
+    </template>
+  </template>
+
+  <template is="dom-if" if="[[enableHandTrackingContentSetting_]]">
+    <template is="dom-if" if="[[renderView_(
+        routes_.SITE_SETTINGS_HAND_TRACKING, currentRoute, inSearchMode)]]">
+      <settings-hand-tracking-page slot="view" id="siteSettingsHandTracking"
+          data-parent-view-id="old">
+      </settings-hand-tracking-page>
+    </template>
+  </template>
+
   <template is="dom-if" if="[[renderView_(
       routes_.SITE_SETTINGS_HANDLERS, currentRoute, inSearchMode)]]">
     <protocol-handlers slot="view" id="siteSettingsHandlers"
@@ -141,6 +183,13 @@
     </protocol-handlers>
   </template>
 
+  <template is="dom-if" if="[[renderView_(
+      routes_.SITE_SETTINGS_IDLE_DETECTION, currentRoute, inSearchMode)]]">
+    <settings-idle-detection-page slot="view" id="siteSettingsIdleDetection"
+        data-parent-view-id="old">
+    </settings-idle-detection-page>
+  </template>
+
   <template is="dom-if" if="[[enableKeyboardLockPrompt_]]">
     <template is="dom-if" if="[[renderView_(
         routes_.SITE_SETTINGS_KEYBOARD_LOCK, currentRoute, inSearchMode)]]">
@@ -162,6 +211,13 @@
   </template>
 
   <template is="dom-if" if="[[renderView_(
+      routes_.SITE_SETTINGS_LOCAL_FONTS, currentRoute, inSearchMode)]]">
+    <settings-local-fonts-page slot="view" id="siteSettingsLocalFonts"
+        data-parent-view-id="old">
+    </settings-local-fonts-page>
+  </template>
+
+  <template is="dom-if" if="[[renderView_(
       routes_.SITE_SETTINGS_LOCATION, currentRoute, inSearchMode)]]">
     <settings-geolocation-page slot="view" id="siteSettingsLocation"
         data-parent-view-id="old" prefs="{{prefs}}">
@@ -189,6 +245,19 @@
     </settings-site-data>
   </template>
 
+  <template is="dom-if" if="[[renderView_(
+      routes_.SITE_SETTINGS_STORAGE_ACCESS, currentRoute, inSearchMode)]]">
+    <settings-storage-access-page slot="view" id="siteSettingsStorageAccess"
+        data-parent-view-id="old">
+    </settings-storage-access-page>
+  </template>
+
+  <template is="dom-if" if="[[renderView_(
+      routes_.SITE_SETTINGS_VR, currentRoute, inSearchMode)]]">
+    <settings-vr-page slot="view" id="siteSettingsVr" data-parent-view-id="old">
+    </settings-vr-page>
+  </template>
+
   <template is="dom-if" if="[[enableWebAppInstallation_]]">
     <template is="dom-if" if="[[renderView_(
         routes_.SITE_SETTINGS_WEB_APP_INSTALLATION, currentRoute,
@@ -201,6 +270,13 @@
   </template>
 
   <template is="dom-if" if="[[renderView_(
+      routes_.SITE_SETTINGS_WINDOW_MANAGEMENT, currentRoute, inSearchMode)]]">
+    <settings-window-management-page slot="view"
+        id="siteSettingsWindowManagement" data-parent-view-id="old">
+    </settings-window-management-page>
+  </template>
+
+  <template is="dom-if" if="[[renderView_(
       routes_.SITE_SETTINGS_ZOOM_LEVELS, currentRoute, inSearchMode)]]">
     <zoom-levels slot="view" id="siteSettingsZoomLevels"
         data-parent-view-id="old">
diff --git a/chrome/browser/resources/settings/privacy_page/privacy_page_index.ts b/chrome/browser/resources/settings/privacy_page/privacy_page_index.ts
index 58c4684..627672b 100644
--- a/chrome/browser/resources/settings/privacy_page/privacy_page_index.ts
+++ b/chrome/browser/resources/settings/privacy_page/privacy_page_index.ts
@@ -79,6 +79,31 @@
         value: false,
       },
 
+      autoPictureInPictureEnabled_: {
+        type: Boolean,
+        value: () => loadTimeData.getBoolean('autoPictureInPictureEnabled'),
+      },
+
+      capturedSurfaceControlEnabled_: {
+        type: Boolean,
+        value: () => loadTimeData.getBoolean('capturedSurfaceControlEnabled'),
+      },
+
+      enableExperimentalWebPlatformFeatures_: {
+        type: Boolean,
+        value: () => {
+          return loadTimeData.getBoolean(
+              'enableExperimentalWebPlatformFeatures');
+        },
+      },
+
+      enableHandTrackingContentSetting_: {
+        type: Boolean,
+        value: () => {
+          return loadTimeData.getBoolean('enableHandTrackingContentSetting');
+        },
+      },
+
       enableIncognitoTrackingProtections_: {
         type: Boolean,
         value: () =>
@@ -88,9 +113,7 @@
       enableSecurityKeysSubpage_: {
         type: Boolean,
         readOnly: true,
-        value: () => {
-          return loadTimeData.getBoolean('enableSecurityKeysSubpage');
-        },
+        value: () => loadTimeData.getBoolean('enableSecurityKeysSubpage'),
       },
 
       enableKeyboardLockPrompt_: {
@@ -136,6 +159,10 @@
   declare private pageVisibility_: PageVisibility;
   declare private routes_: SettingsRoutes;
   declare private showPrivacyGuidePromo_: boolean;
+  declare private autoPictureInPictureEnabled_: boolean;
+  declare private capturedSurfaceControlEnabled_: boolean;
+  declare private enableExperimentalWebPlatformFeatures_: boolean;
+  declare private enableHandTrackingContentSetting_: boolean;
   declare private enableIncognitoTrackingProtections_: boolean;
   declare private enableKeyboardLockPrompt_: boolean;
   declare private enableLocalNetworkAccessSetting_: boolean;
@@ -199,13 +226,31 @@
         return ['securityKeys'];
       case routes.SITE_SETTINGS:
         return ['siteSettings'];
+      case routes.SITE_SETTINGS_AR:
+        return ['siteSettingsAr'];
       case routes.SITE_SETTINGS_AUTOMATIC_FULLSCREEN:
         return ['siteSettingsAutomaticFullscreen'];
+      case routes.SITE_SETTINGS_AUTO_PICTURE_IN_PICTURE:
+        assert(this.autoPictureInPictureEnabled_);
+        return ['siteSettingsAutoPictureInPicture'];
+      case routes.SITE_SETTINGS_BLUETOOTH_SCANNING:
+        assert(this.enableExperimentalWebPlatformFeatures_);
+        return ['siteSettingsBluetoothScanning'];
+      case routes.SITE_SETTINGS_CAPTURED_SURFACE_CONTROL:
+        assert(this.capturedSurfaceControlEnabled_);
+        return ['siteSettingsCapturedSurfaceControl'];
       case routes.SITE_SETTINGS_HANDLERS:
         return ['siteSettingsHandlers'];
+      case routes.SITE_SETTINGS_HAND_TRACKING:
+        assert(this.enableHandTrackingContentSetting_);
+        return ['siteSettingsHandTracking'];
+      case routes.SITE_SETTINGS_IDLE_DETECTION:
+        return ['siteSettingsIdleDetection'];
       case routes.SITE_SETTINGS_KEYBOARD_LOCK:
         assert(this.enableKeyboardLockPrompt_);
         return ['siteSettingsKeyboardLock'];
+      case routes.SITE_SETTINGS_LOCAL_FONTS:
+        return ['siteSettingsLocalFonts'];
       case routes.SITE_SETTINGS_LOCAL_NETWORK_ACCESS:
         assert(this.enableLocalNetworkAccessSetting_);
         return ['siteSettingsLocalNetworkAccess'];
@@ -217,9 +262,15 @@
         return ['siteSettingsPdfDocuments'];
       case routes.SITE_SETTINGS_SITE_DATA:
         return ['siteSettingsSiteData'];
+      case routes.SITE_SETTINGS_STORAGE_ACCESS:
+        return ['siteSettingsStorageAccess'];
+      case routes.SITE_SETTINGS_VR:
+        return ['siteSettingsVr'];
       case routes.SITE_SETTINGS_WEB_APP_INSTALLATION:
         assert(this.enableWebAppInstallation_);
         return ['siteSettingsWebAppInstallation'];
+      case routes.SITE_SETTINGS_WINDOW_MANAGEMENT:
+        return ['siteSettingsWindowManagement'];
       case routes.SITE_SETTINGS_ZOOM_LEVELS:
         return ['siteSettingsZoomLevels'];
       case routes.SAFETY_HUB:
diff --git a/chrome/browser/resources/settings/route.ts b/chrome/browser/resources/settings/route.ts
index 41b2a7bf..d503b24 100644
--- a/chrome/browser/resources/settings/route.ts
+++ b/chrome/browser/resources/settings/route.ts
@@ -81,15 +81,18 @@
   // routes.
   r.SITE_SETTINGS_ADS = r.SITE_SETTINGS.createChild('ads');
   r.SITE_SETTINGS_AR = r.SITE_SETTINGS.createChild('ar');
+  r.SITE_SETTINGS_AR.hasMigratedToPlugin = true;
   r.SITE_SETTINGS_AUTOMATIC_DOWNLOADS =
       r.SITE_SETTINGS.createChild('automaticDownloads');
   if (loadTimeData.getBoolean('autoPictureInPictureEnabled')) {
     r.SITE_SETTINGS_AUTO_PICTURE_IN_PICTURE =
         r.SITE_SETTINGS.createChild('autoPictureInPicture');
+    r.SITE_SETTINGS_AUTO_PICTURE_IN_PICTURE.hasMigratedToPlugin = true;
   }
   if (loadTimeData.getBoolean('capturedSurfaceControlEnabled')) {
     r.SITE_SETTINGS_CAPTURED_SURFACE_CONTROL =
         r.SITE_SETTINGS.createChild('capturedSurfaceControl');
+    r.SITE_SETTINGS_CAPTURED_SURFACE_CONTROL.hasMigratedToPlugin = true;
   }
   // <if expr="is_chromeos">
   if (loadTimeData.getBoolean('enableSmartCardReadersContentSetting')) {
@@ -104,8 +107,10 @@
   r.SITE_SETTINGS_CLIPBOARD = r.SITE_SETTINGS.createChild('clipboard');
   if (loadTimeData.getBoolean('enableHandTrackingContentSetting')) {
     r.SITE_SETTINGS_HAND_TRACKING = r.SITE_SETTINGS.createChild('handTracking');
+    r.SITE_SETTINGS_HAND_TRACKING.hasMigratedToPlugin = true;
   }
   r.SITE_SETTINGS_IDLE_DETECTION = r.SITE_SETTINGS.createChild('idleDetection');
+  r.SITE_SETTINGS_IDLE_DETECTION.hasMigratedToPlugin = true;
   r.SITE_SETTINGS_IMAGES = r.SITE_SETTINGS.createChild('images');
   r.SITE_SETTINGS_MIXEDSCRIPT = r.SITE_SETTINGS.createChild('insecureContent');
   r.SITE_SETTINGS_JAVASCRIPT = r.SITE_SETTINGS.createChild('javascript');
@@ -150,18 +155,22 @@
   r.SITE_SETTINGS_SITE_DATA = r.SITE_SETTINGS.createChild('siteData');
   r.SITE_SETTINGS_SITE_DATA.hasMigratedToPlugin = true;
   r.SITE_SETTINGS_VR = r.SITE_SETTINGS.createChild('vr');
+  r.SITE_SETTINGS_VR.hasMigratedToPlugin = true;
   if (loadTimeData.getBoolean('enableExperimentalWebPlatformFeatures')) {
     r.SITE_SETTINGS_BLUETOOTH_SCANNING =
         r.SITE_SETTINGS.createChild('bluetoothScanning');
+    r.SITE_SETTINGS_BLUETOOTH_SCANNING.hasMigratedToPlugin = true;
   }
 
   r.SITE_SETTINGS_WINDOW_MANAGEMENT =
       r.SITE_SETTINGS.createChild('windowManagement');
+  r.SITE_SETTINGS_WINDOW_MANAGEMENT.hasMigratedToPlugin = true;
   r.SITE_SETTINGS_FILE_SYSTEM_WRITE = r.SITE_SETTINGS.createChild('filesystem');
   r.SITE_SETTINGS_FILE_SYSTEM_WRITE_DETAILS =
       r.SITE_SETTINGS_FILE_SYSTEM_WRITE.createChild('siteDetails');
   r.SITE_SETTINGS_LOCAL_FONTS = r.SITE_SETTINGS.createChild('localFonts');
   r.SITE_SETTINGS_STORAGE_ACCESS = r.SITE_SETTINGS.createChild('storageAccess');
+  r.SITE_SETTINGS_STORAGE_ACCESS.hasMigratedToPlugin = true;
   r.SITE_SETTINGS_AUTOMATIC_FULLSCREEN =
       r.SITE_SETTINGS.createChild('automaticFullScreen');
   r.SITE_SETTINGS_AUTOMATIC_FULLSCREEN.hasMigratedToPlugin = true;
diff --git a/chrome/browser/resources/settings/site_settings/ar_page.html b/chrome/browser/resources/settings/site_settings/ar_page.html
index cdc5845d..8dba1501 100644
--- a/chrome/browser/resources/settings/site_settings/ar_page.html
+++ b/chrome/browser/resources/settings/site_settings/ar_page.html
@@ -1,19 +1,23 @@
-  <style include="settings-shared site-settings-shared"></style>
-   <div class="content-settings-header secondary">
-     $i18n{siteSettingsArDescription}
-   </div>
-   <!-- TODO(crbug.com/40176677): Fix redesign string when available.-->
-   <settings-category-default-radio-group
-       category="[[contentSettingsTypesEnum_.AR]]"
-       allow-option-label="$i18n{siteSettingsArAsk}"
-       allow-option-icon="privacy:cardboard"
-       block-option-label="$i18n{siteSettingsArBlock}"
-       block-option-icon="privacy:cardboard-off">
-   </settings-category-default-radio-group>
-   <category-setting-exceptions
-       category="[[contentSettingsTypesEnum_.AR]]"
-       read-only-list
-       allow-header="$i18n{siteSettingsArAllowedExceptions}"
-       block-header="$i18n{siteSettingsArBlockedExceptions}"
-       search-filter="[[searchTerm]]">
-   </category-setting-exceptions>
+<style include="settings-shared site-settings-shared"></style>
+<settings-subpage page-title="$i18n{siteSettingsAr}"
+    search-label="$i18n{siteSettingsAllSitesSearch}"
+    search-term="{{searchTerm}}">
+  <div class="content-settings-header secondary">
+    $i18n{siteSettingsArDescription}
+  </div>
+  <!-- TODO(crbug.com/40176677): Fix redesign string when available.-->
+  <settings-category-default-radio-group
+      category="[[contentSettingsTypesEnum_.AR]]"
+      allow-option-label="$i18n{siteSettingsArAsk}"
+      allow-option-icon="privacy:cardboard"
+      block-option-label="$i18n{siteSettingsArBlock}"
+      block-option-icon="privacy:cardboard-off">
+  </settings-category-default-radio-group>
+  <category-setting-exceptions
+      category="[[contentSettingsTypesEnum_.AR]]"
+      read-only-list
+      allow-header="$i18n{siteSettingsArAllowedExceptions}"
+      block-header="$i18n{siteSettingsArBlockedExceptions}"
+      search-filter="[[searchTerm]]">
+  </category-setting-exceptions>
+</settings-subpage>
diff --git a/chrome/browser/resources/settings/site_settings/ar_page.ts b/chrome/browser/resources/settings/site_settings/ar_page.ts
index 5950b8a..fb751c2 100644
--- a/chrome/browser/resources/settings/site_settings/ar_page.ts
+++ b/chrome/browser/resources/settings/site_settings/ar_page.ts
@@ -4,16 +4,20 @@
 
 import './settings_category_default_radio_group.js';
 import './site_settings_shared.css.js';
+import '../settings_page/settings_subpage.js';
 import '../settings_shared.css.js';
 import './category_setting_exceptions.js';
 
 import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
+import {SettingsViewMixin} from '../settings_page/settings_view_mixin.js';
 import {ContentSettingsTypes} from '../site_settings/constants.js';
 
 import {getTemplate} from './ar_page.html.js';
 
-export class ArPageElement extends PolymerElement {
+const ArPageElementBase = SettingsViewMixin(PolymerElement);
+
+export class ArPageElement extends ArPageElementBase {
   static get is() {
     return 'settings-ar-page';
   }
@@ -35,6 +39,11 @@
   }
 
   declare searchTerm: string;
+
+  // SettingsViewMixin implementation.
+  override focusBackButton() {
+    this.shadowRoot!.querySelector('settings-subpage')!.focusBackButton();
+  }
 }
 
 declare global {
diff --git a/chrome/browser/resources/settings/site_settings/auto_picture_in_picture_page.html b/chrome/browser/resources/settings/site_settings/auto_picture_in_picture_page.html
index bff39e3..f7569fc 100644
--- a/chrome/browser/resources/settings/site_settings/auto_picture_in_picture_page.html
+++ b/chrome/browser/resources/settings/site_settings/auto_picture_in_picture_page.html
@@ -1,4 +1,7 @@
-  <style include="settings-shared site-settings-shared"></style>
+<style include="settings-shared site-settings-shared"></style>
+<settings-subpage page-title="$i18n{siteSettingsAutoPictureInPicture}"
+    search-label="$i18n{siteSettingsAllSitesSearch}"
+    search-term="{{searchTerm}}">
    <div class="content-settings-header secondary">
      $i18n{siteSettingsAutoPictureInPictureDescription}
    </div>
@@ -18,3 +21,4 @@
            "$i18n{siteSettingsAutoPictureInPictureBlockedExceptions}"
        search-filter="[[searchTerm]]">
    </category-setting-exceptions>
+</settings-subpage>
diff --git a/chrome/browser/resources/settings/site_settings/auto_picture_in_picture_page.ts b/chrome/browser/resources/settings/site_settings/auto_picture_in_picture_page.ts
index 000f2e7..ecad7b9 100644
--- a/chrome/browser/resources/settings/site_settings/auto_picture_in_picture_page.ts
+++ b/chrome/browser/resources/settings/site_settings/auto_picture_in_picture_page.ts
@@ -4,16 +4,21 @@
 
 import './settings_category_default_radio_group.js';
 import './site_settings_shared.css.js';
+import '../settings_page/settings_subpage.js';
 import '../settings_shared.css.js';
 import './category_setting_exceptions.js';
 
 import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
+import {SettingsViewMixin} from '../settings_page/settings_view_mixin.js';
 import {ContentSettingsTypes} from '../site_settings/constants.js';
 
 import {getTemplate} from './auto_picture_in_picture_page.html.js';
 
-export class AutoPictureInPicturePageElement extends PolymerElement {
+const AutoPictureInPicturePageElementBase = SettingsViewMixin(PolymerElement);
+
+export class AutoPictureInPicturePageElement extends
+    AutoPictureInPicturePageElementBase {
   static get is() {
     return 'settings-auto-picture-in-picture-page';
   }
@@ -35,6 +40,11 @@
   }
 
   declare searchTerm: string;
+
+  // SettingsViewMixin implementation.
+  override focusBackButton() {
+    this.shadowRoot!.querySelector('settings-subpage')!.focusBackButton();
+  }
 }
 
 declare global {
diff --git a/chrome/browser/resources/settings/site_settings/bluetooth_scanning_page.html b/chrome/browser/resources/settings/site_settings/bluetooth_scanning_page.html
index ac29f9b..5dc4f61 100644
--- a/chrome/browser/resources/settings/site_settings/bluetooth_scanning_page.html
+++ b/chrome/browser/resources/settings/site_settings/bluetooth_scanning_page.html
@@ -1,21 +1,25 @@
-  <style include="settings-shared site-settings-shared"></style>
-   <div class="content-settings-header secondary">
-     $i18n{siteSettingsBluetoothScanningDescription}
-   </div>
-   <settings-category-default-radio-group
-       category="[[contentSettingsTypesEnum_.BLUETOOTH_SCANNING]]"
-       allow-option-label=
-           "$i18n{siteSettingsBluetoothScanningAsk}"
-       allow-option-icon="settings:bluetooth-scanning"
-       block-option-label="$i18n{siteSettingsBluetoothScanningBlock}"
-       block-option-icon="settings:bluetooth-off">
-   </settings-category-default-radio-group>
-   <category-setting-exceptions
-       category="[[contentSettingsTypesEnum_.BLUETOOTH_SCANNING]]"
-       read-only-list
-       block-header=
-           "$i18n{siteSettingsBluetoothScanningBlockedExceptions}"
-       allow-header=
-           "$i18n{siteSettingsBluetoothScanningAllowedExceptions}"
-       search-filter="[[searchTerm]]">
-   </category-setting-exceptions>
+<style include="settings-shared site-settings-shared"></style>
+<settings-subpage page-title="$i18n{siteSettingsBluetoothScanning}"
+    search-label="$i18n{siteSettingsAllSitesSearch}"
+    search-term="{{searchTerm}}">
+  <div class="content-settings-header secondary">
+    $i18n{siteSettingsBluetoothScanningDescription}
+  </div>
+  <settings-category-default-radio-group
+      category="[[contentSettingsTypesEnum_.BLUETOOTH_SCANNING]]"
+      allow-option-label=
+          "$i18n{siteSettingsBluetoothScanningAsk}"
+      allow-option-icon="settings:bluetooth-scanning"
+      block-option-label="$i18n{siteSettingsBluetoothScanningBlock}"
+      block-option-icon="settings:bluetooth-off">
+  </settings-category-default-radio-group>
+  <category-setting-exceptions
+      category="[[contentSettingsTypesEnum_.BLUETOOTH_SCANNING]]"
+      read-only-list
+      block-header=
+          "$i18n{siteSettingsBluetoothScanningBlockedExceptions}"
+      allow-header=
+          "$i18n{siteSettingsBluetoothScanningAllowedExceptions}"
+      search-filter="[[searchTerm]]">
+  </category-setting-exceptions>
+</settings-subpage>
diff --git a/chrome/browser/resources/settings/site_settings/bluetooth_scanning_page.ts b/chrome/browser/resources/settings/site_settings/bluetooth_scanning_page.ts
index aebdd1fb..3f911bc 100644
--- a/chrome/browser/resources/settings/site_settings/bluetooth_scanning_page.ts
+++ b/chrome/browser/resources/settings/site_settings/bluetooth_scanning_page.ts
@@ -4,16 +4,21 @@
 
 import './settings_category_default_radio_group.js';
 import './site_settings_shared.css.js';
+import '../settings_page/settings_subpage.js';
 import '../settings_shared.css.js';
 import './category_setting_exceptions.js';
 
 import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
+import {SettingsViewMixin} from '../settings_page/settings_view_mixin.js';
 import {ContentSettingsTypes} from '../site_settings/constants.js';
 
 import {getTemplate} from './bluetooth_scanning_page.html.js';
 
-export class BluetoothScanningPageElement extends PolymerElement {
+const BluetoothScanningPageElementBase = SettingsViewMixin(PolymerElement);
+
+export class BluetoothScanningPageElement extends
+    BluetoothScanningPageElementBase {
   static get is() {
     return 'settings-bluetooth-scanning-page';
   }
@@ -35,6 +40,11 @@
   }
 
   declare searchTerm: string;
+
+  // SettingsViewMixin implementation.
+  override focusBackButton() {
+    this.shadowRoot!.querySelector('settings-subpage')!.focusBackButton();
+  }
 }
 
 declare global {
diff --git a/chrome/browser/resources/settings/site_settings/captured_surface_control_page.html b/chrome/browser/resources/settings/site_settings/captured_surface_control_page.html
index 86d15662..caf2d3e 100644
--- a/chrome/browser/resources/settings/site_settings/captured_surface_control_page.html
+++ b/chrome/browser/resources/settings/site_settings/captured_surface_control_page.html
@@ -1,4 +1,7 @@
-  <style include="settings-shared site-settings-shared"></style>
+<style include="settings-shared site-settings-shared"></style>
+<settings-subpage page-title="$i18n{siteSettingsCapturedSurfaceControl}"
+    search-label="$i18n{siteSettingsAllSitesSearch}"
+    search-term="{{searchTerm}}">
   <div class="content-settings-header secondary">
     $i18n{siteSettingsCapturedSurfaceControlDescription}
   </div>
@@ -18,3 +21,4 @@
           "$i18n{siteSettingsCapturedSurfaceControlBlockedExceptions}"
       search-filter="[[searchTerm]]">
   </category-setting-exceptions>
+</settings-subpage>
diff --git a/chrome/browser/resources/settings/site_settings/captured_surface_control_page.ts b/chrome/browser/resources/settings/site_settings/captured_surface_control_page.ts
index 1db186f..95910ed 100644
--- a/chrome/browser/resources/settings/site_settings/captured_surface_control_page.ts
+++ b/chrome/browser/resources/settings/site_settings/captured_surface_control_page.ts
@@ -4,16 +4,21 @@
 
 import './settings_category_default_radio_group.js';
 import './site_settings_shared.css.js';
+import '../settings_page/settings_subpage.js';
 import '../settings_shared.css.js';
 import './category_setting_exceptions.js';
 
 import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
+import {SettingsViewMixin} from '../settings_page/settings_view_mixin.js';
 import {ContentSettingsTypes} from '../site_settings/constants.js';
 
 import {getTemplate} from './captured_surface_control_page.html.js';
 
-export class CapturedSurfaceControlPageElement extends PolymerElement {
+const CapturedSurfaceControlPageElementBase = SettingsViewMixin(PolymerElement);
+
+export class CapturedSurfaceControlPageElement extends
+    CapturedSurfaceControlPageElementBase {
   static get is() {
     return 'settings-captured-surface-control-page';
   }
@@ -35,6 +40,11 @@
   }
 
   declare searchTerm: string;
+
+  // SettingsViewMixin implementation.
+  override focusBackButton() {
+    this.shadowRoot!.querySelector('settings-subpage')!.focusBackButton();
+  }
 }
 
 declare global {
diff --git a/chrome/browser/resources/settings/site_settings/hand_tracking_page.html b/chrome/browser/resources/settings/site_settings/hand_tracking_page.html
index 1677efc0..69e1c40 100644
--- a/chrome/browser/resources/settings/site_settings/hand_tracking_page.html
+++ b/chrome/browser/resources/settings/site_settings/hand_tracking_page.html
@@ -1,4 +1,7 @@
-  <style include="settings-shared site-settings-shared"></style>
+<style include="settings-shared site-settings-shared"></style>
+<settings-subpage page-title="$i18n{siteSettingsHandTracking}"
+    search-label="$i18n{siteSettingsAllSitesSearch}"
+    search-term="{{searchTerm}}">
   <div class="content-settings-header secondary">
     $i18n{siteSettingsHandTrackingDescription}
   </div>
@@ -16,3 +19,4 @@
       block-header="$i18n{siteSettingsHandTrackingBlockedExceptions}"
       search-filter="[[searchTerm]]">
   </category-setting-exceptions>
+</settings-subpage>
diff --git a/chrome/browser/resources/settings/site_settings/hand_tracking_page.ts b/chrome/browser/resources/settings/site_settings/hand_tracking_page.ts
index 9d7c333..e307ea3c 100644
--- a/chrome/browser/resources/settings/site_settings/hand_tracking_page.ts
+++ b/chrome/browser/resources/settings/site_settings/hand_tracking_page.ts
@@ -4,16 +4,20 @@
 
 import './settings_category_default_radio_group.js';
 import './site_settings_shared.css.js';
+import '../settings_page/settings_subpage.js';
 import '../settings_shared.css.js';
 import './category_setting_exceptions.js';
 
 import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
+import {SettingsViewMixin} from '../settings_page/settings_view_mixin.js';
 import {ContentSettingsTypes} from '../site_settings/constants.js';
 
 import {getTemplate} from './hand_tracking_page.html.js';
 
-export class HandTrackingPageElement extends PolymerElement {
+const HandTrackingPageElementBase = SettingsViewMixin(PolymerElement);
+
+export class HandTrackingPageElement extends HandTrackingPageElementBase {
   static get is() {
     return 'settings-hand-tracking-page';
   }
@@ -35,6 +39,11 @@
   }
 
   declare searchTerm: string;
+
+  // SettingsViewMixin implementation.
+  override focusBackButton() {
+    this.shadowRoot!.querySelector('settings-subpage')!.focusBackButton();
+  }
 }
 
 declare global {
diff --git a/chrome/browser/resources/settings/site_settings/idle_detection_page.html b/chrome/browser/resources/settings/site_settings/idle_detection_page.html
index 2855024..dbaba16 100644
--- a/chrome/browser/resources/settings/site_settings/idle_detection_page.html
+++ b/chrome/browser/resources/settings/site_settings/idle_detection_page.html
@@ -1,4 +1,7 @@
-  <style include="settings-shared site-settings-shared"></style>
+<style include="settings-shared site-settings-shared"></style>
+<settings-subpage page-title="$i18n{siteSettingsIdleDetection}"
+    search-label="$i18n{siteSettingsAllSitesSearch}"
+    search-term="{{searchTerm}}">
   <div class="content-settings-header secondary">
     $i18n{siteSettingsDeviceUseDescription}
   </div>
@@ -15,3 +18,4 @@
       block-header="$i18n{siteSettingsDeviceUseBlockedExceptions}"
       search-filter="[[searchTerm]]">
   </category-setting-exceptions>
+</settings-subpage>
diff --git a/chrome/browser/resources/settings/site_settings/idle_detection_page.ts b/chrome/browser/resources/settings/site_settings/idle_detection_page.ts
index e2262f5a..b204b53 100644
--- a/chrome/browser/resources/settings/site_settings/idle_detection_page.ts
+++ b/chrome/browser/resources/settings/site_settings/idle_detection_page.ts
@@ -4,16 +4,20 @@
 
 import './settings_category_default_radio_group.js';
 import './site_settings_shared.css.js';
+import '../settings_page/settings_subpage.js';
 import '../settings_shared.css.js';
 import './category_setting_exceptions.js';
 
 import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
+import {SettingsViewMixin} from '../settings_page/settings_view_mixin.js';
 import {ContentSettingsTypes} from '../site_settings/constants.js';
 
 import {getTemplate} from './idle_detection_page.html.js';
 
-export class IdleDetectionPageElement extends PolymerElement {
+const IdleDetectionPageElementBase = SettingsViewMixin(PolymerElement);
+
+export class IdleDetectionPageElement extends IdleDetectionPageElementBase {
   static get is() {
     return 'settings-idle-detection-page';
   }
@@ -35,6 +39,11 @@
   }
 
   declare searchTerm: string;
+
+  // SettingsViewMixin implementation.
+  override focusBackButton() {
+    this.shadowRoot!.querySelector('settings-subpage')!.focusBackButton();
+  }
 }
 
 declare global {
diff --git a/chrome/browser/resources/settings/site_settings/local_fonts_page.html b/chrome/browser/resources/settings/site_settings/local_fonts_page.html
index 859a2b84..af4b95e 100644
--- a/chrome/browser/resources/settings/site_settings/local_fonts_page.html
+++ b/chrome/browser/resources/settings/site_settings/local_fonts_page.html
@@ -1,4 +1,7 @@
-  <style include="settings-shared site-settings-shared"></style>
+<style include="settings-shared site-settings-shared"></style>
+<settings-subpage page-title="$i18n{fonts}"
+    search-label="$i18n{siteSettingsAllSitesSearch}"
+    search-term="{{searchTerm}}">
   <div class="content-settings-header secondary">
     $i18n{siteSettingsFontsDescription}
   </div>
@@ -15,3 +18,4 @@
       allow-header="$i18n{siteSettingsFontsAllowedExceptions}"
       search-filter="[[searchTerm]]">
   </category-setting-exceptions>
+</settings-subpage>
diff --git a/chrome/browser/resources/settings/site_settings/local_fonts_page.ts b/chrome/browser/resources/settings/site_settings/local_fonts_page.ts
index 3c3928a..b7b8bed7 100644
--- a/chrome/browser/resources/settings/site_settings/local_fonts_page.ts
+++ b/chrome/browser/resources/settings/site_settings/local_fonts_page.ts
@@ -4,16 +4,20 @@
 
 import './settings_category_default_radio_group.js';
 import './site_settings_shared.css.js';
+import '../settings_page/settings_subpage.js';
 import '../settings_shared.css.js';
 import './category_setting_exceptions.js';
 
 import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
+import {SettingsViewMixin} from '../settings_page/settings_view_mixin.js';
 import {ContentSettingsTypes} from '../site_settings/constants.js';
 
 import {getTemplate} from './local_fonts_page.html.js';
 
-export class LocalFontsPageElement extends PolymerElement {
+const LocalFontsPageElementBase = SettingsViewMixin(PolymerElement);
+
+export class LocalFontsPageElement extends LocalFontsPageElementBase {
   static get is() {
     return 'settings-local-fonts-page';
   }
@@ -35,6 +39,11 @@
   }
 
   declare searchTerm: string;
+
+  // SettingsViewMixin implementation.
+  override focusBackButton() {
+    this.shadowRoot!.querySelector('settings-subpage')!.focusBackButton();
+  }
 }
 
 declare global {
diff --git a/chrome/browser/resources/settings/site_settings/storage_access_page.html b/chrome/browser/resources/settings/site_settings/storage_access_page.html
index bb0de7b..0c5e18f 100644
--- a/chrome/browser/resources/settings/site_settings/storage_access_page.html
+++ b/chrome/browser/resources/settings/site_settings/storage_access_page.html
@@ -1,4 +1,7 @@
-  <style include="settings-shared site-settings-shared"></style>
+<style include="settings-shared site-settings-shared"></style>
+<settings-subpage page-title="$i18n{siteSettingsStorageAccess}"
+    search-label="$i18n{siteSettingsAllSitesSearch}"
+    search-term="{{searchTerm}}">
   <div class="content-settings-header secondary">
     $i18n{storageAccessDescription}
   </div>
@@ -26,3 +29,4 @@
       category-header="$i18n{storageAccessAllowedExceptions}"
       search-filter="[[searchTerm]]">
   </storage-access-site-list>
+</settings-subpage>
diff --git a/chrome/browser/resources/settings/site_settings/storage_access_page.ts b/chrome/browser/resources/settings/site_settings/storage_access_page.ts
index 633bce2..207b911 100644
--- a/chrome/browser/resources/settings/site_settings/storage_access_page.ts
+++ b/chrome/browser/resources/settings/site_settings/storage_access_page.ts
@@ -9,11 +9,14 @@
 
 import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
+import {SettingsViewMixin} from '../settings_page/settings_view_mixin.js';
 import {ContentSetting, ContentSettingsTypes} from '../site_settings/constants.js';
 
 import {getTemplate} from './storage_access_page.html.js';
 
-export class StorageAccessPageElement extends PolymerElement {
+const StorageAccessPageElementBase = SettingsViewMixin(PolymerElement);
+
+export class StorageAccessPageElement extends StorageAccessPageElementBase {
   static get is() {
     return 'settings-storage-access-page';
   }
@@ -41,6 +44,11 @@
   }
 
   declare searchTerm: string;
+
+  // SettingsViewMixin implementation.
+  override focusBackButton() {
+    this.shadowRoot!.querySelector('settings-subpage')!.focusBackButton();
+  }
 }
 
 declare global {
diff --git a/chrome/browser/resources/settings/site_settings/vr_page.html b/chrome/browser/resources/settings/site_settings/vr_page.html
index 71a7d791..c6aab02 100644
--- a/chrome/browser/resources/settings/site_settings/vr_page.html
+++ b/chrome/browser/resources/settings/site_settings/vr_page.html
@@ -1,4 +1,7 @@
-  <style include="settings-shared site-settings-shared"></style>
+<style include="settings-shared site-settings-shared"></style>
+<settings-subpage page-title="$i18n{siteSettingsVr}"
+    search-label="$i18n{siteSettingsAllSitesSearch}"
+    search-term="{{searchTerm}}">
   <div class="content-settings-header secondary">
     $i18n{siteSettingsVrDescription}
   </div>
@@ -16,3 +19,4 @@
       block-header="$i18n{siteSettingsVrBlockedExceptions}"
       search-filter="[[searchTerm]]">
   </category-setting-exceptions>
+</settings-subpage>
diff --git a/chrome/browser/resources/settings/site_settings/vr_page.ts b/chrome/browser/resources/settings/site_settings/vr_page.ts
index 7e467b2..af449cf 100644
--- a/chrome/browser/resources/settings/site_settings/vr_page.ts
+++ b/chrome/browser/resources/settings/site_settings/vr_page.ts
@@ -4,16 +4,20 @@
 
 import './settings_category_default_radio_group.js';
 import './site_settings_shared.css.js';
+import '../settings_page/settings_subpage.js';
 import '../settings_shared.css.js';
 import './category_setting_exceptions.js';
 
 import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
+import {SettingsViewMixin} from '../settings_page/settings_view_mixin.js';
 import {ContentSettingsTypes} from '../site_settings/constants.js';
 
 import {getTemplate} from './vr_page.html.js';
 
-export class VrPageElement extends PolymerElement {
+const VrPageElementBase = SettingsViewMixin(PolymerElement);
+
+export class VrPageElement extends VrPageElementBase {
   static get is() {
     return 'settings-vr-page';
   }
@@ -35,6 +39,11 @@
   }
 
   declare searchTerm: string;
+
+  // SettingsViewMixin implementation.
+  override focusBackButton() {
+    this.shadowRoot!.querySelector('settings-subpage')!.focusBackButton();
+  }
 }
 
 declare global {
diff --git a/chrome/browser/resources/settings/site_settings/window_management_page.html b/chrome/browser/resources/settings/site_settings/window_management_page.html
index 3eda5d8..d1b9f44b3 100644
--- a/chrome/browser/resources/settings/site_settings/window_management_page.html
+++ b/chrome/browser/resources/settings/site_settings/window_management_page.html
@@ -1,4 +1,7 @@
-  <style include="settings-shared site-settings-shared"></style>
+<style include="settings-shared site-settings-shared"></style>
+<settings-subpage page-title="$i18n{siteSettingsWindowManagement}"
+    search-label="$i18n{siteSettingsAllSitesSearch}"
+    search-term="{{searchTerm}}">
   <div class="content-settings-header secondary">
     $i18n{siteSettingsWindowManagementDescription}
   </div>
@@ -16,3 +19,4 @@
           "$i18n{siteSettingsWindowManagementBlockedExceptions}"
       search-filter="[[searchTerm]]">
   </category-setting-exceptions>
+</settings-subpage>
diff --git a/chrome/browser/resources/settings/site_settings/window_management_page.ts b/chrome/browser/resources/settings/site_settings/window_management_page.ts
index 54f3a8a6..2b82e29a3 100644
--- a/chrome/browser/resources/settings/site_settings/window_management_page.ts
+++ b/chrome/browser/resources/settings/site_settings/window_management_page.ts
@@ -4,16 +4,21 @@
 
 import './settings_category_default_radio_group.js';
 import './site_settings_shared.css.js';
+import '../settings_page/settings_subpage.js';
 import '../settings_shared.css.js';
 import './category_setting_exceptions.js';
 
 import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
+import {SettingsViewMixin} from '../settings_page/settings_view_mixin.js';
 import {ContentSettingsTypes} from '../site_settings/constants.js';
 
 import {getTemplate} from './window_management_page.html.js';
 
-export class WindowManagementPageElement extends PolymerElement {
+const WindowManagementPageElementBase = SettingsViewMixin(PolymerElement);
+
+export class WindowManagementPageElement extends
+    WindowManagementPageElementBase {
   static get is() {
     return 'settings-window-management-page';
   }
@@ -35,6 +40,11 @@
   }
 
   declare searchTerm: string;
+
+  // SettingsViewMixin implementation.
+  override focusBackButton() {
+    this.shadowRoot!.querySelector('settings-subpage')!.focusBackButton();
+  }
 }
 
 declare global {
diff --git a/chrome/browser/resources/side_panel/read_anything/content_controller.ts b/chrome/browser/resources/side_panel/read_anything/content_controller.ts
index c74c1314..08a5084c 100644
--- a/chrome/browser/resources/side_panel/read_anything/content_controller.ts
+++ b/chrome/browser/resources/side_panel/read_anything/content_controller.ts
@@ -117,20 +117,22 @@
   }
 
   private createTextNode_(nodeId: number): Node {
-    // When creating text nodes, save the first text node id. We need this
-    // 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 (chrome.readingMode.isReadAloudEnabled) {
-      this.speechController_.initializeSpeechTree(nodeId);
-    }
-
     const textContent = chrome.readingMode.getTextContent(nodeId);
     const textNode = document.createTextNode(textContent);
     this.nodeStore_.setDomNode(textNode, nodeId);
     const isOverline = chrome.readingMode.isOverline(nodeId);
     const shouldBold = chrome.readingMode.shouldBold(nodeId);
 
+    // When creating text nodes, save the first text node id. We need this
+    // 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.
+    // This needs to be done after the text node is created and added to the
+    // node store.
+    if (chrome.readingMode.isReadAloudEnabled) {
+      this.speechController_.initializeSpeechTree(nodeId);
+    }
+
     if (!shouldBold && !isOverline) {
       return textNode;
     }
diff --git a/chrome/browser/resources/side_panel/read_anything/node_store.ts b/chrome/browser/resources/side_panel/read_anything/node_store.ts
index 4e88596..1fc2431 100644
--- a/chrome/browser/resources/side_panel/read_anything/node_store.ts
+++ b/chrome/browser/resources/side_panel/read_anything/node_store.ts
@@ -6,7 +6,6 @@
 
 import {getWordCount, isRectMostlyVisible} from './common.js';
 import type {ReadAloudNode} from './read_aloud/read_aloud_types.js';
-import {AxReadAloudNode} from './read_aloud/read_aloud_types.js';
 
 // A two-way map where each key is unique and each value is unique. The keys are
 // DOM nodes and the values are numbers, representing AXNodeIDs.
@@ -181,9 +180,7 @@
   }
 
   hasAnyNode(nodes: ReadAloudNode[]): boolean {
-    return nodes.some(
-        node => node instanceof AxReadAloudNode &&
-            this.getDomNode(node.axNodeId) !== undefined);
+    return nodes.some(node => node && node.domNode() !== undefined);
   }
 
   getDomNode(axNodeId: number): Node|undefined {
@@ -221,9 +218,11 @@
   // TODO: crbug.com/440400392- Handle hidden image node ids for read aloud
   // when non-AXNode-based read aloud nodes are used.
   areNodesAllHidden(nodes: ReadAloudNode[]): boolean {
-    return nodes.every(
-        node => node instanceof AxReadAloudNode &&
-            this.hiddenImageNodesIds_.has(node.axNodeId));
+    return nodes.every(node => {
+      const domNode = node && node.domNode();
+      const id = domNode && this.getAxId(domNode);
+      return !!id && this.hiddenImageNodesIds_.has(id);
+    });
   }
 
   addImageToFetch(nodeId: number): void {
diff --git a/chrome/browser/resources/side_panel/read_anything/read_aloud/highlighter.ts b/chrome/browser/resources/side_panel/read_anything/read_aloud/highlighter.ts
index b1be79e..192043e6 100644
--- a/chrome/browser/resources/side_panel/read_anything/read_aloud/highlighter.ts
+++ b/chrome/browser/resources/side_panel/read_anything/read_aloud/highlighter.ts
@@ -9,7 +9,6 @@
 
 import {getReadAloudModel} from './read_aloud_model_browser_proxy.js';
 import type {ReadAloudModelBrowserProxy} from './read_aloud_model_browser_proxy.js';
-import {AxReadAloudNode} from './read_aloud_types.js';
 import type {Segment} from './read_aloud_types.js';
 import {getCurrentSpeechRate, isInvalidHighlightForWordHighlighting} from './speech_presentation_rules.js';
 import {VoiceLanguageController} from './voice_language_controller.js';
@@ -242,12 +241,7 @@
     let didApplyHighlight = false;
     for (const segment of highlightSegments) {
       const {node, start, length: segmentLength} = segment;
-      if (!(node instanceof AxReadAloudNode)) {
-        continue;
-      }
-      const nodeId = node.axNodeId;
-
-      const domNode = this.nodeStore_.getDomNode(nodeId);
+      const domNode = node.domNode();
       if (!domNode) {
         continue;
       }
@@ -298,10 +292,7 @@
 
     this.resetPreviousHighlight();
     for (const {node, start, length} of segments) {
-      if (!(node instanceof AxReadAloudNode)) {
-        continue;
-      }
-      const element = this.nodeStore_.getDomNode(node.axNodeId) as HTMLElement;
+      const element = node.domNode() as HTMLElement;
       if (!element) {
         continue;
       }
diff --git a/chrome/browser/resources/side_panel/read_anything/read_aloud/read_aloud_model_browser_proxy.ts b/chrome/browser/resources/side_panel/read_anything/read_aloud/read_aloud_model_browser_proxy.ts
index 7ee738c..ab3fc54 100644
--- a/chrome/browser/resources/side_panel/read_anything/read_aloud/read_aloud_model_browser_proxy.ts
+++ b/chrome/browser/resources/side_panel/read_anything/read_aloud/read_aloud_model_browser_proxy.ts
@@ -34,7 +34,7 @@
 
   // Handle initialization.
   isInitialized(): boolean;
-  init(context: ReadAloudNode|string): void;
+  init(context: ReadAloudNode): void;
 }
 
 export function getReadAloudModel(): ReadAloudModelBrowserProxy {
diff --git a/chrome/browser/resources/side_panel/read_anything/read_aloud/read_aloud_types.ts b/chrome/browser/resources/side_panel/read_anything/read_aloud/read_aloud_types.ts
index 765d4dcc..8df1c46 100644
--- a/chrome/browser/resources/side_panel/read_anything/read_aloud/read_aloud_types.ts
+++ b/chrome/browser/resources/side_panel/read_anything/read_aloud/read_aloud_types.ts
@@ -2,6 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+import {NodeStore} from '../node_store.js';
+
 // This file contains type definitions for the data structures
 // used in the read aloud feature. It provides an abstraction layer for
 // representing text nodes and segments, allowing the core logic to work
@@ -12,10 +14,28 @@
 // segmentation method is used.
 export abstract class ReadAloudNode {
   abstract equals(other: ReadAloudNode|undefined|null): boolean;
+
+  abstract domNode(): Node|undefined;
+
+  static create(node: Node, nodeStore = NodeStore.getInstance()): ReadAloudNode
+      |undefined {
+    if (chrome.readingMode.isTsTextSegmentationEnabled) {
+      return new DomReadAloudNode(node);
+    }
+
+    const axNodeId = nodeStore.getAxId(node);
+    if (axNodeId) {
+      return new AxReadAloudNode(axNodeId, nodeStore);
+    }
+
+    return undefined;
+  }
 }
 
 export class AxReadAloudNode extends ReadAloudNode {
-  constructor(public readonly axNodeId: number) {
+  constructor(
+      public readonly axNodeId: number,
+      private readonly nodeStore_ = NodeStore.getInstance()) {
     super();
   }
 
@@ -26,8 +46,35 @@
 
     return this.axNodeId === other.axNodeId;
   }
+
+  domNode(): Node|undefined {
+    return this.nodeStore_.getDomNode(this.axNodeId);
+  }
 }
 
+// Represents a node used by read aloud that's based entirely on the DOM.
+export class DomReadAloudNode extends ReadAloudNode {
+  constructor(public readonly node: Node) {
+    super();
+  }
+
+  equals(other: ReadAloudNode|undefined|null): boolean {
+    if (!(other instanceof DomReadAloudNode)) {
+      return false;
+    }
+    return this.node.isEqualNode(other.node);
+  }
+
+  getText(): string {
+    return this.node.textContent || '';
+  }
+
+  domNode(): Node|undefined {
+    return this.node;
+  }
+}
+
+
 // A segment of text within a single node.
 export interface Segment {
   node: ReadAloudNode;
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 679f2ee2..79d5adc 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
@@ -13,8 +13,8 @@
 import {ReadAloudHighlighter} from './highlighter.js';
 import {getReadAloudModel} from './read_aloud_model_browser_proxy.js';
 import type {ReadAloudModelBrowserProxy} from './read_aloud_model_browser_proxy.js';
-import {AxReadAloudNode} from './read_aloud_types.js';
-import type {ReadAloudNode, Segment} from './read_aloud_types.js';
+import {ReadAloudNode} from './read_aloud_types.js';
+import type {Segment} from './read_aloud_types.js';
 import {PauseActionSource, SpeechEngineState, SpeechModel} from './speech_model.js';
 import type {SpeechPlayingState} from './speech_model.js';
 import {getCurrentSpeechRate, isInvalidHighlightForWordHighlighting} from './speech_presentation_rules.js';
@@ -177,7 +177,15 @@
 
     // TODO: crbug.com/40927698 - This step should be skipped on migrating to
     // a non-AXPosition-based text segmentation strategy.
-    this.readAloudModel_.init(new AxReadAloudNode(firstTextNode));
+    const domNode = this.nodeStore_.getDomNode(firstTextNode);
+    if (!domNode) {
+      return;
+    }
+    const readAloudNode = ReadAloudNode.create(domNode);
+    if (!readAloudNode) {
+      return;
+    }
+    this.readAloudModel_.init(readAloudNode);
   }
 
   onSelectionChange() {
@@ -404,8 +412,15 @@
     // Iterate through the nodes asynchronously so that we can show the spinner
     // in the toolbar while we move up to the selection.
     setTimeout(() => {
-      this.movePlaybackToNode_(
-          new AxReadAloudNode(startingNodeId), startingOffset);
+      const domNode = this.nodeStore_.getDomNode(startingNodeId);
+      if (!domNode) {
+        return;
+      }
+      const readAloudNode = ReadAloudNode.create(domNode);
+      if (!readAloudNode) {
+        return;
+      }
+      this.movePlaybackToNode_(readAloudNode, startingOffset);
       // Set everything to previous and then play the next granularity, which
       // includes the selection.
       this.highlighter_.resetPreviousHighlight();
@@ -830,8 +845,7 @@
     }
 
     const lastNode = lastPosition.node;
-    if (lastNode instanceof AxReadAloudNode &&
-        this.nodeStore_.getDomNode(lastNode.axNodeId)) {
+    if (lastNode.domNode()) {
       this.movePlaybackToNode_(lastNode, lastPosition.offset);
       this.setState_(savedSpeechPlayingState);
       this.wordBoundaries_.state = savedWordBoundaryState;
diff --git a/chrome/browser/resources/webui_browser/BUILD.gn b/chrome/browser/resources/webui_browser/BUILD.gn
index 197f9eb0..99b66be 100644
--- a/chrome/browser/resources/webui_browser/BUILD.gn
+++ b/chrome/browser/resources/webui_browser/BUILD.gn
@@ -21,6 +21,7 @@
     "browser_proxy.ts",
     "content_region.ts",
     "icons.html.ts",
+    "side_panel.html.ts",
     "side_panel.ts",
     "tab_element.ts",
     "tab_element.html.ts",
@@ -35,6 +36,7 @@
     "app.css",
     "bookmark_element.css",
     "content_region.css",
+    "side_panel.css",
     "tab_element.css",
     "tab_strip.css",
     "webview.css",
diff --git a/chrome/browser/resources/webui_browser/app.css b/chrome/browser/resources/webui_browser/app.css
index 4b89b69a..4d7164e 100644
--- a/chrome/browser/resources/webui_browser/app.css
+++ b/chrome/browser/resources/webui_browser/app.css
@@ -137,3 +137,11 @@
 content-region {
   border-top: 1px var(--color-toolbar-separator) solid;
 }
+
+content-region[showing-side-panel] {
+  border-top: 1px var(--color-toolbar-separator) solid;
+  border-right: 1px var(--color-toolbar-separator) solid;
+  border-left: none;
+  border-bottom: none;
+  border-radius: 0 16px 0 0;
+}
\ No newline at end of file
diff --git a/chrome/browser/resources/webui_browser/app.html.ts b/chrome/browser/resources/webui_browser/app.html.ts
index 29c5298..2e12f252 100644
--- a/chrome/browser/resources/webui_browser/app.html.ts
+++ b/chrome/browser/resources/webui_browser/app.html.ts
@@ -58,9 +58,11 @@
     </webui-browser-bookmark-bar>
   </div>
   <div id="main">
-    <content-region id="contentRegion"">
+    <content-region id="contentRegion"
+      ?showing-side-panel="${this.showingSidePanel_}">
     </content-region>
-    <side-panel id="sidePanel"></side-panel>
+    <side-panel id="sidePanel" @side-panel-closed="${this.onSidePanelClosed_}">
+    </side-panel>
   </div>
 </div>
 
diff --git a/chrome/browser/resources/webui_browser/app.ts b/chrome/browser/resources/webui_browser/app.ts
index cc59d665..9a97092e 100644
--- a/chrome/browser/resources/webui_browser/app.ts
+++ b/chrome/browser/resources/webui_browser/app.ts
@@ -57,6 +57,7 @@
     return {
       backButtonDisabled_: {state: true, type: Boolean},
       forwardButtonDisabled_: {state: true, type: Boolean},
+      showingSidePanel_: {state: true, type: Boolean},
       reloadOrStopIcon_: {state: true, type: String},
     };
   }
@@ -67,6 +68,7 @@
   protected accessor backButtonDisabled_: boolean = true;
   protected accessor forwardButtonDisabled_: boolean = true;
   protected accessor reloadOrStopIcon_: string = 'icon-refresh';
+  protected accessor showingSidePanel_: boolean = false;
 
   constructor() {
     super();
@@ -78,6 +80,7 @@
 
     const callbackRouter = BrowserProxy.getCallbackRouter();
     callbackRouter.showSidePanel.addListener(this.showSidePanel_.bind(this));
+    callbackRouter.closeSidePanel.addListener(this.closeSidePanel_.bind(this));
   }
 
   override connectedCallback() {
@@ -247,8 +250,21 @@
     this.reloadOrStopIcon_ = isLoading ? 'icon-clear' : 'icon-refresh';
   }
 
-  protected showSidePanel_(guestContentsId: number) {
-    this.$.sidePanel.show(guestContentsId);
+
+  protected showSidePanel_(guestContentsId: number, title: string) {
+    this.showingSidePanel_ = true;
+    this.$.sidePanel.show(guestContentsId, title);
+  }
+
+  protected closeSidePanel_() {
+    this.$.sidePanel.close();
+    this.showingSidePanel_ = false;
+  }
+
+  // This function is called when the side panel closes itself. For example,
+  // when user clicks the close "x" button.
+  protected onSidePanelClosed_() {
+    this.showingSidePanel_ = false;
   }
 }
 
diff --git a/chrome/browser/resources/webui_browser/side_panel.css b/chrome/browser/resources/webui_browser/side_panel.css
new file mode 100644
index 0000000..fa7b559
--- /dev/null
+++ b/chrome/browser/resources/webui_browser/side_panel.css
@@ -0,0 +1,42 @@
+/* Copyright 2025 The Chromium Authors
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file. */
+
+/* #css_wrapper_metadata_start
+ * #type=style-lit
+ * #scheme=relative
+ * #css_wrapper_metadata_end */
+
+#frame {
+  display: flex;
+  flex-direction: column;
+  height: 100%;
+  background-color: var(--color-toolbar);
+  padding: 8px;
+  box-sizing: border-box;
+}
+
+#header {
+  display: flex;
+  align-items: center;
+  padding: 0 10px;
+}
+
+#header h2 {
+  font-size: 14px;
+  font-weight: 500;
+  line-height: 20px;
+}
+
+#closeButton {
+  --cr-icon-button-icon-size: 16px;
+  /* Put button at the header tail */
+  margin-left: auto;
+}
+
+#content {
+  flex: 1;
+  border-radius: 16px;
+  border: 1px var(--color-side-panel-content-area-separator) solid;
+  overflow: hidden;
+}
diff --git a/chrome/browser/resources/webui_browser/side_panel.html.ts b/chrome/browser/resources/webui_browser/side_panel.html.ts
new file mode 100644
index 0000000..407eb04b
--- /dev/null
+++ b/chrome/browser/resources/webui_browser/side_panel.html.ts
@@ -0,0 +1,31 @@
+// Copyright 2025 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import '//resources/cr_elements/cr_icon_button/cr_icon_button.js';
+
+import {html, nothing} from 'chrome://resources/lit/v3_0/lit.rollup.js';
+
+import type {SidePanel} from './side_panel.js';
+
+export function getHtml(this: SidePanel) {
+  if (!this.showing_) {
+    return nothing;
+  }
+
+  // clang-format off
+  return html`<!--_html_template_start_-->
+<div id="frame">
+  <div id="header">
+  <h2>${this.title_}</h2>
+  <cr-icon-button
+      id="closeButton"
+      iron-icon="cr:clear"
+      @click="${this.close}">
+  </cr-icon-button>
+  </div>
+  <div id="content">${this.webView}</div>
+</div>
+<!--_html_template_end_-->`;
+  // clang-format on
+}
diff --git a/chrome/browser/resources/webui_browser/side_panel.ts b/chrome/browser/resources/webui_browser/side_panel.ts
index 40f799a..05be51c 100644
--- a/chrome/browser/resources/webui_browser/side_panel.ts
+++ b/chrome/browser/resources/webui_browser/side_panel.ts
@@ -2,8 +2,11 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-import {CrLitElement, nothing} from '//resources/lit/v3_0/lit.rollup.js';
+import {CrLitElement} from '//resources/lit/v3_0/lit.rollup.js';
 
+import {BrowserProxy} from './browser_proxy.js';
+import {getCss} from './side_panel.css.js';
+import {getHtml} from './side_panel.html.js';
 import {WebviewElement} from './webview.js';
 
 export class SidePanel extends CrLitElement {
@@ -11,24 +14,42 @@
     return 'side-panel';
   }
 
-  private webView_?: WebviewElement;
-  private showing_: boolean = false;
-
-  override render() {
-    if (!this.showing_) {
-      return nothing;
-    }
-
-    return this.webView_;
+  static override get styles() {
+    return getCss();
   }
 
-  show(guestContentsId: number) {
-    this.webView_ = new WebviewElement();
-    this.webView_.guestId = guestContentsId;
+  override render() {
+    return getHtml.bind(this)();
+  }
+
+  static override get properties() {
+    return {
+      showing_: {state: true, type: Boolean},
+      title_: {state: true, type: String},
+      webView: {state: true, type: Object},
+    };
+  }
+
+  accessor webView: WebviewElement|null = null;
+  protected accessor title_: string = '';
+  protected accessor showing_: boolean = false;
+
+  show(guestContentsId: number, title: string) {
+    this.webView = new WebviewElement();
+    this.webView.guestId = guestContentsId;
+    this.title_ = title;
     this.showing_ = true;
     this.requestUpdate();
   }
 
+  async close() {
+    this.showing_ = false;
+    this.webView = null;
+    this.dispatchEvent(new Event('side-panel-closed', {bubbles: true}));
+    await this.updateComplete;
+    BrowserProxy.getPageHandler().onSidePanelClosed();
+  }
+
   hide() {
     this.showing_ = false;
   }
diff --git a/chrome/browser/safe_browsing/android/password_reuse_controller_android.cc b/chrome/browser/safe_browsing/android/password_reuse_controller_android.cc
index a29027d..5e9559b 100644
--- a/chrome/browser/safe_browsing/android/password_reuse_controller_android.cc
+++ b/chrome/browser/safe_browsing/android/password_reuse_controller_android.cc
@@ -87,16 +87,6 @@
     return l10n_util::GetStringUTF16(IDS_CLOSE);
   }
 
-  // This is a rare corner-case. It can only occur for users with no GMS Core,
-  // outdated GMS Core, or users who have failed automatic password migration
-  // to GMS Core. In addition, for this case to occur the user has to have
-  // entered a password on a phishing website in the exact time interval in
-  // which Chrome was exporting passwods to an internally-stored CSV or
-  // in-between export tries if the first attempt failed.
-  if (!password_manager_android_util::LoginDbDeprecationReady(pref_service_)) {
-    return l10n_util::GetStringUTF16(IDS_CLOSE);
-  }
-
   if (password_type_.account_type() ==
       ReusedPasswordAccountType::SAVED_PASSWORD) {
     return l10n_util::GetStringUTF16(IDS_PAGE_INFO_CHECK_PASSWORDS_BUTTON);
@@ -117,10 +107,6 @@
     return std::u16string();
   }
 
-  if (!password_manager_android_util::LoginDbDeprecationReady(pref_service_)) {
-    return std::u16string();
-  }
-
   if (password_type_.account_type() ==
       ReusedPasswordAccountType::SAVED_PASSWORD) {
     return l10n_util::GetStringUTF16(
diff --git a/chrome/browser/safe_browsing/android/password_reuse_controller_android_unittest.cc b/chrome/browser/safe_browsing/android/password_reuse_controller_android_unittest.cc
index 4c7b0753..db5aecdc 100644
--- a/chrome/browser/safe_browsing/android/password_reuse_controller_android_unittest.cc
+++ b/chrome/browser/safe_browsing/android/password_reuse_controller_android_unittest.cc
@@ -13,7 +13,6 @@
 #include "chrome/browser/safe_browsing/chrome_password_protection_service.h"
 #include "chrome/test/base/chrome_render_view_host_test_harness.h"
 #include "components/password_manager/core/browser/features/password_features.h"
-#include "components/password_manager/core/browser/split_stores_and_local_upm.h"
 #include "components/prefs/pref_service.h"
 #include "components/prefs/testing_pref_service.h"
 #include "components/strings/grit/components_strings.h"
@@ -132,80 +131,12 @@
   delete controller;
 }
 
-TEST_F(PasswordReuseControllerAndroidTest,
-       VerifyButtonTextLoginDbExportNotDone) {
-  // Password export is only relevant if UPM is not already active.
-  password_manager::SetLegacySplitStoresPrefForTest(profile()->GetPrefs(),
-                                                    false);
-  profile()->GetPrefs()->SetBoolean(
-      password_manager::prefs::kUpmUnmigratedPasswordsExported, false);
-
-  MockOnWarningDone empty_callback;
-  ReusedPasswordAccountType password_type;
-
-  PasswordReuseControllerAndroid* controller =
-      MakeController(nullptr, password_type, empty_callback.Get());
-
-  {
-    ASSERT_EQ(l10n_util::GetStringUTF16(IDS_CLOSE),
-              controller->GetPrimaryButtonText());
-    ASSERT_EQ(std::u16string(), controller->GetSecondaryButtonText());
-  }
-  {
-    password_type.set_account_type(ReusedPasswordAccountType::SAVED_PASSWORD);
-    password_type.set_is_account_syncing(false);
-
-    controller->SetReusedPasswordAccountTypeForTesting(password_type);
-
-    ASSERT_EQ(l10n_util::GetStringUTF16(IDS_CLOSE),
-              controller->GetPrimaryButtonText());
-    ASSERT_EQ(std::u16string(), controller->GetSecondaryButtonText());
-  }
-  {
-    password_type.set_account_type(ReusedPasswordAccountType::GMAIL);
-    password_type.set_is_account_syncing(true);
-
-    controller->SetReusedPasswordAccountTypeForTesting(password_type);
-
-    ASSERT_EQ(l10n_util::GetStringUTF16(IDS_PAGE_INFO_PROTECT_ACCOUNT_BUTTON),
-              controller->GetPrimaryButtonText());
-    ASSERT_EQ(
-        l10n_util::GetStringUTF16(IDS_PAGE_INFO_IGNORE_PASSWORD_WARNING_BUTTON),
-        controller->GetSecondaryButtonText());
-  }
-  {
-    ReusedPasswordAccountType empty_reused_password;
-    controller->SetReusedPasswordAccountTypeForTesting(empty_reused_password);
-
-    ASSERT_EQ(l10n_util::GetStringUTF16(IDS_CLOSE),
-              controller->GetPrimaryButtonText());
-    ASSERT_EQ(std::u16string(), controller->GetSecondaryButtonText());
-  }
-  {
-    password_type.set_account_type(ReusedPasswordAccountType::GMAIL);
-    password_type.set_is_account_syncing(false);
-
-    controller->SetReusedPasswordAccountTypeForTesting(password_type);
-
-    ASSERT_EQ(l10n_util::GetStringUTF16(IDS_CLOSE),
-              controller->GetPrimaryButtonText());
-    ASSERT_EQ(std::u16string(), controller->GetSecondaryButtonText());
-  }
-
-  delete controller;
-}
-
-TEST_F(PasswordReuseControllerAndroidTest,
-       VerifyButtonTextLoginDbDeprecationUPMActive) {
+TEST_F(PasswordReuseControllerAndroidTest, VerifyButtonText) {
   // Skipping on automotive, since the regular button text for
   // SAVED_PASSWORD does not apply there.
   if (base::android::device_info::is_automotive()) {
     GTEST_SKIP() << "This test should not run on automotive.";
   }
-  password_manager::SetLegacySplitStoresPrefForTest(profile()->GetPrefs(),
-                                                    true);
-  profile()->GetPrefs()->SetBoolean(
-      password_manager::prefs::kUpmUnmigratedPasswordsExported, false);
 
   MockOnWarningDone empty_callback;
   ReusedPasswordAccountType password_type;
diff --git a/chrome/browser/safety_check/android/java/src/org/chromium/chrome/browser/safety_check/SafetyCheckCoordinator.java b/chrome/browser/safety_check/android/java/src/org/chromium/chrome/browser/safety_check/SafetyCheckCoordinator.java
index 40fe866d..7d5a7d16 100644
--- a/chrome/browser/safety_check/android/java/src/org/chromium/chrome/browser/safety_check/SafetyCheckCoordinator.java
+++ b/chrome/browser/safety_check/android/java/src/org/chromium/chrome/browser/safety_check/SafetyCheckCoordinator.java
@@ -22,7 +22,6 @@
 import org.chromium.chrome.browser.password_manager.PasswordStoreBridge;
 import org.chromium.chrome.browser.pwd_check_wrapper.PasswordCheckControllerFactory;
 import org.chromium.components.browser_ui.settings.SettingsCustomTabLauncher;
-import org.chromium.components.prefs.PrefService;
 import org.chromium.components.signin.base.CoreAccountInfo;
 import org.chromium.components.sync.SyncService;
 import org.chromium.ui.modaldialog.ModalDialogManager;
@@ -62,7 +61,6 @@
             SafetyCheckBridge bridge,
             ObservableSupplier<@Nullable ModalDialogManager> modalDialogManagerSupplier,
             @Nullable SyncService syncService,
-            PrefService prefService,
             PasswordStoreBridge passwordStoreBridge,
             PasswordManagerHelper passwordManagerHelper,
             SettingsCustomTabLauncher settingsCustomTabLauncher) {
@@ -72,7 +70,6 @@
                 bridge,
                 modalDialogManagerSupplier,
                 syncService,
-                prefService,
                 passwordStoreBridge,
                 passwordManagerHelper,
                 settingsCustomTabLauncher);
@@ -84,7 +81,6 @@
             SafetyCheckBridge bridge,
             ObservableSupplier<@Nullable ModalDialogManager> modalDialogManagerSupplier,
             @Nullable SyncService syncService,
-            PrefService prefService,
             PasswordStoreBridge passwordStoreBridge,
             PasswordManagerHelper passwordManagerHelper,
             SettingsCustomTabLauncher settingsCustomTabLauncher) {
@@ -128,7 +124,6 @@
                                                     mUpdatesClient,
                                                     bridge,
                                                     syncService,
-                                                    prefService,
                                                     new Handler(),
                                                     passwordStoreBridge,
                                                     new PasswordCheckControllerFactory(),
diff --git a/chrome/browser/safety_check/android/java/src/org/chromium/chrome/browser/safety_check/SafetyCheckMediator.java b/chrome/browser/safety_check/android/java/src/org/chromium/chrome/browser/safety_check/SafetyCheckMediator.java
index 63a8a60..a9d799d 100644
--- a/chrome/browser/safety_check/android/java/src/org/chromium/chrome/browser/safety_check/SafetyCheckMediator.java
+++ b/chrome/browser/safety_check/android/java/src/org/chromium/chrome/browser/safety_check/SafetyCheckMediator.java
@@ -44,7 +44,6 @@
 import org.chromium.chrome.browser.safety_check.SafetyCheckProperties.UpdatesState;
 import org.chromium.chrome.browser.settings.SettingsNavigationFactory;
 import org.chromium.components.browser_ui.settings.SettingsCustomTabLauncher;
-import org.chromium.components.prefs.PrefService;
 import org.chromium.components.sync.SyncService;
 import org.chromium.content_public.common.ContentUrlConstants;
 import org.chromium.ui.modaldialog.ModalDialogManager;
@@ -190,7 +189,6 @@
             SafetyCheckUpdatesDelegate client,
             SafetyCheckBridge bridge,
             @Nullable SyncService syncService,
-            PrefService prefService,
             Handler handler,
             PasswordStoreBridge passwordStoreBridge,
             PasswordCheckControllerFactory passwordCheckControllerFactory,
@@ -207,7 +205,7 @@
         mPreferenceManager = ChromeSharedPreferences.getInstance();
         mPasswordCheckController =
                 passwordCheckControllerFactory.create(
-                        syncService, prefService, passwordStoreBridge, passwordManagerHelper);
+                        syncService, passwordStoreBridge, passwordManagerHelper);
         mPasswordManagerHelper = passwordManagerHelper;
         mModalDialogManagerSupplier = modalDialogManagerSupplier;
         mSettingsCustomTabLauncher = settingsCustomTabLauncher;
diff --git a/chrome/browser/safety_check/android/javatests/src/org/chromium/chrome/browser/safety_check/SafetyCheckMediatorTest.java b/chrome/browser/safety_check/android/javatests/src/org/chromium/chrome/browser/safety_check/SafetyCheckMediatorTest.java
index ae7d86a..6493d6f 100644
--- a/chrome/browser/safety_check/android/javatests/src/org/chromium/chrome/browser/safety_check/SafetyCheckMediatorTest.java
+++ b/chrome/browser/safety_check/android/javatests/src/org/chromium/chrome/browser/safety_check/SafetyCheckMediatorTest.java
@@ -72,12 +72,9 @@
 import org.chromium.chrome.browser.sync.SyncServiceFactory;
 import org.chromium.components.browser_ui.settings.SettingsCustomTabLauncher;
 import org.chromium.components.browser_ui.settings.SettingsNavigation;
-import org.chromium.components.prefs.PrefService;
 import org.chromium.components.signin.base.CoreAccountInfo;
 import org.chromium.components.sync.SyncService;
 import org.chromium.components.sync.UserSelectableType;
-import org.chromium.components.user_prefs.UserPrefs;
-import org.chromium.components.user_prefs.UserPrefsJni;
 import org.chromium.content_public.browser.BrowserContextHandle;
 import org.chromium.google_apis.gaia.GaiaId;
 import org.chromium.ui.modaldialog.ModalDialogManager;
@@ -122,8 +119,6 @@
     @Mock private PasswordCheckupClientHelper mPasswordCheckupHelper;
     @Mock private CredentialManagerLauncher mCredentialManagerLauncher;
     @Mock private PasswordStoreBridge mPasswordStoreBridge;
-    @Mock private PrefService mPrefService;
-    @Mock private UserPrefs.Natives mUserPrefsJniMock;
 
     // TODO(crbug.com/40854050): Use fake instead of mocking
     @Mock private PasswordManagerBackendSupportHelper mBackendSupportHelperMock;
@@ -210,7 +205,6 @@
                 mUpdatesDelegate,
                 new SafetyCheckBridge(mProfile),
                 mSyncService,
-                mPrefService,
                 mHandler,
                 mPasswordStoreBridge,
                 mPasswordCheckControllerFactory,
@@ -243,14 +237,11 @@
 
         SafetyCheckBridgeJni.setInstanceForTesting(mSafetyCheckBridge);
 
-        UserPrefsJni.setInstanceForTesting(mUserPrefsJniMock);
-        when(mUserPrefsJniMock.get(mProfile)).thenReturn(mPrefService);
-
         mSafetyCheckModel = SafetyCheckProperties.createSafetyCheckModel();
         mPasswordCheckModel =
                 PasswordsCheckPreferenceProperties.createPasswordSafetyCheckModel("Passwords");
         mPasswordCheckControllerFactory = new FakePasswordCheckControllerFactory();
-        when(mPasswordManagerUtilBridgeNativeMock.isPasswordManagerAvailable(mPrefService, true))
+        when(mPasswordManagerUtilBridgeNativeMock.isPasswordManagerAvailable(true))
                 .thenReturn(mUseGmsApi);
         // TODO(crbug.com/40854050): Use existing fake instead of mocking
         PasswordCheckupClientHelperFactory mockPasswordCheckFactory =
@@ -692,6 +683,7 @@
     @Test
     public void testClickListenerLeadsToUPMAccountPasswordCheckup() {
         // Order: initial state -> safety check triggered -> check done -> load completed.
+        LoginDbDeprecationUtilBridge.setHasCsvFileForTesting(false);
         mMediator.setInitialState();
         assertEquals(PasswordsState.CHECKING, mPasswordCheckModel.get(PASSWORDS_STATE));
 
@@ -748,6 +740,7 @@
 
     @Test
     public void testClickListenerLeadsToUPMLocalPasswordCheckup() {
+        LoginDbDeprecationUtilBridge.setHasCsvFileForTesting(false);
         PropertyModel passwordCheckLocalModel =
                 PasswordsCheckPreferenceProperties.createPasswordSafetyCheckModel("Passwords");
         mMediator =
diff --git a/chrome/browser/safety_check/android/javatests/src/org/chromium/chrome/browser/safety_check/SafetyCheckSettingsFragmentTest.java b/chrome/browser/safety_check/android/javatests/src/org/chromium/chrome/browser/safety_check/SafetyCheckSettingsFragmentTest.java
index aba2711..df799b1 100644
--- a/chrome/browser/safety_check/android/javatests/src/org/chromium/chrome/browser/safety_check/SafetyCheckSettingsFragmentTest.java
+++ b/chrome/browser/safety_check/android/javatests/src/org/chromium/chrome/browser/safety_check/SafetyCheckSettingsFragmentTest.java
@@ -6,8 +6,6 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.when;
 
 import android.content.Context;
@@ -44,7 +42,6 @@
 import org.chromium.chrome.browser.settings.SettingsActivityTestRule;
 import org.chromium.chrome.browser.sync.SyncServiceFactory;
 import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
-import org.chromium.components.prefs.PrefService;
 import org.chromium.components.signin.base.CoreAccountInfo;
 import org.chromium.components.sync.SyncService;
 import org.chromium.components.sync.UserSelectableType;
@@ -186,8 +183,7 @@
     }
 
     private void configurePasswordManagerUtilBridge() {
-        when(mPasswordManagerUtilBridgeNativeMock.isPasswordManagerAvailable(
-                        any(PrefService.class), eq(true)))
+        when(mPasswordManagerUtilBridgeNativeMock.isPasswordManagerAvailable(true))
                 .thenReturn(true);
     }
 
diff --git a/chrome/browser/safety_hub/android/java/src/org/chromium/chrome/browser/safety_hub/SafetyHubModuleDelegateImpl.java b/chrome/browser/safety_hub/android/java/src/org/chromium/chrome/browser/safety_hub/SafetyHubModuleDelegateImpl.java
index b1b685c3..01d11cc 100644
--- a/chrome/browser/safety_hub/android/java/src/org/chromium/chrome/browser/safety_hub/SafetyHubModuleDelegateImpl.java
+++ b/chrome/browser/safety_hub/android/java/src/org/chromium/chrome/browser/safety_hub/SafetyHubModuleDelegateImpl.java
@@ -25,7 +25,6 @@
 import org.chromium.components.browser_ui.settings.SettingsCustomTabLauncher;
 import org.chromium.components.signin.metrics.SigninAccessPoint;
 import org.chromium.components.sync.SyncService;
-import org.chromium.components.user_prefs.UserPrefs;
 import org.chromium.content_public.common.ContentUrlConstants;
 import org.chromium.ui.modaldialog.ModalDialogManager;
 
@@ -94,7 +93,7 @@
             return INVALID_PASSWORD_COUNT;
         }
 
-        if (PasswordManagerUtilBridge.isPasswordManagerAvailable(UserPrefs.get(mProfile))) {
+        if (PasswordManagerUtilBridge.isPasswordManagerAvailable()) {
             return passwordStoreBridge.getPasswordStoreCredentialsCountForAccountStore();
         }
         return INVALID_PASSWORD_COUNT;
@@ -106,7 +105,7 @@
             return INVALID_PASSWORD_COUNT;
         }
 
-        if (PasswordManagerUtilBridge.isPasswordManagerAvailable(UserPrefs.get(mProfile))) {
+        if (PasswordManagerUtilBridge.isPasswordManagerAvailable()) {
             return passwordStoreBridge.getPasswordStoreCredentialsCountForProfileStore();
         }
         return INVALID_PASSWORD_COUNT;
diff --git a/chrome/browser/safety_hub/android/java/src/org/chromium/chrome/browser/safety_hub/SafetyHubPasswordsFetchService.java b/chrome/browser/safety_hub/android/java/src/org/chromium/chrome/browser/safety_hub/SafetyHubPasswordsFetchService.java
index 1c91caf4..12a58ef 100644
--- a/chrome/browser/safety_hub/android/java/src/org/chromium/chrome/browser/safety_hub/SafetyHubPasswordsFetchService.java
+++ b/chrome/browser/safety_hub/android/java/src/org/chromium/chrome/browser/safety_hub/SafetyHubPasswordsFetchService.java
@@ -143,7 +143,7 @@
 
     /** Returns true if a password fetch can be performed, namely if GMSCore can be called. */
     public boolean canPerformFetch() {
-        return PasswordManagerUtilBridge.isPasswordManagerAvailable(mPrefService);
+        return PasswordManagerUtilBridge.isPasswordManagerAvailable();
     }
 
     public void clearPrefs() {
diff --git a/chrome/browser/safety_hub/android/junit/src/org/chromium/chrome/browser/safety_hub/SafetyHubTestRule.java b/chrome/browser/safety_hub/android/junit/src/org/chromium/chrome/browser/safety_hub/SafetyHubTestRule.java
index 5265bb1..b856631b 100644
--- a/chrome/browser/safety_hub/android/junit/src/org/chromium/chrome/browser/safety_hub/SafetyHubTestRule.java
+++ b/chrome/browser/safety_hub/android/junit/src/org/chromium/chrome/browser/safety_hub/SafetyHubTestRule.java
@@ -109,7 +109,7 @@
     }
 
     public void setPasswordManagerAvailable(boolean isPasswordManagerAvailable) {
-        when(mPasswordManagerUtilBridgeNatives.isPasswordManagerAvailable(mPrefService, true))
+        when(mPasswordManagerUtilBridgeNatives.isPasswordManagerAvailable(true))
                 .thenReturn(isPasswordManagerAvailable);
     }
 
diff --git a/chrome/browser/sessions/session_data_deleter.cc b/chrome/browser/sessions/session_data_deleter.cc
index 81a37a4f4..d9ca5675 100644
--- a/chrome/browser/sessions/session_data_deleter.cc
+++ b/chrome/browser/sessions/session_data_deleter.cc
@@ -114,11 +114,10 @@
         /*perform_storage_cleanup=*/false, base::Time(), base::Time::Max(),
         base::BindOnce(&SessionDataDeleterInternal::OnStorageDeletionDone,
                        this));
-    // The const_cast here is safe, as the profile received in the constructor
-    // is not const. It is just that ScopedProfileKeepAlive wraps it as const.
+
     if (auto* media_device_salt_service =
             MediaDeviceSaltServiceFactory::GetInstance()->GetForBrowserContext(
-                const_cast<Profile*>(profile_keep_alive_->profile()))) {
+                profile_keep_alive_->profile())) {
       media_device_salt_service->DeleteSalts(
           base::Time(), base::Time::Max(),
           base::BindRepeating(&StorageKeyMatcher, storage_policy_),
diff --git a/chrome/browser/signin/signin_promo_unittest.cc b/chrome/browser/signin/signin_promo_unittest.cc
index 68936f1..bf9ef97 100644
--- a/chrome/browser/signin/signin_promo_unittest.cc
+++ b/chrome/browser/signin/signin_promo_unittest.cc
@@ -348,7 +348,8 @@
 
   autofill::AutofillProfile CreateAddress(
       const std::string& country_code = "US") {
-    return autofill::test::StandardProfile(AddressCountryCode(country_code));
+    return autofill::test::StandardProfile(
+        autofill::AddressCountryCode(country_code));
   }
 
  private:
diff --git a/chrome/browser/site_protection/site_familiarity_utils.cc b/chrome/browser/site_protection/site_familiarity_utils.cc
new file mode 100644
index 0000000..1bce109
--- /dev/null
+++ b/chrome/browser/site_protection/site_familiarity_utils.cc
@@ -0,0 +1,23 @@
+// Copyright 2025 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/site_protection/site_familiarity_utils.h"
+
+#include "components/content_settings/core/common/features.h"
+#include "components/prefs/pref_service.h"
+#include "components/safe_browsing/core/common/safe_browsing_prefs.h"
+#include "content/public/common/content_features.h"
+
+namespace site_protection {
+
+bool AreV8OptimizationsDisabledOnUnfamiliarSites(const PrefService& prefs) {
+  return base::FeatureList::IsEnabled(
+             features::kProcessSelectionDeferringConditions) &&
+         base::FeatureList::IsEnabled(
+             content_settings::features::
+                 kBlockV8OptimizerOnUnfamiliarSitesSetting) &&
+         prefs.GetBoolean(prefs::kJavascriptOptimizerBlockedForUnfamiliarSites);
+}
+
+}  // namespace site_protection
diff --git a/chrome/browser/site_protection/site_familiarity_utils.h b/chrome/browser/site_protection/site_familiarity_utils.h
new file mode 100644
index 0000000..0e65183
--- /dev/null
+++ b/chrome/browser/site_protection/site_familiarity_utils.h
@@ -0,0 +1,19 @@
+// Copyright 2025 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_SITE_PROTECTION_SITE_FAMILIARITY_UTILS_H_
+#define CHROME_BROWSER_SITE_PROTECTION_SITE_FAMILIARITY_UTILS_H_
+
+class PrefService;
+
+namespace site_protection {
+
+// Returns whether v8-optimizations are disabled on sites which are unfamiliar
+// to the user. Site familiarity is computed using a heuristic based on the
+// user's navigation history.
+bool AreV8OptimizationsDisabledOnUnfamiliarSites(const PrefService& prefs);
+
+}  // namespace site_protection
+
+#endif  // CHROME_BROWSER_SITE_PROTECTION_SITE_FAMILIARITY_UTILS_H_
diff --git a/chrome/browser/site_protection/site_protection_metrics_observer.cc b/chrome/browser/site_protection/site_protection_metrics_observer.cc
index 7c38cb3..2b89973 100644
--- a/chrome/browser/site_protection/site_protection_metrics_observer.cc
+++ b/chrome/browser/site_protection/site_protection_metrics_observer.cc
@@ -13,6 +13,7 @@
 #include "chrome/browser/history/history_service_factory.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/site_protection/site_familiarity_heuristic_name.h"
+#include "chrome/browser/site_protection/site_familiarity_utils.h"
 #include "chrome/browser/site_protection/site_protection_metrics.h"
 #include "components/history/core/browser/history_service.h"
 #include "components/history/core/browser/history_types.h"
@@ -139,8 +140,7 @@
 
 void SiteProtectionMetricsObserver::DidFinishNavigation(
     content::NavigationHandle* navigation_handle) {
-  if (!base::FeatureList::IsEnabled(
-          features::kProcessSelectionDeferringConditions)) {
+  if (!AreV8OptimizationsDisabledOnUnfamiliarSites(*profile_->GetPrefs())) {
     return;
   }
 
diff --git a/chrome/browser/site_protection/site_protection_metrics_observer_unittest.cc b/chrome/browser/site_protection/site_protection_metrics_observer_unittest.cc
index fcefb6c..28581ff 100644
--- a/chrome/browser/site_protection/site_protection_metrics_observer_unittest.cc
+++ b/chrome/browser/site_protection/site_protection_metrics_observer_unittest.cc
@@ -20,10 +20,14 @@
 #include "chrome/common/chrome_features.h"
 #include "chrome/test/base/chrome_render_view_host_test_harness.h"
 #include "chrome/test/base/testing_browser_process.h"
+#include "components/content_settings/core/common/features.h"
 #include "components/history/core/browser/history_service.h"
 #include "components/history/core/browser/history_types.h"
+#include "components/prefs/pref_registry_simple.h"
+#include "components/safe_browsing/core/common/safe_browsing_prefs.h"
 #include "components/site_engagement/content/site_engagement_helper.h"
 #include "components/site_engagement/content/site_engagement_service.h"
+#include "components/sync_preferences/testing_pref_service_syncable.h"
 #include "components/ukm/test_ukm_recorder.h"
 #include "content/public/browser/render_process_host.h"
 #include "content/public/browser/spare_render_process_host_manager.h"
@@ -657,6 +661,8 @@
   SiteProtectionMetricsObserverV8OptTest() {
     feature_list_.InitWithFeatures(
         /*enabled_features=*/{features::kProcessSelectionDeferringConditions,
+                              content_settings::features::
+                                  kBlockV8OptimizerOnUnfamiliarSitesSetting,
                               features::kOriginKeyedProcessesByDefault},
         /*disabled_features=*/{});
   }
@@ -665,6 +671,9 @@
     old_browser_client_ = SetBrowserClientForTesting(&browser_client_);
     SiteProtectionMetricsObserverTest::SetUp();
 
+    profile()->GetTestingPrefService()->SetBoolean(
+        prefs::kJavascriptOptimizerBlockedForUnfamiliarSites, true);
+
     content::SpareRenderProcessHostManager::Get().CleanupSparesForTesting();
 
     test_render_process_host_factory_ =
diff --git a/chrome/browser/sync/test/integration/autofill_helper.cc b/chrome/browser/sync/test/integration/autofill_helper.cc
index ad1081f..a1e9393 100644
--- a/chrome/browser/sync/test/integration/autofill_helper.cc
+++ b/chrome/browser/sync/test/integration/autofill_helper.cc
@@ -217,7 +217,7 @@
 }
 
 AutofillProfile CreateUniqueAutofillProfile() {
-  AutofillProfile profile(AddressCountryCode("US"));
+  AutofillProfile profile(autofill::AddressCountryCode("US"));
   autofill::test::SetProfileInfoWithGuid(
       &profile, base::Uuid::GenerateRandomV4().AsLowercaseString().c_str(),
       "First", "Middle", "Last", "email@domain.tld", "Company", "123 Main St",
diff --git a/chrome/browser/ui/android/fast_checkout/ui_view_android_utils.cc b/chrome/browser/ui/android/fast_checkout/ui_view_android_utils.cc
index 2a29976..a475f0a 100644
--- a/chrome/browser/ui/android/fast_checkout/ui_view_android_utils.cc
+++ b/chrome/browser/ui/android/fast_checkout/ui_view_android_utils.cc
@@ -114,8 +114,9 @@
     JNIEnv* env,
     const base::android::JavaParamRef<jobject>& jprofile,
     const std::string& locale) {
-  AddressCountryCode country_code = AddressCountryCode(ConvertJavaStringToUTF8(
-      Java_FastCheckoutAutofillProfile_getCountryCode(env, jprofile)));
+  autofill::AddressCountryCode country_code =
+      autofill::AddressCountryCode(ConvertJavaStringToUTF8(
+          Java_FastCheckoutAutofillProfile_getCountryCode(env, jprofile)));
   auto profile = std::make_unique<autofill::AutofillProfile>(country_code);
   // Only set the guid if it is an existing profile (Java guid not empty).
   // Otherwise, keep the generated one.
diff --git a/chrome/browser/ui/android/toolbar/java/res/layout/toolbar_tablet.xml b/chrome/browser/ui/android/toolbar/java/res/layout/toolbar_tablet.xml
index 27dc461..48a8120 100644
--- a/chrome/browser/ui/android/toolbar/java/res/layout/toolbar_tablet.xml
+++ b/chrome/browser/ui/android/toolbar/java/res/layout/toolbar_tablet.xml
@@ -87,8 +87,6 @@
             android:layout="@layout/incognito_indicator"
             android:layout_width="wrap_content"
             android:layout_height="match_parent"
-            android:layout_marginVertical="@dimen/location_bar_vertical_margin"
-            android:layout_marginHorizontal="@dimen/incognito_indicator_lateral_margin"
             android:visibility="gone" />
 
         <ViewStub
diff --git a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/ToolbarProgressBar.java b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/ToolbarProgressBar.java
index eea3372..63c87516 100644
--- a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/ToolbarProgressBar.java
+++ b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/ToolbarProgressBar.java
@@ -163,7 +163,8 @@
         setAlpha(0.0f);
         mAnimationLogic = new ProgressAnimationSmooth();
 
-        if (!ChromeFeatureList.sAndroidAnimatedProgressBarInViz.isEnabled()) {
+        if (!(ChromeFeatureList.sAndroidAnimatedProgressBarInViz.isEnabled()
+                || ChromeFeatureList.sAndroidAnimatedProgressBarInBrowser.isEnabled())) {
             setVisibility(View.VISIBLE);
         }
 
diff --git a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/TopToolbarOverlayMediator.java b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/TopToolbarOverlayMediator.java
index 3c94cf1..e5ed9ae1 100644
--- a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/TopToolbarOverlayMediator.java
+++ b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/TopToolbarOverlayMediator.java
@@ -4,12 +4,16 @@
 
 package org.chromium.chrome.browser.toolbar.top;
 
+import android.animation.TimeAnimator;
+import android.animation.TimeAnimator.TimeListener;
 import android.content.Context;
+import android.graphics.Rect;
 import android.view.View;
 
 import androidx.annotation.ColorInt;
 
 import org.chromium.base.Callback;
+import org.chromium.base.MathUtils;
 import org.chromium.base.ResettersForTesting;
 import org.chromium.base.supplier.ObservableSupplier;
 import org.chromium.build.annotations.NullMarked;
@@ -114,6 +118,25 @@
     private @Nullable OffsetTag mBottomProgressBarOffsetTag;
     private @ControlsPosition int mControlsPosition;
 
+    private float mAnimatedProgress;
+    private float mTargetProgress;
+    private static final long ANIMATION_DURATION_MS = 3000;
+
+    private final TimeAnimator mProgressBarAnimation = new TimeAnimator();
+
+    {
+        mProgressBarAnimation.setTimeListener(
+                new TimeListener() {
+                    @Override
+                    public void onTimeUpdate(
+                            TimeAnimator animation, long totalTimeMs, long deltaTimeMs) {
+                        mAnimatedProgress += (deltaTimeMs / ((float) ANIMATION_DURATION_MS));
+                        mAnimatedProgress = Math.min(mAnimatedProgress, mTargetProgress);
+                        updateProgress();
+                    }
+                });
+    }
+
     TopToolbarOverlayMediator(
             PropertyModel model,
             Context context,
@@ -176,7 +199,18 @@
 
                             @Override
                             public void onLoadProgressChanged(Tab tab, float progress) {
-                                updateProgress();
+                                mTargetProgress = progress;
+                                if (ChromeFeatureList.sAndroidAnimatedProgressBarInBrowser
+                                        .isEnabled()) {
+                                    if (MathUtils.areFloatsEqual(1.0f, progress)) {
+                                        mProgressBarAnimation.cancel();
+                                        updateProgress();
+                                    } else if (!mProgressBarAnimation.isStarted()) {
+                                        mProgressBarAnimation.start();
+                                    }
+                                } else {
+                                    updateProgress();
+                                }
                             }
 
                             @Override
@@ -397,6 +431,23 @@
             onProgressBarOffsetTagsChanged();
         }
 
+        if (ChromeFeatureList.sAndroidAnimatedProgressBarInBrowser.isEnabled()) {
+            Rect foregroundRect = drawingInfo.progressBarRect;
+            Rect backgroundRect = drawingInfo.progressBarBackgroundRect;
+            Rect staticBackgroundRect = drawingInfo.progressBarStaticBackgroundRect;
+            int progressX =
+                    foregroundRect.left
+                            + Math.round(mAnimatedProgress * staticBackgroundRect.width());
+            int gap = backgroundRect.left - foregroundRect.right;
+            drawingInfo.progressBarRect.set(
+                    foregroundRect.left, foregroundRect.top, progressX, foregroundRect.bottom);
+            drawingInfo.progressBarBackgroundRect.set(
+                    progressX + gap,
+                    backgroundRect.top,
+                    backgroundRect.right,
+                    backgroundRect.bottom);
+        }
+
         // TODO(https://crbug.com/439461293) Try not updating the model if nothing changed.
         mModel.set(
                 TopToolbarOverlayProperties.PROGRESS_BAR_INFO,
diff --git a/chrome/browser/ui/browser_element_identifiers.cc b/chrome/browser/ui/browser_element_identifiers.cc
index 79a9a0e..963efdd 100644
--- a/chrome/browser/ui/browser_element_identifiers.cc
+++ b/chrome/browser/ui/browser_element_identifiers.cc
@@ -20,7 +20,11 @@
 DEFINE_ELEMENT_IDENTIFIER_VALUE(kConstrainedDialogWebViewElementId);
 DEFINE_ELEMENT_IDENTIFIER_VALUE(kContentsCaptureBorder);
 DEFINE_ELEMENT_IDENTIFIER_VALUE(kContentsContainerViewElementId);
-DEFINE_ELEMENT_IDENTIFIER_VALUE(kContentsSeparatorViewElementId);
+DEFINE_ELEMENT_IDENTIFIER_VALUE(kContentsSeparatorLeadingEdgeElementId);
+DEFINE_ELEMENT_IDENTIFIER_VALUE(kContentsSeparatorLeadingTopCornerElementId);
+DEFINE_ELEMENT_IDENTIFIER_VALUE(kContentsSeparatorTopEdgeElementId);
+DEFINE_ELEMENT_IDENTIFIER_VALUE(kContentsSeparatorTrailingEdgeElementId);
+DEFINE_ELEMENT_IDENTIFIER_VALUE(kContentsSeparatorTrailingTopCornerElementId);
 DEFINE_ELEMENT_IDENTIFIER_VALUE(kCookieControlsIconElementId);
 DEFINE_ELEMENT_IDENTIFIER_VALUE(kCustomizeChromeSidePanelWebViewElementId);
 DEFINE_ELEMENT_IDENTIFIER_VALUE(kDataSharingBubbleElementId);
diff --git a/chrome/browser/ui/browser_element_identifiers.h b/chrome/browser/ui/browser_element_identifiers.h
index 48e939a..aabebbd 100644
--- a/chrome/browser/ui/browser_element_identifiers.h
+++ b/chrome/browser/ui/browser_element_identifiers.h
@@ -29,7 +29,11 @@
 DECLARE_ELEMENT_IDENTIFIER_VALUE(kConstrainedDialogWebViewElementId);
 DECLARE_ELEMENT_IDENTIFIER_VALUE(kContentsCaptureBorder);
 DECLARE_ELEMENT_IDENTIFIER_VALUE(kContentsContainerViewElementId);
-DECLARE_ELEMENT_IDENTIFIER_VALUE(kContentsSeparatorViewElementId);
+DECLARE_ELEMENT_IDENTIFIER_VALUE(kContentsSeparatorLeadingEdgeElementId);
+DECLARE_ELEMENT_IDENTIFIER_VALUE(kContentsSeparatorLeadingTopCornerElementId);
+DECLARE_ELEMENT_IDENTIFIER_VALUE(kContentsSeparatorTopEdgeElementId);
+DECLARE_ELEMENT_IDENTIFIER_VALUE(kContentsSeparatorTrailingEdgeElementId);
+DECLARE_ELEMENT_IDENTIFIER_VALUE(kContentsSeparatorTrailingTopCornerElementId);
 DECLARE_ELEMENT_IDENTIFIER_VALUE(kCookieControlsIconElementId);
 DECLARE_ELEMENT_IDENTIFIER_VALUE(kCustomizeChromeSidePanelWebViewElementId);
 DECLARE_ELEMENT_IDENTIFIER_VALUE(kDataSharingBubbleElementId);
diff --git a/chrome/browser/ui/browser_window/internal/BUILD.gn b/chrome/browser/ui/browser_window/internal/BUILD.gn
index 07e44ea..26cbf4e 100644
--- a/chrome/browser/ui/browser_window/internal/BUILD.gn
+++ b/chrome/browser/ui/browser_window/internal/BUILD.gn
@@ -61,6 +61,7 @@
       "//chrome/browser/ui/tabs/organization",
       "//chrome/browser/ui/tabs/saved_tab_groups",
       "//chrome/browser/ui/tabs/tab_strip_api",
+      "//chrome/browser/ui/tabs/tab_strip_api:impl_header",
       "//chrome/browser/ui/toasts",
       "//chrome/browser/ui/toolbar/chrome_labs",
       "//chrome/browser/ui/views/download",
diff --git a/chrome/browser/ui/browser_window/internal/browser_window_features.cc b/chrome/browser/ui/browser_window/internal/browser_window_features.cc
index 25ab77d..3b295f50 100644
--- a/chrome/browser/ui/browser_window/internal/browser_window_features.cc
+++ b/chrome/browser/ui/browser_window/internal/browser_window_features.cc
@@ -59,7 +59,7 @@
 #include "chrome/browser/ui/tabs/split_tab_highlight_controller.h"
 #include "chrome/browser/ui/tabs/tab_group_deletion_dialog_controller.h"
 #include "chrome/browser/ui/tabs/tab_list_bridge.h"
-#include "chrome/browser/ui/tabs/tab_strip_api/tab_strip_service_impl.h"
+#include "chrome/browser/ui/tabs/tab_strip_api/tab_strip_service_mojo_handler.h"
 #include "chrome/browser/ui/tabs/vertical_tab_strip_state_controller.h"
 #include "chrome/browser/ui/toasts/toast_controller.h"
 #include "chrome/browser/ui/toasts/toast_features.h"
@@ -144,8 +144,8 @@
 #include "chrome/browser/glic/browser_ui/glic_iph_controller.h"
 #include "chrome/browser/glic/public/glic_enabling.h"
 #include "chrome/browser/glic/public/glic_keyed_service.h"
-#include "chrome/browser/glic/widget/glic_side_panel_coordinator.h"
 #include "chrome/browser/ui/tabs/glic_actor_task_icon_controller.h"
+#include "chrome/browser/ui/views/side_panel/glic/glic_side_panel_coordinator.h"
 #endif
 
 #if defined(USE_AURA)
@@ -256,7 +256,7 @@
   tab_strip_model_ = browser->GetTabStripModel();
 
   tab_strip_service_ =
-      std::make_unique<TabStripServiceImpl>(browser, tab_strip_model_);
+      std::make_unique<TabStripServiceMojoHandler>(browser, tab_strip_model_);
 
   memory_saver_bubble_controller_ =
       std::make_unique<memory_saver::MemorySaverBubbleController>(browser);
@@ -561,7 +561,8 @@
 
 #if BUILDFLAG(ENABLE_GLIC)
   glic_side_panel_coordinator_ =
-      std::make_unique<glic::GlicSidePanelCoordinator>();
+      std::make_unique<glic::GlicSidePanelCoordinator>(
+          browser_view->GetProfile());
 #endif  // BUILDFLAG(ENABLE_GLIC)
 
   side_panel_coordinator_->Init(browser_view->browser());
diff --git a/chrome/browser/ui/lens/lens_overlay_controller_browsertest.cc b/chrome/browser/ui/lens/lens_overlay_controller_browsertest.cc
index 33f45153..ae9eff5 100644
--- a/chrome/browser/ui/lens/lens_overlay_controller_browsertest.cc
+++ b/chrome/browser/ui/lens/lens_overlay_controller_browsertest.cc
@@ -148,6 +148,7 @@
 #include "ui/events/event_constants.h"
 #include "ui/views/accessibility/view_accessibility.h"
 #include "ui/views/controls/webview/webview.h"
+#include "ui/views/interaction/element_tracker_views.h"
 #include "ui/views/test/widget_test.h"
 #include "ui/views/view_utils.h"
 #include "ui/views/widget/any_widget_observer.h"
@@ -8852,6 +8853,23 @@
          {features::kSideBySide, {}}},
         {});
   }
+
+  bool AreAnyRoundedCornersShowing() {
+    const ui::ElementContext context =
+        views::ElementTrackerViews::GetContextForView(
+            BrowserView::GetBrowserViewForBrowser(browser()));
+    views::View* start_corner =
+        views::ElementTrackerViews::GetInstance()->GetFirstMatchingView(
+            kContentsSeparatorLeadingTopCornerElementId, context);
+    views::View* end_corner =
+        views::ElementTrackerViews::GetInstance()->GetFirstMatchingView(
+            kContentsSeparatorTrailingTopCornerElementId, context);
+    return (start_corner && start_corner->GetVisible()) ||
+           (end_corner && end_corner->GetVisible());
+  }
+
+ private:
+  base::test::ScopedFeatureList feature_list_;
 };
 
 IN_PROC_BROWSER_TEST_F(LensOverlayControllerSideBySideBrowserTest,
@@ -8985,9 +9003,7 @@
                        ->layer()
                        ->GetTargetRoundedCornerRadius()
                        .upper_right() > 0);
-  EXPECT_TRUE(BrowserView::GetBrowserViewForBrowser(browser())
-                  ->GetSidePanelRoundedCornerForTesting()
-                  ->GetVisible());
+  EXPECT_TRUE(AreAnyRoundedCornersShowing());
 }
 
 IN_PROC_BROWSER_TEST_F(LensOverlayControllerSideBySideBrowserTest,
@@ -9022,9 +9038,7 @@
                       ->layer()
                       ->GetTargetRoundedCornerRadius()
                       .upper_right() > 0);
-  EXPECT_FALSE(BrowserView::GetBrowserViewForBrowser(browser())
-                   ->GetSidePanelRoundedCornerForTesting()
-                   ->GetVisible());
+  EXPECT_FALSE(AreAnyRoundedCornersShowing());
 
   // Switch to the first tab.
   browser()->tab_strip_model()->ActivateTabAt(0);
@@ -9048,9 +9062,7 @@
                        ->layer()
                        ->GetTargetRoundedCornerRadius()
                        .upper_right() > 0);
-  EXPECT_FALSE(BrowserView::GetBrowserViewForBrowser(browser())
-                   ->GetSidePanelRoundedCornerForTesting()
-                   ->GetVisible());
+  EXPECT_FALSE(AreAnyRoundedCornersShowing());
 
   // Switch back to the second tab.
   browser()->tab_strip_model()->ActivateTabAt(1);
@@ -9065,9 +9077,7 @@
                       ->layer()
                       ->GetTargetRoundedCornerRadius()
                       .upper_right() > 0);
-  EXPECT_FALSE(BrowserView::GetBrowserViewForBrowser(browser())
-                   ->GetSidePanelRoundedCornerForTesting()
-                   ->GetVisible());
+  EXPECT_FALSE(AreAnyRoundedCornersShowing());
 }
 
 IN_PROC_BROWSER_TEST_F(LensOverlayControllerSideBySideBrowserTest,
diff --git a/chrome/browser/ui/lens/lens_overlay_query_controller.cc b/chrome/browser/ui/lens/lens_overlay_query_controller.cc
index e438a90..5fe6cd4 100644
--- a/chrome/browser/ui/lens/lens_overlay_query_controller.cc
+++ b/chrome/browser/ui/lens/lens_overlay_query_controller.cc
@@ -703,17 +703,18 @@
       /*url_loader_factory=*/profile_
           ? profile_->GetURLLoaderFactory().get()
           : g_browser_process->shared_url_loader_factory(),
-      /*url=*/fetch_url,
-      /*content_type=*/kContentType,
-      /*timeout=*/timeout,
-      /*post_data=*/std::move(request_string),
-      /*headers=*/request_headers,
-      /*cors_exempt_headers=*/cors_exempt_headers, chrome::GetChannel(),
-      /*request_params=*/
+      /*identity_manager=*/nullptr,
       EndpointFetcher::RequestParams::Builder(http_method,
                                               kTrafficAnnotationTag)
+          .SetChannel(chrome::GetChannel())
+          .SetContentType(kContentType)
+          .SetCorsExemptHeaders(cors_exempt_headers)
           .SetCredentialsMode(CredentialsMode::kInclude)
+          .SetHeaders(request_headers)
+          .SetPostData(std::move(request_string))
           .SetSetSiteForCookies(true)
+          .SetTimeout(timeout)
+          .SetUrl(fetch_url)
           .SetUploadProgressCallback(std::move(upload_progress_callback))
           .Build());
 }
diff --git a/chrome/browser/ui/tabs/tab_strip_api/BUILD.gn b/chrome/browser/ui/tabs/tab_strip_api/BUILD.gn
index 71e69250..d1c66c3 100644
--- a/chrome/browser/ui/tabs/tab_strip_api/BUILD.gn
+++ b/chrome/browser/ui/tabs/tab_strip_api/BUILD.gn
@@ -19,6 +19,7 @@
   visibility = [
     ":browser_tests",
     ":impl",
+    ":impl_header",
     ":tab_strip_api",
     ":unit_tests",
   ]
@@ -113,7 +114,6 @@
 source_set("tab_strip_api") {
   sources = [
     "tab_strip_service.h",
-    "tab_strip_service_impl.h",
     "tab_strip_service_register.h",
   ]
 
@@ -123,8 +123,18 @@
     "//chrome/browser/ui/tabs:tab_strip_model_observer",
   ]
 
+  deps = [ ":mojom_experiment" ]
+}
+
+# This target is needed to prevent circular dependency.
+source_set("impl_header") {
+  sources = [ "tab_strip_service_mojo_handler.h" ]
+
   deps = [
+    ":mojom",
     ":mojom_experiment",
+    ":tab_strip_api",
+    "adapters",
     "//chrome/browser/ui/tabs/tab_strip_api/events",
   ]
 }
@@ -134,15 +144,14 @@
     "event_broadcaster.cc",
     "event_broadcaster.h",
     "tab_strip_service_impl.cc",
-
-    # intermediary transition classes. Will be renamed
-    "tab_strip_service_sync_impl.cc",
-    "tab_strip_service_sync_impl.h",
+    "tab_strip_service_mojo_handler.cc",
+    "tab_strip_service_mojo_handler.h",
   ]
 
   public_deps = [ "//chrome/browser:browser_public_dependencies" ]
 
   deps = [
+    ":impl_header",
     ":mojom_experiment",
     ":tab_strip_api",
     "adapters:impl",
diff --git a/chrome/browser/ui/tabs/tab_strip_api/tab_strip_experiment_service_unittest.cc b/chrome/browser/ui/tabs/tab_strip_api/tab_strip_experiment_service_unittest.cc
index 8754f999..83f6d7d 100644
--- a/chrome/browser/ui/tabs/tab_strip_api/tab_strip_experiment_service_unittest.cc
+++ b/chrome/browser/ui/tabs/tab_strip_api/tab_strip_experiment_service_unittest.cc
@@ -8,7 +8,7 @@
 #include "base/test/task_environment.h"
 #include "chrome/browser/ui/tabs/tab_strip_api/tab_strip_experiment_api.mojom.h"
 #include "chrome/browser/ui/tabs/tab_strip_api/tab_strip_service_impl.h"
-#include "chrome/browser/ui/tabs/tab_strip_api/tab_strip_service_sync_impl.h"
+#include "chrome/browser/ui/tabs/tab_strip_api/tab_strip_service_mojo_handler.h"
 #include "chrome/browser/ui/tabs/tab_strip_api/testing/toy_tab_strip.h"
 #include "chrome/browser/ui/tabs/tab_strip_api/testing/toy_tab_strip_browser_adapter.h"
 #include "chrome/browser/ui/tabs/tab_strip_api/testing/toy_tab_strip_model_adapter.h"
@@ -31,10 +31,10 @@
 
   void SetUp() override {
     tab_strip_ = std::make_unique<testing::ToyTabStrip>();
-    auto tab_strip_service = std::make_unique<TabStripServiceSyncImpl>(
+    auto tab_strip_service = std::make_unique<TabStripServiceImpl>(
         std::make_unique<testing::ToyTabStripBrowserAdapter>(tab_strip_.get()),
         std::make_unique<testing::ToyTabStripModelAdapter>(tab_strip_.get()));
-    impl_ = std::make_unique<TabStripServiceImpl>(
+    impl_ = std::make_unique<TabStripServiceMojoHandler>(
         std::move(tab_strip_service),
         std::make_unique<testing::ToyTabStripModelAdapter>(tab_strip_.get()));
     impl_->AcceptExperimental(client_.BindNewPipeAndPassReceiver());
@@ -45,7 +45,7 @@
 
  private:
   content::BrowserTaskEnvironment task_environment_;
-  std::unique_ptr<TabStripServiceImpl> impl_;
+  std::unique_ptr<TabStripServiceMojoHandler> impl_;
 };
 
 TEST_F(TabStripExperimentServiceImplTest, UpdateTabGroupVisual) {
diff --git a/chrome/browser/ui/tabs/tab_strip_api/tab_strip_service_impl.cc b/chrome/browser/ui/tabs/tab_strip_api/tab_strip_service_impl.cc
index 1f4a8fc1..1606a12 100644
--- a/chrome/browser/ui/tabs/tab_strip_api/tab_strip_service_impl.cc
+++ b/chrome/browser/ui/tabs/tab_strip_api/tab_strip_service_impl.cc
@@ -1,155 +1,308 @@
 // Copyright 2025 The Chromium Authors
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
+
 #include "chrome/browser/ui/tabs/tab_strip_api/tab_strip_service_impl.h"
 
+#include <algorithm>
+#include <optional>
+#include <utility>
+
+#include "base/strings/string_number_conversions.h"
 #include "base/types/expected.h"
+#include "chrome/browser/ui/browser_tabstrip.h"
+#include "chrome/browser/ui/browser_window/public/browser_window_interface.h"
+#include "chrome/browser/ui/tabs/tab_strip_api/adapters/browser_adapter_impl.h"
 #include "chrome/browser/ui/tabs/tab_strip_api/adapters/tab_strip_model_adapter_impl.h"
+#include "chrome/browser/ui/tabs/tab_strip_api/converters/tab_converters.h"
 #include "chrome/browser/ui/tabs/tab_strip_api/event_broadcaster.h"
-#include "chrome/browser/ui/tabs/tab_strip_api/tab_strip_service.h"
-#include "chrome/browser/ui/tabs/tab_strip_api/tab_strip_service_sync_impl.h"
+#include "chrome/browser/ui/tabs/tab_strip_api/events/tab_strip_event_recorder.h"
 #include "chrome/browser/ui/tabs/tab_strip_model.h"
+#include "chrome/browser/ui/tabs/tab_strip_model_delegate.h"
+#include "mojo/public/mojom/base/error.mojom.h"
+#include "url/gurl.h"
 
-// Starts a mutation session that suppresses incoming messages to prevent
-// re-entrancy and replays all recorded mutations on session destruction.
-class MutationSession {
- public:
-   explicit MutationSession(tabs_api::events::TabStripEventRecorder* recorder)
-       : recorder_(recorder) {
-     recorder_->StopNotificationAndStartRecording();
-   }
-
-   ~MutationSession() { recorder_->PlayRecordingsAndStartNotification(); }
-
-   // Disallow copy and assign.
-   MutationSession(const MutationSession&) = delete;
-   MutationSession& operator=(const MutationSession&) = delete;
-
-  private:
-   raw_ptr<tabs_api::events::TabStripEventRecorder> recorder_;
-};
+namespace tabs_api {
 
 TabStripServiceImpl::TabStripServiceImpl(BrowserWindowInterface* browser,
                                          TabStripModel* tab_strip_model)
     : TabStripServiceImpl(
-          std::make_unique<tabs_api::TabStripServiceSyncImpl>(browser,
-                                                              tab_strip_model),
+          std::make_unique<tabs_api::BrowserAdapterImpl>(browser),
           std::make_unique<tabs_api::TabStripModelAdapterImpl>(
               tab_strip_model)) {}
 
 TabStripServiceImpl::TabStripServiceImpl(
-    std::unique_ptr<tabs_api::TabStripService> service,
-    std::unique_ptr<tabs_api::TabStripModelAdapter> tab_strip_model_adapter)
-    : tab_strip_service_(std::move(service)),
-      tab_strip_model_adapter_(std::move(tab_strip_model_adapter)) {
-  recorder_ = std::make_unique<tabs_api::events::TabStripEventRecorder>(
-      tab_strip_model_adapter_.get(),
-      base::BindRepeating(&TabStripServiceImpl::BroadcastEvents,
-                          base::Unretained(this)));
-  tab_strip_service_->AddObserver(recorder_.get());
+    std::unique_ptr<BrowserAdapter> browser_adapter,
+    std::unique_ptr<TabStripModelAdapter> tab_strip_model_adapter)
+    : browser_adapter_(std::move(browser_adapter)),
+      tab_strip_model_adapter_(std::move(tab_strip_model_adapter)) {}
+
+TabStripServiceImpl::~TabStripServiceImpl() = default;
+
+TabStripService::GetTabsResult TabStripServiceImpl::GetTabs() {
+  return tab_strip_model_adapter_->GetTabStripTopology();
 }
 
-void TabStripServiceImpl::BroadcastEvents(
-    const std::vector<tabs_api::events::Event>& events) const {
-  tabs_api::EventBroadcaster broadcaster;
-  broadcaster.Broadcast(observers_, events);
-}
-
-TabStripServiceImpl::~TabStripServiceImpl() {
-  tab_strip_service_->RemoveObserver(recorder_.get());
-
-  // Clear all observers
-  // TODO (crbug.com/412955607): Implement a removal mechanism similar to
-  // TabStripModelObserver where on shutdown of the TabStripService, it notifies
-  // to all clients that service is shutting down.
-  observers_.Clear();
-}
-
-void TabStripServiceImpl::GetTabs(GetTabsCallback callback) {
-  MutationSession recorder_session(recorder_.get());
-  auto snapshot = tabs_api::mojom::TabsSnapshot::New();
-  auto result = tab_strip_service_->GetTabs();
-  if (!result.has_value()) {
-    std::move(callback).Run(base::unexpected(std::move(result.error())));
+mojom::TabStripService::GetTabResult TabStripServiceImpl::GetTab(
+    const tabs_api::NodeId& tab_mojom_id) {
+  if (tab_mojom_id.Type() != tabs_api::NodeId::Type::kContent) {
+    return base::unexpected(
+        mojo_base::mojom::Error::New(mojo_base::mojom::Code::kInvalidArgument,
+                                     "only tab content ids accepted"));
   }
-  snapshot->tab_strip = std::move(result.value());
 
-  mojo::AssociatedRemote<tabs_api::mojom::TabsObserver> stream;
-  auto pending_receiver = stream.BindNewEndpointAndPassReceiver();
-  observers_.Add(std::move(stream));
-  snapshot->stream = std::move(pending_receiver);
+  int32_t tab_id;
+  if (!base::StringToInt(tab_mojom_id.Id(), &tab_id)) {
+    return base::unexpected(mojo_base::mojom::Error::New(
+        mojo_base::mojom::Code::kInvalidArgument, "invalid tab id provided"));
+  }
 
-  std::move(callback).Run(std::move(snapshot));
+  tabs_api::mojom::TabPtr tab_result;
+  // TODO (crbug.com/412709270) TabStripModel or TabCollections should have an
+  // api that can fetch id without of relying on indexes.
+  auto tabs = tab_strip_model_adapter_->GetTabs();
+  for (unsigned int i = 0; i < tabs.size(); ++i) {
+    auto& handle = tabs.at(i);
+    if (tab_id == handle.raw_value()) {
+      auto renderer_data = tab_strip_model_adapter_->GetTabRendererData(i);
+      const ui::ColorProvider& color_provider =
+          tab_strip_model_adapter_->GetColorProvider();
+      tab_result = tabs_api::converters::BuildMojoTab(
+          handle, renderer_data, color_provider,
+          tab_strip_model_adapter_->GetTabStates(handle));
+    }
+  }
+
+  if (tab_result) {
+    return std::move(tab_result);
+  } else {
+    return base::unexpected(mojo_base::mojom::Error::New(
+        mojo_base::mojom::Code::kNotFound, "Tab not found"));
+  }
 }
 
-void TabStripServiceImpl::GetTab(const tabs_api::NodeId& tab_mojom_id,
-                                 GetTabCallback callback) {
-  MutationSession recorder_session(recorder_.get());
-
-  std::move(callback).Run(tab_strip_service_->GetTab(tab_mojom_id));
-}
-
-void TabStripServiceImpl::CreateTabAt(
+mojom::TabStripService::CreateTabAtResult TabStripServiceImpl::CreateTabAt(
     const std::optional<tabs_api::Position>& pos,
-    const std::optional<GURL>& url,
-    CreateTabAtCallback callback) {
-  MutationSession recorder_session(recorder_.get());
+    const std::optional<GURL>& url) {
+  GURL target_url;
+  if (url.has_value()) {
+    target_url = url.value();
+  }
+  std::optional<int> index;
+  if (pos.has_value()) {
+    // TODO(crbug.com/409086859): Does not use the parent_id yet. Currently only
+    // inserts in the unpinned collection.
+    index = pos->index();
+  }
 
-  std::move(callback).Run(tab_strip_service_->CreateTabAt(pos, url));
+  auto tab_handle = browser_adapter_->AddTabAt(target_url, index);
+  if (tab_handle == tabs::TabHandle::Null()) {
+    // Missing content can happen for a number of reasons. i.e. If the profile
+    // is shutting down or if navigation requests are blocked due to some
+    // internal state. This is usually because the browser is not in the
+    // required state to perform the action.
+    return base::unexpected(mojo_base::mojom::Error::New(
+        mojo_base::mojom::Code::kInternal, "Failed to create WebContents"));
+  }
+
+  auto tab_index = tab_strip_model_adapter_->GetIndexForHandle(tab_handle);
+  if (!tab_index.has_value()) {
+    return base::unexpected(mojo_base::mojom::Error::New(
+        mojo_base::mojom::Code::kInternal,
+        "Could not find the index of the newly created tab"));
+  }
+
+  auto renderer_data =
+      tab_strip_model_adapter_->GetTabRendererData(tab_index.value());
+  const ui::ColorProvider& color_provider =
+      tab_strip_model_adapter_->GetColorProvider();
+  auto mojo_tab = tabs_api::converters::BuildMojoTab(
+      tab_handle, renderer_data, color_provider,
+      tab_strip_model_adapter_->GetTabStates(tab_handle));
+  return mojo_tab->Clone();
 }
 
-void TabStripServiceImpl::CloseTabs(const std::vector<tabs_api::NodeId>& ids,
-                                    CloseTabsCallback callback) {
-  MutationSession recorder_session(recorder_.get());
+mojom::TabStripService::CloseTabsResult TabStripServiceImpl::CloseTabs(
+    const std::vector<tabs_api::NodeId>& ids) {
+  std::vector<int32_t> tab_content_targets;
+  for (const auto& id : ids) {
+    if (id.Type() != tabs_api::NodeId::Type::kContent) {
+      return base::unexpected(mojo_base::mojom::Error::New(
+          mojo_base::mojom::Code::kUnimplemented,
+          "only content tab closing has been implemented right now"));
+    }
+    int32_t numeric_id;
+    if (!base::StringToInt(id.Id(), &numeric_id)) {
+      return base::unexpected(mojo_base::mojom::Error::New(
+          mojo_base::mojom::Code::kInvalidArgument, "invalid tab content id"));
+    }
+    tab_content_targets.push_back(numeric_id);
+  }
 
-  std::move(callback).Run(tab_strip_service_->CloseTabs(ids));
+  std::vector<size_t> tab_strip_indices;
+  // Transform targets from ids to indices in the tabstrip.
+  for (auto target : tab_content_targets) {
+    auto target_idx =
+        tab_strip_model_adapter_->GetIndexForHandle(tabs::TabHandle(target));
+    if (!target_idx.has_value()) {
+      return base::unexpected(mojo_base::mojom::Error::New(
+          mojo_base::mojom::Code::kNotFound, "could not find the a tab"));
+    }
+    tab_strip_indices.push_back(target_idx.value());
+  }
+
+  // Close from last to first, that way the removals won't change the index of
+  // the next target.
+  std::ranges::sort(tab_strip_indices, std::ranges::greater());
+  for (auto idx : tab_strip_indices) {
+    tab_strip_model_adapter_->CloseTab(idx);
+  }
+
+  return std::monostate();
 }
 
-void TabStripServiceImpl::ActivateTab(const tabs_api::NodeId& id,
-                                      ActivateTabCallback callback) {
-  MutationSession recorder_session(recorder_.get());
+mojom::TabStripService::ActivateTabResult TabStripServiceImpl::ActivateTab(
+    const tabs_api::NodeId& id) {
+  if (id.Type() != tabs_api::NodeId::Type::kContent) {
+    return base::unexpected(
+        mojo_base::mojom::Error::New(mojo_base::mojom::Code::kInvalidArgument,
+                                     "only a content tab id can be provided"));
+  }
 
-  std::move(callback).Run(tab_strip_service_->ActivateTab(id));
+  int32_t handle_id;
+  if (!base::StringToInt(id.Id(), &handle_id)) {
+    return base::unexpected(mojo_base::mojom::Error::New(
+        mojo_base::mojom::Code::kInvalidArgument, "id is malformed"));
+  }
+
+  auto maybe_idx =
+      tab_strip_model_adapter_->GetIndexForHandle(tabs::TabHandle(handle_id));
+  if (!maybe_idx.has_value()) {
+    return base::unexpected(mojo_base::mojom::Error::New(
+        mojo_base::mojom::Code::kNotFound, "tab not found"));
+  }
+
+  tab_strip_model_adapter_->ActivateTab(maybe_idx.value());
+  return std::monostate();
 }
 
-void TabStripServiceImpl::SetSelectedTabs(
+mojom::TabStripService::SetSelectedTabsResult
+TabStripServiceImpl::SetSelectedTabs(
     const std::vector<tabs_api::NodeId>& selection,
-    const tabs_api::NodeId& tab_to_activate,
-    SetSelectedTabsCallback callback) {
-  MutationSession recorder_session(recorder_.get());
+    const tabs_api::NodeId& tab_to_activate) {
+  if (std::find(selection.begin(), selection.end(), tab_to_activate) ==
+      selection.end()) {
+    return base::unexpected(mojo_base::mojom::Error::New(
+        mojo_base::mojom::Code::kInvalidArgument,
+        "the selection must include the tab_to_activate"));
+  }
 
-  std::move(callback).Run(
-      tab_strip_service_->SetSelectedTabs(selection, tab_to_activate));
+  auto is_not_content_id = [](tabs_api::NodeId id) {
+    return id.Type() != tabs_api::NodeId::Type::kContent;
+  };
+  if (std::find_if(selection.begin(), selection.end(), is_not_content_id) !=
+      selection.end()) {
+    return base::unexpected(mojo_base::mojom::Error::New(
+        mojo_base::mojom::Code::kInvalidArgument,
+        "the selection can only include content IDs"));
+  }
+
+  std::vector<tabs::TabHandle> selection_handles;
+  for (auto& id : selection) {
+    int32_t handle_id;
+    if (!base::StringToInt(id.Id(), &handle_id)) {
+      return base::unexpected(mojo_base::mojom::Error::New(
+          mojo_base::mojom::Code::kInvalidArgument, "id is malformed"));
+    }
+    selection_handles.push_back(tabs::TabHandle(handle_id));
+  }
+
+  int32_t activate_handle_id;
+  if (!base::StringToInt(tab_to_activate.Id(), &activate_handle_id)) {
+    return base::unexpected(mojo_base::mojom::Error::New(
+        mojo_base::mojom::Code::kInvalidArgument, "activate id is malformed"));
+  }
+
+  tab_strip_model_adapter_->SetTabSelection(
+      selection_handles, tabs::TabHandle(activate_handle_id));
+
+  return std::monostate();
 }
 
-void TabStripServiceImpl::MoveTab(const tabs_api::NodeId& id,
-                                  const tabs_api::Position& position,
-                                  MoveTabCallback callback) {
-  MutationSession recorder_session(recorder_.get());
-
-  std::move(callback).Run(tab_strip_service_->MoveTab(id, position));
-}
-
-void TabStripServiceImpl::UpdateTabGroupVisual(
+mojom::TabStripService::MoveTabResult TabStripServiceImpl::MoveTab(
     const tabs_api::NodeId& id,
-    const tab_groups::TabGroupVisualData& visual_data,
-    UpdateTabGroupVisualCallback callback) {
-  MutationSession recorder_session(recorder_.get());
+    const tabs_api::Position& position) {
+  if (position.index() >= tab_strip_model_adapter_->GetTabs().size()) {
+    return base::unexpected(
+        mojo_base::mojom::Error::New(mojo_base::mojom::Code::kInvalidArgument,
+                                     "position cannot exceed tab strip"));
+  }
 
-  std::move(callback).Run(
-      tab_strip_service_->UpdateTabGroupVisual(id, visual_data));
+  switch (id.Type()) {
+    case tabs_api::NodeId::Type::kContent: {
+      std::optional<tabs::TabHandle> tab_handle = id.ToTabHandle();
+      if (!tab_handle.has_value()) {
+        return base::unexpected(mojo_base::mojom::Error::New(
+            mojo_base::mojom::Code::kInvalidArgument, "id is malformed"));
+      }
+      // TODO(crbug.com/409086859): Add error handling for cases where a
+      // position's parent id is impossible to be moved to.
+      tab_strip_model_adapter_->MoveTab(tab_handle.value(), position);
+      break;
+    }
+    case tabs_api::NodeId::Type::kCollection: {
+      tab_strip_model_adapter_->MoveCollection(id, position);
+      break;
+    }
+    default:
+      return base::unexpected(mojo_base::mojom::Error::New(
+          mojo_base::mojom::Code::kInvalidArgument, "invalid node type"));
+  }
+
+  return std::monostate();
 }
 
-tabs_api::TabStripService* TabStripServiceImpl::GetSyncVersion() const {
-  return tab_strip_service_.get();
+// tabs_api::mojom::TabStripExperimentalService overrides
+//
+// TabStripExperimentalService is intended for quick prototyping for
+// experimental apis that may not necessarily fit in the standard
+// TabStripService.
+mojom::TabStripExperimentService::UpdateTabGroupVisualResult
+TabStripServiceImpl::UpdateTabGroupVisual(
+    const tabs_api::NodeId& id,
+    const tab_groups::TabGroupVisualData& visual_data) {
+  if (id.Type() != tabs_api::NodeId::Type::kCollection) {
+    return base::unexpected(mojo_base::mojom::Error::New(
+        mojo_base::mojom::Code::kInvalidArgument, "id must be a collection"));
+  }
+
+  const std::optional<tabs::TabCollectionHandle> collection_handle =
+      id.ToTabCollectionHandle();
+  if (!collection_handle.has_value()) {
+    return base::unexpected(mojo_base::mojom::Error::New(
+        mojo_base::mojom::Code::kInvalidArgument, "id is malformed"));
+  }
+
+  const std::optional<const tab_groups::TabGroupId> group_id =
+      tab_strip_model_adapter_->FindGroupIdFor(collection_handle.value());
+  if (!group_id.has_value()) {
+    return base::unexpected(
+        mojo_base::mojom::Error::New(mojo_base::mojom::Code::kNotFound,
+                                     "group with the specified ID not found."));
+  }
+
+  tab_strip_model_adapter_->UpdateTabGroupVisuals(group_id.value(),
+                                                  visual_data);
+
+  return std::monostate();
 }
 
-void TabStripServiceImpl::Accept(
-    mojo::PendingReceiver<tabs_api::mojom::TabStripService> client) {
-  clients_.Add(this, std::move(client));
+void TabStripServiceImpl::AddObserver(TabStripModelObserver* observer) {
+  tab_strip_model_adapter_->AddObserver(observer);
 }
 
-void TabStripServiceImpl::AcceptExperimental(
-    mojo::PendingReceiver<tabs_api::mojom::TabStripExperimentService> client) {
-  experiment_clients_.Add(this, std::move(client));
+void TabStripServiceImpl::RemoveObserver(TabStripModelObserver* observer) {
+  tab_strip_model_adapter_->RemoveObserver(observer);
 }
+
+}  // namespace tabs_api
diff --git a/chrome/browser/ui/tabs/tab_strip_api/tab_strip_service_impl.h b/chrome/browser/ui/tabs/tab_strip_api/tab_strip_service_impl.h
index 1e77bf5..62bb6248c 100644
--- a/chrome/browser/ui/tabs/tab_strip_api/tab_strip_service_impl.h
+++ b/chrome/browser/ui/tabs/tab_strip_api/tab_strip_service_impl.h
@@ -5,89 +5,60 @@
 #ifndef CHROME_BROWSER_UI_TABS_TAB_STRIP_API_TAB_STRIP_SERVICE_IMPL_H_
 #define CHROME_BROWSER_UI_TABS_TAB_STRIP_API_TAB_STRIP_SERVICE_IMPL_H_
 
-#include "base/functional/callback.h"
-#include "base/memory/raw_ptr.h"
-#include "base/observer_list.h"
 #include "chrome/browser/ui/tabs/tab_strip_api/adapters/browser_adapter.h"
 #include "chrome/browser/ui/tabs/tab_strip_api/adapters/tab_strip_model_adapter.h"
-#include "chrome/browser/ui/tabs/tab_strip_api/events/tab_strip_event_recorder.h"
 #include "chrome/browser/ui/tabs/tab_strip_api/tab_strip_api.mojom.h"
 #include "chrome/browser/ui/tabs/tab_strip_api/tab_strip_experiment_api.mojom.h"
 #include "chrome/browser/ui/tabs/tab_strip_api/tab_strip_service.h"
-#include "chrome/browser/ui/tabs/tab_strip_api/tab_strip_service_register.h"
-#include "chrome/browser/ui/tabs/tab_strip_api/types/node_id.h"
-#include "components/tab_groups/tab_group_visual_data.h"
-#include "mojo/public/cpp/bindings/receiver_set.h"
-#include "mojo/public/cpp/bindings/remote_set.h"
 
-class BrowserWindowInterface;
-class TabStripModel;
+namespace tabs_api {
 
-// TODO (crbug.com/409086859). See bug for dd.
-// tabs_api::mojom::TabStripController is an experimental TabStrip Api between
-// any view and the TabStripModel.
-class TabStripServiceImpl : public tabs_api::mojom::TabStripService,
-                            public tabs_api::mojom::TabStripExperimentService,
-                            public TabStripModelObserver,
-                            public TabStripServiceRegister {
+class TabStripServiceImpl : public TabStripService {
  public:
   TabStripServiceImpl(BrowserWindowInterface* browser,
                       TabStripModel* tab_strip_model);
   TabStripServiceImpl(
-      std::unique_ptr<tabs_api::TabStripService> service,
-      std::unique_ptr<tabs_api::TabStripModelAdapter> tab_strip_model_adapter);
-  TabStripServiceImpl(const TabStripServiceImpl&&) = delete;
-  TabStripServiceImpl& operator=(const TabStripServiceImpl&) = delete;
+      std::unique_ptr<BrowserAdapter> browser_adapter,
+      std::unique_ptr<TabStripModelAdapter> tab_strip_model_adapter);
+  TabStripServiceImpl(const TabStripServiceImpl&) = delete;
+  TabStripServiceImpl operator=(const TabStripServiceImpl&&) = delete;
   ~TabStripServiceImpl() override;
 
-  // TabStripServiceregister overrides
-  void Accept(
-      mojo::PendingReceiver<tabs_api::mojom::TabStripService> client) override;
-  void AcceptExperimental(
-      mojo::PendingReceiver<tabs_api::mojom::TabStripExperimentService> client)
-      override;
-
-  // tabs_api::mojom::TabStripService overrides
-  void GetTabs(GetTabsCallback callback) override;
-  void GetTab(const tabs_api::NodeId& id, GetTabCallback callback) override;
-  void CreateTabAt(const std::optional<tabs_api::Position>& pos,
-                   const std::optional<GURL>& url,
-                   CreateTabAtCallback callback) override;
-  void CloseTabs(const std::vector<tabs_api::NodeId>& ids,
-                 CloseTabsCallback callback) override;
-  void ActivateTab(const tabs_api::NodeId& id,
-                   ActivateTabCallback callback) override;
-  void SetSelectedTabs(const std::vector<tabs_api::NodeId>& selection,
-                       const tabs_api::NodeId& tab_to_activate,
-                       SetSelectedTabsCallback callback) override;
-  void MoveTab(const tabs_api::NodeId& id,
-               const tabs_api::Position& position,
-               MoveTabCallback callback) override;
+  TabStripService::GetTabsResult GetTabs() override;
+  mojom::TabStripService::GetTabResult GetTab(
+      const tabs_api::NodeId& id) override;
+  mojom::TabStripService::CreateTabAtResult CreateTabAt(
+      const std::optional<tabs_api::Position>& pos,
+      const std::optional<GURL>& url) override;
+  mojom::TabStripService::CloseTabsResult CloseTabs(
+      const std::vector<tabs_api::NodeId>& ids) override;
+  mojom::TabStripService::ActivateTabResult ActivateTab(
+      const tabs_api::NodeId& id) override;
+  mojom::TabStripService::SetSelectedTabsResult SetSelectedTabs(
+      const std::vector<tabs_api::NodeId>& selection,
+      const tabs_api::NodeId& tab_to_activate) override;
+  mojom::TabStripService::MoveTabResult MoveTab(
+      const tabs_api::NodeId& id,
+      const tabs_api::Position& position) override;
 
   // tabs_api::mojom::TabStripExperimentalService overrides
   //
   // TabStripExperimentalService is intended for quick prototyping for
   // experimental apis that may not necessarily fit in the standard
   // TabStripService.
-  void UpdateTabGroupVisual(const tabs_api::NodeId& id,
-                            const tab_groups::TabGroupVisualData& visual_data,
-                            UpdateTabGroupVisualCallback) override;
+  mojom::TabStripExperimentService::UpdateTabGroupVisualResult
+  UpdateTabGroupVisual(
+      const tabs_api::NodeId& id,
+      const tab_groups::TabGroupVisualData& visual_data) override;
 
-  tabs_api::TabStripService* GetSyncVersion() const;
+  void AddObserver(TabStripModelObserver* observer) override;
+  void RemoveObserver(TabStripModelObserver* observer) override;
 
  private:
-  void BroadcastEvents(
-      const std::vector<tabs_api::events::Event>& events) const;
-
-  std::unique_ptr<tabs_api::TabStripService> tab_strip_service_;
+  std::unique_ptr<tabs_api::BrowserAdapter> browser_adapter_;
   std::unique_ptr<tabs_api::TabStripModelAdapter> tab_strip_model_adapter_;
-  std::unique_ptr<tabs_api::events::TabStripEventRecorder> recorder_;
-
-  // Mojo plumbing.
-  mojo::ReceiverSet<tabs_api::mojom::TabStripService> clients_;
-  mojo::ReceiverSet<tabs_api::mojom::TabStripExperimentService>
-      experiment_clients_;
-  mojo::AssociatedRemoteSet<tabs_api::mojom::TabsObserver> observers_;
 };
 
+}  // namespace tabs_api
+
 #endif  // CHROME_BROWSER_UI_TABS_TAB_STRIP_API_TAB_STRIP_SERVICE_IMPL_H_
diff --git a/chrome/browser/ui/tabs/tab_strip_api/tab_strip_service_impl_browsertest.cc b/chrome/browser/ui/tabs/tab_strip_api/tab_strip_service_impl_browsertest.cc
index ea5a55c..c943393 100644
--- a/chrome/browser/ui/tabs/tab_strip_api/tab_strip_service_impl_browsertest.cc
+++ b/chrome/browser/ui/tabs/tab_strip_api/tab_strip_service_impl_browsertest.cc
@@ -17,6 +17,7 @@
 #include "chrome/browser/ui/tabs/tab_group_model.h"
 #include "chrome/browser/ui/tabs/tab_strip_api/tab_strip_api.mojom.h"
 #include "chrome/browser/ui/tabs/tab_strip_api/tab_strip_experiment_api.mojom.h"
+#include "chrome/browser/ui/tabs/tab_strip_api/tab_strip_service_mojo_handler.h"
 #include "chrome/browser/ui/tabs/tab_strip_model.h"
 #include "chrome/browser/ui/ui_features.h"
 #include "chrome/test/base/in_process_browser_test.h"
@@ -121,12 +122,13 @@
 
   void SetUpOnMainThread() override {
     InProcessBrowserTest::SetUpOnMainThread();
-    tab_strip_service_impl_ = std::make_unique<TabStripServiceImpl>(
-        browser(), browser()->tab_strip_model());
+    tab_strip_service_mojo_handler_ =
+        std::make_unique<TabStripServiceMojoHandler>(
+            browser(), browser()->tab_strip_model());
   }
 
   void TearDownOnMainThread() override {
-    tab_strip_service_impl_.reset();
+    tab_strip_service_mojo_handler_.reset();
     InProcessBrowserTest::TearDownOnMainThread();
   }
 
@@ -141,7 +143,7 @@
 
   std::unique_ptr<Observation> SetUpObservation() {
     auto observation = std::make_unique<Observation>();
-    tab_strip_service_impl_->Accept(
+    tab_strip_service_mojo_handler_->Accept(
         observation->remote.BindNewPipeAndPassReceiver());
 
     base::RunLoop run_loop;
@@ -158,12 +160,12 @@
   }
 
   base::test::ScopedFeatureList feature_list_;
-  std::unique_ptr<TabStripServiceImpl> tab_strip_service_impl_;
+  std::unique_ptr<TabStripServiceMojoHandler> tab_strip_service_mojo_handler_;
 };
 
 IN_PROC_BROWSER_TEST_F(TabStripServiceImplBrowserTest, CreateTabAt) {
   mojo::Remote<TabStripService> remote;
-  tab_strip_service_impl_->Accept(remote.BindNewPipeAndPassReceiver());
+  tab_strip_service_mojo_handler_->Accept(remote.BindNewPipeAndPassReceiver());
 
   TabStripModel* model = GetTabStripModel();
   const int expected_tab_count = model->count() + 1;
@@ -192,7 +194,7 @@
 
 IN_PROC_BROWSER_TEST_F(TabStripServiceImplBrowserTest, Observation) {
   mojo::Remote<TabStripService> remote;
-  tab_strip_service_impl_->Accept(remote.BindNewPipeAndPassReceiver());
+  tab_strip_service_mojo_handler_->Accept(remote.BindNewPipeAndPassReceiver());
   TestTabStripClient client;
   mojo::AssociatedReceiver<tabs_api::mojom::TabsObserver> receiver(&client);
   const GURL url("http://example.com/");
@@ -258,7 +260,7 @@
 
 IN_PROC_BROWSER_TEST_F(TabStripServiceImplBrowserTest, CloseTabs) {
   mojo::Remote<TabStripService> remote;
-  tab_strip_service_impl_->Accept(remote.BindNewPipeAndPassReceiver());
+  tab_strip_service_mojo_handler_->Accept(remote.BindNewPipeAndPassReceiver());
 
   const int starting_num_tabs = GetTabStripModel()->GetTabCount();
 
@@ -293,7 +295,7 @@
 
 IN_PROC_BROWSER_TEST_F(TabStripServiceImplBrowserTest, ActivateTab) {
   mojo::Remote<TabStripService> remote;
-  tab_strip_service_impl_->Accept(remote.BindNewPipeAndPassReceiver());
+  tab_strip_service_mojo_handler_->Accept(remote.BindNewPipeAndPassReceiver());
 
   tabs_api::NodeId created_id;
   base::RunLoop create_loop;
@@ -330,7 +332,7 @@
 // Create 5 tabs and select 3 random ones.
 IN_PROC_BROWSER_TEST_F(TabStripServiceImplBrowserTest, SetSelectedTabs) {
   mojo::Remote<TabStripService> remote;
-  tab_strip_service_impl_->Accept(remote.BindNewPipeAndPassReceiver());
+  tab_strip_service_mojo_handler_->Accept(remote.BindNewPipeAndPassReceiver());
 
   auto observation = SetUpObservation();
 
@@ -399,7 +401,7 @@
 
 IN_PROC_BROWSER_TEST_F(TabStripServiceImplBrowserTest, MoveTab) {
   mojo::Remote<TabStripService> remote;
-  tab_strip_service_impl_->Accept(remote.BindNewPipeAndPassReceiver());
+  tab_strip_service_mojo_handler_->Accept(remote.BindNewPipeAndPassReceiver());
 
   auto observation = SetUpObservation();
 
@@ -444,7 +446,7 @@
 
 IN_PROC_BROWSER_TEST_F(TabStripServiceImplBrowserTest, MoveTabIntoGroup) {
   mojo::Remote<TabStripService> remote;
-  tab_strip_service_impl_->Accept(remote.BindNewPipeAndPassReceiver());
+  tab_strip_service_mojo_handler_->Accept(remote.BindNewPipeAndPassReceiver());
 
   TabStripModel* model = GetTabStripModel();
   for (int i = 0; i < 3; i++) {
@@ -490,7 +492,7 @@
 
 IN_PROC_BROWSER_TEST_F(TabStripServiceImplBrowserTest, MoveCollection) {
   mojo::Remote<TabStripService> remote;
-  tab_strip_service_impl_->Accept(remote.BindNewPipeAndPassReceiver());
+  tab_strip_service_mojo_handler_->Accept(remote.BindNewPipeAndPassReceiver());
 
   TabStripModel* model = GetTabStripModel();
   for (int i = 0; i < 3; i++) {
@@ -537,8 +539,8 @@
                        UpdateTabGroupVisualData) {
   mojo::Remote<TabStripService> remote;
   mojo::Remote<TabStripExperimentService> experiment_remote;
-  tab_strip_service_impl_->Accept(remote.BindNewPipeAndPassReceiver());
-  tab_strip_service_impl_->AcceptExperimental(
+  tab_strip_service_mojo_handler_->Accept(remote.BindNewPipeAndPassReceiver());
+  tab_strip_service_mojo_handler_->AcceptExperimental(
       experiment_remote.BindNewPipeAndPassReceiver());
   TabStripModel* model = GetTabStripModel();
 
diff --git a/chrome/browser/ui/tabs/tab_strip_api/tab_strip_service_impl_unittest.cc b/chrome/browser/ui/tabs/tab_strip_api/tab_strip_service_impl_unittest.cc
index 1e666b3c..97952b2 100644
--- a/chrome/browser/ui/tabs/tab_strip_api/tab_strip_service_impl_unittest.cc
+++ b/chrome/browser/ui/tabs/tab_strip_api/tab_strip_service_impl_unittest.cc
@@ -13,7 +13,8 @@
 #include "chrome/browser/ui/tabs/tab_strip_api/adapters/browser_adapter.h"
 #include "chrome/browser/ui/tabs/tab_strip_api/adapters/tab_strip_model_adapter.h"
 #include "chrome/browser/ui/tabs/tab_strip_api/tab_strip_api.mojom.h"
-#include "chrome/browser/ui/tabs/tab_strip_api/tab_strip_service_sync_impl.h"
+#include "chrome/browser/ui/tabs/tab_strip_api/tab_strip_service_impl.h"
+#include "chrome/browser/ui/tabs/tab_strip_api/tab_strip_service_mojo_handler.h"
 #include "chrome/browser/ui/tabs/tab_strip_api/testing/toy_tab_strip.h"
 #include "chrome/browser/ui/tabs/tab_strip_api/testing/toy_tab_strip_browser_adapter.h"
 #include "chrome/browser/ui/tabs/tab_strip_api/testing/toy_tab_strip_model_adapter.h"
@@ -41,10 +42,10 @@
 
   void SetUp() override {
     tab_strip_ = std::make_unique<testing::ToyTabStrip>();
-    auto tab_strip_service = std::make_unique<TabStripServiceSyncImpl>(
+    auto tab_strip_service = std::make_unique<TabStripServiceImpl>(
         std::make_unique<testing::ToyTabStripBrowserAdapter>(tab_strip_.get()),
         std::make_unique<testing::ToyTabStripModelAdapter>(tab_strip_.get()));
-    impl_ = std::make_unique<TabStripServiceImpl>(
+    impl_ = std::make_unique<TabStripServiceMojoHandler>(
         std::move(tab_strip_service),
         std::make_unique<testing::ToyTabStripModelAdapter>(tab_strip_.get()));
     impl_->Accept(client_.BindNewPipeAndPassReceiver());
@@ -57,7 +58,7 @@
 
  private:
   content::BrowserTaskEnvironment task_environment_;
-  std::unique_ptr<TabStripServiceImpl> impl_;
+  std::unique_ptr<TabStripServiceMojoHandler> impl_;
 };
 
 TEST_F(TabStripServiceImplTest, CreateNewTab) {
diff --git a/chrome/browser/ui/tabs/tab_strip_api/tab_strip_service_mojo_handler.cc b/chrome/browser/ui/tabs/tab_strip_api/tab_strip_service_mojo_handler.cc
new file mode 100644
index 0000000..89b1c44
--- /dev/null
+++ b/chrome/browser/ui/tabs/tab_strip_api/tab_strip_service_mojo_handler.cc
@@ -0,0 +1,159 @@
+// Copyright 2025 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+#include "chrome/browser/ui/tabs/tab_strip_api/tab_strip_service_mojo_handler.h"
+
+#include "base/types/expected.h"
+#include "chrome/browser/ui/tabs/tab_strip_api/adapters/tab_strip_model_adapter_impl.h"
+#include "chrome/browser/ui/tabs/tab_strip_api/event_broadcaster.h"
+#include "chrome/browser/ui/tabs/tab_strip_api/tab_strip_service.h"
+#include "chrome/browser/ui/tabs/tab_strip_api/tab_strip_service_impl.h"
+#include "chrome/browser/ui/tabs/tab_strip_model.h"
+
+// Starts a mutation session that suppresses incoming messages to prevent
+// re-entrancy and replays all recorded mutations on session destruction.
+class MutationSession {
+ public:
+  explicit MutationSession(tabs_api::events::TabStripEventRecorder* recorder)
+      : recorder_(recorder) {
+    recorder_->StopNotificationAndStartRecording();
+  }
+
+  ~MutationSession() { recorder_->PlayRecordingsAndStartNotification(); }
+
+  // Disallow copy and assign.
+  MutationSession(const MutationSession&) = delete;
+  MutationSession& operator=(const MutationSession&) = delete;
+
+ private:
+  raw_ptr<tabs_api::events::TabStripEventRecorder> recorder_;
+};
+
+TabStripServiceMojoHandler::TabStripServiceMojoHandler(
+    BrowserWindowInterface* browser,
+    TabStripModel* tab_strip_model)
+    : TabStripServiceMojoHandler(
+          std::make_unique<tabs_api::TabStripServiceImpl>(browser,
+                                                          tab_strip_model),
+          std::make_unique<tabs_api::TabStripModelAdapterImpl>(
+              tab_strip_model)) {}
+
+TabStripServiceMojoHandler::TabStripServiceMojoHandler(
+    std::unique_ptr<tabs_api::TabStripService> service,
+    std::unique_ptr<tabs_api::TabStripModelAdapter> tab_strip_model_adapter)
+    : tab_strip_service_(std::move(service)),
+      tab_strip_model_adapter_(std::move(tab_strip_model_adapter)) {
+  recorder_ = std::make_unique<tabs_api::events::TabStripEventRecorder>(
+      tab_strip_model_adapter_.get(),
+      base::BindRepeating(&TabStripServiceMojoHandler::BroadcastEvents,
+                          base::Unretained(this)));
+  tab_strip_service_->AddObserver(recorder_.get());
+}
+
+void TabStripServiceMojoHandler::BroadcastEvents(
+    const std::vector<tabs_api::events::Event>& events) const {
+  tabs_api::EventBroadcaster broadcaster;
+  broadcaster.Broadcast(observers_, events);
+}
+
+TabStripServiceMojoHandler::~TabStripServiceMojoHandler() {
+  tab_strip_service_->RemoveObserver(recorder_.get());
+
+  // Clear all observers
+  // TODO (crbug.com/412955607): Implement a removal mechanism similar to
+  // TabStripModelObserver where on shutdown of the TabStripService, it notifies
+  // to all clients that service is shutting down.
+  observers_.Clear();
+}
+
+void TabStripServiceMojoHandler::GetTabs(GetTabsCallback callback) {
+  MutationSession recorder_session(recorder_.get());
+
+  auto snapshot = tabs_api::mojom::TabsSnapshot::New();
+  auto result = tab_strip_service_->GetTabs();
+  if (!result.has_value()) {
+    std::move(callback).Run(base::unexpected(std::move(result.error())));
+  }
+  snapshot->tab_strip = std::move(result.value());
+
+  mojo::AssociatedRemote<tabs_api::mojom::TabsObserver> stream;
+  auto pending_receiver = stream.BindNewEndpointAndPassReceiver();
+  observers_.Add(std::move(stream));
+  snapshot->stream = std::move(pending_receiver);
+
+  std::move(callback).Run(std::move(snapshot));
+}
+
+void TabStripServiceMojoHandler::GetTab(const tabs_api::NodeId& tab_mojom_id,
+                                        GetTabCallback callback) {
+  MutationSession recorder_session(recorder_.get());
+
+  std::move(callback).Run(tab_strip_service_->GetTab(tab_mojom_id));
+}
+
+void TabStripServiceMojoHandler::CreateTabAt(
+    const std::optional<tabs_api::Position>& pos,
+    const std::optional<GURL>& url,
+    CreateTabAtCallback callback) {
+  MutationSession recorder_session(recorder_.get());
+
+  std::move(callback).Run(tab_strip_service_->CreateTabAt(pos, url));
+}
+
+void TabStripServiceMojoHandler::CloseTabs(
+    const std::vector<tabs_api::NodeId>& ids,
+    CloseTabsCallback callback) {
+  MutationSession recorder_session(recorder_.get());
+
+  std::move(callback).Run(tab_strip_service_->CloseTabs(ids));
+}
+
+void TabStripServiceMojoHandler::ActivateTab(const tabs_api::NodeId& id,
+                                             ActivateTabCallback callback) {
+  MutationSession recorder_session(recorder_.get());
+
+  std::move(callback).Run(tab_strip_service_->ActivateTab(id));
+}
+
+void TabStripServiceMojoHandler::SetSelectedTabs(
+    const std::vector<tabs_api::NodeId>& selection,
+    const tabs_api::NodeId& tab_to_activate,
+    SetSelectedTabsCallback callback) {
+  MutationSession recorder_session(recorder_.get());
+
+  std::move(callback).Run(
+      tab_strip_service_->SetSelectedTabs(selection, tab_to_activate));
+}
+
+void TabStripServiceMojoHandler::MoveTab(const tabs_api::NodeId& id,
+                                         const tabs_api::Position& position,
+                                         MoveTabCallback callback) {
+  MutationSession recorder_session(recorder_.get());
+
+  std::move(callback).Run(tab_strip_service_->MoveTab(id, position));
+}
+
+void TabStripServiceMojoHandler::UpdateTabGroupVisual(
+    const tabs_api::NodeId& id,
+    const tab_groups::TabGroupVisualData& visual_data,
+    UpdateTabGroupVisualCallback callback) {
+  MutationSession recorder_session(recorder_.get());
+
+  std::move(callback).Run(
+      tab_strip_service_->UpdateTabGroupVisual(id, visual_data));
+}
+
+tabs_api::TabStripService* TabStripServiceMojoHandler::GetTabStripService()
+    const {
+  return tab_strip_service_.get();
+}
+
+void TabStripServiceMojoHandler::Accept(
+    mojo::PendingReceiver<tabs_api::mojom::TabStripService> client) {
+  clients_.Add(this, std::move(client));
+}
+
+void TabStripServiceMojoHandler::AcceptExperimental(
+    mojo::PendingReceiver<tabs_api::mojom::TabStripExperimentService> client) {
+  experiment_clients_.Add(this, std::move(client));
+}
diff --git a/chrome/browser/ui/tabs/tab_strip_api/tab_strip_service_mojo_handler.h b/chrome/browser/ui/tabs/tab_strip_api/tab_strip_service_mojo_handler.h
new file mode 100644
index 0000000..2fb9fb1
--- /dev/null
+++ b/chrome/browser/ui/tabs/tab_strip_api/tab_strip_service_mojo_handler.h
@@ -0,0 +1,95 @@
+// Copyright 2025 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_UI_TABS_TAB_STRIP_API_TAB_STRIP_SERVICE_MOJO_HANDLER_H_
+#define CHROME_BROWSER_UI_TABS_TAB_STRIP_API_TAB_STRIP_SERVICE_MOJO_HANDLER_H_
+
+#include "base/functional/callback.h"
+#include "base/memory/raw_ptr.h"
+#include "base/observer_list.h"
+#include "chrome/browser/ui/tabs/tab_strip_api/adapters/browser_adapter.h"
+#include "chrome/browser/ui/tabs/tab_strip_api/adapters/tab_strip_model_adapter.h"
+#include "chrome/browser/ui/tabs/tab_strip_api/events/tab_strip_event_recorder.h"
+#include "chrome/browser/ui/tabs/tab_strip_api/tab_strip_api.mojom.h"
+#include "chrome/browser/ui/tabs/tab_strip_api/tab_strip_experiment_api.mojom.h"
+#include "chrome/browser/ui/tabs/tab_strip_api/tab_strip_service.h"
+#include "chrome/browser/ui/tabs/tab_strip_api/tab_strip_service_register.h"
+#include "chrome/browser/ui/tabs/tab_strip_api/types/node_id.h"
+#include "components/tab_groups/tab_group_visual_data.h"
+#include "mojo/public/cpp/bindings/receiver_set.h"
+#include "mojo/public/cpp/bindings/remote_set.h"
+
+class BrowserWindowInterface;
+class TabStripModel;
+
+// TODO (crbug.com/409086859). See bug for dd.
+// tabs_api::mojom::TabStripController is an experimental TabStrip Api between
+// any view and the TabStripModel.
+class TabStripServiceMojoHandler
+    : public tabs_api::mojom::TabStripService,
+      public tabs_api::mojom::TabStripExperimentService,
+      public TabStripModelObserver,
+      public TabStripServiceRegister {
+ public:
+  TabStripServiceMojoHandler(BrowserWindowInterface* browser,
+                             TabStripModel* tab_strip_model);
+  TabStripServiceMojoHandler(
+      std::unique_ptr<tabs_api::TabStripService> service,
+      std::unique_ptr<tabs_api::TabStripModelAdapter> tab_strip_model_adapter);
+  TabStripServiceMojoHandler(const TabStripServiceMojoHandler&&) = delete;
+  TabStripServiceMojoHandler& operator=(const TabStripServiceMojoHandler&) =
+      delete;
+  ~TabStripServiceMojoHandler() override;
+
+  // TabStripServiceregister overrides
+  void Accept(
+      mojo::PendingReceiver<tabs_api::mojom::TabStripService> client) override;
+  void AcceptExperimental(
+      mojo::PendingReceiver<tabs_api::mojom::TabStripExperimentService> client)
+      override;
+
+  // tabs_api::mojom::TabStripService overrides
+  void GetTabs(GetTabsCallback callback) override;
+  void GetTab(const tabs_api::NodeId& id, GetTabCallback callback) override;
+  void CreateTabAt(const std::optional<tabs_api::Position>& pos,
+                   const std::optional<GURL>& url,
+                   CreateTabAtCallback callback) override;
+  void CloseTabs(const std::vector<tabs_api::NodeId>& ids,
+                 CloseTabsCallback callback) override;
+  void ActivateTab(const tabs_api::NodeId& id,
+                   ActivateTabCallback callback) override;
+  void SetSelectedTabs(const std::vector<tabs_api::NodeId>& selection,
+                       const tabs_api::NodeId& tab_to_activate,
+                       SetSelectedTabsCallback callback) override;
+  void MoveTab(const tabs_api::NodeId& id,
+               const tabs_api::Position& position,
+               MoveTabCallback callback) override;
+
+  // tabs_api::mojom::TabStripExperimentalService overrides
+  //
+  // TabStripExperimentalService is intended for quick prototyping for
+  // experimental apis that may not necessarily fit in the standard
+  // TabStripService.
+  void UpdateTabGroupVisual(const tabs_api::NodeId& id,
+                            const tab_groups::TabGroupVisualData& visual_data,
+                            UpdateTabGroupVisualCallback) override;
+
+  tabs_api::TabStripService* GetTabStripService() const;
+
+ private:
+  void BroadcastEvents(
+      const std::vector<tabs_api::events::Event>& events) const;
+
+  std::unique_ptr<tabs_api::TabStripService> tab_strip_service_;
+  std::unique_ptr<tabs_api::TabStripModelAdapter> tab_strip_model_adapter_;
+  std::unique_ptr<tabs_api::events::TabStripEventRecorder> recorder_;
+
+  // Mojo plumbing.
+  mojo::ReceiverSet<tabs_api::mojom::TabStripService> clients_;
+  mojo::ReceiverSet<tabs_api::mojom::TabStripExperimentService>
+      experiment_clients_;
+  mojo::AssociatedRemoteSet<tabs_api::mojom::TabsObserver> observers_;
+};
+
+#endif  // CHROME_BROWSER_UI_TABS_TAB_STRIP_API_TAB_STRIP_SERVICE_MOJO_HANDLER_H_
diff --git a/chrome/browser/ui/tabs/tab_strip_api/tab_strip_service_sync_impl.cc b/chrome/browser/ui/tabs/tab_strip_api/tab_strip_service_sync_impl.cc
deleted file mode 100644
index 71fca9c1..0000000
--- a/chrome/browser/ui/tabs/tab_strip_api/tab_strip_service_sync_impl.cc
+++ /dev/null
@@ -1,309 +0,0 @@
-// Copyright 2025 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "chrome/browser/ui/tabs/tab_strip_api/tab_strip_service_sync_impl.h"
-
-#include <algorithm>
-#include <optional>
-#include <utility>
-
-#include "base/strings/string_number_conversions.h"
-#include "base/types/expected.h"
-#include "chrome/browser/ui/browser_tabstrip.h"
-#include "chrome/browser/ui/browser_window/public/browser_window_interface.h"
-#include "chrome/browser/ui/tabs/tab_strip_api/adapters/browser_adapter_impl.h"
-#include "chrome/browser/ui/tabs/tab_strip_api/adapters/tab_strip_model_adapter_impl.h"
-#include "chrome/browser/ui/tabs/tab_strip_api/converters/tab_converters.h"
-#include "chrome/browser/ui/tabs/tab_strip_api/event_broadcaster.h"
-#include "chrome/browser/ui/tabs/tab_strip_api/events/tab_strip_event_recorder.h"
-#include "chrome/browser/ui/tabs/tab_strip_model.h"
-#include "chrome/browser/ui/tabs/tab_strip_model_delegate.h"
-#include "mojo/public/mojom/base/error.mojom.h"
-#include "url/gurl.h"
-
-namespace tabs_api {
-
-TabStripServiceSyncImpl::TabStripServiceSyncImpl(
-    BrowserWindowInterface* browser,
-    TabStripModel* tab_strip_model)
-    : TabStripServiceSyncImpl(
-          std::make_unique<tabs_api::BrowserAdapterImpl>(browser),
-          std::make_unique<tabs_api::TabStripModelAdapterImpl>(
-              tab_strip_model)) {}
-
-TabStripServiceSyncImpl::TabStripServiceSyncImpl(
-    std::unique_ptr<BrowserAdapter> browser_adapter,
-    std::unique_ptr<TabStripModelAdapter> tab_strip_model_adapter)
-    : browser_adapter_(std::move(browser_adapter)),
-      tab_strip_model_adapter_(std::move(tab_strip_model_adapter)) {}
-
-TabStripServiceSyncImpl::~TabStripServiceSyncImpl() = default;
-
-TabStripService::GetTabsResult TabStripServiceSyncImpl::GetTabs() {
-  return tab_strip_model_adapter_->GetTabStripTopology();
-}
-
-mojom::TabStripService::GetTabResult TabStripServiceSyncImpl::GetTab(
-    const tabs_api::NodeId& tab_mojom_id) {
-  if (tab_mojom_id.Type() != tabs_api::NodeId::Type::kContent) {
-    return base::unexpected(
-        mojo_base::mojom::Error::New(mojo_base::mojom::Code::kInvalidArgument,
-                                     "only tab content ids accepted"));
-  }
-
-  int32_t tab_id;
-  if (!base::StringToInt(tab_mojom_id.Id(), &tab_id)) {
-    return base::unexpected(mojo_base::mojom::Error::New(
-        mojo_base::mojom::Code::kInvalidArgument, "invalid tab id provided"));
-  }
-
-  tabs_api::mojom::TabPtr tab_result;
-  // TODO (crbug.com/412709270) TabStripModel or TabCollections should have an
-  // api that can fetch id without of relying on indexes.
-  auto tabs = tab_strip_model_adapter_->GetTabs();
-  for (unsigned int i = 0; i < tabs.size(); ++i) {
-    auto& handle = tabs.at(i);
-    if (tab_id == handle.raw_value()) {
-      auto renderer_data = tab_strip_model_adapter_->GetTabRendererData(i);
-      const ui::ColorProvider& color_provider =
-          tab_strip_model_adapter_->GetColorProvider();
-      tab_result = tabs_api::converters::BuildMojoTab(
-          handle, renderer_data, color_provider,
-          tab_strip_model_adapter_->GetTabStates(handle));
-    }
-  }
-
-  if (tab_result) {
-    return std::move(tab_result);
-  } else {
-    return base::unexpected(mojo_base::mojom::Error::New(
-        mojo_base::mojom::Code::kNotFound, "Tab not found"));
-  }
-}
-
-mojom::TabStripService::CreateTabAtResult TabStripServiceSyncImpl::CreateTabAt(
-    const std::optional<tabs_api::Position>& pos,
-    const std::optional<GURL>& url) {
-  GURL target_url;
-  if (url.has_value()) {
-    target_url = url.value();
-  }
-  std::optional<int> index;
-  if (pos.has_value()) {
-    // TODO(crbug.com/409086859): Does not use the parent_id yet. Currently only
-    // inserts in the unpinned collection.
-    index = pos->index();
-  }
-
-  auto tab_handle = browser_adapter_->AddTabAt(target_url, index);
-  if (tab_handle == tabs::TabHandle::Null()) {
-    // Missing content can happen for a number of reasons. i.e. If the profile
-    // is shutting down or if navigation requests are blocked due to some
-    // internal state. This is usually because the browser is not in the
-    // required state to perform the action.
-    return base::unexpected(mojo_base::mojom::Error::New(
-        mojo_base::mojom::Code::kInternal, "Failed to create WebContents"));
-  }
-
-  auto tab_index = tab_strip_model_adapter_->GetIndexForHandle(tab_handle);
-  if (!tab_index.has_value()) {
-    return base::unexpected(mojo_base::mojom::Error::New(
-        mojo_base::mojom::Code::kInternal,
-        "Could not find the index of the newly created tab"));
-  }
-
-  auto renderer_data =
-      tab_strip_model_adapter_->GetTabRendererData(tab_index.value());
-  const ui::ColorProvider& color_provider =
-      tab_strip_model_adapter_->GetColorProvider();
-  auto mojo_tab = tabs_api::converters::BuildMojoTab(
-      tab_handle, renderer_data, color_provider,
-      tab_strip_model_adapter_->GetTabStates(tab_handle));
-  return mojo_tab->Clone();
-}
-
-mojom::TabStripService::CloseTabsResult TabStripServiceSyncImpl::CloseTabs(
-    const std::vector<tabs_api::NodeId>& ids) {
-  std::vector<int32_t> tab_content_targets;
-  for (const auto& id : ids) {
-    if (id.Type() != tabs_api::NodeId::Type::kContent) {
-      return base::unexpected(mojo_base::mojom::Error::New(
-          mojo_base::mojom::Code::kUnimplemented,
-          "only content tab closing has been implemented right now"));
-    }
-    int32_t numeric_id;
-    if (!base::StringToInt(id.Id(), &numeric_id)) {
-      return base::unexpected(mojo_base::mojom::Error::New(
-          mojo_base::mojom::Code::kInvalidArgument, "invalid tab content id"));
-    }
-    tab_content_targets.push_back(numeric_id);
-  }
-
-  std::vector<size_t> tab_strip_indices;
-  // Transform targets from ids to indices in the tabstrip.
-  for (auto target : tab_content_targets) {
-    auto target_idx =
-        tab_strip_model_adapter_->GetIndexForHandle(tabs::TabHandle(target));
-    if (!target_idx.has_value()) {
-      return base::unexpected(mojo_base::mojom::Error::New(
-          mojo_base::mojom::Code::kNotFound, "could not find the a tab"));
-    }
-    tab_strip_indices.push_back(target_idx.value());
-  }
-
-  // Close from last to first, that way the removals won't change the index of
-  // the next target.
-  std::ranges::sort(tab_strip_indices, std::ranges::greater());
-  for (auto idx : tab_strip_indices) {
-    tab_strip_model_adapter_->CloseTab(idx);
-  }
-
-  return std::monostate();
-}
-
-mojom::TabStripService::ActivateTabResult TabStripServiceSyncImpl::ActivateTab(
-    const tabs_api::NodeId& id) {
-  if (id.Type() != tabs_api::NodeId::Type::kContent) {
-    return base::unexpected(
-        mojo_base::mojom::Error::New(mojo_base::mojom::Code::kInvalidArgument,
-                                     "only a content tab id can be provided"));
-  }
-
-  int32_t handle_id;
-  if (!base::StringToInt(id.Id(), &handle_id)) {
-    return base::unexpected(mojo_base::mojom::Error::New(
-        mojo_base::mojom::Code::kInvalidArgument, "id is malformed"));
-  }
-
-  auto maybe_idx =
-      tab_strip_model_adapter_->GetIndexForHandle(tabs::TabHandle(handle_id));
-  if (!maybe_idx.has_value()) {
-    return base::unexpected(mojo_base::mojom::Error::New(
-        mojo_base::mojom::Code::kNotFound, "tab not found"));
-  }
-
-  tab_strip_model_adapter_->ActivateTab(maybe_idx.value());
-  return std::monostate();
-}
-
-mojom::TabStripService::SetSelectedTabsResult
-TabStripServiceSyncImpl::SetSelectedTabs(
-    const std::vector<tabs_api::NodeId>& selection,
-    const tabs_api::NodeId& tab_to_activate) {
-  if (std::find(selection.begin(), selection.end(), tab_to_activate) ==
-      selection.end()) {
-    return base::unexpected(mojo_base::mojom::Error::New(
-        mojo_base::mojom::Code::kInvalidArgument,
-        "the selection must include the tab_to_activate"));
-  }
-
-  auto is_not_content_id = [](tabs_api::NodeId id) {
-    return id.Type() != tabs_api::NodeId::Type::kContent;
-  };
-  if (std::find_if(selection.begin(), selection.end(), is_not_content_id) !=
-      selection.end()) {
-    return base::unexpected(mojo_base::mojom::Error::New(
-        mojo_base::mojom::Code::kInvalidArgument,
-        "the selection can only include content IDs"));
-  }
-
-  std::vector<tabs::TabHandle> selection_handles;
-  for (auto& id : selection) {
-    int32_t handle_id;
-    if (!base::StringToInt(id.Id(), &handle_id)) {
-      return base::unexpected(mojo_base::mojom::Error::New(
-          mojo_base::mojom::Code::kInvalidArgument, "id is malformed"));
-    }
-    selection_handles.push_back(tabs::TabHandle(handle_id));
-  }
-
-  int32_t activate_handle_id;
-  if (!base::StringToInt(tab_to_activate.Id(), &activate_handle_id)) {
-    return base::unexpected(mojo_base::mojom::Error::New(
-        mojo_base::mojom::Code::kInvalidArgument, "activate id is malformed"));
-  }
-
-  tab_strip_model_adapter_->SetTabSelection(
-      selection_handles, tabs::TabHandle(activate_handle_id));
-
-  return std::monostate();
-}
-
-mojom::TabStripService::MoveTabResult TabStripServiceSyncImpl::MoveTab(
-    const tabs_api::NodeId& id,
-    const tabs_api::Position& position) {
-  if (position.index() >= tab_strip_model_adapter_->GetTabs().size()) {
-    return base::unexpected(
-        mojo_base::mojom::Error::New(mojo_base::mojom::Code::kInvalidArgument,
-                                     "position cannot exceed tab strip"));
-  }
-
-  switch (id.Type()) {
-    case tabs_api::NodeId::Type::kContent: {
-      std::optional<tabs::TabHandle> tab_handle = id.ToTabHandle();
-      if (!tab_handle.has_value()) {
-        return base::unexpected(mojo_base::mojom::Error::New(
-            mojo_base::mojom::Code::kInvalidArgument, "id is malformed"));
-      }
-      // TODO(crbug.com/409086859): Add error handling for cases where a
-      // position's parent id is impossible to be moved to.
-      tab_strip_model_adapter_->MoveTab(tab_handle.value(), position);
-      break;
-    }
-    case tabs_api::NodeId::Type::kCollection: {
-      tab_strip_model_adapter_->MoveCollection(id, position);
-      break;
-    }
-    default:
-      return base::unexpected(mojo_base::mojom::Error::New(
-          mojo_base::mojom::Code::kInvalidArgument, "invalid node type"));
-  }
-
-  return std::monostate();
-}
-
-// tabs_api::mojom::TabStripExperimentalService overrides
-//
-// TabStripExperimentalService is intended for quick prototyping for
-// experimental apis that may not necessarily fit in the standard
-// TabStripService.
-mojom::TabStripExperimentService::UpdateTabGroupVisualResult
-TabStripServiceSyncImpl::UpdateTabGroupVisual(
-    const tabs_api::NodeId& id,
-    const tab_groups::TabGroupVisualData& visual_data) {
-  if (id.Type() != tabs_api::NodeId::Type::kCollection) {
-    return base::unexpected(mojo_base::mojom::Error::New(
-        mojo_base::mojom::Code::kInvalidArgument, "id must be a collection"));
-  }
-
-  const std::optional<tabs::TabCollectionHandle> collection_handle =
-      id.ToTabCollectionHandle();
-  if (!collection_handle.has_value()) {
-    return base::unexpected(mojo_base::mojom::Error::New(
-        mojo_base::mojom::Code::kInvalidArgument, "id is malformed"));
-  }
-
-  const std::optional<const tab_groups::TabGroupId> group_id =
-      tab_strip_model_adapter_->FindGroupIdFor(collection_handle.value());
-  if (!group_id.has_value()) {
-    return base::unexpected(
-        mojo_base::mojom::Error::New(mojo_base::mojom::Code::kNotFound,
-                                     "group with the specified ID not found."));
-  }
-
-  tab_strip_model_adapter_->UpdateTabGroupVisuals(group_id.value(),
-                                                  visual_data);
-
-  return std::monostate();
-}
-
-void TabStripServiceSyncImpl::AddObserver(TabStripModelObserver* observer) {
-  tab_strip_model_adapter_->AddObserver(observer);
-}
-
-void TabStripServiceSyncImpl::RemoveObserver(TabStripModelObserver* observer) {
-  tab_strip_model_adapter_->RemoveObserver(observer);
-}
-
-}  // namespace tabs_api
diff --git a/chrome/browser/ui/tabs/tab_strip_api/tab_strip_service_sync_impl.h b/chrome/browser/ui/tabs/tab_strip_api/tab_strip_service_sync_impl.h
deleted file mode 100644
index 0bb973b..0000000
--- a/chrome/browser/ui/tabs/tab_strip_api/tab_strip_service_sync_impl.h
+++ /dev/null
@@ -1,64 +0,0 @@
-// Copyright 2025 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef CHROME_BROWSER_UI_TABS_TAB_STRIP_API_TAB_STRIP_SERVICE_SYNC_IMPL_H_
-#define CHROME_BROWSER_UI_TABS_TAB_STRIP_API_TAB_STRIP_SERVICE_SYNC_IMPL_H_
-
-#include "chrome/browser/ui/tabs/tab_strip_api/adapters/browser_adapter.h"
-#include "chrome/browser/ui/tabs/tab_strip_api/adapters/tab_strip_model_adapter.h"
-#include "chrome/browser/ui/tabs/tab_strip_api/tab_strip_api.mojom.h"
-#include "chrome/browser/ui/tabs/tab_strip_api/tab_strip_experiment_api.mojom.h"
-#include "chrome/browser/ui/tabs/tab_strip_api/tab_strip_service.h"
-
-namespace tabs_api {
-
-class TabStripServiceSyncImpl : public TabStripService {
- public:
-  TabStripServiceSyncImpl(BrowserWindowInterface* browser,
-                          TabStripModel* tab_strip_model);
-
-  TabStripServiceSyncImpl(
-      std::unique_ptr<BrowserAdapter> browser_adapter,
-      std::unique_ptr<TabStripModelAdapter> tab_strip_model_adapter);
-
-  ~TabStripServiceSyncImpl() override;
-
-  TabStripService::GetTabsResult GetTabs() override;
-  mojom::TabStripService::GetTabResult GetTab(
-      const tabs_api::NodeId& id) override;
-  mojom::TabStripService::CreateTabAtResult CreateTabAt(
-      const std::optional<tabs_api::Position>& pos,
-      const std::optional<GURL>& url) override;
-  mojom::TabStripService::CloseTabsResult CloseTabs(
-      const std::vector<tabs_api::NodeId>& ids) override;
-  mojom::TabStripService::ActivateTabResult ActivateTab(
-      const tabs_api::NodeId& id) override;
-  mojom::TabStripService::SetSelectedTabsResult SetSelectedTabs(
-      const std::vector<tabs_api::NodeId>& selection,
-      const tabs_api::NodeId& tab_to_activate) override;
-  mojom::TabStripService::MoveTabResult MoveTab(
-      const tabs_api::NodeId& id,
-      const tabs_api::Position& position) override;
-
-  // tabs_api::mojom::TabStripExperimentalService overrides
-  //
-  // TabStripExperimentalService is intended for quick prototyping for
-  // experimental apis that may not necessarily fit in the standard
-  // TabStripService.
-  mojom::TabStripExperimentService::UpdateTabGroupVisualResult
-  UpdateTabGroupVisual(
-      const tabs_api::NodeId& id,
-      const tab_groups::TabGroupVisualData& visual_data) override;
-
-  void AddObserver(TabStripModelObserver* observer) override;
-  void RemoveObserver(TabStripModelObserver* observer) override;
-
- private:
-  std::unique_ptr<tabs_api::BrowserAdapter> browser_adapter_;
-  std::unique_ptr<tabs_api::TabStripModelAdapter> tab_strip_model_adapter_;
-};
-
-}  // namespace tabs_api
-
-#endif  // CHROME_BROWSER_UI_TABS_TAB_STRIP_API_TAB_STRIP_SERVICE_SYNC_IMPL_H_
diff --git a/chrome/browser/ui/ui_features.cc b/chrome/browser/ui/ui_features.cc
index a79370a..14ce803 100644
--- a/chrome/browser/ui/ui_features.cc
+++ b/chrome/browser/ui/ui_features.cc
@@ -726,4 +726,15 @@
 }
 #endif  // !BUILDFLAG(IS_ANDROID)
 
+#if BUILDFLAG(IS_ANDROID)
+BASE_FEATURE(kAndroidAnimatedProgressBarInBrowser,
+             "AndroidAnimatedProgressBarInBrowser",
+             base::FEATURE_DISABLED_BY_DEFAULT);
+
+bool IsAndroidAnimatedProgressBarInBrowserEnabled() {
+  return base::FeatureList::IsEnabled(
+      features::kAndroidAnimatedProgressBarInBrowser);
+}
+#endif  // BUILDFLAG(IS_ANDROID)
+
 }  // namespace features
diff --git a/chrome/browser/ui/ui_features.h b/chrome/browser/ui/ui_features.h
index 3559187..c4cbd0b 100644
--- a/chrome/browser/ui/ui_features.h
+++ b/chrome/browser/ui/ui_features.h
@@ -420,6 +420,12 @@
 
 bool IsBookmarkTabGroupConversionEnabled();
 
+#if BUILDFLAG(IS_ANDROID)
+BASE_DECLARE_FEATURE(kAndroidAnimatedProgressBarInBrowser);
+
+bool IsAndroidAnimatedProgressBarInBrowserEnabled();
+#endif  // BUILDFLAG(IS_ANDROID)
+
 }  // namespace features
 
 #endif  // CHROME_BROWSER_UI_UI_FEATURES_H_
diff --git a/chrome/browser/ui/views/frame/browser_view.cc b/chrome/browser/ui/views/frame/browser_view.cc
index a6031f9..c8b2a11 100644
--- a/chrome/browser/ui/views/frame/browser_view.cc
+++ b/chrome/browser/ui/views/frame/browser_view.cc
@@ -999,10 +999,10 @@
   toolbar_ = top_container_->AddChildView(
       std::make_unique<ToolbarView>(browser_.get(), this));
 
-  contents_separator_ =
+  top_container_separator_ =
       top_container_->AddChildView(std::make_unique<ContentsSeparator>());
-  contents_separator_->SetProperty(views::kElementIdentifierKey,
-                                   kContentsSeparatorViewElementId);
+  top_container_separator_->SetProperty(views::kElementIdentifierKey,
+                                        kContentsSeparatorTopEdgeElementId);
 
   contents_container_ = AddChildView(std::move(contents_container));
   set_contents_view(contents_container_);
@@ -1015,29 +1015,33 @@
         AddChildView(std::move(vertical_tab_strip_container));
   }
 
-  right_aligned_side_panel_separator_ =
-      AddChildView(std::make_unique<ContentsSeparator>());
-  right_aligned_side_panel_separator_->SetProperty(
-      views::kElementIdentifierKey,
-      kRightAlignedSidePanelSeparatorViewElementId);
-
   const bool is_right_aligned = GetProfile()->GetPrefs()->GetBoolean(
       prefs::kSidePanelHorizontalAlignment);
   unified_side_panel_ = AddChildView(std::make_unique<SidePanel>(
       this, is_right_aligned ? SidePanel::HorizontalAlignment::kRight
                              : SidePanel::HorizontalAlignment::kLeft));
-  left_aligned_side_panel_separator_ =
-      AddChildView(std::make_unique<ContentsSeparator>());
-  left_aligned_side_panel_separator_->SetProperty(
-      views::kElementIdentifierKey,
-      kLeftAlignedSidePanelSeparatorViewElementId);
-  side_panel_rounded_corner_ =
-      AddChildView(std::make_unique<ContentsRoundedCorner>(
-          this, views::ShapeContextTokens::kContentSeparatorRadius,
-          base::BindRepeating(&SidePanel::IsRightAligned,
-                              base::Unretained(unified_side_panel_))));
-  side_panel_rounded_corner_->SetProperty(views::kElementIdentifierKey,
-                                          kSidePanelRoundedCornerViewElementId);
+
+  // `MultiContentsView` owns separators when `SideBySide` is enabled.
+  if (!multi_contents_view_) {
+    right_aligned_side_panel_separator_ =
+        AddChildView(std::make_unique<ContentsSeparator>());
+    right_aligned_side_panel_separator_->SetProperty(
+        views::kElementIdentifierKey,
+        kRightAlignedSidePanelSeparatorViewElementId);
+
+    left_aligned_side_panel_separator_ =
+        AddChildView(std::make_unique<ContentsSeparator>());
+    left_aligned_side_panel_separator_->SetProperty(
+        views::kElementIdentifierKey,
+        kLeftAlignedSidePanelSeparatorViewElementId);
+    side_panel_rounded_corner_ =
+        AddChildView(std::make_unique<ContentsRoundedCorner>(
+            this, views::ShapeContextTokens::kContentSeparatorRadius,
+            base::BindRepeating(&SidePanel::IsRightAligned,
+                                base::Unretained(unified_side_panel_))));
+    side_panel_rounded_corner_->SetProperty(
+        views::kElementIdentifierKey, kSidePanelRoundedCornerViewElementId);
+  }
 
   // InfoBarContainer needs to be added as a child here for drop-shadow, but
   // needs to come after toolbar in focus order (see EnsureFocusOrder()).
@@ -1127,7 +1131,7 @@
 
   webui_tab_strip_ = nullptr;
   toolbar_ = nullptr;
-  contents_separator_ = nullptr;
+  top_container_separator_ = nullptr;
   loading_bar_ = nullptr;
   find_bar_host_view_ = nullptr;
   infobar_container_ = nullptr;
@@ -2788,8 +2792,6 @@
       is_right_aligned ? SidePanel::HorizontalAlignment::kRight
                        : SidePanel::HorizontalAlignment::kLeft);
   GetBrowserViewLayout()->Layout(this);
-  side_panel_rounded_corner_->DeprecatedLayoutImmediately();
-  side_panel_rounded_corner_->SchedulePaint();
 }
 
 void BrowserView::FocusBookmarksToolbar() {
@@ -5209,7 +5211,7 @@
           contents_container_, multi_contents_view_,
           left_aligned_side_panel_separator_, unified_side_panel_,
           right_aligned_side_panel_separator_, side_panel_rounded_corner_,
-          contents_separator_));
+          top_container_separator_));
   browser_view_layout->SetUseBrowserContentMinimumSize(
       ShouldUseBrowserContentMinimumSize());
 
diff --git a/chrome/browser/ui/views/frame/browser_view.h b/chrome/browser/ui/views/frame/browser_view.h
index 75cf121..8393cf9 100644
--- a/chrome/browser/ui/views/frame/browser_view.h
+++ b/chrome/browser/ui/views/frame/browser_view.h
@@ -1186,7 +1186,11 @@
   std::unique_ptr<TabSearchBubbleHost> tab_search_bubble_host_;
 
   // Separator between top container and contents.
-  raw_ptr<views::View> contents_separator_ = nullptr;
+  // Note: when `SideBySide` feature is disabled, this separator is also
+  // used when not in `TopContainerOverlayView. Once the feature is fully
+  // rolled out, we can rely on `MultiContentsView` to manage the contents
+  // separator when not overlaid (i.e. no immersive fullscreen).
+  raw_ptr<views::View> top_container_separator_ = nullptr;
 
   // Loading bar (part of top container for / WebUI tab strip).
   raw_ptr<TopContainerLoadingBar> loading_bar_ = nullptr;
@@ -1225,6 +1229,9 @@
   // Conceptually this member should exist if and only if the
   // side_panel_coordinator is created.
   raw_ptr<SidePanel> unified_side_panel_ = nullptr;
+
+  // These are only non-null when the `SideBySide` feature is disabled.
+  // Otherwise, `multi_contents_view_` will create its own separators.
   raw_ptr<views::View> right_aligned_side_panel_separator_ = nullptr;
   raw_ptr<views::View> left_aligned_side_panel_separator_ = nullptr;
   raw_ptr<views::View> side_panel_rounded_corner_ = nullptr;
diff --git a/chrome/browser/ui/views/frame/browser_view_layout.cc b/chrome/browser/ui/views/frame/browser_view_layout.cc
index 84ae670..ec24160 100644
--- a/chrome/browser/ui/views/frame/browser_view_layout.cc
+++ b/chrome/browser/ui/views/frame/browser_view_layout.cc
@@ -48,6 +48,7 @@
 #include "ui/gfx/geometry/point.h"
 #include "ui/gfx/geometry/rect.h"
 #include "ui/gfx/geometry/size.h"
+#include "ui/views/controls/separator.h"
 #include "ui/views/controls/webview/webview.h"
 #include "ui/views/view.h"
 #include "ui/views/view_utils.h"
@@ -245,7 +246,7 @@
     views::View* unified_side_panel,
     views::View* right_aligned_side_panel_separator,
     views::View* side_panel_rounded_corner,
-    views::View* contents_separator)
+    views::View* top_container_separator)
     : delegate_(std::move(delegate)),
       browser_view_(browser_view),
       window_scrim_(window_scrim),
@@ -258,11 +259,11 @@
       infobar_container_(infobar_container),
       contents_container_(contents_container),
       multi_contents_view_(multi_contents_view),
-      left_aligned_side_panel_separator_(left_aligned_side_panel_separator),
       unified_side_panel_(unified_side_panel),
+      left_aligned_side_panel_separator_(left_aligned_side_panel_separator),
       right_aligned_side_panel_separator_(right_aligned_side_panel_separator),
       side_panel_rounded_corner_(side_panel_rounded_corner),
-      contents_separator_(contents_separator),
+      top_container_separator_(top_container_separator),
       tab_strip_(tab_strip_region_view_->tab_strip()),
       dialog_host_(std::make_unique<BrowserModalDialogHostViews>(this)) {}
 
@@ -605,11 +606,30 @@
 
   if (delegate_->IsContentsSeparatorEnabled() &&
       (toolbar_->GetVisible() || bookmark_bar_) && available_bounds.y() > 0) {
-    SetViewVisibility(contents_separator_, true);
-    const int separator_height =
-        contents_separator_->GetPreferredSize().height();
-    contents_separator_->SetBounds(available_bounds.x(), available_bounds.y(),
-                                   available_bounds.width(), separator_height);
+    int separator_height = 0;
+    if (multi_contents_view_) {
+      const bool show_overlay_contents_separator =
+          delegate_->GetImmersiveModeController()->IsEnabled();
+      if (show_overlay_contents_separator) {
+        separator_height =
+            top_container_separator_->GetPreferredSize().height();
+        SetViewVisibility(top_container_separator_, true);
+        top_container_separator_->SetBounds(
+            available_bounds.x(), available_bounds.y(),
+            available_bounds.width(), separator_height);
+      }
+      // If the loading bar will be shown, it's supposed to replace the
+      // separator.
+      multi_contents_view_->SetShouldShowTopSeparator(
+          !show_overlay_contents_separator && !loading_bar_);
+    } else {
+      separator_height = top_container_separator_->GetPreferredSize().height();
+      SetViewVisibility(top_container_separator_, true);
+      top_container_separator_->SetBounds(
+          available_bounds.x(), available_bounds.y(), available_bounds.width(),
+          separator_height);
+    }
+
     if (loading_bar_) {
       SetViewVisibility(loading_bar_, true);
       loading_bar_->SetBounds(available_bounds.x(), available_bounds.y() - 2,
@@ -619,7 +639,10 @@
     }
     available_bounds.set_y(available_bounds.y() + separator_height);
   } else {
-    SetViewVisibility(contents_separator_, false);
+    SetViewVisibility(top_container_separator_, false);
+    if (multi_contents_view_) {
+      multi_contents_view_->SetShouldShowTopSeparator(false);
+    }
     if (loading_bar_) {
       SetViewVisibility(loading_bar_, false);
     }
@@ -649,7 +672,7 @@
     // TODO(crbug.com/41344902): Remove once the pixel canvas is enabled on
     // all aura platforms.
     if (top_container_ == bookmark_bar_->parent()) {
-      top_container_->ReorderChildView(contents_separator_,
+      top_container_->ReorderChildView(top_container_separator_,
                                        top_container_->children().size());
     }
   }
@@ -729,9 +752,10 @@
   views::View* side_panel_separator =
       side_panel_right_aligned ? right_aligned_side_panel_separator_.get()
                                : left_aligned_side_panel_separator_.get();
-  CHECK(side_panel_separator);
   const int separator_width =
-      is_in_split ? 0 : side_panel_separator->GetPreferredSize().width();
+      is_in_split || !side_panel_separator
+          ? 0
+          : side_panel_separator->GetPreferredSize().width();
 
   // Side panel occupies some of the container's space. The side panel should
   // never occupy more space than is available in the content window, and
@@ -827,39 +851,46 @@
   if (unified_side_panel_) {
     unified_side_panel_->SetBoundsRect(layout_result.side_panel_bounds);
   }
-  if (right_aligned_side_panel_separator_) {
+
+  if (multi_contents_view_) {
+    multi_contents_view_->SetShouldShowLeadingSeparator(
+        layout_result.side_panel_visible && !is_in_split &&
+        (layout_result.side_panel_right_aligned == base::i18n::IsRTL()));
+
+    multi_contents_view_->SetShouldShowTrailingSeparator(
+        layout_result.side_panel_visible && !is_in_split &&
+        (layout_result.side_panel_right_aligned != base::i18n::IsRTL()));
+  } else {
     SetViewVisibility(right_aligned_side_panel_separator_,
                       layout_result.side_panel_visible &&
                           layout_result.side_panel_right_aligned &&
                           !is_in_split);
     right_aligned_side_panel_separator_->SetBoundsRect(
         layout_result.separator_bounds);
-  }
-  if (left_aligned_side_panel_separator_) {
     SetViewVisibility(left_aligned_side_panel_separator_,
                       layout_result.side_panel_visible &&
                           !layout_result.side_panel_right_aligned &&
                           !is_in_split);
     left_aligned_side_panel_separator_->SetBoundsRect(
         layout_result.separator_bounds);
-  }
 
-  if (side_panel_rounded_corner_) {
     SetViewVisibility(side_panel_rounded_corner_,
                       layout_result.side_panel_visible && !is_in_split);
     if (layout_result.side_panel_visible) {
       // Adjust the rounded corner bounds based on the side panel bounds.
       const int corner_size =
           side_panel_rounded_corner_->GetPreferredSize().width();
+
+      const int top_separator_height = views::Separator::kThickness;
       if (layout_result.contents_container_after_side_panel) {
         side_panel_rounded_corner_->SetBounds(
             layout_result.side_panel_bounds.right(),
-            layout_result.side_panel_bounds.y() - views::Separator::kThickness,
+            layout_result.side_panel_bounds.y() - top_separator_height,
             corner_size, corner_size);
       } else {
         side_panel_rounded_corner_->SetBounds(
             layout_result.side_panel_bounds.x() - corner_size,
-            layout_result.side_panel_bounds.y() - views::Separator::kThickness,
+            layout_result.side_panel_bounds.y() - top_separator_height,
             corner_size, corner_size);
       }
     }
@@ -909,7 +940,9 @@
   int min_width =
       kMainBrowserContentsMinimumWidth -
       unified_side_panel_->GetMinimumSize().width() -
-      right_aligned_side_panel_separator_->GetPreferredSize().width();
+      (right_aligned_side_panel_separator_
+           ? right_aligned_side_panel_separator_->GetPreferredSize().width()
+           : 0);
 
   // When in split view, the minimum width of the contents is higher.
   if (base::FeatureList::IsEnabled(features::kSideBySide)) {
diff --git a/chrome/browser/ui/views/frame/browser_view_layout.h b/chrome/browser/ui/views/frame/browser_view_layout.h
index 8132c2d9..6fffdc1 100644
--- a/chrome/browser/ui/views/frame/browser_view_layout.h
+++ b/chrome/browser/ui/views/frame/browser_view_layout.h
@@ -66,7 +66,7 @@
                     views::View* unified_side_panel,
                     views::View* right_aligned_side_panel_separator,
                     views::View* side_panel_rounded_corner,
-                    views::View* contents_separator);
+                    views::View* top_container_separator);
 
   BrowserViewLayout(const BrowserViewLayout&) = delete;
   BrowserViewLayout& operator=(const BrowserViewLayout&) = delete;
@@ -164,11 +164,20 @@
   const raw_ptr<InfoBarContainerView> infobar_container_;
   const raw_ptr<views::View> contents_container_;
   const raw_ptr<MultiContentsView> multi_contents_view_;
-  const raw_ptr<views::View> left_aligned_side_panel_separator_;
   const raw_ptr<views::View> unified_side_panel_;
+
+  // TODO(crbug.com/424236535): These can be removed once `SideBySide` is
+  // launched.
+  const raw_ptr<views::View> left_aligned_side_panel_separator_;
   const raw_ptr<views::View> right_aligned_side_panel_separator_;
   const raw_ptr<views::View> side_panel_rounded_corner_;
-  const raw_ptr<views::View> contents_separator_;
+
+  // The contents separator used for when the top container is overlaid.
+  // Note: when `SideBySide` feature is disabled, this separator is also
+  // used when not overlaid. Once the feature is fully rolled out, we can
+  // rely on `MultiContentsView` to manage the contents separator when not
+  // overlaid (i.e. no immersive fullscreen).
+  const raw_ptr<views::View> top_container_separator_ = nullptr;
 
   // These views are dynamically set.
   raw_ptr<views::View> webui_tab_strip_ = nullptr;
diff --git a/chrome/browser/ui/views/frame/browser_view_layout_unittest.cc b/chrome/browser/ui/views/frame/browser_view_layout_unittest.cc
index 65b4dc5..dae1cd1 100644
--- a/chrome/browser/ui/views/frame/browser_view_layout_unittest.cc
+++ b/chrome/browser/ui/views/frame/browser_view_layout_unittest.cc
@@ -182,6 +182,10 @@
     infobar_container_ = browser_view_->AddChildView(
         std::make_unique<InfoBarContainerView>(nullptr));
 
+    left_aligned_side_panel_separator_ =
+        browser_view_->AddChildView(std::make_unique<views::Separator>());
+    right_aligned_side_panel_separator_ =
+        browser_view_->AddChildView(std::make_unique<views::Separator>());
     side_panel_rounded_corner_ =
         browser_view_->AddChildView(CreateFixedSizeView(gfx::Size(16, 16)));
 
@@ -213,10 +217,8 @@
         /*web_app_window_title=*/nullptr, tab_strip_region_view_,
         /*vertical_tab_strip_container=*/nullptr, toolbar_, infobar_container_,
         contents_container_,
-        /*multi_contents_view=*/nullptr,
-        /*left_aligned_side_panel_separator=*/nullptr,
-        /*unified_side_panel=*/nullptr,
-        /*right_aligned_side_panel_separator=*/nullptr,
+        /*multi_contents_view=*/nullptr, left_aligned_side_panel_separator_,
+        /*unified_side_panel=*/nullptr, right_aligned_side_panel_separator_,
         side_panel_rounded_corner_, separator_);
     layout->set_webui_tab_strip(webui_tab_strip());
     layout_ = layout.get();
@@ -238,6 +240,8 @@
     toolbar_ = nullptr;
     separator_ = nullptr;
     infobar_container_ = nullptr;
+    left_aligned_side_panel_separator_ = nullptr;
+    right_aligned_side_panel_separator_ = nullptr;
     side_panel_rounded_corner_ = nullptr;
     contents_container_ = nullptr;
     contents_web_view_ = nullptr;
@@ -269,6 +273,8 @@
   raw_ptr<views::View> toolbar_;
   raw_ptr<views::Separator> separator_;
   raw_ptr<InfoBarContainerView> infobar_container_;
+  raw_ptr<views::View> left_aligned_side_panel_separator_;
+  raw_ptr<views::View> right_aligned_side_panel_separator_;
   raw_ptr<views::View> side_panel_rounded_corner_;
   raw_ptr<views::View> contents_container_;
   raw_ptr<views::View> contents_web_view_;
diff --git a/chrome/browser/ui/views/frame/multi_contents_view.cc b/chrome/browser/ui/views/frame/multi_contents_view.cc
index b2ebcdc..e71df1a 100644
--- a/chrome/browser/ui/views/frame/multi_contents_view.cc
+++ b/chrome/browser/ui/views/frame/multi_contents_view.cc
@@ -9,12 +9,15 @@
 
 #include "base/check_deref.h"
 #include "base/feature_list.h"
+#include "base/i18n/rtl.h"
 #include "base/notreached.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/ui/browser_element_identifiers.h"
 #include "chrome/browser/ui/ui_features.h"
 #include "chrome/browser/ui/views/frame/browser_view.h"
 #include "chrome/browser/ui/views/frame/contents_container_view.h"
+#include "chrome/browser/ui/views/frame/contents_rounded_corner.h"
+#include "chrome/browser/ui/views/frame/contents_separator.h"
 #include "chrome/browser/ui/views/frame/contents_web_view.h"
 #include "chrome/browser/ui/views/frame/multi_contents_drop_target_view.h"
 #include "chrome/browser/ui/views/frame/multi_contents_resize_area.h"
@@ -24,6 +27,7 @@
 #include "chrome/browser/ui/views/frame/scrim_view.h"
 #include "chrome/browser/ui/views/frame/top_container_background.h"
 #include "chrome/browser/ui/views/new_tab_footer/footer_web_view.h"
+#include "chrome/browser/ui/views/toolbar/toolbar_view.h"
 #include "chrome/common/pref_names.h"
 #include "components/prefs/pref_service.h"
 #include "content/public/browser/web_contents.h"
@@ -34,6 +38,14 @@
 #include "ui/ozone/public/ozone_platform.h"
 #include "ui/views/view_class_properties.h"
 
+void MultiContentsView::ContentsSeparators::Reset() {
+  top_separator = nullptr;
+  leading_separator = nullptr;
+  trailing_separator = nullptr;
+  top_leading_rounded_corner = nullptr;
+  top_trailing_rounded_corner = nullptr;
+}
+
 MultiContentsView::MultiContentsView(
     BrowserView* browser_view,
     std::unique_ptr<MultiContentsViewDelegate> delegate)
@@ -44,6 +56,8 @@
       end_contents_view_inset_(
           gfx::Insets(kSplitViewContentInset).set_top(0).set_left(0)) {
   SetLayoutManager(std::make_unique<views::DelegatingLayoutManager>(this));
+  SetProperty(views::kElementIdentifierKey, kMultiContentsViewElementId);
+
   contents_container_views_.push_back(
       AddChildView(std::make_unique<ContentsContainerView>(browser_view_)));
   contents_container_views_[0]
@@ -57,6 +71,43 @@
       AddChildView(std::make_unique<ContentsContainerView>(browser_view_)));
   contents_container_views_[1]->SetVisible(false);
 
+  drop_target_view_ =
+      AddChildView(std::make_unique<MultiContentsDropTargetView>());
+  drop_target_controller_ =
+      std::make_unique<MultiContentsViewDropTargetController>(
+          *drop_target_view_, *delegate_);
+
+  contents_separators_.top_separator =
+      AddChildView(std::make_unique<ContentsSeparator>());
+  contents_separators_.top_separator->SetProperty(
+      views::kElementIdentifierKey, kContentsSeparatorTopEdgeElementId);
+
+  contents_separators_.leading_separator =
+      AddChildView(std::make_unique<ContentsSeparator>());
+  contents_separators_.leading_separator->SetProperty(
+      views::kElementIdentifierKey, kContentsSeparatorLeadingEdgeElementId);
+
+  contents_separators_.trailing_separator =
+      AddChildView(std::make_unique<ContentsSeparator>());
+  contents_separators_.trailing_separator->SetProperty(
+      views::kElementIdentifierKey, kContentsSeparatorTrailingEdgeElementId);
+
+  contents_separators_.top_leading_rounded_corner =
+      AddChildView(std::make_unique<ContentsRoundedCorner>(
+          browser_view_, views::ShapeContextTokens::kContentSeparatorRadius,
+          base::BindRepeating([]() { return base::i18n::IsRTL(); })));
+  contents_separators_.top_leading_rounded_corner->SetProperty(
+      views::kElementIdentifierKey,
+      kContentsSeparatorLeadingTopCornerElementId);
+
+  contents_separators_.top_trailing_rounded_corner =
+      AddChildView(std::make_unique<ContentsRoundedCorner>(
+          browser_view_, views::ShapeContextTokens::kContentSeparatorRadius,
+          base::BindRepeating([]() { return !base::i18n::IsRTL(); })));
+  contents_separators_.top_trailing_rounded_corner->SetProperty(
+      views::kElementIdentifierKey,
+      kContentsSeparatorTrailingTopCornerElementId);
+
   for (auto* contents_container_view : contents_container_views_) {
     web_contents_focused_subscriptions_.push_back(
         contents_container_view->contents_view()->AddWebContentsFocusedCallback(
@@ -72,13 +123,6 @@
     }
   }
 
-  SetProperty(views::kElementIdentifierKey, kMultiContentsViewElementId);
-
-  drop_target_view_ =
-      AddChildView(std::make_unique<MultiContentsDropTargetView>());
-  drop_target_controller_ =
-      std::make_unique<MultiContentsViewDropTargetController>(
-          *drop_target_view_, *delegate_);
   is_drag_drop_pref_enabled_ =
       browser_view_->GetProfile()->GetPrefs()->GetBoolean(
           prefs::kSplitViewDragAndDropEnabled);
@@ -96,6 +140,7 @@
   }
   drop_target_view_ = nullptr;
   resize_area_ = nullptr;
+  contents_separators_.Reset();
   RemoveAllChildViews();
 }
 
@@ -131,7 +176,24 @@
   const int drop_target_width =
       IsDragAndDropEnabled() ? drop_target_view_->GetPreferredWidth(width())
                              : 0;
-  return gfx::Size(width() - drop_target_width, height());
+  const int separator_height =
+      contents_separators_.should_show_top
+          ? contents_separators_.top_separator->GetPreferredSize().height()
+          : 0;
+
+  const int leading_separator_width =
+      contents_separators_.should_show_leading
+          ? contents_separators_.leading_separator->GetPreferredSize().height()
+          : 0;
+
+  const int trailing_separator_width =
+      contents_separators_.should_show_trailing
+          ? contents_separators_.trailing_separator->GetPreferredSize().height()
+          : 0;
+
+  return gfx::Size(width() - drop_target_width - leading_separator_width -
+                       trailing_separator_width,
+                   height() - separator_height);
 }
 
 bool MultiContentsView::IsInSplitView() const {
@@ -347,11 +409,12 @@
   if (!size_bounds.is_fully_bounded()) {
     return layouts;
   }
+  const int width = size_bounds.width().value();
+  const int height = size_bounds.height().value();
 
-  int height = size_bounds.height().value();
-  int width = size_bounds.width().value();
+  gfx::Rect available_space = CalculateSeparatorLayouts(
+      gfx::Rect(width, height), layouts.child_layouts);
 
-  const gfx::Rect available_space(width, height);
   ViewWidths widths = GetViewWidths(available_space);
 
   gfx::Rect drop_target_rect(widths.drop_target_width,
@@ -406,6 +469,66 @@
   return layouts;
 }
 
+gfx::Rect MultiContentsView::CalculateSeparatorLayouts(
+    const gfx::Rect& available_space,
+    std::vector<views::ChildLayout>& child_layouts) const {
+  const int width = available_space.width();
+  const int height = available_space.height();
+
+  const int separator_height =
+      contents_separators_.should_show_top
+          ? contents_separators_.top_separator->GetPreferredSize().height()
+          : 0;
+  child_layouts.emplace_back(
+      contents_separators_.top_separator.get(),
+      contents_separators_.should_show_top,
+      gfx::Rect(available_space.origin(), {width, separator_height}));
+
+  const int leading_separator_width =
+      contents_separators_.should_show_leading
+          ? contents_separators_.leading_separator->GetPreferredSize().width()
+          : 0;
+  child_layouts.emplace_back(
+      contents_separators_.leading_separator.get(),
+      contents_separators_.should_show_leading,
+      gfx::Rect(available_space.origin(), {leading_separator_width, height}));
+
+  const int trailing_separator_width =
+      contents_separators_.should_show_trailing
+          ? contents_separators_.trailing_separator->GetPreferredSize().width()
+          : 0;
+  child_layouts.emplace_back(
+      contents_separators_.trailing_separator.get(),
+      contents_separators_.should_show_trailing,
+      gfx::Rect(available_space.right() - trailing_separator_width,
+                available_space.y(), trailing_separator_width, height));
+
+  child_layouts.emplace_back(
+      contents_separators_.top_leading_rounded_corner.get(),
+      contents_separators_.should_show_leading &&
+          contents_separators_.should_show_top,
+      gfx::Rect(
+          available_space.origin(),
+          contents_separators_.top_leading_rounded_corner->GetPreferredSize()));
+
+  child_layouts.emplace_back(
+      contents_separators_.top_trailing_rounded_corner.get(),
+      contents_separators_.should_show_trailing &&
+          contents_separators_.should_show_top,
+      gfx::Rect({available_space.right() -
+                     contents_separators_.top_trailing_rounded_corner
+                         ->GetPreferredSize()
+                         .width(),
+                 available_space.y()},
+                contents_separators_.top_trailing_rounded_corner
+                    ->GetPreferredSize()));
+
+  return gfx::Rect(available_space.x() + leading_separator_width,
+                   available_space.y() + separator_height,
+                   width - trailing_separator_width - leading_separator_width,
+                   height - separator_height);
+}
+
 MultiContentsView::ViewWidths MultiContentsView::GetViewWidths(
     gfx::Rect available_space) const {
   ViewWidths widths;
@@ -488,5 +611,32 @@
   InvalidateLayout();
 }
 
+void MultiContentsView::SetShouldShowTopSeparator(bool should_show) {
+  if (contents_separators_.should_show_top == should_show) {
+    return;
+  }
+  contents_separators_.should_show_top = should_show;
+
+  InvalidateLayout();
+}
+
+void MultiContentsView::SetShouldShowLeadingSeparator(bool should_show) {
+  if (contents_separators_.should_show_leading == should_show) {
+    return;
+  }
+  contents_separators_.should_show_leading = should_show;
+
+  InvalidateLayout();
+}
+
+void MultiContentsView::SetShouldShowTrailingSeparator(bool should_show) {
+  if (contents_separators_.should_show_trailing == should_show) {
+    return;
+  }
+  contents_separators_.should_show_trailing = should_show;
+
+  InvalidateLayout();
+}
+
 BEGIN_METADATA(MultiContentsView)
 END_METADATA
diff --git a/chrome/browser/ui/views/frame/multi_contents_view.h b/chrome/browser/ui/views/frame/multi_contents_view.h
index 4613d278..37f59ce2 100644
--- a/chrome/browser/ui/views/frame/multi_contents_view.h
+++ b/chrome/browser/ui/views/frame/multi_contents_view.h
@@ -10,6 +10,7 @@
 
 #include "base/callback_list.h"
 #include "base/functional/callback_forward.h"
+#include "base/gtest_prod_util.h"
 #include "base/memory/raw_ptr.h"
 #include "chrome/browser/ui/ui_features.h"
 #include "chrome/browser/ui/views/frame/contents_container_view.h"
@@ -151,6 +152,10 @@
   bool IsDragAndDropEnabled() const;
   void OnDragAndDropPrefStateChange();
 
+  void SetShouldShowTopSeparator(bool should_show);
+  void SetShouldShowLeadingSeparator(bool should_show);
+  void SetShouldShowTrailingSeparator(bool should_show);
+
   void set_min_contents_width_for_testing(int width) {
     min_contents_width_for_testing_ = std::make_optional(width);
   }
@@ -172,6 +177,23 @@
   }
 
  private:
+  FRIEND_TEST_ALL_PREFIXES(MultiContentsViewBrowserTest, SeparatorLayout);
+
+  // Encapsulates the views required to draw a separator around contents.
+  struct ContentsSeparators {
+    void Reset();
+
+    raw_ptr<views::View> top_separator = nullptr;
+    raw_ptr<views::View> leading_separator = nullptr;
+    raw_ptr<views::View> trailing_separator = nullptr;
+    raw_ptr<views::View> top_leading_rounded_corner = nullptr;
+    raw_ptr<views::View> top_trailing_rounded_corner = nullptr;
+
+    bool should_show_top = false;
+    bool should_show_leading = false;
+    bool should_show_trailing = false;
+  };
+
   static constexpr int kMinWebContentsWidth = 200;
   static constexpr double kMinWebContentsWidthPercentage = 0.1;
 
@@ -179,6 +201,12 @@
   views::ProposedLayout CalculateProposedLayout(
       const views::SizeBounds& size_bounds) const override;
 
+  // Adds separator layouts to the given list and returns the remaining
+  // space after the layout.
+  gfx::Rect CalculateSeparatorLayouts(
+      const gfx::Rect& available_space,
+      std::vector<views::ChildLayout>& child_layouts) const;
+
   int GetInactiveIndex() const;
 
   void OnWebContentsFocused(views::WebView*);
@@ -199,6 +227,8 @@
   raw_ptr<BrowserView> browser_view_;
   std::unique_ptr<MultiContentsViewDelegate> delegate_;
 
+  ContentsSeparators contents_separators_;
+
   // Holds ContentsContainerViews, when not in a split view the second
   // ContentsContainerView is not visible.
   std::vector<ContentsContainerView*> contents_container_views_;
diff --git a/chrome/browser/ui/views/frame/multi_contents_view_browsertest.cc b/chrome/browser/ui/views/frame/multi_contents_view_browsertest.cc
index 7e18b56..f6bbac04 100644
--- a/chrome/browser/ui/views/frame/multi_contents_view_browsertest.cc
+++ b/chrome/browser/ui/views/frame/multi_contents_view_browsertest.cc
@@ -38,6 +38,7 @@
 #include "ui/base/ozone_buildflags.h"
 #include "ui/compositor/layer_tree_owner.h"
 #include "ui/ozone/public/ozone_platform.h"
+#include "ui/views/controls/separator.h"
 #include "ui/views/interaction/element_tracker_views.h"
 #include "ui/views/view_utils.h"
 #include "url/gurl.h"
@@ -517,3 +518,54 @@
   // Should resized twice.
   EXPECT_EQ(GetResizeCount(split_tab), 2);
 }
+
+IN_PROC_BROWSER_TEST_F(MultiContentsViewBrowserTest, SeparatorLayout) {
+  MultiContentsView* view = multi_contents_view();
+  view->SetShouldShowTrailingSeparator(true);
+  view->SetShouldShowLeadingSeparator(true);
+  view->SetShouldShowTopSeparator(true);
+
+  gfx::Rect initial_bounds(10, 20, 100, 80);
+  std::vector<views::ChildLayout> actual_child_layouts;
+
+  gfx::Rect remaining_space =
+      view->CalculateSeparatorLayouts(initial_bounds, actual_child_layouts);
+
+  constexpr int kSeparatorThickness = views::Separator::kThickness;
+
+  gfx::Rect expected_remaining_space(
+      initial_bounds.x() + kSeparatorThickness,
+      initial_bounds.y() + kSeparatorThickness,
+      initial_bounds.width() - 2 * kSeparatorThickness,
+      initial_bounds.height() - kSeparatorThickness);
+  EXPECT_EQ(expected_remaining_space, remaining_space);
+
+  std::vector<views::ChildLayout> expected_separator_layouts;
+  expected_separator_layouts.emplace_back(
+      view->contents_separators_.top_separator.get(), true,
+      gfx::Rect(10, 20, 100, kSeparatorThickness));
+  expected_separator_layouts.emplace_back(
+      view->contents_separators_.leading_separator.get(), true,
+      gfx::Rect(10, 20, kSeparatorThickness, 80));
+  expected_separator_layouts.emplace_back(
+      view->contents_separators_.trailing_separator.get(), true,
+      gfx::Rect(10 + 100 - kSeparatorThickness, 20, kSeparatorThickness, 80));
+  expected_separator_layouts.emplace_back(
+      view->contents_separators_.top_leading_rounded_corner.get(), true,
+      gfx::Rect(initial_bounds.origin(),
+                view->contents_separators_.top_leading_rounded_corner
+                    ->GetPreferredSize()));
+  expected_separator_layouts.emplace_back(
+      view->contents_separators_.top_trailing_rounded_corner.get(), true,
+      gfx::Rect(
+          gfx::Point(initial_bounds.right() -
+                         view->contents_separators_.top_trailing_rounded_corner
+                             ->GetPreferredSize()
+                             .width(),
+                     initial_bounds.y()),
+          view->contents_separators_.top_trailing_rounded_corner
+              ->GetPreferredSize()));
+
+  EXPECT_THAT(actual_child_layouts,
+              testing::UnorderedElementsAreArray(expected_separator_layouts));
+}
diff --git a/chrome/browser/ui/views/frame/multi_contents_view_interactive_uitest.cc b/chrome/browser/ui/views/frame/multi_contents_view_interactive_uitest.cc
index 2f5f405..0012bd4 100644
--- a/chrome/browser/ui/views/frame/multi_contents_view_interactive_uitest.cc
+++ b/chrome/browser/ui/views/frame/multi_contents_view_interactive_uitest.cc
@@ -230,7 +230,7 @@
       ObserveState(kMultiContentsViewBoundsChangedObserver, browser()),
       SelectTab(kTabStripElementId, 2, InputType::kMouse),
       WaitForActiveTabChange(2),
-      CheckState(kMultiContentsViewBoundsChangedObserver, 1),
+      CheckState(kMultiContentsViewBoundsChangedObserver, 0),
       StopObservingState(kMultiContentsViewBoundsChangedObserver));
 }
 
@@ -636,17 +636,19 @@
       // Verify expected contents separators are visible. Note, only one side
       // panel separator should be visible and the side panel is right aligned
       // by default.
-      WaitForShow(kContentsSeparatorViewElementId),
-      WaitForShow(kRightAlignedSidePanelSeparatorViewElementId),
-      WaitForHide(kLeftAlignedSidePanelSeparatorViewElementId),
-      WaitForShow(kSidePanelRoundedCornerViewElementId),
+      WaitForShow(kContentsSeparatorTopEdgeElementId),
+      WaitForShow(kContentsSeparatorTrailingEdgeElementId),
+      WaitForHide(kContentsSeparatorLeadingEdgeElementId),
+      WaitForShow(kContentsSeparatorTrailingTopCornerElementId),
+      WaitForHide(kContentsSeparatorLeadingTopCornerElementId),
       // Open split view.
       CreateTabsAndEnterSplitView(),
       // Verify no contents separators are visible.
-      WaitForHide(kContentsSeparatorViewElementId),
-      WaitForHide(kRightAlignedSidePanelSeparatorViewElementId),
-      WaitForHide(kLeftAlignedSidePanelSeparatorViewElementId),
-      WaitForHide(kSidePanelRoundedCornerViewElementId));
+      WaitForHide(kContentsSeparatorTopEdgeElementId),
+      WaitForHide(kContentsSeparatorTrailingEdgeElementId),
+      WaitForHide(kContentsSeparatorLeadingEdgeElementId),
+      WaitForHide(kContentsSeparatorTrailingTopCornerElementId),
+      WaitForHide(kContentsSeparatorLeadingTopCornerElementId));
 }
 
 IN_PROC_BROWSER_TEST_F(MultiContentsViewUiTest,
diff --git a/chrome/browser/ui/views/omnibox/omnibox_view_views.cc b/chrome/browser/ui/views/omnibox/omnibox_view_views.cc
index ce82ddaf7..94456d6 100644
--- a/chrome/browser/ui/views/omnibox/omnibox_view_views.cc
+++ b/chrome/browser/ui/views/omnibox/omnibox_view_views.cc
@@ -2247,6 +2247,7 @@
 }
 
 void OmniboxViewViews::OnDidPresentCompositorFrame(
+    ui::Compositor* compositor,
     uint32_t frame_token,
     const gfx::PresentationFeedback& feedback) {
   if (latency_histogram_state_ == COMPOSITING_STARTED) {
diff --git a/chrome/browser/ui/views/omnibox/omnibox_view_views.h b/chrome/browser/ui/views/omnibox/omnibox_view_views.h
index 34c01b7..6c6c3329 100644
--- a/chrome/browser/ui/views/omnibox/omnibox_view_views.h
+++ b/chrome/browser/ui/views/omnibox/omnibox_view_views.h
@@ -300,6 +300,7 @@
   void OnCompositingStarted(ui::Compositor* compositor,
                             base::TimeTicks start_time) override;
   void OnDidPresentCompositorFrame(
+      ui::Compositor* compositor,
       uint32_t frame_token,
       const gfx::PresentationFeedback& feedback) override;
   void OnCompositingShuttingDown(ui::Compositor* compositor) override;
diff --git a/chrome/browser/ui/views/payments/shipping_address_editor_view_controller.cc b/chrome/browser/ui/views/payments/shipping_address_editor_view_controller.cc
index 188bf94..217bd5e 100644
--- a/chrome/browser/ui/views/payments/shipping_address_editor_view_controller.cc
+++ b/chrome/browser/ui/views/payments/shipping_address_editor_view_controller.cc
@@ -146,9 +146,9 @@
       const variations::VariationsService* variations_service =
           g_browser_process->variations_service();
       model->SetCountries(
-          GeoIpCountryCode(variations_service
-                               ? variations_service->GetLatestCountry()
-                               : std::string()),
+          autofill::GeoIpCountryCode(
+              variations_service ? variations_service->GetLatestCountry()
+                                 : std::string()),
           state()->GetApplicationLocale());
       if (model->countries().size() != countries_.size()) {
         UpdateCountries(model.get());
@@ -402,9 +402,9 @@
     const variations::VariationsService* variations_service =
         g_browser_process->variations_service();
     local_model.SetCountries(
-        GeoIpCountryCode(variations_service
-                             ? variations_service->GetLatestCountry()
-                             : std::string()),
+        autofill::GeoIpCountryCode(variations_service
+                                       ? variations_service->GetLatestCountry()
+                                       : std::string()),
         state()->GetApplicationLocale());
     model = &local_model;
   }
diff --git a/chrome/browser/ui/views/payments/shipping_address_editor_view_controller_browsertest.cc b/chrome/browser/ui/views/payments/shipping_address_editor_view_controller_browsertest.cc
index f453748..3916a5c 100644
--- a/chrome/browser/ui/views/payments/shipping_address_editor_view_controller_browsertest.cc
+++ b/chrome/browser/ui/views/payments/shipping_address_editor_view_controller_browsertest.cc
@@ -515,8 +515,8 @@
                        SelectingIncompleteAddress) {
   NavigateTo("/payment_request_dynamic_shipping_test.html");
   // Add incomplete address.
-  autofill::AutofillProfile profile(
-      AddressCountryCode(base::UTF16ToUTF8(kCountryWithoutStatesCode)));
+  autofill::AutofillProfile profile(autofill::AddressCountryCode(
+      base::UTF16ToUTF8(kCountryWithoutStatesCode)));
   profile.SetInfo(autofill::NAME_FULL, kNameFull, kLocale);
   AddAutofillProfile(profile);
 
diff --git a/chrome/browser/ui/views/side_panel/BUILD.gn b/chrome/browser/ui/views/side_panel/BUILD.gn
index 5d68e63..edb82ca 100644
--- a/chrome/browser/ui/views/side_panel/BUILD.gn
+++ b/chrome/browser/ui/views/side_panel/BUILD.gn
@@ -94,6 +94,12 @@
     "side_panel_web_ui_view.cc",
     "side_panel_web_ui_view.h",
   ]
+  if (enable_glic) {
+    sources += [
+      "glic/glic_side_panel_coordinator.cc",
+      "glic/glic_side_panel_coordinator.h",
+    ]
+  }
   public_deps = [
     "//base",
     "//chrome/browser:browser_public_dependencies",
diff --git a/chrome/browser/ui/views/side_panel/glic/OWNERS b/chrome/browser/ui/views/side_panel/glic/OWNERS
new file mode 100644
index 0000000..f7b6e02
--- /dev/null
+++ b/chrome/browser/ui/views/side_panel/glic/OWNERS
@@ -0,0 +1,2 @@
+perrier@chromium.org
+file://chrome/browser/glic/OWNERS
\ No newline at end of file
diff --git a/chrome/browser/glic/widget/glic_side_panel_coordinator.cc b/chrome/browser/ui/views/side_panel/glic/glic_side_panel_coordinator.cc
similarity index 65%
rename from chrome/browser/glic/widget/glic_side_panel_coordinator.cc
rename to chrome/browser/ui/views/side_panel/glic/glic_side_panel_coordinator.cc
index f295c43..57c2183 100644
--- a/chrome/browser/glic/widget/glic_side_panel_coordinator.cc
+++ b/chrome/browser/ui/views/side_panel/glic/glic_side_panel_coordinator.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "chrome/browser/glic/widget/glic_side_panel_coordinator.h"
+#include "chrome/browser/ui/views/side_panel/glic/glic_side_panel_coordinator.h"
 
 #include "base/functional/callback.h"
 #include "chrome/browser/glic/host/glic_ui.h"
@@ -19,22 +19,32 @@
 
 namespace glic {
 
-GlicSidePanelCoordinator::GlicSidePanelCoordinator() = default;
+GlicSidePanelCoordinator::GlicSidePanelCoordinator(Profile* profile)
+    : glic_service_(GlicKeyedServiceFactory::GetGlicKeyedService(profile)) {}
 
 void GlicSidePanelCoordinator::CreateAndRegisterEntry(
     SidePanelRegistry* global_registry) {
-  global_registry->Register(std::make_unique<SidePanelEntry>(
+  auto entry = std::make_unique<SidePanelEntry>(
       SidePanelEntry::Key(SidePanelEntry::Id::kGlic),
       base::BindRepeating(&GlicSidePanelCoordinator::CreateGlicWebView,
                           base::Unretained(this)),
-      /*default_content_width_callback=*/base::NullCallback()));
+      /*default_content_width_callback=*/base::NullCallback());
+  entry->AddObserver(this);
+  global_registry->Register(std::move(entry));
+}
+
+void GlicSidePanelCoordinator::OnEntryHidden(SidePanelEntry* entry) {
+  if (glic_service_) {
+    glic_service_->ClosePanel();
+  }
 }
 
 std::unique_ptr<views::View> GlicSidePanelCoordinator::CreateGlicWebView(
     SidePanelEntryScope& scope) {
-  auto* const glic_service = GlicKeyedServiceFactory::GetGlicKeyedService(
-      scope.GetBrowserWindowInterface().GetProfile());
-  return glic_service->window_controller().CreateGlicViewForSidePanel();
+  if (!glic_service_) {
+    return nullptr;
+  }
+  return glic_service_->window_controller().CreateGlicViewForSidePanel();
 }
 
 }  // namespace glic
diff --git a/chrome/browser/ui/views/side_panel/glic/glic_side_panel_coordinator.h b/chrome/browser/ui/views/side_panel/glic/glic_side_panel_coordinator.h
new file mode 100644
index 0000000..9aeab29b
--- /dev/null
+++ b/chrome/browser/ui/views/side_panel/glic/glic_side_panel_coordinator.h
@@ -0,0 +1,47 @@
+// Copyright 2025 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_UI_VIEWS_SIDE_PANEL_GLIC_GLIC_SIDE_PANEL_COORDINATOR_H_
+#define CHROME_BROWSER_UI_VIEWS_SIDE_PANEL_GLIC_GLIC_SIDE_PANEL_COORDINATOR_H_
+
+#include <memory>
+
+#include "chrome/browser/ui/views/side_panel/side_panel_entry_observer.h"
+
+class Profile;
+
+class SidePanelEntryScope;
+class SidePanelRegistry;
+
+namespace views {
+class View;
+}  // namespace views
+
+namespace glic {
+
+class GlicKeyedService;
+
+// GlicSidePanelCoordinator handles the creation and registration of the
+// glic SidePanelEntry.
+class GlicSidePanelCoordinator : public SidePanelEntryObserver {
+ public:
+  explicit GlicSidePanelCoordinator(Profile* profile);
+  ~GlicSidePanelCoordinator() override = default;
+
+  // Create and register the Glic side panel entry.
+  void CreateAndRegisterEntry(SidePanelRegistry* global_registry);
+
+  // `SidePanelEntryObserver`:
+  void OnEntryHidden(SidePanelEntry* entry) override;
+
+ private:
+  // Gets the Glic WebView from the Glic service.
+  std::unique_ptr<views::View> CreateGlicWebView(SidePanelEntryScope& scope);
+
+  raw_ptr<GlicKeyedService> glic_service_;
+};
+
+}  // namespace glic
+
+#endif  // CHROME_BROWSER_UI_VIEWS_SIDE_PANEL_GLIC_GLIC_SIDE_PANEL_COORDINATOR_H_
diff --git a/chrome/browser/ui/views/side_panel/side_panel_util.cc b/chrome/browser/ui/views/side_panel/side_panel_util.cc
index 3fa4eb0..efefc8c 100644
--- a/chrome/browser/ui/views/side_panel/side_panel_util.cc
+++ b/chrome/browser/ui/views/side_panel/side_panel_util.cc
@@ -33,7 +33,7 @@
 
 #if BUILDFLAG(ENABLE_GLIC)
 #include "chrome/browser/glic/public/glic_enabling.h"
-#include "chrome/browser/glic/widget/glic_side_panel_coordinator.h"
+#include "chrome/browser/ui/views/side_panel/glic/glic_side_panel_coordinator.h"
 #endif
 
 // static
diff --git a/chrome/browser/ui/webui/feedback/feedback_dialog.cc b/chrome/browser/ui/webui/feedback/feedback_dialog.cc
index a0dc384..4cbbc3f 100644
--- a/chrome/browser/ui/webui/feedback/feedback_dialog.cc
+++ b/chrome/browser/ui/webui/feedback/feedback_dialog.cc
@@ -57,8 +57,7 @@
     const extensions::api::feedback_private::FeedbackInfo& info) {
   if (current_instance_) {
     DCHECK(current_instance_->widget_);
-    const Profile* current_profile =
-        current_instance_->profile_keep_alive_.profile();
+    Profile* current_profile = current_instance_->profile_keep_alive_.profile();
     if (profile == current_profile) {
       // Focus the window hosting the dialog that has already been created.
       current_instance_->widget_->Show();
diff --git a/chrome/browser/ui/webui_browser/browser.mojom b/chrome/browser/ui/webui_browser/browser.mojom
index e4700f1..ad622b6 100644
--- a/chrome/browser/ui/webui_browser/browser.mojom
+++ b/chrome/browser/ui/webui_browser/browser.mojom
@@ -29,7 +29,10 @@
 
   // Shows the side panel UI. The side panel contents is hosted by a guest
   // contents.
-  ShowSidePanel(int32 guest_contents_id);
+  ShowSidePanel(int32 guest_contents_id, string title);
+
+  // Closes the side panel, destroys the webview.
+  CloseSidePanel();
 };
 
 // Browser-side handler for requests from WebUI page.
diff --git a/chrome/browser/ui/webui_browser/webui_browser_page_handler.cc b/chrome/browser/ui/webui_browser/webui_browser_page_handler.cc
index c747b2c..7fd607b 100644
--- a/chrome/browser/ui/webui_browser/webui_browser_page_handler.cc
+++ b/chrome/browser/ui/webui_browser/webui_browser_page_handler.cc
@@ -17,6 +17,7 @@
 #include "chrome/browser/ui/views/profiles/profile_menu_coordinator.h"
 #include "chrome/browser/ui/views/tab_search_bubble_host.h"
 #include "chrome/browser/ui/views/toolbar/app_menu.h"
+#include "chrome/browser/ui/webui_browser/webui_browser_side_panel_ui.h"
 #include "chrome/browser/ui/webui_browser/webui_browser_ui.h"
 #include "components/guest_contents/browser/guest_contents_handle.h"
 #include "content/public/browser/devtools_agent_host.h"
@@ -193,8 +194,7 @@
 }
 
 void WebUIBrowserPageHandler::OnSidePanelClosed() {
-  // TODO(webium): Find side panel UI and call OnSidePanelClosed().
-  NOTIMPLEMENTED();
+  GetBrowserWindow()->GetWebUIBrowserSidePanelUI()->OnSidePanelClosed();
 }
 
 void WebUIBrowserPageHandler::Minimize() {
diff --git a/chrome/browser/ui/webui_browser/webui_browser_side_panel_ui.cc b/chrome/browser/ui/webui_browser/webui_browser_side_panel_ui.cc
index 24450ef..85c7cd4 100644
--- a/chrome/browser/ui/webui_browser/webui_browser_side_panel_ui.cc
+++ b/chrome/browser/ui/webui_browser/webui_browser_side_panel_ui.cc
@@ -36,7 +36,19 @@
 
 WebUIBrowserSidePanelUI::~WebUIBrowserSidePanelUI() = default;
 
-void WebUIBrowserSidePanelUI::Close() {}
+void WebUIBrowserSidePanelUI::Close() {
+  if (!current_key().has_value()) {
+    return;
+  }
+
+  if (SidePanelEntry* entry = GetEntryForUniqueKey(*current_key())) {
+    entry->OnEntryWillHide(SidePanelEntryHideReason::kSidePanelClosed);
+  }
+  // Asynchronously close the side panel in webshell.
+  // WebUI then notifies the browser when the side panel is actually closed
+  // via OnSidePanelClosed().
+  GetWebUIBrowserWindow()->CloseSidePanel();
+}
 
 void WebUIBrowserSidePanelUI::Toggle(SidePanelEntryKey key,
                                      SidePanelOpenTrigger open_trigger) {}
@@ -55,7 +67,9 @@
 void WebUIBrowserSidePanelUI::SetNoDelaysForTesting(
     bool no_delays_for_testing) {}
 
-void WebUIBrowserSidePanelUI::Close(bool suppress_animations) {}
+void WebUIBrowserSidePanelUI::Close(bool suppress_animations) {
+  Close();
+}
 
 content::WebContents* WebUIBrowserSidePanelUI::GetWebContentsForId(
     SidePanelEntryId entry_id) const {
@@ -133,6 +147,35 @@
     SidePanelRegistry* old_contextual_registry,
     SidePanelRegistry* new_contextual_registry) {}
 
+void WebUIBrowserSidePanelUI::OnSidePanelClosed() {
+  if (!current_key()) {
+    return;
+  }
+
+  const bool closing_global = !current_key()->tab_handle;
+  SidePanelEntry* previous_entry = GetEntryForUniqueKey(*current_key());
+  set_current_key(std::nullopt);
+  previous_entry->OnEntryHidden();
+
+  // Reset active entry values for all observed registries and clear cache for
+  // everything except remaining active entries (i.e. if another tab has an
+  // active contextual entry).
+  if (auto* contextual_registry = GetActiveContextualRegistry()) {
+    contextual_registry->ResetActiveEntry();
+    if (closing_global) {
+      // Reset last active entry in contextual registry as global entry should
+      // take precedence.
+      contextual_registry->ResetLastActiveEntry();
+    }
+  }
+
+  window_registry_->ResetActiveEntry();
+
+  current_side_panel_view_.reset();
+  // TODO(webium): Clear cached views for registry entries for global and
+  // contextual registries.
+}
+
 WebUIBrowserWindow* WebUIBrowserSidePanelUI::GetWebUIBrowserWindow() {
   return static_cast<WebUIBrowserWindow*>(browser()->window());
 }
diff --git a/chrome/browser/ui/webui_browser/webui_browser_side_panel_ui.h b/chrome/browser/ui/webui_browser/webui_browser_side_panel_ui.h
index 38bbcdc2..6af90ce1 100644
--- a/chrome/browser/ui/webui_browser/webui_browser_side_panel_ui.h
+++ b/chrome/browser/ui/webui_browser/webui_browser_side_panel_ui.h
@@ -33,6 +33,8 @@
 
   content::WebContents* GetWebContentsForId(SidePanelEntryId entry_id) const;
 
+  void OnSidePanelClosed();
+
  private:
   // SidePanelUIBase:
   void Close(bool suppress_animations) override;
diff --git a/chrome/browser/ui/webui_browser/webui_browser_ui.cc b/chrome/browser/ui/webui_browser/webui_browser_ui.cc
index f79ecfd..51697f6c 100644
--- a/chrome/browser/ui/webui_browser/webui_browser_ui.cc
+++ b/chrome/browser/ui/webui_browser/webui_browser_ui.cc
@@ -11,6 +11,7 @@
 #include "chrome/browser/ui/browser_window/public/browser_window_features.h"
 #include "chrome/browser/ui/interaction/browser_elements.h"
 #include "chrome/browser/ui/tabs/tab_strip_api/tab_strip_service_register.h"
+#include "chrome/browser/ui/views/side_panel/side_panel_entry_id.h"
 #include "chrome/browser/ui/webui/searchbox/realbox_handler.h"
 #include "chrome/browser/ui/webui/searchbox/searchbox_handler.h"
 #include "chrome/browser/ui/webui_browser/bookmark_bar_page_handler.h"
@@ -18,6 +19,7 @@
 #include "chrome/browser/ui/webui_browser/webui_browser_page_handler.h"
 #include "chrome/browser/ui/webui_browser/webui_browser_side_panel_ui.h"
 #include "chrome/common/webui_url_constants.h"
+#include "chrome/grit/generated_resources.h"
 #include "chrome/grit/tab_strip_api_resources_map.h"
 #include "chrome/grit/webui_browser_resources.h"
 #include "chrome/grit/webui_browser_resources_map.h"
@@ -26,10 +28,38 @@
 #include "content/public/browser/web_contents.h"
 #include "content/public/browser/web_ui.h"
 #include "content/public/browser/web_ui_data_source.h"
+#include "ui/base/l10n/l10n_util.h"
 #include "ui/views/interaction/element_tracker_views.h"
 #include "ui/webui/tracked_element/tracked_element_handler.h"
 #include "ui/webui/webui_util.h"
 
+namespace {
+
+std::string SidePanelEntryIdToTitle(SidePanelEntryId id) {
+  // TODO(webium): Ideally, the titles should be added to SIDE_PANEL_ENTRY_IDS
+  // macros in chrome/browser/ui/views/side_panel/side_panel_entry_id.h, and
+  // then this conversion function would be written in that same file,
+  // analogously to the other functions it contains. But it appears that some
+  // of the entry  ids there are stale and no longer have a matching generated
+  // IDS_*_TITLE string that we can include.
+  //
+  // Since we are currently only explicitly supporting three side panel types,
+  // we do the id to title conversion here manually to minimize modification to
+  // existing code.
+  switch (id) {
+    case SidePanelEntryId::kCustomizeChrome:
+      return l10n_util::GetStringUTF8(IDS_SIDE_PANEL_CUSTOMIZE_CHROME_TITLE);
+    case SidePanelEntryId::kReadingList:
+      return l10n_util::GetStringUTF8(IDS_READ_LATER_TITLE);
+    case SidePanelEntryId::kBookmarks:
+      return l10n_util::GetStringUTF8(IDS_BOOKMARK_MANAGER_TITLE);
+    default:
+      NOTREACHED();
+  }
+}
+
+}  // namespace
+
 WebUIBrowserUIConfig::WebUIBrowserUIConfig()
     : DefaultWebUIConfig(content::kChromeUIScheme,
                          chrome::kChromeUIWebuiBrowserHost) {}
@@ -165,12 +195,12 @@
       guest_contents::GuestContentsHandle::CreateForWebContents(web_contents);
 
   // Notify JS.
-  page_->ShowSidePanel(guest_handle->id());
+  page_->ShowSidePanel(guest_handle->id(),
+                       SidePanelEntryIdToTitle(side_panel_entry_key.id()));
 }
 
 void WebUIBrowserUI::CloseSidePanel() {
-  // TODO(webium): Create side panel and call page_->CloseSidePanel()
-  NOTIMPLEMENTED();
+  page_->CloseSidePanel();
 }
 
 WEB_UI_CONTROLLER_TYPE_IMPL(WebUIBrowserUI)
diff --git a/chrome/browser/ui/webui_browser/webui_browser_window.cc b/chrome/browser/ui/webui_browser/webui_browser_window.cc
index 05b8412..f60a12a 100644
--- a/chrome/browser/ui/webui_browser/webui_browser_window.cc
+++ b/chrome/browser/ui/webui_browser/webui_browser_window.cc
@@ -1079,6 +1079,10 @@
   GetWebUIBrowserUI()->ShowSidePanel(side_panel_entry_key);
 }
 
+void WebUIBrowserWindow::CloseSidePanel() {
+  GetWebUIBrowserUI()->CloseSidePanel();
+}
+
 WebUIBrowserSidePanelUI* WebUIBrowserWindow::GetWebUIBrowserSidePanelUI() {
   return static_cast<WebUIBrowserSidePanelUI*>(
       browser_->browser_window_features()->side_panel_ui());
diff --git a/chrome/browser/ui/webui_browser/webui_browser_window.h b/chrome/browser/ui/webui_browser/webui_browser_window.h
index e0f5dc45..78d128a 100644
--- a/chrome/browser/ui/webui_browser/webui_browser_window.h
+++ b/chrome/browser/ui/webui_browser/webui_browser_window.h
@@ -261,6 +261,7 @@
                                   ui::Accelerator* accelerator) const override;
 
   void ShowSidePanel(SidePanelEntryKey side_panel_entry_key);
+  void CloseSidePanel();
 
   WebUIBrowserSidePanelUI* GetWebUIBrowserSidePanelUI();
 
diff --git a/chrome/build/android-arm64.pgo.txt b/chrome/build/android-arm64.pgo.txt
index c7b19af..6c63f3e0 100644
--- a/chrome/build/android-arm64.pgo.txt
+++ b/chrome/build/android-arm64.pgo.txt
@@ -1 +1 @@
-chrome-android64-main-1756822069-de4bd7d7669f6757eebc12283731a9eadfb5bee8-28a1ee4d22e556527709ae0c833a2580a4f89f7e.profdata
+chrome-android64-main-1756825951-f7fd9b0a650ff4846caaef4c59f4dec626487321-f77acfdc1b3b8ea06fc49db464df065c16664370.profdata
diff --git a/chrome/build/win32.pgo.txt b/chrome/build/win32.pgo.txt
index 0127685..c6dd893b4 100644
--- a/chrome/build/win32.pgo.txt
+++ b/chrome/build/win32.pgo.txt
@@ -1 +1 @@
-chrome-win32-main-1756803502-220b7b3c12588346e324a15b398e7a00e4fa5edb-dde15930c4bf0c6c6424b21021203dbd21508dfe.profdata
+chrome-win32-main-1756814343-722eb9404d7007635e7225e5015e3d1054841f35-af4c4b4a345d0ad4a3f8aa22f873f21587a1a6e3.profdata
diff --git a/chrome/build/win64.pgo.txt b/chrome/build/win64.pgo.txt
index 38203c0..ab0713ac 100644
--- a/chrome/build/win64.pgo.txt
+++ b/chrome/build/win64.pgo.txt
@@ -1 +1 @@
-chrome-win64-main-1756803502-2c048ae3b0371bc55013ee081f39516aa2cf9532-dde15930c4bf0c6c6424b21021203dbd21508dfe.profdata
+chrome-win64-main-1756814343-db57bdd082f29af593eb784b36bbd3d8f9c676e6-af4c4b4a345d0ad4a3f8aa22f873f21587a1a6e3.profdata
diff --git a/chrome/common/extensions/api/experimental_actor.idl b/chrome/common/extensions/api/experimental_actor.idl
index 7f836ea..df72033 100644
--- a/chrome/common/extensions/api/experimental_actor.idl
+++ b/chrome/common/extensions/api/experimental_actor.idl
@@ -10,22 +10,6 @@
   callback TaskIdCallback = void(long taskId);
 
   interface Functions {
-    // Creates and starts a new task. By default, opens a new tab to
-    // about:blank that is ready for actions. The startTaskProto can
-    // optionally specify a tab_id to use an existing tab instead of creating
-    // a new one.
-    // startTaskProto: encoded optimization_guide.proto.BrowserStartTask
-    // startTaskcallback:
-    //   encoded optimization_guide.proto.BrowserStartTaskResult
-    [deprecated="Use createTask instead"] static void startTask(
-        ArrayBuffer startTaskProto,
-        DataCallback startTaskcallback);
-    // Executes one or more actions according to request.
-    // request: encoded optimization_guide.proto.BrowserAction
-    // response: encoded optimization_guide.proto.BrowserActionResult
-    [deprecated="Use performActions instead"] static void executeAction(
-        ArrayBuffer browserActionProto,
-        DataCallback executeActioncallback);
     // Stops a task.
     // taskId: id of the task to stop.
     // stopTaskcallback: a closure that is called when the task is stopped.
diff --git a/chrome/test/data/webui/settings/privacy_page_index_test.ts b/chrome/test/data/webui/settings/privacy_page_index_test.ts
index 5bf28e8..df61ed4 100644
--- a/chrome/test/data/webui/settings/privacy_page_index_test.ts
+++ b/chrome/test/data/webui/settings/privacy_page_index_test.ts
@@ -18,6 +18,10 @@
 
     loadTimeData.overrideValues(Object.assign(
         {
+          autoPictureInPictureEnabled: false,
+          capturedSurfaceControlEnabled: false,
+          enableExperimentalWebPlatformFeatures: false,
+          enableHandTrackingContentSetting: false,
           enableIncognitoTrackingProtections: false,
           enableKeyboardLockPrompt: false,
           enableLocalNetworkAccessSetting: false,
@@ -89,16 +93,31 @@
         parentViewId: 'old',
       },
       {
+        route: routes.SITE_SETTINGS_AR,
+        viewId: 'siteSettingsAr',
+        parentViewId: 'old',
+      },
+      {
         route: routes.SITE_SETTINGS_AUTOMATIC_FULLSCREEN,
         viewId: 'siteSettingsAutomaticFullscreen',
         parentViewId: 'old',
       },
       {
+        route: routes.SITE_SETTINGS_IDLE_DETECTION,
+        viewId: 'siteSettingsIdleDetection',
+        parentViewId: 'old',
+      },
+      {
         route: routes.SITE_SETTINGS_HANDLERS,
         viewId: 'siteSettingsHandlers',
         parentViewId: 'old',
       },
       {
+        route: routes.SITE_SETTINGS_LOCAL_FONTS,
+        viewId: 'siteSettingsLocalFonts',
+        parentViewId: 'old',
+      },
+      {
         route: routes.SITE_SETTINGS_LOCATION,
         viewId: 'siteSettingsLocation',
         parentViewId: 'old',
@@ -119,6 +138,21 @@
         parentViewId: 'old',
       },
       {
+        route: routes.SITE_SETTINGS_STORAGE_ACCESS,
+        viewId: 'siteSettingsStorageAccess',
+        parentViewId: 'old',
+      },
+      {
+        route: routes.SITE_SETTINGS_VR,
+        viewId: 'siteSettingsVr',
+        parentViewId: 'old',
+      },
+      {
+        route: routes.SITE_SETTINGS_WINDOW_MANAGEMENT,
+        viewId: 'siteSettingsWindowManagement',
+        parentViewId: 'old',
+      },
+      {
         route: routes.SITE_SETTINGS_ZOOM_LEVELS,
         viewId: 'siteSettingsZoomLevels',
         parentViewId: 'old',
@@ -200,6 +234,58 @@
     }
   });
 
+  test('RoutingAutoPictureInPicture', async function() {
+    assertFalse(loadTimeData.getBoolean('autoPictureInPictureEnabled'));
+    await createPrivacyPageIndex({autoPictureInPictureEnabled: true});
+
+    const viewId = 'siteSettingsAutoPictureInPicture';
+    await testActiveViewsForRoute(
+        routes.SITE_SETTINGS_AUTO_PICTURE_IN_PICTURE, [viewId]);
+
+    // Test that data-parent-view is correctly populated.
+    assertTrue(!!index.$.viewManager.querySelector(
+        `#${viewId}[slot=view][data-parent-view-id=old]`));
+  });
+
+  test('RoutingBluetoothScanning', async function() {
+    assertFalse(
+        loadTimeData.getBoolean('enableExperimentalWebPlatformFeatures'));
+    await createPrivacyPageIndex({enableExperimentalWebPlatformFeatures: true});
+
+    const viewId = 'siteSettingsBluetoothScanning';
+    await testActiveViewsForRoute(
+        routes.SITE_SETTINGS_BLUETOOTH_SCANNING, [viewId]);
+
+    // Test that data-parent-view is correctly populated.
+    assertTrue(!!index.$.viewManager.querySelector(
+        `#${viewId}[slot=view][data-parent-view-id=old]`));
+  });
+
+  test('RoutingCapturedSurfaceControl', async function() {
+    assertFalse(loadTimeData.getBoolean('capturedSurfaceControlEnabled'));
+    await createPrivacyPageIndex({capturedSurfaceControlEnabled: true});
+
+    const viewId = 'siteSettingsCapturedSurfaceControl';
+    await testActiveViewsForRoute(
+        routes.SITE_SETTINGS_CAPTURED_SURFACE_CONTROL, [viewId]);
+
+    // Test that data-parent-view is correctly populated.
+    assertTrue(!!index.$.viewManager.querySelector(
+        `#${viewId}[slot=view][data-parent-view-id=old]`));
+  });
+
+  test('RoutingHandTracking', async function() {
+    assertFalse(loadTimeData.getBoolean('enableHandTrackingContentSetting'));
+    await createPrivacyPageIndex({enableHandTrackingContentSetting: true});
+
+    const viewId = 'siteSettingsHandTracking';
+    await testActiveViewsForRoute(routes.SITE_SETTINGS_HAND_TRACKING, [viewId]);
+
+    // Test that data-parent-view is correctly populated.
+    assertTrue(!!index.$.viewManager.querySelector(
+        `#${viewId}[slot=view][data-parent-view-id=old]`));
+  });
+
   test('RoutingKeyboardLock', async function() {
     assertFalse(loadTimeData.getBoolean('enableKeyboardLockPrompt'));
     await createPrivacyPageIndex({enableKeyboardLockPrompt: true});
diff --git a/chrome/test/data/webui/settings/privacy_page_test.ts b/chrome/test/data/webui/settings/privacy_page_test.ts
index 11d5677..441d93b 100644
--- a/chrome/test/data/webui/settings/privacy_page_test.ts
+++ b/chrome/test/data/webui/settings/privacy_page_test.ts
@@ -7,7 +7,7 @@
 import {loadTimeData} from 'chrome://resources/js/load_time_data.js';
 import {flush} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 import type {CrToastElement} from 'chrome://settings/lazy_load.js';
-import {ClearBrowsingDataBrowserProxyImpl, ContentSetting, CookieControlsMode, SiteSettingsPrefsBrowserProxyImpl} from 'chrome://settings/lazy_load.js';
+import {ClearBrowsingDataBrowserProxyImpl, CookieControlsMode, SiteSettingsPrefsBrowserProxyImpl} from 'chrome://settings/lazy_load.js';
 import type {CrLinkRowElement, Route, SettingsPrefsElement, SettingsPrivacyPageElement, SyncStatus} from 'chrome://settings/settings.js';
 import {CrSettingsPrefs, HatsBrowserProxyImpl, MetricsBrowserProxyImpl, PrivacyGuideInteractions, PrivacyPageBrowserProxyImpl, resetRouterForTesting, Router, routes, StatusAction, TrustSafetyInteraction} from 'chrome://settings/settings.js';
 import {assertEquals, assertFalse, assertTrue, assertThrows} from 'chrome://webui-test/chai_assert.js';
@@ -195,24 +195,6 @@
         settingsSubpage.learnMoreUrl,
         'https://support.google.com/chrome?p=webusb&hl=en-US');
   });
-
-  test('StorageAccessPage', async function() {
-    Router.getInstance().navigateTo(routes.SITE_SETTINGS_STORAGE_ACCESS);
-    await flushTasks();
-
-    const categorySettingExceptions =
-        page.shadowRoot!.querySelector('settings-storage-access-page')!
-            .shadowRoot!.querySelectorAll('storage-access-site-list');
-
-    assertEquals(2, categorySettingExceptions.length);
-    assertTrue(isVisible(categorySettingExceptions[0]!));
-    assertEquals(
-        ContentSetting.BLOCK, categorySettingExceptions[0]!.categorySubtype);
-
-    assertTrue(isVisible(categorySettingExceptions[1]!));
-    assertEquals(
-        ContentSetting.ALLOW, categorySettingExceptions[1]!.categorySubtype);
-  });
 });
 
 // Isolated ContentSettingsVisibility test suite due to significantly higher
diff --git a/chrome/test/data/webui/side_panel/read_anything/node_store_test.ts b/chrome/test/data/webui/side_panel/read_anything/node_store_test.ts
index d353af1..64fe0cc 100644
--- a/chrome/test/data/webui/side_panel/read_anything/node_store_test.ts
+++ b/chrome/test/data/webui/side_panel/read_anything/node_store_test.ts
@@ -4,7 +4,7 @@
 
 import 'chrome-untrusted://read-anything-side-panel.top-chrome/read_anything.js';
 
-import {AxReadAloudNode, BrowserProxy, ESTIMATED_WORDS_PER_MS, getWordCount, MIN_MS_TO_READ, NodeStore} from 'chrome-untrusted://read-anything-side-panel.top-chrome/read_anything.js';
+import {BrowserProxy, ESTIMATED_WORDS_PER_MS, getWordCount, MIN_MS_TO_READ, NodeStore, ReadAloudNode} from 'chrome-untrusted://read-anything-side-panel.top-chrome/read_anything.js';
 import {assertArrayEquals, assertEquals, assertFalse, assertGT, assertNotEquals, assertTrue} from 'chrome-untrusted://webui-test/chai_assert.js';
 import {MockTimer} from 'chrome-untrusted://webui-test/mock_timer.js';
 
@@ -18,7 +18,19 @@
 
   function areNodesAllHidden(axNodeIds: number[]): boolean {
     return nodeStore.areNodesAllHidden(
-        axNodeIds.map(id => new AxReadAloudNode(id)));
+        axNodeIds.map(id => getReadAloudNode(id)));
+  }
+
+  function getReadAloudNode(axNodeId: number): ReadAloudNode {
+    const domNode = nodeStore.getDomNode(axNodeId);
+    return ReadAloudNode.create(domNode!, nodeStore)!;
+  }
+
+  function setDomNodes(axNodeIds: number[]) {
+    axNodeIds.forEach(id => {
+      const element = document.createElement('p');
+      nodeStore.setDomNode(element, id);
+    });
   }
 
   setup(() => {
@@ -42,6 +54,19 @@
     assertEquals(node, nodeStore.getDomNode(id));
   });
 
+  test('getDomNode', () => {
+    const node = document.createElement('p');
+    const id = 308;
+
+    // Before the node has been set, getDomNode returns undefined.
+    nodeStore.getDomNode(id);
+    assertEquals(undefined, nodeStore.getDomNode(id));
+
+    nodeStore.setDomNode(node, id);
+    assertEquals(id, nodeStore.getAxId(node));
+    assertEquals(node, nodeStore.getDomNode(id));
+  });
+
   test('removeDomNode after set', () => {
     const node1 = document.createElement('p');
     const id1 = 308;
@@ -101,6 +126,10 @@
   test('hideImageNode', () => {
     const id = 216;
     nodeStore.hideImageNode(id);
+    assertFalse(areNodesAllHidden([id]));
+
+    setDomNodes([id]);
+    nodeStore.hideImageNode(id);
     assertTrue(areNodesAllHidden([id]));
   });
 
@@ -108,6 +137,7 @@
     const id1 = 216;
     const id2 = 218;
     const id3 = 219;
+    setDomNodes([id1, id2, id3]);
     nodeStore.hideImageNode(id1);
     nodeStore.hideImageNode(id2);
 
@@ -120,19 +150,27 @@
     const id1 = 216;
     const id2 = 218;
     const id3 = 219;
-    nodeStore.setDomNode(document.createElement('p'), id1);
-    nodeStore.setDomNode(document.createElement('p'), id2);
+
+    // hasAnyNode returns false before there are associated DOM nodes.
+    assertFalse(nodeStore.hasAnyNode([
+      getReadAloudNode(id1),
+      getReadAloudNode(id2),
+      getReadAloudNode(id3),
+    ]));
+
+    setDomNodes([id1, id2]);
 
     assertTrue(nodeStore.hasAnyNode([
-      new AxReadAloudNode(id1),
-      new AxReadAloudNode(id2),
-      new AxReadAloudNode(id3),
+      getReadAloudNode(id1),
+      getReadAloudNode(id2),
+      getReadAloudNode(id3),
     ]));
-    assertTrue(nodeStore.hasAnyNode(
-        [new AxReadAloudNode(id1), new AxReadAloudNode(id2)]));
-    assertTrue(nodeStore.hasAnyNode(
-        [new AxReadAloudNode(id1), new AxReadAloudNode(id3)]));
-    assertFalse(nodeStore.hasAnyNode([new AxReadAloudNode(id3)]));
+
+    assertTrue(
+        nodeStore.hasAnyNode([getReadAloudNode(id1), getReadAloudNode(id2)]));
+    assertTrue(
+        nodeStore.hasAnyNode([getReadAloudNode(id1), getReadAloudNode(id3)]));
+    assertFalse(nodeStore.hasAnyNode([getReadAloudNode(id3)]));
   });
 
   test('addImageToFetch', () => {
diff --git a/chrome/test/data/webui/side_panel/read_anything/read_anything_browsertest.cc b/chrome/test/data/webui/side_panel/read_anything/read_anything_browsertest.cc
index 7390c373..75dbcf44 100644
--- a/chrome/test/data/webui/side_panel/read_anything/read_anything_browsertest.cc
+++ b/chrome/test/data/webui/side_panel/read_anything/read_anything_browsertest.cc
@@ -340,3 +340,25 @@
   RunSidePanelTest("side_panel/read_anything/phrase_highlighting_test.js",
                    "mocha.run()");
 }
+
+class ReadAnythingReadAloudTsSegmentationMochaTest
+    : public ReadAnythingMochaBrowserTest {
+ protected:
+  ReadAnythingReadAloudTsSegmentationMochaTest() {
+    scoped_feature_list_.InitWithFeatures(
+        {features::kReadAnythingReadAloud,
+         features::kReadAnythingReadAloudTSTextSegmentation},
+        {});
+  }
+
+ private:
+  base::test::ScopedFeatureList scoped_feature_list_;
+};
+
+// NodeStore tests should pass regardless of whether or not the TsSegmentation
+// flag is enabled without any special handling.
+IN_PROC_BROWSER_TEST_F(ReadAnythingReadAloudTsSegmentationMochaTest,
+                       NodeStore) {
+  RunSidePanelTest("side_panel/read_anything/node_store_test.js",
+                   "mocha.run()");
+}
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 ef18542a..d94df6d 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
@@ -24,6 +24,13 @@
   let voiceLanguageController: VoiceLanguageController;
   let readingMode: FakeReadingMode;
 
+  function setDomNodes(axNodeIds: number[]) {
+    axNodeIds.forEach(id => {
+      const element = document.createElement('p');
+      nodeStore.setDomNode(element, id);
+    });
+  }
+
   setup(() => {
     // Clearing the DOM should always be done first.
     document.body.innerHTML = window.trustedTypes!.emptyHTML;
@@ -179,9 +186,18 @@
       assertFalse(speechController.isSpeechTreeInitialized());
     });
 
+    test('with no dom node does nothing', () => {
+      const id1 = 10;
+      speechController.initializeSpeechTree(id1);
+
+      assertFalse(!!initAxPositionWithNode);
+      assertFalse(speechController.isSpeechTreeInitialized());
+    });
+
     test('when already initialized does nothing', () => {
       const id1 = 10;
       const id2 = 12;
+      setDomNodes([10, 12]);
       speechController.initializeSpeechTree(id1);
       speechController.initializeSpeechTree(id2);
       assertEquals(id1, initAxPositionWithNode);
@@ -189,6 +205,7 @@
 
     test('initializes speech tree after content is set', () => {
       const id = 14;
+      setDomNodes([14]);
       speechController.initializeSpeechTree(id);
       assertEquals(id, initAxPositionWithNode);
 
diff --git a/components/autofill/core/browser/country_type.h b/components/autofill/core/browser/country_type.h
index d2af9cfe..64754a6c 100644
--- a/components/autofill/core/browser/country_type.h
+++ b/components/autofill/core/browser/country_type.h
@@ -9,12 +9,15 @@
 
 #include "base/types/strong_alias.h"
 
+namespace autofill {
+
 // Country code in the format of uppercase ISO 3166-1 alpha-2. Example: US, BR,
 // IN. Empty if unknown.
 // StrongAlias to distinguish it from language codes and coutries specified in
 // address profiles or inferred from address forms. This country code is
 // estimated by the variations::VariationsService.
-using GeoIpCountryCode = base::StrongAlias<class GeoIpCountryTag, std::string>;
+using GeoIpCountryCode =
+    base::StrongAlias<class GeoIpCountryCodeTag, std::string>;
 
 // Country code in the format of uppercase ISO 3166-1 alpha-2. Example: US, BR,
 // IN. Empty if unknown.
@@ -22,4 +25,6 @@
 using AddressCountryCode =
     base::StrongAlias<class AutofillAddressCountryTag, std::string>;
 
+}  // namespace autofill
+
 #endif  // COMPONENTS_AUTOFILL_CORE_BROWSER_COUNTRY_TYPE_H_
diff --git a/components/autofill/core/browser/filling/form_filler.h b/components/autofill/core/browser/filling/form_filler.h
index bfc0c598..b94fb12 100644
--- a/components/autofill/core/browser/filling/form_filler.h
+++ b/components/autofill/core/browser/filling/form_filler.h
@@ -179,6 +179,10 @@
       base::optional_ref<const FormFieldData> field = std::nullopt,
       base::optional_ref<const std::u16string> old_value = std::nullopt);
 
+  base::WeakPtr<FormFiller> GetWeakPtr() {
+    return weak_ptr_factory_.GetWeakPtr();
+  }
+
  private:
   friend class FormFillerTestApi;
   friend class TestFormFiller;
diff --git a/components/autofill/core/browser/foundations/browser_autofill_manager.cc b/components/autofill/core/browser/foundations/browser_autofill_manager.cc
index c30e1dd..9f1a855f 100644
--- a/components/autofill/core/browser/foundations/browser_autofill_manager.cc
+++ b/components/autofill/core/browser/foundations/browser_autofill_manager.cc
@@ -960,7 +960,7 @@
 
   if (auto* save_and_fill_manager =
           client().GetPaymentsAutofillClient()->GetSaveAndFillManager()) {
-    save_and_fill_manager->OnCreditCardFormSubmitted();
+    save_and_fill_manager->MaybeAddStrikeForSaveAndFill();
   }
 }
 
@@ -2986,70 +2986,40 @@
   return metrics_->credit_card_form_event_logger;
 }
 
+// TODO(crbug.com/409962888): Remove once the new suggestion generation logic is
+// launched.
 std::vector<Suggestion> BrowserAutofillManager::GetProfileSuggestions(
     const FormData& form,
     const FormStructure& form_structure,
     const FormFieldData& trigger_field,
     const AutofillField& trigger_autofill_field,
     std::optional<std::string> plus_address_email_override) {
-#if !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS)
-  bool should_suppress =
-      client()
-          .GetPersonalDataManager()
-          .address_data_manager()
-          .AreAddressSuggestionsBlocked(
-              CalculateFormSignature(form),
-              CalculateFieldSignatureForField(trigger_field), form.url());
-  base::UmaHistogramBoolean("Autofill.Suggestion.StrikeSuppression.Address",
-                            should_suppress);
-  if (should_suppress &&
-      !base::FeatureList::IsEnabled(
-          features::test::kAutofillDisableSuggestionStrikeDatabase)) {
-    LOG_AF(log_manager()) << LoggingScope::kFilling
-                          << LogMessage::kSuggestionSuppressed
-                          << " Reason: strike limit reached.";
-    // If the user already reached the strike limit on this particular field,
-    // address suggestions are suppressed.
-    return {};
-  }
-#endif
+  std::vector<Suggestion> suggestions;
+  AddressSuggestionGenerator address_suggestion_generator(
+      client(), plus_address_email_override, form_filler_->GetWeakPtr(),
+      log_manager());
 
-  // If the user triggers suggestions on an autofilled field, field-by-field
-  // filling suggestions should be shown so that the user could easily correct
-  // values to something present in different stored addresses.
-  SuggestionType current_suggestion_type =
-      trigger_field.is_autofilled()
-          ? SuggestionType::kAddressFieldByFieldFilling
-          : SuggestionType::kAddressEntry;
+  auto on_suggestions_generated =
+      [&suggestions](
+          SuggestionGenerator::ReturnedSuggestions returned_suggestions) {
+        suggestions = std::move(returned_suggestions.second);
+      };
 
-  FieldTypeSet field_types = [&]() -> FieldTypeSet {
-    if (current_suggestion_type ==
-        SuggestionType::kAddressFieldByFieldFilling) {
-      return {trigger_autofill_field.Type().GetAddressType()};
-    }
-    // If the FormData and FormStructure do not have the same size, we assume
-    // as a fallback that all fields are fillable.
-    base::flat_map<FieldGlobalId, DenseSet<FieldFillingSkipReason>>
-        skip_reasons;
-    if (form.fields().size() == form_structure.field_count()) {
-      skip_reasons = form_filler_->GetFieldFillingSkipReasons(
-          form.fields(), form_structure, trigger_autofill_field,
-          FormFiller::RefillOptions::NotRefill(), FillingProduct::kAddress);
-    }
-    FieldTypeSet field_types;
-    for (size_t i = 0; i < form_structure.field_count(); ++i) {
-      if (auto it = skip_reasons.find(form_structure.field(i)->global_id());
-          it == skip_reasons.end() || it->second.empty()) {
-        field_types.insert(form_structure.field(i)->Type().GetAddressType());
-      }
-    }
-    return field_types;
-  }();
+  auto on_suggestion_data_returned =
+      [&on_suggestions_generated, &form, &trigger_field, &form_structure,
+       &trigger_autofill_field, &address_suggestion_generator](
+          std::pair<FillingProduct,
+                    std::vector<SuggestionGenerator::SuggestionData>>
+              suggestion_data) {
+        address_suggestion_generator.GenerateSuggestions(
+            form, trigger_field, &form_structure, &trigger_autofill_field,
+            {std::move(suggestion_data)}, on_suggestions_generated);
+      };
 
-  return GetSuggestionsForProfiles(
-      client(), field_types, trigger_field,
-      trigger_autofill_field.Type().GetAddressType(), current_suggestion_type,
-      std::move(plus_address_email_override));
+  address_suggestion_generator.FetchSuggestionData(
+      form, trigger_field, &form_structure, &trigger_autofill_field, client(),
+      on_suggestion_data_returned);
+  return suggestions;
 }
 
 std::vector<Suggestion> BrowserAutofillManager::GetCreditCardSuggestions(
diff --git a/components/autofill/core/browser/foundations/browser_autofill_manager_unittest.cc b/components/autofill/core/browser/foundations/browser_autofill_manager_unittest.cc
index 2a091e8..ea1c151 100644
--- a/components/autofill/core/browser/foundations/browser_autofill_manager_unittest.cc
+++ b/components/autofill/core/browser/foundations/browser_autofill_manager_unittest.cc
@@ -3998,15 +3998,31 @@
   FormSubmitted(response_data);
 }
 
-TEST_F(BrowserAutofillManagerTest, FormSubmission_NotifiesSaveAndFillManager) {
+TEST_F(BrowserAutofillManagerTest, FormEvents_NotifiesSaveAndFillManager) {
   EXPECT_CALL(*client().GetPaymentsAutofillClient()->GetSaveAndFillManager(),
-              OnCreditCardFormSubmitted());
+              LogCreditCardFormFilled());
+  EXPECT_CALL(*client().GetPaymentsAutofillClient()->GetSaveAndFillManager(),
+              LogCreditCardFormSubmitted());
+  EXPECT_CALL(*client().GetPaymentsAutofillClient()->GetSaveAndFillManager(),
+              MaybeAddStrikeForSaveAndFill());
 
+  client().SetAutofillPaymentMethodsEnabled(true);
   FormData form =
       CreateTestCreditCardFormData(/*is_https=*/true, /*use_month_type=*/false);
-
   FormsSeen({form});
-  FormSubmitted(form);
+
+  // Create a card and add it to the PDM.
+  personal_data().test_payments_data_manager().ClearCreditCards();
+  CreditCard card = test::GetCreditCard();
+  personal_data().payments_data_manager().AddCreditCard(card);
+  const CreditCard* pdm_card =
+      personal_data().payments_data_manager().GetCreditCardByGUID(card.guid());
+  ASSERT_TRUE(pdm_card);
+
+  FormData filled_form = FillAutofillFormDataAndGetResults(
+      form, form.fields()[0], pdm_card->guid(),
+      AutofillTriggerSource::kCreditCardSaveAndFill);
+  FormSubmitted(filled_form);
 }
 
 TEST_F(BrowserAutofillManagerTest,
diff --git a/components/autofill/core/browser/integrators/autofill_ai/autofill_ai_manager.cc b/components/autofill/core/browser/integrators/autofill_ai/autofill_ai_manager.cc
index 9574fc7c..f6831d8 100644
--- a/components/autofill/core/browser/integrators/autofill_ai/autofill_ai_manager.cc
+++ b/components/autofill/core/browser/integrators/autofill_ai/autofill_ai_manager.cc
@@ -198,10 +198,11 @@
                                         ukm::SourceId ukm_source_id) {
   logger_.RecordFormMetrics(form, ukm_source_id, /*submission_state=*/true,
                             GetAutofillAiOptInStatus(*client_));
-  return MaybeImportForm(form);
+  return MaybeImportForm(form, ukm_source_id);
 }
 
-bool AutofillAiManager::MaybeImportForm(const FormStructure& form) {
+bool AutofillAiManager::MaybeImportForm(const FormStructure& form,
+                                        ukm::SourceId ukm_source_id) {
   if (!MayPerformAutofillAiAction(*client_, AutofillAiAction::kImport)) {
     return false;
   }
@@ -230,7 +231,7 @@
                         form.main_frame_origin(),
                         net::registry_controlled_domains::
                             EXCLUDE_PRIVATE_REGISTRIES),
-                    old_entity->guid())
+                    ukm_source_id, old_entity->guid())
               : BindOnce(
                     &AutofillAiManager::HandleSavePromptResult, GetWeakPtr(),
                     form.source_url(),
@@ -239,7 +240,7 @@
                         form.main_frame_origin(),
                         net::registry_controlled_domains::
                             EXCLUDE_PRIVATE_REGISTRIES),
-                    new_entity);
+                    ukm_source_id, new_entity);
       client_->ShowEntitySaveOrUpdateBubble(std::move(new_entity),
                                             std::move(old_entity),
                                             std::move(prompt_result_callback));
@@ -252,11 +253,12 @@
     const GURL& form_url,
     uint64_t form_session_id,
     const std::string& domain,
+    ukm::SourceId ukm_source_id,
     const EntityInstance& entity,
     AutofillClient::EntitySaveOrUpdatePromptResult result) {
   logger_.OnSaveOrUpdatePromptResult(
       AutofillClient::AutofillAiPromptTypes::kSave, entity.type(),
-      entity.record_type(), form_session_id, domain, result);
+      entity.record_type(), form_session_id, domain, result, ukm_source_id);
   if (!result.entity) {
     if (result.did_user_decline) {
       AddStrikeForSaveAttempt(form_url, entity);
@@ -276,6 +278,7 @@
 void AutofillAiManager::HandleUpdatePromptResult(
     uint64_t form_session_id,
     const std::string& domain,
+    ukm::SourceId ukm_source_id,
     const EntityInstance::EntityId& entity_uuid,
     AutofillClient::EntitySaveOrUpdatePromptResult result) {
   if (const EntityDataManager* entity_manager =
@@ -284,7 +287,8 @@
             entity_manager->GetEntityInstance(entity_uuid)) {
       logger_.OnSaveOrUpdatePromptResult(
           AutofillClient::AutofillAiPromptTypes::kUpdate, entity->type(),
-          entity->record_type(), form_session_id, domain, result);
+          entity->record_type(), form_session_id, domain, result,
+          ukm_source_id);
     }
   }
 
diff --git a/components/autofill/core/browser/integrators/autofill_ai/autofill_ai_manager.h b/components/autofill/core/browser/integrators/autofill_ai/autofill_ai_manager.h
index 2cb1f2e9..3bd1f160 100644
--- a/components/autofill/core/browser/integrators/autofill_ai/autofill_ai_manager.h
+++ b/components/autofill/core/browser/integrators/autofill_ai/autofill_ai_manager.h
@@ -105,7 +105,7 @@
 
   // Attempts to display an import bubble for `form` if Autofill AI is
   // interested in the form. Returns whether an import bubble will be shown.
-  bool MaybeImportForm(const FormStructure& form);
+  bool MaybeImportForm(const FormStructure& form, ukm::SourceId ukm_source_id);
 
   // Updates the `EntityDataManager` and the save strike database depending on
   // the prompt `result`.
@@ -113,6 +113,7 @@
       const GURL& form_url,
       uint64_t form_session_id,
       const std::string& domain,
+      ukm::SourceId ukm_source_id,
       const EntityInstance& entity,
       AutofillClient::EntitySaveOrUpdatePromptResult result);
   // Updates the `EntityDataManager` and the update strike database depending on
@@ -120,6 +121,7 @@
   void HandleUpdatePromptResult(
       uint64_t form_session_id,
       const std::string& domain,
+      ukm::SourceId ukm_source_id,
       const EntityInstance::EntityId& entity_uuid,
       AutofillClient::EntitySaveOrUpdatePromptResult result);
 
diff --git a/components/autofill/core/browser/integrators/autofill_ai/metrics/autofill_ai_logger.cc b/components/autofill/core/browser/integrators/autofill_ai/metrics/autofill_ai_logger.cc
index 9bc8cda..a7584bb7 100644
--- a/components/autofill/core/browser/integrators/autofill_ai/metrics/autofill_ai_logger.cc
+++ b/components/autofill/core/browser/integrators/autofill_ai/metrics/autofill_ai_logger.cc
@@ -222,9 +222,11 @@
     EntityInstance::RecordType record_type,
     uint64_t form_session_id,
     const std::string& domain,
-    AutofillClient::EntitySaveOrUpdatePromptResult result) {
+    AutofillClient::EntitySaveOrUpdatePromptResult result,
+    ukm::SourceId ukm_source_id) {
   ukm_logger_.LogSaveOrUpdatePromptResult(prompt_type, entity_type, record_type,
-                                          form_session_id, domain, result);
+                                          form_session_id, domain, result,
+                                          ukm_source_id);
 }
 
 void AutofillAiLogger::RecordKeyMetrics(
diff --git a/components/autofill/core/browser/integrators/autofill_ai/metrics/autofill_ai_logger.h b/components/autofill/core/browser/integrators/autofill_ai/metrics/autofill_ai_logger.h
index 786cf2a..0b5525b9 100644
--- a/components/autofill/core/browser/integrators/autofill_ai/metrics/autofill_ai_logger.h
+++ b/components/autofill/core/browser/integrators/autofill_ai/metrics/autofill_ai_logger.h
@@ -52,7 +52,8 @@
       EntityInstance::RecordType record_type,
       uint64_t form_session_id,
       const std::string& domain,
-      AutofillClient::EntitySaveOrUpdatePromptResult result);
+      AutofillClient::EntitySaveOrUpdatePromptResult result,
+      ukm::SourceId ukm_source_id);
 
   // Function that records the contents of `form_states` for `form` into
   // appropriate metrics. `submission_state` denotes whether the form was
diff --git a/components/autofill/core/browser/integrators/autofill_ai/metrics/autofill_ai_logger_unittest.cc b/components/autofill/core/browser/integrators/autofill_ai/metrics/autofill_ai_logger_unittest.cc
index 1ed0d44..cbd3227 100644
--- a/components/autofill/core/browser/integrators/autofill_ai/metrics/autofill_ai_logger_unittest.cc
+++ b/components/autofill/core/browser/integrators/autofill_ai/metrics/autofill_ai_logger_unittest.cc
@@ -58,6 +58,7 @@
 constexpr auto kPassport = EntityType(EntityTypeName::kPassport);
 constexpr auto kNationalIdCard = EntityType(EntityTypeName::kNationalIdCard);
 constexpr char kDefaultUrl[] = "https://example.com";
+constexpr uint64_t kFormSession = 123456UL;
 
 class MockAutofillClient : public TestAutofillClient {
  public:
@@ -727,19 +728,28 @@
 
   const optimization_guide::proto::AutofillAiFieldEvent&
   GetLastFieldEventLogs() {
-    return *(logs_uploader_->uploaded_logs()
-                 .back()
-                 ->mutable_forms_classifications()
-                 ->mutable_quality()
-                 ->mutable_field_event());
+    return logs_uploader_->uploaded_logs()
+        .back()
+        ->forms_classifications()
+        .quality()
+        .field_event();
   }
 
   const optimization_guide::proto::AutofillAiKeyMetrics& GetKeyMetricsLogs() {
-    return *(logs_uploader_->uploaded_logs()
-                 .back()
-                 ->mutable_forms_classifications()
-                 ->mutable_quality()
-                 ->mutable_key_metrics());
+    return logs_uploader_->uploaded_logs()
+        .back()
+        ->forms_classifications()
+        .quality()
+        .key_metrics();
+  }
+
+  const optimization_guide::proto::AutofillAiUserPromptMetrics&
+  GetUserPromptMetrics() {
+    return logs_uploader_->uploaded_logs()
+        .back()
+        ->forms_classifications()
+        .quality()
+        .user_prompt_metrics();
   }
 
   void ExpectCorrectMqlsFieldEventLogging(
@@ -846,6 +856,31 @@
       /*event_order=*/3);
 }
 
+TEST_F(AutofillAiMqlsMetricsTest, UserPrompts) {
+  test_api(manager()).logger().OnSaveOrUpdatePromptResult(
+      AutofillClient::AutofillAiPromptTypes::kUpdate, kPassport,
+      EntityInstance::RecordType::kLocal, /*form_session_id=*/kFormSession,
+      "myform_root.com",
+      AutofillClient::EntitySaveOrUpdatePromptResult(
+          /*did_user_decline=*/false, test::GetPassportEntityInstance()),
+      /*ukm_source_id=*/{});
+  ASSERT_EQ(mqls_logs().size(), 1u);
+
+  const optimization_guide::proto::AutofillAiUserPromptMetrics&
+      mqls_user_prompt = GetUserPromptMetrics();
+  EXPECT_EQ(mqls_user_prompt.domain(), "myform_root.com");
+  EXPECT_EQ(mqls_user_prompt.form_session_identifier(), kFormSession);
+  EXPECT_EQ(mqls_user_prompt.storage_type(),
+            optimization_guide::proto::AUTOFILL_AI_ENTITY_STORAGE_TYPE_LOCAL);
+  EXPECT_EQ(mqls_user_prompt.prompt_type(),
+            optimization_guide::proto::AUTOFILL_AI_PROMPT_TYPE_UPDATE_ENTITY);
+  EXPECT_EQ(mqls_user_prompt.entity_type(),
+            optimization_guide::proto::AUTOFILL_AI_ENTITY_TYPE_PASSPORT);
+  EXPECT_EQ(
+      mqls_user_prompt.result(),
+      optimization_guide::proto::AUTOFILL_AI_PROMPT_USER_DECISION_ACCEPTED);
+}
+
 TEST_F(AutofillAiMqlsMetricsTest, KeyMetrics) {
   std::unique_ptr<FormStructure> form = CreatePassportForm();
 
diff --git a/components/autofill/core/browser/integrators/autofill_ai/metrics/autofill_ai_ukm_logger.cc b/components/autofill/core/browser/integrators/autofill_ai/metrics/autofill_ai_ukm_logger.cc
index 5588a50..c02e773 100644
--- a/components/autofill/core/browser/integrators/autofill_ai/metrics/autofill_ai_ukm_logger.cc
+++ b/components/autofill/core/browser/integrators/autofill_ai/metrics/autofill_ai_ukm_logger.cc
@@ -120,6 +120,37 @@
   NOTREACHED();
 }
 
+optimization_guide::proto::AutofillAiPromptUserDecision GetUserDecision(
+    AutofillClient::EntitySaveOrUpdatePromptResult decision) {
+  if (decision.did_user_decline) {
+    return optimization_guide::proto::AUTOFILL_AI_PROMPT_USER_DECISION_DECLINED;
+  } else if (!decision.did_user_decline && !decision.entity) {
+    return optimization_guide::proto::AUTOFILL_AI_PROMPT_USER_DECISION_IGNORED;
+  } else {
+    return optimization_guide::proto::AUTOFILL_AI_PROMPT_USER_DECISION_ACCEPTED;
+  }
+}
+
+optimization_guide::proto::AutofillAiPromptType GetPromptType(
+    AutofillClient::AutofillAiPromptTypes prompt_type) {
+  switch (prompt_type) {
+    case AutofillClient::AutofillAiPromptTypes::kSave:
+      return optimization_guide::proto::AUTOFILL_AI_PROMPT_TYPE_SAVE_ENTITY;
+    case AutofillClient::AutofillAiPromptTypes::kUpdate:
+      return optimization_guide::proto::AUTOFILL_AI_PROMPT_TYPE_UPDATE_ENTITY;
+  }
+}
+
+optimization_guide::proto::AutofillAiEntityStorageType GetStorageType(
+    EntityInstance::RecordType record_type) {
+  switch (record_type) {
+    case EntityInstance::RecordType::kLocal:
+      return optimization_guide::proto::AUTOFILL_AI_ENTITY_STORAGE_TYPE_LOCAL;
+    case EntityInstance::RecordType::kServerWallet:
+      return optimization_guide::proto::AUTOFILL_AI_ENTITY_STORAGE_TYPE_WALLET;
+  }
+}
+
 }  // namespace
 
 AutofillAiUkmLogger::AutofillAiUkmLogger(AutofillClient* client)
@@ -225,8 +256,43 @@
     EntityInstance::RecordType record_type,
     uint64_t form_session_id,
     const std::string& domain,
-    AutofillClient::EntitySaveOrUpdatePromptResult result) {
-  // TODO(crbug.com/439779727): Log to MQLS and UKM.
+    AutofillClient::EntitySaveOrUpdatePromptResult result,
+    ukm::SourceId ukm_source_id) {
+  if (optimization_guide::ModelQualityLogsUploaderService* uploader_ =
+          client_->GetMqlsUploadService();
+      uploader_ &&
+      MayPerformAutofillAiAction(*client_, AutofillAiAction::kLogToMqls)) {
+    // Note that the actual logging of the metric happens when `log_entry` goes
+    // out of scope and is destroyed. Also note that in this case it is not
+    // necessary to check if the user is opted in because it is assumed that all
+    // field event types can only occur if the user is opted in for Autofill AI.
+    optimization_guide::ModelQualityLogEntry log_entry(
+        client_->GetMqlsUploadService()->GetWeakPtr());
+
+    optimization_guide::proto::AutofillAiUserPromptMetrics*
+        mqls_user_prompt_event = log_entry.log_ai_data_request()
+                                     ->mutable_forms_classifications()
+                                     ->mutable_quality()
+                                     ->mutable_user_prompt_metrics();
+
+    mqls_user_prompt_event->set_domain(domain);
+    mqls_user_prompt_event->set_form_session_identifier(form_session_id);
+    mqls_user_prompt_event->set_storage_type(GetStorageType(record_type));
+    mqls_user_prompt_event->set_prompt_type(GetPromptType(prompt_type));
+    mqls_user_prompt_event->set_entity_type(GetEntityType(entity_type));
+    mqls_user_prompt_event->set_result(GetUserDecision(result));
+  }
+
+  if (!CanLogUkm(ukm_source_id)) {
+    return;
+  }
+  ukm::builders::AutofillAi_UserPromptMetrics(ukm_source_id)
+      .SetFormSessionIdentifier(form_session_id)
+      .SetEntityType(base::to_underlying(entity_type.name()))
+      .SetStorageType(GetStorageType(record_type))
+      .SetPromptType(GetPromptType(prompt_type))
+      .SetResult(GetUserDecision(result))
+      .Record(client_->GetUkmRecorder());
 }
 
 void AutofillAiUkmLogger::LogFieldEvent(ukm::SourceId ukm_source_id,
diff --git a/components/autofill/core/browser/integrators/autofill_ai/metrics/autofill_ai_ukm_logger.h b/components/autofill/core/browser/integrators/autofill_ai/metrics/autofill_ai_ukm_logger.h
index af3f19d..92c65f1 100644
--- a/components/autofill/core/browser/integrators/autofill_ai/metrics/autofill_ai_ukm_logger.h
+++ b/components/autofill/core/browser/integrators/autofill_ai/metrics/autofill_ai_ukm_logger.h
@@ -40,7 +40,8 @@
       EntityInstance::RecordType record_type,
       uint64_t form_session_id,
       const std::string& domain,
-      AutofillClient::EntitySaveOrUpdatePromptResult result);
+      AutofillClient::EntitySaveOrUpdatePromptResult result,
+      ukm::SourceId ukm_source_id);
 
   // These values are persisted to logs. Entries should not be renumbered and
   // numeric values should never be reused.
diff --git a/components/autofill/core/browser/metrics/form_events/credit_card_form_event_logger.cc b/components/autofill/core/browser/metrics/form_events/credit_card_form_event_logger.cc
index 3d0d2c3d..7badfd2 100644
--- a/components/autofill/core/browser/metrics/form_events/credit_card_form_event_logger.cc
+++ b/components/autofill/core/browser/metrics/form_events/credit_card_form_event_logger.cc
@@ -31,6 +31,7 @@
 #include "components/autofill/core/browser/payments/constants.h"
 #include "components/autofill/core/browser/payments/credit_card_access_manager.h"
 #include "components/autofill/core/browser/payments/payments_autofill_client.h"
+#include "components/autofill/core/browser/payments/save_and_fill_manager.h"
 #include "components/autofill/core/common/autofill_internals/log_message.h"
 #include "components/autofill/core/common/autofill_internals/logging_scope.h"
 #include "components/autofill/core/common/autofill_payments_features.h"
@@ -335,6 +336,7 @@
   signin_state_for_metrics_ = signin_state_for_metrics;
 
   filled_credit_card_ = credit_card;
+  trigger_source_ = trigger_source;
 
   client().GetFormInteractionsUkmLogger().LogDidFillSuggestion(
       driver().GetPageUkmSourceId(), form, field, record_type);
@@ -348,6 +350,21 @@
        .newly_filled_fields = newly_filled_fields,
        .safe_fields = safe_filled_fields});
 
+  if (trigger_source_ == AutofillTriggerSource::kCreditCardSaveAndFill) {
+    // If the fill is triggered by the Save and Fill flow. We log form filling
+    // separately as the it is not triggered by regular Autofill credit card
+    // suggestions. Also Save and Fill flow is offered only on full credit card
+    // forms. These factors could pollute the existing card
+    // retrieval / filling / submission metrics.
+    auto* save_and_fill_manager =
+        client().GetPaymentsAutofillClient()->GetSaveAndFillManager();
+    // If the `trigger_source` is kCreditCardSaveAndFill, then
+    // `save_and_fill_manager` must exist.
+    CHECK(save_and_fill_manager);
+    save_and_fill_manager->LogCreditCardFormFilled();
+    return;
+  }
+
   latest_filled_card_was_masked_server_card_ = false;
   latest_filled_card_was_card_info_retrieval_enrolled_ = false;
   switch (record_type) {
@@ -509,7 +526,7 @@
   base::RecordAction(
       base::UserMetricsAction("Autofill_FilledCreditCardSuggestion"));
 
-  if (trigger_source != AutofillTriggerSource::kFastCheckout) {
+  if (trigger_source_ != AutofillTriggerSource::kFastCheckout) {
     ++form_interaction_counts_.autofill_fills;
   }
   UpdateFlowId();
@@ -600,6 +617,11 @@
 }
 
 void CreditCardFormEventLogger::LogWillSubmitForm(const FormStructure& form) {
+  if (trigger_source_ == AutofillTriggerSource::kCreditCardSaveAndFill) {
+    // If it is a Save and Fill flow. Don't log any will-submit metrics.
+    return;
+  }
+
   if (!has_logged_form_filling_suggestion_filled_) {
     Log(FORM_EVENT_NO_SUGGESTION_WILL_SUBMIT_ONCE, form);
   } else if (logged_suggestion_filled_was_masked_server_card_) {
@@ -653,6 +675,14 @@
 }
 
 void CreditCardFormEventLogger::LogFormSubmitted(const FormStructure& form) {
+  if (trigger_source_ == AutofillTriggerSource::kCreditCardSaveAndFill) {
+    auto* save_and_fill_manager =
+        client().GetPaymentsAutofillClient()->GetSaveAndFillManager();
+    CHECK(save_and_fill_manager);
+    save_and_fill_manager->LogCreditCardFormSubmitted();
+    return;
+  }
+
   if (!has_logged_form_filling_suggestion_filled_) {
     Log(FORM_EVENT_NO_SUGGESTION_SUBMITTED_ONCE, form);
   } else if (logged_suggestion_filled_was_masked_server_card_) {
diff --git a/components/autofill/core/browser/metrics/form_events/credit_card_form_event_logger.h b/components/autofill/core/browser/metrics/form_events/credit_card_form_event_logger.h
index e28babe..da0cac7 100644
--- a/components/autofill/core/browser/metrics/form_events/credit_card_form_event_logger.h
+++ b/components/autofill/core/browser/metrics/form_events/credit_card_form_event_logger.h
@@ -256,6 +256,8 @@
   AutofillMetrics::PaymentsSigninState signin_state_for_metrics_ =
       AutofillMetrics::PaymentsSigninState::kUnknown;
 
+  AutofillTriggerSource trigger_source_ = AutofillTriggerSource::kNone;
+
   // Weak references.
   raw_ptr<PersonalDataManager> personal_data_manager_;
 
diff --git a/components/autofill/core/browser/metrics/payments/save_and_fill_metrics.cc b/components/autofill/core/browser/metrics/payments/save_and_fill_metrics.cc
index 60882bf..0db4aa32 100644
--- a/components/autofill/core/browser/metrics/payments/save_and_fill_metrics.cc
+++ b/components/autofill/core/browser/metrics/payments/save_and_fill_metrics.cc
@@ -63,4 +63,14 @@
                             /*sample=*/true);
 }
 
+void LogSaveAndFillFunnelMetrics(bool succeeded,
+                                 bool is_for_upload,
+                                 SaveAndFillFormEvent event) {
+  base::UmaHistogramEnumeration(
+      base::StrCat({"Autofill.SaveAndFill.Funnel",
+                    is_for_upload ? ".Upload" : ".Local",
+                    succeeded ? ".Success" : ".Failure"}),
+      event);
+}
+
 }  // namespace autofill::autofill_metrics
diff --git a/components/autofill/core/browser/metrics/payments/save_and_fill_metrics.h b/components/autofill/core/browser/metrics/payments/save_and_fill_metrics.h
index e3921c2..276bb5d 100644
--- a/components/autofill/core/browser/metrics/payments/save_and_fill_metrics.h
+++ b/components/autofill/core/browser/metrics/payments/save_and_fill_metrics.h
@@ -19,7 +19,11 @@
   kSuggestionShown = 0,
   // The Save and Fill suggestion was accepted by the user.
   kSuggestionAccepted = 1,
-  kMaxValue = kSuggestionAccepted,
+  // The form was filled after Save and Fill finished.
+  kFormFilled = 2,
+  // The form was submitted after Save and Fill finished.
+  kFormSubmitted = 3,
+  kMaxValue = kFormSubmitted,
 };
 // LINT.ThenChange(/tools/metrics/histograms/metadata/autofill/enums.xml:SaveAndFillFormEvent)
 
@@ -82,6 +86,13 @@
 // Logs that the Save and Fill dialog was shown.
 void LogSaveAndFillDialogShown(bool is_upload);
 
+// Logs the form being filled and form being submitted event. Broken down by
+// whether the Save and Fill attempt succeeded and whether it was for upload
+// Save and Fill.
+void LogSaveAndFillFunnelMetrics(bool succeeded,
+                                 bool is_for_upload,
+                                 SaveAndFillFormEvent event);
+
 }  // namespace autofill::autofill_metrics
 
 #endif  // COMPONENTS_AUTOFILL_CORE_BROWSER_METRICS_PAYMENTS_SAVE_AND_FILL_METRICS_H_
diff --git a/components/autofill/core/browser/ml_model/field_classification_model_handler.cc b/components/autofill/core/browser/ml_model/field_classification_model_handler.cc
index b366d2d..6665397 100644
--- a/components/autofill/core/browser/ml_model/field_classification_model_handler.cc
+++ b/components/autofill/core/browser/ml_model/field_classification_model_handler.cc
@@ -251,8 +251,7 @@
   for (size_t i = 0;
        i < model_output.size() && i < prediction_log.field_predictions.size();
        ++i) {
-    prediction_log.field_predictions[i]->probabilities = std::vector(
-        model_output[i].begin(), model_output[i].end());
+    prediction_log.field_predictions[i]->probabilities = model_output[i];
   }
 }
 
@@ -272,10 +271,20 @@
     prediction_log = CreateMlPredictionLog(form);
   }
 
-  // TODO(crbug.com/428686605) Set tokenized representation in `prediction_log`.
   FieldClassificationModelEncoder::ModelInput encoded_input =
       state_->encoder.EncodeForm(form);
 
+  if (prediction_log) {
+    for (auto [encoded_field, field_prediction] :
+         base::zip(encoded_input, prediction_log.value()->field_predictions)) {
+      field_prediction->tokenized_field_representation = base::ToVector(
+          encoded_field,
+          [this](FieldClassificationModelEncoder::TokenId token_id) {
+            return TokenIdToString(token_id);
+          });
+    }
+  }
+
   std::optional<ModelInputHash> input_hash;
   if (base::FeatureList::IsEnabled(
           features::kFieldClassificationModelCaching)) {
@@ -489,4 +498,26 @@
   return ModelInputHash(base::FastHash(base::as_byte_span(flattened_data)));
 }
 
+std::string FieldClassificationModelHandler::TokenIdToString(
+    FieldClassificationModelEncoder::TokenId token_id) const {
+  const int token = static_cast<int>(token_id.value());
+  if (token == 0) {
+    // Padding token, always encoded as 0.
+    return "";
+  } else if (token == 1) {
+    // Unknown, out-of-vocabulary token, always encoded as 1.
+    return "[UNK]";
+  } else if (token == state_->metadata.input_token_size() + 2) {
+    // Special "classification" token used by the model, encoded as
+    // `vocabulary_size` (where the vocab size includes the two special tokens,
+    // hence the +2).
+    return "[CLS]";
+  } else if (token < 2 || token >= state_->metadata.input_token_size() + 2) {
+    return "[INVALID]";
+  } else {
+    // Indexing starts at 2 because of the special tokens.
+    return state_->metadata.input_token(token_id.value() - 2);
+  }
+}
+
 }  // namespace autofill
diff --git a/components/autofill/core/browser/ml_model/field_classification_model_handler.h b/components/autofill/core/browser/ml_model/field_classification_model_handler.h
index 67251ac..69570d1 100644
--- a/components/autofill/core/browser/ml_model/field_classification_model_handler.h
+++ b/components/autofill/core/browser/ml_model/field_classification_model_handler.h
@@ -135,6 +135,10 @@
   autofill_ml_internals::mojom::MlPredictionLogPtr CreateMlPredictionLog(
       const FormData& form_structure) const;
 
+  // Converts a `TokenId` to the string representation.
+  std::string TokenIdToString(
+      FieldClassificationModelEncoder::TokenId token_id) const;
+
   struct ModelState {
     optimization_guide::proto::AutofillFieldClassificationModelMetadata
         metadata;
diff --git a/components/autofill/core/browser/ml_model/logging/autofill_ml_internals.mojom b/components/autofill/core/browser/ml_model/logging/autofill_ml_internals.mojom
index 391769e..cb76995 100644
--- a/components/autofill/core/browser/ml_model/logging/autofill_ml_internals.mojom
+++ b/components/autofill/core/browser/ml_model/logging/autofill_ml_internals.mojom
@@ -29,6 +29,7 @@
   array<SelectOption> select_options;
 
   array<float>? probabilities;
+  array<string> tokenized_field_representation;
 };
 
 // Represents a single ML prediction log entry.
diff --git a/components/autofill/core/browser/payments/save_and_fill_manager.h b/components/autofill/core/browser/payments/save_and_fill_manager.h
index f618a38..458a7eea 100644
--- a/components/autofill/core/browser/payments/save_and_fill_manager.h
+++ b/components/autofill/core/browser/payments/save_and_fill_manager.h
@@ -29,15 +29,19 @@
       FillCardCallback fill_card_callback) = 0;
   // Called when the Save and Fill suggestion is shown to the user.
   virtual void OnSuggestionOffered() = 0;
-  // Called when the form is submitted. This is used to check if a strike should
-  // be added if the suggestion was shown but not selected.
-  virtual void OnCreditCardFormSubmitted() = 0;
+  // If the strike database exists, add a strike if the suggestion was shown but
+  // not selected.
+  virtual void MaybeAddStrikeForSaveAndFill() = 0;
   // Returns true if the feature offer should be blocked.
   virtual bool ShouldBlockFeature() = 0;
   // Logs the reason why the Save and Fill suggestion was not shown if this
   // metric has not yet been recorded, as this is logged once per page load.
   virtual void MaybeLogSaveAndFillSuggestionNotShownReason(
       autofill_metrics::SaveAndFillSuggestionNotShownReason reason) = 0;
+  // Logs when the credit card form was filled / submitted with the
+  // Save-and-Fill candidate card.
+  virtual void LogCreditCardFormFilled() = 0;
+  virtual void LogCreditCardFormSubmitted() = 0;
 };
 
 }  // namespace autofill::payments
diff --git a/components/autofill/core/browser/payments/save_and_fill_manager_impl.cc b/components/autofill/core/browser/payments/save_and_fill_manager_impl.cc
index 3d9779d5..fbe7cb7 100644
--- a/components/autofill/core/browser/payments/save_and_fill_manager_impl.cc
+++ b/components/autofill/core/browser/payments/save_and_fill_manager_impl.cc
@@ -68,7 +68,7 @@
   save_and_fill_suggestion_offered_ = true;
 }
 
-void SaveAndFillManagerImpl::OnCreditCardFormSubmitted() {
+void SaveAndFillManagerImpl::MaybeAddStrikeForSaveAndFill() {
   if (save_and_fill_suggestion_offered_ &&
       !save_and_fill_suggestion_selected_) {
     GetSaveAndFillStrikeDatabase()->AddStrike();
@@ -101,11 +101,36 @@
 
 void SaveAndFillManagerImpl::MaybeLogSaveAndFillSuggestionNotShownReason(
     autofill_metrics::SaveAndFillSuggestionNotShownReason reason) {
-  if (has_logged_save_and_fill_suggestion_not_shown_reason_) {
+  if (logging_context_.has_logged_save_and_fill_suggestion_not_shown_reason) {
     return;
   }
   autofill_metrics::LogSaveAndFillSuggestionNotShownReason(reason);
-  has_logged_save_and_fill_suggestion_not_shown_reason_ = true;
+  logging_context_.has_logged_save_and_fill_suggestion_not_shown_reason = true;
+}
+
+void SaveAndFillManagerImpl::LogCreditCardFormFilled() {
+  if (!logging_context_.has_logged_form_filled) {
+    CHECK(logging_context_.last_attempt_succeeded.has_value());
+    CHECK(logging_context_.last_attempt_was_for_upload.has_value());
+    autofill_metrics::LogSaveAndFillFunnelMetrics(
+        logging_context_.last_attempt_succeeded.value(),
+        logging_context_.last_attempt_was_for_upload.value(),
+        autofill_metrics::SaveAndFillFormEvent::kFormFilled);
+    logging_context_.has_logged_form_filled = true;
+  }
+}
+
+void SaveAndFillManagerImpl::LogCreditCardFormSubmitted() {
+  if (!logging_context_.has_logged_form_submitted &&
+      logging_context_.has_logged_form_filled) {
+    CHECK(logging_context_.last_attempt_succeeded.has_value());
+    CHECK(logging_context_.last_attempt_was_for_upload.has_value());
+    autofill_metrics::LogSaveAndFillFunnelMetrics(
+        logging_context_.last_attempt_succeeded.value(),
+        logging_context_.last_attempt_was_for_upload.value(),
+        autofill_metrics::SaveAndFillFormEvent::kFormSubmitted);
+    logging_context_.has_logged_form_submitted = true;
+  }
 }
 
 void SaveAndFillManagerImpl::OnUserDidDecideOnLocalSave(
@@ -114,6 +139,8 @@
         user_provided_card_save_and_fill_details) {
   switch (user_decision) {
     case CardSaveAndFillDialogUserDecision::kAccepted: {
+      logging_context_.last_attempt_was_for_upload = false;
+      logging_context_.last_attempt_succeeded = true;
       if (auto* strike_database = GetSaveAndFillStrikeDatabase()) {
         autofill_metrics::LogSaveAndFillNumOfStrikesPresentWhenDialogAccepted(
             strike_database->GetStrikes());
@@ -294,6 +321,7 @@
   switch (user_decision) {
     case CardSaveAndFillDialogUserDecision::kAccepted:
       upload_save_and_fill_dialog_accepted_ = true;
+      logging_context_.last_attempt_was_for_upload = true;
       if (auto* strike_database = GetSaveAndFillStrikeDatabase()) {
         autofill_metrics::LogSaveAndFillNumOfStrikesPresentWhenDialogAccepted(
             strike_database->GetStrikes());
@@ -301,9 +329,6 @@
       }
       PopulateCreditCardInfo(upload_details_.card,
                              user_provided_card_save_and_fill_details);
-      if (fill_card_callback_) {
-        std::move(fill_card_callback_).Run(upload_details_.card);
-      }
       // If risk data has already been loaded, send the request now. Otherwise,
       // continue to wait and let OnDidLoadRiskData handle it.
       if (!upload_details_.risk_data.empty()) {
@@ -346,6 +371,8 @@
   autofill_metrics::LogSaveAndFillCreateCardResultAndLatency(
       result == PaymentsRpcResult::kSuccess,
       base::TimeTicks::Now() - request_sent_timestamp);
+  logging_context_.last_attempt_succeeded =
+      result == PaymentsRpcResult::kSuccess;
 
   if (result != PaymentsAutofillClient::PaymentsRpcResult::kSuccess) {
     // If card creation fails, save the card locally instead. All card
@@ -365,6 +392,9 @@
           parsed_instrument_id, upload_details_.card.cvc());
     }
   }
+  if (fill_card_callback_) {
+    std::move(fill_card_callback_).Run(upload_details_.card);
+  }
   payments_autofill_client()->HideCreditCardSaveAndFillDialog();
   // Invoke feedback bubble. No callback needed (virtual card enrollment is not
   // eligible for card saved via the Save and Fill flow).
diff --git a/components/autofill/core/browser/payments/save_and_fill_manager_impl.h b/components/autofill/core/browser/payments/save_and_fill_manager_impl.h
index f08abd92..a62a073 100644
--- a/components/autofill/core/browser/payments/save_and_fill_manager_impl.h
+++ b/components/autofill/core/browser/payments/save_and_fill_manager_impl.h
@@ -19,6 +19,25 @@
 // Contents. This class manages the flow for the Save and Fill dialog.
 class SaveAndFillManagerImpl : public SaveAndFillManager {
  public:
+  // A struct containing a few logging related info. This is not reset when flow
+  // ends as metrics loggings are once per page load.
+  struct LoggingContext {
+    // Set to true after the first time the Save and Fill suggestion not being
+    // shown is logged. Ensures that logging occurs only once per page load.
+    bool has_logged_save_and_fill_suggestion_not_shown_reason = false;
+
+    // Set when the Save and Fill dialog was accepted by the user. Records
+    // whether the attempt was for upload / local save and whether the attempt
+    // succeeded.
+    std::optional<bool> last_attempt_succeeded;
+    std::optional<bool> last_attempt_was_for_upload;
+
+    // Whether credit card form filling / submission event has been logged.
+    // Ensures that logging occurs only once per page load.
+    bool has_logged_form_filled = false;
+    bool has_logged_form_submitted = false;
+  };
+
   explicit SaveAndFillManagerImpl(AutofillClient* autofill_client);
   SaveAndFillManagerImpl(const SaveAndFillManagerImpl& other) = delete;
   SaveAndFillManagerImpl& operator=(const SaveAndFillManagerImpl& other) =
@@ -29,10 +48,12 @@
   void OnDidAcceptCreditCardSaveAndFillSuggestion(
       FillCardCallback fill_card_callback) override;
   void OnSuggestionOffered() override;
-  void OnCreditCardFormSubmitted() override;
+  void MaybeAddStrikeForSaveAndFill() override;
   bool ShouldBlockFeature() override;
   void MaybeLogSaveAndFillSuggestionNotShownReason(
       autofill_metrics::SaveAndFillSuggestionNotShownReason reason) override;
+  void LogCreditCardFormFilled() override;
+  void LogCreditCardFormSubmitted() override;
 
   // Called when the user makes a decision on the local Save and Fill dialog.
   // The `user_provided_card_save_and_fill_details` holds the  data entered by
@@ -48,9 +69,13 @@
 
  private:
   FRIEND_TEST_ALL_PREFIXES(SaveAndFillManagerImplTest,
+                           OnUserDidDecideOnUploadSave_Accepted);
+  FRIEND_TEST_ALL_PREFIXES(SaveAndFillManagerImplTest,
                            ResetOnFlowEnds_ServerSave);
   FRIEND_TEST_ALL_PREFIXES(SaveAndFillManagerImplTest,
                            ResetOnFlowEnds_LocalSave);
+  FRIEND_TEST_ALL_PREFIXES(SaveAndFillManagerImplTest,
+                           LogFunnelMetrics_ServerSave);
 
   // Begins the process to show the local Save and Fill dialog.
   void OfferLocalSaveAndFill();
@@ -127,9 +152,8 @@
   bool save_and_fill_suggestion_offered_ = false;
   // True if the user accepted the Save and Fill suggestion.
   bool save_and_fill_suggestion_selected_ = false;
-  // Set to true after the first time the Save and Fill suggestion not being
-  // shown is logged. Ensures that logging occurs only once per page load.
-  bool has_logged_save_and_fill_suggestion_not_shown_reason_ = false;
+
+  LoggingContext logging_context_;
 
   // StrikeDatabase used to check whether to show the Save and Fill suggestion.
   std::unique_ptr<SaveAndFillStrikeDatabase> save_and_fill_strike_database_;
diff --git a/components/autofill/core/browser/payments/save_and_fill_manager_impl_unittest.cc b/components/autofill/core/browser/payments/save_and_fill_manager_impl_unittest.cc
index 703afd3a..5877a45 100644
--- a/components/autofill/core/browser/payments/save_and_fill_manager_impl_unittest.cc
+++ b/components/autofill/core/browser/payments/save_and_fill_manager_impl_unittest.cc
@@ -13,6 +13,7 @@
 #include "base/time/time.h"
 #include "components/autofill/core/browser/form_import/form_data_importer_test_api.h"
 #include "components/autofill/core/browser/foundations/test_autofill_client.h"
+#include "components/autofill/core/browser/metrics/payments/save_and_fill_metrics.h"
 #include "components/autofill/core/browser/payments/client_behavior_constants.h"
 #include "components/autofill/core/browser/payments/payments_util.h"
 #include "components/autofill/core/browser/payments/test/mock_multiple_request_payments_network_interface.h"
@@ -659,6 +660,10 @@
 
   save_and_fill_manager_impl_->OnDidAcceptCreditCardSaveAndFillSuggestion(
       fill_card_callback_.Get());
+  save_and_fill_manager_impl_->OnDidCreateCard(
+      base::TimeTicks::Now(),
+      PaymentsAutofillClient::PaymentsRpcResult::kSuccess,
+      /*instrument_id=*/"1122334455");
 
   EXPECT_EQ(u"1111222233334444", card_to_fill.GetRawInfo(CREDIT_CARD_NUMBER));
   EXPECT_EQ(u"Jane Smith", card_to_fill.GetRawInfo(CREDIT_CARD_NAME_FULL));
@@ -750,7 +755,7 @@
   SaveAndFillStrikeDatabase save_and_fill_strike_database(strike_database_);
 
   save_and_fill_manager_impl_->OnSuggestionOffered();
-  save_and_fill_manager_impl_->OnCreditCardFormSubmitted();
+  save_and_fill_manager_impl_->MaybeAddStrikeForSaveAndFill();
 
   EXPECT_EQ(1, save_and_fill_strike_database.GetStrikes());
 }
@@ -764,7 +769,7 @@
   save_and_fill_manager_impl_->OnSuggestionOffered();
   save_and_fill_manager_impl_->OnDidAcceptCreditCardSaveAndFillSuggestion(
       fill_card_callback_.Get());
-  save_and_fill_manager_impl_->OnCreditCardFormSubmitted();
+  save_and_fill_manager_impl_->MaybeAddStrikeForSaveAndFill();
 
   EXPECT_EQ(0, save_and_fill_strike_database.GetStrikes());
 }
@@ -1103,4 +1108,105 @@
       fill_card_callback_.Get());
 }
 
+TEST_F(SaveAndFillManagerImplTest, LogFunnelMetrics_ServerSave) {
+  base::HistogramTester histogram_tester;
+
+  save_and_fill_manager_impl_->SetCreditCardUploadEnabledOverrideForTesting(
+      true);
+  SetUpGetDetailsForCreateCardResponse(
+      PaymentsAutofillClient::PaymentsRpcResult::kSuccess,
+      /*create_valid_legal_message=*/true);
+
+  EXPECT_CALL(*payments_autofill_client_, ShowCreditCardUploadSaveAndFillDialog)
+      .WillOnce([&](const LegalMessageLines&,
+                    TestPaymentsAutofillClient::CardSaveAndFillDialogCallback
+                        callback) {
+        std::move(callback).Run(CardSaveAndFillDialogUserDecision::kAccepted,
+                                CreateUserProvidedCardDetails(
+                                    /*card_number=*/u"1111222233334444",
+                                    /*cardholder_name=*/u"Jane Smith",
+                                    /*expiration_date_month=*/u"06",
+                                    /*expiration_date_year=*/u"2035",
+                                    /*security_code=*/u"456"));
+      });
+
+  EXPECT_CALL(*payments_autofill_client_, LoadRiskData)
+      .WillOnce([](base::OnceCallback<void(const std::string&)> callback) {
+        std::move(callback).Run("some risk data");
+      });
+  EXPECT_CALL(*mock_network_interface_, CreateCard)
+      .WillOnce(testing::Return(RequestId("11223344")));
+
+  save_and_fill_manager_impl_->OnDidAcceptCreditCardSaveAndFillSuggestion(
+      fill_card_callback_.Get());
+  save_and_fill_manager_impl_->OnDidCreateCard(
+      base::TimeTicks::Now(),
+      PaymentsAutofillClient::PaymentsRpcResult::kPermanentFailure,
+      /*instrument_id=*/"");
+
+  save_and_fill_manager_impl_->LogCreditCardFormFilled();
+  save_and_fill_manager_impl_->LogCreditCardFormSubmitted();
+
+  histogram_tester.ExpectBucketCount(
+      "Autofill.SaveAndFill.Funnel.Upload.Failure",
+      autofill_metrics::SaveAndFillFormEvent::kFormFilled,
+      /*expected_count=*/1);
+  histogram_tester.ExpectBucketCount(
+      "Autofill.SaveAndFill.Funnel.Upload.Failure",
+      autofill_metrics::SaveAndFillFormEvent::kFormSubmitted,
+      /*expected_count=*/1);
+
+  // Make sure calling it multiple times has no effect.
+  save_and_fill_manager_impl_->LogCreditCardFormFilled();
+  save_and_fill_manager_impl_->LogCreditCardFormSubmitted();
+
+  histogram_tester.ExpectBucketCount(
+      "Autofill.SaveAndFill.Funnel.Upload.Failure",
+      autofill_metrics::SaveAndFillFormEvent::kFormFilled,
+      /*expected_count=*/1);
+  histogram_tester.ExpectBucketCount(
+      "Autofill.SaveAndFill.Funnel.Upload.Failure",
+      autofill_metrics::SaveAndFillFormEvent::kFormSubmitted,
+      /*expected_count=*/1);
+}
+
+TEST_F(SaveAndFillManagerImplTest, LogFunnelMetrics_LocalSave) {
+  base::HistogramTester histogram_tester;
+  save_and_fill_manager_impl_->OnDidAcceptCreditCardSaveAndFillSuggestion(
+      fill_card_callback_.Get());
+  save_and_fill_manager_impl_->OnUserDidDecideOnLocalSave(
+      CardSaveAndFillDialogUserDecision::kAccepted,
+      CreateUserProvidedCardDetails(
+          /*card_number=*/u"1111222233334444",
+          /*cardholder_name=*/u"Jane Smith",
+          /*expiration_date_month=*/u"06",
+          /*expiration_date_year=*/u"2035",
+          /*security_code=*/u"456"));
+
+  save_and_fill_manager_impl_->LogCreditCardFormFilled();
+  save_and_fill_manager_impl_->LogCreditCardFormSubmitted();
+
+  histogram_tester.ExpectBucketCount(
+      "Autofill.SaveAndFill.Funnel.Local.Success",
+      autofill_metrics::SaveAndFillFormEvent::kFormFilled,
+      /*expected_count=*/1);
+  histogram_tester.ExpectBucketCount(
+      "Autofill.SaveAndFill.Funnel.Local.Success",
+      autofill_metrics::SaveAndFillFormEvent::kFormSubmitted,
+      /*expected_count=*/1);
+
+  // Make sure calling it multiple times has no effect.
+  save_and_fill_manager_impl_->LogCreditCardFormFilled();
+  save_and_fill_manager_impl_->LogCreditCardFormSubmitted();
+
+  histogram_tester.ExpectBucketCount(
+      "Autofill.SaveAndFill.Funnel.Local.Success",
+      autofill_metrics::SaveAndFillFormEvent::kFormFilled,
+      /*expected_count=*/1);
+  histogram_tester.ExpectBucketCount(
+      "Autofill.SaveAndFill.Funnel.Local.Success",
+      autofill_metrics::SaveAndFillFormEvent::kFormSubmitted,
+      /*expected_count=*/1);
+}
+
 }  // namespace autofill::payments
diff --git a/components/autofill/core/browser/payments/test/mock_save_and_fill_manager.h b/components/autofill/core/browser/payments/test/mock_save_and_fill_manager.h
index d974f27..529bed30 100644
--- a/components/autofill/core/browser/payments/test/mock_save_and_fill_manager.h
+++ b/components/autofill/core/browser/payments/test/mock_save_and_fill_manager.h
@@ -21,12 +21,14 @@
               (FillCardCallback fill_card_callback),
               (override));
   MOCK_METHOD(void, OnSuggestionOffered, (), (override));
-  MOCK_METHOD(void, OnCreditCardFormSubmitted, (), (override));
+  MOCK_METHOD(void, MaybeAddStrikeForSaveAndFill, (), (override));
   MOCK_METHOD(bool, ShouldBlockFeature, (), (override));
   MOCK_METHOD(void,
               MaybeLogSaveAndFillSuggestionNotShownReason,
               (autofill_metrics::SaveAndFillSuggestionNotShownReason reason),
               (override));
+  MOCK_METHOD(void, LogCreditCardFormFilled, (), (override));
+  MOCK_METHOD(void, LogCreditCardFormSubmitted, (), (override));
 };
 
 }  // namespace autofill
diff --git a/components/autofill/core/browser/suggestions/addresses/address_suggestion_generator.cc b/components/autofill/core/browser/suggestions/addresses/address_suggestion_generator.cc
index f232931..7e8a1d39 100644
--- a/components/autofill/core/browser/suggestions/addresses/address_suggestion_generator.cc
+++ b/components/autofill/core/browser/suggestions/addresses/address_suggestion_generator.cc
@@ -12,10 +12,12 @@
 
 #include "base/check_deref.h"
 #include "base/containers/contains.h"
+#include "base/containers/extend.h"
 #include "base/containers/to_vector.h"
 #include "base/feature_list.h"
 #include "base/i18n/case_conversion.h"
 #include "base/memory/raw_ptr.h"
+#include "base/metrics/histogram_functions.h"
 #include "base/notreached.h"
 #include "base/strings/strcat.h"
 #include "base/strings/string_util.h"
@@ -37,12 +39,15 @@
 #include "components/autofill/core/browser/foundations/autofill_client.h"
 #include "components/autofill/core/browser/geo/address_i18n.h"
 #include "components/autofill/core/browser/geo/phone_number_i18n.h"
+#include "components/autofill/core/browser/logging/log_manager.h"
 #include "components/autofill/core/browser/metrics/autofill_metrics.h"
 #include "components/autofill/core/browser/suggestions/suggestion.h"
 #include "components/autofill/core/browser/suggestions/suggestion_type.h"
 #include "components/autofill/core/common/autofill_clock.h"
 #include "components/autofill/core/common/autofill_constants.h"
 #include "components/autofill/core/common/autofill_features.h"
+#include "components/autofill/core/common/autofill_internals/log_message.h"
+#include "components/autofill/core/common/autofill_internals/logging_scope.h"
 #include "components/autofill/core/common/autofill_util.h"
 #include "components/autofill/core/common/form_field_data.h"
 #include "components/feature_engagement/public/feature_constants.h"
@@ -264,6 +269,7 @@
       Suggestion::Acceptability::kUnacceptableWithDeactivatedStyle;
   suggestion.children.emplace_back(SuggestionType::kSeparator);
   for (const AutofillProfile& test_address : test_addresses) {
+    CHECK(test_address.is_devtools_testing_profile());
     const std::u16string test_address_country =
         test_address.GetInfo(ADDRESS_HOME_COUNTRY, locale);
     suggestion.children.emplace_back(test_address_country,
@@ -499,6 +505,7 @@
   std::vector<Suggestion::AutofillProfilePayload> payloads;
   payloads.reserve(profiles.size());
   for (AutofillProfile& profile : profiles) {
+    CHECK(!profile.is_devtools_testing_profile());
     std::u16string email_override;
     // If the following conditions are met:
     // - A plus address override is available
@@ -606,8 +613,18 @@
   return suggestions;
 }
 
+SuggestionType GetSuggestionType(FormFieldData trigger_field) {
+  // If the user triggers suggestions on an autofilled field, field-by-field
+  // filling suggestions should be shown so that the user could easily correct
+  // values to something present in different stored addresses.
+  return trigger_field.is_autofilled()
+             ? SuggestionType::kAddressFieldByFieldFilling
+             : SuggestionType::kAddressEntry;
+}
+
 }  // namespace
 
+// TODO(crbug.com/409962888): Handle address suggestions on typing.
 std::vector<Suggestion> GetSuggestionsOnTypingForProfile(
     const AddressDataManager& address_data_manager,
     const std::u16string& field_contents) {
@@ -728,42 +745,6 @@
   return suggestions;
 }
 
-std::vector<Suggestion> GetSuggestionsForProfiles(
-    const AutofillClient& client,
-    const FieldTypeSet& field_types,
-    const FormFieldData& trigger_field,
-    FieldType trigger_field_type,
-    SuggestionType suggestion_type,
-    std::optional<std::string> plus_address_email_override) {
-  CHECK(trigger_field.is_autofilled() ||
-        suggestion_type != SuggestionType::kAddressFieldByFieldFilling);
-  std::vector<AutofillProfile> profiles_to_suggest = GetProfilesToSuggest(
-      client.GetPersonalDataManager().address_data_manager(), trigger_field,
-      trigger_field_type, field_types);
-  const std::string gaia_email =
-      client.GetIdentityManager()
-          ->GetPrimaryAccountInfo(signin::ConsentLevel::kSignin)
-          .email;
-  std::vector<Suggestion> suggestions = CreateSuggestionsFromProfiles(
-      std::move(profiles_to_suggest), gaia_email, field_types, suggestion_type,
-      trigger_field_type, trigger_field, std::move(plus_address_email_override),
-      client.GetAppLocale());
-
-  // Add devtools test addresses suggestion if it exists. A suggestion will
-  // exist if devtools is open and therefore test addresses were set.
-  if (std::optional<Suggestion> test_addresses_suggestion =
-          GetSuggestionForTestAddresses(client.GetTestAddresses(),
-                                        client.GetAppLocale())) {
-    suggestions.push_back(std::move(*test_addresses_suggestion));
-  }
-  if (suggestions.empty()) {
-    return suggestions;
-  }
-  std::ranges::move(GetAddressFooterSuggestions(trigger_field.is_autofilled()),
-                    std::back_inserter(suggestions));
-  return suggestions;
-}
-
 Suggestion CreateManageAddressesSuggestion() {
   Suggestion suggestion(
       l10n_util::GetStringUTF16(IDS_AUTOFILL_MANAGE_ADDRESSES),
@@ -796,4 +777,186 @@
                                        plus_address_email_override, app_locale);
 }
 
+AddressSuggestionGenerator::AddressSuggestionGenerator(
+    const AutofillClient& client,
+    const std::optional<std::string>& plus_address_email_override,
+    base::WeakPtr<FormFiller> form_filler,
+    LogManager* log_manager)
+    : plus_address_email_override_(plus_address_email_override),
+      client_(client),
+      form_filler_(form_filler),
+      log_manager_(log_manager) {}
+
+AddressSuggestionGenerator::~AddressSuggestionGenerator() = default;
+
+void AddressSuggestionGenerator::FetchSuggestionData(
+    const FormData& form,
+    const FormFieldData& trigger_field,
+    const FormStructure* form_structure,
+    const AutofillField* trigger_autofill_field,
+    const AutofillClient& client,
+    base::OnceCallback<
+        void(std::pair<FillingProduct,
+                       std::vector<SuggestionGenerator::SuggestionData>>)>
+        callback) {
+  FetchSuggestionData(
+      form, trigger_field, form_structure, trigger_autofill_field, client,
+      [&callback](std::pair<FillingProduct,
+                            std::vector<SuggestionGenerator::SuggestionData>>
+                      suggestion_data) {
+        std::move(callback).Run(std::move(suggestion_data));
+      });
+}
+
+void AddressSuggestionGenerator::GenerateSuggestions(
+    const FormData& form,
+    const FormFieldData& trigger_field,
+    const FormStructure* form_structure,
+    const AutofillField* trigger_autofill_field,
+    const std::vector<std::pair<FillingProduct, std::vector<SuggestionData>>>&
+        all_suggestion_data,
+    base::OnceCallback<void(ReturnedSuggestions)> callback) {
+  GenerateSuggestions(
+      form, trigger_field, form_structure, trigger_autofill_field,
+      all_suggestion_data,
+      [&callback](ReturnedSuggestions returned_suggestions) {
+        std::move(callback).Run(std::move(returned_suggestions));
+      });
+}
+
+void AddressSuggestionGenerator::FetchSuggestionData(
+    const FormData& form,
+    const FormFieldData& trigger_field,
+    const FormStructure* form_structure,
+    const AutofillField* trigger_autofill_field,
+    const AutofillClient& client,
+    base::FunctionRef<
+        void(std::pair<FillingProduct,
+                       std::vector<SuggestionGenerator::SuggestionData>>)>
+        callback) {
+  if (!form_structure || !trigger_autofill_field) {
+    callback({FillingProduct::kAddress, {}});
+    return;
+  }
+#if !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS)
+  bool should_suppress =
+      client.GetPersonalDataManager()
+          .address_data_manager()
+          .AreAddressSuggestionsBlocked(
+              CalculateFormSignature(form),
+              CalculateFieldSignatureForField(trigger_field), form.url());
+  base::UmaHistogramBoolean("Autofill.Suggestion.StrikeSuppression.Address",
+                            should_suppress);
+  if (should_suppress &&
+      !base::FeatureList::IsEnabled(
+          features::test::kAutofillDisableSuggestionStrikeDatabase)) {
+    if (log_manager_) {
+      LOG_AF(log_manager_) << LoggingScope::kFilling
+                           << LogMessage::kSuggestionSuppressed
+                           << " Reason: strike limit reached.";
+    }
+    // If the user already reached the strike limit on this particular field,
+    // address suggestions are suppressed.
+    callback({FillingProduct::kAddress, {}});
+    return;
+  }
+#endif
+
+  field_types_ = [&]() -> FieldTypeSet {
+    if (GetSuggestionType(trigger_field) ==
+        SuggestionType::kAddressFieldByFieldFilling) {
+      return {trigger_autofill_field->Type().GetAddressType()};
+    }
+    // If the FormData `form_data` and FormStructure `form` do not have the same
+    // size, we assume as a fallback that all fields are fillable.
+    base::flat_map<FieldGlobalId, DenseSet<FieldFillingSkipReason>>
+        skip_reasons;
+    if (form_filler_ && form.fields().size() == form_structure->field_count()) {
+      skip_reasons = form_filler_->GetFieldFillingSkipReasons(
+          form.fields(), *form_structure, *trigger_autofill_field,
+          FormFiller::RefillOptions::NotRefill(), FillingProduct::kAddress);
+    }
+    FieldTypeSet field_types;
+    for (size_t i = 0; i < form_structure->field_count(); ++i) {
+      if (auto it = skip_reasons.find(form_structure->field(i)->global_id());
+          it == skip_reasons.end() || it->second.empty()) {
+        field_types.insert(form_structure->field(i)->Type().GetAddressType());
+      }
+    }
+    return field_types;
+  }();
+
+  std::vector<AutofillProfile> profiles_to_suggest = GetProfilesToSuggest(
+      client.GetPersonalDataManager().address_data_manager(), trigger_field,
+      trigger_autofill_field->Type().GetAddressType(), field_types_);
+
+  std::vector<SuggestionGenerator::SuggestionData> suggestion_data =
+      base::ToVector(
+          std::move(profiles_to_suggest), [](AutofillProfile& profile) {
+            return SuggestionGenerator::SuggestionData(std::move(profile));
+          });
+
+  // Add devtools test addresses if it exists. A test addresses will
+  // exist if devtools is open and therefore test addresses were set.
+  for (AutofillProfile test_address : client.GetTestAddresses()) {
+    suggestion_data.emplace_back(test_address);
+  }
+
+  callback({FillingProduct::kAddress, std::move(suggestion_data)});
+}
+
+void AddressSuggestionGenerator::GenerateSuggestions(
+    const FormData& form,
+    const FormFieldData& trigger_field,
+    const FormStructure* form_structure,
+    const AutofillField* trigger_autofill_field,
+    const std::vector<std::pair<FillingProduct, std::vector<SuggestionData>>>&
+        all_suggestion_data,
+    base::FunctionRef<void(ReturnedSuggestions)> callback) {
+  if (!form_structure || !trigger_autofill_field ||
+      !client_->GetIdentityManager()) {
+    callback({FillingProduct::kAddress, {}});
+    return;
+  }
+
+  std::vector<SuggestionData> address_suggestion_data =
+      ExtractSuggestionDataForFillingProduct(all_suggestion_data,
+                                             FillingProduct::kAddress);
+
+  std::vector<AutofillProfile> profiles_to_suggest = base::ToVector(
+      std::move(address_suggestion_data), [](SuggestionData& suggestion_data) {
+        return std::get<AutofillProfile>(std::move(suggestion_data));
+      });
+
+  // Testing profiles were added last.
+  auto partition_it = std::ranges::find_if(
+      profiles_to_suggest, &AutofillProfile::is_devtools_testing_profile);
+
+  std::vector<Suggestion> suggestions = CreateSuggestionsFromProfiles(
+      std::vector(std::make_move_iterator(profiles_to_suggest.begin()),
+                  std::make_move_iterator(partition_it)),
+      client_->GetIdentityManager()
+          ->GetPrimaryAccountInfo(signin::ConsentLevel::kSignin)
+          .email,
+      field_types_, GetSuggestionType(trigger_field),
+      trigger_autofill_field->Type().GetAddressType(), trigger_field,
+      plus_address_email_override_, client_->GetAppLocale());
+
+  // Add devtools test addresses suggestion if it exists.
+  if (std::optional<Suggestion> test_addresses_suggestion =
+          GetSuggestionForTestAddresses(
+              std::vector(std::make_move_iterator(partition_it),
+                          std::make_move_iterator(profiles_to_suggest.end())),
+              client_->GetAppLocale())) {
+    suggestions.push_back(std::move(*test_addresses_suggestion));
+  }
+  if (suggestions.empty()) {
+    callback({FillingProduct::kAddress, {}});
+    return;
+  }
+  base::Extend(suggestions,
+               GetAddressFooterSuggestions(trigger_field.is_autofilled()));
+  callback({FillingProduct::kAddress, std::move(suggestions)});
+}
+
 }  // namespace autofill
diff --git a/components/autofill/core/browser/suggestions/addresses/address_suggestion_generator.h b/components/autofill/core/browser/suggestions/addresses/address_suggestion_generator.h
index f8eade0..2ee6ebc6 100644
--- a/components/autofill/core/browser/suggestions/addresses/address_suggestion_generator.h
+++ b/components/autofill/core/browser/suggestions/addresses/address_suggestion_generator.h
@@ -12,11 +12,15 @@
 #include "base/check_deref.h"
 #include "base/containers/flat_map.h"
 #include "base/memory/raw_ptr.h"
+#include "base/memory/raw_ref.h"
 #include "components/autofill/core/browser/data_manager/personal_data_manager.h"
 #include "components/autofill/core/browser/data_model/addresses/autofill_profile.h"
 #include "components/autofill/core/browser/field_types.h"
+#include "components/autofill/core/browser/filling/form_filler.h"
+#include "components/autofill/core/browser/logging/log_manager.h"
 #include "components/autofill/core/browser/metrics/log_event.h"
 #include "components/autofill/core/browser/suggestions/suggestion.h"
+#include "components/autofill/core/browser/suggestions/suggestion_generator.h"
 #include "components/autofill/core/browser/suggestions/suggestion_type.h"
 #include "components/autofill/core/common/aliases.h"
 
@@ -35,18 +39,6 @@
     const AddressDataManager& adress_data_manager,
     const std::u16string& field_contents);
 
-// Generates suggestions for a form containing the given `field_types`. It
-// considers all available profiles, deduplicates them based on the types and
-// returns one suggestion per remaining profile.
-// `field_types` are the relevant types for the current suggestions.
-std::vector<Suggestion> GetSuggestionsForProfiles(
-    const AutofillClient& client,
-    const FieldTypeSet& field_types,
-    const FormFieldData& trigger_field,
-    FieldType trigger_field_type,
-    SuggestionType suggestion_type,
-    std::optional<std::string> plus_address_email_override);
-
 // Generates a footer suggestion "Manage addresses..." menu item which will
 // redirect to Chrome address settings page.
 Suggestion CreateManageAddressesSuggestion();
@@ -69,6 +61,82 @@
     std::optional<std::string> plus_address_email_override = std::nullopt,
     const std::string& gaia_email = "");
 
+// `SuggestionGenerator` implementation for addresses. Obtaining the address
+// suggestions should be done only through this class.
+class AddressSuggestionGenerator : public SuggestionGenerator {
+ public:
+  // TODO(crbug.com/409962888): `plus_address_email_override`
+  // has to be removed once the plus address suggestion generator and
+  // suggestions merging are implemented.
+  AddressSuggestionGenerator(
+      const AutofillClient& client,
+      const std::optional<std::string>& plus_address_email_override,
+      base::WeakPtr<FormFiller> form_filler,
+      LogManager* log_manager);
+  ~AddressSuggestionGenerator() override;
+
+  void FetchSuggestionData(
+      const FormData& form,
+      const FormFieldData& trigger_field,
+      const FormStructure* form_structure,
+      const AutofillField* trigger_autofill_field,
+      const AutofillClient& client,
+      base::OnceCallback<
+          void(std::pair<FillingProduct,
+                         std::vector<SuggestionGenerator::SuggestionData>>)>
+          callback) override;
+
+  void GenerateSuggestions(
+      const FormData& form,
+      const FormFieldData& trigger_field,
+      const FormStructure* form_structure,
+      const AutofillField* trigger_autofill_field,
+      const std::vector<std::pair<FillingProduct, std::vector<SuggestionData>>>&
+          all_suggestion_data,
+      base::OnceCallback<void(ReturnedSuggestions)> callback) override;
+
+  // Like SuggestionGenerator override, but takes a base::FunctionRef instead of
+  // a base::OnceCallback. Calls that callback exactly once.
+  void FetchSuggestionData(
+      const FormData& form,
+      const FormFieldData& trigger_field,
+      const FormStructure* form_structure,
+      const AutofillField* trigger_autofill_field,
+      const AutofillClient& client,
+      base::FunctionRef<
+          void(std::pair<FillingProduct,
+                         std::vector<SuggestionGenerator::SuggestionData>>)>
+          callback);
+
+  // Like SuggestionGenerator override, but takes a base::FunctionRef instead of
+  // a base::OnceCallback. Calls that callback exactly once.
+  void GenerateSuggestions(
+      const FormData& form,
+      const FormFieldData& trigger_field,
+      const FormStructure* form_structure,
+      const AutofillField* trigger_autofill_field,
+      const std::vector<std::pair<FillingProduct, std::vector<SuggestionData>>>&
+          all_suggestion_data,
+      base::FunctionRef<void(ReturnedSuggestions)> callback);
+
+ private:
+  // Used to change the emails matching the GAIA email in suggestions with
+  // the `plus_address_email_override_`.
+  // TODO(crbug.com/409962888): `plus_address_email_override_` has to be removed
+  // once the plus address suggestion generator and suggestions merging are
+  // implemented.
+  const std::optional<std::string> plus_address_email_override_;
+  const raw_ref<const AutofillClient> client_;
+
+  // Used to obtain field filling skip reasons.
+  base::WeakPtr<FormFiller> form_filler_;
+
+  raw_ptr<LogManager> log_manager_;
+
+  // Stores a set of types of fillable fields that are in the form.
+  FieldTypeSet field_types_;
+};
+
 }  // namespace autofill
 
 #endif  // COMPONENTS_AUTOFILL_CORE_BROWSER_SUGGESTIONS_ADDRESSES_ADDRESS_SUGGESTION_GENERATOR_H_
diff --git a/components/autofill/core/browser/suggestions/addresses/address_suggestion_generator_unittest.cc b/components/autofill/core/browser/suggestions/addresses/address_suggestion_generator_unittest.cc
index e3ad32f47..42a96748 100644
--- a/components/autofill/core/browser/suggestions/addresses/address_suggestion_generator_unittest.cc
+++ b/components/autofill/core/browser/suggestions/addresses/address_suggestion_generator_unittest.cc
@@ -4,6 +4,7 @@
 
 #include "components/autofill/core/browser/suggestions/addresses/address_suggestion_generator.h"
 
+#include <memory>
 #include <optional>
 #include <string>
 #include <vector>
@@ -12,6 +13,7 @@
 #include "base/strings/stringprintf.h"
 #include "base/strings/utf_string_conversions.h"
 #include "base/test/metrics/histogram_tester.h"
+#include "base/test/mock_callback.h"
 #include "base/test/scoped_feature_list.h"
 #include "base/test/task_environment.h"
 #include "base/time/time.h"
@@ -22,6 +24,7 @@
 #include "components/autofill/core/browser/data_model/addresses/autofill_profile.h"
 #include "components/autofill/core/browser/data_model/addresses/autofill_profile_test_api.h"
 #include "components/autofill/core/browser/field_types.h"
+#include "components/autofill/core/browser/form_structure_test_api.h"
 #include "components/autofill/core/browser/foundations/test_autofill_client.h"
 #include "components/autofill/core/browser/geo/phone_number_i18n.h"
 #include "components/autofill/core/browser/suggestions/suggestion.h"
@@ -32,6 +35,7 @@
 #include "components/autofill/core/common/autofill_clock.h"
 #include "components/autofill/core/common/autofill_constants.h"
 #include "components/autofill/core/common/autofill_features.h"
+#include "components/autofill/core/common/form_data_test_api.h"
 #include "components/autofill/core/common/form_field_data.h"
 #include "components/feature_engagement/public/feature_constants.h"
 #include "components/strings/grit/components_strings.h"
@@ -117,6 +121,46 @@
   const std::string& app_locale() { return address_data().app_locale(); }
 
   TestAutofillClient* autofill_client() { return &autofill_client_; }
+  AutofillField& field() { return *form_structure_->fields().front(); }
+
+  std::vector<Suggestion> GetSuggestionsForProfiles(
+      const FormFieldData& field_data,
+      FieldType field_type) {
+    // Preparing the test form and field.
+    FormData form_data;
+    test_api(form_data).Append(field_data);
+    form_structure_ = std::make_unique<FormStructure>(form_data);
+    test_api(*form_structure_).SetFieldTypes({field_type});
+
+    std::vector<Suggestion> suggestions;
+    AddressSuggestionGenerator address_suggestion_generator(
+        autofill_client_, /*plus_address_email_override=*/std::nullopt,
+        /*form_filler=*/nullptr, /*log_manager=*/nullptr);
+
+    auto on_suggestions_generated =
+        [&suggestions](
+            SuggestionGenerator::ReturnedSuggestions returned_suggestions) {
+          suggestions = std::move(returned_suggestions.second);
+        };
+
+    auto on_suggestion_data_returned =
+        [this, &on_suggestions_generated, &form_data, &field_data,
+         &address_suggestion_generator](
+            std::pair<FillingProduct,
+                      std::vector<SuggestionGenerator::SuggestionData>>
+                suggestion_data) {
+          address_suggestion_generator.GenerateSuggestions(
+              form_data, field_data, form_structure_.get(), &field(),
+              {std::move(suggestion_data)}, on_suggestions_generated);
+        };
+
+    // Since the `on_suggestions_generated` callback is called synchronously,
+    // we can assume that `suggestions` will hold correct value.
+    address_suggestion_generator.FetchSuggestionData(
+        form_data, field_data, form_structure_.get(), &field(),
+        autofill_client_, on_suggestion_data_returned);
+    return suggestions;
+  }
 
  private:
   base::test::TaskEnvironment task_environment_{
@@ -124,6 +168,7 @@
   test::AutofillUnitTestEnvironment autofill_test_environment_;
   TestAutofillClient autofill_client_;
   syncer::TestSyncService sync_service_;
+  std::unique_ptr<FormStructure> form_structure_;
 };
 
 // Tests that `SuggestionType::AddressEntryOnTyping` suggestions are returned
@@ -770,9 +815,8 @@
                    .starts_with(profile1.GetRawInfo(NAME_FIRST)));
 
   // Expect that suggestions are prefix matched.
-  std::vector<Suggestion> address_suggestions = GetSuggestionsForProfiles(
-      *autofill_client(), {NAME_FIRST}, triggering_field, NAME_FIRST,
-      SuggestionType::kAddressEntry, std::nullopt);
+  std::vector<Suggestion> address_suggestions =
+      GetSuggestionsForProfiles(triggering_field, NAME_FIRST);
   EXPECT_EQ(address_suggestions.size(), 3ul);
   EXPECT_THAT(address_suggestions, ContainsAddressFooterSuggestions());
 }
@@ -798,9 +842,7 @@
   // Expect that only the second address yields a suggestion because the first
   // one would be removed for exactly matching the field's content.
   EXPECT_THAT(
-      GetSuggestionsForProfiles(
-          *autofill_client(), {NAME_FULL}, triggering_field, NAME_FULL,
-          SuggestionType::kAddressFieldByFieldFilling, std::nullopt),
+      GetSuggestionsForProfiles(triggering_field, NAME_FULL),
       ElementsAre(EqualsSuggestion(SuggestionType::kAddressFieldByFieldFilling,
                                    profile2.GetRawInfo(NAME_FULL)),
                   EqualsSuggestion(SuggestionType::kSeparator),
@@ -815,9 +857,7 @@
   // otherwise there would be no address suggestions at all and we would not
   // show the popup, making the user unable to use the footer suggestions.
   EXPECT_THAT(
-      GetSuggestionsForProfiles(
-          *autofill_client(), {NAME_FULL}, triggering_field, NAME_FULL,
-          SuggestionType::kAddressFieldByFieldFilling, std::nullopt),
+      GetSuggestionsForProfiles(triggering_field, NAME_FULL),
       ElementsAre(EqualsSuggestion(SuggestionType::kAddressFieldByFieldFilling,
                                    profile1.GetRawInfo(NAME_FULL)),
                   EqualsSuggestion(SuggestionType::kSeparator),
@@ -848,9 +888,7 @@
   // one would be removed for exactly matching the field's content, even though
   // the two values are equal up to normalization.
   EXPECT_THAT(
-      GetSuggestionsForProfiles(
-          *autofill_client(), {NAME_FULL}, triggering_field, NAME_FULL,
-          SuggestionType::kAddressFieldByFieldFilling, std::nullopt),
+      GetSuggestionsForProfiles(triggering_field, NAME_FULL),
       ElementsAre(EqualsSuggestion(SuggestionType::kAddressFieldByFieldFilling,
                                    u"Tést Name"),
                   EqualsSuggestion(SuggestionType::kSeparator),
@@ -967,14 +1005,14 @@
   address_data().AddProfile(test::GetFullProfile());
   FormFieldData field;
   field.set_is_autofilled(true);
-  std::vector<Suggestion> suggestions = GetSuggestionsForProfiles(
-      *autofill_client(), {NAME_FIRST}, field, NAME_FIRST,
-      SuggestionType::kAddressEntry, std::nullopt);
-  EXPECT_THAT(suggestions,
-              ElementsAre(EqualsSuggestion(SuggestionType::kAddressEntry),
-                          EqualsSuggestion(SuggestionType::kSeparator),
-                          EqualsUndoAutofillSuggestion(),
-                          EqualsManageAddressesSuggestion()));
+  std::vector<Suggestion> suggestions =
+      GetSuggestionsForProfiles(field, NAME_FIRST);
+  EXPECT_THAT(
+      suggestions,
+      ElementsAre(EqualsSuggestion(SuggestionType::kAddressFieldByFieldFilling),
+                  EqualsSuggestion(SuggestionType::kSeparator),
+                  EqualsUndoAutofillSuggestion(),
+                  EqualsManageAddressesSuggestion()));
 }
 #endif
 
@@ -982,9 +1020,8 @@
        TestAddressSuggestion_AddressField_ReturnSuggestion) {
   AutofillProfile profile = test::GetFullProfile();
   autofill_client()->set_test_addresses({profile});
-  std::vector<Suggestion> suggestions = GetSuggestionsForProfiles(
-      *autofill_client(), /*field_types=*/{NAME_FIRST}, FormFieldData(),
-      NAME_FIRST, SuggestionType::kAddressEntry, std::nullopt);
+  std::vector<Suggestion> suggestions =
+      GetSuggestionsForProfiles(FormFieldData(), NAME_FIRST);
 
   // There should be one `SuggestionType::kDevtoolsTestAddresses`, one
   // `SuggestionType::kSeparator` and one `SuggestionType::kManageAddress`.
@@ -1221,5 +1258,52 @@
 }
 #endif  // !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS)
 
+TEST_F(AddressSuggestionGeneratorTest, GeneratesSuggestions) {
+  base::MockCallback<base::OnceCallback<void(
+      std::pair<FillingProduct,
+                std::vector<SuggestionGenerator::SuggestionData>>)>>
+      suggestion_data_callback;
+  base::MockCallback<
+      base::OnceCallback<void(SuggestionGenerator::ReturnedSuggestions)>>
+      suggestions_generated_callback;
+
+  AutofillProfile profile1 = test::GetFullProfile();
+  address_data().AddProfile(profile1);
+
+  // Create a form with one field, that expects a full name.
+  FormFieldData field;
+  FormData form_data;
+  test_api(form_data).Append(field);
+  std::unique_ptr<FormStructure> form_structure =
+      std::make_unique<FormStructure>(form_data);
+  test_api(*form_structure).SetFieldTypes({NAME_FULL});
+
+  AddressSuggestionGenerator generator(
+      *autofill_client(), /*plus_address_email_override=*/std::nullopt,
+      /*form_filler=*/nullptr, /*log_manager=*/nullptr);
+  std::pair<FillingProduct, std::vector<SuggestionGenerator::SuggestionData>>
+      savedCallbackArgument;
+
+  EXPECT_CALL(suggestion_data_callback,
+              Run(testing::Pair(FillingProduct::kAddress,
+                                testing::ElementsAre(profile1))))
+      .WillOnce(testing::SaveArg<0>(&savedCallbackArgument));
+  generator.FetchSuggestionData(form_data, field, form_structure.get(),
+                                form_structure->field(0), *autofill_client(),
+                                suggestion_data_callback.Get());
+
+  EXPECT_CALL(
+      suggestions_generated_callback,
+      Run(testing::Pair(
+          FillingProduct::kAddress,
+          testing::ElementsAre(
+              EqualsSuggestion(SuggestionType::kAddressEntry, u"John H. Doe"),
+              EqualsSuggestion(SuggestionType::kSeparator),
+              EqualsSuggestion(SuggestionType::kManageAddress)))));
+  generator.GenerateSuggestions(
+      form_data, field, form_structure.get(), form_structure->field(0),
+      {savedCallbackArgument}, suggestions_generated_callback.Get());
+}
+
 }  // namespace
 }  // namespace autofill
diff --git a/components/browser_ui/widget/android/java/src/org/chromium/components/browser_ui/widget/ClipDrawableProgressBar.java b/components/browser_ui/widget/android/java/src/org/chromium/components/browser_ui/widget/ClipDrawableProgressBar.java
index f54c5b1..16bfac9 100644
--- a/components/browser_ui/widget/android/java/src/org/chromium/components/browser_ui/widget/ClipDrawableProgressBar.java
+++ b/components/browser_ui/widget/android/java/src/org/chromium/components/browser_ui/widget/ClipDrawableProgressBar.java
@@ -260,7 +260,7 @@
      */
     public void getDrawingInfo(DrawingInfo drawingInfoOut) {
         boolean visible = getVisibility() == VISIBLE;
-        if (ChromeFeatureList.sAndroidAnimatedProgressBarInViz.isEnabled()) {
+        if (shouldAnimateCompositedLayer()) {
             visible = mDesiredVisibility == VISIBLE;
             drawingInfoOut.visible = visible;
         }
@@ -339,7 +339,7 @@
         int newVisibility = mDesiredVisibility;
         if (getAlpha() == 0 && mDesiredVisibility == VISIBLE) newVisibility = INVISIBLE;
         if (oldVisibility != newVisibility) {
-            if (!ChromeFeatureList.sAndroidAnimatedProgressBarInViz.isEnabled()) {
+            if (!shouldAnimateCompositedLayer()) {
                 super.setVisibility(newVisibility);
             }
             if (mProgressBarObserver != null) mProgressBarObserver.onVisibilityChanged();
@@ -418,7 +418,7 @@
 
     @Override
     protected boolean onSetAlpha(int alpha) {
-        if (ChromeFeatureList.sAndroidAnimatedProgressBarInViz.isEnabled()) {
+        if (shouldAnimateCompositedLayer()) {
             if (alpha == 0) {
                 mDesiredVisibility = INVISIBLE;
             } else {
@@ -428,4 +428,9 @@
         updateInternalVisibility();
         return super.onSetAlpha(alpha);
     }
+
+    private boolean shouldAnimateCompositedLayer() {
+        return ChromeFeatureList.sAndroidAnimatedProgressBarInViz.isEnabled()
+                || ChromeFeatureList.sAndroidAnimatedProgressBarInBrowser.isEnabled();
+    }
 }
diff --git a/components/content_settings/core/common/features.cc b/components/content_settings/core/common/features.cc
index 1f9115a..3ddbb3e 100644
--- a/components/content_settings/core/common/features.cc
+++ b/components/content_settings/core/common/features.cc
@@ -105,6 +105,10 @@
              "NativeUnpartitionedStoragePermittedWhen3PCOff",
              base::FEATURE_DISABLED_BY_DEFAULT);
 
+BASE_FEATURE(kBlockV8OptimizerOnUnfamiliarSitesSetting,
+             "BlockV8OptimizerOnUnfamiliarSitesSetting",
+             base::FEATURE_DISABLED_BY_DEFAULT);
+
 const char kTpcdReadHeuristicsGrantsName[] = "TpcdReadHeuristicsGrants";
 
 const char kTpcdWriteRedirectHeuristicGrantsName[] =
diff --git a/components/content_settings/core/common/features.h b/components/content_settings/core/common/features.h
index 0f57dee2..1cd3b34 100644
--- a/components/content_settings/core/common/features.h
+++ b/components/content_settings/core/common/features.h
@@ -123,6 +123,11 @@
 COMPONENT_EXPORT(CONTENT_SETTINGS_FEATURES)
 BASE_DECLARE_FEATURE(kNativeUnpartitionedStoragePermittedWhen3PCOff);
 
+// Shows the option to disable the v8 optimizer for unfamiliar sites on the
+// site settings page.
+COMPONENT_EXPORT(CONTENT_SETTINGS_FEATURES)
+BASE_DECLARE_FEATURE(kBlockV8OptimizerOnUnfamiliarSitesSetting);
+
 ////////////////////////////////////////////////////////////
 // Start of third-party cookie access heuristics features //
 ////////////////////////////////////////////////////////////
diff --git a/components/input/input_router_impl.cc b/components/input/input_router_impl.cc
index 3da5728..f6b06457 100644
--- a/components/input/input_router_impl.cc
+++ b/components/input/input_router_impl.cc
@@ -253,7 +253,8 @@
       // then no scrolling really ever occurs (even though we still send
       // GestureScrollBegin).
       touch_scroll_started_sent_ = true;
-      touch_event_queue_.PrependTouchScrollNotification();
+      touch_event_queue_.PrependTouchScrollNotification(
+          gesture_event.event.primary_unique_touch_event_id);
     }
   }
 
diff --git a/components/input/passthrough_touch_event_queue.cc b/components/input/passthrough_touch_event_queue.cc
index 2c34662..713b20d 100644
--- a/components/input/passthrough_touch_event_queue.cc
+++ b/components/input/passthrough_touch_event_queue.cc
@@ -9,11 +9,13 @@
 #include <utility>
 
 #include "base/auto_reset.h"
+#include "base/feature_list.h"
 #include "base/metrics/field_trial_params.h"
 #include "base/trace_event/trace_event.h"
 #include "base/trace_event/typed_macros.h"
 #include "components/input/touch_timeout_handler.h"
 #include "components/input/web_touch_event_traits.h"
+#include "third_party/blink/public/common/features.h"
 #include "ui/events/base_event_utils.h"
 #include "ui/gfx/geometry/point_f.h"
 
@@ -117,10 +119,17 @@
   SendTouchEventImmediately(&cloned_event, true, dispatch_callback);
 }
 
-void PassthroughTouchEventQueue::PrependTouchScrollNotification() {
+void PassthroughTouchEventQueue::PrependTouchScrollNotification(
+    uint32_t primary_unique_touch_event_id) {
   TRACE_EVENT0("input",
                "PassthroughTouchEventQueue::PrependTouchScrollNotification");
 
+  if (base::FeatureList::IsEnabled(
+          blink::features::kAsyncTouchMovesImmediatelyAfterScroll)) {
+    send_touch_events_async_ = true;
+    SetAckStateForPendingTouchMovesFromSequence(primary_unique_touch_event_id);
+  }
+
   TouchEventWithLatencyInfo touch(
       WebInputEvent::Type::kTouchScrollStarted, WebInputEvent::kNoModifiers,
       ui::EventTimeForNow(), LatencyInfo());
@@ -133,6 +142,35 @@
   }
 }
 
+void PassthroughTouchEventQueue::SetAckStateForPendingTouchMovesFromSequence(
+    uint32_t primary_unique_touch_event_id) {
+  if (curr_sequence_down_event_id_ != primary_unique_touch_event_id) {
+    // The touch sequence that started scroll has ended.
+    return;
+  }
+
+  CHECK(processing_acks_, base::NotFatalUntil::M143);
+
+  // Ack all outstanding touches in the current sequence to unblock the
+  // browser.
+  for (auto& it : outstanding_touches_) {
+    if (it.event.GetType() != WebInputEvent::Type::kTouchMove) {
+      break;
+    }
+
+    auto& outstanding_touch =
+        const_cast<TouchEventWithLatencyInfoAndAckState&>(it);
+    if (it.ack_state() == blink::mojom::InputEventResultState::kUnknown) {
+      // Based on the CHECK above we are already in middle of processing acks,
+      // we can just set them ignored here and the existing loop can process
+      // acks for these events as well.
+      outstanding_touch.set_ack_info(
+          blink::mojom::InputEventResultSource::kBrowser,
+          blink::mojom::InputEventResultState::kIgnored);
+    }
+  }
+}
+
 void PassthroughTouchEventQueue::ProcessTouchAck(
     blink::mojom::InputEventResultSource ack_source,
     blink::mojom::InputEventResultState ack_result,
@@ -305,6 +343,12 @@
       last_sent_touchevent_ = std::make_unique<WebTouchEvent>(touch->event);
   }
 
+  if (touch->event.IsTouchSequenceStart()) {
+    curr_sequence_down_event_id_ = touch->event.unique_touch_event_id;
+  } else if (touch->event.IsTouchSequenceEnd()) {
+    curr_sequence_down_event_id_.reset();
+  }
+
   if (timeout_handler_)
     timeout_handler_->StartIfNecessary(*touch);
   touch->event.GetModifiableEventLatencyMetadata().dispatched_to_renderer =
diff --git a/components/input/passthrough_touch_event_queue.h b/components/input/passthrough_touch_event_queue.h
index ccd3cd39..1337d564 100644
--- a/components/input/passthrough_touch_event_queue.h
+++ b/components/input/passthrough_touch_event_queue.h
@@ -5,6 +5,7 @@
 #ifndef COMPONENTS_INPUT_PASSTHROUGH_TOUCH_EVENT_QUEUE_H_
 #define COMPONENTS_INPUT_PASSTHROUGH_TOUCH_EVENT_QUEUE_H_
 
+#include <optional>
 #include <set>
 #include <string>
 
@@ -106,7 +107,7 @@
   void QueueEvent(const TouchEventWithLatencyInfo& event,
                   DispatchToRendererCallback& dispatch_callback);
 
-  void PrependTouchScrollNotification();
+  void PrependTouchScrollNotification(uint32_t primary_unique_touch_event_id);
 
   void ProcessTouchAck(blink::mojom::InputEventResultSource ack_source,
                        blink::mojom::InputEventResultState ack_result,
@@ -229,6 +230,9 @@
   PreFilterResult FilterBeforeForwardingImpl(const blink::WebTouchEvent& event);
   bool ShouldFilterForEvent(const blink::WebTouchEvent& event);
 
+  void SetAckStateForPendingTouchMovesFromSequence(
+      uint32_t primary_unique_touch_event_id);
+
   void AckTouchEventToClient(
       const TouchEventWithLatencyInfo& acked_event,
       blink::mojom::InputEventResultSource ack_source,
@@ -278,6 +282,8 @@
            TouchEventWithLatencyInfoAndAckStateComparator>
       outstanding_touches_;
 
+  std::optional<uint32_t> curr_sequence_down_event_id_ = std::nullopt;
+
   // Whether we should allow events to bypass normal queue filter rules.
   const bool skip_touch_filter_;
   // What events types are allowed to bypass the filter.
diff --git a/components/input/passthrough_touch_event_queue_unittest.cc b/components/input/passthrough_touch_event_queue_unittest.cc
index 929024f8..b3c19ab 100644
--- a/components/input/passthrough_touch_event_queue_unittest.cc
+++ b/components/input/passthrough_touch_event_queue_unittest.cc
@@ -90,6 +90,13 @@
     }
     last_acked_event_ = event.event;
     last_acked_event_state_ = ack_result;
+
+    if (will_start_scrolling_on_touch_move_ack_) {
+      DCHECK_NE(event_id_for_scroll_, -1);
+      PrependTouchScrollNotification(event_id_for_scroll_);
+      will_start_scrolling_on_touch_move_ack_ = false;
+      event_id_for_scroll_ = -1;
+    }
   }
 
   void OnFilteringTouchEvent(const blink::WebTouchEvent& touch_event) override {
@@ -189,6 +196,11 @@
     followup_touch_event_ = std::make_unique<WebTouchEvent>(event);
   }
 
+  void SetWillStartScrollingOnTouchMoveAck(int primary_unique_touch_event_id) {
+    event_id_for_scroll_ = primary_unique_touch_event_id;
+    will_start_scrolling_on_touch_move_ack_ = true;
+  }
+
   void SetSyncAckResult(blink::mojom::InputEventResultState sync_ack_result) {
     sync_ack_result_ =
         std::make_unique<blink::mojom::InputEventResultState>(sync_ack_result);
@@ -262,8 +274,8 @@
     SendTouchEvent();
   }
 
-  void PrependTouchScrollNotification() {
-    queue_->PrependTouchScrollNotification();
+  void PrependTouchScrollNotification(int primary_unique_touch_event_id = 0) {
+    queue_->PrependTouchScrollNotification(primary_unique_touch_event_id);
   }
 
   void AdvanceTouchTime(double seconds) {
@@ -353,6 +365,8 @@
   std::vector<WebTouchEvent> sent_events_;
   blink::mojom::InputEventResultState last_acked_event_state_;
   SyntheticWebTouchEvent touch_event_;
+  int event_id_for_scroll_ = -1;
+  bool will_start_scrolling_on_touch_move_ack_ = false;
   std::unique_ptr<WebTouchEvent> followup_touch_event_;
   std::unique_ptr<blink::mojom::InputEventResultState> sync_ack_result_;
   double slop_length_dips_;
@@ -1990,4 +2004,71 @@
             FilterBeforeForwarding(event));
 }
 
+// Testing sequence: TouchDown1, TouchMove1, TouchMove2, TouchMove1Ack.
+// Test that the TouchMove2 is immediately ack'd, when TouchMove1Ack starts a
+// scroll.
+TEST_F(PassthroughTouchEventQueueTest,
+       TouchMoveBetweenScrollStartingTouchMoveAndAckSentAsync) {
+  base::test::ScopedFeatureList scoped_feature_list;
+  scoped_feature_list.InitAndEnableFeature(
+      blink::features::kAsyncTouchMovesImmediatelyAfterScroll);
+
+  // A touch sequence that will turn into a scroll.
+  PressTouchPoint(0, 1);
+  int primary_unique_touch_event_id = GetUniqueTouchEventID();
+  SendTouchEventAck(blink::mojom::InputEventResultState::kNotConsumed);
+  EXPECT_EQ(1U, GetAndResetSentEventCount());
+  EXPECT_EQ(1U, GetAndResetAckedEventCount());
+  EXPECT_EQ(0U, queued_event_count());
+
+  // Send two touch moves. Both will be sent to the renderer.
+  MoveTouchPoint(0, 20, 5);
+  EXPECT_EQ(1U, GetAndResetSentEventCount());
+  EXPECT_EQ(1U, queued_event_count());
+
+  MoveTouchPoint(0, 40, 10);
+  EXPECT_EQ(1U, GetAndResetSentEventCount());
+  EXPECT_EQ(2U, queued_event_count());
+
+  // A scroll gesture is generated from the first touch move. The compositor
+  // consumes the gesture.
+  SetWillStartScrollingOnTouchMoveAck(primary_unique_touch_event_id);
+  SendTouchEventAck(blink::mojom::InputEventResultState::kNotConsumed);
+
+  // The second touch move should be acked immediately as ignored.
+  EXPECT_EQ(2U, GetAndResetAckedEventCount());
+}
+
+// Testing sequence: TouchDown1, TouchMove1, TouchMove1Ack, TouchMove2, GSUAck.
+// Test that the TouchMove2 is sent async.
+TEST_F(PassthroughTouchEventQueueTest, TouchMoveGSUAckSentAsync) {
+  base::test::ScopedFeatureList scoped_feature_list;
+  scoped_feature_list.InitAndEnableFeature(
+      blink::features::kAsyncTouchMovesImmediatelyAfterScroll);
+
+  // A touch sequence that will turn into a scroll.
+  PressTouchPoint(0, 1);
+  int primary_unique_touch_event_id = GetUniqueTouchEventID();
+  SendTouchEventAck(blink::mojom::InputEventResultState::kNotConsumed);
+  EXPECT_EQ(1U, GetAndResetSentEventCount());
+  EXPECT_EQ(1U, GetAndResetAckedEventCount());
+  EXPECT_EQ(0U, queued_event_count());
+
+  // Send two touch moves. Both will be sent to the renderer.
+  MoveTouchPoint(0, 20, 5);
+  EXPECT_EQ(1U, GetAndResetSentEventCount());
+  EXPECT_EQ(1U, queued_event_count());
+
+  // A scroll gesture is generated from the first touch move. The compositor
+  // consumes the gesture.
+  SetWillStartScrollingOnTouchMoveAck(primary_unique_touch_event_id);
+  SendTouchEventAck(blink::mojom::InputEventResultState::kNotConsumed);
+
+  MoveTouchPoint(0, 40, 10);
+  EXPECT_EQ(sent_event().dispatch_type,
+            WebInputEvent::DispatchType::kEventNonBlocking);
+  EXPECT_EQ(sent_event().GetType(), WebInputEvent::Type::kTouchMove);
+  EXPECT_EQ(2U, GetAndResetSentEventCount());
+}
+
 }  // namespace input
diff --git a/components/input/render_input_router.mojom b/components/input/render_input_router.mojom
index d4563b9..81eed30 100644
--- a/components/input/render_input_router.mojom
+++ b/components/input/render_input_router.mojom
@@ -57,6 +57,7 @@
 
 // Implemented by Viz, an interface between Browser and Viz. It defines methods
 // used to communicate input event handling related state from Browser to Viz.
+[DirectReceiver]
 interface RenderInputRouterDelegate {
   // Send |TouchTransferState| from Browser upon a successful touch transfer.
   // Viz needs this to do input handling on it's side.
diff --git a/components/lens/BUILD.gn b/components/lens/BUILD.gn
index f71d963..350cb188 100644
--- a/components/lens/BUILD.gn
+++ b/components/lens/BUILD.gn
@@ -68,7 +68,6 @@
     ":features",
     ":mime_type",
   ]
-
   sources += [
     "lens_bitmap_processing.cc",
     "lens_bitmap_processing.h",
@@ -84,6 +83,7 @@
       "tab_contextualization_controller.h",
     ]
     public_deps += [ "//ui/base/unowned_user_data" ]
+    deps += [ "//content/public/browser" ]
   }
 
   if (!is_android) {
diff --git a/components/lens/DEPS b/components/lens/DEPS
index 0ec3383..12790b9 100644
--- a/components/lens/DEPS
+++ b/components/lens/DEPS
@@ -7,6 +7,7 @@
   "+components/tabs",
   "+components/variations",
   "+components/unified_consent",
+  "+content/public/browser",
   "+google_apis",
   "+net",
   "+services/metrics/public/cpp",
diff --git a/components/lens/tab_contextualization_controller.cc b/components/lens/tab_contextualization_controller.cc
index 97d401014..22b014de0 100644
--- a/components/lens/tab_contextualization_controller.cc
+++ b/components/lens/tab_contextualization_controller.cc
@@ -4,6 +4,7 @@
 
 #include "components/lens/tab_contextualization_controller.h"
 
+#include "content/public/browser/navigation_handle.h"
 #include "ui/base/unowned_user_data/scoped_unowned_user_data.h"
 
 namespace lens {
@@ -12,15 +13,51 @@
 
 TabContextualizationController::TabContextualizationController(
     tabs::TabInterface* tab)
-    : scoped_unowned_user_data_(tab->GetUnownedUserDataHost(), *this) {}
+    : content::WebContentsObserver(tab->GetContents()),
+      scoped_unowned_user_data_(tab->GetUnownedUserDataHost(), *this),
+      tab_(tab) {
+  tab_subscription_ = tab->RegisterWillDiscardContents(
+      base::BindRepeating(&TabContextualizationController::WillDiscardContents,
+                          base::Unretained(this)));
+}
 
 TabContextualizationController::~TabContextualizationController() = default;
 
+void TabContextualizationController::WillDiscardContents(
+    tabs::TabInterface* tab,
+    content::WebContents* old_contents,
+    content::WebContents* new_contents) {
+  Observe(new_contents);
+}
+
 TabContextualizationController* TabContextualizationController::From(
     tabs::TabInterface* tab) {
   return tab ? Get(tab->GetUnownedUserDataHost()) : nullptr;
 }
 
+void TabContextualizationController::PrimaryPageChanged(content::Page& page) {
+  is_page_context_eligible_ = false;
+}
+
+void TabContextualizationController::DidFinishNavigation(
+    content::NavigationHandle* navigation_handle) {
+  if (!navigation_handle->IsInPrimaryMainFrame() ||
+      !navigation_handle->HasCommitted() ||
+      navigation_handle->IsSameDocument()) {
+    return;
+  }
+
+  // Call the asynchronous eligibility check.
+  GetInitialPageContextEligibility(
+      base::BindOnce(&TabContextualizationController::OnEligibilityChecked,
+                     base::Unretained(this)));
+}
+
+void TabContextualizationController::OnEligibilityChecked(
+    bool is_page_context_eligible) {
+  is_page_context_eligible_ = is_page_context_eligible;
+}
+
 // TODO(crbug.com/439595898): Get contextual page content.
 void TabContextualizationController::GetPageContext(
     GetPageContextCallback callback) {}
@@ -29,9 +66,8 @@
 void TabContextualizationController::GetInitialPageContextEligibility(
     GetPageContextEligibilityCallback callback) {}
 
-// TODO(crbug.com/439597165): Check tab eligibility.
 bool TabContextualizationController::GetCurrentPageContextEligibility() {
-  return false;
+  return is_page_context_eligible_;
 }
 
 }  // namespace lens
diff --git a/components/lens/tab_contextualization_controller.h b/components/lens/tab_contextualization_controller.h
index 5313a36..29a5b3e 100644
--- a/components/lens/tab_contextualization_controller.h
+++ b/components/lens/tab_contextualization_controller.h
@@ -12,15 +12,16 @@
 #include "base/memory/raw_ptr.h"
 #include "components/lens/contextual_input.h"
 #include "components/tabs/public/tab_interface.h"
+#include "content/public/browser/web_contents_observer.h"
 #include "ui/base/unowned_user_data/scoped_unowned_user_data.h"
 
 namespace lens {
 
-class TabContextualizationController {
+class TabContextualizationController : public content::WebContentsObserver {
  public:
   TabContextualizationController();
   explicit TabContextualizationController(tabs::TabInterface* tab);
-  ~TabContextualizationController();
+  ~TabContextualizationController() override;
 
   DECLARE_USER_DATA(TabContextualizationController);
   static TabContextualizationController* From(tabs::TabInterface* tab);
@@ -50,8 +51,26 @@
   bool GetCurrentPageContextEligibility();
 
  private:
+  // content::WebContentsObserver:
+  void PrimaryPageChanged(content::Page& page) override;
+  void DidFinishNavigation(
+      content::NavigationHandle* navigation_handle) override;
+
+  // TabInterface::WillDiscardContentsCallback:
+  void WillDiscardContents(tabs::TabInterface* tab,
+                           content::WebContents* old_contents,
+                           content::WebContents* new_contents);
+
+  void OnEligibilityChecked(bool is_page_context_eligible);
+
   ::ui::ScopedUnownedUserData<TabContextualizationController>
       scoped_unowned_user_data_;
+
+  raw_ptr<tabs::TabInterface> tab_;
+
+  base::CallbackListSubscription tab_subscription_;
+
+  bool is_page_context_eligible_ = false;
 };
 
 }  // namespace lens
diff --git a/components/optimization_guide/content/browser/page_content_proto_provider.cc b/components/optimization_guide/content/browser/page_content_proto_provider.cc
index f228b5e..90dd6f1 100644
--- a/components/optimization_guide/content/browser/page_content_proto_provider.cc
+++ b/components/optimization_guide/content/browser/page_content_proto_provider.cc
@@ -15,15 +15,20 @@
 #include "components/optimization_guide/content/browser/media_transcript_provider.h"
 #include "components/optimization_guide/content/browser/page_content_proto_util.h"
 #include "components/optimization_guide/core/optimization_guide_features.h"
+#include "content/public/browser/global_routing_id.h"
 #include "content/public/browser/media_session.h"
 #include "content/public/browser/render_frame_host.h"
 #include "content/public/browser/web_contents.h"
 #include "mojo/public/cpp/bindings/callback_helpers.h"
 #include "mojo/public/cpp/bindings/remote.h"
+#include "net/base/schemeful_site.h"
 #include "services/metrics/public/cpp/metrics_utils.h"
 #include "services/metrics/public/cpp/ukm_builders.h"
 #include "services/metrics/public/cpp/ukm_recorder.h"
 #include "services/service_manager/public/cpp/interface_provider.h"
+#include "third_party/blink/public/mojom/content_extraction/ai_page_content.mojom-data-view.h"
+#include "third_party/blink/public/mojom/content_extraction/ai_page_content.mojom-forward.h"
+#include "third_party/blink/public/mojom/content_extraction/ai_page_content.mojom.h"
 
 namespace optimization_guide {
 
@@ -66,7 +71,7 @@
   // TODO(crbug.com/389737599): There's a bug with scheduling idle tasks in an
   // OOPIF with site isolation if there are no other main frames in the process.
   // See crbug.com/40785325.
-  auto new_options = blink::mojom::AIPageContentOptions::New(input);
+  auto new_options = input.Clone();
   new_options->on_critical_path = true;
   return new_options;
 }
@@ -422,6 +427,9 @@
   const auto* main_frame_rph =
       web_contents->GetPrimaryMainFrame()->GetProcess();
 
+  const url::Origin& top_level_origin =
+      web_contents->GetPrimaryMainFrame()->GetLastCommittedOrigin();
+
   web_contents->GetPrimaryMainFrame()->ForEachRenderFrameHost(
       [&](content::RenderFrameHost* rfh) {
         if (!rfh->IsRenderFrameLive()) {
@@ -429,12 +437,26 @@
         }
 
         auto* parent_frame = rfh->GetParentOrOuterDocument();
+        content::GlobalRenderFrameHostToken frame_token =
+            rfh->GetGlobalFrameToken();
+
+        const url::Origin& frame_origin = rfh->GetLastCommittedOrigin();
+        if (options->include_same_site_only &&
+            (!net::SchemefulSite::IsSameSite(top_level_origin, frame_origin) ||
+             rfh->IsFencedFrameRoot())) {
+          CHECK(page_content_map->find(frame_token) == page_content_map->end());
+          (*page_content_map)[frame_token] =
+              blink::mojom::RedactedFrameMetadata::New(
+                  blink::mojom::RedactedFrameMetadata_Reason::kCrossSite);
+          return;
+        }
 
         // Skip dispatching IPCs for non-local root frames. The local root
         // provides data for itself and all child local frames.
         const bool is_local_root =
             !parent_frame ||
             parent_frame->GetRenderWidgetHost() != rfh->GetRenderWidgetHost();
+
         if (!is_local_root) {
           return;
         }
@@ -452,9 +474,8 @@
         agent_ptr->GetAIPageContent(
             std::move(options_to_use),
             mojo::WrapCallbackWithDefaultInvokeIfNotRun(
-                base::BindOnce(&OnGotAIPageContentForFrame,
-                               rfh->GetGlobalFrameToken(), std::move(agent),
-                               page_content_map.get(),
+                base::BindOnce(&OnGotAIPageContentForFrame, frame_token,
+                               std::move(agent), page_content_map.get(),
                                concurrent.CreateClosure()),
                 nullptr));
       });
diff --git a/components/optimization_guide/content/browser/page_content_proto_provider_browsertest.cc b/components/optimization_guide/content/browser/page_content_proto_provider_browsertest.cc
index 32a49a9..1a9f5daa 100644
--- a/components/optimization_guide/content/browser/page_content_proto_provider_browsertest.cc
+++ b/components/optimization_guide/content/browser/page_content_proto_provider_browsertest.cc
@@ -10,6 +10,7 @@
 #include "components/network_session_configurator/common/network_switches.h"
 #include "components/optimization_guide/content/browser/mock_media_transcript_provider.h"
 #include "components/optimization_guide/core/optimization_guide_features.h"
+#include "components/optimization_guide/proto/features/common_quality_data.pb.h"
 #include "components/ukm/test_ukm_recorder.h"
 #include "content/public/browser/media_session.h"
 #include "content/public/browser/web_contents.h"
@@ -95,9 +96,11 @@
   return request;
 }
 
-blink::mojom::AIPageContentOptionsPtr GetActionableAIPageContentOptions() {
+blink::mojom::AIPageContentOptionsPtr GetActionableAIPageContentOptions(
+    bool include_same_site_only = false) {
   auto request = ActionableAIPageContentOptions(
       /*on_critical_path =*/true);
+  request->include_same_site_only = include_same_site_only;
   return request;
 }
 
@@ -905,6 +908,67 @@
             c_geometry.outer_bounding_box().x());
 }
 
+IN_PROC_BROWSER_TEST_P(PageContentProtoProviderBrowserTestMultiProcess,
+                       AIPageContentMultipleMixedCrossSiteFrames) {
+  LoadPage(https_server()->GetURL("a.com", "/iframe_mixed_cross_site.html"),
+           GetActionableAIPageContentOptions(/*include_same_site_only=*/true));
+
+  const auto& root_node = ActionableContentRootNode();
+
+  EXPECT_EQ(root_node.children_nodes().size(), 2);
+
+  const auto& same_site_frame = root_node.children_nodes()[0];
+  EXPECT_EQ(same_site_frame.content_attributes().attribute_type(),
+            optimization_guide::proto::CONTENT_ATTRIBUTE_IFRAME);
+  const auto& same_site_frame_data =
+      same_site_frame.content_attributes().iframe_data();
+  AssertValidOrigin(same_site_frame_data.frame_data().security_origin(),
+                    ChildFrameAt(web_contents()->GetPrimaryMainFrame(), 0)
+                        ->GetLastCommittedOrigin());
+  EXPECT_FALSE(same_site_frame_data.likely_ad_frame());
+
+  const auto& same_site_frame_root = ContentRootNodeForFrameActionableMode(
+      same_site_frame.children_nodes()[0]);
+  EXPECT_EQ(same_site_frame_root.children_nodes().size(), 1);
+  AssertIsTextNode(same_site_frame_root.children_nodes()[0],
+                   "This page has no title.\n\n");
+  const auto& same_site_geometry =
+      same_site_frame.content_attributes().geometry();
+  AssertRectsEqual(same_site_geometry.outer_bounding_box(),
+                   same_site_geometry.visible_bounding_box());
+
+  const auto& cross_site_frame = root_node.children_nodes()[1];
+  EXPECT_EQ(cross_site_frame.content_attributes().attribute_type(),
+            optimization_guide::proto::CONTENT_ATTRIBUTE_IFRAME);
+  const auto& cross_site_frame_data =
+      cross_site_frame.content_attributes().iframe_data();
+
+  // Ensure the frame data isn't populated and a redaction reason is included.
+  EXPECT_FALSE(cross_site_frame_data.likely_ad_frame());
+  EXPECT_FALSE(cross_site_frame_data.has_frame_data());
+  EXPECT_TRUE(cross_site_frame_data.has_redacted_frame_metadata());
+  EXPECT_EQ(cross_site_frame_data.redacted_frame_metadata().reason(),
+            optimization_guide::proto::IframeData_RedactedFrameMetadata::
+                REASON_CROSS_SITE);
+
+  // The cross-site frame itself should have no children.
+  EXPECT_EQ(cross_site_frame.children_nodes().size(), 0);
+
+  const auto& cross_site_frame_geometry =
+      cross_site_frame.content_attributes().geometry();
+  AssertRectsEqual(cross_site_frame_geometry.outer_bounding_box(),
+                   cross_site_frame_geometry.visible_bounding_box());
+
+  EXPECT_ALMOST_EQ(same_site_geometry.outer_bounding_box().width(),
+                   cross_site_frame_geometry.outer_bounding_box().width());
+  EXPECT_ALMOST_EQ(same_site_geometry.outer_bounding_box().height(),
+                   cross_site_frame_geometry.outer_bounding_box().height());
+  EXPECT_ALMOST_EQ(same_site_geometry.outer_bounding_box().y(),
+                   cross_site_frame_geometry.outer_bounding_box().y());
+  EXPECT_NE(same_site_geometry.outer_bounding_box().x(),
+            cross_site_frame_geometry.outer_bounding_box().x());
+}
+
 INSTANTIATE_TEST_SUITE_P(All,
                          PageContentProtoProviderBrowserTestMultiProcess,
                          testing::Bool());
diff --git a/components/optimization_guide/content/browser/page_content_proto_util.cc b/components/optimization_guide/content/browser/page_content_proto_util.cc
index 952a3d7..2f19bb754 100644
--- a/components/optimization_guide/content/browser/page_content_proto_util.cc
+++ b/components/optimization_guide/content/browser/page_content_proto_util.cc
@@ -6,6 +6,7 @@
 
 #include <optional>
 #include <string>
+#include <variant>
 #include <vector>
 
 #include "base/notreached.h"
@@ -15,6 +16,9 @@
 #include "components/optimization_guide/core/optimization_guide_proto_util.h"
 #include "components/optimization_guide/core/page_content_proto_serializer.h"
 #include "components/optimization_guide/proto/features/common_quality_data.pb.h"
+#include "third_party/abseil-cpp/absl/functional/overload.h"
+#include "third_party/blink/public/mojom/content_extraction/ai_page_content.mojom-data-view.h"
+#include "third_party/blink/public/mojom/content_extraction/ai_page_content.mojom-forward.h"
 #include "third_party/blink/public/mojom/content_extraction/ai_page_content.mojom.h"
 #include "third_party/blink/public/mojom/content_extraction/ai_page_content_metadata.mojom.h"
 #include "third_party/blink/public/mojom/forms/form_control_type.mojom-shared.h"
@@ -703,6 +707,37 @@
                    frame_token_set);
 }
 
+void ConvertRedactionReason(
+    const blink::mojom::RedactedFrameMetadata_Reason& mojom_reason,
+    optimization_guide::proto::IframeData::RedactedFrameMetadata*
+        proto_redacted_frame_metadata) {
+  switch (mojom_reason) {
+    case blink::mojom::RedactedFrameMetadata_Reason::kCrossSite:
+      proto_redacted_frame_metadata->set_reason(
+          optimization_guide::proto::IframeData::RedactedFrameMetadata::Reason::
+              IframeData_RedactedFrameMetadata_Reason_REASON_CROSS_SITE);
+      break;
+    case blink::mojom::RedactedFrameMetadata_Reason::kCrossOrigin:
+      proto_redacted_frame_metadata->set_reason(
+          optimization_guide::proto::IframeData::RedactedFrameMetadata::Reason::
+              IframeData_RedactedFrameMetadata_Reason_REASON_CROSS_ORIGIN);
+      break;
+  };
+}
+
+void ConvertRedactedIframeData(
+    const RenderFrameInfo& render_frame_info,
+    const blink::mojom::AIPageContentIframeData& mojom_iframe_data,
+    const blink::mojom::RedactedFrameMetadata& mojom_redacted_frame_metadata,
+    blink::mojom::PageMetadata& metadata,
+    FrameTokenSet& frame_token_set,
+    optimization_guide::proto::IframeData* proto_iframe_data) {
+  proto_iframe_data->set_likely_ad_frame(mojom_iframe_data.likely_ad_frame);
+
+  ConvertRedactionReason(mojom_redacted_frame_metadata.reason,
+                         proto_iframe_data->mutable_redacted_frame_metadata());
+}
+
 base::expected<void, std::string> ConvertNode(
     content::GlobalRenderFrameHostToken source_frame_token,
     const blink::mojom::AIPageContentNode& mojom_node,
@@ -735,7 +770,8 @@
       return base::unexpected("could not find render_frame_info for iframe");
     }
 
-    const blink::mojom::AIPageContentFrameData* frame_data = nullptr;
+    auto* proto_iframe_data =
+        proto_node->mutable_content_attributes()->mutable_iframe_data();
     if (frame_token.Is<blink::RemoteFrameToken>()) {
       // RemoteFrame should have no child nodes since the content is out of
       // process.
@@ -744,41 +780,75 @@
       }
 
       // The embedder shouldn't be providing LocalFrameData for remote frames.
-      if (iframe_data.local_frame_data) {
+      if (iframe_data.content) {
         return base::unexpected(
-            "embedder incorrectly provided local_frame_data for iframe");
+            "embedder incorrectly provided content for this iframe");
       }
 
       auto it = page_content_map.find(render_frame_info->global_frame_token);
       if (it == page_content_map.end()) {
+        // This may happen either because the remote renderer responsible for
+        // this frame was destroyed before we were able to query it, or because
+        // the supplied frame token was manipulated by a compromised renderer.
         return base::ok();
       }
 
-      const auto& frame_page_content = *it->second;
-      frame_data = frame_page_content.frame_data.get();
-      auto* proto_child_frame_node = proto_node->add_children_nodes();
+      return std::visit(
+          absl::Overload{
+              [&](const blink::mojom::AIPageContentPtr& page_content) mutable
+                  -> base::expected<void, std::string> {
+                auto* proto_child_frame_node = proto_node->add_children_nodes();
+                if (auto result =
+                        ConvertNode(render_frame_info->global_frame_token,
+                                    *page_content->root_node, page_content_map,
+                                    frame_token_set, get_render_frame_info,
+                                    metadata, proto_child_frame_node);
+                    !result.has_value()) {
+                  return result;
+                }
 
-      if (auto result = ConvertNode(
-              render_frame_info->global_frame_token,
-              *frame_page_content.root_node, page_content_map, frame_token_set,
-              get_render_frame_info, metadata, proto_child_frame_node);
-          !result.has_value()) {
-        return result;
-      }
-    } else {
-      if (!iframe_data.local_frame_data) {
-        return base::unexpected("local frame missing local_frame_data");
+                ConvertIframeData(
+                    *render_frame_info, iframe_data,
+                    /*mojom_local_frame_data=*/*page_content->frame_data.get(),
+                    metadata, frame_token_set, proto_iframe_data);
+                return base::ok();
+              },
+              [&](const blink::mojom::RedactedFrameMetadataPtr& r) mutable
+                  -> base::expected<void, std::string> {
+                ConvertRedactedIframeData(
+                    *render_frame_info, iframe_data,
+                    /*mojom_redacted_frame_metadata*/ *r.get(), metadata,
+                    frame_token_set, proto_iframe_data);
+                return base::ok();
+              }},
+          it->second);
+    } else /* this is a local frame */ {
+      if (!iframe_data.content) {
+        return base::unexpected(
+            "local frame missing local_frame_data or redacted_frame_metadata");
       }
 
-      frame_data = iframe_data.local_frame_data.get();
+      switch (iframe_data.content->which()) {
+        case blink::mojom::AIPageContentIframeContent::Tag::kLocalFrameData:
+          ConvertIframeData(*render_frame_info, iframe_data,
+                            /*mojom_local_frame_data=*/
+                            *iframe_data.content->get_local_frame_data(),
+                            metadata, frame_token_set, proto_iframe_data);
+          // Breaking instead of returning so we get to copy the child nodes.
+          break;
+        case blink::mojom::AIPageContentIframeContent::Tag::
+            kRedactedFrameMetadata:
+          ConvertRedactedIframeData(
+              *render_frame_info, iframe_data,
+              *iframe_data.content->get_redacted_frame_metadata().get(),
+              metadata, frame_token_set, proto_iframe_data);
+          return base::ok();
+      }
     }
-
-    auto* proto_iframe_data =
-        proto_node->mutable_content_attributes()->mutable_iframe_data();
-    ConvertIframeData(*render_frame_info, iframe_data, *frame_data, metadata,
-                      frame_token_set, proto_iframe_data);
   }
 
+  // We should only get here if this is either a non-redacted local frame or a
+  // regular node.
   const auto source_frame_for_children =
       render_frame_info ? render_frame_info->global_frame_token
                         : source_frame_token;
@@ -806,10 +876,25 @@
     optimization_guide::AIPageContentResult& page_content_result) {
   auto it = page_content_map.find(main_frame_token);
   if (it == page_content_map.end()) {
-    return base::unexpected("could not find AIPageContent for main frame");
+    return base::unexpected(
+        "could not find AIPageContent or RedactedFrameMetadata for main frame");
   }
 
-  const auto& main_frame_page_content = *it->second;
+  const blink::mojom::AIPageContent* main_frame_page_content = nullptr;
+  std::visit(absl::Overload{
+                 [&main_frame_page_content](
+                     const blink::mojom::AIPageContentPtr& p) mutable {
+                   main_frame_page_content = p.get();
+                 },
+                 [](const blink::mojom::RedactedFrameMetadataPtr& r) mutable {
+                   return;
+                 }},
+             it->second);
+
+  if (!main_frame_page_content) {
+    return base::unexpected(
+        "Main content frame was redacted; this should not happen.");
+  }
 
   auto render_frame_info = get_render_frame_info.Run(
       main_frame_token.child_id, main_frame_token.frame_token);
@@ -818,11 +903,11 @@
     return base::unexpected("could not find RenderFrameInfo for main frame");
   }
 
-  ConvertFrameData(*render_frame_info, *main_frame_page_content.frame_data,
+  ConvertFrameData(*render_frame_info, *main_frame_page_content->frame_data,
                    page_content_result.proto.mutable_main_frame_data(),
                    *page_content_result.metadata, frame_token_set);
   if (auto result =
-          ConvertNode(main_frame_token, *main_frame_page_content.root_node,
+          ConvertNode(main_frame_token, *main_frame_page_content->root_node,
                       page_content_map, frame_token_set, get_render_frame_info,
                       *page_content_result.metadata,
                       page_content_result.proto.mutable_root_node());
@@ -830,9 +915,9 @@
     return result;
   }
 
-  if (main_frame_page_content.page_interaction_info) {
+  if (main_frame_page_content->page_interaction_info) {
     ConvertPageInteractionInfo(
-        *main_frame_page_content.page_interaction_info,
+        *main_frame_page_content->page_interaction_info,
         page_content_result.proto.mutable_page_interaction_info());
   }
 
diff --git a/components/optimization_guide/content/browser/page_content_proto_util.h b/components/optimization_guide/content/browser/page_content_proto_util.h
index d52cec8..bd50369 100644
--- a/components/optimization_guide/content/browser/page_content_proto_util.h
+++ b/components/optimization_guide/content/browser/page_content_proto_util.h
@@ -37,12 +37,18 @@
   raw_ptr<const optimization_guide::proto::ContentNode> node = nullptr;
 };
 
-using AIPageContentMap = base::flat_map<content::GlobalRenderFrameHostToken,
-                                        blink::mojom::AIPageContentPtr>;
+using AIPageContentMap =
+    base::flat_map<content::GlobalRenderFrameHostToken,
+                   std::variant<blink::mojom::AIPageContentPtr,
+                                blink::mojom::RedactedFrameMetadataPtr>>;
 
 // A set of frame tokens that have been seen during conversion.
 using FrameTokenSet = base::flat_set<content::GlobalRenderFrameHostToken>;
 
+using FrameOrRedaction =
+    std::variant<const blink::mojom::AIPageContentFrameData*,
+                 const blink::mojom::RedactedFrameMetadata*>;
+
 // A callback to get the RenderFrameInfo for a given frame token.
 using GetRenderFrameInfo =
     base::RepeatingCallback<std::optional<RenderFrameInfo>(int child_process_id,
diff --git a/components/optimization_guide/content/browser/page_content_proto_util_unittest.cc b/components/optimization_guide/content/browser/page_content_proto_util_unittest.cc
index fc4aebe..0b286577 100644
--- a/components/optimization_guide/content/browser/page_content_proto_util_unittest.cc
+++ b/components/optimization_guide/content/browser/page_content_proto_util_unittest.cc
@@ -590,21 +590,21 @@
   auto iframe_data = blink::mojom::AIPageContentIframeData::New();
   iframe_data->frame_token = iframe_token.frame_token;
   iframe_data->likely_ad_frame = true;
-  iframe_data->local_frame_data = blink::mojom::AIPageContentFrameData::New();
-  iframe_data->local_frame_data->frame_interaction_info =
+  auto frame_data = blink::mojom::AIPageContentFrameData::New();
+  frame_data->frame_interaction_info =
       blink::mojom::AIPageContentFrameInteractionInfo::New();
-  iframe_data->local_frame_data->frame_interaction_info->selection =
+  frame_data->frame_interaction_info->selection =
       blink::mojom::AIPageContentSelection::New();
-  iframe_data->local_frame_data->frame_interaction_info->selection
-      ->selected_text = "selected text";
-  iframe_data->local_frame_data->frame_interaction_info->selection
-      ->start_dom_node_id = 1;
-  iframe_data->local_frame_data->frame_interaction_info->selection
-      ->end_dom_node_id = 2;
-  iframe_data->local_frame_data->frame_interaction_info->selection
-      ->start_offset = 3;
-  iframe_data->local_frame_data->frame_interaction_info->selection->end_offset =
-      4;
+  frame_data->frame_interaction_info->selection->selected_text =
+      "selected text";
+  frame_data->frame_interaction_info->selection->start_dom_node_id = 1;
+  frame_data->frame_interaction_info->selection->end_dom_node_id = 2;
+  frame_data->frame_interaction_info->selection->start_offset = 3;
+  frame_data->frame_interaction_info->selection->end_offset = 4;
+  iframe_data->content =
+      blink::mojom::AIPageContentIframeContent::NewLocalFrameData(
+          std::move(frame_data));
+
   root_content->root_node->children_nodes.back()
       ->content_attributes->iframe_data = std::move(iframe_data);
 
diff --git a/components/paint_preview/browser/paint_preview_base_service_unittest.cc b/components/paint_preview/browser/paint_preview_base_service_unittest.cc
index df666f98..ef7fe6d 100644
--- a/components/paint_preview/browser/paint_preview_base_service_unittest.cc
+++ b/components/paint_preview/browser/paint_preview_base_service_unittest.cc
@@ -198,7 +198,7 @@
   if (GetParam() == RecordingPersistence::kMemoryBuffer) {
     response->skp.emplace(mojo_base::BigBuffer());
   }
-  recorder.SetResponse(mojom::PaintPreviewStatus::kOk, std::move(response));
+  recorder.SetResponse(std::move(response));
   OverrideInterface(&recorder);
 
   auto* service = GetService();
@@ -265,10 +265,7 @@
   params->is_main_frame = true;
   params->max_capture_size = 0;
   recorder.SetExpectedParams(std::move(params));
-  auto response = mojom::PaintPreviewCaptureResponse::New();
-  response->geometry_metadata = mojom::GeometryMetadataResponse::New();
-  response->embedding_token = std::nullopt;
-  recorder.SetResponse(mojom::PaintPreviewStatus::kFailed, std::move(response));
+  recorder.SetResponse(base::unexpected(mojom::PaintPreviewStatus::kFailed));
   OverrideInterface(&recorder);
 
   auto* service = GetService();
diff --git a/components/paint_preview/browser/paint_preview_client.cc b/components/paint_preview/browser/paint_preview_client.cc
index de2cbb9..73c531c5 100644
--- a/components/paint_preview/browser/paint_preview_client.cc
+++ b/components/paint_preview/browser/paint_preview_client.cc
@@ -175,8 +175,9 @@
     base::File file) {
   if (!file.IsValid()) {
     DLOG(ERROR) << "File create failed: " << file.error_details();
-    std::move(callback).Run(std::move(capture_params),
-                            mojom::PaintPreviewStatus::kFileCreationError, {});
+    std::move(callback).Run(
+        std::move(capture_params),
+        base::unexpected(mojom::PaintPreviewStatus::kFileCreationError));
   } else if (callback.IsCancelled()) {
     // The weak pointer is invalid, we should close the file on a background
     // thread to avoid it being closed implicitly via the default dtor on the UI
@@ -187,8 +188,7 @@
   } else {
     mojom::PaintPreviewCaptureParamsPtr params = CreateRecordingRequestParams(
         RecordingPersistence::kFileSystem, capture_params, std::move(file));
-    std::move(callback).Run(std::move(capture_params),
-                            mojom::PaintPreviewStatus::kOk, std::move(params));
+    std::move(callback).Run(std::move(capture_params), std::move(params));
   }
 }
 
@@ -215,7 +215,7 @@
   return memory_stream.detachAsData();
 }
 
-std::pair<mojom::PaintPreviewStatus, mojom::PaintPreviewCaptureResponsePtr>
+base::expected<mojom::PaintPreviewCaptureResponsePtr, mojom::PaintPreviewStatus>
 SerializeRedactedFrameToFile(const gfx::Size& size,
                              std::optional<size_t> max_capture_size,
                              const base::FilePath& file_path,
@@ -225,16 +225,16 @@
 
   base::span<const uint8_t> bytes = skia::as_byte_span(*data);
   if (max_capture_size && data->size() > max_capture_size) {
-    return {mojom::PaintPreviewStatus::kFileCreationError, std::move(response)};
+    return base::unexpected(mojom::PaintPreviewStatus::kFileCreationError);
   }
 
   base::File file = CreateOrOverwriteFileForWriting(file_path);
 
   if (!file.IsValid() || !file.WriteAndCheck(/*offset=*/0, bytes)) {
-    return {mojom::PaintPreviewStatus::kFileCreationError, std::move(response)};
+    return base::unexpected(mojom::PaintPreviewStatus::kFileCreationError);
   }
 
-  return {mojom::PaintPreviewStatus::kOk, std::move(response)};
+  return std::move(response);
 }
 
 struct BufferAndMetadata {
@@ -266,18 +266,6 @@
                                           rfh->GetRoutingID());
 }
 
-// Converts a callback that accepts two args into a callback that accepts a
-// single std::pair arg.
-template <typename T, typename U>
-base::OnceCallback<void(std::pair<T, U>)> ConvertToPairArgCallback(
-    base::OnceCallback<void(T, U)> callback) {
-  return base::BindOnce(
-      [](base::OnceCallback<void(T, U)> callback, std::pair<T, U> pair) {
-        std::move(callback).Run(std::move(pair.first), std::move(pair.second));
-      },
-      std::move(callback));
-}
-
 }  // namespace
 
 PaintPreviewClient::PaintPreviewParams::PaintPreviewParams(
@@ -380,9 +368,7 @@
   } else {
     mojom::PaintPreviewCaptureParamsPtr params =
         CreateRecordingRequestParams(persistence, capture_params, {});
-    std::move(ready_callback)
-        .Run(std::move(capture_params), mojom::PaintPreviewStatus::kOk,
-             std::move(params));
+    std::move(ready_callback).Run(std::move(capture_params), std::move(params));
   }
 }
 
@@ -609,9 +595,10 @@
     const base::UnguessableToken& frame_guid,
     const content::GlobalRenderFrameHostId& render_frame_id,
     RecordingParams params,
-    base::OnceCallback<void(RecordingParams,
-                            mojom::PaintPreviewStatus,
-                            mojom::PaintPreviewCaptureResponsePtr)> callback,
+    base::OnceCallback<
+        void(RecordingParams,
+             base::expected<mojom::PaintPreviewCaptureResponsePtr,
+                            mojom::PaintPreviewStatus>)> callback,
     mojom::GeometryMetadataResponsePtr response) {
   auto* document_data =
       base::FindOrNull(all_document_data_, params.get_document_guid());
@@ -625,8 +612,9 @@
       render_frame_host->GetEmbeddingToken().value_or(
           base::UnguessableToken::Null()) != frame_guid ||
       !response) {
-    std::move(callback).Run(std::move(params),
-                            mojom::PaintPreviewStatus::kCaptureFailed, {});
+    std::move(callback).Run(
+        std::move(params),
+        base::unexpected(mojom::PaintPreviewStatus::kCaptureFailed));
     return;
   }
 
@@ -647,30 +635,31 @@
                          max_capture_size,
                          document_data->FilePathForFrame(frame_guid),
                          std::move(capture_response)),
-          ConvertToPairArgCallback(
-              base::BindOnce(std::move(callback), std::move(params))));
+          base::BindOnce(std::move(callback), std::move(params)));
       return;
     }
     case RecordingPersistence::kMemoryBuffer: {
-      mojom::PaintPreviewStatus capture_status =
-          mojom::PaintPreviewStatus::kCaptureFailed;
       std::optional<BufferAndMetadata> serialized =
           SerializeRedactedFrameToBuffer(
               params.clip_rect.size(),
               document_data->max_per_capture_size == 0
                   ? std::nullopt
                   : std::make_optional(document_data->max_per_capture_size));
-      if (serialized) {
-        capture_status = mojom::PaintPreviewStatus::kOk;
-        capture_response->skp.emplace(std::move(serialized->buffer));
-        capture_response->serialized_size = serialized->buffer_size;
+      if (!serialized) {
+        std::move(callback).Run(
+            std::move(params),
+            base::unexpected(mojom::PaintPreviewStatus::kCaptureFailed));
+        return;
       }
 
-      std::move(callback).Run(std::move(params), capture_status,
-                              std::move(capture_response));
+      capture_response->skp.emplace(std::move(serialized->buffer));
+      capture_response->serialized_size = serialized->buffer_size;
+
+      std::move(callback).Run(std::move(params), std::move(capture_response));
       return;
     }
   }
+  NOTREACHED();
 }
 
 void PaintPreviewClient::AwaitSubframeCapture(
@@ -765,8 +754,8 @@
     const base::UnguessableToken& frame_guid,
     const content::GlobalRenderFrameHostId& render_frame_id,
     RecordingParams params,
-    mojom::PaintPreviewStatus status,
-    mojom::PaintPreviewCaptureParamsPtr capture_params) {
+    base::expected<mojom::PaintPreviewCaptureParamsPtr,
+                   mojom::PaintPreviewStatus> capture_params) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
 
   auto* document_data =
@@ -776,9 +765,9 @@
   }
   CHECK(document_data->callback);
 
-  if (status != mojom::PaintPreviewStatus::kOk) {
+  if (!capture_params.has_value()) {
     std::move(document_data->callback)
-        .Run(params.get_document_guid(), status, {});
+        .Run(params.get_document_guid(), capture_params.error(), {});
     return;
   }
 
@@ -789,7 +778,7 @@
   if (!render_frame_host ||
       render_frame_host->GetEmbeddingToken().value_or(
           base::UnguessableToken::Null()) != frame_guid ||
-      !capture_params) {
+      !capture_params.value()) {
     std::move(document_data->callback)
         .Run(params.get_document_guid(),
              mojom::PaintPreviewStatus::kCaptureFailed, {});
@@ -800,12 +789,12 @@
 
   // For the main frame, apply a clip rect if one is provided.
   if (params.is_main_frame) {
-    capture_params->geometry_metadata_params->clip_rect_is_hint = false;
+    capture_params.value()->geometry_metadata_params->clip_rect_is_hint = false;
   }
 
   GetOrInsertRecorder(frame_guid, *render_frame_host)
       ->CapturePaintPreview(
-          std::move(capture_params),
+          std::move(capture_params).value(),
           base::BindOnce(&PaintPreviewClient::OnPaintPreviewCapturedCallback,
                          weak_ptr_factory_.GetWeakPtr(), frame_guid,
                          render_frame_id, std::move(params)));
@@ -834,8 +823,8 @@
     const base::UnguessableToken& frame_guid,
     const content::GlobalRenderFrameHostId& render_frame_id,
     RecordingParams params,
-    mojom::PaintPreviewStatus status,
-    mojom::PaintPreviewCaptureResponsePtr response) {
+    base::expected<mojom::PaintPreviewCaptureResponsePtr,
+                   mojom::PaintPreviewStatus> response) {
   auto* document_data =
       base::FindOrNull(all_document_data_, params.get_document_guid());
 
@@ -848,16 +837,16 @@
   auto* render_frame_host = content::RenderFrameHost::FromID(render_frame_id);
   if (!render_frame_host || render_frame_host->GetEmbeddingToken().value_or(
                                 base::UnguessableToken::Null()) != frame_guid) {
-    status = mojom::PaintPreviewStatus::kCaptureFailed;
+    response = base::unexpected(mojom::PaintPreviewStatus::kCaptureFailed);
   }
 
   if (!document_data) {
     return;
   }
 
-  if (status == mojom::PaintPreviewStatus::kOk) {
+  if (response.has_value()) {
     document_data->RecordSuccessfulFrame(frame_guid, params.is_main_frame,
-                                         std::move(response));
+                                         std::move(response).value());
   } else {
     document_data->had_error = true;
 
diff --git a/components/paint_preview/browser/paint_preview_client.h b/components/paint_preview/browser/paint_preview_client.h
index 4f56e46..6d2ee5a 100644
--- a/components/paint_preview/browser/paint_preview_client.h
+++ b/components/paint_preview/browser/paint_preview_client.h
@@ -42,10 +42,10 @@
                               mojom::PaintPreviewStatus,
                               std::unique_ptr<CaptureResult>)>;
 
-  using RecordingRequestParamsReadyCallback =
-      base::OnceCallback<void(RecordingParams,
-                              mojom::PaintPreviewStatus,
-                              mojom::PaintPreviewCaptureParamsPtr)>;
+  using RecordingRequestParamsReadyCallback = base::OnceCallback<void(
+      RecordingParams,
+      base::expected<mojom::PaintPreviewCaptureParamsPtr,
+                     mojom::PaintPreviewStatus>)>;
 
   // Augmented version of mojom::PaintPreviewServiceParams.
   struct PaintPreviewParams {
@@ -236,8 +236,8 @@
       const base::UnguessableToken& frame_guid,
       const content::GlobalRenderFrameHostId& render_frame_id,
       RecordingParams params,
-      mojom::PaintPreviewStatus status,
-      mojom::PaintPreviewCaptureParamsPtr capture_params);
+      base::expected<mojom::PaintPreviewCaptureParamsPtr,
+                     mojom::PaintPreviewStatus> capture_params);
 
   // Handles recording the frame and updating client state when capture is
   // complete.
@@ -245,8 +245,8 @@
       const base::UnguessableToken& frame_guid,
       const content::GlobalRenderFrameHostId& render_frame_id,
       RecordingParams params,
-      mojom::PaintPreviewStatus status,
-      mojom::PaintPreviewCaptureResponsePtr response);
+      base::expected<mojom::PaintPreviewCaptureResponsePtr,
+                     mojom::PaintPreviewStatus> response);
 
   // Marks a frame as having been processed, this should occur regardless of
   // whether the processed frame is valid as there is no retry.
@@ -267,14 +267,14 @@
 
   // Synthesizes a redacted subframe and persists it appropriately, then resumes
   // the capture.
-  void RedactSubframe(
-      const base::UnguessableToken& frame_guid,
-      const content::GlobalRenderFrameHostId& render_frame_id,
-      RecordingParams params,
-      base::OnceCallback<void(RecordingParams,
-                              mojom::PaintPreviewStatus,
-                              mojom::PaintPreviewCaptureResponsePtr)> callback,
-      mojom::GeometryMetadataResponsePtr response);
+  void RedactSubframe(const base::UnguessableToken& frame_guid,
+                      const content::GlobalRenderFrameHostId& render_frame_id,
+                      RecordingParams params,
+                      base::OnceCallback<void(
+                          RecordingParams,
+                          base::expected<mojom::PaintPreviewCaptureResponsePtr,
+                                         mojom::PaintPreviewStatus>)> callback,
+                      mojom::GeometryMetadataResponsePtr response);
 
   // Performs bookkeeping to keep track of the fact that this frame's capture is
   // still pending.
diff --git a/components/paint_preview/browser/paint_preview_client_unittest.cc b/components/paint_preview/browser/paint_preview_client_unittest.cc
index 4c5c93aa..796327ac 100644
--- a/components/paint_preview/browser/paint_preview_client_unittest.cc
+++ b/components/paint_preview/browser/paint_preview_client_unittest.cc
@@ -241,7 +241,7 @@
 
   MockPaintPreviewRecorder service;
   service.SetExpectedParams(ToMojoParams(params));
-  service.SetResponse(mojom::PaintPreviewStatus::kOk, std::move(response));
+  service.SetResponse(std::move(response));
   OverrideInterface(rfh, &service);
   PaintPreviewClient::CreateForWebContents(web_contents());
   auto* client = PaintPreviewClient::FromWebContents(web_contents());
@@ -290,13 +290,10 @@
   params.root_dir = temp_dir_.GetPath();
   params.inner.is_main_frame = true;
 
-  auto response = NewMockPaintPreviewCaptureResponse();
-  response->skp = {mojo_base::BigBuffer()};
-
   MockPaintPreviewRecorder recorder;
   recorder.SetExpectedParams(ToMojoParams(params));
-  recorder.SetResponse(mojom::PaintPreviewStatus::kCaptureFailed,
-                       std::move(response));
+  recorder.SetResponse(
+      base::unexpected(mojom::PaintPreviewStatus::kCaptureFailed));
   OverrideInterface(main_rfh(), &recorder);
   PaintPreviewClient::CreateForWebContents(web_contents());
   auto* client = PaintPreviewClient::FromWebContents(web_contents());
@@ -323,13 +320,10 @@
   params.root_dir = temp_dir_.GetPath();
   params.inner.is_main_frame = true;
 
-  auto response = mojom::PaintPreviewCaptureResponse::New();
-  response->geometry_metadata = mojom::GeometryMetadataResponse::New();
-
   MockPaintPreviewRecorder recorder;
   recorder.SetExpectedParams(ToMojoParams(params));
-  recorder.SetResponse(mojom::PaintPreviewStatus::kCaptureFailed,
-                       std::move(response));
+  recorder.SetResponse(
+      base::unexpected(mojom::PaintPreviewStatus::kCaptureFailed));
   OverrideInterface(main_rfh(), &recorder);
 
   PaintPreviewClient::CreateForWebContents(web_contents());
@@ -389,7 +383,7 @@
 
   MockPaintPreviewRecorder service;
   service.SetExpectedParams(ToMojoParams(params));
-  service.SetResponse(mojom::PaintPreviewStatus::kOk, std::move(response));
+  service.SetResponse(std::move(response));
   OverrideInterface(rfh, &service);
   PaintPreviewClient::CreateForWebContents(web_contents());
   auto* client = PaintPreviewClient::FromWebContents(web_contents());
@@ -479,8 +473,7 @@
 
   MockPaintPreviewRecorder main_frame_recorder;
   main_frame_recorder.SetExpectedParams(ToMojoParams(main_frame_params));
-  main_frame_recorder.SetResponse(mojom::PaintPreviewStatus::kOk,
-                                  std::move(main_frame_response));
+  main_frame_recorder.SetResponse(std::move(main_frame_response));
   base::test::TestFuture<void> main_frame_req;
   main_frame_recorder.SetReceivedRequestClosure(main_frame_req.GetCallback());
   OverrideInterface(rfh, &main_frame_recorder);
@@ -494,8 +487,7 @@
   expected_subframe_params.inner.clip_rect = subframe_rect;
   MockPaintPreviewRecorder subframe_recorder;
   subframe_recorder.SetExpectedParams(ToMojoParams(expected_subframe_params));
-  subframe_recorder.SetResponse(mojom::PaintPreviewStatus::kOk,
-                                std::move(subframe_response));
+  subframe_recorder.SetResponse(std::move(subframe_response));
   base::test::TestFuture<void> subframe_req;
   subframe_recorder.SetReceivedRequestClosure(subframe_req.GetCallback());
   OverrideInterface(subframe, &subframe_recorder);
@@ -572,8 +564,7 @@
 
   MockPaintPreviewRecorder main_frame_recorder;
   main_frame_recorder.SetExpectedParams(ToMojoParams(main_frame_params));
-  main_frame_recorder.SetResponse(mojom::PaintPreviewStatus::kOk,
-                                  std::move(main_frame_response));
+  main_frame_recorder.SetResponse(std::move(main_frame_response));
   base::test::TestFuture<void> main_frame_req;
   main_frame_recorder.SetReceivedRequestClosure(main_frame_req.GetCallback());
   OverrideInterface(rfh, &main_frame_recorder);
diff --git a/components/paint_preview/common/mock_paint_preview_recorder.cc b/components/paint_preview/common/mock_paint_preview_recorder.cc
index 5dd63377..24d1c65e 100644
--- a/components/paint_preview/common/mock_paint_preview_recorder.cc
+++ b/components/paint_preview/common/mock_paint_preview_recorder.cc
@@ -22,14 +22,11 @@
     mojom::PaintPreviewRecorder::CapturePaintPreviewCallback callback) {
   CheckParams(params);
 
+  send_response_callback_ = std::move(callback);
   if (received_request_closure_) {
-    send_response_callback_ = std::move(callback);
     std::move(received_request_closure_).Run();
   } else {
-    if (!response_) {
-      response_ = NewResponse();
-    }
-    std::move(callback).Run(status_, std::move(response_));
+    SendResponse();
   }
 }
 
@@ -38,14 +35,11 @@
     mojom::PaintPreviewRecorder::GetGeometryMetadataCallback callback) {
   CheckGeometryParams(params);
 
+  send_geometry_response_callback_ = std::move(callback);
   if (received_request_closure_) {
-    send_geometry_response_callback_ = std::move(callback);
     std::move(received_request_closure_).Run();
   } else {
-    if (!geometry_response_) {
-      geometry_response_ = mojom::GeometryMetadataResponse::New();
-    }
-    std::move(callback).Run(std::move(geometry_response_));
+    SendGeometryResponse();
   }
 }
 
@@ -60,9 +54,8 @@
 }
 
 void MockPaintPreviewRecorder::SetResponse(
-    mojom::PaintPreviewStatus status,
-    mojom::PaintPreviewCaptureResponsePtr response) {
-  status_ = status;
+    base::expected<mojom::PaintPreviewCaptureResponsePtr,
+                   mojom::PaintPreviewStatus> response) {
   response_ = std::move(response);
 }
 
@@ -79,14 +72,22 @@
 
 void MockPaintPreviewRecorder::SendResponse() {
   ASSERT_TRUE(send_response_callback_);
-  if (!response_) {
+  ASSERT_TRUE(response_) << "SetResponse() not called";
+  if (response_.value().has_value() && response_.value().value().is_null()) {
     response_ = NewResponse();
   }
-  std::move(send_response_callback_).Run(status_, std::move(response_));
+  std::optional<base::expected<mojom::PaintPreviewCaptureResponsePtr,
+                               mojom::PaintPreviewStatus>>
+      resp = std::nullopt;
+  resp.swap(response_);
+  std::move(send_response_callback_).Run(std::move(resp).value());
 }
 
 void MockPaintPreviewRecorder::SendGeometryResponse() {
   ASSERT_TRUE(send_geometry_response_callback_);
+  if (!geometry_response_) {
+    geometry_response_ = mojom::GeometryMetadataResponse::New();
+  }
   std::move(send_geometry_response_callback_)
       .Run(std::move(geometry_response_));
 }
diff --git a/components/paint_preview/common/mock_paint_preview_recorder.h b/components/paint_preview/common/mock_paint_preview_recorder.h
index ab45802..46c868c7 100644
--- a/components/paint_preview/common/mock_paint_preview_recorder.h
+++ b/components/paint_preview/common/mock_paint_preview_recorder.h
@@ -32,10 +32,14 @@
   void SetExpectedParams(mojom::PaintPreviewCaptureParamsPtr params);
   void SetExpectedGeometryParams(mojom::GeometryMetadataParamsPtr params);
 
-  // Sets the status and response that will be sent. If `response` is nullptr, a
-  // new response will be conructed on demand when needed.
-  void SetResponse(mojom::PaintPreviewStatus status,
-                   mojom::PaintPreviewCaptureResponsePtr response = nullptr);
+  // Sets the status or response that will be sent. If `response` is
+  // `base::ok(nullptr)`, a new response will be conructed on demand when
+  // needed. Each response is consumed when it is sent.
+  void SetResponse(
+      base::expected<mojom::PaintPreviewCaptureResponsePtr,
+                     mojom::PaintPreviewStatus> response = base::ok(nullptr));
+  // Sets the response that will be sent. If `response` is `nullptr`, a new
+  // response will be conructed on demand when needed.
   void SetGeometryResponse(
       mojom::GeometryMetadataResponsePtr response = nullptr);
 
@@ -72,9 +76,9 @@
   mojom::PaintPreviewCaptureParamsPtr expected_params_;
   mojom::GeometryMetadataParamsPtr expected_geometry_params_;
 
-  mojom::PaintPreviewStatus status_;
-
-  mojom::PaintPreviewCaptureResponsePtr response_;
+  std::optional<base::expected<mojom::PaintPreviewCaptureResponsePtr,
+                               mojom::PaintPreviewStatus>>
+      response_ = std::nullopt;
   mojom::GeometryMetadataResponsePtr geometry_response_;
 
   mojo::AssociatedReceiver<mojom::PaintPreviewRecorder> binding_{this};
diff --git a/components/paint_preview/common/mojom/paint_preview_recorder.mojom b/components/paint_preview/common/mojom/paint_preview_recorder.mojom
index ce15a4b..6e13eb5c 100644
--- a/components/paint_preview/common/mojom/paint_preview_recorder.mojom
+++ b/components/paint_preview/common/mojom/paint_preview_recorder.mojom
@@ -153,11 +153,10 @@
   // via the RenderFrameProxy. The browser handles dispatching these requests to
   // the correct RenderFrame and aggregating all the outputs.
   //
-  // Returns a status, and if |status| == kOK a valid instance of
-  // PaintPreviewCaptureResponse.
+  // Returns a valid instance of PaintPreviewCaptureResponse, or a status code
+  // on error.
   CapturePaintPreview(PaintPreviewCaptureParams params) =>
-    (PaintPreviewStatus status,
-     PaintPreviewCaptureResponse response);
+    result<PaintPreviewCaptureResponse, PaintPreviewStatus>;
 
   // Computes the geometry that would be used for a capture with the given
   // geometry params. Returns null if the geometry couldn't be calculated.
diff --git a/components/paint_preview/renderer/paint_preview_recorder_browsertest.cc b/components/paint_preview/renderer/paint_preview_recorder_browsertest.cc
index 1a8fd87b..50a4bbb6 100644
--- a/components/paint_preview/renderer/paint_preview_recorder_browsertest.cc
+++ b/components/paint_preview/renderer/paint_preview_recorder_browsertest.cc
@@ -5,6 +5,7 @@
 #include "base/files/file.h"
 #include "base/files/file_path.h"
 #include "base/files/scoped_temp_dir.h"
+#include "base/test/gmock_expected_support.h"
 #include "base/test/scoped_feature_list.h"
 #include "base/test/test_future.h"
 #include "base/threading/thread_restrictions.h"
@@ -111,15 +112,14 @@
     params->file = std::move(skp_file);
 
     PaintPreviewRecorderImpl paint_preview_recorder(frame);
-    base::test::TestFuture<mojom::PaintPreviewStatus,
-                           mojom::PaintPreviewCaptureResponsePtr>
+    base::test::TestFuture<base::expected<mojom::PaintPreviewCaptureResponsePtr,
+                                          mojom::PaintPreviewStatus>>
         future;
     paint_preview_recorder.CapturePaintPreview(std::move(params),
                                                future.GetCallback());
-    auto [status, response] = future.Take();
 
-    EXPECT_EQ(status, mojom::PaintPreviewStatus::kOk);
-    return {skp_path, std::move(response)};
+    EXPECT_THAT(future.Get(), base::test::HasValue());
+    return {skp_path, future.Take().value()};
   }
 
   mojom::GeometryMetadataResponsePtr GetGeometryMetadata(
@@ -739,14 +739,14 @@
   params->file = std::move(skp_file);
 
   content::RenderFrame* frame = GetMainRenderFrame();
-  base::test::TestFuture<mojom::PaintPreviewStatus,
-                         mojom::PaintPreviewCaptureResponsePtr>
+  base::test::TestFuture<base::expected<mojom::PaintPreviewCaptureResponsePtr,
+                                        mojom::PaintPreviewStatus>>
       future;
   PaintPreviewRecorderImpl paint_preview_recorder(frame);
   paint_preview_recorder.CapturePaintPreview(std::move(params),
                                              future.GetCallback());
-  auto [status, response] = future.Take();
-  EXPECT_EQ(mojom::PaintPreviewStatus::kCaptureFailed, status);
+  EXPECT_THAT(future.Take(),
+              base::test::ErrorIs(mojom::PaintPreviewStatus::kCaptureFailed));
 }
 
 TEST_P(PaintPreviewRecorderRenderViewTest, TestCaptureInvalidXYClip) {
@@ -768,14 +768,14 @@
   params->file = std::move(skp_file);
 
   content::RenderFrame* frame = GetMainRenderFrame();
-  base::test::TestFuture<mojom::PaintPreviewStatus,
-                         mojom::PaintPreviewCaptureResponsePtr>
+  base::test::TestFuture<base::expected<mojom::PaintPreviewCaptureResponsePtr,
+                                        mojom::PaintPreviewStatus>>
       future;
   PaintPreviewRecorderImpl paint_preview_recorder(frame);
   paint_preview_recorder.CapturePaintPreview(std::move(params),
                                              future.GetCallback());
-  auto [status, response] = future.Take();
-  EXPECT_EQ(mojom::PaintPreviewStatus::kCaptureFailed, status);
+  EXPECT_THAT(future.Take(),
+              base::test::ErrorIs(mojom::PaintPreviewStatus::kCaptureFailed));
 }
 
 TEST_P(PaintPreviewRecorderRenderViewTest, TestCaptureMainFrameAndLocalFrame) {
diff --git a/components/paint_preview/renderer/paint_preview_recorder_impl.cc b/components/paint_preview/renderer/paint_preview_recorder_impl.cc
index 5e0918d8..7455317a 100644
--- a/components/paint_preview/renderer/paint_preview_recorder_impl.cc
+++ b/components/paint_preview/renderer/paint_preview_recorder_impl.cc
@@ -81,18 +81,19 @@
 };
 
 using CapturePaintPreviewCallback =
-    base::OnceCallback<void(mojom::PaintPreviewStatus,
-                            mojom::PaintPreviewCaptureResponsePtr)>;
+    mojom::PaintPreviewRecorder::CapturePaintPreviewCallback;
 
 // Finishes building the PaintPreviewCaptureResponse mojo message and sends it
 // or sends an error if the status is not `PaintPreviewStatus::kOK`
 void BuildAndSendResponse(std::unique_ptr<PaintPreviewTracker> tracker,
                           FinishedRecording out,
                           CapturePaintPreviewCallback callback) {
-  if (out.status == mojom::PaintPreviewStatus::kOk) {
-    BuildResponse(tracker.get(), out.response.get());
+  if (out.status != mojom::PaintPreviewStatus::kOk) {
+    std::move(callback).Run(base::unexpected(out.status));
+    return;
   }
-  std::move(callback).Run(out.status, std::move(out.response));
+  BuildResponse(tracker.get(), out.response.get());
+  std::move(callback).Run(std::move(out.response));
 }
 
 // Records `skp` to `skp_file` on the threadpool to avoid blocking the main
@@ -165,8 +166,8 @@
   TRACE_EVENT0("paint_preview", "FinishRecordingOnUIThread");
   DCHECK(tracker);
   if (!tracker) {
-    std::move(callback).Run(mojom::PaintPreviewStatus::kCaptureFailed,
-                            std::move(response));
+    std::move(callback).Run(
+        base::unexpected(mojom::PaintPreviewStatus::kCaptureFailed));
     return;
   }
 
@@ -176,8 +177,8 @@
   auto skp =
       PaintRecordToSkPicture(std::move(recording), tracker.get(), bounds);
   if (!skp) {
-    std::move(callback).Run(mojom::PaintPreviewStatus::kCaptureFailed,
-                            std::move(response));
+    std::move(callback).Run(
+        base::unexpected(mojom::PaintPreviewStatus::kCaptureFailed));
     return;
   }
   TRACE_EVENT_BEGIN0("paint_preview", "ConvertToSkPicture");
@@ -329,8 +330,8 @@
   // recoverable.
   auto response = mojom::PaintPreviewCaptureResponse::New();
   if (is_painting_preview_) {
-    std::move(callback).Run(mojom::PaintPreviewStatus::kAlreadyCapturing,
-                            std::move(response));
+    std::move(callback).Run(
+        base::unexpected(mojom::PaintPreviewStatus::kAlreadyCapturing));
     return;
   }
   const base::AutoReset<bool> resetter(&is_painting_preview_, true);
@@ -359,26 +360,25 @@
   // Ensure the a frame actually exists to avoid a possible crash.
   if (!frame) {
     DVLOG(1) << "Error: renderer has no frame yet!";
-    std::move(callback).Run(mojom::PaintPreviewStatus::kFailed,
-                            std::move(response));
+    std::move(callback).Run(
+        base::unexpected(mojom::PaintPreviewStatus::kFailed));
     return;
   }
 
   DCHECK_EQ(is_main_frame_, params->is_main_frame);
 
-  ASSIGN_OR_RETURN(
-      const CaptureGeometry geometry,
-      ComputeCaptureGeometry(
-          frame->GetScrollOffset(), frame->DocumentSize(),
-          params->geometry_metadata_params->clip_rect,
-          params->geometry_metadata_params->clip_x_coord_override,
-          params->geometry_metadata_params->clip_y_coord_override,
-          params->geometry_metadata_params->clip_rect_is_hint),
-      [&] {
-        std::move(callback).Run(mojom::PaintPreviewStatus::kCaptureFailed,
-                                std::move(response));
-        return;
-      });
+  ASSIGN_OR_RETURN(const CaptureGeometry geometry,
+                   ComputeCaptureGeometry(
+                       frame->GetScrollOffset(), frame->DocumentSize(),
+                       params->geometry_metadata_params->clip_rect,
+                       params->geometry_metadata_params->clip_x_coord_override,
+                       params->geometry_metadata_params->clip_y_coord_override,
+                       params->geometry_metadata_params->clip_rect_is_hint),
+                   [&] {
+                     std::move(callback).Run(base::unexpected(
+                         mojom::PaintPreviewStatus::kCaptureFailed));
+                     return;
+                   });
 
   response->geometry_metadata = mojom::GeometryMetadataResponse::New();
   response->geometry_metadata->scroll_offsets = geometry.scroll_offsets;
@@ -432,8 +432,8 @@
 
   // Restore to before out-of-lifecycle paint phase.
   if (!success) {
-    std::move(callback).Run(mojom::PaintPreviewStatus::kCaptureFailed,
-                            std::move(response));
+    std::move(callback).Run(
+        base::unexpected(mojom::PaintPreviewStatus::kCaptureFailed));
     return;
   }
 
diff --git a/components/password_manager/core/browser/export/BUILD.gn b/components/password_manager/core/browser/export/BUILD.gn
index a8bcd0d..29b0e35e3 100644
--- a/components/password_manager/core/browser/export/BUILD.gn
+++ b/components/password_manager/core/browser/export/BUILD.gn
@@ -22,22 +22,6 @@
     "//components/password_manager/core/browser/ui",
     "//components/sync/base:features",
   ]
-
-  if (is_android) {
-    sources += [
-      "login_db_deprecation_password_exporter.cc",
-      "login_db_deprecation_password_exporter.h",
-      "login_db_deprecation_password_exporter_interface.h",
-      "login_db_deprecation_runner.cc",
-      "login_db_deprecation_runner.h",
-    ]
-    deps += [
-      "//components/password_manager/core/browser/features:password_features",
-      "//components/password_manager/core/browser/password_store:password_store_interface",
-      "//components/password_manager/core/common",
-      "//components/prefs",
-    ]
-  }
   configs += [ "//build/config/compiler:wexit_time_destructors" ]
 }
 
@@ -58,17 +42,6 @@
     "//testing/gtest",
   ]
 
-  if (is_android) {
-    sources += [
-      "login_db_deprecation_password_exporter_unittest.cc",
-      "login_db_deprecation_runner_unittest.cc",
-    ]
-    deps += [
-      "//components/password_manager/core/browser/features:password_features",
-      "//components/prefs:test_support",
-    ]
-  }
-
   # TODO(crbug.com/40031409): Fix code that adds exit-time destructors and
   # enable the diagnostic by removing this line.
   configs += [ "//build/config/compiler:no_exit_time_destructors" ]
diff --git a/components/password_manager/core/browser/export/login_db_deprecation_password_exporter.cc b/components/password_manager/core/browser/export/login_db_deprecation_password_exporter.cc
deleted file mode 100644
index 529298c..0000000
--- a/components/password_manager/core/browser/export/login_db_deprecation_password_exporter.cc
+++ /dev/null
@@ -1,148 +0,0 @@
-// Copyright 2024 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "components/password_manager/core/browser/export/login_db_deprecation_password_exporter.h"
-
-#include <variant>
-
-#include "base/files/file_path.h"
-#include "base/functional/callback_helpers.h"
-#include "base/metrics/histogram_functions.h"
-#include "base/time/time.h"
-#include "components/password_manager/core/browser/export/export_progress_status.h"
-#include "components/password_manager/core/browser/export/password_manager_exporter.h"
-#include "components/password_manager/core/browser/password_manager_metrics_util.h"
-#include "components/password_manager/core/browser/password_store/password_store_interface.h"
-#include "components/password_manager/core/browser/ui/credential_ui_entry.h"
-#include "components/password_manager/core/common/password_manager_pref_names.h"
-#include "components/prefs/pref_service.h"
-
-namespace password_manager {
-
-namespace {
-void LogExportResult(LoginDbDeprecationExportResult result) {
-  base::UmaHistogramEnumeration(
-      "PasswordManager.UPM.LoginDbDeprecationExport.Result", result);
-}
-
-void LogExportLatency(base::TimeDelta latency) {
-  base::UmaHistogramMediumTimes(
-      "PasswordManager.UPM.LoginDbDeprecationExport.Latency", latency);
-}
-
-}  // namespace
-
-LoginDbDeprecationPasswordExporter::LoginDbDeprecationPasswordExporter(
-    PrefService* pref_service,
-    base::FilePath export_dir_path)
-    : pref_service_(pref_service),
-      export_dir_path_(std::move(export_dir_path)) {
-  exporter_ = std::make_unique<PasswordManagerExporter>(
-      this,
-      base::BindRepeating(&LoginDbDeprecationPasswordExporter::OnExportProgress,
-                          weak_factory_.GetWeakPtr()),
-      base::BindOnce(&LoginDbDeprecationPasswordExporter::OnExportComplete,
-                     weak_factory_.GetWeakPtr()));
-}
-
-LoginDbDeprecationPasswordExporter::~LoginDbDeprecationPasswordExporter() =
-    default;
-
-void LoginDbDeprecationPasswordExporter::Start(
-    scoped_refptr<PasswordStoreInterface> password_store,
-    base::OnceClosure export_cleanup_calback) {
-  password_store_ = password_store;
-  export_cleanup_callback_ = std::move(export_cleanup_calback);
-  start_time_ = base::Time::Now();
-  password_store->GetAutofillableLogins(weak_factory_.GetWeakPtr());
-}
-
-std::vector<CredentialUIEntry>
-LoginDbDeprecationPasswordExporter::GetSavedCredentials() const {
-  return passwords_;
-}
-
-PasswordManagerExporter*
-LoginDbDeprecationPasswordExporter::GetInternalExporterForTesting(
-    base::PassKey<LoginDbDeprecationPasswordExporterTest>) {
-  return exporter_.get();
-}
-
-void LoginDbDeprecationPasswordExporter::OnGetPasswordStoreResultsOrErrorFrom(
-    PasswordStoreInterface* store,
-    LoginsResultOrError logins_or_error) {
-  if (std::holds_alternative<PasswordStoreBackendError>(logins_or_error)) {
-    OnExportCompleteWithResult(
-        LoginDbDeprecationExportResult::kErrorFetchingPasswords);
-    return;
-  }
-
-  // This is only invoked once, since the export flow goverened by this
-  // class is a one-time operation.
-  CHECK(passwords_.empty());
-  passwords_.reserve(std::get<LoginsResult>(logins_or_error).size());
-  for (const auto& password_form : std::get<LoginsResult>(logins_or_error)) {
-    passwords_.emplace_back(password_form);
-  }
-  if (passwords_.empty()) {
-    OnExportCompleteWithResult(LoginDbDeprecationExportResult::kNoPasswords);
-    return;
-  }
-
-  exporter_->PreparePasswordsForExport();
-  exporter_->SetDestination(
-      export_dir_path_.Append(FILE_PATH_LITERAL(kExportedPasswordsFileName)));
-}
-
-void LoginDbDeprecationPasswordExporter::OnExportProgress(
-    const PasswordExportInfo& export_info) {
-  export_status_ = export_info.status;
-}
-
-void LoginDbDeprecationPasswordExporter::OnExportComplete() {
-  LoginDbDeprecationExportResult result;
-  switch (export_status_) {
-    case ExportProgressStatus::kNotStarted:
-    case ExportProgressStatus::kInProgress: {
-      // `OnExportComplete` should only be called for completed export flows.
-      NOTREACHED();
-    }
-    case ExportProgressStatus::kSucceeded: {
-      result = LoginDbDeprecationExportResult::kSuccess;
-      break;
-    }
-    case ExportProgressStatus::kFailedCancelled: {
-      // There is no option that cancels this export flow.
-      NOTREACHED();
-    }
-    case ExportProgressStatus::kFailedWrite: {
-      result = LoginDbDeprecationExportResult::kFileWriteError;
-      break;
-    }
-  };
-  OnExportCompleteWithResult(result);
-}
-
-void LoginDbDeprecationPasswordExporter::OnExportCompleteWithResult(
-    LoginDbDeprecationExportResult result) {
-  LogExportResult(result);
-
-  // If the export wasn't successful and there are passwords to export,
-  // it will be re-attempted on the next startup.
-  if (result == LoginDbDeprecationExportResult::kSuccess) {
-    LogExportLatency(base::Time::Now() - start_time_);
-    pref_service_->SetBoolean(prefs::kUpmUnmigratedPasswordsExported, true);
-    password_store_->RemoveLoginsCreatedBetween(FROM_HERE, base::Time(),
-                                                base::Time::Max());
-  } else if (result == LoginDbDeprecationExportResult::kNoPasswords) {
-    // Nothing to export, so the export can be marked as done.
-    pref_service_->SetBoolean(
-        password_manager::prefs::kUpmUnmigratedPasswordsExported, true);
-  }
-
-  std::move(export_cleanup_callback_).Run();
-  // The callback above destroys `this`.
-}
-
-}  // namespace password_manager
diff --git a/components/password_manager/core/browser/export/login_db_deprecation_password_exporter.h b/components/password_manager/core/browser/export/login_db_deprecation_password_exporter.h
deleted file mode 100644
index f4ccf982..0000000
--- a/components/password_manager/core/browser/export/login_db_deprecation_password_exporter.h
+++ /dev/null
@@ -1,112 +0,0 @@
-// Copyright 2024 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef COMPONENTS_PASSWORD_MANAGER_CORE_BROWSER_EXPORT_LOGIN_DB_DEPRECATION_PASSWORD_EXPORTER_H_
-#define COMPONENTS_PASSWORD_MANAGER_CORE_BROWSER_EXPORT_LOGIN_DB_DEPRECATION_PASSWORD_EXPORTER_H_
-
-#include <vector>
-
-#include "base/files/file_path.h"
-#include "base/functional/callback_forward.h"
-#include "components/password_manager/core/browser/export/login_db_deprecation_password_exporter_interface.h"
-#include "components/password_manager/core/browser/export/password_manager_exporter.h"
-#include "components/password_manager/core/browser/password_store/password_store_consumer.h"
-#include "components/password_manager/core/browser/password_store/password_store_interface.h"
-#include "components/password_manager/core/browser/ui/credential_ui_entry.h"
-#include "components/password_manager/core/browser/ui/passwords_provider.h"
-#include "components/prefs/pref_service.h"
-
-namespace password_manager {
-
-// These values are persisted to logs. Entries should not be renumbered and
-// numeric values should never be reused.
-//
-// LINT.IfChange(LoginDbDeprecationExportResult)
-enum class LoginDbDeprecationExportResult {
-  kSuccess = 0,
-  kNoPasswords = 1,
-  kErrorFetchingPasswords = 2,
-  kFileWriteError = 3,
-
-  kMaxValue = kFileWriteError,
-};
-// LINT.ThenChange(/tools/metrics/histograms/metadata/password/enums.xml:LoginDbDeprecationExportResult)
-
-inline constexpr std::string_view kExportedPasswordsFileName =
-    "ChromePasswords.csv";
-
-// Directs exporting the passwords from the `LoginDatabase` to a CSV stored
-// in the same place to allow for database deprecation.
-class LoginDbDeprecationPasswordExporter
-    : public LoginDbDeprecationPasswordExporterInterface,
-      public PasswordStoreConsumer,
-      public PasswordsProvider {
- public:
-  explicit LoginDbDeprecationPasswordExporter(PrefService* pref_service,
-                                              base::FilePath export_dir_path);
-  LoginDbDeprecationPasswordExporter(
-      const LoginDbDeprecationPasswordExporter&) = delete;
-  LoginDbDeprecationPasswordExporter& operator=(
-      const LoginDbDeprecationPasswordExporter&) = delete;
-  ~LoginDbDeprecationPasswordExporter() override;
-
-  void Start(scoped_refptr<PasswordStoreInterface> password_store,
-             base::OnceClosure export_cleanup_calback) override;
-
-  // Allows the `PasswordManagerExporter` to retrieve the saved credentials
-  // after `this` receives them. Not a necessary pattern for this use-case
-  // but one which the `PasswordManagerExport` expects, since it's usually
-  // used in UI applications where another class holds the credentials.
-  std::vector<CredentialUIEntry> GetSavedCredentials() const override;
-
-  PasswordManagerExporter* GetInternalExporterForTesting(
-      base::PassKey<class LoginDbDeprecationPasswordExporterTest>);
-
- private:
-  void OnGetPasswordStoreResultsOrErrorFrom(
-      PasswordStoreInterface* store,
-      LoginsResultOrError results_or_error) override;
-
-  // Reports on the progress of the export flow.
-  void OnExportProgress(const PasswordExportInfo& export_info);
-
-  // Called when the `exporter_` completes all the export operations,
-  // irrespective of whether the export succeeded.
-  void OnExportComplete();
-
-  // Called when the export finishes (from `OnExportComplete`),
-  // or before it starts if the passwords to export could not be fetched.
-  void OnExportCompleteWithResult(LoginDbDeprecationExportResult result);
-
-  // Used to delete the passwords after successful export.
-  scoped_refptr<PasswordStoreInterface> password_store_;
-
-  // Callback to invoke when ALL the export operations finished. It will clean
-  // up `this`.
-  base::OnceClosure export_cleanup_callback_;
-
-  // Serializes the passwords and writes them to a CSV file.
-  std::unique_ptr<PasswordManagerExporter> exporter_;
-
-  // Stores the saved credentials.
-  std::vector<CredentialUIEntry> passwords_;
-
-  // Used to store the export completion status in a pref.
-  raw_ptr<PrefService> pref_service_;
-
-  // Path where the exported CSV will be written. It should be the same as the
-  // login db path.
-  base::FilePath export_dir_path_;
-
-  // The last reported status of the export flow.
-  ExportProgressStatus export_status_{ExportProgressStatus::kNotStarted};
-
-  base::Time start_time_;
-
-  base::WeakPtrFactory<LoginDbDeprecationPasswordExporter> weak_factory_{this};
-};
-
-}  // namespace password_manager
-
-#endif  // COMPONENTS_PASSWORD_MANAGER_CORE_BROWSER_EXPORT_LOGIN_DB_DEPRECATION_PASSWORD_EXPORTER_H_
diff --git a/components/password_manager/core/browser/export/login_db_deprecation_password_exporter_interface.h b/components/password_manager/core/browser/export/login_db_deprecation_password_exporter_interface.h
deleted file mode 100644
index 11cab84c..0000000
--- a/components/password_manager/core/browser/export/login_db_deprecation_password_exporter_interface.h
+++ /dev/null
@@ -1,26 +0,0 @@
-// Copyright 2025 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef COMPONENTS_PASSWORD_MANAGER_CORE_BROWSER_EXPORT_LOGIN_DB_DEPRECATION_PASSWORD_EXPORTER_INTERFACE_H_
-#define COMPONENTS_PASSWORD_MANAGER_CORE_BROWSER_EXPORT_LOGIN_DB_DEPRECATION_PASSWORD_EXPORTER_INTERFACE_H_
-
-#include "components/password_manager/core/browser/password_store/password_store_interface.h"
-
-namespace password_manager {
-
-// Interface for `LoginDbDeprecationPasswordExporter` to allow mocking in tests.
-class LoginDbDeprecationPasswordExporterInterface {
- public:
-  virtual ~LoginDbDeprecationPasswordExporterInterface() = default;
-
-  // Starts the export flow. It will first fetch the stored credentials
-  // and when fetching completes the actual export will be automatically
-  // started.
-  virtual void Start(scoped_refptr<PasswordStoreInterface> password_store,
-                     base::OnceClosure export_cleanup_calback) = 0;
-};
-
-}  // namespace password_manager
-
-#endif  // COMPONENTS_PASSWORD_MANAGER_CORE_BROWSER_EXPORT_LOGIN_DB_DEPRECATION_PASSWORD_EXPORTER_INTERFACE_H_
diff --git a/components/password_manager/core/browser/export/login_db_deprecation_password_exporter_unittest.cc b/components/password_manager/core/browser/export/login_db_deprecation_password_exporter_unittest.cc
deleted file mode 100644
index 6823ba9..0000000
--- a/components/password_manager/core/browser/export/login_db_deprecation_password_exporter_unittest.cc
+++ /dev/null
@@ -1,279 +0,0 @@
-// Copyright 2024 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "components/password_manager/core/browser/export/login_db_deprecation_password_exporter.h"
-
-#include <iterator>
-#include <memory>
-
-#include "base/files/file_path.h"
-#include "base/files/file_util.h"
-#include "base/files/scoped_temp_dir.h"
-#include "base/memory/scoped_refptr.h"
-#include "base/memory/weak_ptr.h"
-#include "base/strings/utf_string_conversions.h"
-#include "base/test/bind.h"
-#include "base/test/metrics/histogram_tester.h"
-#include "base/test/mock_callback.h"
-#include "base/test/run_until.h"
-#include "base/test/task_environment.h"
-#include "base/test/test_future.h"
-#include "base/time/time.h"
-#include "components/password_manager/core/browser/password_form.h"
-#include "components/password_manager/core/browser/password_store/fake_password_store_backend.h"
-#include "components/password_manager/core/browser/password_store/mock_password_store_backend.h"
-#include "components/password_manager/core/browser/password_store/password_store.h"
-#include "components/password_manager/core/browser/password_store/password_store_backend.h"
-#include "components/password_manager/core/browser/password_store/password_store_backend_error.h"
-#include "components/password_manager/core/browser/password_store/password_store_consumer.h"
-#include "components/password_manager/core/browser/password_store/password_store_interface.h"
-#include "components/password_manager/core/browser/password_store/password_store_results_observer.h"
-#include "components/password_manager/core/browser/password_store/test_password_store.h"
-#include "components/password_manager/core/common/password_manager_pref_names.h"
-#include "components/prefs/pref_registry_simple.h"
-#include "components/prefs/testing_pref_service.h"
-#include "testing/gtest/include/gtest/gtest.h"
-
-namespace password_manager {
-
-namespace {
-using ::testing::Return;
-using ::testing::StrictMock;
-using ::testing::WithArg;
-using ::testing::WithArgs;
-
-const std::string kLineEnding = "\n";
-const std::string kExportFilename = "ChromePasswords.csv";
-const std::string kExportResultHistogram =
-    "PasswordManager.UPM.LoginDbDeprecationExport.Result";
-const std::string kExportLatencyHistogram =
-    "PasswordManager.UPM.LoginDbDeprecationExport.Latency";
-
-std::pair<PasswordForm, std::string> GetTestFormWithExpectedExportData() {
-  const std::u16string kNoteValue =
-      base::UTF8ToUTF16("Note Line 1" + kLineEnding + "Note Line 2");
-  PasswordForm form;
-  form.signon_realm = "https://example.com/";
-  form.url = GURL("https://example.com");
-  form.username_value = u"Someone";
-  form.password_value = u"Secret";
-  form.notes = {PasswordNote(kNoteValue, base::Time::Now())};
-
-  std::string expected_export_data =
-      "name,url,username,password,note" + kLineEnding +
-      "example.com,https://example.com/,Someone,Secret,\"Note Line "
-      "1" +
-      kLineEnding + "Note Line 2\"" + kLineEnding;
-  return {form, expected_export_data};
-}
-
-}  // namespace
-
-class LoginDbDeprecationPasswordExporterTest : public testing::Test {
- public:
-  LoginDbDeprecationPasswordExporterTest() = default;
-
-  void SetUp() override {
-    ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
-    pref_service_.registry()->RegisterBooleanPref(
-        prefs::kUpmUnmigratedPasswordsExported, false);
-
-    password_store_ = base::MakeRefCounted<TestPasswordStore>();
-    password_store_->Init(&pref_service_, /*affiliated_match_helper=*/nullptr);
-    exporter_ = std::make_unique<LoginDbDeprecationPasswordExporter>(
-        &pref_service_, temp_dir_.GetPath());
-  }
-
-  void TearDown() override {
-    password_store_->ShutdownOnUIThread();
-    ASSERT_TRUE(temp_dir_.Delete());
-  }
-
-  const base::FilePath& export_dir() { return temp_dir_.GetPath(); }
-
-  LoginDbDeprecationPasswordExporter* exporter() { return exporter_.get(); }
-
-  TestingPrefServiceSimple* pref_service() { return &pref_service_; }
-
-  PasswordStoreInterface* password_store() { return password_store_.get(); }
-
- protected:
-  base::test::TaskEnvironment task_env_{
-      base::test::TaskEnvironment::TimeSource::MOCK_TIME,
-      base::test::TaskEnvironment::ThreadPoolExecutionMode::QUEUED};
-  base::PassKey<class LoginDbDeprecationPasswordExporterTest> passkey =
-      base::PassKey<class LoginDbDeprecationPasswordExporterTest>();
-
- private:
-  std::unique_ptr<LoginDbDeprecationPasswordExporter> exporter_;
-  TestingPrefServiceSimple pref_service_;
-  scoped_refptr<TestPasswordStore> password_store_;
-  base::ScopedTempDir temp_dir_;
-};
-
-TEST_F(LoginDbDeprecationPasswordExporterTest, DoesntCreateFileIfNoPasswords) {
-  base::HistogramTester histogram_tester;
-  // No passwords in the backend.
-  exporter()->Start(password_store(), task_env_.QuitClosure());
-  task_env_.RunUntilQuit();
-
-  base::FilePath expected_file_path =
-      export_dir().Append(FILE_PATH_LITERAL("ChromePasswords.csv"));
-  EXPECT_FALSE(base::PathExists(expected_file_path));
-  histogram_tester.ExpectUniqueSample(
-      kExportResultHistogram, LoginDbDeprecationExportResult::kNoPasswords, 1);
-  histogram_tester.ExpectTotalCount(kExportLatencyHistogram, 0);
-  EXPECT_TRUE(
-      pref_service()->GetBoolean(prefs::kUpmUnmigratedPasswordsExported));
-}
-
-TEST_F(LoginDbDeprecationPasswordExporterTest, ExportFailedToFetchPasswords) {
-  base::HistogramTester histogram_tester;
-  // No passwords in the backend.
-  std::unique_ptr<MockPasswordStoreBackend> mock_backend =
-      std::make_unique<MockPasswordStoreBackend>();
-  MockPasswordStoreBackend* weak_mock_backend = mock_backend.get();
-  scoped_refptr<PasswordStore> password_store =
-      base::MakeRefCounted<PasswordStore>(std::move(mock_backend));
-
-  EXPECT_CALL(*weak_mock_backend, InitBackend)
-      .WillOnce(WithArgs<3>([](base::OnceCallback<void(bool)> completion) {
-        base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
-            FROM_HERE, base::BindOnce(std::move(completion), /*success=*/true));
-      }));
-
-  password_store->Init(pref_service(), /*affiliation_match_service=*/nullptr);
-
-  EXPECT_CALL(*weak_mock_backend, GetAutofillableLoginsAsync)
-      .WillOnce(WithArg<0>([password_store](LoginsOrErrorReply callback) {
-        PasswordStoreBackendError error{
-            PasswordStoreBackendErrorType::kUncategorized};
-        base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
-            FROM_HERE, base::BindOnce(std::move(callback), std::move(error)));
-      }));
-
-  exporter()->Start(password_store, task_env_.QuitClosure());
-  task_env_.RunUntilQuit();
-
-  base::FilePath expected_file_path =
-      export_dir().Append(FILE_PATH_LITERAL("ChromePasswords.csv"));
-  EXPECT_FALSE(base::PathExists(expected_file_path));
-  histogram_tester.ExpectUniqueSample(
-      kExportResultHistogram,
-      LoginDbDeprecationExportResult::kErrorFetchingPasswords, 1);
-  histogram_tester.ExpectTotalCount(kExportLatencyHistogram, 0);
-  password_store->ShutdownOnUIThread();
-}
-
-TEST_F(LoginDbDeprecationPasswordExporterTest, ExportsBackendPasswords) {
-  base::HistogramTester histogram_tester;
-  std::pair<PasswordForm, std::string> form_expected_data_pair =
-      GetTestFormWithExpectedExportData();
-  PasswordForm form = std::move(form_expected_data_pair.first);
-  std::string expected_exported_data =
-      std::move(form_expected_data_pair.second);
-
-  password_store()->AddLogin(form, task_env_.QuitClosure());
-  task_env_.RunUntilQuit();
-
-  const base::TimeDelta kExportLatency = base::Minutes(1);
-  exporter()->Start(password_store(), task_env_.QuitClosure());
-  task_env_.AdvanceClock(kExportLatency);
-  task_env_.RunUntilQuit();
-
-  base::FilePath expected_file_path =
-      export_dir().Append(FILE_PATH_LITERAL(kExportFilename));
-
-  EXPECT_TRUE(base::PathExists(expected_file_path));
-  EXPECT_TRUE(
-      pref_service()->GetBoolean(prefs::kUpmUnmigratedPasswordsExported));
-
-  std::string contents;
-  ASSERT_TRUE(base::ReadFileToString(expected_file_path, &contents));
-  EXPECT_TRUE(!contents.empty());
-  EXPECT_EQ(expected_exported_data, contents);
-  histogram_tester.ExpectUniqueSample(
-      kExportResultHistogram, LoginDbDeprecationExportResult::kSuccess, 1);
-  histogram_tester.ExpectUniqueTimeSample(kExportLatencyHistogram,
-                                          kExportLatency, 1);
-}
-
-TEST_F(LoginDbDeprecationPasswordExporterTest, ExportFailure) {
-  base::HistogramTester histogram_tester;
-  PasswordForm form = GetTestFormWithExpectedExportData().first;
-  password_store()->AddLogin(form, task_env_.QuitClosure());
-  task_env_.RunUntilQuit();
-
-  PasswordManagerExporter* internal_exporter =
-      exporter()->GetInternalExporterForTesting(passkey);
-  StrictMock<base::MockCallback<PasswordManagerExporter::WriteCallback>>
-      mock_write_callback;
-
-  // Fake a failed write.
-  internal_exporter->SetWriteForTesting(mock_write_callback.Get());
-  EXPECT_CALL(mock_write_callback, Run).WillOnce(Return(false));
-
-  exporter()->Start(password_store(), task_env_.QuitClosure());
-  task_env_.RunUntilQuit();
-
-  EXPECT_FALSE(
-      pref_service()->GetBoolean(prefs::kUpmUnmigratedPasswordsExported));
-  histogram_tester.ExpectUniqueSample(
-      kExportResultHistogram, LoginDbDeprecationExportResult::kFileWriteError,
-      1);
-  histogram_tester.ExpectTotalCount(kExportLatencyHistogram, 0);
-}
-
-TEST_F(LoginDbDeprecationPasswordExporterTest, RemovesLoginsOnExportSuccess) {
-  std::pair<PasswordForm, std::string> form_expected_data_pair =
-      GetTestFormWithExpectedExportData();
-  PasswordForm form = std::move(form_expected_data_pair.first);
-  std::string expected_exported_data =
-      std::move(form_expected_data_pair.second);
-
-  password_store()->AddLogin(form, task_env_.QuitClosure());
-  task_env_.RunUntilQuit();
-
-  exporter()->Start(password_store(), task_env_.QuitClosure());
-  task_env_.RunUntilQuit();
-
-  ASSERT_TRUE(
-      pref_service()->GetBoolean(prefs::kUpmUnmigratedPasswordsExported));
-
-  password_manager::PasswordStoreResultsObserver results_observer;
-  password_store()->GetAllLogins(results_observer.GetWeakPtr());
-  std::vector<std::unique_ptr<PasswordForm>> forms =
-      results_observer.WaitForResults();
-  EXPECT_TRUE(forms.empty());
-}
-
-TEST_F(LoginDbDeprecationPasswordExporterTest,
-       DoesntRemoveLoginsOnExportFailure) {
-  PasswordForm form = GetTestFormWithExpectedExportData().first;
-  password_store()->AddLogin(form, task_env_.QuitClosure());
-  task_env_.RunUntilQuit();
-
-  PasswordManagerExporter* internal_exporter =
-      exporter()->GetInternalExporterForTesting(passkey);
-  StrictMock<base::MockCallback<PasswordManagerExporter::WriteCallback>>
-      mock_write_callback;
-
-  // Fake a failed write.
-  internal_exporter->SetWriteForTesting(mock_write_callback.Get());
-  EXPECT_CALL(mock_write_callback, Run).WillOnce(Return(false));
-
-  exporter()->Start(password_store(), task_env_.QuitClosure());
-  task_env_.RunUntilQuit();
-
-  ASSERT_FALSE(
-      pref_service()->GetBoolean(prefs::kUpmUnmigratedPasswordsExported));
-
-  password_manager::PasswordStoreResultsObserver results_observer;
-  password_store()->GetAllLogins(results_observer.GetWeakPtr());
-  std::vector<std::unique_ptr<PasswordForm>> forms =
-      results_observer.WaitForResults();
-  EXPECT_FALSE(forms.empty());
-}
-
-}  // namespace password_manager
diff --git a/components/password_manager/core/browser/export/login_db_deprecation_runner.cc b/components/password_manager/core/browser/export/login_db_deprecation_runner.cc
deleted file mode 100644
index aec0347..0000000
--- a/components/password_manager/core/browser/export/login_db_deprecation_runner.cc
+++ /dev/null
@@ -1,49 +0,0 @@
-// Copyright 2024 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "components/password_manager/core/browser/export/login_db_deprecation_runner.h"
-
-#include "base/memory/scoped_refptr.h"
-#include "base/metrics/histogram_functions.h"
-#include "base/time/time.h"
-#include "components/password_manager/core/browser/export/login_db_deprecation_password_exporter.h"
-#include "components/password_manager/core/browser/features/password_features.h"
-namespace password_manager {
-
-void LogExportProgress(LoginDbDeprecationExportProgress progress) {
-  base::UmaHistogramEnumeration(
-      "PasswordManager.UPM.LoginDbDeprecationExport.Progress", progress);
-}
-
-LoginDbDeprecationRunner::LoginDbDeprecationRunner(
-    std::unique_ptr<LoginDbDeprecationPasswordExporterInterface> exporter)
-    : exporter_(std::move(exporter)) {}
-
-LoginDbDeprecationRunner::~LoginDbDeprecationRunner() = default;
-
-void LoginDbDeprecationRunner::StartExportWithDelay(
-    scoped_refptr<PasswordStoreInterface> password_store) {
-  CHECK(exporter_);
-  LogExportProgress(LoginDbDeprecationExportProgress::kScheduled);
-  base::SequencedTaskRunner::GetCurrentDefault()->PostDelayedTask(
-      FROM_HERE,
-      base::BindOnce(&LoginDbDeprecationRunner::StartExport,
-                     weak_ptr_factory_.GetWeakPtr(), password_store),
-      kLoginDbDeprecationExportDelay);
-}
-
-void LoginDbDeprecationRunner::StartExport(
-    scoped_refptr<PasswordStoreInterface> password_store) {
-  LogExportProgress(LoginDbDeprecationExportProgress::kStarted);
-  exporter_->Start(password_store,
-                   base::BindOnce(&LoginDbDeprecationRunner::ExportFinished,
-                                  weak_ptr_factory_.GetWeakPtr()));
-}
-
-void LoginDbDeprecationRunner::ExportFinished() {
-  LogExportProgress(LoginDbDeprecationExportProgress::kFinished);
-  exporter_.reset();
-}
-
-}  // namespace password_manager
diff --git a/components/password_manager/core/browser/export/login_db_deprecation_runner.h b/components/password_manager/core/browser/export/login_db_deprecation_runner.h
deleted file mode 100644
index 0e2ed399..0000000
--- a/components/password_manager/core/browser/export/login_db_deprecation_runner.h
+++ /dev/null
@@ -1,58 +0,0 @@
-// Copyright 2024 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef COMPONENTS_PASSWORD_MANAGER_CORE_BROWSER_EXPORT_LOGIN_DB_DEPRECATION_RUNNER_H_
-#define COMPONENTS_PASSWORD_MANAGER_CORE_BROWSER_EXPORT_LOGIN_DB_DEPRECATION_RUNNER_H_
-
-#include "base/time/time.h"
-#include "components/keyed_service/core/keyed_service.h"
-#include "components/password_manager/core/browser/export/login_db_deprecation_password_exporter.h"
-#include "components/password_manager/core/browser/export/login_db_deprecation_password_exporter_interface.h"
-#include "components/password_manager/core/browser/password_store/password_store_interface.h"
-
-namespace password_manager {
-
-inline constexpr base::TimeDelta kLoginDbDeprecationExportDelay =
-    base::Seconds(5);
-
-// These values are persisted to logs. Entries should not be renumbered and
-// numeric values should never be reused.
-//
-// LINT.IfChange(LoginDbDeprecationExportProgress)
-enum class LoginDbDeprecationExportProgress {
-  kScheduled = 0,
-  kStarted = 1,
-  kFinished = 2,
-  kMaxValue = kFinished,
-};
-// LINT.ThenChange(/tools/metrics/histograms/metadata/password/enums.xml:LoginDbDeprecationExportProgress)
-
-// Owns and runs the pre-deprecation password export on Android.
-// Once the export is done it will destroy the exporter and clear the memory.
-// The service will only be instantiated for clients whose passwords couldn't
-// be migrated to GMS Core.
-class LoginDbDeprecationRunner : public KeyedService {
- public:
-  explicit LoginDbDeprecationRunner(
-      std::unique_ptr<LoginDbDeprecationPasswordExporterInterface> exporter);
-  LoginDbDeprecationRunner(const LoginDbDeprecationRunner&) = delete;
-  LoginDbDeprecationRunner& operator=(const LoginDbDeprecationRunner&) = delete;
-  ~LoginDbDeprecationRunner() override;
-
-  void StartExportWithDelay(
-      scoped_refptr<PasswordStoreInterface> password_store);
-
- private:
-  void StartExport(scoped_refptr<PasswordStoreInterface> password_store);
-
-  void ExportFinished();
-
-  std::unique_ptr<LoginDbDeprecationPasswordExporterInterface> exporter_;
-
-  base::WeakPtrFactory<LoginDbDeprecationRunner> weak_ptr_factory_{this};
-};
-
-}  // namespace password_manager
-
-#endif  // COMPONENTS_PASSWORD_MANAGER_CORE_BROWSER_EXPORT_LOGIN_DB_DEPRECATION_RUNNER_H_
diff --git a/components/password_manager/core/browser/export/login_db_deprecation_runner_unittest.cc b/components/password_manager/core/browser/export/login_db_deprecation_runner_unittest.cc
deleted file mode 100644
index 7e271d04..0000000
--- a/components/password_manager/core/browser/export/login_db_deprecation_runner_unittest.cc
+++ /dev/null
@@ -1,122 +0,0 @@
-// Copyright 2025 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "components/password_manager/core/browser/export/login_db_deprecation_runner.h"
-
-#include <memory>
-
-#include "base/functional/callback_forward.h"
-#include "base/memory/scoped_refptr.h"
-#include "base/test/metrics/histogram_tester.h"
-#include "base/test/task_environment.h"
-#include "base/time/time.h"
-#include "components/password_manager/core/browser/export/login_db_deprecation_password_exporter_interface.h"
-#include "components/password_manager/core/browser/password_store/mock_password_store_interface.h"
-#include "testing/gmock/include/gmock/gmock.h"
-#include "testing/gtest/include/gtest/gtest.h"
-
-namespace password_manager {
-
-namespace {
-using ::testing::WithArgs;
-
-const std::string kExportProgressHistogram =
-    "PasswordManager.UPM.LoginDbDeprecationExport.Progress";
-
-class MockLoginDbDeprecationPasswordExporter
-    : public LoginDbDeprecationPasswordExporterInterface {
- public:
-  MOCK_METHOD(void,
-              Start,
-              (scoped_refptr<PasswordStoreInterface> password_store,
-               base::OnceClosure export_cleanup_calback),
-              (override));
-};
-}  // namespace
-
-class LoginDbDeprecationdRunnerTest : public testing::Test {
- public:
-  LoginDbDeprecationdRunnerTest() = default;
-
-  void SetUp() override {
-    std::unique_ptr<MockLoginDbDeprecationPasswordExporter>
-        exporter_unique_ptr =
-            std::make_unique<MockLoginDbDeprecationPasswordExporter>();
-    mock_exporter_ = exporter_unique_ptr.get();
-    db_export_runner_ = std::make_unique<LoginDbDeprecationRunner>(
-        std::move(exporter_unique_ptr));
-    mock_password_store_ = base::MakeRefCounted<MockPasswordStoreInterface>();
-  }
-
-  LoginDbDeprecationRunner* db_export_runner() {
-    return db_export_runner_.get();
-  }
-
-  MockLoginDbDeprecationPasswordExporter* mock_exporter() {
-    return mock_exporter_;
-  }
-
-  scoped_refptr<MockPasswordStoreInterface> mock_password_store() {
-    return mock_password_store_;
-  }
-
- protected:
-  base::test::SingleThreadTaskEnvironment task_env_{
-      base::test::TaskEnvironment::TimeSource::MOCK_TIME,
-      base::test::TaskEnvironment::ThreadPoolExecutionMode::QUEUED};
-
- private:
-  raw_ptr<MockLoginDbDeprecationPasswordExporter> mock_exporter_;
-  std::unique_ptr<LoginDbDeprecationRunner> db_export_runner_;
-  scoped_refptr<MockPasswordStoreInterface> mock_password_store_;
-};
-
-TEST_F(LoginDbDeprecationdRunnerTest, ExportScheduledNotStartingBeforeDelay) {
-  base::HistogramTester histogram_tester;
-  EXPECT_CALL(*mock_exporter(), Start).Times(0);
-  db_export_runner()->StartExportWithDelay(mock_password_store());
-
-  // Fast forward by a less time than the task delay.
-  task_env_.FastForwardBy(kLoginDbDeprecationExportDelay / 2);
-  histogram_tester.ExpectUniqueSample(
-      kExportProgressHistogram, LoginDbDeprecationExportProgress::kScheduled,
-      1);
-}
-
-TEST_F(LoginDbDeprecationdRunnerTest, ExportRunsAfterDelayButDoesntFinish) {
-  base::HistogramTester histogram_tester;
-
-  db_export_runner()->StartExportWithDelay(mock_password_store());
-  EXPECT_CALL(*mock_exporter(), Start);
-  task_env_.FastForwardBy(kLoginDbDeprecationExportDelay);
-
-  histogram_tester.ExpectBucketCount(
-      kExportProgressHistogram, LoginDbDeprecationExportProgress::kScheduled,
-      1);
-  histogram_tester.ExpectBucketCount(
-      kExportProgressHistogram, LoginDbDeprecationExportProgress::kStarted, 1);
-  histogram_tester.ExpectBucketCount(
-      kExportProgressHistogram, LoginDbDeprecationExportProgress::kFinished, 0);
-}
-
-TEST_F(LoginDbDeprecationdRunnerTest, ExportRunsAndFinishes) {
-  base::HistogramTester histogram_tester;
-
-  db_export_runner()->StartExportWithDelay(mock_password_store());
-  EXPECT_CALL(*mock_exporter(), Start)
-      .WillOnce(WithArgs<1>([](base::OnceClosure completion_callback) {
-        std::move(completion_callback).Run();
-      }));
-  task_env_.FastForwardBy(kLoginDbDeprecationExportDelay);
-
-  histogram_tester.ExpectBucketCount(
-      kExportProgressHistogram, LoginDbDeprecationExportProgress::kScheduled,
-      1);
-  histogram_tester.ExpectBucketCount(
-      kExportProgressHistogram, LoginDbDeprecationExportProgress::kStarted, 1);
-  histogram_tester.ExpectBucketCount(
-      kExportProgressHistogram, LoginDbDeprecationExportProgress::kFinished, 1);
-}
-
-}  // namespace password_manager
diff --git a/components/password_manager/core/browser/password_manager.cc b/components/password_manager/core/browser/password_manager.cc
index bfaa54b..26e89fc 100644
--- a/components/password_manager/core/browser/password_manager.cc
+++ b/components/password_manager/core/browser/password_manager.cc
@@ -69,7 +69,6 @@
 #include "components/password_manager/core/browser/first_cct_page_load_passwords_ukm_recorder.h"
 #include "components/password_manager/core/browser/password_feature_manager.h"
 #include "components/password_manager/core/browser/password_sync_util.h"
-#include "components/password_manager/core/browser/split_stores_and_local_upm.h"
 #endif  // BUILDFLAG(IS_ANDROID)
 
 #if BUILDFLAG(IS_WIN)
@@ -535,18 +534,10 @@
 #if BUILDFLAG(IS_ANDROID)
   registry->RegisterBooleanPref(prefs::kOfferToSavePasswordsEnabledGMS, true);
   registry->RegisterBooleanPref(prefs::kAutoSignInEnabledGMS, true);
-  RegisterLegacySplitStoresPref(registry);
   registry->RegisterStringPref(prefs::kUPMErrorUIShownTimestamp, "0");
   registry->RegisterIntegerPref(
       prefs::kPasswordGenerationBottomSheetDismissCount, 0);
-  // This pref is used to decide whether the PasswordStore can be connected to
-  // the new Android backend without migrating existing entries in the
-  // LoginDatabase. In doubt, it's best to assume that's not the case, otherwise
-  // passwords might be left behind. In practice, the default value should make
-  // little difference, the pref is always written on startup.
-  registry->RegisterBooleanPref(prefs::kEmptyProfileStoreLoginDatabase, false);
   registry->RegisterBooleanPref(prefs::kUpmAutoExportCsvNeedsDeletion, false);
-  registry->RegisterBooleanPref(prefs::kUpmUnmigratedPasswordsExported, false);
 #endif  // BUILDFLAG(IS_ANDROID)
 
 #if BUILDFLAG(IS_MAC) || BUILDFLAG(IS_WIN) || BUILDFLAG(IS_CHROMEOS)
diff --git a/components/password_manager/core/browser/password_store/login_database.cc b/components/password_manager/core/browser/password_store/login_database.cc
index 913d1b8..250546cb 100644
--- a/components/password_manager/core/browser/password_store/login_database.cc
+++ b/components/password_manager/core/browser/password_store/login_database.cc
@@ -1282,7 +1282,6 @@
     return false;
   }
 
-  TriggerIsEmptyCb();
   LogDatabaseInitError(INIT_OK);
 
   // Keep the database open if everything went well.
@@ -1348,7 +1347,6 @@
 PasswordStoreChangeList LoginDatabase::AddLogin(const PasswordForm& form,
                                                 AddCredentialError* error) {
   TRACE_EVENT0("passwords", "LoginDatabase::AddLogin");
-  absl::Cleanup is_empty_runner = [this] { TriggerIsEmptyCb(); };
   if (error) {
     *error = AddCredentialError::kNone;
   }
@@ -1586,7 +1584,6 @@
 bool LoginDatabase::RemoveLogin(const PasswordForm& form,
                                 PasswordStoreChangeList* changes) {
   TRACE_EVENT0("passwords", "LoginDatabase::RemoveLogin");
-  absl::Cleanup is_empty_runner = [this] { TriggerIsEmptyCb(); };
   if (changes) {
     changes->clear();
   }
@@ -1624,7 +1621,6 @@
   TRACE_EVENT0("passwords", "LoginDatabase::RemoveLoginByPrimaryKey");
   CHECK(changes);
 
-  absl::Cleanup is_empty_runner = [this] { TriggerIsEmptyCb(); };
   changes->clear();
   sql::Statement s1(db_.GetCachedStatement(
       SQL_FROM_HERE, "SELECT * FROM logins WHERE id = ?"));
@@ -1658,7 +1654,6 @@
     base::Time delete_end,
     PasswordStoreChangeList* changes) {
   TRACE_EVENT0("passwords", "LoginDatabase::RemoveLoginsCreatedBetween");
-  absl::Cleanup is_empty_runner = [this] { TriggerIsEmptyCb(); };
   if (changes) {
     changes->clear();
   }
@@ -1909,12 +1904,6 @@
   return true;
 }
 
-bool LoginDatabase::IsEmpty() {
-  sql::Statement count_all_logins(db_.GetCachedStatement(
-      SQL_FROM_HERE, "SELECT EXISTS(SELECT 1 FROM logins)"));
-  return count_all_logins.Step() && count_all_logins.ColumnInt(0) == 0;
-}
-
 bool LoginDatabase::DeleteAndRecreateDatabaseFile() {
   TRACE_EVENT0("passwords", "LoginDatabase::DeleteAndRecreateDatabaseFile");
   DCHECK(db_.is_open());
@@ -1941,7 +1930,6 @@
 
 DatabaseCleanupResult LoginDatabase::DeleteUndecryptableLogins() {
   TRACE_EVENT0("passwords", "LoginDatabase::DeleteUndecryptableLogins");
-  absl::Cleanup is_empty_runner = [this] { TriggerIsEmptyCb(); };
   // If the Keychain in MacOS or the real secret key in Linux is unavailable,
   // don't delete any logins.
   if (!OSCrypt::IsEncryptionAvailable()) {
@@ -2004,10 +1992,6 @@
   return db_.CommitTransactionDeprecated();
 }
 
-void LoginDatabase::SetIsEmptyCb(IsEmptyCallback is_empty_cb) {
-  is_empty_cb_ = std::move(is_empty_cb);
-}
-
 LoginDatabase::SyncMetadataStore::SyncMetadataStore(sql::Database* db)
     : db_(db) {
   CHECK(db);
@@ -2520,10 +2504,4 @@
   return true;
 }
 
-void LoginDatabase::TriggerIsEmptyCb() {
-  if (is_empty_cb_) {
-    is_empty_cb_.Run(IsEmpty());
-  }
-}
-
 }  // namespace password_manager
diff --git a/components/password_manager/core/browser/password_store/login_database.h b/components/password_manager/core/browser/password_store/login_database.h
index f3b632d..7f2a6fa5 100644
--- a/components/password_manager/core/browser/password_store/login_database.h
+++ b/components/password_manager/core/browser/password_store/login_database.h
@@ -50,7 +50,6 @@
 // the login information.
 class LoginDatabase : public EncryptDecryptInterface {
  public:
-  using IsEmptyCallback = base::RepeatingCallback<void(bool)>;
   using DeletingUndecryptablePasswordsEnabled =
       base::StrongAlias<class DeletingUndecryptablePasswordsEnabledTag, bool>;
   using OnUndecryptablePasswordsRemoved =
@@ -167,8 +166,6 @@
   // whether further use of this login database will succeed is unspecified.
   bool DeleteAndRecreateDatabaseFile();
 
-  bool IsEmpty();
-
   // On MacOS, it deletes all logins from the database that cannot be decrypted
   // when encryption key from Keychain is available. If the Keychain is locked,
   // it does nothing and returns ENCRYPTION_UNAVAILABLE. If it's not running on
@@ -186,12 +183,6 @@
   void RollbackTransaction();
   bool CommitTransaction();
 
-  // `is_empty_cb`is called to signal whether the database is empty (i.e.
-  // without any logins *or* blocklists) and whether there are any autofillable
-  // logins. The call happens when initializing the database and when
-  // adding/removing entries, regardless of success.
-  void SetIsEmptyCb(IsEmptyCallback is_empty_cb);
-
 #if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC)
   void SetIsUserDataDirPolicySet(bool is_set) {
     is_user_data_dir_policy_set_ = is_set;
@@ -359,13 +350,8 @@
   bool UpdatePasswordNotes(FormPrimaryKey primary_key,
                            const std::vector<PasswordNote>& notes);
 
-  // If a non-null `is_empty_cb` was passed on construction, computes whether
-  // the DB is empty (SQL statement) and invokes the callback with the result.
-  void TriggerIsEmptyCb();
-
   const base::FilePath db_path_;
   const IsAccountStore is_account_store_;
-  IsEmptyCallback is_empty_cb_ = base::NullCallback();
 
   // `on_undecryptable_passwords_removed_`is called to signal whether user
   // interacted with the kClearUndecryptablePasswords experiment. It is needed
diff --git a/components/password_manager/core/browser/password_store/login_database_unittest.cc b/components/password_manager/core/browser/password_store/login_database_unittest.cc
index 7fbbe18..7efac72 100644
--- a/components/password_manager/core/browser/password_store/login_database_unittest.cc
+++ b/components/password_manager/core/browser/password_store/login_database_unittest.cc
@@ -137,33 +137,6 @@
   return form;
 }
 
-PasswordForm GenerateBlocklistedForm() {
-  PasswordForm form = GenerateExamplePasswordForm();
-  form.url = GURL("http://accounts.blocklisted.com/LoginAuth");
-  form.action = GURL("http://accounts.blocklisted.com/Login");
-  form.signon_realm = "http://www.blocklisted.com/";
-  form.blocked_by_user = true;
-  return form;
-}
-
-PasswordForm GenerateFederatedCredentialForm() {
-  PasswordForm form = GenerateExamplePasswordForm();
-  form.url = GURL("http://accounts.federated.com/LoginAuth");
-  form.action = GURL("http://accounts.federated.com/Login");
-  form.federation_origin =
-      url::SchemeHostPort(GURL("https://accounts.federated.com/"));
-  return form;
-}
-
-PasswordForm GenerateUsernameOnlyForm() {
-  PasswordForm form = GenerateExamplePasswordForm();
-  form.url = GURL("http://accounts.usernameonly.com/LoginAuth");
-  form.action = GURL("http://accounts.usernameonly.com/Login");
-  form.signon_realm = "http://www.usernameonly.com/";
-  form.scheme = PasswordForm::Scheme::kUsernameOnly;
-  return form;
-}
-
 // Helper functions to read the value of the first column of an executed
 // statement if we know its type. You must implement a specialization for
 // every column type you use.
@@ -296,7 +269,6 @@
     OSCryptMocker::SetUp();
 
     db_ = std::make_unique<LoginDatabase>(file_, IsAccountStore(false));
-    db_->SetIsEmptyCb(is_empty_cb_.Get());
     ASSERT_TRUE(
         db_->Init(/*on_undecryptable_passwords_removed=*/base::NullCallback(),
                   /*encryptor=*/nullptr));
@@ -308,7 +280,6 @@
 
   base::ScopedTempDir temp_dir_;
   base::FilePath file_;
-  NiceMock<base::MockCallback<LoginDatabase::IsEmptyCallback>> is_empty_cb_;
   std::unique_ptr<LoginDatabase> db_;
   // A full TaskEnvironment is required instead of only
   // SingleThreadTaskEnvironment because on iOS,
@@ -327,7 +298,6 @@
     OSCryptMocker::SetUp();
 
     db_ = std::make_unique<LoginDatabase>(file_, IsAccountStore(false));
-    db_->SetIsEmptyCb(is_empty_cb_.Get());
     ASSERT_TRUE(
         db_->Init(/*on_undecryptable_passwords_removed=*/base::NullCallback(),
                   /*encryptor=*/encryptor()));
@@ -2421,8 +2391,6 @@
                     /*should_be_corrupted=*/true, /*blocklisted=*/true);
 
   LoginDatabase db(database_path(), IsAccountStore(false));
-  NiceMock<base::MockCallback<LoginDatabase::IsEmptyCallback>> is_empty_cb;
-  db.SetIsEmptyCb(is_empty_cb.Get());
   base::HistogramTester histogram_tester;
   ASSERT_TRUE(
       db.Init(/*on_undecryptable_passwords_removed=*/base::NullCallback(),
@@ -2440,9 +2408,6 @@
   EXPECT_TRUE(result.empty());
 
   // Delete undecryptable logins and make sure we can get valid logins.
-  // `is_empty_cb_` is called more than once because DeleteUndecryptableLogins()
-  // internally calls RemoveLogin() for each form.
-  EXPECT_CALL(is_empty_cb, Run(false)).Times(AnyNumber());
   EXPECT_EQ(DatabaseCleanupResult::kSuccess, db.DeleteUndecryptableLogins());
   EXPECT_TRUE(db.GetAutofillableLogins(&result));
   EXPECT_THAT(result, ElementsAre(HasPrimaryKeyAndEquals(form1)));
@@ -3446,123 +3411,6 @@
   EXPECT_EQ(error, AddCredentialError::kConstraintViolation);
 }
 
-TEST_P(LoginDatabaseTest, IsEmptyCb_InitEmpty) {
-  LoginDatabase db(temp_dir_.GetPath().AppendASCII("DbDirectory"),
-                   IsAccountStore(false));
-  NiceMock<base::MockCallback<LoginDatabase::IsEmptyCallback>> is_empty_cb;
-  db.SetIsEmptyCb(is_empty_cb.Get());
-  EXPECT_CALL(is_empty_cb, Run(true));
-  db.Init(/*on_undecryptable_passwords_removed=*/base::NullCallback(),
-          /*encryptor=*/encryptor());
-}
-
-TEST_P(LoginDatabaseTest, IsEmptyCb_InitNonEmpty) {
-  base::FilePath directory = temp_dir_.GetPath().AppendASCII("DbDirectory");
-  {
-    // Simulate the DB being populated in a previous startup.
-    auto db = std::make_unique<LoginDatabase>(directory, IsAccountStore(false));
-    db->Init(/*on_undecryptable_passwords_removed=*/base::NullCallback(),
-             /*encryptor=*/encryptor());
-    std::ignore =
-        db->AddLogin(GenerateExamplePasswordForm(), /*error=*/nullptr);
-    db.reset();
-  }
-
-  LoginDatabase db(directory, IsAccountStore(false));
-  NiceMock<base::MockCallback<LoginDatabase::IsEmptyCallback>> is_empty_cb;
-  db.SetIsEmptyCb(is_empty_cb.Get());
-  EXPECT_CALL(is_empty_cb, Run(false));
-  db.Init(/*on_undecryptable_passwords_removed=*/base::NullCallback(),
-          /*encryptor=*/encryptor());
-}
-
-TEST_P(LoginDatabaseTest, IsEmptyCb_AddLogin) {
-  ASSERT_TRUE(db().IsEmpty());
-  EXPECT_CALL(is_empty_cb_, Run(false));
-  std::ignore = db().AddLogin(GenerateExamplePasswordForm(), /*error=*/nullptr);
-}
-
-TEST_P(LoginDatabaseTest, IsEmptyCb_RemoveLogin) {
-  PasswordForm normal_form = GenerateExamplePasswordForm();
-  PasswordForm blocklist_form = GenerateBlocklistedForm();
-  PasswordForm federated_form = GenerateFederatedCredentialForm();
-  PasswordForm username_only_form = GenerateUsernameOnlyForm();
-
-  ASSERT_EQ(db().AddLogin(normal_form, /*error=*/nullptr).size(), 1u);
-  ASSERT_EQ(db().AddLogin(blocklist_form, /*error=*/nullptr).size(), 1u);
-  ASSERT_EQ(db().AddLogin(federated_form, /*error=*/nullptr).size(), 1u);
-  ASSERT_EQ(db().AddLogin(username_only_form, /*error=*/nullptr).size(), 1u);
-  ASSERT_FALSE(db().IsEmpty());
-
-  testing::MockFunction<void(int)> check;
-  {
-    testing::InSequence in_sequence;
-    EXPECT_CALL(is_empty_cb_, Run(false)).Times(3);
-    EXPECT_CALL(check, Call(1));
-    EXPECT_CALL(is_empty_cb_, Run(true));
-  }
-  std::ignore = db().RemoveLogin(normal_form, /*changes=*/nullptr);
-  std::ignore = db().RemoveLogin(blocklist_form, /*changes=*/nullptr);
-  std::ignore = db().RemoveLogin(federated_form, /*changes=*/nullptr);
-  check.Call(1);
-  std::ignore = db().RemoveLogin(username_only_form, /*changes=*/nullptr);
-}
-
-TEST_P(LoginDatabaseTest, IsEmptyCb_RemoveLoginByPrimaryKey) {
-  PasswordForm normal_form = GenerateExamplePasswordForm();
-  PasswordForm blocklist_form = GenerateBlocklistedForm();
-  PasswordForm federated_form = GenerateFederatedCredentialForm();
-  PasswordForm username_only_form = GenerateUsernameOnlyForm();
-
-  PasswordStoreChangeList normal_form_changes = db().AddLogin(normal_form);
-  PasswordStoreChangeList blocklist_form_changes =
-      db().AddLogin(blocklist_form);
-  PasswordStoreChangeList federated_form_changes =
-      db().AddLogin(federated_form);
-  PasswordStoreChangeList username_only_form_changes =
-      db().AddLogin(username_only_form);
-
-  ASSERT_EQ(normal_form_changes.size(), 1u);
-  ASSERT_EQ(blocklist_form_changes.size(), 1u);
-  ASSERT_EQ(federated_form_changes.size(), 1u);
-  ASSERT_EQ(username_only_form_changes.size(), 1u);
-  ASSERT_FALSE(db().IsEmpty());
-
-  testing::MockFunction<void(int)> check;
-  {
-    testing::InSequence in_sequence;
-    EXPECT_CALL(is_empty_cb_, Run(false)).Times(3);
-    EXPECT_CALL(check, Call(1));
-    EXPECT_CALL(is_empty_cb_, Run(true));
-  }
-
-  std::ignore = db().RemoveLoginByPrimaryKey(
-      *normal_form_changes[0].form().primary_key, &normal_form_changes);
-  std::ignore = db().RemoveLoginByPrimaryKey(
-      *blocklist_form_changes[0].form().primary_key, &blocklist_form_changes);
-  std::ignore = db().RemoveLoginByPrimaryKey(
-      *federated_form_changes[0].form().primary_key, &federated_form_changes);
-  check.Call(1);
-  std::ignore = db().RemoveLoginByPrimaryKey(
-      *username_only_form_changes[0].form().primary_key,
-      &username_only_form_changes);
-}
-
-TEST_P(LoginDatabaseTest, IsEmptyCb_RemoveLoginsCreatedBetween) {
-  std::ignore = db().AddLogin(GenerateExamplePasswordForm(), /*error=*/nullptr);
-  ASSERT_FALSE(db().IsEmpty());
-  EXPECT_CALL(is_empty_cb_, Run(true));
-  std::ignore = db().RemoveLoginsCreatedBetween(base::Time(), base::Time::Now(),
-                                                /*changes=*/nullptr);
-}
-
-TEST_P(LoginDatabaseTest, IsEmptyCb_DeleteAndRecreateDatabaseFile) {
-  std::ignore = db().AddLogin(GenerateExamplePasswordForm(), /*error=*/nullptr);
-  ASSERT_FALSE(db().IsEmpty());
-  EXPECT_CALL(is_empty_cb_, Run(true));
-  db().DeleteAndRecreateDatabaseFile();
-}
-
 class LoginDatabaseForAccountStoreTest : public testing::Test {
  protected:
   void SetUp() override {
diff --git a/components/password_manager/core/browser/password_store_factory_util.cc b/components/password_manager/core/browser/password_store_factory_util.cc
index 40eb050..0a862ed 100644
--- a/components/password_manager/core/browser/password_store_factory_util.cc
+++ b/components/password_manager/core/browser/password_store_factory_util.cc
@@ -18,7 +18,6 @@
 #include "components/password_manager/core/browser/password_change_backup_password_cleaner.h"
 #include "components/password_manager/core/browser/password_manager_constants.h"
 #include "components/password_manager/core/browser/password_store/login_database.h"
-#include "components/password_manager/core/browser/password_store/password_store_backend.h"
 #include "components/password_manager/core/browser/password_store/password_store_interface.h"
 #include "components/password_manager/core/common/password_manager_pref_names.h"
 #include "components/prefs/pref_service.h"
@@ -107,15 +106,4 @@
   }
 }
 
-void IntermediateCallbackForSettingPrefs(
-    base::WeakPtr<PasswordStoreBackend> backend,
-    LoginDatabase::IsEmptyCallback set_prefs_callback,
-    bool value) {
-  // When a `PasswordStoreBackend` is shut down, the weak pointers are
-  // invalidated.
-  if (backend) {
-    set_prefs_callback.Run(value);
-  }
-}
-
 }  // namespace password_manager
diff --git a/components/password_manager/core/browser/password_store_factory_util.h b/components/password_manager/core/browser/password_store_factory_util.h
index 66fb856..b89d8a85 100644
--- a/components/password_manager/core/browser/password_store_factory_util.h
+++ b/components/password_manager/core/browser/password_store_factory_util.h
@@ -9,7 +9,6 @@
 
 #include "base/files/file_path.h"
 #include "base/functional/callback.h"
-#include "base/memory/weak_ptr.h"
 #include "base/time/time.h"
 #include "components/password_manager/core/browser/password_store/login_database.h"
 #include "components/password_manager/core/browser/password_store/password_store_interface.h"
@@ -23,7 +22,6 @@
 namespace password_manager {
 
 class CredentialsCleanerRunner;
-class PasswordStoreBackend;
 
 // Creates a LoginDatabase. Looks in |db_directory| for the database file.
 // Does not call LoginDatabase::Init() -- to avoid UI jank, that needs to be
@@ -58,22 +56,6 @@
     base::RepeatingCallback<network::mojom::NetworkContext*()>
         network_context_getter);
 
-// Checks that the backend was not yet shut down (i.e. the weak pointer to the
-// backend was not yet invalidated) before calling `set_prefs_callback`.
-//
-// Example of usage:
-//
-// base::BindRepeating(
-//     &password_manager::IntermediateCallbackForSettingPrefs,
-//     backend->AsWeakPtr(), base::BindRepeating(
-//         &password_manager::SetFooPref, pref_service,
-//        password_manager::prefs::
-//            kEmptyProfileStoreLoginDatabase))
-void IntermediateCallbackForSettingPrefs(
-    base::WeakPtr<PasswordStoreBackend> backend,
-    LoginDatabase::IsEmptyCallback set_prefs_callback,
-    bool value);
-
 }  // namespace password_manager
 
 #endif  // COMPONENTS_PASSWORD_MANAGER_CORE_BROWSER_PASSWORD_STORE_FACTORY_UTIL_H_
diff --git a/components/password_manager/core/browser/sharing/password_sharing_recipients_downloader.cc b/components/password_manager/core/browser/sharing/password_sharing_recipients_downloader.cc
index 4ffbede..450e7a3 100644
--- a/components/password_manager/core/browser/sharing/password_sharing_recipients_downloader.cc
+++ b/components/password_manager/core/browser/sharing/password_sharing_recipients_downloader.cc
@@ -99,10 +99,6 @@
   DVLOG(1) << "Access token fetch complete, error state: "
            << static_cast<int>(error.state());
 
-  base::UmaHistogramEnumeration(
-      "PasswordManager.PasswordSharingRecipients.FetchAccessTokenResult",
-      error.state(), GoogleServiceAuthError::NUM_STATES);
-
   CHECK(ongoing_access_token_fetch_);
   ongoing_access_token_fetch_.reset();
 
diff --git a/components/password_manager/core/browser/split_stores_and_local_upm.cc b/components/password_manager/core/browser/split_stores_and_local_upm.cc
index f80fbe5..004ed734 100644
--- a/components/password_manager/core/browser/split_stores_and_local_upm.cc
+++ b/components/password_manager/core/browser/split_stores_and_local_upm.cc
@@ -8,54 +8,18 @@
 #include "base/notreached.h"
 #include "base/strings/string_number_conversions.h"
 #include "build/buildflag.h"
-#include "components/password_manager/core/browser/features/password_features.h"
 #include "components/password_manager/core/browser/password_manager_buildflags.h"
-#include "components/password_manager/core/common/password_manager_pref_names.h"
-#include "components/prefs/pref_registry_simple.h"
-#include "components/prefs/pref_service.h"
 
 namespace password_manager {
 
 namespace {
 
-// DO NOT expose the enum nor the pref name in a header! This is a legacy pref
-// and usages should be limited to GetLegacySplitStoresPref().
-//
-// Do not renumber UseUpmLocalAndSeparateStoresState, values are persisted.
-// Values are also used for metrics recording.
-enum class UseUpmLocalAndSeparateStoresState {
-  kOff = 0,
-  kOffAndMigrationPending = 1,
-  kOn = 2,
-  kMaxValue = kOn
-};
-constexpr char kPasswordsUseUPMLocalAndSeparateStores[] =
-    "passwords_use_upm_local_and_separate_stores";
-
 // Do not expose these constants! Use GetSplitStoresUpmMinVersion() instead.
 const int kSplitStoresUpmMinVersionForNonAuto = 240212000;
 const int kSplitStoresUpmMinVersionForAuto = 241512000;
 
 }  // namespace
 
-void RegisterLegacySplitStoresPref(PrefRegistrySimple* registry) {
-  registry->RegisterIntegerPref(
-      kPasswordsUseUPMLocalAndSeparateStores,
-      static_cast<int>(UseUpmLocalAndSeparateStoresState::kOff));
-}
-
-bool GetLegacySplitStoresPref(const PrefService* pref_service) {
-  switch (static_cast<UseUpmLocalAndSeparateStoresState>(
-      pref_service->GetInteger(kPasswordsUseUPMLocalAndSeparateStores))) {
-    case UseUpmLocalAndSeparateStoresState::kOff:
-    case UseUpmLocalAndSeparateStoresState::kOffAndMigrationPending:
-      return false;
-    case UseUpmLocalAndSeparateStoresState::kOn:
-      return true;
-  }
-  NOTREACHED();
-}
-
 bool IsGmsCoreUpdateRequired() {
 #if BUILDFLAG(USE_LOGIN_DATABASE_AS_BACKEND)
   return false;
@@ -78,11 +42,4 @@
              : kSplitStoresUpmMinVersionForNonAuto;
 }
 
-void SetLegacySplitStoresPrefForTest(PrefService* pref_service, bool enabled) {
-  pref_service->SetInteger(
-      kPasswordsUseUPMLocalAndSeparateStores,
-      static_cast<int>(enabled ? UseUpmLocalAndSeparateStoresState::kOn
-                               : UseUpmLocalAndSeparateStoresState::kOff));
-}
-
 }  // namespace password_manager
diff --git a/components/password_manager/core/browser/split_stores_and_local_upm.h b/components/password_manager/core/browser/split_stores_and_local_upm.h
index 3964f60..b1c06d85 100644
--- a/components/password_manager/core/browser/split_stores_and_local_upm.h
+++ b/components/password_manager/core/browser/split_stores_and_local_upm.h
@@ -5,22 +5,12 @@
 #ifndef COMPONENTS_PASSWORD_MANAGER_CORE_BROWSER_SPLIT_STORES_AND_LOCAL_UPM_H_
 #define COMPONENTS_PASSWORD_MANAGER_CORE_BROWSER_SPLIT_STORES_AND_LOCAL_UPM_H_
 
-class PrefRegistrySimple;
-class PrefService;
+// TODO(crbug.com/442347616): Rename this file and the test fixture. Ideally
+// IsPasswordManagerAvailable() should live next to these methods and they would
+// all be named consistently.
 
 namespace password_manager {
 
-void RegisterLegacySplitStoresPref(PrefRegistrySimple* registry);
-
-// WARNING: Do not add new callers without consulting with ioanap@.
-//
-// This returns the value of a certain pref that used to dictate whether a
-// second PasswordStore should be created. As of 07/2025, Android always creates
-// 2 stores, regardless of the pref. For now, the pref value still exists on
-// disk and is read in specific places for migration reasons. But it is never
-// written in production anymore.
-bool GetLegacySplitStoresPref(const PrefService* pref_service);
-
 // Returns if it is a requirement to update the GMSCore based on whether split
 // password stores are supported or not.
 bool IsGmsCoreUpdateRequired();
@@ -30,8 +20,6 @@
 // non-auto and the form factor can only be checked in runtime.
 int GetSplitStoresUpmMinVersion();
 
-void SetLegacySplitStoresPrefForTest(PrefService* pref_service, bool enabled);
-
 }  // namespace password_manager
 
 #endif  // COMPONENTS_PASSWORD_MANAGER_CORE_BROWSER_SPLIT_STORES_AND_LOCAL_UPM_H_
diff --git a/components/password_manager/core/browser/split_stores_and_local_upm_unittest.cc b/components/password_manager/core/browser/split_stores_and_local_upm_unittest.cc
index b10ec67..479504d 100644
--- a/components/password_manager/core/browser/split_stores_and_local_upm_unittest.cc
+++ b/components/password_manager/core/browser/split_stores_and_local_upm_unittest.cc
@@ -5,12 +5,7 @@
 #include "components/password_manager/core/browser/split_stores_and_local_upm.h"
 
 #include "base/android/device_info.h"
-#include "base/test/scoped_feature_list.h"
-#include "components/password_manager/core/browser/features/password_features.h"
 #include "components/password_manager/core/browser/password_manager_buildflags.h"
-#include "components/password_manager/core/common/password_manager_pref_names.h"
-#include "components/prefs/pref_registry_simple.h"
-#include "components/prefs/testing_pref_service.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 namespace password_manager {
@@ -19,28 +14,6 @@
 constexpr std::string kSplitStoresUpmMinVersionForNonAuto = "240212000";
 constexpr std::string kSplitStoresUpmMinVersionForAuto = "241512000";
 
-class SplitStoresAndLocalUpmTest : public ::testing::Test {
- public:
-  SplitStoresAndLocalUpmTest() {
-    RegisterLegacySplitStoresPref(pref_service_.registry());
-  }
-
-  TestingPrefServiceSimple* pref_service() { return &pref_service_; }
-
- private:
-  TestingPrefServiceSimple pref_service_;
-};
-
-TEST_F(SplitStoresAndLocalUpmTest, UpmPrefOff) {
-  EXPECT_FALSE(GetLegacySplitStoresPref(pref_service()));
-}
-
-TEST_F(SplitStoresAndLocalUpmTest, UpmPrefOn) {
-  SetLegacySplitStoresPrefForTest(pref_service(), true);
-
-  EXPECT_TRUE(GetLegacySplitStoresPref(pref_service()));
-}
-
 struct IsGmsCoreUpdateRequiredTestCase {
   std::string test_case_desc;
   std::string gms_version;
@@ -49,7 +22,7 @@
 };
 
 class SplitStoresAndLocalUpmTestIsGmsCoreUpdateRequired
-    : public SplitStoresAndLocalUpmTest,
+    : public ::testing::Test,
       public ::testing::WithParamInterface<IsGmsCoreUpdateRequiredTestCase> {
  public:
   SplitStoresAndLocalUpmTestIsGmsCoreUpdateRequired() = default;
diff --git a/components/password_manager/core/common/password_manager_pref_names.h b/components/password_manager/core/common/password_manager_pref_names.h
index 3ef5890..b29bd8e 100644
--- a/components/password_manager/core/common/password_manager_pref_names.h
+++ b/components/password_manager/core/common/password_manager_pref_names.h
@@ -76,11 +76,6 @@
 inline constexpr char kAutoSignInEnabledGMS[] =
     "profile.auto_sign_in_enabled_gms";
 
-// A cache of whether the profile LoginDatabase is empty, so that can be checked
-// early on startup.
-inline constexpr char kEmptyProfileStoreLoginDatabase[] =
-    "password_manager.empty_profile_store_login_database";
-
 // Boolean controlling whether the password manager offers to save passwords.
 // If false, the password manager will not save credentials, but it will still
 // fill previously saved ones. This pref is not synced. Its value is set
@@ -140,14 +135,6 @@
 // that deletion should be retried.
 inline constexpr char kUpmAutoExportCsvNeedsDeletion[] =
     "profile.upm_auto_export_csv_needs_deletion";
-
-// Whether the passwords who couldn't be migrated to UPM have been
-// saved as a CSV. The user can then choose to export the CSV out of Chrome
-// via a separate flow. The pref is also set to true if there were no
-// saved passwords. The value is used as a signal that the login db
-// can stop being used.
-inline constexpr char kUpmUnmigratedPasswordsExported[] =
-    "profile.upm_unmigrated_passwords_exported";
 #endif
 
 #if BUILDFLAG(IS_WIN)
diff --git a/components/payments/content/payment_request_state_unittest.cc b/components/payments/content/payment_request_state_unittest.cc
index 7e2b96d..67c5d27d 100644
--- a/components/payments/content/payment_request_state_unittest.cc
+++ b/components/payments/content/payment_request_state_unittest.cc
@@ -477,7 +477,7 @@
 
   // Select an address, nothing should happen until the normalization is
   // completed and the merchant has validated the address.
-  autofill::AutofillProfile profile(AddressCountryCode("JP"));
+  autofill::AutofillProfile profile(autofill::AddressCountryCode("JP"));
   autofill::test::SetProfileInfo(&profile, "Jon", "V.", "Doe",
                                  "jon.doe@exampl.com", "Example Inc",
                                  "Roppongi", "6 Chrome-10-1", "Tokyo", "",
diff --git a/components/persistent_cache/backend_params.cc b/components/persistent_cache/backend_params.cc
index 3e9797d..57c1576 100644
--- a/components/persistent_cache/backend_params.cc
+++ b/components/persistent_cache/backend_params.cc
@@ -17,6 +17,7 @@
   params.db_file_is_writable = db_file_is_writable;
   params.journal_file = journal_file.Duplicate();
   params.journal_file_is_writable = journal_file_is_writable;
+  params.shared_lock = shared_lock.Duplicate();
   params.type = type;
   return params;
 }
diff --git a/components/persistent_cache/backend_params.h b/components/persistent_cache/backend_params.h
index 09125605..bcb64eff 100644
--- a/components/persistent_cache/backend_params.h
+++ b/components/persistent_cache/backend_params.h
@@ -8,6 +8,7 @@
 #include "base/component_export.h"
 #include "base/containers/flat_map.h"
 #include "base/files/file.h"
+#include "base/memory/unsafe_shared_memory_region.h"
 
 namespace persistent_cache {
 
@@ -38,6 +39,7 @@
   bool db_file_is_writable = false;
   base::File journal_file;
   bool journal_file_is_writable = false;
+  base::UnsafeSharedMemoryRegion shared_lock;
 };
 
 }  // namespace persistent_cache
diff --git a/components/persistent_cache/backend_params_manager.cc b/components/persistent_cache/backend_params_manager.cc
index 1090d90..f569ed7 100644
--- a/components/persistent_cache/backend_params_manager.cc
+++ b/components/persistent_cache/backend_params_manager.cc
@@ -23,6 +23,7 @@
 #include "base/strings/utf_string_conversions.h"
 #include "base/task/thread_pool.h"
 #include "components/persistent_cache/sqlite/sqlite_backend_impl.h"
+#include "components/persistent_cache/sqlite/vfs/sandboxed_file.h"
 
 namespace {
 
@@ -416,6 +417,9 @@
     }
   }
 
+  params.shared_lock =
+      base::UnsafeSharedMemoryRegion::Create(sizeof(SharedMemoryLocks));
+
   return params;
 }
 
diff --git a/components/persistent_cache/sqlite/sqlite_backend_impl.cc b/components/persistent_cache/sqlite/sqlite_backend_impl.cc
index bb6b4478..fbd4e520b 100644
--- a/components/persistent_cache/sqlite/sqlite_backend_impl.cc
+++ b/components/persistent_cache/sqlite/sqlite_backend_impl.cc
@@ -9,6 +9,7 @@
 
 #include "base/check_op.h"
 #include "base/containers/span.h"
+#include "base/memory/unsafe_shared_memory_region.h"
 #include "base/strings/string_view_util.h"
 #include "base/trace_event/trace_event.h"
 #include "components/persistent_cache/backend_params.h"
@@ -31,17 +32,24 @@
     BackendParams backend_params) {
   CHECK_EQ(backend_params.type, BackendType::kSqlite);
 
+  base::UnsafeSharedMemoryRegion shared_lock =
+      std::move(backend_params.shared_lock);
+
+  base::WritableSharedMemoryMapping mapped_shared_lock = shared_lock.Map();
+
   using AccessRights = SandboxedFile::AccessRights;
   std::unique_ptr<SandboxedFile> db_file = std::make_unique<SandboxedFile>(
-      std::move(backend_params.db_file), backend_params.db_file_is_writable
-                                             ? AccessRights::kReadWrite
-                                             : AccessRights::kReadOnly);
+      std::move(backend_params.db_file),
+      backend_params.db_file_is_writable ? AccessRights::kReadWrite
+                                         : AccessRights::kReadOnly,
+      std::move(mapped_shared_lock));
   std::unique_ptr<SandboxedFile> journal_file = std::make_unique<SandboxedFile>(
       std::move(backend_params.journal_file),
       backend_params.journal_file_is_writable ? AccessRights::kReadWrite
                                               : AccessRights::kReadOnly);
 
-  return SqliteVfsFileSet(std::move(db_file), std::move(journal_file));
+  return SqliteVfsFileSet(std::move(db_file), std::move(journal_file),
+                          std::move(shared_lock));
 }
 
 SqliteBackendImpl::SqliteBackendImpl(BackendParams backend_params)
@@ -54,6 +62,7 @@
           SqliteSandboxedVfsDelegate::GetInstance()->RegisterSandboxedFiles(
               vfs_file_set_)),
       db_(sql::DatabaseOptions()
+              .set_exclusive_locking(false)
               .set_vfs_name_discouraged(
                   SqliteSandboxedVfsDelegate::kSqliteVfsName)
               // Prevent SQLite from trying to use mmap, as SandboxedVfs does
diff --git a/components/persistent_cache/sqlite/test_utils.cc b/components/persistent_cache/sqlite/test_utils.cc
index 27a200a..a5f8c8d7 100644
--- a/components/persistent_cache/sqlite/test_utils.cc
+++ b/components/persistent_cache/sqlite/test_utils.cc
@@ -11,6 +11,7 @@
 #include "base/files/scoped_temp_dir.h"
 #include "components/persistent_cache/backend_params.h"
 #include "components/persistent_cache/sqlite/vfs/sqlite_database_vfs_file_set.h"
+#include "sql/sandboxed_vfs_file.h"
 
 namespace persistent_cache::test_utils {
 
@@ -31,6 +32,8 @@
   backend_params.db_file_is_writable = true;
   backend_params.journal_file = CreateFile(temporary_subdir, "SECOND");
   backend_params.journal_file_is_writable = true;
+  backend_params.shared_lock =
+      base::UnsafeSharedMemoryRegion::Create(sizeof(SharedMemoryLocks));
   backend_params.type = type;
   return backend_params;
 }
@@ -38,6 +41,10 @@
 SqliteVfsFileSet TestHelper::CreateFilesAndBuildVfsFileSet() {
   base::FilePath temporary_subdir = CreateTemporaryDir();
 
+  base::UnsafeSharedMemoryRegion shared_lock =
+      base::UnsafeSharedMemoryRegion::Create(sizeof(SharedMemoryLocks));
+  base::WritableSharedMemoryMapping mapped_shared_lock = shared_lock.Map();
+
   // Note: Specifically give nonsensical names to the files here to examplify
   // that using a vfs allows for their use not through their actual names.
   base::File db_file = CreateFile(temporary_subdir, "FIRST");
@@ -45,9 +52,11 @@
 
   return SqliteVfsFileSet(
       std::make_unique<SandboxedFile>(std::move(db_file),
-                                      SandboxedFile::AccessRights::kReadWrite),
+                                      SandboxedFile::AccessRights::kReadWrite,
+                                      std::move(mapped_shared_lock)),
       std::make_unique<SandboxedFile>(std::move(journal_file),
-                                      SandboxedFile::AccessRights::kReadWrite));
+                                      SandboxedFile::AccessRights::kReadWrite),
+      std::move(shared_lock));
 }
 
 base::FilePath TestHelper::CreateTemporaryDir() {
diff --git a/components/persistent_cache/sqlite/vfs/sandboxed_file.cc b/components/persistent_cache/sqlite/vfs/sandboxed_file.cc
index 92600df..48085b2 100644
--- a/components/persistent_cache/sqlite/vfs/sandboxed_file.cc
+++ b/components/persistent_cache/sqlite/vfs/sandboxed_file.cc
@@ -9,10 +9,21 @@
 
 namespace persistent_cache {
 
-SandboxedFile::SandboxedFile(base::File file, AccessRights access_rights)
+namespace {
+constexpr uint32_t kMaxReaders = 0x8000000;
+constexpr uint32_t kSharedMask = 0xFFFFFFF;
+constexpr uint32_t kReservedBit = 0x20000000;
+constexpr uint32_t kPendingBit = 0x40000000;
+}  // namespace
+
+SandboxedFile::SandboxedFile(
+    base::File file,
+    AccessRights access_rights,
+    base::WritableSharedMemoryMapping mapped_shared_lock)
     : underlying_file_(std::move(file)),
       access_rights_(access_rights),
-      sqlite_lock_mode_(SQLITE_LOCK_NONE) {}
+      sqlite_lock_mode_(SQLITE_LOCK_NONE),
+      mapped_shared_lock_(std::move(mapped_shared_lock)) {}
 SandboxedFile::~SandboxedFile() = default;
 
 base::File SandboxedFile::TakeUnderlyingFile() {
@@ -120,25 +131,206 @@
   return SQLITE_OK;
 }
 
+// This function implements the database locking mechanism as defined by the
+// SQLite VFS (Virtual File System) interface. It is responsible for escalating
+// locks on the database file to ensure that multiple processes can access the
+// database in a controlled and serialized manner, preventing data corruption.
+//
+// In this shared memory implementation, the lock states are managed directly
+// in a shared memory region accessible by all client processes, rather than
+// relying on traditional file-system locks (like fcntl on Unix or LockFileEx
+// on Windows).
+//
+// The lock implementation mirrors the state transitions of the standard SQLite
+// locking mechanism:
+//
+//     SHARED: Allows multiple readers.
+//     RESERVED: A process signals its intent to write.
+//     PENDING: A writer is waiting for readers to finish.
+//     EXCLUSIVE: A single process has exclusive write access.
+//
+// The valid transitions are:
+//
+//    UNLOCKED -> SHARED
+//    SHARED -> RESERVED
+//    SHARED -> (PENDING) -> EXCLUSIVE
+//    RESERVED -> (PENDING) -> EXCLUSIVE
+//    PENDING -> EXCLUSIVE
+//
+// See original implementation:
+//    https://source.chromium.org/chromium/chromium/src/+/main:third_party/sqlite/src/src/os_win.c;l=3514;drc=4a0b7a332f3aeb27814cfa12dc0ebdbbd994a928
+//
+// Some issues related to file system locks:
+//    https://source.chromium.org/chromium/chromium/src/+/main:third_party/sqlite/src/src/os_unix.c;l=1077;drc=5d60f47001bf64b48abac68ed59621e528144ea4
+//
+// The SQLite core uses two distinct strategies to acquire an EXCLUSIVE lock.
+// This VFS implementation must correctly handle lock requests from both paths.
+//
+// 1. Normal transaction path
+//   The standard database operations (INSERT, UPDATE, BEGIN COMMIT, etc.) on a
+//   healthy database will escalate the lock sequentially:
+//       SHARED -> RESERVED -> PENDING -> EXCLUSIVE.
+//   The intermediate RESERVED lock is mandatory. It signals an intent to write
+//   while still permitting other connections to hold SHARED locks for reading.
+//
+// 2. Hot-journal recovery path
+//   A special case that occurs upon initial connection when a hot-journal is
+//   detected, indicating a previous crash or power loss. A direct request for
+//   an EXCLUSIVE lock is required. In this state, the database is known to be
+//   inconsistent. The RESERVED lock is intentionally skipped because its
+//   purpose is to allow concurrent readers, which would be disastrous. A direct
+//   EXCLUSIVE lock acts as an emergency lockdown, preventing ALL other
+//   connections from reading corrupt data until the recovery process is
+//   complete.
+//
+//   see:
+//     https://source.chromium.org/chromium/chromium/src/+/main:third_party/sqlite/src/src/pager.c;l=5260;drc=65d0312c96cd23958372fac8940314c782a6b03c
 int SandboxedFile::Lock(int mode) {
-  // TODO(https://crbug.com/377475540): Implement a cross-process lock.
-  if (mode > sqlite_lock_mode_) {
-    sqlite_lock_mode_ = mode;
+  // Ensures valid lock states are used (see: sqlite3OsLock(...) assertions).
+  CHECK(mode == SQLITE_LOCK_SHARED || mode == SQLITE_LOCK_RESERVED ||
+        mode == SQLITE_LOCK_EXCLUSIVE);
+  CHECK(mapped_shared_lock_.IsValid());
+
+  // Do nothing if there is already a lock of this type or more restrictive.
+  if (sqlite_lock_mode_ >= mode) {
+    return SQLITE_OK;
   }
-  return SQLITE_OK;
+
+  switch (mode) {
+    case SQLITE_LOCK_SHARED: {
+      // Check the PENDING lock to prevent adding readers (as readers are banned
+      // once we've granted the PENDING_LOCK to a writer).
+      uint32_t shared_state = GetSharedLock()->shared_state.load();
+      if ((shared_state & kPendingBit) != 0) {
+        return SQLITE_BUSY;
+      }
+
+      // Add this connection to the shared readers.
+      uint32_t acquired_shared_state = (shared_state + 1);
+      CHECK_LT(shared_state & kSharedMask, kMaxReaders);
+      if (!GetSharedLock()->shared_state.compare_exchange_weak(
+              shared_state, acquired_shared_state)) {
+        return SQLITE_BUSY;
+      }
+
+      // The SHARED lock was successfully acquired.
+      sqlite_lock_mode_ = SQLITE_LOCK_SHARED;
+      return SQLITE_OK;
+    }
+
+    case SQLITE_LOCK_RESERVED: {
+      // To acquire a RESERVED lock, the current connection must already have
+      // a shared access to it.
+      CHECK_EQ(sqlite_lock_mode_, SQLITE_LOCK_SHARED);
+
+      // Acquire a RESERVED lock to prevent a different writer to declare its
+      // intention to modify the database. At this point, readers are still
+      // allowed to get a SHARED lock on the database.
+      const uint32_t acquired_shared_state =
+          GetSharedLock()->shared_state.fetch_or(kReservedBit);
+      if ((acquired_shared_state & kReservedBit) != 0) {
+        return SQLITE_BUSY;
+      }
+
+      // The RESERVED lock was successfully acquired.
+      sqlite_lock_mode_ = SQLITE_LOCK_RESERVED;
+      return SQLITE_OK;
+    }
+
+    case SQLITE_LOCK_EXCLUSIVE: {
+      // Acquiring an EXCLUSIVE lock may happen through multiple calls to
+      // SandboxedFile::Lock(...) and the PENDING lock may be kept between these
+      // calls.
+
+      // To acquire an EXCLUSIVE lock, the current connection must already have
+      // at least SHARED lock. Owning RESERVED lock not mandatory.
+      CHECK_GE(sqlite_lock_mode_, SQLITE_LOCK_SHARED);
+
+      // Acquire a PENDING lock, if not already acquired. Keep it until the
+      // EXCLUSIVE lock is obtained to let readers leaving the critical section
+      // while avoiding new readers to get into it.
+      if (sqlite_lock_mode_ < SQLITE_LOCK_PENDING) {
+        const uint32_t acquired_shared_state =
+            GetSharedLock()->shared_state.fetch_or(kPendingBit);
+        if ((acquired_shared_state & kPendingBit) != 0) {
+          // This connection is not the owner of the PENDING lock.
+          return SQLITE_BUSY;
+        }
+        // The PENDING lock was acquired. Keep it for subsequent calls until
+        // every SHARED locks are released.
+        sqlite_lock_mode_ = SQLITE_LOCK_PENDING;
+      }
+
+      // Do not grant the EXCLUSIVE lock until other readers released their
+      // SHARED locks. This connection still owns and keeps a SHARED lock.
+      const uint32_t readers_shared_state =
+          GetSharedLock()->shared_state.load();
+      if ((readers_shared_state & kSharedMask) != 1) {
+        return SQLITE_BUSY;
+      }
+
+      // There is no active SHARED locks except for this connection. The PENDING
+      // lock is owned by this connection so it is valid to grant the EXCLUSIVE
+      // lock.
+      sqlite_lock_mode_ = SQLITE_LOCK_EXCLUSIVE;
+      return SQLITE_OK;
+    }
+  }
+
+  return SQLITE_IOERR_LOCK;
 }
 
+// This function is the counterpart to Lock and is responsible for reducing the
+// lock level on the database file. This typically happens after a transaction
+// is committed or rolled back, or when a process holding a write lock is
+// ready to allow other readers in.
+//
+// The valid transitions are:
+//
+//    SHARED -> UNLOCKED
+//    EXCLUSIVE -> UNLOCKED
+//    EXCLUSIVE -> SHARED
+//
+// It is also valid to release any pending state (PENDING or RESERVED) even if
+// the state never went to EXCLUSIVE. This can happen when a connection give up
+// on trying to get an EXCLUSIVE lock.
 int SandboxedFile::Unlock(int mode) {
-  // TODO(https://crbug.com/377475540): Implement a cross-process lock.
-  if (mode < sqlite_lock_mode_) {
-    sqlite_lock_mode_ = mode;
+  // Ensures valid lock states are used (see: sqlite3OsUnlock(...) assertions).
+  CHECK(mode == SQLITE_LOCK_NONE || mode == SQLITE_LOCK_SHARED);
+  CHECK(mapped_shared_lock_.IsValid());
+
+  // Do nothing if there is already a lock of this type or less restrictive.
+  if (sqlite_lock_mode_ <= mode) {
+    return SQLITE_OK;
   }
+
+  // Release the RESERVED lock if we had it.
+  if (sqlite_lock_mode_ >= SQLITE_LOCK_RESERVED) {
+    // It is valid to have a PENDING or EXCLUSIVE lock without the RESERVED.
+    GetSharedLock()->shared_state.fetch_and(~kReservedBit);
+  }
+
+  // Release the SHARED lock if no longer needed.
+  if (mode == SQLITE_LOCK_NONE) {
+    const uint32_t readers_shared_state =
+        GetSharedLock()->shared_state.fetch_sub(1);
+    CHECK_GE(readers_shared_state & 0xFFFFFFF, 1u);
+  }
+
+  // Release the PENDING lock if we had it.
+  if (sqlite_lock_mode_ >= SQLITE_LOCK_PENDING) {
+    GetSharedLock()->shared_state.fetch_and(~kPendingBit);
+  }
+
+  // Lock was successfully released.
+  sqlite_lock_mode_ = mode;
   return SQLITE_OK;
 }
 
 int SandboxedFile::CheckReservedLock(int* has_reserved_lock) {
-  // TODO(https://crbug.com/377475540): Implement a cross-process lock.
-  *has_reserved_lock = sqlite_lock_mode_ >= SQLITE_LOCK_RESERVED;
+  CHECK(mapped_shared_lock_.IsValid());
+  uint32_t shared_state = GetSharedLock()->shared_state.load();
+  *has_reserved_lock = (shared_state & kReservedBit) != 0;
   return SQLITE_OK;
 }
 
@@ -187,4 +379,9 @@
   return SQLITE_IOERR;
 }
 
+SharedMemoryLocks* SandboxedFile::GetSharedLock() {
+  CHECK(mapped_shared_lock_.IsValid());
+  return mapped_shared_lock_.GetMemoryAs<SharedMemoryLocks>();
+}
+
 }  // namespace persistent_cache
diff --git a/components/persistent_cache/sqlite/vfs/sandboxed_file.h b/components/persistent_cache/sqlite/vfs/sandboxed_file.h
index 317eeedf..c59b7692 100644
--- a/components/persistent_cache/sqlite/vfs/sandboxed_file.h
+++ b/components/persistent_cache/sqlite/vfs/sandboxed_file.h
@@ -5,12 +5,37 @@
 #ifndef COMPONENTS_PERSISTENT_CACHE_SQLITE_VFS_SANDBOXED_FILE_H_
 #define COMPONENTS_PERSISTENT_CACHE_SQLITE_VFS_SANDBOXED_FILE_H_
 
+#include <cstdint>
+
 #include "base/component_export.h"
 #include "base/files/file.h"
+#include "base/memory/shared_memory_safety_checker.h"
+#include "base/memory/unsafe_shared_memory_region.h"
 #include "sql/sandboxed_vfs_file.h"
 
 namespace persistent_cache {
 
+// The lock shared state is encoded over 32-bits:
+//    [ pending ][ reserved ][ readers ]
+//
+// Readers   : Number of clients holding a shared lock.
+//
+// Reserved  : Whether a client holds a reserved lock. Clients may acquire a
+//             shared lock while a client holds a reserved lock. Only one client
+//             can hold a reserved lock.
+//
+// Pending   : Whether a client holds a pending lock. No new client may acquire
+//             a shared lock when a client holds a pending lock (but clients
+//             that had a shared lock before may not have released it yet when a
+//             client acquires the pending lock). The client may also hold the
+//             reserved lock, but that's not always the case.
+//
+// Exclusive : A client is considered to have "exclusive" locking when it holds
+//             the pending lock and it is the only one holding a shared lock.
+struct SharedMemoryLocks {
+  base::subtle::SharedAtomic<uint32_t> shared_state;
+};
+
 // Represents a file to be exposed to sql::Database via
 // SqliteSandboxedVfsDelegate.
 //
@@ -22,7 +47,10 @@
  public:
   enum class AccessRights { kReadWrite, kReadOnly };
 
-  SandboxedFile(base::File file, AccessRights access_rights);
+  SandboxedFile(base::File file,
+                AccessRights access_rights,
+                base::WritableSharedMemoryMapping mapped_shared_lock =
+                    base::WritableSharedMemoryMapping());
   SandboxedFile(SandboxedFile& other) = delete;
   SandboxedFile& operator=(const SandboxedFile& other) = delete;
   SandboxedFile(SandboxedFile&& other) = delete;
@@ -76,13 +104,24 @@
   int Fetch(sqlite3_int64 offset, int size, void** result) override;
   int Unfetch(sqlite3_int64 offset, void* fetch_result) override;
 
+  int LockModeForTesting() const { return sqlite_lock_mode_; }
+
  private:
+  // Returns a pointer to the SharedMemoryLocks struct, which is shared across
+  // other instances of SandboxedFile via shared memory
+  SharedMemoryLocks* GetSharedLock();
+
   base::File underlying_file_;
   base::File opened_file_;
   AccessRights access_rights_;
 
-  // One of the SQLite locking mode constants.
+  // One of the SQLite locking mode constants which represent the current lock
+  // state of this connection (see: https://www.sqlite.org/lockingv3.html).
   int sqlite_lock_mode_;
+
+  // The actual shared locks across processes to implement the SQLite algorithm
+  // and from which `sqlite_lock_mode_` is coming from.
+  base::WritableSharedMemoryMapping mapped_shared_lock_;
 };
 
 }  // namespace persistent_cache
diff --git a/components/persistent_cache/sqlite/vfs/sandboxed_file_unittest.cc b/components/persistent_cache/sqlite/vfs/sandboxed_file_unittest.cc
index d7dfada9..7e05b54 100644
--- a/components/persistent_cache/sqlite/vfs/sandboxed_file_unittest.cc
+++ b/components/persistent_cache/sqlite/vfs/sandboxed_file_unittest.cc
@@ -7,6 +7,7 @@
 #include "base/containers/span.h"
 #include "base/files/scoped_temp_dir.h"
 #include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/sqlite/sqlite3.h"
 
 namespace persistent_cache {
 
@@ -18,14 +19,18 @@
  public:
   void SetUp() override {
     ASSERT_TRUE(temporary_directory_.CreateUniqueTempDir());
+    shared_region_ =
+        base::UnsafeSharedMemoryRegion::Create(sizeof(SharedMemoryLocks));
   }
 
   std::unique_ptr<SandboxedFile> CreateEmptyFile(const std::string& file_name) {
+    base::WritableSharedMemoryMapping mapped_shared_lock = shared_region_.Map();
+
     return std::make_unique<SandboxedFile>(
         base::File(temporary_directory_.GetPath().AppendASCII(file_name),
                    base::File::FLAG_CREATE_ALWAYS | base::File::FLAG_READ |
                        base::File::FLAG_WRITE),
-        SandboxedFile::AccessRights::kReadWrite);
+        SandboxedFile::AccessRights::kReadWrite, std::move(mapped_shared_lock));
   }
 
   // Simulate an OpenFile from the VFS delegate.
@@ -54,6 +59,7 @@
  private:
   base::ScopedTempDir temporary_directory_;
   std::vector<uint8_t> buffer_;
+  base::UnsafeSharedMemoryRegion shared_region_;
 };
 
 TEST_F(SandboxedFileTest, OpenClose) {
@@ -230,6 +236,188 @@
   EXPECT_EQ(GetReadBuffer(), base::span(expected_buffer));
 }
 
+TEST_F(SandboxedFileTest, LockBasics) {
+  std::unique_ptr<SandboxedFile> file = CreateEmptyFile("lock");
+  EXPECT_EQ(file->LockModeForTesting(), SQLITE_LOCK_NONE);
+
+  EXPECT_EQ(file->Lock(SQLITE_LOCK_SHARED), SQLITE_OK);
+  EXPECT_EQ(file->LockModeForTesting(), SQLITE_LOCK_SHARED);
+
+  EXPECT_EQ(file->Lock(SQLITE_LOCK_RESERVED), SQLITE_OK);
+  EXPECT_EQ(file->LockModeForTesting(), SQLITE_LOCK_RESERVED);
+
+  EXPECT_EQ(file->Lock(SQLITE_LOCK_EXCLUSIVE), SQLITE_OK);
+  EXPECT_EQ(file->LockModeForTesting(), SQLITE_LOCK_EXCLUSIVE);
+
+  EXPECT_EQ(file->Unlock(SQLITE_LOCK_SHARED), SQLITE_OK);
+  EXPECT_EQ(file->LockModeForTesting(), SQLITE_LOCK_SHARED);
+
+  EXPECT_EQ(file->Unlock(SQLITE_LOCK_NONE), SQLITE_OK);
+  EXPECT_EQ(file->LockModeForTesting(), SQLITE_LOCK_NONE);
+}
+
+TEST_F(SandboxedFileTest, AcquireSameLockLevel) {
+  std::unique_ptr<SandboxedFile> file = CreateEmptyFile("lock");
+
+  EXPECT_EQ(file->Lock(SQLITE_LOCK_SHARED), SQLITE_OK);
+  EXPECT_EQ(file->LockModeForTesting(), SQLITE_LOCK_SHARED);
+  EXPECT_EQ(file->Lock(SQLITE_LOCK_RESERVED), SQLITE_OK);
+  EXPECT_EQ(file->LockModeForTesting(), SQLITE_LOCK_RESERVED);
+  EXPECT_EQ(file->Lock(SQLITE_LOCK_EXCLUSIVE), SQLITE_OK);
+  EXPECT_EQ(file->LockModeForTesting(), SQLITE_LOCK_EXCLUSIVE);
+
+  // Re-acquire EXCLUSIVE must be valid.
+  EXPECT_EQ(file->Lock(SQLITE_LOCK_EXCLUSIVE), SQLITE_OK);
+  EXPECT_EQ(file->LockModeForTesting(), SQLITE_LOCK_EXCLUSIVE);
+}
+
+TEST_F(SandboxedFileTest, AcquireLowerLockLevel) {
+  std::unique_ptr<SandboxedFile> file = CreateEmptyFile("lock");
+
+  EXPECT_EQ(file->Lock(SQLITE_LOCK_SHARED), SQLITE_OK);
+  EXPECT_EQ(file->LockModeForTesting(), SQLITE_LOCK_SHARED);
+  EXPECT_EQ(file->Lock(SQLITE_LOCK_RESERVED), SQLITE_OK);
+  EXPECT_EQ(file->LockModeForTesting(), SQLITE_LOCK_RESERVED);
+  EXPECT_EQ(file->Lock(SQLITE_LOCK_EXCLUSIVE), SQLITE_OK);
+  EXPECT_EQ(file->LockModeForTesting(), SQLITE_LOCK_EXCLUSIVE);
+
+  // Acquiring the lower SHARED lock when the connection is at EXCLUSIVE must be
+  // valid.
+  EXPECT_EQ(file->Lock(SQLITE_LOCK_SHARED), SQLITE_OK);
+  EXPECT_EQ(file->LockModeForTesting(), SQLITE_LOCK_EXCLUSIVE);
+}
+
+TEST_F(SandboxedFileTest, UnlockWhenNone) {
+  std::unique_ptr<SandboxedFile> file = CreateEmptyFile("lock");
+
+  EXPECT_EQ(file->Unlock(SQLITE_LOCK_NONE), SQLITE_OK);
+  EXPECT_EQ(file->LockModeForTesting(), SQLITE_LOCK_NONE);
+}
+
+TEST_F(SandboxedFileTest, UnlockToNone) {
+  std::unique_ptr<SandboxedFile> file = CreateEmptyFile("lock");
+
+  EXPECT_EQ(file->Lock(SQLITE_LOCK_SHARED), SQLITE_OK);
+  EXPECT_EQ(file->LockModeForTesting(), SQLITE_LOCK_SHARED);
+  EXPECT_EQ(file->Lock(SQLITE_LOCK_RESERVED), SQLITE_OK);
+  EXPECT_EQ(file->LockModeForTesting(), SQLITE_LOCK_RESERVED);
+  EXPECT_EQ(file->Lock(SQLITE_LOCK_EXCLUSIVE), SQLITE_OK);
+  EXPECT_EQ(file->LockModeForTesting(), SQLITE_LOCK_EXCLUSIVE);
+
+  // Downgrade the connection from EXCLUSIVE to NONE.
+  EXPECT_EQ(file->Unlock(SQLITE_LOCK_NONE), SQLITE_OK);
+  EXPECT_EQ(file->LockModeForTesting(), SQLITE_LOCK_NONE);
+}
+
+TEST_F(SandboxedFileTest, UnlockToShared) {
+  std::unique_ptr<SandboxedFile> file = CreateEmptyFile("lock");
+
+  EXPECT_EQ(file->Lock(SQLITE_LOCK_SHARED), SQLITE_OK);
+  EXPECT_EQ(file->LockModeForTesting(), SQLITE_LOCK_SHARED);
+  EXPECT_EQ(file->Lock(SQLITE_LOCK_RESERVED), SQLITE_OK);
+  EXPECT_EQ(file->LockModeForTesting(), SQLITE_LOCK_RESERVED);
+  EXPECT_EQ(file->Lock(SQLITE_LOCK_EXCLUSIVE), SQLITE_OK);
+  EXPECT_EQ(file->LockModeForTesting(), SQLITE_LOCK_EXCLUSIVE);
+
+  // Downgrade the connection from EXCLUSIVE to SHARED.
+  EXPECT_EQ(file->Unlock(SQLITE_LOCK_SHARED), SQLITE_OK);
+  EXPECT_EQ(file->LockModeForTesting(), SQLITE_LOCK_SHARED);
+}
+
+TEST_F(SandboxedFileTest, MultipleLocks) {
+  std::unique_ptr<SandboxedFile> reader1 = CreateEmptyFile("multi-lock");
+  std::unique_ptr<SandboxedFile> reader2 = CreateEmptyFile("multi-lock");
+  std::unique_ptr<SandboxedFile> reader3 = CreateEmptyFile("multi-lock");
+  std::unique_ptr<SandboxedFile> writer1 = CreateEmptyFile("multi-lock");
+  std::unique_ptr<SandboxedFile> writer2 = CreateEmptyFile("multi-lock");
+
+  // Take SHARED lock for the first reader.
+  EXPECT_EQ(reader1->Lock(SQLITE_LOCK_SHARED), SQLITE_OK);
+  EXPECT_EQ(reader1->LockModeForTesting(), SQLITE_LOCK_SHARED);
+
+  // First writer is getting the RESERVED lock.
+  EXPECT_EQ(writer1->Lock(SQLITE_LOCK_SHARED), SQLITE_OK);
+  EXPECT_EQ(writer1->LockModeForTesting(), SQLITE_LOCK_SHARED);
+  EXPECT_EQ(writer1->Lock(SQLITE_LOCK_RESERVED), SQLITE_OK);
+  EXPECT_EQ(writer1->LockModeForTesting(), SQLITE_LOCK_RESERVED);
+
+  // Second reader should be able to get a SHARED lock even with the RESERVED
+  // lock.
+  EXPECT_EQ(reader2->Lock(SQLITE_LOCK_SHARED), SQLITE_OK);
+  EXPECT_EQ(reader2->LockModeForTesting(), SQLITE_LOCK_SHARED);
+
+  // Second writer can't get the RESERVED lock.
+  EXPECT_EQ(writer2->Lock(SQLITE_LOCK_SHARED), SQLITE_OK);
+  EXPECT_EQ(writer2->LockModeForTesting(), SQLITE_LOCK_SHARED);
+  EXPECT_EQ(writer2->Lock(SQLITE_LOCK_RESERVED), SQLITE_BUSY);
+  EXPECT_EQ(writer2->LockModeForTesting(), SQLITE_LOCK_SHARED);
+
+  // Try to upgrade the lock to EXCLUSIVE but fails since there are pending
+  // readers.
+  EXPECT_EQ(writer1->Lock(SQLITE_LOCK_EXCLUSIVE), SQLITE_BUSY);
+  EXPECT_EQ(writer1->LockModeForTesting(), SQLITE_LOCK_PENDING);
+
+  // No new writer can get the lock EXCLUSIVE.
+  EXPECT_EQ(writer2->Lock(SQLITE_LOCK_RESERVED), SQLITE_BUSY);
+  EXPECT_EQ(writer2->LockModeForTesting(), SQLITE_LOCK_SHARED);
+  EXPECT_EQ(writer2->Lock(SQLITE_LOCK_EXCLUSIVE), SQLITE_BUSY);
+  EXPECT_EQ(writer2->LockModeForTesting(), SQLITE_LOCK_SHARED);
+  EXPECT_EQ(writer2->Unlock(SQLITE_LOCK_NONE), SQLITE_OK);
+  EXPECT_EQ(writer2->LockModeForTesting(), SQLITE_LOCK_NONE);
+
+  // No new reader can enter while a writer has the PENDING lock.
+  EXPECT_EQ(reader3->Lock(SQLITE_LOCK_SHARED), SQLITE_BUSY);
+
+  // Release a SHARED lock.
+  EXPECT_EQ(reader1->Unlock(SQLITE_LOCK_NONE), SQLITE_OK);
+  EXPECT_EQ(reader1->LockModeForTesting(), SQLITE_LOCK_NONE);
+
+  // Try to upgrade the lock to EXCLUSIVE but fails since there are still
+  // pending readers.
+  EXPECT_EQ(writer1->Lock(SQLITE_LOCK_EXCLUSIVE), SQLITE_BUSY);
+  EXPECT_EQ(writer1->LockModeForTesting(), SQLITE_LOCK_PENDING);
+
+  // Release the last SHARED lock.
+  EXPECT_EQ(reader2->Unlock(SQLITE_LOCK_NONE), SQLITE_OK);
+  EXPECT_EQ(reader2->LockModeForTesting(), SQLITE_LOCK_NONE);
+
+  // The write lock can now be upgraded to EXCLUSIVE.
+  EXPECT_EQ(writer1->Lock(SQLITE_LOCK_EXCLUSIVE), SQLITE_OK);
+  EXPECT_EQ(writer1->LockModeForTesting(), SQLITE_LOCK_EXCLUSIVE);
+
+  // No other writer can get the lock.
+  EXPECT_EQ(writer2->Lock(SQLITE_LOCK_SHARED), SQLITE_BUSY);
+  EXPECT_EQ(writer2->LockModeForTesting(), SQLITE_LOCK_NONE);
+
+  // Unlock the writer.
+  EXPECT_EQ(writer1->Unlock(SQLITE_LOCK_NONE), SQLITE_OK);
+  EXPECT_EQ(writer1->LockModeForTesting(), SQLITE_LOCK_NONE);
+
+  // Second writer can now get the lock.
+  EXPECT_EQ(writer2->Lock(SQLITE_LOCK_SHARED), SQLITE_OK);
+  EXPECT_EQ(writer2->LockModeForTesting(), SQLITE_LOCK_SHARED);
+  EXPECT_EQ(writer2->Lock(SQLITE_LOCK_RESERVED), SQLITE_OK);
+  EXPECT_EQ(writer2->LockModeForTesting(), SQLITE_LOCK_RESERVED);
+  EXPECT_EQ(writer2->Lock(SQLITE_LOCK_EXCLUSIVE), SQLITE_OK);
+  EXPECT_EQ(writer2->LockModeForTesting(), SQLITE_LOCK_EXCLUSIVE);
+  EXPECT_EQ(writer2->Unlock(SQLITE_LOCK_NONE), SQLITE_OK);
+  EXPECT_EQ(writer2->LockModeForTesting(), SQLITE_LOCK_NONE);
+}
+
+TEST_F(SandboxedFileTest, LockHotJournal) {
+  std::unique_ptr<SandboxedFile> file = CreateEmptyFile("lock");
+
+  EXPECT_EQ(file->Lock(SQLITE_LOCK_SHARED), SQLITE_OK);
+  EXPECT_EQ(file->LockModeForTesting(), SQLITE_LOCK_SHARED);
+
+  // It is valid to skip the RESERVED lock in the presence of am hot-journal.
+  // There can't be any RESERVED on any connection at that point since any
+  // attempt to open the database will get into the same state (an hot-journal
+  // that forced an exclusive lock).
+  EXPECT_EQ(file->Lock(SQLITE_LOCK_EXCLUSIVE), SQLITE_OK);
+  EXPECT_EQ(file->LockModeForTesting(), SQLITE_LOCK_EXCLUSIVE);
+}
+
 }  // namespace
 
 }  // namespace persistent_cache
diff --git a/components/persistent_cache/sqlite/vfs/sqlite_database_vfs_file_set.cc b/components/persistent_cache/sqlite/vfs/sqlite_database_vfs_file_set.cc
index c9e2e397..f42b374 100644
--- a/components/persistent_cache/sqlite/vfs/sqlite_database_vfs_file_set.cc
+++ b/components/persistent_cache/sqlite/vfs/sqlite_database_vfs_file_set.cc
@@ -22,11 +22,13 @@
 namespace persistent_cache {
 
 SqliteVfsFileSet::SqliteVfsFileSet(std::unique_ptr<SandboxedFile> db_file,
-                                   std::unique_ptr<SandboxedFile> journal_file)
+                                   std::unique_ptr<SandboxedFile> journal_file,
+                                   base::UnsafeSharedMemoryRegion shared_lock)
     : db_file_(std::move(db_file)),
       journal_file_(std::move(journal_file)),
-      virtual_fs_path_(base::NumberToString(
-          g_file_set_id_generator.fetch_add(1, std::memory_order_relaxed))),
+      shared_lock_(std::move(shared_lock)),
+      virtual_fs_path_(
+          base::NumberToString(g_file_set_id_generator.fetch_add(1))),
       read_only_(db_file_->access_rights() ==
                  SandboxedFile::AccessRights::kReadOnly) {
   // It makes no sense to have a file writeable and not the other.
diff --git a/components/persistent_cache/sqlite/vfs/sqlite_database_vfs_file_set.h b/components/persistent_cache/sqlite/vfs/sqlite_database_vfs_file_set.h
index e999f6c..09ac282 100644
--- a/components/persistent_cache/sqlite/vfs/sqlite_database_vfs_file_set.h
+++ b/components/persistent_cache/sqlite/vfs/sqlite_database_vfs_file_set.h
@@ -11,6 +11,7 @@
 #include "base/component_export.h"
 #include "base/files/file.h"
 #include "base/files/file_path.h"
+#include "base/memory/unsafe_shared_memory_region.h"
 #include "components/persistent_cache/sqlite/vfs/sandboxed_file.h"
 
 namespace persistent_cache {
@@ -22,7 +23,8 @@
 class COMPONENT_EXPORT(PERSISTENT_CACHE) SqliteVfsFileSet {
  public:
   SqliteVfsFileSet(std::unique_ptr<SandboxedFile> db_file,
-                   std::unique_ptr<SandboxedFile> journal_file);
+                   std::unique_ptr<SandboxedFile> journal_file,
+                   base::UnsafeSharedMemoryRegion shared_lock);
   SqliteVfsFileSet(SqliteVfsFileSet& other) = delete;
   SqliteVfsFileSet& operator=(const SqliteVfsFileSet& other) = delete;
   SqliteVfsFileSet(SqliteVfsFileSet&& other);
@@ -44,6 +46,7 @@
 
   std::unique_ptr<SandboxedFile> db_file_;
   std::unique_ptr<SandboxedFile> journal_file_;
+  base::UnsafeSharedMemoryRegion shared_lock_;
 
   // SQLite databases use standard naming for their files. Since the vfs might
   // register files for many databases at once it needs some way to
diff --git a/components/persistent_cache/sqlite/vfs/sqlite_sandboxed_vfs.h b/components/persistent_cache/sqlite/vfs/sqlite_sandboxed_vfs.h
index 3a9b049d..9e95ad5 100644
--- a/components/persistent_cache/sqlite/vfs/sqlite_sandboxed_vfs.h
+++ b/components/persistent_cache/sqlite/vfs/sqlite_sandboxed_vfs.h
@@ -35,7 +35,7 @@
 //  // Register the file set for use by any sql::Database in this process
 //  // that uses the `SqliteSandboxedVfsDelegate`.
 //  auto unregister_runner = SqliteSandboxedVfsDelelegate::GetInstance()
-//    ->RegisterSandboxedFiles(vfs_file_set.Copy());
+//    ->RegisterSandboxedFiles(vfs_file_set);
 //
 //  // Create an `sql::Database` which uses the
 //  // `SqliteSandboxedVfsDelegate`.
diff --git a/components/policy/core/common/generate_policy_source_unittest.cc b/components/policy/core/common/generate_policy_source_unittest.cc
index bf3b3a49..cd472b8 100644
--- a/components/policy/core/common/generate_policy_source_unittest.cc
+++ b/components/policy/core/common/generate_policy_source_unittest.cc
@@ -137,7 +137,7 @@
   EXPECT_TRUE(*next == nullptr);
 #endif  // !BUILDFLAG(IS_IOS)
 
-#if !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS)
+#if !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS) && !BUILDFLAG(IS_FUCHSIA)
   subschema = schema.GetProperty(key::kExtensionSettings);
   ASSERT_TRUE(subschema.valid());
   ASSERT_EQ(base::Value::Type::DICT, subschema.type());
diff --git a/components/policy/core/common/policy_loader_common.cc b/components/policy/core/common/policy_loader_common.cc
index fec1b1c..a9d2f87 100644
--- a/components/policy/core/common/policy_loader_common.cc
+++ b/components/policy/core/common/policy_loader_common.cc
@@ -16,7 +16,7 @@
 namespace policy {
 
 namespace {
-#if !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS)
+#if !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS) && !BUILDFLAG(IS_FUCHSIA)
 // Duplicate the extension constants in order to avoid extension dependency.
 // However, those values below must be synced with files in extension folders.
 // In long term, we can refactor the code and create an interface for sensitive
@@ -83,7 +83,7 @@
   base::UmaHistogramSparse("EnterpriseCheck.InvalidPolicies", details->id);
 }
 
-#if !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS)
+#if !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS) && !BUILDFLAG(IS_FUCHSIA)
 // Marks the sensitive ExtensionInstallForceList policy entries, returns true if
 // there is any sensitive entries in the policy.
 bool FilterSensitiveExtensionsInstallForcelist(PolicyMap::Entry* map_entry) {
@@ -183,7 +183,7 @@
 
 void FilterSensitivePolicies(PolicyMap* policy) {
   int invalid_policies = 0;
-#if !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS)
+#if !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS) && !BUILDFLAG(IS_FUCHSIA)
   if (FilterSensitiveExtensionsInstallForcelist(
           policy->GetMutable(key::kExtensionInstallForcelist))) {
     invalid_policies++;
diff --git a/components/policy/core/common/policy_merger.cc b/components/policy/core/common/policy_merger.cc
index 18732f0..4b99691 100644
--- a/components/policy/core/common/policy_merger.cc
+++ b/components/policy/core/common/policy_merger.cc
@@ -19,7 +19,7 @@
 
 namespace {
 
-#if !BUILDFLAG(IS_IOS) && !BUILDFLAG(IS_ANDROID)
+#if !BUILDFLAG(IS_IOS) && !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_FUCHSIA)
 constexpr const char* kDictionaryPoliciesToMerge[] = {
 #if BUILDFLAG(IS_CHROMEOS)
     key::kExtensionSettings,       key::kDeviceLoginScreenPowerManagement,
@@ -29,7 +29,8 @@
     key::kExtensionSettings,
 #endif  //  BUILDFLAG(IS_CHROMEOS)
 };
-#endif  // !BUILDFLAG(IS_IOS) && !BUILDFLAG(IS_ANDROID)
+#endif  // !BUILDFLAG(IS_IOS) && !BUILDFLAG(IS_ANDROID) &&
+        // !BUILDFLAG(IS_FUCHSIA)
 
 }  // namespace
 
@@ -186,7 +187,7 @@
 
 PolicyDictionaryMerger::PolicyDictionaryMerger(
     base::flat_set<std::string> policies_to_merge)
-#if BUILDFLAG(IS_IOS) || BUILDFLAG(IS_ANDROID)
+#if BUILDFLAG(IS_IOS) || BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_FUCHSIA)
     : policies_to_merge_(std::move(policies_to_merge)){}
 #else
     : policies_to_merge_(std::move(policies_to_merge)),
diff --git a/components/policy/core/common/policy_service_impl_unittest.cc b/components/policy/core/common/policy_service_impl_unittest.cc
index 4e687b6..3682796 100644
--- a/components/policy/core/common/policy_service_impl_unittest.cc
+++ b/components/policy/core/common/policy_service_impl_unittest.cc
@@ -1245,7 +1245,8 @@
   policy_service_->RemoveObserver(POLICY_DOMAIN_SIGNIN_EXTENSIONS, &observer);
 }
 
-#if !BUILDFLAG(IS_CHROMEOS) && !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS)
+#if !BUILDFLAG(IS_CHROMEOS) && !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS) && \
+    !BUILDFLAG(IS_FUCHSIA)
 TEST_F(PolicyServiceTest, DictionaryPoliciesMerging) {
   const PolicyNamespace chrome_namespace(POLICY_DOMAIN_CHROME, std::string());
 
@@ -1462,7 +1463,7 @@
   EXPECT_TRUE(VerifyPolicies(chrome_namespace, expected_chrome));
 }
 #endif  // !BUILDFLAG(IS_CHROMEOS) && !BUILDFLAG(IS_ANDROID) &&
-        // !BUILDFLAG(IS_IOS)
+        // !BUILDFLAG(IS_IOS) && !BUILDFLAG(IS_FUCHSIA)
 
 TEST_F(PolicyServiceTest, ListsPoliciesMerging) {
   const PolicyNamespace chrome_namespace(POLICY_DOMAIN_CHROME, std::string());
diff --git a/components/policy/resources/templates/policy_definitions/Extensions/BlockExternalExtensions.yaml b/components/policy/resources/templates/policy_definitions/Extensions/BlockExternalExtensions.yaml
index a06e3c2..ca36934 100644
--- a/components/policy/resources/templates/policy_definitions/Extensions/BlockExternalExtensions.yaml
+++ b/components/policy/resources/templates/policy_definitions/Extensions/BlockExternalExtensions.yaml
@@ -13,8 +13,6 @@
 features:
   dynamic_refresh: false
   per_profile: true
-future_on:
-- fuchsia
 items:
 - caption: Block installation of external extensions
   value: true
diff --git a/components/policy/resources/templates/policy_definitions/Extensions/ExtensionAllowedTypes.yaml b/components/policy/resources/templates/policy_definitions/Extensions/ExtensionAllowedTypes.yaml
index f7f6039..e3d92463 100644
--- a/components/policy/resources/templates/policy_definitions/Extensions/ExtensionAllowedTypes.yaml
+++ b/components/policy/resources/templates/policy_definitions/Extensions/ExtensionAllowedTypes.yaml
@@ -28,8 +28,6 @@
 features:
   dynamic_refresh: true
   per_profile: true
-future_on:
-- fuchsia
 items:
 - caption: Extension
   name: extension
diff --git a/components/policy/resources/templates/policy_definitions/Extensions/ExtensionExtendedBackgroundLifetimeForPortConnectionsToUrls.yaml b/components/policy/resources/templates/policy_definitions/Extensions/ExtensionExtendedBackgroundLifetimeForPortConnectionsToUrls.yaml
index cfe942c..8fa5a17 100644
--- a/components/policy/resources/templates/policy_definitions/Extensions/ExtensionExtendedBackgroundLifetimeForPortConnectionsToUrls.yaml
+++ b/components/policy/resources/templates/policy_definitions/Extensions/ExtensionExtendedBackgroundLifetimeForPortConnectionsToUrls.yaml
@@ -24,8 +24,6 @@
 supported_on:
 - chrome.*:112-
 - chrome_os:112-
-future_on:
-- fuchsia
 owners:
 - mpetrisor@chromium.org
 - chromeos-commercial-identity@google.com
diff --git a/components/policy/resources/templates/policy_definitions/Extensions/ExtensionInstallAllowlist.yaml b/components/policy/resources/templates/policy_definitions/Extensions/ExtensionInstallAllowlist.yaml
index 20a826c..68d09b6 100644
--- a/components/policy/resources/templates/policy_definitions/Extensions/ExtensionInstallAllowlist.yaml
+++ b/components/policy/resources/templates/policy_definitions/Extensions/ExtensionInstallAllowlist.yaml
@@ -13,7 +13,6 @@
   per_profile: true
 future_on:
 - android
-- fuchsia
 label: Extension IDs to exempt from the blocklist
 owners:
 - rdevlin.cronin@chromium.org
diff --git a/components/policy/resources/templates/policy_definitions/Extensions/ExtensionInstallBlocklist.yaml b/components/policy/resources/templates/policy_definitions/Extensions/ExtensionInstallBlocklist.yaml
index 58db0d8..771d045 100644
--- a/components/policy/resources/templates/policy_definitions/Extensions/ExtensionInstallBlocklist.yaml
+++ b/components/policy/resources/templates/policy_definitions/Extensions/ExtensionInstallBlocklist.yaml
@@ -13,7 +13,6 @@
   per_profile: true
 future_on:
 - android
-- fuchsia
 label: Extension IDs the user should be prevented from installing (or * for all)
 owners:
 - lazyboy@chromium.org
diff --git a/components/policy/resources/templates/policy_definitions/Extensions/ExtensionInstallForcelist.yaml b/components/policy/resources/templates/policy_definitions/Extensions/ExtensionInstallForcelist.yaml
index 4b7aacfa..4bc3f295 100644
--- a/components/policy/resources/templates/policy_definitions/Extensions/ExtensionInstallForcelist.yaml
+++ b/components/policy/resources/templates/policy_definitions/Extensions/ExtensionInstallForcelist.yaml
@@ -27,7 +27,6 @@
   per_profile: true
 future_on:
 - android
-- fuchsia
 label: Extension/App IDs and update URLs to be silently installed
 owners:
 - karandeepb@chromium.org
diff --git a/components/policy/resources/templates/policy_definitions/Extensions/ExtensionInstallSources.yaml b/components/policy/resources/templates/policy_definitions/Extensions/ExtensionInstallSources.yaml
index b72ca78..47ef3289 100644
--- a/components/policy/resources/templates/policy_definitions/Extensions/ExtensionInstallSources.yaml
+++ b/components/policy/resources/templates/policy_definitions/Extensions/ExtensionInstallSources.yaml
@@ -10,8 +10,6 @@
 features:
   dynamic_refresh: true
   per_profile: true
-future_on:
-- fuchsia
 label: URL patterns to allow extension, app, and user script installs from
 owners:
 - dbertoni@chromium.org
diff --git a/components/policy/resources/templates/policy_definitions/Extensions/ExtensionManifestV2Availability.yaml b/components/policy/resources/templates/policy_definitions/Extensions/ExtensionManifestV2Availability.yaml
index 7077a77..ebc2a63 100644
--- a/components/policy/resources/templates/policy_definitions/Extensions/ExtensionManifestV2Availability.yaml
+++ b/components/policy/resources/templates/policy_definitions/Extensions/ExtensionManifestV2Availability.yaml
@@ -20,8 +20,6 @@
 - chrome.*:110-138
 - chrome_os:110-138
 deprecated: true
-future_on:
-- fuchsia
 features:
   dynamic_refresh: true
   per_profile: true
diff --git a/components/policy/resources/templates/policy_definitions/Extensions/ExtensionSettings.yaml b/components/policy/resources/templates/policy_definitions/Extensions/ExtensionSettings.yaml
index 7b2d947d..01164cc6 100644
--- a/components/policy/resources/templates/policy_definitions/Extensions/ExtensionSettings.yaml
+++ b/components/policy/resources/templates/policy_definitions/Extensions/ExtensionSettings.yaml
@@ -62,7 +62,6 @@
   per_profile: true
 future_on:
 - android
-- fuchsia
 owners:
 - finnur@chromium.org
 - file://extensions/OWNERS
diff --git a/components/policy/resources/templates/policy_definitions/Extensions/MandatoryExtensionsForIncognitoNavigation.yaml b/components/policy/resources/templates/policy_definitions/Extensions/MandatoryExtensionsForIncognitoNavigation.yaml
index 8dd7ffd..709f9659 100644
--- a/components/policy/resources/templates/policy_definitions/Extensions/MandatoryExtensionsForIncognitoNavigation.yaml
+++ b/components/policy/resources/templates/policy_definitions/Extensions/MandatoryExtensionsForIncognitoNavigation.yaml
@@ -14,7 +14,6 @@
   per_profile: true
 future_on:
 - android
-- fuchsia
 - chrome.*
 supported_on:
 - chrome_os:114-
diff --git a/components/policy/test/data/pref_mapping/BlockExternalExtensions.json b/components/policy/test/data/pref_mapping/BlockExternalExtensions.json
index 9efdd12..6e536a34 100644
--- a/components/policy/test/data/pref_mapping/BlockExternalExtensions.json
+++ b/components/policy/test/data/pref_mapping/BlockExternalExtensions.json
@@ -3,8 +3,7 @@
     "os": [
       "win",
       "linux",
-      "mac",
-      "fuchsia"
+      "mac"
     ],
     "simple_policy_pref_mapping_test": {
       "pref_name": "extensions.block_external_extensions",
diff --git a/components/policy/test/data/pref_mapping/ExtensionAllowedTypes.json b/components/policy/test/data/pref_mapping/ExtensionAllowedTypes.json
index c8b8759..9ac33e7 100644
--- a/components/policy/test/data/pref_mapping/ExtensionAllowedTypes.json
+++ b/components/policy/test/data/pref_mapping/ExtensionAllowedTypes.json
@@ -4,8 +4,7 @@
       "win",
       "linux",
       "mac",
-      "chromeos",
-      "fuchsia"
+      "chromeos"
     ],
     "policy_pref_mapping_tests": [
       {
diff --git a/components/policy/test/data/pref_mapping/ExtensionExtendedBackgroundLifetimeForPortConnectionsToUrls.json b/components/policy/test/data/pref_mapping/ExtensionExtendedBackgroundLifetimeForPortConnectionsToUrls.json
index 0b89bf4..b0e4aac 100644
--- a/components/policy/test/data/pref_mapping/ExtensionExtendedBackgroundLifetimeForPortConnectionsToUrls.json
+++ b/components/policy/test/data/pref_mapping/ExtensionExtendedBackgroundLifetimeForPortConnectionsToUrls.json
@@ -4,8 +4,7 @@
       "win",
       "linux",
       "mac",
-      "chromeos",
-      "fuchsia"
+      "chromeos"
     ],
     "simple_policy_pref_mapping_test": {
       "pref_name": "extensions.extended_background_lifetime_urls",
diff --git a/components/policy/test/data/pref_mapping/ExtensionInstallAllowlist.json b/components/policy/test/data/pref_mapping/ExtensionInstallAllowlist.json
index 4d30a2a..af70ae0 100644
--- a/components/policy/test/data/pref_mapping/ExtensionInstallAllowlist.json
+++ b/components/policy/test/data/pref_mapping/ExtensionInstallAllowlist.json
@@ -5,8 +5,7 @@
       "linux",
       "mac",
       "chromeos",
-      "desktop_android",
-      "fuchsia"
+      "desktop_android"
     ],
     "simple_policy_pref_mapping_test": {
       "pref_name": "extensions.install.allowlist",
diff --git a/components/policy/test/data/pref_mapping/ExtensionInstallBlocklist.json b/components/policy/test/data/pref_mapping/ExtensionInstallBlocklist.json
index 33c4520..505c70c 100644
--- a/components/policy/test/data/pref_mapping/ExtensionInstallBlocklist.json
+++ b/components/policy/test/data/pref_mapping/ExtensionInstallBlocklist.json
@@ -5,8 +5,7 @@
       "linux",
       "mac",
       "chromeos",
-      "desktop_android",
-      "fuchsia"
+      "desktop_android"
     ],
     "simple_policy_pref_mapping_test": {
       "pref_name": "extensions.install.denylist",
diff --git a/components/policy/test/data/pref_mapping/ExtensionInstallForcelist.json b/components/policy/test/data/pref_mapping/ExtensionInstallForcelist.json
index f06aa88..4d98a1b 100644
--- a/components/policy/test/data/pref_mapping/ExtensionInstallForcelist.json
+++ b/components/policy/test/data/pref_mapping/ExtensionInstallForcelist.json
@@ -5,8 +5,7 @@
       "linux",
       "mac",
       "chromeos",
-      "desktop_android",
-      "fuchsia"
+      "desktop_android"
     ],
     "policy_pref_mapping_tests": [
       {
diff --git a/components/policy/test/data/pref_mapping/ExtensionInstallSources.json b/components/policy/test/data/pref_mapping/ExtensionInstallSources.json
index 2da5ab1..8d6daa85 100644
--- a/components/policy/test/data/pref_mapping/ExtensionInstallSources.json
+++ b/components/policy/test/data/pref_mapping/ExtensionInstallSources.json
@@ -4,8 +4,7 @@
       "win",
       "linux",
       "mac",
-      "chromeos",
-      "fuchsia"
+      "chromeos"
     ],
     "simple_policy_pref_mapping_test": {
       "pref_name": "extensions.allowed_install_sites",
diff --git a/components/policy/test/data/pref_mapping/ExtensionInstallTypeBlocklist.json b/components/policy/test/data/pref_mapping/ExtensionInstallTypeBlocklist.json
index b040741..77c8bae5 100644
--- a/components/policy/test/data/pref_mapping/ExtensionInstallTypeBlocklist.json
+++ b/components/policy/test/data/pref_mapping/ExtensionInstallTypeBlocklist.json
@@ -3,8 +3,7 @@
     "os": [
       "win",
       "linux",
-      "mac",
-      "fuchsia"
+      "mac"
     ],
     "simple_policy_pref_mapping_test": {
       "pref_name": "extensions.extension_install_type_blocklist",
diff --git a/components/policy/test/data/pref_mapping/ExtensionSettings.json b/components/policy/test/data/pref_mapping/ExtensionSettings.json
index 46265cb..a347241d 100644
--- a/components/policy/test/data/pref_mapping/ExtensionSettings.json
+++ b/components/policy/test/data/pref_mapping/ExtensionSettings.json
@@ -5,8 +5,7 @@
       "linux",
       "mac",
       "chromeos",
-      "desktop_android",
-      "fuchsia"
+      "desktop_android"
     ],
     "simple_policy_pref_mapping_test": {
       "pref_name": "extensions.management",
diff --git a/components/policy/test_support/request_handler_for_policy_unittest.cc b/components/policy/test_support/request_handler_for_policy_unittest.cc
index 415f36c..71e6c08 100644
--- a/components/policy/test_support/request_handler_for_policy_unittest.cc
+++ b/components/policy/test_support/request_handler_for_policy_unittest.cc
@@ -28,10 +28,11 @@
 constexpr char kMachineName[] = "machine_name";
 constexpr char kPolicyInvalidationTopic[] = "policy_invalidation_topic";
 constexpr char kUsername[] = "user-for-policy@example.com";
-#if !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS)
+#if !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS) && !BUILDFLAG(IS_FUCHSIA)
 constexpr char kPublicAccountEntityId[] = "test_user";
 constexpr char kExtensionId[] = "extension_id";
-#endif  // !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS)
+#endif  // !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS) &&
+        // !BUILDFLAG(IS_FUCHSIA)
 
 }  // namespace
 
@@ -372,7 +373,7 @@
             GetSignatureTypeParam());
 }
 
-#if !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS)
+#if !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS) && !BUILDFLAG(IS_FUCHSIA)
 TEST_F(RequestHandlerForPolicyTest,
        HandleRequest_Success_UnsignedExtensionPolicies) {
   ClientStorage::ClientInfo client_info;
@@ -515,7 +516,8 @@
   EXPECT_EQ(public_account_fetch_response.policy_data_signature_type(),
             GetSignatureTypeParam());
 }
-#endif  // !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS)
+#endif  // !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS) &&
+        // !BUILDFLAG(IS_FUCHSIA)
 
 INSTANTIATE_TEST_SUITE_P(
     SignatureType,
diff --git a/components/safe_browsing/core/common/safe_browsing_prefs.cc b/components/safe_browsing/core/common/safe_browsing_prefs.cc
index 637e113b..0b8b86e 100644
--- a/components/safe_browsing/core/common/safe_browsing_prefs.cc
+++ b/components/safe_browsing/core/common/safe_browsing_prefs.cc
@@ -205,6 +205,8 @@
 }
 
 void RegisterProfilePrefs(PrefRegistrySimple* registry) {
+  registry->RegisterBooleanPref(
+      prefs::kJavascriptOptimizerBlockedForUnfamiliarSites, false);
   // TODO(crbug.com/422747384): Implement correct logic to set bundle level
   // based on user's safe browsing status.
   registry->RegisterIntegerPref(prefs::kSecuritySettingsBundle,
diff --git a/components/safe_browsing/core/common/safe_browsing_prefs.h b/components/safe_browsing/core/common/safe_browsing_prefs.h
index c0bd977..c9f874e3 100644
--- a/components/safe_browsing/core/common/safe_browsing_prefs.h
+++ b/components/safe_browsing/core/common/safe_browsing_prefs.h
@@ -283,6 +283,13 @@
 // the user is in.
 inline constexpr char kSecuritySettingsBundle[] = "safebrowsing.bundle";
 
+// A boolean indicating whether the user selected on chrome://settings to
+// disable the JavaScript optimizer on unfamiliar sites for improved security.
+// The site-familiarity computation is done locally based on the user's
+// browsing habits.
+inline constexpr char kJavascriptOptimizerBlockedForUnfamiliarSites[] =
+    "safebrowsing.javascript_optimizer_blocked_for_unfamiliar_sites";
+
 }  // namespace prefs
 
 namespace safe_browsing {
diff --git a/components/test/data/optimization_guide/iframe_mixed_cross_site.html b/components/test/data/optimization_guide/iframe_mixed_cross_site.html
new file mode 100644
index 0000000..88a88fa
--- /dev/null
+++ b/components/test/data/optimization_guide/iframe_mixed_cross_site.html
@@ -0,0 +1,5 @@
+<html><head><title>mixed cross-site iframe test</title></head>
+<body>
+<iframe src="/title1.html" id="frame1"></iframe>
+<iframe src="/cross-site/b.com/title1.html" id="frame2"></iframe>
+</body></html>
diff --git a/components/test/data/optimization_guide/unit_tests_bundle_data.filelist b/components/test/data/optimization_guide/unit_tests_bundle_data.filelist
index f510bb0..a109b37 100644
--- a/components/test/data/optimization_guide/unit_tests_bundle_data.filelist
+++ b/components/test/data/optimization_guide/unit_tests_bundle_data.filelist
@@ -20,6 +20,7 @@
 //components/test/data/optimization_guide/for_label.html
 //components/test/data/optimization_guide/i18n_visibility_test_model.tflite
 //components/test/data/optimization_guide/iframe_cross_site.html
+//components/test/data/optimization_guide/iframe_mixed_cross_site.html
 //components/test/data/optimization_guide/iframe_same_site.html
 //components/test/data/optimization_guide/invalid_model.crx3
 //components/test/data/optimization_guide/label_not_actionable.html
diff --git a/components/viz/service/BUILD.gn b/components/viz/service/BUILD.gn
index 4017fef..d38cb3a 100644
--- a/components/viz/service/BUILD.gn
+++ b/components/viz/service/BUILD.gn
@@ -172,6 +172,8 @@
     "display_embedder/skia_output_surface_impl_on_gpu.h",
     "display_embedder/skia_output_surface_impl_on_gpu_debug_capture.cc",
     "display_embedder/skia_output_surface_impl_on_gpu_debug_capture.h",
+    "display_embedder/skia_output_surface_shared_image_interface.cc",
+    "display_embedder/skia_output_surface_shared_image_interface.h",
     "display_embedder/skia_render_copy_results.cc",
     "display_embedder/skia_render_copy_results.h",
     "display_embedder/software_output_surface.cc",
diff --git a/components/viz/service/display_embedder/skia_output_surface_impl.cc b/components/viz/service/display_embedder/skia_output_surface_impl.cc
index 7c13c5a..8c31acc 100644
--- a/components/viz/service/display_embedder/skia_output_surface_impl.cc
+++ b/components/viz/service/display_embedder/skia_output_surface_impl.cc
@@ -18,6 +18,7 @@
 #include "base/functional/callback_forward.h"
 #include "base/functional/callback_helpers.h"
 #include "base/memory/memory_pressure_listener.h"
+#include "base/memory/scoped_refptr.h"
 #include "base/no_destructor.h"
 #include "base/observer_list.h"
 #include "base/strings/stringprintf.h"
@@ -45,6 +46,7 @@
 #include "components/viz/service/display_embedder/image_context_impl.h"
 #include "components/viz/service/display_embedder/skia_output_surface_dependency.h"
 #include "components/viz/service/display_embedder/skia_output_surface_impl_on_gpu.h"
+#include "components/viz/service/display_embedder/skia_output_surface_shared_image_interface.h"
 #include "gpu/command_buffer/client/shared_image_interface.h"
 #include "gpu/command_buffer/common/swap_buffers_complete_params.h"
 #include "gpu/command_buffer/common/sync_token.h"
@@ -405,6 +407,11 @@
   }
   DCHECK(render_pass_image_cache_.empty());
 
+  // Break reference cycle.
+  if (shared_image_interface_) {
+    shared_image_interface_->DetachOutputSurfaceOnHostThread();
+  }
+
   // Save a copy of this pointer before moving it into the task. Tasks that are
   // already enqueud may need to use it before |impl_on_gpu_| is destroyed.
   SkiaOutputSurfaceImplOnGpu* impl_on_gpu = impl_on_gpu_.get();
@@ -1090,6 +1097,17 @@
   // |capabilities_| will be initialized in InitializeOnGpuThread(), so have to
   // wait.
   FlushGpuTasks(SyncMode::kWaitForTasksFinished);
+  // Exit early if creation of `impl_on_gpu` fails.
+  if (!result) {
+    return false;
+  }
+
+  // We need to wait for `impl_on_gpu_` to be initialized to initialize
+  // `shared_image_interface_`, so can't do that before now.
+  DCHECK(impl_on_gpu_);
+  shared_image_interface_ =
+      base::MakeRefCounted<SkiaOutputSurfaceSharedImageInterface>(
+          *this, *impl_on_gpu_);
 
   if (capabilities_.damage_area_from_skia_output_device) {
     damage_of_current_buffer_.emplace();
@@ -1104,7 +1122,7 @@
         graphite_recorder_, graphite_cache_controller_.get(),
         dependency_->GetClientTaskRunner());
   }
-  return result;
+  return true;
 }
 
 void SkiaOutputSurfaceImpl::InitializeOnGpuThread(bool* result) {
@@ -1548,9 +1566,8 @@
 }
 
 gpu::SyncToken SkiaOutputSurfaceImpl::Flush() {
-  gpu::SyncToken sync_token(
-      gpu::CommandBufferNamespace::VIZ_SKIA_OUTPUT_SURFACE,
-      impl_on_gpu_->command_buffer_id(), ++sync_fence_release_);
+  DCHECK(shared_image_interface_);
+  gpu::SyncToken sync_token = shared_image_interface_->GenNextSyncToken();
   sync_token.SetVerifyFlush();
   FlushGpuTasks(SyncMode::kNoWait, sync_token);
   return sync_token;
diff --git a/components/viz/service/display_embedder/skia_output_surface_impl.h b/components/viz/service/display_embedder/skia_output_surface_impl.h
index 7c0d00e..5bf966a 100644
--- a/components/viz/service/display_embedder/skia_output_surface_impl.h
+++ b/components/viz/service/display_embedder/skia_output_surface_impl.h
@@ -67,6 +67,7 @@
 class ImageContextImpl;
 class SkiaOutputSurfaceDependency;
 class SkiaOutputSurfaceImplOnGpu;
+class SkiaOutputSurfaceSharedImageInterface;
 
 // The SkiaOutputSurface implementation. It is the output surface for
 // SkiaRenderer. It lives on the compositor thread, but it will post tasks
@@ -217,6 +218,8 @@
       CopyOutputRequest::CopyOutputRequestCallback result_callback) override;
 
  private:
+  friend class SkiaOutputSurfaceSharedImageInterface;
+
   bool Initialize();
   void InitializeOnGpuThread(bool* result);
   GrSurfaceCharacterization CreateGrSurfaceCharacterizationRenderPass(
@@ -288,7 +291,7 @@
   // Observers for context lost.
   base::ObserverList<ContextLostObserver>::Unchecked observers_;
 
-  uint64_t sync_fence_release_ = 0;
+  scoped_refptr<SkiaOutputSurfaceSharedImageInterface> shared_image_interface_;
   raw_ptr<SkiaOutputSurfaceDependency> dependency_;
   UpdateVSyncParametersCallback update_vsync_parameters_callback_;
 
diff --git a/components/viz/service/display_embedder/skia_output_surface_impl_on_gpu.h b/components/viz/service/display_embedder/skia_output_surface_impl_on_gpu.h
index e615ffb2..bf30a2d 100644
--- a/components/viz/service/display_embedder/skia_output_surface_impl_on_gpu.h
+++ b/components/viz/service/display_embedder/skia_output_surface_impl_on_gpu.h
@@ -286,6 +286,10 @@
     return context_state_.get();
   }
 
+  gpu::SharedImageFactory* shared_image_factory() const {
+    return shared_image_factory_.get();
+  }
+
 #if BUILDFLAG(ENABLE_VULKAN) && BUILDFLAG(IS_CHROMEOS) && \
     BUILDFLAG(USE_V4L2_CODEC)
   void DetileOverlay(gpu::Mailbox input,
diff --git a/components/viz/service/display_embedder/skia_output_surface_shared_image_interface.cc b/components/viz/service/display_embedder/skia_output_surface_shared_image_interface.cc
new file mode 100644
index 0000000..1cf7aec
--- /dev/null
+++ b/components/viz/service/display_embedder/skia_output_surface_shared_image_interface.cc
@@ -0,0 +1,112 @@
+// 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/viz/service/display_embedder/skia_output_surface_shared_image_interface.h"
+
+#include <cstddef>
+#include <utility>
+
+#include "base/check.h"
+#include "base/functional/bind.h"
+#include "base/task/sequenced_task_runner.h"
+#include "components/viz/service/display_embedder/skia_output_surface_impl.h"
+#include "components/viz/service/display_embedder/skia_output_surface_impl_on_gpu.h"
+#include "gpu/command_buffer/common/constants.h"
+#include "gpu/command_buffer/common/sync_token.h"
+#include "gpu/command_buffer/service/shared_image_interface_in_process_base.h"
+
+namespace viz {
+
+SkiaOutputSurfaceSharedImageInterface::SkiaOutputSurfaceSharedImageInterface(
+    SkiaOutputSurfaceImpl& output_surface,
+    SkiaOutputSurfaceImplOnGpu& output_surface_on_gpu)
+    : gpu::SharedImageInterfaceInProcessBase(
+          gpu::CommandBufferNamespace::VIZ_SKIA_OUTPUT_SURFACE,
+          output_surface_on_gpu.command_buffer_id(),
+          /*verify_creation_sync_token=*/true),
+      output_surface_(&output_surface),
+      output_surface_on_gpu_(&output_surface_on_gpu),
+      host_task_runner_(base::SequencedTaskRunner::GetCurrentDefault()) {}
+
+SkiaOutputSurfaceSharedImageInterface::
+    ~SkiaOutputSurfaceSharedImageInterface() = default;
+
+void SkiaOutputSurfaceSharedImageInterface::DetachOutputSurfaceOnHostThread() {
+  DCHECK(host_task_runner_->RunsTasksInCurrentSequence());
+
+  if (!output_surface_) {
+    return;
+  }
+  output_surface_->EnqueueGpuTask(
+      base::BindOnce(&SkiaOutputSurfaceSharedImageInterface::
+                         DetachOutputSurfaceOnGpuThread,
+                     this),
+      /*sync_tokens=*/{}, /*make_current=*/true, /*need_framebuffer=*/false);
+  output_surface_ = nullptr;
+}
+
+void SkiaOutputSurfaceSharedImageInterface::DetachOutputSurfaceOnGpuThread() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(gpu_sequence_checker_);
+  output_surface_on_gpu_ = nullptr;
+}
+
+void SkiaOutputSurfaceSharedImageInterface::ScheduleGpuTask(
+    base::OnceClosure task,
+    std::vector<gpu::SyncToken> sync_token_fences,
+    const gpu::SyncToken& release) {
+  // Here we assume that either this is a rendering task scheduled by
+  // `SkiaOutputSurfaceImpl`, in which case the output surface will eventually
+  // call `FlushGpuTasks()` and signal the `release` token (or one that subsumes
+  // it), or the `release` token is empty coming from another task.
+
+  if (host_task_runner_->RunsTasksInCurrentSequence()) {
+    ScheduleGpuTaskOnHostThread(std::move(task), std::move(sync_token_fences));
+  } else {
+    DCHECK(!release.HasData());
+    host_task_runner_->PostTask(
+        FROM_HERE,
+        base::BindOnce(
+            &SkiaOutputSurfaceSharedImageInterface::ScheduleGpuTaskOnHostThread,
+            this, std::move(task), std::move(sync_token_fences)));
+  }
+}
+
+void SkiaOutputSurfaceSharedImageInterface::ScheduleGpuTaskOnHostThread(
+    base::OnceClosure task,
+    std::vector<gpu::SyncToken> sync_token_fences) {
+  DCHECK(host_task_runner_->RunsTasksInCurrentSequence());
+
+  if (!output_surface_) {
+    return;
+  }
+  output_surface_->EnqueueGpuTask(std::move(task), std::move(sync_token_fences),
+                                  /*make_current=*/true,
+                                  /*need_framebuffer=*/false);
+}
+
+gpu::SharedImageFactory*
+SkiaOutputSurfaceSharedImageInterface::GetSharedImageFactoryOnGpuThread() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(gpu_sequence_checker_);
+  if (!output_surface_on_gpu_) {
+    return nullptr;
+  }
+  return output_surface_on_gpu_->shared_image_factory();
+}
+
+bool SkiaOutputSurfaceSharedImageInterface::MakeContextCurrentOnGpuThread(
+    bool needs_gl) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(gpu_sequence_checker_);
+  // Passing `make_current=true` in `ScheduleGpuTask()` will call
+  // `MakeCurrent()` at an appropriate time so it doesn't need to be done here.
+  return output_surface_on_gpu_;
+}
+
+void SkiaOutputSurfaceSharedImageInterface::MarkContextLostOnGpuThread() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(gpu_sequence_checker_);
+  if (output_surface_on_gpu_) {
+    output_surface_on_gpu_->OnContextLost();
+  }
+}
+
+}  // namespace viz
diff --git a/components/viz/service/display_embedder/skia_output_surface_shared_image_interface.h b/components/viz/service/display_embedder/skia_output_surface_shared_image_interface.h
new file mode 100644
index 0000000..b4509d86
--- /dev/null
+++ b/components/viz/service/display_embedder/skia_output_surface_shared_image_interface.h
@@ -0,0 +1,65 @@
+// 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_VIZ_SERVICE_DISPLAY_EMBEDDER_SKIA_OUTPUT_SURFACE_SHARED_IMAGE_INTERFACE_H_
+#define COMPONENTS_VIZ_SERVICE_DISPLAY_EMBEDDER_SKIA_OUTPUT_SURFACE_SHARED_IMAGE_INTERFACE_H_
+
+#include <vector>
+
+#include "base/memory/raw_ptr.h"
+#include "base/memory/scoped_refptr.h"
+#include "base/task/sequenced_task_runner.h"
+#include "components/viz/service/viz_service_export.h"
+#include "gpu/command_buffer/service/shared_image_interface_in_process_base.h"
+
+namespace gpu {
+class SharedImageFactory;
+struct SyncToken;
+}  // namespace gpu
+
+namespace viz {
+class SkiaOutputSurfaceImpl;
+class SkiaOutputSurfaceImplOnGpu;
+
+// This wraps `SkiaOutputSurfaceImpl` to implement `SharedImageInterface`.
+class VIZ_SERVICE_EXPORT SkiaOutputSurfaceSharedImageInterface
+    : public gpu::SharedImageInterfaceInProcessBase {
+ public:
+  explicit SkiaOutputSurfaceSharedImageInterface(
+      SkiaOutputSurfaceImpl& output_surface,
+      SkiaOutputSurfaceImplOnGpu& output_surface_on_gpu);
+
+  // Call from destructor of `output_surface_` to break reference cycle.
+  // This will also break the reference to `output_surface_on_gpu_`. This can
+  // only be called from the host thread.
+  void DetachOutputSurfaceOnHostThread();
+
+ protected:
+  ~SkiaOutputSurfaceSharedImageInterface() override;
+
+  // SharedImageInterfaceInProcessBase:
+  void ScheduleGpuTask(base::OnceClosure task,
+                       std::vector<gpu::SyncToken> sync_token_fences,
+                       const gpu::SyncToken& release) override;
+  gpu::SharedImageFactory* GetSharedImageFactoryOnGpuThread() override;
+  bool MakeContextCurrentOnGpuThread(bool needs_gl) override;
+  void MarkContextLostOnGpuThread() override;
+
+ private:
+  // This breaks the reference to `output_surface_on_gpu_`.
+  void DetachOutputSurfaceOnGpuThread();
+
+  // This schedules a GPU task from the host thread.
+  void ScheduleGpuTaskOnHostThread(
+      base::OnceClosure task,
+      std::vector<gpu::SyncToken> sync_token_fences);
+
+  raw_ptr<SkiaOutputSurfaceImpl> output_surface_;
+  raw_ptr<SkiaOutputSurfaceImplOnGpu> output_surface_on_gpu_;
+  scoped_refptr<base::SequencedTaskRunner> host_task_runner_;
+};
+
+}  // namespace viz
+
+#endif  // COMPONENTS_VIZ_SERVICE_DISPLAY_EMBEDDER_SKIA_OUTPUT_SURFACE_SHARED_IMAGE_INTERFACE_H_
diff --git a/content/browser/accessibility/dump_accessibility_tree_browsertest.cc b/content/browser/accessibility/dump_accessibility_tree_browsertest.cc
index 5f819f1..603ec936 100644
--- a/content/browser/accessibility/dump_accessibility_tree_browsertest.cc
+++ b/content/browser/accessibility/dump_accessibility_tree_browsertest.cc
@@ -943,17 +943,8 @@
   RunAriaTest(FILE_PATH_LITERAL("aria-controls-reference-target.html"));
 }
 
-// TODO(crbug.com/405117678): Re-enable this test on Mac.
-#if BUILDFLAG(IS_MAC)
-#define MAYBE_AccessibilityAriaControlsManyParagraphsBetween \
-  DISABLED_AccessibilityAriaControlsManyParagraphsBetween
-#else
-#define MAYBE_AccessibilityAriaControlsManyParagraphsBetween \
-  AccessibilityAriaControlsManyParagraphsBetween
-#endif
-
 IN_PROC_BROWSER_TEST_P(DumpAccessibilityTreeTest,
-                       MAYBE_AccessibilityAriaControlsManyParagraphsBetween) {
+                       AccessibilityAriaControlsManyParagraphsBetween) {
   RunAriaTest(FILE_PATH_LITERAL("aria-controls-many-paragraphs-between.html"));
 }
 
diff --git a/content/browser/attribution_reporting/attribution_manager_impl.cc b/content/browser/attribution_reporting/attribution_manager_impl.cc
index abc43b6..64a9d3f 100644
--- a/content/browser/attribution_reporting/attribution_manager_impl.cc
+++ b/content/browser/attribution_reporting/attribution_manager_impl.cc
@@ -1212,7 +1212,6 @@
   for (auto& report : reports) {
     SendReport(base::NullCallback(), now, std::move(report));
   }
-  report_sender_->SetInFirstBatch(/*in_first_batch=*/false);
 }
 
 // If `web_ui_callback` is null, assumes that `report` is being sent at its
diff --git a/content/browser/attribution_reporting/attribution_manager_impl_unittest.cc b/content/browser/attribution_reporting/attribution_manager_impl_unittest.cc
index ce150e5..a8333032 100644
--- a/content/browser/attribution_reporting/attribution_manager_impl_unittest.cc
+++ b/content/browser/attribution_reporting/attribution_manager_impl_unittest.cc
@@ -226,7 +226,6 @@
 
 class MockReportSender : public AttributionReportSender {
  public:
-  MOCK_METHOD(void, SetInFirstBatch, (bool in_first_batch), (override));
   MOCK_METHOD(void,
               SendReport,
               (AttributionReport report,
diff --git a/content/browser/attribution_reporting/attribution_report_network_sender.cc b/content/browser/attribution_reporting/attribution_report_network_sender.cc
index 2d75b347..1351c55 100644
--- a/content/browser/attribution_reporting/attribution_report_network_sender.cc
+++ b/content/browser/attribution_reporting/attribution_report_network_sender.cc
@@ -95,10 +95,6 @@
 
 AttributionReportNetworkSender::~AttributionReportNetworkSender() = default;
 
-void AttributionReportNetworkSender::SetInFirstBatch(bool in_first_batch) {
-  in_first_batch_ = in_first_batch;
-}
-
 void AttributionReportNetworkSender::SendReport(
     AttributionReport report,
     bool is_debug_report,
@@ -258,12 +254,6 @@
       loader->GetNumRetries() > 0 ? std::make_optional<bool>(net_ok_and_http_ok)
                                   : std::nullopt;
 
-  if (in_first_batch_) {
-    base::UmaHistogramSparse(
-        "Conversions.FirstBatch.HttpResponseOrNetErrorCode",
-        response_or_net_error);
-  }
-
 #if BUILDFLAG(IS_ANDROID)
   std::string_view suffix;
   switch (app_state_) {
diff --git a/content/browser/attribution_reporting/attribution_report_network_sender.h b/content/browser/attribution_reporting/attribution_report_network_sender.h
index 7adfa4f..3a7ea75 100644
--- a/content/browser/attribution_reporting/attribution_report_network_sender.h
+++ b/content/browser/attribution_reporting/attribution_report_network_sender.h
@@ -56,8 +56,6 @@
   ~AttributionReportNetworkSender() override;
 
   // AttributionReportSender:
-  void SetInFirstBatch(bool in_first_batch) override;
-
   void SendReport(AttributionReport report,
                   bool is_debug_report,
                   ReportSentCallback sent_callback) override;
@@ -105,9 +103,6 @@
   // Used for network requests.
   scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory_;
 
-  // Used for metric logging.
-  bool in_first_batch_ = true;
-
 #if BUILDFLAG(IS_ANDROID)
   // Callback invoked when the application state changes.
   void OnApplicationStateChanged(base::android::ApplicationState state);
diff --git a/content/browser/attribution_reporting/attribution_report_network_sender_unittest.cc b/content/browser/attribution_reporting/attribution_report_network_sender_unittest.cc
index 1a0653d..b8507377 100644
--- a/content/browser/attribution_reporting/attribution_report_network_sender_unittest.cc
+++ b/content/browser/attribution_reporting/attribution_report_network_sender_unittest.cc
@@ -540,8 +540,6 @@
                                 base::DoNothing());
     EXPECT_TRUE(test_url_loader_factory_.SimulateResponseForPendingRequest(
         kEventLevelReportUrl, ""));
-    histograms.ExpectUniqueSample(
-        "Conversions.FirstBatch.HttpResponseOrNetErrorCode", net::HTTP_OK, 1);
     histograms.ExpectUniqueSample(kErrorCodeMetric, net::HTTP_OK, 1);
     histograms.ExpectTotalCount(kReportSizeMetric, 1);
   }
@@ -556,9 +554,6 @@
     EXPECT_TRUE(test_url_loader_factory_.SimulateResponseForPendingRequest(
         GURL(kEventLevelReportUrl), completion_status,
         network::mojom::URLResponseHead::New(), ""));
-    histograms.ExpectUniqueSample(
-        "Conversions.FirstBatch.HttpResponseOrNetErrorCode", net::ERR_FAILED,
-        1);
     histograms.ExpectUniqueSample(kErrorCodeMetric, net::ERR_FAILED, 1);
     histograms.ExpectTotalCount(kReportSizeMetric, 1);
   }
@@ -570,9 +565,6 @@
                                 base::DoNothing());
     EXPECT_TRUE(test_url_loader_factory_.SimulateResponseForPendingRequest(
         kEventLevelReportUrl, "", net::HTTP_UNAUTHORIZED));
-    histograms.ExpectUniqueSample(
-        "Conversions.FirstBatch.HttpResponseOrNetErrorCode",
-        net::HTTP_UNAUTHORIZED, 1);
     histograms.ExpectUniqueSample(kErrorCodeMetric, net::HTTP_UNAUTHORIZED, 1);
     histograms.ExpectTotalCount(kReportSizeMetric, 1);
   }
@@ -697,8 +689,6 @@
                                   base::DoNothing());
       EXPECT_TRUE(test_url_loader_factory_.SimulateResponseForPendingRequest(
           kAggregatableReportUrl, ""));
-      histograms.ExpectUniqueSample(
-          "Conversions.FirstBatch.HttpResponseOrNetErrorCode", net::HTTP_OK, 1);
       verify_histogram(histograms, kErrorCodeMetric, has_trigger_context_id,
                        net::HTTP_OK, 1);
       histograms.ExpectTotalCount(kReportSizeMetric, 1);
@@ -714,9 +704,6 @@
       EXPECT_TRUE(test_url_loader_factory_.SimulateResponseForPendingRequest(
           GURL(kAggregatableReportUrl), completion_status,
           network::mojom::URLResponseHead::New(), ""));
-      histograms.ExpectUniqueSample(
-          "Conversions.FirstBatch.HttpResponseOrNetErrorCode", net::ERR_FAILED,
-          1);
       verify_histogram(histograms, kErrorCodeMetric, has_trigger_context_id,
                        net::ERR_FAILED, 1);
       histograms.ExpectTotalCount(kReportSizeMetric, 1);
@@ -729,9 +716,6 @@
                                   base::DoNothing());
       EXPECT_TRUE(test_url_loader_factory_.SimulateResponseForPendingRequest(
           kAggregatableReportUrl, "", net::HTTP_UNAUTHORIZED));
-      histograms.ExpectUniqueSample(
-          "Conversions.FirstBatch.HttpResponseOrNetErrorCode",
-          net::HTTP_UNAUTHORIZED, 1);
       verify_histogram(histograms, kErrorCodeMetric, has_trigger_context_id,
                        net::HTTP_UNAUTHORIZED, 1);
       histograms.ExpectTotalCount(kReportSizeMetric, 1);
diff --git a/content/browser/attribution_reporting/attribution_report_sender.h b/content/browser/attribution_reporting/attribution_report_sender.h
index 08e1c20..41495881 100644
--- a/content/browser/attribution_reporting/attribution_report_sender.h
+++ b/content/browser/attribution_reporting/attribution_report_sender.h
@@ -39,8 +39,6 @@
   using AggregatableDebugReportSentCallback = base::OnceCallback<
       void(const AggregatableDebugReport&, base::ValueView, int status)>;
 
-  virtual void SetInFirstBatch(bool in_first_batch) = 0;
-
   // Sends `report` and runs `sent_callback` when done.
   virtual void SendReport(AttributionReport report,
                           bool is_debug_report,
diff --git a/content/browser/media/webaudio/audio_context_manager_impl.cc b/content/browser/media/webaudio/audio_context_manager_impl.cc
index 51a41ae..48538cf 100644
--- a/content/browser/media/webaudio/audio_context_manager_impl.cc
+++ b/content/browser/media/webaudio/audio_context_manager_impl.cc
@@ -66,7 +66,7 @@
 
 void AudioContextManagerImpl::AudioContextAudiblePlaybackStarted(
     uint32_t audio_context_id) {
-  DCHECK(pending_audible_durations_[audio_context_id].is_null());
+  CHECK(pending_audible_durations_[audio_context_id].is_null());
 
   // Keeps track of the start audible time for this context.
   pending_audible_durations_[audio_context_id] = clock_->NowTicks();
diff --git a/content/browser/renderer_host/render_widget_host_impl.cc b/content/browser/renderer_host/render_widget_host_impl.cc
index 817b5da..fdb69cd 100644
--- a/content/browser/renderer_host/render_widget_host_impl.cc
+++ b/content/browser/renderer_host/render_widget_host_impl.cc
@@ -3724,6 +3724,16 @@
   return true;
 }
 
+bool RenderWidgetHostImpl::ShouldWaitForInputProcessed() {
+  for (auto& it : GetHostFrameSinkManager()->GetDisplayHitTestQuery()) {
+    if (it.second->ContainsActiveFrameSinkId(GetFrameSinkId())) {
+      // Browser already has hit test data for frame sink.
+      return false;
+    }
+  }
+  return true;
+}
+
 bool RenderWidgetHostImpl::IsHidden() const {
   return is_hidden_;
 }
diff --git a/content/browser/renderer_host/render_widget_host_impl.h b/content/browser/renderer_host/render_widget_host_impl.h
index 43c8402..04cc38e 100644
--- a/content/browser/renderer_host/render_widget_host_impl.h
+++ b/content/browser/renderer_host/render_widget_host_impl.h
@@ -816,6 +816,8 @@
   // resolving any ambiguity that could arise from multiple inheritance.
   bool IsHidden() const override;
 
+  bool ShouldWaitForInputProcessed() override;
+
   // Signals that a frame with token |frame_token| was finished processing. If
   // there are any queued messages belonging to it, they will be processed.
   void DidProcessFrame(uint32_t frame_token, base::TimeTicks activation_time);
@@ -1029,6 +1031,10 @@
   // cpu-priority boosted to run discard logic.
   void SetIsDiscarding(bool is_discarding);
 
+  SyntheticGestureController* SyntheticGestureControllerForTesting() {
+    return synthetic_gesture_controller_.get();
+  }
+
  protected:
   // |routing_id| must not be IPC::mojom::kRoutingIdNone.
   // If this object outlives |delegate|, DetachDelegate() must be called when
diff --git a/content/browser/site_per_process_scroll_browsertest.cc b/content/browser/site_per_process_scroll_browsertest.cc
index 95d2335..45c3c45 100644
--- a/content/browser/site_per_process_scroll_browsertest.cc
+++ b/content/browser/site_per_process_scroll_browsertest.cc
@@ -7,11 +7,13 @@
 #include "base/task/single_thread_task_runner.h"
 #include "base/test/bind.h"
 #include "base/test/run_until.h"
+#include "base/test/scoped_feature_list.h"
 #include "base/test/test_timeouts.h"
 #include "build/build_config.h"
 #include "content/browser/renderer_host/cross_process_frame_connector.h"
 #include "content/browser/renderer_host/frame_tree.h"
 #include "content/browser/renderer_host/render_frame_proxy_host.h"
+#include "content/browser/renderer_host/render_widget_host_impl.h"
 #include "content/browser/site_per_process_browsertest.h"
 #include "content/common/input/synthetic_smooth_scroll_gesture.h"
 #include "content/public/common/content_switches.h"
@@ -24,6 +26,8 @@
 #include "content/test/render_document_feature.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/blink/public/common/features.h"
+#include "third_party/blink/public/common/input/web_touch_event.h"
 #include "ui/events/gesture_detection/gesture_configuration.h"
 #include "ui/events/keycodes/dom/keycode_converter.h"
 #include "ui/native_theme/features/native_theme_features.h"
@@ -919,10 +923,166 @@
   ack_observer.Wait();
 }
 
+class OOPIFScrollBubblingTest : public SitePerProcessBrowserTest {
+ public:
+  OOPIFScrollBubblingTest() {
+    scoped_feature_list_.InitWithFeatureState(
+        blink::features::kAsyncTouchMovesImmediatelyAfterScroll,
+        /* enabled= */ true);
+  }
+  ~OOPIFScrollBubblingTest() override = default;
+
+ private:
+  base::test::ScopedFeatureList scoped_feature_list_;
+};
+
+class TouchMoveInjectingObserver : public RenderWidgetHost::InputEventObserver {
+ public:
+  TouchMoveInjectingObserver(
+      RenderWidgetHost* target_widget,
+      base::WeakPtr<SyntheticGestureController> synthetic_gesture_controller,
+      base::RunLoop* run_loop)
+      : target_rwh_(static_cast<RenderWidgetHostImpl*>(target_widget)),
+        gesture_controller_(synthetic_gesture_controller),
+        run_loop_(run_loop) {}
+
+  TouchMoveInjectingObserver(const TouchMoveInjectingObserver&) = delete;
+  TouchMoveInjectingObserver& operator=(const TouchMoveInjectingObserver&) =
+      delete;
+
+  void OnInputEvent(const RenderWidgetHost& widget,
+                    const blink::WebInputEvent& event) override {
+    const RenderWidgetHostViewBase* view =
+        static_cast<const RenderWidgetHostViewBase*>(widget.GetView());
+    bool is_child_view = view->IsRenderWidgetHostViewChildFrame();
+    if (is_child_view) {
+      OnInputEventOnChildFrame(event);
+      return;
+    }
+    OnInputEventOnRootFrame(event);
+  }
+
+  bool root_view_has_seen_gsu_for_async_touch_move() const {
+    return root_view_has_seen_gsu_for_async_touch_move_;
+  }
+
+ private:
+  void OnInputEventOnChildFrame(const blink::WebInputEvent& event) {
+    if (event.GetType() == blink::WebInputEvent::Type::kGestureScrollBegin) {
+      GetUIThreadTaskRunner({BrowserTaskType::kUserInput})
+          ->PostTask(FROM_HERE, base::BindOnce(
+                                    [](base::WeakPtr<SyntheticGestureController>
+                                           gesture_controller) {
+                                      if (!gesture_controller) {
+                                        return;
+                                      }
+                                      gesture_controller->DispatchNextEvent(
+                                          base::TimeTicks::Now());
+                                    },
+                                    gesture_controller_));
+      has_seen_gesture_scroll_begin_ = true;
+      return;
+    }
+    if (event.GetType() == blink::WebInputEvent::Type::kTouchMove &&
+        has_seen_gesture_scroll_begin_ &&
+        first_touch_move_after_scroll_begin_id_ == -1) {
+      first_touch_move_after_scroll_begin_id_ =
+          static_cast<const blink::WebTouchEvent&>(event).unique_touch_event_id;
+      return;
+    }
+  }
+
+  void OnInputEventOnRootFrame(const blink::WebInputEvent& event) {
+    if (event.GetType() != blink::WebInputEvent::Type::kGestureScrollUpdate) {
+      return;
+    }
+    const blink::WebGestureEvent& gesture_event =
+        static_cast<const blink::WebGestureEvent&>(event);
+    if (gesture_event.unique_touch_event_id ==
+        first_touch_move_after_scroll_begin_id_) {
+      ASSERT_FALSE(root_view_has_seen_gsu_for_async_touch_move_);
+      root_view_has_seen_gsu_for_async_touch_move_ = true;
+      run_loop_->Quit();
+    }
+  }
+  int first_touch_move_after_scroll_begin_id_ = -1;
+  bool root_view_has_seen_gsu_for_async_touch_move_ = false;
+  bool has_seen_gesture_scroll_begin_ = false;
+  raw_ptr<RenderWidgetHostImpl> target_rwh_;
+  base::WeakPtr<SyntheticGestureController> gesture_controller_;
+  raw_ptr<base::RunLoop> run_loop_;
+};
+
+IN_PROC_BROWSER_TEST_P(OOPIFScrollBubblingTest,
+                       ScrollBubblingWithTouchMoveInjection) {
+  GURL main_url(embedded_test_server()->GetURL(
+      "a.com", "/scrollable_page_with_iframe.html"));
+  EXPECT_TRUE(NavigateToURL(shell(), main_url));
+  RenderFrameSubmissionObserver frame_observer(shell()->web_contents());
+
+  FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root();
+  RenderWidgetHostImpl* root_rwh =
+      root->current_frame_host()->GetRenderWidgetHost();
+  root_rwh->EnsureReadyForSyntheticGestures(base::DoNothing());
+  RenderWidgetHostViewBase* root_view =
+      static_cast<RenderWidgetHostViewBase*>(root_rwh->GetView());
+
+  FrameTreeNode* iframe_node = root->child_at(0);
+  GURL url_domain_b(embedded_test_server()->GetURL(
+      "b.com", "/body_overflow_hidden_blocking_touch_handlers.html"));
+  EXPECT_TRUE(NavigateToURLFromRenderer(iframe_node, url_domain_b));
+  WaitForHitTestData(iframe_node->current_frame_host());
+  RenderWidgetHostImpl* iframe_rwh =
+      iframe_node->current_frame_host()->GetRenderWidgetHost();
+
+  gfx::Rect iframe_bounds =
+      iframe_node->current_frame_host()->GetView()->GetViewBounds();
+  float scale_factor =
+      frame_observer.LastRenderFrameMetadata().page_scale_factor;
+  gfx::PointF scroll_pos(
+      std::ceil((iframe_bounds.x() - root_view->GetViewBounds().x() + 10) *
+                scale_factor),
+      std::ceil((iframe_bounds.y() - root_view->GetViewBounds().y() + 10) *
+                scale_factor));
+
+  SyntheticSmoothScrollGestureParams params;
+  params.gesture_source_type = content::mojom::GestureSourceType::kTouchInput;
+  params.anchor = scroll_pos;
+  params.distances.push_back(gfx::Vector2d(0, -200));  // Scroll down.
+  params.granularity = ui::ScrollGranularity::kScrollByPrecisePixel;
+
+  auto gesture = std::make_unique<SyntheticSmoothScrollGesture>(params);
+
+  base::RunLoop run_loop;
+  root_rwh->QueueSyntheticGesture(std::move(gesture), base::DoNothing());
+
+  TouchMoveInjectingObserver observer(
+      iframe_rwh,
+      root->current_frame_host()
+          ->GetRenderWidgetHost()
+          ->SyntheticGestureControllerForTesting()
+          ->GetWeakPtr(),
+      &run_loop);
+
+  root->current_frame_host()->GetRenderWidgetHost()->AddInputEventObserver(
+      &observer);
+  iframe_rwh->AddInputEventObserver(&observer);
+  run_loop.Run();
+
+  EXPECT_TRUE(observer.root_view_has_seen_gsu_for_async_touch_move());
+
+  root->current_frame_host()->GetRenderWidgetHost()->RemoveInputEventObserver(
+      &observer);
+  iframe_rwh->RemoveInputEventObserver(&observer);
+}
+
 INSTANTIATE_TEST_SUITE_P(All,
                          ScrollingIntegrationTest,
                          testing::ValuesIn(RenderDocumentFeatureLevelValues()));
 INSTANTIATE_TEST_SUITE_P(All,
+                         OOPIFScrollBubblingTest,
+                         testing::ValuesIn(RenderDocumentFeatureLevelValues()));
+INSTANTIATE_TEST_SUITE_P(All,
                          SitePerProcessScrollAnchorTest,
                          testing::ValuesIn(RenderDocumentFeatureLevelValues()));
 INSTANTIATE_TEST_SUITE_P(All,
diff --git a/content/common/input/synthetic_gesture_controller.cc b/content/common/input/synthetic_gesture_controller.cc
index 8e8270ab..a3a877d 100644
--- a/content/common/input/synthetic_gesture_controller.cc
+++ b/content/common/input/synthetic_gesture_controller.cc
@@ -38,6 +38,12 @@
   if (renderer_known_to_be_initialized_)
     return;
 
+  if (!delegate_->ShouldWaitForInputProcessed()) {
+    renderer_known_to_be_initialized_ = true;
+    std::move(on_completed).Run();
+    return;
+  }
+
   base::OnceClosure wrapper = base::BindOnce(
       [](base::WeakPtr<SyntheticGestureController> weak_ptr,
          base::OnceClosure on_completed) {
diff --git a/content/common/input/synthetic_gesture_controller.h b/content/common/input/synthetic_gesture_controller.h
index 4c47e58..35a3b77 100644
--- a/content/common/input/synthetic_gesture_controller.h
+++ b/content/common/input/synthetic_gesture_controller.h
@@ -38,6 +38,8 @@
     virtual bool HasGestureStopped() = 0;
 
     virtual bool IsHidden() const = 0;
+
+    virtual bool ShouldWaitForInputProcessed() = 0;
   };
   SyntheticGestureController(
       Delegate* delegate,
diff --git a/content/common/input/synthetic_gesture_controller_unittest.cc b/content/common/input/synthetic_gesture_controller_unittest.cc
index 14438a4..d9842793 100644
--- a/content/common/input/synthetic_gesture_controller_unittest.cc
+++ b/content/common/input/synthetic_gesture_controller_unittest.cc
@@ -805,6 +805,7 @@
   // SyntheticGestureController::Delegate:
   bool HasGestureStopped() override { return true; }
   bool IsHidden() const override { return false; }
+  bool ShouldWaitForInputProcessed() override { return true; }
 };
 
 }  // namespace
diff --git a/content/common/input/synthetic_pointer_action_unittest.cc b/content/common/input/synthetic_pointer_action_unittest.cc
index 6d5c1e64..5d323b27 100644
--- a/content/common/input/synthetic_pointer_action_unittest.cc
+++ b/content/common/input/synthetic_pointer_action_unittest.cc
@@ -421,6 +421,7 @@
   // SyntheticGestureController::Delegate:
   bool HasGestureStopped() override { return true; }
   bool IsHidden() const override { return false; }
+  bool ShouldWaitForInputProcessed() override { return true; }
 };
 
 class SyntheticPointerActionTest : public testing::Test {
diff --git a/content/test/content_test_bundle_data.filelist b/content/test/content_test_bundle_data.filelist
index 484ace75..39e953bb 100644
--- a/content/test/content_test_bundle_data.filelist
+++ b/content/test/content_test_bundle_data.filelist
@@ -2196,10 +2196,10 @@
 data/accessibility/css/table-display.html
 data/accessibility/css/table-incomplete-expected-blink.txt
 data/accessibility/css/table-incomplete.html
-data/accessibility/css/target-current-html-anchor-role-expected-blink.txt
-data/accessibility/css/target-current-html-anchor-role.html
 data/accessibility/css/target-current-html-anchor-role-dynamic-expected-blink.txt
 data/accessibility/css/target-current-html-anchor-role-dynamic.html
+data/accessibility/css/target-current-html-anchor-role-expected-blink.txt
+data/accessibility/css/target-current-html-anchor-role.html
 data/accessibility/css/text-overflow-ellipsis-expected-blink.txt
 data/accessibility/css/text-overflow-ellipsis.html
 data/accessibility/css/transform-expected-blink.txt
@@ -6808,6 +6808,7 @@
 data/blob_storage/common.js
 data/blue.html
 data/body_overflow_hidden.html
+data/body_overflow_hidden_blocking_touch_handlers.html
 data/browsing_data/embedded_site_data.html
 data/browsing_data/embeds_storage_accessor.html
 data/browsing_data/empty.js
diff --git a/content/test/data/accessibility/aria/aria-controls-many-paragraphs-between.html b/content/test/data/accessibility/aria/aria-controls-many-paragraphs-between.html
index 272b15f8..eb9cec9 100644
--- a/content/test/data/accessibility/aria/aria-controls-many-paragraphs-between.html
+++ b/content/test/data/accessibility/aria/aria-controls-many-paragraphs-between.html
@@ -498,1207 +498,6 @@
       <p>The quick brown fox jumps over the lazy dog.</p>
       <p>The quick brown fox jumps over the lazy dog.</p>
       <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
-      <p>The quick brown fox jumps over the lazy dog.</p>
     </div>
     <p id="last-para">Last paragraph</p>
   </body>
diff --git a/content/test/data/body_overflow_hidden_blocking_touch_handlers.html b/content/test/data/body_overflow_hidden_blocking_touch_handlers.html
new file mode 100644
index 0000000..087540c
--- /dev/null
+++ b/content/test/data/body_overflow_hidden_blocking_touch_handlers.html
@@ -0,0 +1,21 @@
+<html>
+<head>
+  <meta name='viewport' content='width=device-width, minimum-scale=1'>
+</head>
+<style>
+body {
+  height: 10000px;
+  overflow: hidden;
+}
+</style>
+<div style="width:100px; height:300px; overflow: scroll;">
+  <div style="width:150px; height:250px; overflow: scroll">
+    <div style="width:300px; height:200px; overflow: scroll">
+    </div>
+  </div>
+</div>
+<script>
+  document.addEventListener('touchstart', (e) => {}, {passive: false});
+  document.addEventListener('touchmove', (e) => {}, {passive: false});
+</script>
+</html>
diff --git a/docs/unsafe_buffers.md b/docs/unsafe_buffers.md
index 4a4d5e4..52c0cdcf 100644
--- a/docs/unsafe_buffers.md
+++ b/docs/unsafe_buffers.md
@@ -17,7 +17,7 @@
 error: unsafe buffer access [-Werror,-Wunsafe-buffer-usage]
 ```
 and directs developers to this file for more information. Several common
-[Techniques](#container-based-ecosystem) for fixing these issues are presented
+[Techniques](#container_based-ecosystem) for fixing these issues are presented
 later in this document.
 
 Clang documentation includes a guide to working with unsafe-buffer-usage
diff --git a/docs/website b/docs/website
index c6edf98..4f4404f 160000
--- a/docs/website
+++ b/docs/website
@@ -1 +1 @@
-Subproject commit c6edf98e7fab2385c90caac6211f00b62e9b773d
+Subproject commit 4f4404f68b21aefa24a86abad8fb0cf01a0db7fc
diff --git a/extensions/browser/events/event_dispatch_helper.cc b/extensions/browser/events/event_dispatch_helper.cc
index e96bcd32..2ad3b92 100644
--- a/extensions/browser/events/event_dispatch_helper.cc
+++ b/extensions/browser/events/event_dispatch_helper.cc
@@ -44,7 +44,7 @@
     EventListenerMap& listeners,
     DispatchFunction dispatch_function,
     DispatchToProcessFunction dispatch_to_process_function,
-    const std::string& restrict_to_extension_id,
+    const ExtensionId& restrict_to_extension_id,
     const GURL& restrict_to_url,
     std::unique_ptr<Event> event) {
   const ExtensionRegistry* extension_registry =
@@ -58,7 +58,7 @@
 }
 
 void EventDispatchHelper::DispatchEventImpl(
-    const std::string& restrict_to_extension_id,
+    const ExtensionId& restrict_to_extension_id,
     const GURL& restrict_to_url,
     std::unique_ptr<Event> event) {
   std::set<const EventListener*> listeners(
@@ -71,66 +71,77 @@
   // the event we are dispatching here, we dispatch to the lazy listeners here
   // first.
   for (const EventListener* listener : listeners) {
-    if (!restrict_to_extension_id.empty() &&
-        restrict_to_extension_id != listener->extension_id()) {
-      continue;
-    }
-    if (!restrict_to_url.is_empty() &&
-        !url::IsSameOriginWith(restrict_to_url, listener->listener_url())) {
-      continue;
-    }
-    if (!listener->IsLazy()) {
-      continue;
-    }
-
-    // TODO(richardzh): Move cross browser context check (by calling
-    // EventRouter::CanDispatchEventToBrowserContext) to here. So the check
-    // happens before instead of during the dispatch.
-
-    // Lazy listeners don't have a process, take the stored browser context
-    // for lazy context.
-    TryQueueEventForLazyListener(
-        *event, LazyContextIdForListener(listener, *browser_context_),
-        listener->filter());
-
-    // Dispatch to lazy listener in the incognito context.
-    // We need to use the incognito context in the case of split-mode
-    // extensions.
-    BrowserContext* incognito_context =
-        GetIncognitoContextIfAccessible(listener->extension_id());
-    if (incognito_context) {
-      TryQueueEventForLazyListener(
-          *event, LazyContextIdForListener(listener, *incognito_context),
-          listener->filter());
+    if (listener->IsLazy()) {
+      DispatchEventToLazyListener(restrict_to_extension_id, restrict_to_url,
+                                  *event, listener);
     }
   }
 
   for (const EventListener* listener : listeners) {
-    if (!restrict_to_extension_id.empty() &&
-        restrict_to_extension_id != listener->extension_id()) {
-      continue;
+    if (!listener->IsLazy()) {
+      DispatchEventToActiveListener(restrict_to_extension_id, restrict_to_url,
+                                    *event, listener);
     }
-    if (!restrict_to_url.is_empty() &&
-        !url::IsSameOriginWith(restrict_to_url, listener->listener_url())) {
-      continue;
-    }
-    if (listener->IsLazy()) {
-      continue;
-    }
-    // Non-lazy listeners take the process browser context for
-    // lazy context
-    if (IsAlreadyQueued(LazyContextIdForListener(
-            listener, *listener->process()->GetBrowserContext()))) {
-      continue;
-    }
-
-    dispatch_to_process_function_.Run(
-        listener->extension_id(), listener->listener_url(), listener->process(),
-        listener->service_worker_version_id(), listener->worker_thread_id(),
-        *event, listener->filter(), false /* did_enqueue */);
   }
 }
 
+void EventDispatchHelper::DispatchEventToLazyListener(
+    const ExtensionId& restrict_to_extension_id,
+    const GURL& restrict_to_url,
+    Event& event,
+    const EventListener* listener) {
+  DCHECK(listener->IsLazy());
+  if (!ListenerMeetsRestrictions(listener, restrict_to_extension_id,
+                                 restrict_to_url)) {
+    return;
+  }
+
+  // TODO(richardzh): Move cross browser context check (by calling
+  // EventRouter::CanDispatchEventToBrowserContext) to here. So the check
+  // happens before instead of during the dispatch.
+
+  // Lazy listeners don't have a process, take the stored browser context
+  // for lazy context.
+  TryQueueEventForLazyListener(
+      event, LazyContextIdForListener(listener, *browser_context_),
+      listener->filter());
+
+  // Dispatch to lazy listener in the incognito context.
+  // We need to use the incognito context in the case of split-mode
+  // extensions.
+  BrowserContext* incognito_context =
+      GetIncognitoContextIfAccessible(listener->extension_id());
+  if (incognito_context) {
+    TryQueueEventForLazyListener(
+        event, LazyContextIdForListener(listener, *incognito_context),
+        listener->filter());
+  }
+}
+
+void EventDispatchHelper::DispatchEventToActiveListener(
+    const ExtensionId& restrict_to_extension_id,
+    const GURL& restrict_to_url,
+    const Event& event,
+    const EventListener* listener) {
+  DCHECK(!listener->IsLazy());
+  if (!ListenerMeetsRestrictions(listener, restrict_to_extension_id,
+                                 restrict_to_url)) {
+    return;
+  }
+
+  // Non-lazy listeners take the process browser context for
+  // lazy context.
+  if (IsAlreadyQueued(LazyContextIdForListener(
+          listener, *listener->process()->GetBrowserContext()))) {
+    return;
+  }
+
+  dispatch_to_process_function_.Run(
+      listener->extension_id(), listener->listener_url(), listener->process(),
+      listener->service_worker_version_id(), listener->worker_thread_id(),
+      event, listener->filter(), false /* did_enqueue */);
+}
+
 void EventDispatchHelper::TryQueueEventForLazyListener(
     Event& event,
     const LazyContextId& dispatch_context,
@@ -218,6 +229,23 @@
   return base::Contains(dispatched_ids_, dispatch_context);
 }
 
+bool EventDispatchHelper::ListenerMeetsRestrictions(
+    const EventListener* listener,
+    const ExtensionId& restrict_to_extension_id,
+    const GURL& restrict_to_url) const {
+  if (!restrict_to_extension_id.empty() &&
+      restrict_to_extension_id != listener->extension_id()) {
+    return false;
+  }
+
+  if (!restrict_to_url.is_empty() &&
+      !url::IsSameOriginWith(restrict_to_url, listener->listener_url())) {
+    return false;
+  }
+
+  return true;
+}
+
 BrowserContext* EventDispatchHelper::GetIncognitoContextIfAccessible(
     const ExtensionId& extension_id) const {
   DCHECK(!extension_id.empty());
diff --git a/extensions/browser/events/event_dispatch_helper.h b/extensions/browser/events/event_dispatch_helper.h
index 15750d8..689aec8 100644
--- a/extensions/browser/events/event_dispatch_helper.h
+++ b/extensions/browser/events/event_dispatch_helper.h
@@ -70,10 +70,31 @@
 
   ~EventDispatchHelper();
 
-  void DispatchEventImpl(const std::string& restrict_to_extension_id,
+  void DispatchEventImpl(const ExtensionId& restrict_to_extension_id,
                          const GURL& restrict_to_url,
                          std::unique_ptr<Event> event);
 
+  // Attempts to dispatch the given `event` to the specified lazy `listener`.
+  // This will queue the event to be dispatched later if the lazy context
+  // is not currently running.
+  //
+  // NOTE: this method will not dispatch to a lazy listener if the context
+  // is active, so that it can be dispatched to the corresponding active
+  // (non-lazy) listener instead.
+  void DispatchEventToLazyListener(const ExtensionId& restrict_to_extension_id,
+                                   const GURL& restrict_to_url,
+                                   Event& event,
+                                   const EventListener* listener);
+
+  // Dispatches the given `event` to the specified active `listener`. Avoids
+  // dispatching if an event has already been queued for a lazy listener with
+  // the same context.
+  void DispatchEventToActiveListener(
+      const ExtensionId& restrict_to_extension_id,
+      const GURL& restrict_to_url,
+      const Event& event,
+      const EventListener* listener);
+
   // Possibly queues the lazy `event` for dispatch to `dispatch_context`.
   //
   // If [dispatch_context| is for an event page, it ensures all of the pages
@@ -105,6 +126,16 @@
   // is already queued for dispatch to a lazy listener.
   bool IsAlreadyQueued(const LazyContextId& dispatch_context) const;
 
+  // Returns true if the given `listener` meets dispatch restrictions. Events
+  // may be restricted to a particular extension ID or URL context.
+  //
+  // If `restrict_to_extension_id` is non-empty, the listener's extension ID
+  // must match it. If `restrict_to_url` is non-empty, the listener's URL must
+  // be same-origin with it.
+  bool ListenerMeetsRestrictions(const EventListener* listener,
+                                 const ExtensionId& restrict_to_extension_id,
+                                 const GURL& restrict_to_url) const;
+
   // Gets off-the-record browser context if
   //     - The extension has incognito mode set to "split"
   //     - The on-the-record browser context has an off-the-record context
diff --git a/extensions/browser/extension_function_histogram_value.h b/extensions/browser/extension_function_histogram_value.h
index 9585e3b..70f32c88 100644
--- a/extensions/browser/extension_function_histogram_value.h
+++ b/extensions/browser/extension_function_histogram_value.h
@@ -1994,8 +1994,8 @@
   AUTOFILLPRIVATE_GETPAYOVERTIMEISSUERLIST = 1931,
   AUTOFILLPRIVATE_SETAUTOFILLAIOPTINSTATUS = 1932,
   AUTOFILLPRIVATE_GETAUTOFILLAIOPTINSTATUS = 1933,
-  EXPERIMENTALACTOR_STARTTASK = 1934,
-  EXPERIMENTALACTOR_EXECUTEACTION = 1935,
+  DELETED_EXPERIMENTALACTOR_STARTTASK = 1934,
+  DELETED_EXPERIMENTALACTOR_EXECUTEACTION = 1935,
   EXPERIMENTALACTOR_STOPTASK = 1936,
   BOOKMARKMANAGERPRIVATE_OPENINNEWTABGROUP = 1937,
   CONTROLLEDFRAMEINTERNAL_CONTEXTMENUSUPDATE = 1938,
diff --git a/extensions/common/extension_resource.cc b/extensions/common/extension_resource.cc
index 84ee5fd8..a694c7af 100644
--- a/extensions/common/extension_resource.cc
+++ b/extensions/common/extension_resource.cc
@@ -5,20 +5,10 @@
 #include "extensions/common/extension_resource.h"
 
 #include "base/check.h"
-#include "base/feature_list.h"
 #include "base/files/file_util.h"
 
 namespace extensions {
 
-namespace {
-
-#if BUILDFLAG(IS_WIN)
-BASE_FEATURE(WindowsNormalizeFilePathFallbackOnError,
-             base::FEATURE_DISABLED_BY_DEFAULT);
-#endif
-
-}  // namespace
-
 ExtensionResource::ExtensionResource() : follow_symlinks_anywhere_(false) {}
 
 ExtensionResource::ExtensionResource(const ExtensionId& extension_id,
@@ -70,17 +60,20 @@
   base::FilePath normalized_extension_root;
   if (!base::NormalizeFilePath(extension_root, &normalized_extension_root)) {
 #if BUILDFLAG(IS_WIN)
-    // TODO(crbug.com/410059474): Remove this if-check and the Windows-specific
-    // fallback logic in M143.
-    if (!base::FeatureList::IsEnabled(
-            kWindowsNormalizeFilePathFallbackOnError)) {
-      return base::FilePath();
-    }
-
-    // On Windows, `NormalizeFilePath` fails if the path doesn't start with a
-    // drive letter (e.g. a network path) or if it exceeds `MAX_PATH` characters
-    // in length. Fall back to `MakeAbsoluteFilePath` and proceed if the path
-    // exists.
+    // On Windows, `NormalizeFilePath` is implemented via
+    // `GetFinalPathNameByHandle`, which can fail in some cases. One such case
+    // which was reported by users is with paths on a ramdisk. For example, from
+    // the author of ImDisk:
+    //
+    // > ImDisk is a rather old product. It is designed to be as small and
+    // > simple as possible and compatible with Windows versions as old as NT
+    // > 3.51. By design it lacks support for certain "modern" OS features like
+    // > plug-and-play and Volume Mount Manager. The mentioned API function,
+    // > GetFinalPathNameByHandle, uses Volume Mount Manager and is therefore
+    // > not supported for ImDisk virtual disks.
+    //
+    // Since we can't normalize the path, fall back to `MakeAbsoluteFilePath`
+    // and proceed if the file exists.
     normalized_extension_root = base::MakeAbsoluteFilePath(extension_root);
     if (normalized_extension_root.empty() ||
         !base::PathExists(normalized_extension_root)) {
diff --git a/gpu/command_buffer/service/shared_image/shared_image_factory.cc b/gpu/command_buffer/service/shared_image/shared_image_factory.cc
index e5bc99e2..0879c5b 100644
--- a/gpu/command_buffer/service/shared_image/shared_image_factory.cc
+++ b/gpu/command_buffer/service/shared_image/shared_image_factory.cc
@@ -917,11 +917,11 @@
   // TODO(crbug.com/423037052): Handle offscreen canvas case for WebView where
   // we fail to find a shared image factory.
   // Suppress crashes due to this client for now.
-  if (new_debug_label == "CanvasResourceRasterGmb") {
+  if (new_debug_label.find("CanvasResourceRasterGmb") != std::string::npos) {
     return;
   }
 #endif
-  SCOPED_CRASH_KEY_STRING32("SIFactory", "DebugLabel", new_debug_label);
+  SCOPED_CRASH_KEY_STRING64("SIFactory", "DebugLabel", new_debug_label);
   SCOPED_CRASH_KEY_STRING64("SIFactory", "Format", format.ToString());
   SCOPED_CRASH_KEY_NUMBER("SIFactory", "Usage", usage);
   SCOPED_CRASH_KEY_STRING64("SIFactory", "GMBType", GmbTypeToString(gmb_type));
@@ -930,7 +930,7 @@
                         IsSharedBetweenThreads(usage));
   // DumpWithoutCrashing to get crash reports for failure to find a shared image
   // backing factory.
-  // base::debug::DumpWithoutCrashing();
+  base::debug::DumpWithoutCrashing();
 }
 
 bool SharedImageFactory::RegisterBacking(
diff --git a/gpu/command_buffer/service/shared_image_interface_in_process.cc b/gpu/command_buffer/service/shared_image_interface_in_process.cc
index 7138d39..df2ee96 100644
--- a/gpu/command_buffer/service/shared_image_interface_in_process.cc
+++ b/gpu/command_buffer/service/shared_image_interface_in_process.cc
@@ -9,6 +9,7 @@
 #include "base/functional/bind.h"
 #include "base/memory/raw_ptr.h"
 #include "base/memory/scoped_refptr.h"
+#include "base/sequence_checker.h"
 #include "base/synchronization/waitable_event.h"
 #include "base/task/single_thread_task_runner.h"
 #include "build/build_config.h"
@@ -153,6 +154,7 @@
 
 void SharedImageInterfaceInProcess::DestroyOnGpu(
     base::WaitableEvent* completion) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(gpu_sequence_checker_);
   bool have_context = MakeContextCurrentOnGpuThread();
   if (shared_image_factory_) {
     shared_image_factory_->DestroyAllSharedImages(have_context);
@@ -167,6 +169,7 @@
 
 SharedImageFactory*
 SharedImageInterfaceInProcess::GetSharedImageFactoryOnGpuThread() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(gpu_sequence_checker_);
   if (shared_image_factory_) {
     return shared_image_factory_.get();
   }
@@ -183,6 +186,7 @@
 
 bool SharedImageInterfaceInProcess::MakeContextCurrentOnGpuThread(
     bool needs_gl) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(gpu_sequence_checker_);
   if (gl::GetGLImplementation() == gl::kGLImplementationDisabled) {
     return true;
   }
@@ -202,6 +206,7 @@
 }
 
 void SharedImageInterfaceInProcess::MarkContextLostOnGpuThread() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(gpu_sequence_checker_);
   context_state_->MarkContextLost();
 }
 
diff --git a/gpu/command_buffer/service/shared_image_interface_in_process_base.h b/gpu/command_buffer/service/shared_image_interface_in_process_base.h
index 8bc222b7..a3fdce45 100644
--- a/gpu/command_buffer/service/shared_image_interface_in_process_base.h
+++ b/gpu/command_buffer/service/shared_image_interface_in_process_base.h
@@ -141,7 +141,7 @@
 
   CommandBufferId command_buffer_id() const { return command_buffer_id_; }
 
-  // Sequence checker for tasks that run on the gpu "thread".
+  // Sequence checker for tasks that run on the gpu thread.
   SEQUENCE_CHECKER(gpu_sequence_checker_);
 
  private:
diff --git a/gpu/ipc/service/gpu_channel_shared_image_interface.cc b/gpu/ipc/service/gpu_channel_shared_image_interface.cc
index bd3b7afd..3a4334a 100644
--- a/gpu/ipc/service/gpu_channel_shared_image_interface.cc
+++ b/gpu/ipc/service/gpu_channel_shared_image_interface.cc
@@ -47,9 +47,7 @@
           SchedulingPriority::kLow,
           shared_image_stub->channel()->task_runner(),
           CommandBufferNamespace::GPU_CHANNEL_SHARED_IMAGE_INTERFACE,
-          command_buffer_id())) {
-  DETACH_FROM_SEQUENCE(gpu_sequence_checker_);
-}
+          command_buffer_id())) {}
 
 GpuChannelSharedImageInterface::~GpuChannelSharedImageInterface() {
   scheduler_->DestroySequence(sequence_);
@@ -152,6 +150,7 @@
 
 SharedImageFactory*
 GpuChannelSharedImageInterface::GetSharedImageFactoryOnGpuThread() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(gpu_sequence_checker_);
   if (!shared_image_stub_) {
     return nullptr;
   }
@@ -169,6 +168,7 @@
 }
 
 void GpuChannelSharedImageInterface::MarkContextLostOnGpuThread() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(gpu_sequence_checker_);
   shared_image_stub_->shared_context_state()->MarkContextLost();
 }
 
diff --git a/infra/config/generated/builders/try/ios-simulator/properties.json b/infra/config/generated/builders/try/ios-simulator/properties.json
index b0c6b26..3350b72 100644
--- a/infra/config/generated/builders/try/ios-simulator/properties.json
+++ b/infra/config/generated/builders/try/ios-simulator/properties.json
@@ -57,6 +57,10 @@
     ],
     "use_clang_coverage": true
   },
+  "$build/flakiness": {
+    "check_for_flakiness": true,
+    "check_for_flakiness_with_resultdb": true
+  },
   "$build/siso": {
     "configs": [
       "builder"
@@ -77,6 +81,7 @@
     ]
   },
   "builder_group": "tryserver.chromium.mac",
+  "cq": "required",
   "recipe": "chromium/orchestrator",
   "xcode_build_version": "17a5305f"
 }
\ No newline at end of file
diff --git a/infra/config/generated/cq-builders.md b/infra/config/generated/cq-builders.md
index 5c16382..5ca69e9 100644
--- a/infra/config/generated/cq-builders.md
+++ b/infra/config/generated/cq-builders.md
@@ -45,6 +45,8 @@
 
 * [fuchsia-x64-cast-receiver-rel](https://ci.chromium.org/p/chromium/builders/try/fuchsia-x64-cast-receiver-rel) ([definition](https://cs.chromium.org/search?q=+file:/try/.*\.star$+""fuchsia-x64-cast-receiver-rel""))
 
+* [ios-simulator](https://ci.chromium.org/p/chromium/builders/try/ios-simulator) ([definition](https://cs.chromium.org/search?q=+file:/try/.*\.star$+""ios-simulator""))
+
 * [linux-chromeos-compile-dbg](https://ci.chromium.org/p/chromium/builders/try/linux-chromeos-compile-dbg) ([definition](https://cs.chromium.org/search?q=+file:/try/.*\.star$+""linux-chromeos-compile-dbg""))
 
 * [linux-chromeos-rel](https://ci.chromium.org/p/chromium/builders/try/linux-chromeos-rel) ([definition](https://cs.chromium.org/search?q=+file:/try/.*\.star$+""linux-chromeos-rel""))
@@ -878,7 +880,7 @@
   * Experiment percentage: 10.0
 
 * [mac15-arm64-rel](https://ci.chromium.org/p/chromium/builders/try/mac15-arm64-rel) ([definition](https://cs.chromium.org/search?q=+file:/try/.*\.star$+""mac15-arm64-rel""))
-  * Experiment percentage: 30.0
+  * Experiment percentage: 1.0
 
 * [tricium-clang-tidy](https://ci.chromium.org/p/chromium/builders/try/tricium-clang-tidy) ([definition](https://cs.chromium.org/search?q=+file:/try/.*\.star$+""tricium-clang-tidy""))
   * Experiment percentage: 100.0
diff --git a/infra/config/generated/cq-usage/default.cfg b/infra/config/generated/cq-usage/default.cfg
index 7c519c7..e6c4d6393 100644
--- a/infra/config/generated/cq-usage/default.cfg
+++ b/infra/config/generated/cq-usage/default.cfg
@@ -62,6 +62,9 @@
         name: "chromium/try/fuchsia-x64-cast-receiver-rel"
       }
       builders {
+        name: "chromium/try/ios-simulator"
+      }
+      builders {
         name: "chromium/try/linux-chromeos-compile-dbg"
       }
       builders {
diff --git a/infra/config/generated/cq-usage/full.cfg b/infra/config/generated/cq-usage/full.cfg
index 2d43bc0..ec3c387 100644
--- a/infra/config/generated/cq-usage/full.cfg
+++ b/infra/config/generated/cq-usage/full.cfg
@@ -2572,6 +2572,29 @@
         }
       }
       builders {
+        name: "chromium/try/ios-simulator"
+        location_filters {
+          gerrit_host_regexp: ".*"
+          gerrit_project_regexp: ".*"
+          gerrit_ref_regexp: ".*"
+          path_regexp: "infra/config/.+"
+          exclude: true
+        }
+        location_filters {
+          gerrit_host_regexp: ".*"
+          gerrit_project_regexp: ".*"
+          gerrit_ref_regexp: ".*"
+          path_regexp: "docs/.+"
+          exclude: true
+        }
+        location_filters {
+          gerrit_host_regexp: ".*"
+          gerrit_project_regexp: ".*"
+          gerrit_ref_regexp: ".*"
+          path_regexp: "infra/config/generated/builders/try/ios-simulator/.+"
+        }
+      }
+      builders {
         name: "chromium/try/ios-simulator-full-configs"
         location_filters {
           gerrit_host_regexp: ".*"
diff --git a/infra/config/generated/luci/commit-queue.cfg b/infra/config/generated/luci/commit-queue.cfg
index 16763a8..3506658 100644
--- a/infra/config/generated/luci/commit-queue.cfg
+++ b/infra/config/generated/luci/commit-queue.cfg
@@ -4402,8 +4402,29 @@
       }
       builders {
         name: "chromium/try/ios-simulator"
-        includable_only: true
         disable_reuse_footers: "Include-Ci-Only-Tests"
+        location_filters {
+          gerrit_host_regexp: ".*"
+          gerrit_project_regexp: ".*"
+          gerrit_ref_regexp: ".*"
+          path_regexp: "infra/config/.+"
+          exclude: true
+        }
+        location_filters {
+          gerrit_host_regexp: ".*"
+          gerrit_project_regexp: ".*"
+          gerrit_ref_regexp: ".*"
+          path_regexp: "docs/.+"
+          exclude: true
+        }
+        location_filters {
+          gerrit_host_regexp: ".*"
+          gerrit_project_regexp: ".*"
+          gerrit_ref_regexp: ".*"
+          path_regexp: "infra/config/generated/builders/try/ios-simulator/.+"
+        }
+        mode_allowlist: "DRY_RUN"
+        mode_allowlist: "FULL_RUN"
       }
       builders {
         name: "chromium/try/ios-simulator-code-coverage"
@@ -6723,7 +6744,7 @@
       builders {
         name: "chromium/try/mac15-arm64-rel"
         disable_reuse_footers: "Include-Ci-Only-Tests"
-        experiment_percentage: 30
+        experiment_percentage: 1
         location_filters {
           gerrit_host_regexp: ".*"
           gerrit_project_regexp: ".*"
diff --git a/infra/config/generated/luci/cr-buildbucket.cfg b/infra/config/generated/luci/cr-buildbucket.cfg
index 22be070..3ae6792 100644
--- a/infra/config/generated/luci/cr-buildbucket.cfg
+++ b/infra/config/generated/luci/cr-buildbucket.cfg
@@ -99234,6 +99234,7 @@
         '    }'
         '  },'
         '  "builder_group": "tryserver.chromium.mac",'
+        '  "cq": "required",'
         '  "led_builder_is_bootstrapped": true,'
         '  "recipe": "chromium/orchestrator"'
         '}'
diff --git a/infra/config/subprojects/chromium/try/tryserver.chromium.mac.star b/infra/config/subprojects/chromium/try/tryserver.chromium.mac.star
index 95d2d6bb..358e7ef 100644
--- a/infra/config/subprojects/chromium/try/tryserver.chromium.mac.star
+++ b/infra/config/subprojects/chromium/try/tryserver.chromium.mac.star
@@ -340,7 +340,7 @@
         # TODO (crbug.com/415099984): change to 100,
         # then move out of experimental CQ after,
         # mac15-arm64-rel replaces mac14-arm64-rel on CQ.
-        experiment_percentage = 30,
+        experiment_percentage = 1,
     ),
 )
 
@@ -627,7 +627,7 @@
         "chromium.add_one_test_shard": 10,
     },
     main_list_view = "try",
-    #tryjob = try_.job(),
+    tryjob = try_.job(),
     use_clang_coverage = True,
     xcode = xcode.xcode_default,
 )
diff --git a/internal b/internal
index 3ddd578..603b258 160000
--- a/internal
+++ b/internal
@@ -1 +1 @@
-Subproject commit 3ddd5788bd026e3ed8a3f674c273a6030f54e656
+Subproject commit 603b258ff4ab92ff387e07b3f47c447a52595968
diff --git a/ios/chrome/browser/autofill/ui_bundled/address_editor/autofill_profile_edit_mediator.mm b/ios/chrome/browser/autofill/ui_bundled/address_editor/autofill_profile_edit_mediator.mm
index 883c1f71..2899b968 100644
--- a/ios/chrome/browser/autofill/ui_bundled/address_editor/autofill_profile_edit_mediator.mm
+++ b/ios/chrome/browser/autofill/ui_bundled/address_editor/autofill_profile_edit_mediator.mm
@@ -401,9 +401,9 @@
   const variations::VariationsService* variations_service =
       GetApplicationContext()->GetVariationsService();
   countryModel.SetCountries(
-      GeoIpCountryCode(variations_service
-                           ? variations_service->GetLatestCountry()
-                           : std::string()),
+      autofill::GeoIpCountryCode(variations_service
+                                     ? variations_service->GetLatestCountry()
+                                     : std::string()),
       GetApplicationContext()->GetApplicationLocaleStorage()->Get());
   const autofill::CountryComboboxModel::CountryVector& countriesVector =
       countryModel.countries();
diff --git a/ios/chrome/browser/autofill/ui_bundled/address_editor/autofill_profile_edit_mediator_unittest.mm b/ios/chrome/browser/autofill/ui_bundled/address_editor/autofill_profile_edit_mediator_unittest.mm
index a5c5f302..751d2f33 100644
--- a/ios/chrome/browser/autofill/ui_bundled/address_editor/autofill_profile_edit_mediator_unittest.mm
+++ b/ios/chrome/browser/autofill/ui_bundled/address_editor/autofill_profile_edit_mediator_unittest.mm
@@ -139,9 +139,9 @@
     const variations::VariationsService* variations_service =
         GetApplicationContext()->GetVariationsService();
     country_model_.SetCountries(
-        GeoIpCountryCode(variations_service
-                             ? variations_service->GetLatestCountry()
-                             : std::string()),
+        autofill::GeoIpCountryCode(variations_service
+                                       ? variations_service->GetLatestCountry()
+                                       : std::string()),
         GetApplicationContext()->GetApplicationLocaleStorage()->Get());
     return country_model_.countries();
   }
diff --git a/ios/chrome/browser/autofill/ui_bundled/bottom_sheet/settings_autofill_edit_profile_bottom_sheet_handler.mm b/ios/chrome/browser/autofill/ui_bundled/bottom_sheet/settings_autofill_edit_profile_bottom_sheet_handler.mm
index de31a5b..e172645 100644
--- a/ios/chrome/browser/autofill/ui_bundled/bottom_sheet/settings_autofill_edit_profile_bottom_sheet_handler.mm
+++ b/ios/chrome/browser/autofill/ui_bundled/bottom_sheet/settings_autofill_edit_profile_bottom_sheet_handler.mm
@@ -48,7 +48,7 @@
 - (std::unique_ptr<autofill::AutofillProfile>)autofillProfile {
   // Since this is creating a new (empty) address, use the app's locale country
   // code as the default value.
-  AddressCountryCode countryCode =
+  autofill::AddressCountryCode countryCode =
       _addressDataManager->GetDefaultCountryCodeForNewAddress();
 
   std::unique_ptr<autofill::AutofillProfile> autofillProfile =
diff --git a/ios/chrome/browser/home_customization/coordinator/home_customization_coordinator.mm b/ios/chrome/browser/home_customization/coordinator/home_customization_coordinator.mm
index 559f54e..8d49ce8 100644
--- a/ios/chrome/browser/home_customization/coordinator/home_customization_coordinator.mm
+++ b/ios/chrome/browser/home_customization/coordinator/home_customization_coordinator.mm
@@ -12,6 +12,7 @@
 #import "ios/chrome/browser/home_customization/coordinator/home_customization_background_picker_action_sheet_coordinator.h"
 #import "ios/chrome/browser/home_customization/coordinator/home_customization_delegate.h"
 #import "ios/chrome/browser/home_customization/coordinator/home_customization_mediator.h"
+#import "ios/chrome/browser/home_customization/model/home_background_customization_service.h"
 #import "ios/chrome/browser/home_customization/model/home_background_customization_service_factory.h"
 #import "ios/chrome/browser/home_customization/model/user_uploaded_image_manager_factory.h"
 #import "ios/chrome/browser/home_customization/ui/home_customization_background_color_picker_view_controller.h"
@@ -65,6 +66,10 @@
   // before their configuration requests return.
   NSMutableDictionary<NSString*, SearchEngineLogoMediator*>*
       _activeSearchEngineLogoMediator;
+
+  // The Background customization service for getting current and recently used
+  // backgrounds.
+  raw_ptr<HomeBackgroundCustomizationService> _backgroundService;
 }
 
 // The main page of the customization menu.
@@ -102,14 +107,14 @@
   _activeSearchEngineLogoMediator = [NSMutableDictionary dictionary];
   UserUploadedImageManager* userUploadedImageManager =
       UserUploadedImageManagerFactory::GetForProfile(self.profile);
+  _backgroundService =
+      HomeBackgroundCustomizationServiceFactory::GetForProfile(self.profile);
 
   _mediator = [[HomeCustomizationMediator alloc]
                      initWithPrefService:self.profile->GetPrefs()
       discoverFeedVisibilityBrowserAgent:DiscoverFeedVisibilityBrowserAgent::
                                              FromBrowser(self.browser)
-                       backgroundService:
-                           HomeBackgroundCustomizationServiceFactory::
-                               GetForProfile(self.profile)
+                       backgroundService:_backgroundService
                      imageFetcherService:imageFetcherService
                 userUploadedImageManager:userUploadedImageManager];
   _mediator.navigationDelegate = self;
@@ -207,9 +212,8 @@
       self.mainViewController.backgroundPickerPresentationDelegate = self;
       self.mainViewController.mutator = _mediator;
       self.mainViewController.searchEngineLogoMediatorProvider = self;
-      self.mainViewController.isNTPCustomBackgroundEnabledByPolicy =
-          self.profile->GetPrefs()->GetBoolean(
-              prefs::kNTPCustomBackgroundEnabledByPolicy);
+      self.mainViewController.customizationDisabledByPolicy =
+          _backgroundService->IsCustomizationDisabledByPolicy();
       self.mediator.mainPageConsumer = self.mainViewController;
       [self.mediator configureMainPageData];
       menuPage = self.mainViewController;
diff --git a/ios/chrome/browser/home_customization/coordinator/home_customization_mediator.mm b/ios/chrome/browser/home_customization/coordinator/home_customization_mediator.mm
index 01d23b75..49dde665 100644
--- a/ios/chrome/browser/home_customization/coordinator/home_customization_mediator.mm
+++ b/ios/chrome/browser/home_customization/coordinator/home_customization_mediator.mm
@@ -94,7 +94,7 @@
   [self.mainPageConsumer populateToggles:toggleMap];
 
   if (IsNTPBackgroundCustomizationEnabled() &&
-      _prefService->GetBoolean(prefs::kNTPCustomBackgroundEnabledByPolicy)) {
+      !_backgroundService->IsCustomizationDisabledByPolicy()) {
     BackgroundCollectionConfiguration* collectionConfiguration =
         [[BackgroundCollectionConfiguration alloc] init];
 
diff --git a/ios/chrome/browser/home_customization/model/BUILD.gn b/ios/chrome/browser/home_customization/model/BUILD.gn
index 25d24ae..980fe96 100644
--- a/ios/chrome/browser/home_customization/model/BUILD.gn
+++ b/ios/chrome/browser/home_customization/model/BUILD.gn
@@ -30,6 +30,7 @@
     "//components/pref_registry",
     "//components/sync/protocol",
     "//components/themes",
+    "//components/themes:prefs",
     "//ios/chrome/browser/home_customization/utils",
     "//ios/chrome/browser/shared/model/application_context",
     "//ios/chrome/browser/shared/model/prefs:pref_names",
diff --git a/ios/chrome/browser/home_customization/model/home_background_customization_service.h b/ios/chrome/browser/home_customization/model/home_background_customization_service.h
index 59960be2..cad36bd6 100644
--- a/ios/chrome/browser/home_customization/model/home_background_customization_service.h
+++ b/ios/chrome/browser/home_customization/model/home_background_customization_service.h
@@ -13,6 +13,7 @@
 #import "base/task/sequenced_task_runner.h"
 #import "base/values.h"
 #import "components/keyed_service/core/keyed_service.h"
+#import "components/prefs/pref_change_registrar.h"
 #import "components/sync/protocol/theme_specifics_ios.pb.h"
 #import "components/sync/protocol/theme_types.pb.h"
 #import "ios/chrome/browser/home_customization/model/home_background_data.h"
@@ -200,6 +201,9 @@
   // to disk.
   void ClearCurrentUserUploadedBackground();
 
+  // Return whether the NTP custom background is disabled by enterprise policy.
+  bool IsCustomizationDisabledByPolicy();
+
  private:
   // Alerts observers when the background changes.
   void NotifyObserversOfBackgroundChange();
@@ -250,6 +254,11 @@
   void DeleteUserBackgroundImage(
       HomeUserUploadedBackground user_background_image);
 
+  // Observes changes to enterprise policy prefs for theme color
+  // (kPolicyThemeColor) and custom backgrounds
+  // (kNTPCustomBackgroundEnabledByPolicy).
+  void OnPolicyPrefsChanged(const std::string& name);
+
   // Handles the loaded images.
   void DefaultRecentlyUsedBackgroundsLoaded(
       const HomeBackgroundImageService::CollectionImageMap& collection_map);
@@ -271,6 +280,9 @@
   // Service used to load lists of recently used images.
   raw_ptr<HomeBackgroundImageService> home_background_image_service_;
 
+  // Registrar for prefs change.
+  PrefChangeRegistrar pref_change_registrar_;
+
   base::ObserverList<HomeBackgroundCustomizationServiceObserver> observers_;
 
   base::WeakPtrFactory<HomeBackgroundCustomizationService> weak_ptr_factory_;
diff --git a/ios/chrome/browser/home_customization/model/home_background_customization_service.mm b/ios/chrome/browser/home_customization/model/home_background_customization_service.mm
index d1e81f7..68ee3ba 100644
--- a/ios/chrome/browser/home_customization/model/home_background_customization_service.mm
+++ b/ios/chrome/browser/home_customization/model/home_background_customization_service.mm
@@ -14,6 +14,7 @@
 #import "components/prefs/pref_registry_simple.h"
 #import "components/prefs/pref_service.h"
 #import "components/sync/protocol/theme_types.pb.h"
+#import "components/themes/pref_names.h"
 #import "ios/chrome/browser/home_customization/model/home_background_customization_service_observer.h"
 #import "ios/chrome/browser/home_customization/model/home_background_data.h"
 #import "ios/chrome/browser/home_customization/model/home_background_image_service.h"
@@ -74,9 +75,14 @@
       user_image_manager_(user_image_manager),
       home_background_image_service_(home_background_image_service),
       weak_ptr_factory_{this} {
-  if (!IsNTPBackgroundCustomizationEnabled()) {
-    return;
-  }
+  pref_change_registrar_.Init(pref_service_);
+  PrefChangeRegistrar::NamedChangeCallback callback = base::BindRepeating(
+      &HomeBackgroundCustomizationService::OnPolicyPrefsChanged,
+      weak_ptr_factory_.GetWeakPtr());
+  pref_change_registrar_.Add(themes::prefs::kPolicyThemeColor, callback);
+  pref_change_registrar_.Add(prefs::kNTPCustomBackgroundEnabledByPolicy,
+                             callback);
+
   LoadCurrentTheme();
 
   const base::Value::List& recently_used_backgrounds_list =
@@ -143,6 +149,10 @@
 
 std::optional<HomeCustomBackground>
 HomeBackgroundCustomizationService::GetCurrentCustomBackground() {
+  if (IsCustomizationDisabledByPolicy()) {
+    return std::nullopt;
+  }
+
   std::optional<HomeUserUploadedBackground> user_uploaded_background =
       GetCurrentUserUploadedBackground();
   if (user_uploaded_background) {
@@ -153,6 +163,10 @@
 
 std::optional<sync_pb::NtpCustomBackground>
 HomeBackgroundCustomizationService::GetCurrentNtpCustomBackground() {
+  if (IsCustomizationDisabledByPolicy()) {
+    return std::nullopt;
+  }
+
   if (!current_theme_.has_ntp_background()) {
     return std::nullopt;
   }
@@ -161,6 +175,10 @@
 
 std::optional<sync_pb::UserColorTheme>
 HomeBackgroundCustomizationService::GetCurrentColorTheme() {
+  if (IsCustomizationDisabledByPolicy()) {
+    return std::nullopt;
+  }
+
   if (!current_theme_.has_user_color_theme()) {
     return std::nullopt;
   }
@@ -346,6 +364,11 @@
   if (!IsNTPBackgroundCustomizationEnabled()) {
     return;
   }
+
+  if (IsCustomizationDisabledByPolicy()) {
+    return;
+  }
+
   current_theme_ = DecodeThemeSpecificsIos(
       pref_service_->GetString(prefs::kIosSavedThemeSpecificsIos));
 
@@ -401,6 +424,12 @@
   current_user_uploaded_background_ = std::nullopt;
 }
 
+bool HomeBackgroundCustomizationService::IsCustomizationDisabledByPolicy() {
+  return !pref_service_->GetBoolean(
+             prefs::kNTPCustomBackgroundEnabledByPolicy) ||
+         pref_service_->IsManagedPreference(themes::prefs::kPolicyThemeColor);
+}
+
 RecentlyUsedBackground
 HomeBackgroundCustomizationService::ConvertBackgroundRepresentation(
     RecentlyUsedBackgroundInternal background) {
@@ -520,3 +549,16 @@
 
   StoreRecentlyUsedBackgroundsList();
 }
+
+void HomeBackgroundCustomizationService::OnPolicyPrefsChanged(
+    const std::string& name) {
+  CHECK(themes::prefs::kPolicyThemeColor == name ||
+        prefs::kNTPCustomBackgroundEnabledByPolicy == name);
+  if (IsCustomizationDisabledByPolicy()) {
+    ClearCurrentBackground();
+    return;
+  }
+
+  RestoreCurrentTheme();
+  NotifyObserversOfBackgroundChange();
+}
diff --git a/ios/chrome/browser/home_customization/ui/home_customization_main_view_controller.h b/ios/chrome/browser/home_customization/ui/home_customization_main_view_controller.h
index 61e258f..ca73fcf 100644
--- a/ios/chrome/browser/home_customization/ui/home_customization_main_view_controller.h
+++ b/ios/chrome/browser/home_customization/ui/home_customization_main_view_controller.h
@@ -34,8 +34,8 @@
 @property(nonatomic, weak) id<HomeCustomizationSearchEngineLogoMediatorProvider>
     searchEngineLogoMediatorProvider;
 
-// Whether the NTP custom background is enabled by enterprise policy.
-@property(nonatomic, assign) BOOL isNTPCustomBackgroundEnabledByPolicy;
+// Whether the NTP custom background is disabled by enterprise policy.
+@property(nonatomic, assign) BOOL customizationDisabledByPolicy;
 
 // Whether interaction with the background customization section is enabled.
 // Prevents the background from changing when it should not change.
diff --git a/ios/chrome/browser/home_customization/ui/home_customization_main_view_controller.mm b/ios/chrome/browser/home_customization/ui/home_customization_main_view_controller.mm
index 903e22f..ab8fd1a 100644
--- a/ios/chrome/browser/home_customization/ui/home_customization_main_view_controller.mm
+++ b/ios/chrome/browser/home_customization/ui/home_customization_main_view_controller.mm
@@ -111,8 +111,11 @@
 
   // TODO(crbug.com/439549295): Update the UI to show a message when NTP
   // customization is blocked by enterprise policy.
-  if (IsNTPBackgroundCustomizationEnabled() &&
-      self.isNTPCustomBackgroundEnabledByPolicy) {
+  if (IsNTPBackgroundCustomizationEnabled()) {
+    if (self.customizationDisabledByPolicy) {
+      return;
+    }
+
     _backgroundCellRegistration = [UICollectionViewCellRegistration
         registrationWithCellClass:[HomeCustomizationBackgroundCell class]
              configurationHandler:^(HomeCustomizationBackgroundCell* cell,
@@ -141,7 +144,7 @@
       [[NSDiffableDataSourceSnapshot alloc] init];
 
   if (IsNTPBackgroundCustomizationEnabled() &&
-      self.isNTPCustomBackgroundEnabledByPolicy) {
+      !self.customizationDisabledByPolicy) {
     // Create background customization section and add items to it.
     [snapshot
         appendSectionsWithIdentifiers:@[ kCustomizationSectionBackground ]];
diff --git a/ios/chrome/browser/ntp/ui_bundled/new_tab_page_coordinator.mm b/ios/chrome/browser/ntp/ui_bundled/new_tab_page_coordinator.mm
index 73ba615..9028977 100644
--- a/ios/chrome/browser/ntp/ui_bundled/new_tab_page_coordinator.mm
+++ b/ios/chrome/browser/ntp/ui_bundled/new_tab_page_coordinator.mm
@@ -381,9 +381,7 @@
   [self configureNTPViewController];
   [self configureTabGroupIndicator];
 
-  if (IsNTPBackgroundCustomizationEnabled() &&
-      self.prefService->GetBoolean(
-          prefs::kNTPCustomBackgroundEnabledByPolicy)) {
+  if (IsNTPBackgroundCustomizationEnabled()) {
     // Ensure the initial background is applied after all components have been
     // set up.
     [self.NTPMediator updateBackground];
diff --git a/ios/chrome/browser/ntp/ui_bundled/new_tab_page_mediator.mm b/ios/chrome/browser/ntp/ui_bundled/new_tab_page_mediator.mm
index a1cac9d..e091f6b 100644
--- a/ios/chrome/browser/ntp/ui_bundled/new_tab_page_mediator.mm
+++ b/ios/chrome/browser/ntp/ui_bundled/new_tab_page_mediator.mm
@@ -327,8 +327,7 @@
   _browserViewVisibilityNotifierBrowserAgent->AddObserver(
       _browserViewVisibilityObserverBridge.get());
   _discoverFeedVisibilityBrowserAgent->AddObserver(self.feedVisibilityObserver);
-  if (IsNTPBackgroundCustomizationEnabled() &&
-      _prefService->GetBoolean(prefs::kNTPCustomBackgroundEnabledByPolicy)) {
+  if (IsNTPBackgroundCustomizationEnabled()) {
     _backgroundCustomizationServiceObserverBridge =
         std::make_unique<HomeBackgroundCustomizationServiceObserverBridge>(
             _backgroundCustomizationService, self);
diff --git a/ios/web_view/internal/autofill/cwv_autofill_form_unittest.mm b/ios/web_view/internal/autofill/cwv_autofill_form_unittest.mm
index 8d070d2..9cf63a3d 100644
--- a/ios/web_view/internal/autofill/cwv_autofill_form_unittest.mm
+++ b/ios/web_view/internal/autofill/cwv_autofill_form_unittest.mm
@@ -38,11 +38,12 @@
   std::unique_ptr<autofill::FormStructure> form_structure =
       std::make_unique<autofill::FormStructure>(form_data);
   const autofill::HeuristicPredictions heuristic_predictions =
-      DetermineHeuristicTypes(GeoIpCountryCode(""), autofill::LanguageCode(""),
-                              *form_structure, nullptr);
+      DetermineHeuristicTypes(autofill::GeoIpCountryCode(""),
+                              autofill::LanguageCode(""), *form_structure,
+                              nullptr);
   heuristic_predictions.ApplyTo(form_structure->fields());
   form_structure->RationalizeAndAssignSections(
-      GeoIpCountryCode(""), autofill::LanguageCode(""), nullptr);
+      autofill::GeoIpCountryCode(""), autofill::LanguageCode(""), nullptr);
   CWVAutofillForm* form =
       [[CWVAutofillForm alloc] initWithFormStructure:*form_structure];
   EXPECT_NSEQ(base::SysUTF16ToNSString(form_data.name()), form.name);
diff --git a/ios_internal b/ios_internal
index 6842118..3001825 160000
--- a/ios_internal
+++ b/ios_internal
@@ -1 +1 @@
-Subproject commit 684211821a75efa2da3fad3310385a55308a3da6
+Subproject commit 30018259e2511654b23013f465ac3d119a7e4a35
diff --git a/media/capture/video/android/video_capture_device_android.cc b/media/capture/video/android/video_capture_device_android.cc
index f049013..f8acb54c 100644
--- a/media/capture/video/android/video_capture_device_android.cc
+++ b/media/capture/video/android/video_capture_device_android.cc
@@ -2,11 +2,6 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifdef UNSAFE_BUFFERS_BUILD
-// TODO(crbug.com/40285824): Remove this and convert code to safer constructs.
-#pragma allow_unsafe_buffers
-#endif
-
 #include "media/capture/video/android/video_capture_device_android.h"
 
 #include <stdint.h>
@@ -17,6 +12,7 @@
 #include "base/android/jni_android.h"
 #include "base/android/jni_array.h"
 #include "base/android/jni_string.h"
+#include "base/containers/heap_array.h"
 #include "base/functional/bind.h"
 #include "base/numerics/safe_conversions.h"
 #include "base/strings/string_number_conversions.h"
@@ -364,15 +360,20 @@
   const int y_plane_length = width * height;
   const int uv_plane_length = y_plane_length / 4;
   const int buffer_length = y_plane_length + uv_plane_length * 2;
-  auto buffer = std::make_unique<uint8_t[]>(buffer_length);
+  auto buffer = base::HeapArray<uint8_t>::Uninit(
+      base::checked_cast<size_t>(buffer_length));
+
+  auto dst_y_span = buffer.subspan(0, y_plane_length);
+  auto dst_u_span = buffer.subspan(y_plane_length, uv_plane_length);
+  auto dst_v_span =
+      buffer.subspan(y_plane_length + uv_plane_length, uv_plane_length);
 
   libyuv::Android420ToI420(y_src, y_stride, u_src, uv_row_stride, v_src,
-                           uv_row_stride, uv_pixel_stride, buffer.get(), width,
-                           buffer.get() + y_plane_length, width / 2,
-                           buffer.get() + y_plane_length + uv_plane_length,
-                           width / 2, width, height);
+                           uv_row_stride, uv_pixel_stride, dst_y_span.data(),
+                           width, dst_u_span.data(), width / 2,
+                           dst_v_span.data(), width / 2, width, height);
 
-  SendIncomingDataToClient(buffer.get(), buffer_length, rotation, current_time,
+  SendIncomingDataToClient(buffer.data(), buffer_length, rotation, current_time,
                            capture_time);
 }
 
diff --git a/mojo/public/cpp/bindings/direct_receiver.h b/mojo/public/cpp/bindings/direct_receiver.h
index e57565a..e74c60a7 100644
--- a/mojo/public/cpp/bindings/direct_receiver.h
+++ b/mojo/public/cpp/bindings/direct_receiver.h
@@ -155,6 +155,10 @@
 // DirectReceiver, passing pipes to your DirectReceiver is likely a BAD IDEA.
 template <typename T>
 class DirectReceiver {
+  static_assert(
+      T::kSupportsDirectReceiver,
+      "This interface must be marked with the [DirectReceiver] attribute.");
+
  public:
   DirectReceiver(DirectReceiverKey, T* impl) : receiver_(impl) {}
   ~DirectReceiver() = default;
diff --git a/mojo/public/cpp/bindings/tests/direct_receiver_unittest.test-mojom b/mojo/public/cpp/bindings/tests/direct_receiver_unittest.test-mojom
index d86b8f1..f1c0033d 100644
--- a/mojo/public/cpp/bindings/tests/direct_receiver_unittest.test-mojom
+++ b/mojo/public/cpp/bindings/tests/direct_receiver_unittest.test-mojom
@@ -4,7 +4,7 @@
 
 module mojo.test.direct_receiver_unittest.mojom;
 
+[DirectReceiver]
 interface Service {
   Ping() => ();
 };
-
diff --git a/mojo/public/tools/bindings/BUILD.gn b/mojo/public/tools/bindings/BUILD.gn
index b99a4911..6216ac9 100644
--- a/mojo/public/tools/bindings/BUILD.gn
+++ b/mojo/public/tools/bindings/BUILD.gn
@@ -40,7 +40,6 @@
     "$mojom_generator_root/generators/cpp_templates/struct_declaration.tmpl",
     "$mojom_generator_root/generators/cpp_templates/struct_definition.tmpl",
     "$mojom_generator_root/generators/cpp_templates/struct_macros.tmpl",
-    "$mojom_generator_root/generators/cpp_templates/struct_macros.tmpl",
     "$mojom_generator_root/generators/cpp_templates/struct_serialization_declaration.tmpl",
     "$mojom_generator_root/generators/cpp_templates/struct_traits_declaration.tmpl",
     "$mojom_generator_root/generators/cpp_templates/struct_traits_definition.tmpl",
@@ -132,6 +131,7 @@
   data = [
     mojom_generator_script,
     "checks/mojom_attributes_check_unittest.py",
+    "checks/mojom_interface_direct_receiver_check_unittest.py",
     "checks/mojom_interface_feature_check_unittest.py",
     "checks/mojom_restrictions_checks_unittest.py",
     "mojom_bindings_generator_unittest.py",
diff --git a/mojo/public/tools/bindings/checks/mojom_attributes_check.py b/mojo/public/tools/bindings/checks/mojom_attributes_check.py
index e1b7a42d..7f4e1d1 100644
--- a/mojo/public/tools/bindings/checks/mojom_attributes_check.py
+++ b/mojo/public/tools/bindings/checks/mojom_attributes_check.py
@@ -35,6 +35,7 @@
 
 _INTERFACE_ATTRIBUTES = _COMMON_ATTRIBUTES | {
     'DispatchDebugAlias',
+    'DirectReceiver',
     'RenamedFrom',
     'RequireContext',
     'RuntimeFeature',
diff --git a/mojo/public/tools/bindings/checks/mojom_attributes_check_unittest.py b/mojo/public/tools/bindings/checks/mojom_attributes_check_unittest.py
index 0498faf..e0dc5b2 100644
--- a/mojo/public/tools/bindings/checks/mojom_attributes_check_unittest.py
+++ b/mojo/public/tools/bindings/checks/mojom_attributes_check_unittest.py
@@ -103,6 +103,11 @@
         [SendValidation=test.mojom.FeatureName]
         MethodWithSendValidation(Thingy thing);
       };
+
+      [DirectReceiver]
+      interface FooWithDirectReceiver {
+        Method();
+      };
     """)
 
   def testWrongModuleStable(self):
diff --git a/mojo/public/tools/bindings/checks/mojom_interface_direct_receiver_check.py b/mojo/public/tools/bindings/checks/mojom_interface_direct_receiver_check.py
new file mode 100644
index 0000000..225f4a6
--- /dev/null
+++ b/mojo/public/tools/bindings/checks/mojom_interface_direct_receiver_check.py
@@ -0,0 +1,60 @@
+# 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.
+"""Validate mojo interfaces passed by [DirectReceiver] interfaces are also
+[DirectReceiver] interfaces."""
+
+import mojom.generate.check as check
+import mojom.generate.module as module
+
+
+class Check(check.Check):
+
+  def __init__(self, *args, **kwargs):
+    super(Check, self).__init__(*args, **kwargs)
+
+  # `param` can be a lot of things so check if it is a remote/receiver.
+  # Array/Map must be recursed into.
+  def _CheckFieldOrParam(self, kind):
+    if module.IsAnyInterfaceKind(kind):
+      if module.IsPendingRemoteKind(kind):
+        return
+      if module.IsPendingAssociatedRemoteKind(kind):
+        return
+      if not kind.kind.direct_receiver:
+        interface = kind.kind.mojom_name
+        raise check.CheckException(
+            self.module, f"interface {interface} must be a DirectReceiver")
+    if module.IsStructKind(kind):
+      self._CheckStruct(kind)
+    if module.IsUnionKind(kind):
+      self._CheckUnion(kind)
+    if module.IsArrayKind(kind):
+      self._CheckFieldOrParam(kind.kind)
+    if module.IsMapKind(kind):
+      self._CheckFieldOrParam(kind.key_kind)
+      self._CheckFieldOrParam(kind.value_kind)
+
+  def _CheckInterface(self, interface):
+    if not interface.direct_receiver:
+      return
+    for method in interface.methods:
+      for param in method.parameters:
+        self._CheckFieldOrParam(param.kind)
+      if method.response_parameters:
+        for param in method.response_parameters:
+          self._CheckFieldOrParam(param.kind)
+
+  def _CheckStruct(self, struct):
+    for field in struct.fields:
+      self._CheckFieldOrParam(field.kind)
+
+  def _CheckUnion(self, union):
+    for field in union.fields:
+      self._CheckFieldOrParam(field.kind)
+
+  def CheckModule(self):
+    """Validate that any runtime feature guarded interfaces that might be passed
+    over mojo are nullable."""
+    for interface in self.module.interfaces:
+      self._CheckInterface(interface)
diff --git a/mojo/public/tools/bindings/checks/mojom_interface_direct_receiver_check_unittest.py b/mojo/public/tools/bindings/checks/mojom_interface_direct_receiver_check_unittest.py
new file mode 100644
index 0000000..cbf671b
--- /dev/null
+++ b/mojo/public/tools/bindings/checks/mojom_interface_direct_receiver_check_unittest.py
@@ -0,0 +1,154 @@
+# Copyright 2025 The Chromium Authors
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import unittest
+
+import mojom.generate.check as check
+from mojom_bindings_generator import LoadChecks, _Generate
+from mojom_parser_test_case import MojomParserTestCase
+
+
+class FakeArgs:
+  """Fakes args to _Generate - intention is to do just enough to run checks"""
+
+  def __init__(self, tester, files=None):
+    """ `tester` is MojomParserTestCase for paths.
+        `files` will have tester path added."""
+    self.checks_string = 'direct_receiver'
+    self.depth = tester.GetPath('')
+    self.filelist = None
+    self.filename = [tester.GetPath(x) for x in files]
+    self.gen_directories = tester.GetPath('gen')
+    self.generators_string = ''
+    self.import_directories = []
+    self.output_dir = tester.GetPath('out')
+    self.scrambled_message_id_salt_paths = None
+    self.typemaps = []
+    self.variant = 'none'
+
+
+class MojoBindingsCheckTest(MojomParserTestCase):
+
+  def _ParseAndGenerate(self, mojoms):
+    self.ParseMojoms(mojoms)
+    args = FakeArgs(self, files=mojoms)
+    _Generate(args, {})
+
+  def assertValid(self, filename, content):
+    self.WriteFile(filename, content)
+    self._ParseAndGenerate([filename])
+
+  def assertThrows(self, filename, content, regexp):
+    mojoms = []
+    self.WriteFile(filename, content)
+    mojoms.append(filename)
+    with self.assertRaisesRegex(check.CheckException, regexp):
+      self._ParseAndGenerate(mojoms)
+
+  def testLoads(self):
+    """Validate that the check is registered under the expected name."""
+    check_modules = LoadChecks('direct_receiver')
+    self.assertTrue(check_modules['direct_receiver'])
+
+  def testAllowedWhenAnnotated(self):
+    self.assertValid(
+        "a.mojom", """
+          module a;
+          struct B {
+            pending_associated_receiver<MyInterface3> passed;
+            pending_remote<Normal> normal;
+          };
+          interface Normal {};
+          [DirectReceiver]
+          interface MyInterface {
+            Method(pending_receiver<MyInterface2> passed);
+            Method2(B b, pending_associated_remote<Normal> normal);
+          };
+          [DirectReceiver]
+          interface MyInterface2 {
+          };
+          [DirectReceiver]
+          interface MyInterface3 {
+          };
+    """)
+
+  def testMissingViaMethod(self):
+    contents = """
+      module a;
+      interface A {};
+
+      [DirectReceiver]
+      interface B {
+        Method(pending_receiver<A> a);
+      };
+    """
+    self.assertThrows('b.mojom', contents,
+                      'interface A must be a DirectReceiver')
+
+  def testMissingViaStruct(self):
+    contents = """
+      module a;
+      interface A {};
+      struct C {
+        pending_receiver<A> a;
+      };
+
+      [DirectReceiver]
+      interface B {
+        Method(C c);
+      };
+    """
+    self.assertThrows('b.mojom', contents,
+                      'interface A must be a DirectReceiver')
+
+  def testMissingViaUnion(self):
+    contents = """
+      module a;
+      interface A {};
+      union C {
+        pending_receiver<A> a;
+        bool other;
+      };
+
+      [DirectReceiver]
+      interface B {
+        Method(C c);
+      };
+    """
+    self.assertThrows('b.mojom', contents,
+                      'interface A must be a DirectReceiver')
+
+  def testMissingViaArray(self):
+    contents = """
+      module a;
+      interface A {};
+      struct C {
+        array<pending_receiver<A>> a;
+        bool other;
+      };
+
+      [DirectReceiver]
+      interface B {
+        Method(C c);
+      };
+    """
+    self.assertThrows('b.mojom', contents,
+                      'interface A must be a DirectReceiver')
+
+  def testMissingViaMap(self):
+    contents = """
+      module a;
+      interface A {};
+      struct C {
+        map<string, pending_receiver<A>> a;
+        bool other;
+      };
+
+      [DirectReceiver]
+      interface B {
+        Method(C c);
+      };
+    """
+    self.assertThrows('b.mojom', contents,
+                      'interface A must be a DirectReceiver')
diff --git a/mojo/public/tools/bindings/generators/cpp_templates/interface_declaration.tmpl b/mojo/public/tools/bindings/generators/cpp_templates/interface_declaration.tmpl
index fac7c13b..b9ef3fb6 100644
--- a/mojo/public/tools/bindings/generators/cpp_templates/interface_declaration.tmpl
+++ b/mojo/public/tools/bindings/generators/cpp_templates/interface_declaration.tmpl
@@ -29,6 +29,9 @@
   static constexpr base::Token Uuid_{ {{interface.uuid[0]}}ULL,
                                       {{interface.uuid[1]}}ULL };
 {%-  endif %}
+{%- if interface.direct_receiver %}
+  static constexpr bool kSupportsDirectReceiver = true;
+{%- endif %}
 {%- if interface.service_sandbox %}
 {%- set sandbox_enum = "%s"|format(interface.service_sandbox.GetSpec()|replace(".","::")) %}
   static constexpr auto kServiceSandbox = {{ sandbox_enum }};
diff --git a/mojo/public/tools/bindings/mojom.gni b/mojo/public/tools/bindings/mojom.gni
index b8d5bad..87b3bbe9 100644
--- a/mojo/public/tools/bindings/mojom.gni
+++ b/mojo/public/tools/bindings/mojom.gni
@@ -107,6 +107,7 @@
       "$mojom_generator_root/checks/__init__.py",
       "$mojom_generator_root/checks/mojom_attributes_check.py",
       "$mojom_generator_root/checks/mojom_definitions_check.py",
+      "$mojom_generator_root/checks/mojom_interface_direct_receiver_check.py",
       "$mojom_generator_root/checks/mojom_interface_feature_check.py",
       "$mojom_generator_root/checks/mojom_restrictions_check.py",
       "$mojom_generator_root/generators/__init__.py",
diff --git a/mojo/public/tools/bindings/mojom_bindings_generator.py b/mojo/public/tools/bindings/mojom_bindings_generator.py
index fc327e89..229d5889 100755
--- a/mojo/public/tools/bindings/mojom_bindings_generator.py
+++ b/mojo/public/tools/bindings/mojom_bindings_generator.py
@@ -57,6 +57,7 @@
 _BUILTIN_CHECKS = {
     "attributes": "mojom_attributes_check",
     "definitions": "mojom_definitions_check",
+    "direct_receiver": "mojom_interface_direct_receiver_check",
     "features": "mojom_interface_feature_check",
     "restrictions": "mojom_restrictions_check",
 }
diff --git a/mojo/public/tools/mojom/mojom/generate/module.py b/mojo/public/tools/mojom/mojom/generate/module.py
index 33eb6e0..46d4e31 100644
--- a/mojo/public/tools/mojom/mojom/generate/module.py
+++ b/mojo/public/tools/mojom/mojom/generate/module.py
@@ -389,6 +389,7 @@
 
 ATTRIBUTE_ALLOWED_CONTEXT = 'AllowedContext'
 ATTRIBUTE_DEFAULT = 'Default'
+ATTRIBUTE_DIRECT_RECEIVER = 'DirectReceiver'
 ATTRIBUTE_DISPATCH_DEBUG_ALIAS = 'DispatchDebugAlias'
 ATTRIBUTE_ESTIMATE_SIZE = 'EstimateSize'
 ATTRIBUTE_EXTENSIBLE = 'Extensible'
@@ -1167,6 +1168,11 @@
       constant.Stylize(stylizer)
 
   @property
+  def direct_receiver(self):
+    return self.attributes.get(ATTRIBUTE_DIRECT_RECEIVER, False) \
+        if self.attributes else False
+
+  @property
   def service_sandbox(self):
     if not self.attributes:
       return None
diff --git a/net/android/java/src/org/chromium/net/NetworkChangeNotifier.java b/net/android/java/src/org/chromium/net/NetworkChangeNotifier.java
index ad753250..63291e4 100644
--- a/net/android/java/src/org/chromium/net/NetworkChangeNotifier.java
+++ b/net/android/java/src/org/chromium/net/NetworkChangeNotifier.java
@@ -38,7 +38,7 @@
      * The alert is fired on the UI thread.
      */
     public interface ConnectionTypeObserver {
-        public void onConnectionTypeChanged(int connectionType);
+        void onConnectionTypeChanged(int connectionType);
     }
 
     private final ArrayList<Long> mNativeChangeNotifiers;
diff --git a/net/android/java/src/org/chromium/net/NetworkChangeNotifierAutoDetect.java b/net/android/java/src/org/chromium/net/NetworkChangeNotifierAutoDetect.java
index 15dd657..2cac8c2 100644
--- a/net/android/java/src/org/chromium/net/NetworkChangeNotifierAutoDetect.java
+++ b/net/android/java/src/org/chromium/net/NetworkChangeNotifierAutoDetect.java
@@ -995,48 +995,45 @@
     private boolean mRegisterNetworkCallbackFailed;
 
     /** Observer interface by which observer is notified of network changes. */
-    public static interface Observer {
+    public interface Observer {
         /** Called when default network changes. */
-        public void onConnectionTypeChanged(@ConnectionType int newConnectionType);
+        void onConnectionTypeChanged(@ConnectionType int newConnectionType);
 
         /** Called when connection cost of default network changes. */
-        public void onConnectionCostChanged(int newConnectionCost);
+        void onConnectionCostChanged(int newConnectionCost);
 
         /** Called when connection subtype of default network changes. */
-        public void onConnectionSubtypeChanged(int newConnectionSubtype);
+        void onConnectionSubtypeChanged(int newConnectionSubtype);
 
         /**
-         * Called when device connects to network with NetID netId. For
-         * example device associates with a WiFi access point.
-         * connectionType is the type of the network; a member of
+         * Called when device connects to network with NetID netId. For example device associates
+         * with a WiFi access point. connectionType is the type of the network; a member of
          * ConnectionType. Only called on Android L and above.
          */
-        public void onNetworkConnect(long netId, int connectionType);
+        void onNetworkConnect(long netId, int connectionType);
 
         /**
-         * Called when device determines the connection to the network with
-         * NetID netId is no longer preferred, for example when a device
-         * transitions from cellular to WiFi it might deem the cellular
-         * connection no longer preferred. The device will disconnect from
-         * the network in 30s allowing network communications on that network
-         * to wrap up. Only called on Android L and above.
+         * Called when device determines the connection to the network with NetID netId is no longer
+         * preferred, for example when a device transitions from cellular to WiFi it might deem the
+         * cellular connection no longer preferred. The device will disconnect from the network in
+         * 30s allowing network communications on that network to wrap up. Only called on Android L
+         * and above.
          */
-        public void onNetworkSoonToDisconnect(long netId);
+        void onNetworkSoonToDisconnect(long netId);
 
         /**
-         * Called when device disconnects from network with NetID netId.
-         * Only called on Android L and above.
+         * Called when device disconnects from network with NetID netId. Only called on Android L
+         * and above.
          */
-        public void onNetworkDisconnect(long netId);
+        void onNetworkDisconnect(long netId);
 
         /**
-         * Called to cause a purge of cached lists of active networks, of any
-         * networks not in the accompanying list of active networks. This is
-         * issued if a period elapsed where disconnected notifications may have
-         * been missed, and acts to keep cached lists of active networks
-         * accurate. Only called on Android L and above.
+         * Called to cause a purge of cached lists of active networks, of any networks not in the
+         * accompanying list of active networks. This is issued if a period elapsed where
+         * disconnected notifications may have been missed, and acts to keep cached lists of active
+         * networks accurate. Only called on Android L and above.
          */
-        public void purgeActiveNetworkList(long[] activeNetIds);
+        void purgeActiveNetworkList(long[] activeNetIds);
     }
 
     /**
diff --git a/net/android/java/src/org/chromium/net/ProxyChangeListener.java b/net/android/java/src/org/chromium/net/ProxyChangeListener.java
index 2ade51b..a4af8a0e 100644
--- a/net/android/java/src/org/chromium/net/ProxyChangeListener.java
+++ b/net/android/java/src/org/chromium/net/ProxyChangeListener.java
@@ -113,7 +113,7 @@
 
     /** The delegate for ProxyChangeListener. Use for testing. */
     public interface Delegate {
-        public void proxySettingsChanged();
+        void proxySettingsChanged();
     }
 
     private ProxyChangeListener() {
diff --git a/net/android/javatests/src/org/chromium/net/NetworkChangeNotifierTest.java b/net/android/javatests/src/org/chromium/net/NetworkChangeNotifierTest.java
index 01a9311..d6646ea2 100644
--- a/net/android/javatests/src/org/chromium/net/NetworkChangeNotifierTest.java
+++ b/net/android/javatests/src/org/chromium/net/NetworkChangeNotifierTest.java
@@ -369,7 +369,7 @@
 
     // Types of network changes. Each is associated with a NetworkChangeNotifierAutoDetect.Observer
     // callback, and NONE is provided to indicate no callback observed.
-    private static enum ChangeType {
+    private enum ChangeType {
         NONE,
         CONNECT,
         SOON_TO_DISCONNECT,
@@ -459,7 +459,7 @@
     private MockConnectivityManagerDelegate mConnectivityDelegate;
     private MockWifiManagerDelegate mWifiDelegate;
 
-    private static enum WatchForChanges {
+    private enum WatchForChanges {
         ALWAYS,
         ONLY_WHEN_APP_IN_FOREGROUND,
     }
diff --git a/net/base/features.h b/net/base/features.h
index fca06d14..df5d808a 100644
--- a/net/base/features.h
+++ b/net/base/features.h
@@ -587,9 +587,12 @@
 NET_EXPORT extern const base::FeatureParam<bool> kIpPrivacyEnableIppInDevTools;
 
 // Enables the ability for IP protection features to be gated in the Privacy
-// and Security Panel within DevTools. When this flag is disabled, the IP
-// Protection section will not be shown in the DevTools panel, allowing testing
-// and development of the IP Protection features before public release.
+// and Security Panel within DevTools. When this flag is enabled, the IP
+// Protection section will be shown in the Privacy and Security section of the
+// DevTools panel allowing users to view proxied requests and bypass IP
+// Protection locally.
+// Do not remove or enable this flag for all users until crbug.com/442349180
+// is resolved.
 NET_EXPORT extern const base::FeatureParam<bool>
     kIpPrivacyEnableIppPanelInDevTools;
 
diff --git a/net/extras/sqlite/sqlite_persistent_cookie_store.cc b/net/extras/sqlite/sqlite_persistent_cookie_store.cc
index 05bfa24c..9eeffeef 100644
--- a/net/extras/sqlite/sqlite_persistent_cookie_store.cc
+++ b/net/extras/sqlite/sqlite_persistent_cookie_store.cc
@@ -197,14 +197,14 @@
 // Version 21 - 2023/11/22 - https://crrev.com/c/5049032
 // Version 20 - 2023/11/14 - https://crrev.com/c/5030577
 // Version 19 - 2023/09/22 - https://crrev.com/c/4704672
-// Version 18 - 2022/04/19 - https://crrev.com/c/3594203
 //
 // Versions older than two years should be removed and marked as unsupported.
-// This was last done in February 2024. https://crrev.com/c/5300252
+// This was last done in September 2025. https://crrev.com/c/6819011
 // Be sure to update SQLitePersistentCookieStoreTest.TestInvalidVersionRecovery
 // to test the latest unsupported version number.
 //
 // Unsupported versions:
+// Version 18 - 2022/04/19 - https://crrev.com/c/3594203
 // Version 17 - 2022/01/25 - https://crrev.com/c/3416230
 // Version 16 - 2021/09/10 - https://crrev.com/c/3152897
 // Version 15 - 2021/07/01 - https://crrev.com/c/3001822
@@ -1148,38 +1148,6 @@
 SQLitePersistentCookieStore::Backend::DoMigrateDatabaseSchema() {
   int cur_version = meta_table()->GetVersionNumber();
 
-  if (cur_version == 18) {
-    SCOPED_UMA_HISTOGRAM_TIMER("Cookie.TimeDatabaseMigrationToV19");
-
-    sql::Statement update_statement(
-        db()->GetCachedStatement(SQL_FROM_HERE,
-                                 "UPDATE cookies SET expires_utc = ? WHERE "
-                                 "has_expires = 1 AND expires_utc > ?"));
-    if (!update_statement.is_valid()) {
-      return std::nullopt;
-    }
-
-    sql::Transaction transaction(db());
-    if (!transaction.Begin()) {
-      return std::nullopt;
-    }
-
-    base::Time expires_cap = base::Time::Now() + base::Days(400);
-    update_statement.BindTime(0, expires_cap);
-    update_statement.BindTime(1, expires_cap);
-    if (!update_statement.Run()) {
-      return std::nullopt;
-    }
-
-    ++cur_version;
-    if (!meta_table()->SetVersionNumber(cur_version) ||
-        !meta_table()->SetCompatibleVersionNumber(
-            std::min(cur_version, kCompatibleVersionNumber)) ||
-        !transaction.Commit()) {
-      return std::nullopt;
-    }
-  }
-
   if (cur_version == 19) {
     SCOPED_UMA_HISTOGRAM_TIMER("Cookie.TimeDatabaseMigrationToV20");
 
diff --git a/net/extras/sqlite/sqlite_persistent_cookie_store_unittest.cc b/net/extras/sqlite/sqlite_persistent_cookie_store_unittest.cc
index 28a6428..6b7181a 100644
--- a/net/extras/sqlite/sqlite_persistent_cookie_store_unittest.cc
+++ b/net/extras/sqlite/sqlite_persistent_cookie_store_unittest.cc
@@ -370,7 +370,7 @@
     ASSERT_TRUE(meta_table.Init(&db, 1, 1));
     // Keep in sync with latest unsupported version from:
     // net/extras/sqlite/sqlite_persistent_cookie_store.cc
-    ASSERT_TRUE(meta_table.SetVersionNumber(17));
+    ASSERT_TRUE(meta_table.SetVersionNumber(18));
   }
 
   // Upon loading, the database should be reset to a good, blank state.
@@ -1694,13 +1694,13 @@
                                 sql::SqliteLoggedResultCode::kNotADatabase, 1);
 }
 
-bool CreateV18Schema(sql::Database* db) {
+bool CreateV19Schema(sql::Database* db) {
   sql::MetaTable meta_table;
-  if (!meta_table.Init(db, 18, 18)) {
+  if (!meta_table.Init(db, 19, 19)) {
     return false;
   }
 
-  // Version 18 schema
+  // Version 19 schema
   static constexpr char kCreateSql[] =
       "CREATE TABLE cookies("
       "creation_utc INTEGER NOT NULL,"
@@ -1938,11 +1938,7 @@
   return cookies;
 }
 
-// Versions 18, 19, and 20 use the same schema so they can reuse this function.
-// AddV20CookiesToDB (and future versions) need to set max_expiration_delta to
-// base::Days(400) to simulate expiration limits introduced in version 19.
-bool AddV18CookiesToDB(sql::Database* db,
-                       base::TimeDelta max_expiration_delta) {
+bool AddV19or20CookiesToDB(sql::Database* db) {
   std::vector<CanonicalCookie> cookies = CookiesForMigrationTest();
   sql::Statement statement(db->GetCachedStatement(
       SQL_FROM_HERE,
@@ -1959,7 +1955,7 @@
     return false;
   }
   for (const CanonicalCookie& cookie : cookies) {
-    base::Time max_expiration(cookie.CreationDate() + max_expiration_delta);
+    base::Time max_expiration(cookie.CreationDate() + base::Days(400));
 
     statement.Reset(true);
     statement.BindTime(0, cookie.CreationDate());
@@ -2000,10 +1996,6 @@
   return transaction.Commit();
 }
 
-bool AddV20CookiesToDB(sql::Database* db) {
-  return AddV18CookiesToDB(db, base::Days(400));
-}
-
 bool AddV21CookiesToDB(sql::Database* db) {
   std::vector<CanonicalCookie> cookies = CookiesForMigrationTest();
   sql::Statement statement(db->GetCachedStatement(
@@ -2320,29 +2312,6 @@
   ASSERT_GE(GetDBCurrentVersionNumber(&connection), version);
 }
 
-TEST_F(SQLitePersistentCookieStoreTest, UpgradeToSchemaVersion19) {
-  // Open db.
-  const base::FilePath database_path =
-      temp_dir_.GetPath().Append(kCookieFilename);
-  {
-    sql::Database connection(sql::test::kTestTag);
-    ASSERT_TRUE(connection.Open(database_path));
-    ASSERT_TRUE(CreateV18Schema(&connection));
-    ASSERT_EQ(GetDBCurrentVersionNumber(&connection), 18);
-    ASSERT_TRUE(AddV18CookiesToDB(&connection, base::TimeDelta::Max()));
-  }
-
-  CanonicalCookieVector read_in_cookies = CreateAndLoad(
-      /*crypt_cookies=*/false, /*restore_old_session_cookies=*/false);
-  ASSERT_NO_FATAL_FAILURE(
-      ConfirmCookiesAfterMigrationTest(std::move(read_in_cookies),
-                                       /*expect_last_update_date=*/true));
-  DestroyStore();
-
-  ASSERT_NO_FATAL_FAILURE(
-      ConfirmDatabaseVersionAfterMigration(database_path, 19));
-}
-
 TEST_F(SQLitePersistentCookieStoreTest, UpgradeToSchemaVersion20) {
   // Open db.
   const base::FilePath database_path =
@@ -2350,10 +2319,9 @@
   {
     sql::Database connection(sql::test::kTestTag);
     ASSERT_TRUE(connection.Open(database_path));
-    // V19's schema is the same as V18, so we can reuse the creation function.
-    ASSERT_TRUE(CreateV18Schema(&connection));
-    ASSERT_EQ(GetDBCurrentVersionNumber(&connection), 18);
-    ASSERT_TRUE(AddV18CookiesToDB(&connection, base::TimeDelta::Max()));
+    ASSERT_TRUE(CreateV19Schema(&connection));
+    ASSERT_EQ(GetDBCurrentVersionNumber(&connection), 19);
+    ASSERT_TRUE(AddV19or20CookiesToDB(&connection));
   }
 
   CanonicalCookieVector read_in_cookies = CreateAndLoad(
@@ -2376,7 +2344,7 @@
     ASSERT_TRUE(connection.Open(database_path));
     ASSERT_TRUE(CreateV20Schema(&connection));
     ASSERT_EQ(GetDBCurrentVersionNumber(&connection), 20);
-    ASSERT_TRUE(AddV20CookiesToDB(&connection));
+    ASSERT_TRUE(AddV19or20CookiesToDB(&connection));
   }
 
   CanonicalCookieVector read_in_cookies = CreateAndLoad(
diff --git a/pdf/pdfium/pdfium_engine.cc b/pdf/pdfium/pdfium_engine.cc
index a04a920..b574eae 100644
--- a/pdf/pdfium/pdfium_engine.cc
+++ b/pdf/pdfium/pdfium_engine.cc
@@ -1509,7 +1509,7 @@
       caret_->SetChar(
           PageCharacterIndex(point_data.page_index, point_data.char_index));
     }
-  } else if (click_count == 2 || click_count == 3) {
+  } else if (click_count >= 2) {
     OnMultipleClick(click_count, point_data.page_index, point_data.char_index);
   }
 }
diff --git a/pdf/pdfium/pdfium_engine_unittest.cc b/pdf/pdfium/pdfium_engine_unittest.cc
index 638e10d..750057dec 100644
--- a/pdf/pdfium/pdfium_engine_unittest.cc
+++ b/pdf/pdfium/pdfium_engine_unittest.cc
@@ -143,6 +143,24 @@
 #endif  // BUILDFLAG(ENABLE_PDF_INK2)
 };
 
+void SimulateMultiClick(PDFiumEngine& engine,
+                        const gfx::PointF& position,
+                        int click_count) {
+  for (int i = 0, click = 1; i < click_count; ++i, ++click) {
+#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
+    // On both Linux and ChromeOS `click_count` is only 1, 2 or 3. On MacOS and
+    // Windows `click_count` just keeps increasing as the user keeps clicking.
+    if (click > 3) {
+      click = 1;
+    }
+#endif
+    EXPECT_TRUE(engine.HandleInputEvent(MouseEventBuilder()
+                                            .CreateLeftClickAtPosition(position)
+                                            .SetClickCount(click)
+                                            .Build()));
+  }
+}
+
 }  // namespace
 
 class PDFiumEngineTest : public PDFiumTestBase {
@@ -866,10 +884,7 @@
   EXPECT_THAT(engine->GetSelectedText(), IsEmpty());
 
   constexpr gfx::PointF kPosition(100, 120);
-  EXPECT_TRUE(engine->HandleInputEvent(MouseEventBuilder()
-                                           .CreateLeftClickAtPosition(kPosition)
-                                           .SetClickCount(2)
-                                           .Build()));
+  SimulateMultiClick(*engine, kPosition, 2);
   EXPECT_EQ("Goodbye", engine->GetSelectedText());
 }
 
@@ -885,10 +900,63 @@
   EXPECT_THAT(engine->GetSelectedText(), IsEmpty());
 
   constexpr gfx::PointF kPosition(100, 120);
-  EXPECT_TRUE(engine->HandleInputEvent(MouseEventBuilder()
-                                           .CreateLeftClickAtPosition(kPosition)
-                                           .SetClickCount(3)
-                                           .Build()));
+  SimulateMultiClick(*engine, kPosition, 3);
+  EXPECT_EQ("Goodbye, world!", engine->GetSelectedText());
+}
+
+TEST_P(PDFiumEngineTest, SelectTextWithFourClicks) {
+  TestClient client;
+  std::unique_ptr<PDFiumEngine> engine =
+      InitializeEngine(&client, FILE_PATH_LITERAL("hello_world2.pdf"));
+  ASSERT_TRUE(engine);
+
+  // Plugin size chosen so all pages of the document are visible.
+  engine->PluginSizeUpdated({1024, 4096});
+
+  EXPECT_THAT(engine->GetSelectedText(), IsEmpty());
+
+  constexpr gfx::PointF kPosition(100, 120);
+  SimulateMultiClick(*engine, kPosition, 4);
+#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
+  EXPECT_THAT(engine->GetSelectedText(), IsEmpty());
+#else
+  EXPECT_EQ("Goodbye, world!", engine->GetSelectedText());
+#endif
+}
+
+TEST_P(PDFiumEngineTest, SelectTextFiveClicks) {
+  TestClient client;
+  std::unique_ptr<PDFiumEngine> engine =
+      InitializeEngine(&client, FILE_PATH_LITERAL("hello_world2.pdf"));
+  ASSERT_TRUE(engine);
+
+  // Plugin size chosen so all pages of the document are visible.
+  engine->PluginSizeUpdated({1024, 4096});
+
+  EXPECT_THAT(engine->GetSelectedText(), IsEmpty());
+
+  constexpr gfx::PointF kPosition(100, 120);
+  SimulateMultiClick(*engine, kPosition, 5);
+#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
+  EXPECT_EQ("Goodbye", engine->GetSelectedText());
+#else
+  EXPECT_EQ("Goodbye, world!", engine->GetSelectedText());
+#endif
+}
+
+TEST_P(PDFiumEngineTest, SelectTextWithSixClicks) {
+  TestClient client;
+  std::unique_ptr<PDFiumEngine> engine =
+      InitializeEngine(&client, FILE_PATH_LITERAL("hello_world2.pdf"));
+  ASSERT_TRUE(engine);
+
+  // Plugin size chosen so all pages of the document are visible.
+  engine->PluginSizeUpdated({1024, 4096});
+
+  EXPECT_THAT(engine->GetSelectedText(), IsEmpty());
+
+  constexpr gfx::PointF kPosition(100, 120);
+  SimulateMultiClick(*engine, kPosition, 6);
   EXPECT_EQ("Goodbye, world!", engine->GetSelectedText());
 }
 
diff --git a/services/network/BUILD.gn b/services/network/BUILD.gn
index 9bd750e44..7e605af 100644
--- a/services/network/BUILD.gn
+++ b/services/network/BUILD.gn
@@ -61,8 +61,8 @@
     "host_resolver.h",
     "host_resolver_mdns_listener.cc",
     "host_resolver_mdns_listener.h",
-    "http_auth_cache_copier.cc",
-    "http_auth_cache_copier.h",
+    "http_auth_cache_proxy_copier.cc",
+    "http_auth_cache_proxy_copier.h",
     "http_cache_data_counter.cc",
     "http_cache_data_counter.h",
     "http_cache_data_remover.cc",
diff --git a/services/network/http_auth_cache_copier.cc b/services/network/http_auth_cache_copier.cc
deleted file mode 100644
index 3aff7b57..0000000
--- a/services/network/http_auth_cache_copier.cc
+++ /dev/null
@@ -1,42 +0,0 @@
-// Copyright 2018 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "services/network/http_auth_cache_copier.h"
-
-#include "base/logging.h"
-#include "net/http/http_auth_cache.h"
-
-namespace network {
-
-HttpAuthCacheCopier::HttpAuthCacheCopier() = default;
-HttpAuthCacheCopier::~HttpAuthCacheCopier() = default;
-
-base::UnguessableToken HttpAuthCacheCopier::SaveHttpAuthCache(
-    const net::HttpAuthCache& cache) {
-  base::UnguessableToken key = base::UnguessableToken::Create();
-  auto cache_it = caches_.emplace(
-      key, std::make_unique<net::HttpAuthCache>(
-               cache.key_server_entries_by_network_anonymization_key()));
-  DCHECK(cache_it.second);
-  cache_it.first->second->CopyProxyEntriesFrom(cache);
-  return key;
-}
-
-void HttpAuthCacheCopier::LoadHttpAuthCache(const base::UnguessableToken& key,
-                                            net::HttpAuthCache* cache) {
-  auto it = caches_.find(key);
-  if (it == caches_.end()) {
-    DLOG(ERROR) << "Unknown HttpAuthCache key: " << key;
-    return;
-  }
-
-  // Source and destination caches must have the same configuration.
-  DCHECK_EQ(cache->key_server_entries_by_network_anonymization_key(),
-            it->second->key_server_entries_by_network_anonymization_key());
-
-  cache->CopyProxyEntriesFrom(*it->second);
-  caches_.erase(it);
-}
-
-}  // namespace network
diff --git a/services/network/http_auth_cache_proxy_copier.cc b/services/network/http_auth_cache_proxy_copier.cc
new file mode 100644
index 0000000..62a07c9
--- /dev/null
+++ b/services/network/http_auth_cache_proxy_copier.cc
@@ -0,0 +1,41 @@
+// Copyright 2018 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "services/network/http_auth_cache_proxy_copier.h"
+
+#include "base/logging.h"
+#include "net/http/http_auth_cache.h"
+
+namespace network {
+
+HttpAuthCacheProxyCopier::HttpAuthCacheProxyCopier() = default;
+HttpAuthCacheProxyCopier::~HttpAuthCacheProxyCopier() = default;
+
+base::UnguessableToken HttpAuthCacheProxyCopier::SaveHttpAuthCache(
+    const net::HttpAuthCache& cache) {
+  base::UnguessableToken key = base::UnguessableToken::Create();
+  auto cache_it = caches_.emplace(
+      key, std::make_unique<net::HttpAuthCache>(
+               // It doesn't matter what value we pass here because these
+               // caches are only used for copying proxy entries (not
+               // server entries).
+               /*key_server_entries_by_network_anonymization_key=*/true));
+  DCHECK(cache_it.second);
+  cache_it.first->second->CopyProxyEntriesFrom(cache);
+  return key;
+}
+
+void HttpAuthCacheProxyCopier::LoadHttpAuthCache(
+    const base::UnguessableToken& key,
+    net::HttpAuthCache* cache) {
+  auto it = caches_.find(key);
+  if (it == caches_.end()) {
+    DLOG(ERROR) << "Unknown HttpAuthCache key: " << key;
+    return;
+  }
+  cache->CopyProxyEntriesFrom(*it->second);
+  caches_.erase(it);
+}
+
+}  // namespace network
diff --git a/services/network/http_auth_cache_copier.h b/services/network/http_auth_cache_proxy_copier.h
similarity index 74%
rename from services/network/http_auth_cache_copier.h
rename to services/network/http_auth_cache_proxy_copier.h
index 885169fc..31aef198 100644
--- a/services/network/http_auth_cache_copier.h
+++ b/services/network/http_auth_cache_proxy_copier.h
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef SERVICES_NETWORK_HTTP_AUTH_CACHE_COPIER_H_
-#define SERVICES_NETWORK_HTTP_AUTH_CACHE_COPIER_H_
+#ifndef SERVICES_NETWORK_HTTP_AUTH_CACHE_PROXY_COPIER_H_
+#define SERVICES_NETWORK_HTTP_AUTH_CACHE_PROXY_COPIER_H_
 
 #include <map>
 #include <memory>
@@ -20,14 +20,14 @@
 // an intermediate cache. This allows copying between two HttpAuthCache
 // instances that cannot both be accessed at the same time, such as the
 // HttpAuthCaches in two NetworkContexts.
-class HttpAuthCacheCopier {
+class HttpAuthCacheProxyCopier {
  public:
-  HttpAuthCacheCopier();
+  HttpAuthCacheProxyCopier();
 
-  HttpAuthCacheCopier(const HttpAuthCacheCopier&) = delete;
-  HttpAuthCacheCopier& operator=(const HttpAuthCacheCopier&) = delete;
+  HttpAuthCacheProxyCopier(const HttpAuthCacheProxyCopier&) = delete;
+  HttpAuthCacheProxyCopier& operator=(const HttpAuthCacheProxyCopier&) = delete;
 
-  ~HttpAuthCacheCopier();
+  ~HttpAuthCacheProxyCopier();
 
   // Saves the proxy entries of the given HttpAuthCache in an intermediate
   // HttpAuthCache and returns a key that can be used to load the saved contents
@@ -46,4 +46,4 @@
 
 }  // namespace network
 
-#endif  // SERVICES_NETWORK_HTTP_AUTH_CACHE_COPIER_H_
+#endif  // SERVICES_NETWORK_HTTP_AUTH_CACHE_PROXY_COPIER_H_
diff --git a/services/network/network_context.cc b/services/network/network_context.cc
index 26019db9..88417e9 100644
--- a/services/network/network_context.cc
+++ b/services/network/network_context.cc
@@ -111,7 +111,7 @@
 #include "services/network/devtools_durable_msg_collector.h"
 #include "services/network/disk_cache/mojo_backend_file_operations_factory.h"
 #include "services/network/host_resolver.h"
-#include "services/network/http_auth_cache_copier.h"
+#include "services/network/http_auth_cache_proxy_copier.h"
 #include "services/network/http_server_properties_pref_delegate.h"
 #include "services/network/ignore_errors_cert_verifier.h"
 #include "services/network/is_browser_initiated.h"
@@ -2472,7 +2472,7 @@
           ->GetSession()
           ->http_auth_cache();
   base::UnguessableToken cache_key =
-      network_service_->http_auth_cache_copier()->SaveHttpAuthCache(
+      network_service_->http_auth_cache_proxy_copier()->SaveHttpAuthCache(
           *http_auth_cache);
   std::move(callback).Run(cache_key);
 }
@@ -2484,7 +2484,7 @@
       url_request_context_->http_transaction_factory()
           ->GetSession()
           ->http_auth_cache();
-  network_service_->http_auth_cache_copier()->LoadHttpAuthCache(
+  network_service_->http_auth_cache_proxy_copier()->LoadHttpAuthCache(
       cache_key, http_auth_cache);
   std::move(callback).Run();
 }
diff --git a/services/network/network_service.cc b/services/network/network_service.cc
index 9463331..c44a9801 100644
--- a/services/network/network_service.cc
+++ b/services/network/network_service.cc
@@ -84,7 +84,7 @@
 #include "net/url_request/url_request_context.h"
 #include "services/network/dns_config_change_manager.h"
 #include "services/network/first_party_sets/first_party_sets_manager.h"
-#include "services/network/http_auth_cache_copier.h"
+#include "services/network/http_auth_cache_proxy_copier.h"
 #include "services/network/net_log_exporter.h"
 #include "services/network/net_log_proxy_sink.h"
 #include "services/network/network_context.h"
@@ -484,7 +484,7 @@
       net::NetworkChangeNotifier::GetSystemDnsConfigNotifier(), net_log_);
   host_resolver_factory_ = std::make_unique<net::HostResolver::Factory>();
 
-  http_auth_cache_copier_ = std::make_unique<HttpAuthCacheCopier>();
+  http_auth_cache_proxy_copier_ = std::make_unique<HttpAuthCacheProxyCopier>();
 
   doh_probe_activator_ = std::make_unique<DelayedDohProbeActivator>(this);
 
diff --git a/services/network/network_service.h b/services/network/network_service.h
index 5646097..769a2e8 100644
--- a/services/network/network_service.h
+++ b/services/network/network_service.h
@@ -81,7 +81,7 @@
 namespace network {
 
 class DnsConfigChangeManager;
-class HttpAuthCacheCopier;
+class HttpAuthCacheProxyCopier;
 class NetLogProxySink;
 class NetworkContext;
 class NetworkService;
@@ -313,8 +313,8 @@
   net::HostResolver::Factory* host_resolver_factory() {
     return host_resolver_factory_.get();
   }
-  HttpAuthCacheCopier* http_auth_cache_copier() {
-    return http_auth_cache_copier_.get();
+  HttpAuthCacheProxyCopier* http_auth_cache_proxy_copier() {
+    return http_auth_cache_proxy_copier_.get();
   }
 
   FirstPartySetsManager* first_party_sets_manager() const {
@@ -472,7 +472,7 @@
 
   std::unique_ptr<net::HostResolverManager> host_resolver_manager_;
   std::unique_ptr<net::HostResolver::Factory> host_resolver_factory_;
-  std::unique_ptr<HttpAuthCacheCopier> http_auth_cache_copier_;
+  std::unique_ptr<HttpAuthCacheProxyCopier> http_auth_cache_proxy_copier_;
 
   // Members that would store the http auth network_service related params.
   // These Params are later used by NetworkContext to create
diff --git a/services/viz/privileged/mojom/compositing/display_private.mojom b/services/viz/privileged/mojom/compositing/display_private.mojom
index e063835e..78a96bff 100644
--- a/services/viz/privileged/mojom/compositing/display_private.mojom
+++ b/services/viz/privileged/mojom/compositing/display_private.mojom
@@ -20,6 +20,7 @@
 import "services/viz/privileged/mojom/compositing/vsync_parameter_observer.mojom";
 
 // The DisplayPrivate is used by privileged clients to talk to Display.
+[DirectReceiver]
 interface DisplayPrivate {
   SetDisplayVisible(bool visible);
 
diff --git a/services/viz/privileged/mojom/compositing/external_begin_frame_controller.mojom b/services/viz/privileged/mojom/compositing/external_begin_frame_controller.mojom
index 904f262a..75a64840 100644
--- a/services/viz/privileged/mojom/compositing/external_begin_frame_controller.mojom
+++ b/services/viz/privileged/mojom/compositing/external_begin_frame_controller.mojom
@@ -8,6 +8,7 @@
 import "services/viz/public/mojom/compositing/begin_frame_args.mojom";
 
 // Exposes a way to manually issue BeginFrames to a Display.
+[DirectReceiver]
 interface ExternalBeginFrameController {
   // Request a frame. The callback is invoked when frame has either been
   // produced or can not be produced at this time.
diff --git a/services/viz/privileged/mojom/compositing/frame_sink_manager.mojom b/services/viz/privileged/mojom/compositing/frame_sink_manager.mojom
index 4f1e9c6..5c5381c 100644
--- a/services/viz/privileged/mojom/compositing/frame_sink_manager.mojom
+++ b/services/viz/privileged/mojom/compositing/frame_sink_manager.mojom
@@ -75,6 +75,7 @@
 // Thus, a client will typically simply request a CompositorFrameSink from the
 // frame sink manager host which will forward the request to the frame sink
 // manager.
+[DirectReceiver]
 interface FrameSinkManager {
   // Starts the scope of temporary references tied to this |frame_sink_id|.
   // Temporary references tied to this |frame_sink_id| will be dropped on
@@ -282,6 +283,7 @@
 // RenderInputRouterDelegate[Client] interface to use the same mojo pipe to
 // send/receive information for input handling between the CrBrowserMain and the
 // VizCompositorThread.
+[DirectReceiver]
 interface RendererInputRouterDelegateRegistry {
   // Setup the connection between the Browser (at RenderWidgetHost level) and
   // the VizCompositor thread (at InputManager level) in both directions to
diff --git a/services/viz/privileged/mojom/compositing/frame_sink_manager_test_api.mojom b/services/viz/privileged/mojom/compositing/frame_sink_manager_test_api.mojom
index b3fe71e..a3b5fc5c 100644
--- a/services/viz/privileged/mojom/compositing/frame_sink_manager_test_api.mojom
+++ b/services/viz/privileged/mojom/compositing/frame_sink_manager_test_api.mojom
@@ -10,6 +10,7 @@
 // Note: These mojo definitions are intended for exclusive use in testing.
 
 // FrameSinkManager's test api.
+[DirectReceiver]
 interface FrameSinkManagerTestApi {
   // Returns true if there are any unclaimed ViewTransition resources.
   [Sync]
diff --git a/services/viz/privileged/mojom/compositing/frame_sink_video_capture.mojom b/services/viz/privileged/mojom/compositing/frame_sink_video_capture.mojom
index 3e9144c3..00277d0 100644
--- a/services/viz/privileged/mojom/compositing/frame_sink_video_capture.mojom
+++ b/services/viz/privileged/mojom/compositing/frame_sink_video_capture.mojom
@@ -114,6 +114,7 @@
 // should suspend, Stop() is called. After that point, Start() can be called
 // again to resume capture; or the mojo binding can be dropped to auto-terminate
 // the capturer.
+[DirectReceiver]
 interface FrameSinkVideoCapturer {
   // Specifies the pixel format to use. Please note when format is
   // PIXEL_FORMAT_ARGB, the frame might be BGRA or RGBA depends on platform
@@ -233,6 +234,7 @@
 // Control interface for a small image to be composited on top of each captured
 // video frame. This allows clients to, for example, have the capturer render
 // mouse cursors or debug info boxes on top of the captured content.
+[DirectReceiver]
 interface FrameSinkVideoCaptureOverlay {
   // Sets/Changes the overlay |image| and its position and size, relative to the
   // source content. |bounds| consists of coordinates where the range [0.0,1.0)
diff --git a/services/viz/privileged/mojom/compositing/frame_sinks_metrics_recorder.mojom b/services/viz/privileged/mojom/compositing/frame_sinks_metrics_recorder.mojom
index 4df5405..743b851 100644
--- a/services/viz/privileged/mojom/compositing/frame_sinks_metrics_recorder.mojom
+++ b/services/viz/privileged/mojom/compositing/frame_sinks_metrics_recorder.mojom
@@ -37,6 +37,7 @@
 };
 
 // The interface allows clients to collect metrics related to frame sinks.
+[DirectReceiver]
 interface FrameSinksMetricsRecorder {
   // Starts frame counting per frame sink on the viz.
   StartFrameCounting(mojo_base.mojom.TimeTicks start_time,
diff --git a/services/viz/public/mojom/compositing/compositor_frame_sink.mojom b/services/viz/public/mojom/compositing/compositor_frame_sink.mojom
index b0f7a70..e28e143 100644
--- a/services/viz/public/mojom/compositing/compositor_frame_sink.mojom
+++ b/services/viz/public/mojom/compositing/compositor_frame_sink.mojom
@@ -39,6 +39,7 @@
 // Each time a client has a graphical update, and receives an OnBeginFrame, it
 // is responsible for creating a CompositorFrame to update its portion of the
 // screen.
+[DirectReceiver]
 interface CompositorFrameSink {
   // Sets parameters for the CompositorFrameSink.
   SetParams(CompositorFrameSinkParams params);
@@ -96,6 +97,7 @@
   SetThreads(array<Thread> threads);
 };
 
+[DirectReceiver]
 interface CompositorFrameSinkClient {
   // Notification that the previous CompositorFrame given to
   // SubmitCompositorFrame() has been processed and that another frame
diff --git a/services/viz/public/mojom/compositing/frame_sink_bundle.mojom b/services/viz/public/mojom/compositing/frame_sink_bundle.mojom
index 3072de0a..dee2710 100644
--- a/services/viz/public/mojom/compositing/frame_sink_bundle.mojom
+++ b/services/viz/public/mojom/compositing/frame_sink_bundle.mojom
@@ -46,6 +46,7 @@
 
 // Controls an endpoint for aggregate communication regarding a collection of
 // related frame sinks.
+[DirectReceiver]
 interface FrameSinkBundle {
   // Corresponds to a single message of the same name on CompositorFrameSink
   // for the identified sink.
diff --git a/services/viz/public/mojom/compositing/layer_context.mojom b/services/viz/public/mojom/compositing/layer_context.mojom
index 9853d33..5650401b 100644
--- a/services/viz/public/mojom/compositing/layer_context.mojom
+++ b/services/viz/public/mojom/compositing/layer_context.mojom
@@ -205,6 +205,7 @@
 
 // Drives updates to a GPU-side LayerTreeHostImpl from its corresponding
 // client-side (e.g. renderer- or browser-side) LayerTreeHost.
+[DirectReceiver]
 interface LayerContext {
   // Globally controls whether the tree contents are visible.
   SetVisible(bool visible);
diff --git a/testing/variations/fieldtrial_testing_config.json b/testing/variations/fieldtrial_testing_config.json
index c0688a0..c74a766e 100644
--- a/testing/variations/fieldtrial_testing_config.json
+++ b/testing/variations/fieldtrial_testing_config.json
@@ -630,7 +630,20 @@
             ],
             "experiments": [
                 {
-                    "name": "Enabled",
+                    "name": "Enabled_DefaultDevicePref_True",
+                    "params": {
+                        "show_bookmark_bar": "true"
+                    },
+                    "enable_features": [
+                        "AndroidAppearanceSettings",
+                        "AndroidBookmarkBar"
+                    ]
+                },
+                {
+                    "name": "Enabled_DefaultDevicePref_False",
+                    "params": {
+                        "show_bookmark_bar": "false"
+                    },
                     "enable_features": [
                         "AndroidAppearanceSettings",
                         "AndroidBookmarkBar"
@@ -11360,6 +11373,23 @@
             ]
         }
     ],
+    "GlicAppendModelQualityClientId": [
+        {
+            "platforms": [
+                "mac",
+                "windows",
+                "linux"
+            ],
+            "experiments": [
+                {
+                    "name": "Enabled",
+                    "enable_features": [
+                        "GlicAppendModelQualityClientId"
+                    ]
+                }
+            ]
+        }
+    ],
     "GlicClientResponsivenessCheckExtension": [
         {
             "platforms": [
@@ -11586,6 +11616,22 @@
             ]
         }
     ],
+    "GlicWarming": [
+        {
+            "platforms": [
+                "mac",
+                "windows"
+            ],
+            "experiments": [
+                {
+                    "name": "Enabled",
+                    "enable_features": [
+                        "GlicWarming"
+                    ]
+                }
+            ]
+        }
+    ],
     "GlicZeroStateSuggestionsDogfood": [
         {
             "platforms": [
@@ -16627,9 +16673,11 @@
                     "enable_features": [
                         "AiModeOmniboxEntryPoint",
                         "AllowAiModeMatches",
+                        "LoadingSuggestionsAnimation",
                         "OmniboxHideAimEntrypointOnUserInput",
                         "OmniboxToolbelt",
-                        "OpenLensActionUITweaks"
+                        "OpenLensActionUITweaks",
+                        "SuggestionsFulfilledByLensSupported"
                     ]
                 }
             ]
diff --git a/third_party/blink/common/features.cc b/third_party/blink/common/features.cc
index 6b3808b..06d1baf 100644
--- a/third_party/blink/common/features.cc
+++ b/third_party/blink/common/features.cc
@@ -52,6 +52,11 @@
 // Avoids copying ResourceRequest::TrustedParams when possible.
 BASE_FEATURE(AvoidTrustedParamsCopies, base::FEATURE_DISABLED_BY_DEFAULT);
 
+// Async touchmoves after scroll.
+BASE_FEATURE(kAsyncTouchMovesImmediatelyAfterScroll,
+             "AsyncTouchMovesImmediatelyAfterScroll",
+             base::FEATURE_DISABLED_BY_DEFAULT);
+
 // Block all MIDI access with the MIDI_SYSEX permission
 BASE_FEATURE(BlockMidiByDefault, base::FEATURE_ENABLED_BY_DEFAULT);
 
diff --git a/third_party/blink/public/common/features.h b/third_party/blink/public/common/features.h
index d2552a60..ca7cafb 100644
--- a/third_party/blink/public/common/features.h
+++ b/third_party/blink/public/common/features.h
@@ -42,6 +42,12 @@
 // Avoids copying ResourceRequest::TrustedParams when possible.
 BLINK_COMMON_EXPORT BASE_DECLARE_FEATURE(kAvoidTrustedParamsCopies);
 
+// Sends touch moves async once the scroll has already started. This means the
+// generation of GestureScrollUpdate is not blocked on touch moves being handled
+// by RendererMain.
+BLINK_COMMON_EXPORT BASE_DECLARE_FEATURE(
+    kAsyncTouchMovesImmediatelyAfterScroll);
+
 BLINK_COMMON_EXPORT BASE_DECLARE_FEATURE(kLowerHighResolutionTimerThreshold);
 
 // Allows running DevTools main thread debugger even when a renderer process
diff --git a/third_party/blink/public/mojom/content_extraction/ai_page_content.mojom b/third_party/blink/public/mojom/content_extraction/ai_page_content.mojom
index 6d14d373..bda0e7aa 100644
--- a/third_party/blink/public/mojom/content_extraction/ai_page_content.mojom
+++ b/third_party/blink/public/mojom/content_extraction/ai_page_content.mojom
@@ -309,6 +309,25 @@
   array<ScriptTool> script_tools;
 };
 
+// Provides metadata for local redacted frames.
+struct RedactedFrameMetadata {
+  enum Reason {
+    // This iframe was redacted because it is not on the same eTLD+1 site as
+    // the top-level frame.
+    kCrossSite,
+    // This iframe was redacted because it is not on the same origin as the
+    // top-level frame.
+    kCrossOrigin,
+  };
+  // Reason this iframe was redacted.
+  Reason reason;
+};
+
+union AIPageContentIframeContent {
+  AIPageContentFrameData local_frame_data;
+  RedactedFrameMetadata redacted_frame_metadata;
+};
+
 // Provides metadata for frames (local and remote) accessible from the
 // LocalFrameRoot of the corresponding AIPageContentAgent.
 struct AIPageContentIframeData {
@@ -321,7 +340,7 @@
   // Data common to all local frames (main and embedded). If this is a remote
   // frame, this metadata will be part of the AIPageContent pulled from the
   // local frame's renderer process.
-  AIPageContentFrameData? local_frame_data;
+  AIPageContentIframeContent? content;
 };
 
 struct AIPageContentTableData {
@@ -501,6 +520,10 @@
   // Note: This option is not folded into AIPageContentMode since this
   // information is not added to the proto.
   int32 max_meta_elements = 0;
+
+  // If enabled, only include frames that are same-site with the top level
+  // frame's origin. This implies same-site cross-origin frames are included.
+  bool include_same_site_only = false;
 };
 
 // Used to obtain the AIPageContent representation for Documents. Lives in the
diff --git a/third_party/blink/public/mojom/input/input_handler.mojom b/third_party/blink/public/mojom/input/input_handler.mojom
index fe01b2f..2b454e71 100644
--- a/third_party/blink/public/mojom/input/input_handler.mojom
+++ b/third_party/blink/public/mojom/input/input_handler.mojom
@@ -296,6 +296,7 @@
 // interface. If frame input actions are dispatched the WidgetInputHandler
 // should be fetched via the associated interface request so that input calls
 // remain in order. See https://goo.gl/x4ee8A for more details.
+[DirectReceiver]
 interface FrameWidgetInputHandler {
   // Adds text decorations between a given valid start and end offsets in the
   // currently focused editable field.
@@ -450,6 +451,7 @@
 // an input interface for an associated Widget object. See
 // FrameWidgetInputHandler
 // for an interface at the frame level.
+[DirectReceiver]
 interface WidgetInputHandler {
   // Tells widget focus has been changed.
   SetFocus(FocusState state);
diff --git a/third_party/blink/public/mojom/input/synchronous_compositor.mojom b/third_party/blink/public/mojom/input/synchronous_compositor.mojom
index e2d0f94c..d55340d 100644
--- a/third_party/blink/public/mojom/input/synchronous_compositor.mojom
+++ b/third_party/blink/public/mojom/input/synchronous_compositor.mojom
@@ -47,6 +47,7 @@
 // java UI is drawn in lock step with content renderer by the webview.
 // The SynchronousCompositor is an associated interface with WidgetInputHandler
 // because input must be delivered in order with the compositing events.
+[DirectReceiver]
 interface SynchronousCompositor {
   // Hardware draw asynchronously, ReturnFrame will return the result on
   // the associated SynchronousCompositorControlHost.
diff --git a/third_party/blink/renderer/bindings/core/v8/to_v8_traits.h b/third_party/blink/renderer/bindings/core/v8/to_v8_traits.h
index 4453bf15..e51a369 100644
--- a/third_party/blink/renderer/bindings/core/v8/to_v8_traits.h
+++ b/third_party/blink/renderer/bindings/core/v8/to_v8_traits.h
@@ -494,7 +494,7 @@
 struct ToV8Traits<IDLArray<T>> {
   [[nodiscard]] static v8::Local<v8::Value> ToV8(ScriptState* script_state,
                                                  const FrozenArray<T>& value) {
-    return value.ToV8(script_state);
+    return const_cast<FrozenArray<T>&>(value).ToV8(script_state);
   }
 
   // TODO(yukishiino): Remove this overload as IDL FrozenArray should be
diff --git a/third_party/blink/renderer/core/input/gesture_manager.cc b/third_party/blink/renderer/core/input/gesture_manager.cc
index ac42038..234ffed 100644
--- a/third_party/blink/renderer/core/input/gesture_manager.cc
+++ b/third_party/blink/renderer/core/input/gesture_manager.cc
@@ -143,6 +143,13 @@
     }
   }
 
+  // Long presses are the only gesture that could happen with an ongoing drag.
+  // We clear the flag on any other gesture in case the gesture manager didn't
+  // receive a drag end event for any reason.
+  if (gesture_event.GetType() != WebInputEvent::Type::kGestureLongPress) {
+    drag_in_progress_ = false;
+  }
+
   switch (gesture_event.GetType()) {
     case WebInputEvent::Type::kGestureTapDown:
       return HandleGestureTapDown(targeted_event);
@@ -408,7 +415,6 @@
 
 WebInputEventResult GestureManager::HandleGestureShortPress(
     const GestureEventWithHitTestResults& targeted_event) {
-  drag_in_progress_ = false;
   if (frame_->GetSettings() &&
       frame_->GetSettings()->GetTouchDragDropEnabled() &&
       RuntimeEnabledFeatures::TouchDragOnShortPressEnabled() &&
@@ -435,8 +441,9 @@
 
   gesture_context_menu_deferred_ = false;
 
-  if (RuntimeEnabledFeatures::TouchDragOnShortPressEnabled()) {
-    if (drag_in_progress_ && DragEndOpensContextMenu()) {
+  if (RuntimeEnabledFeatures::TouchDragOnShortPressEnabled() &&
+      drag_in_progress_) {
+    if (DragEndOpensContextMenu()) {
       gesture_context_menu_deferred_ = true;
       return WebInputEventResult::kNotHandled;
     }
diff --git a/third_party/blink/renderer/core/input/mouse_event_manager.cc b/third_party/blink/renderer/core/input/mouse_event_manager.cc
index eafaf122..64c3818 100644
--- a/third_party/blink/renderer/core/input/mouse_event_manager.cc
+++ b/third_party/blink/renderer/core/input/mouse_event_manager.cc
@@ -733,6 +733,10 @@
   ResetDragSource();
   mouse_down_pos_ = frame_->View()->ConvertFromRootFrame(
       gfx::ToFlooredPoint(mouse_drag_event.PositionInRootFrame()));
+  // TODO(crbug.com/435174491): HandleDrag returns whether or not the
+  // application handled the drag attempt, not whether or not a drag started.
+  // `GestureManager` assumes that if this function returns `true` a drag has
+  // started.
   return HandleDrag(mev, gesture_event.primary_pointer_type ==
                                  blink::WebPointerProperties::PointerType::kPen
                              ? DragAndDropToolType::kStylusViaGesture
@@ -846,7 +850,7 @@
   return selection_controller_drag_result;
 }
 
-// TODO(mustaq@chromium.org): The return value here is questionable.  Why even a
+// TODO(crbug.com/435174491): The return value here is questionable.  Why even a
 // failing `TryStartDrag()` below returns a `true` here?
 bool MouseEventManager::HandleDrag(const MouseEventWithHitTestResults& event,
                                    DragAndDropToolType initiator) {
diff --git a/third_party/blink/renderer/core/loader/link_loader_test.cc b/third_party/blink/renderer/core/loader/link_loader_test.cc
index f806c61..0150215 100644
--- a/third_party/blink/renderer/core/loader/link_loader_test.cc
+++ b/third_party/blink/renderer/core/loader/link_loader_test.cc
@@ -510,7 +510,7 @@
     EXPECT_EQ(kNotParserInserted, request.Options().ParserState());
     EXPECT_EQ(params_->expected_credentials_mode,
               request.Options().CredentialsMode());
-    EXPECT_EQ(Referrer::NoReferrer(), request.ReferrerString());
+    EXPECT_EQ(Referrer::ClientReferrerString(), request.ReferrerString());
     EXPECT_EQ(params_->referrer_policy, request.Options().GetReferrerPolicy());
     EXPECT_EQ(params_->integrity,
               request.Options().GetIntegrityAttributeValue());
diff --git a/third_party/blink/renderer/core/loader/preload_helper.cc b/third_party/blink/renderer/core/loader/preload_helper.cc
index 0f7f2568..f0c93d6 100644
--- a/third_party/blink/renderer/core/loader/preload_helper.cc
+++ b/third_party/blink/renderer/core/loader/preload_helper.cc
@@ -62,6 +62,7 @@
 #include "third_party/blink/renderer/platform/loader/link_header.h"
 #include "third_party/blink/renderer/platform/loader/subresource_integrity.h"
 #include "third_party/blink/renderer/platform/network/mime/mime_type_registry.h"
+#include "third_party/blink/renderer/platform/runtime_enabled_features.h"
 #include "third_party/blink/renderer/platform/weborigin/security_origin.h"
 
 namespace blink {
@@ -699,8 +700,10 @@
                          params.referrer_policy,
                          mojom::blink::FetchPriorityHint::kAuto,
                          RenderBlockingBehavior::kNonBlocking),
-      Referrer::NoReferrer(), TextPosition::MinimumPosition(),
-      ModuleImportPhase::kEvaluation);
+      RuntimeEnabledFeatures::ModulePreloadReferrerEnabled()
+          ? Referrer::ClientReferrerString()
+          : Referrer::NoReferrer(),
+      TextPosition::MinimumPosition(), ModuleImportPhase::kEvaluation);
 
   // Step 13. "Fetch a modulepreload module script graph given url, destination,
   // settings object, and options. Wait until the algorithm asynchronously
diff --git a/third_party/blink/renderer/core/testing/data/core_test_bundle_data.filelist b/third_party/blink/renderer/core/testing/data/core_test_bundle_data.filelist
index 392c534c..634697ce 100644
--- a/third_party/blink/renderer/core/testing/data/core_test_bundle_data.filelist
+++ b/third_party/blink/renderer/core/testing/data/core_test_bundle_data.filelist
@@ -133,6 +133,7 @@
 testing/data/fragment_middle_click.html
 testing/data/frame-a-b-c.html
 testing/data/frame.html
+testing/data/frame_another.html
 testing/data/frame_owner_properties.html
 testing/data/frame_timing_1.html
 testing/data/frame_timing_2.html
@@ -311,8 +312,8 @@
 testing/data/no_scale_for_you.html
 testing/data/no_viewport_tag.html
 testing/data/nodeimage.html
-testing/data/non_UTF8.wasm
 testing/data/non-scrollable.html
+testing/data/non_UTF8.wasm
 testing/data/non_user_input_text_update.html
 testing/data/not_an_image.ico
 testing/data/notifications/100x100.png
diff --git a/third_party/blink/renderer/core/testing/data/frame_another.html b/third_party/blink/renderer/core/testing/data/frame_another.html
new file mode 100644
index 0000000..ee59b611
--- /dev/null
+++ b/third_party/blink/renderer/core/testing/data/frame_another.html
@@ -0,0 +1 @@
+I am another iframe
diff --git a/third_party/blink/renderer/modules/content_extraction/ai_page_content_agent.cc b/third_party/blink/renderer/modules/content_extraction/ai_page_content_agent.cc
index 19761ea..a81fc168 100644
--- a/third_party/blink/renderer/modules/content_extraction/ai_page_content_agent.cc
+++ b/third_party/blink/renderer/modules/content_extraction/ai_page_content_agent.cc
@@ -9,6 +9,7 @@
 #include "base/trace_event/trace_id_helper.h"
 #include "services/metrics/public/cpp/ukm_builders.h"
 #include "third_party/blink/public/mojom/content_extraction/ai_page_content.mojom-blink.h"
+#include "third_party/blink/public/mojom/content_extraction/ai_page_content.mojom-forward.h"
 #include "third_party/blink/renderer/core/accessibility/ax_object_cache.h"
 #include "third_party/blink/renderer/core/css/properties/longhands.h"
 #include "third_party/blink/renderer/core/display_lock/display_lock_document_state.h"
@@ -54,6 +55,7 @@
 #include "third_party/blink/renderer/modules/content_extraction/ai_page_content_debug_utils.h"
 #include "third_party/blink/renderer/platform/runtime_enabled_features.h"
 #include "third_party/blink/renderer/platform/scheduler/public/thread_scheduler.h"
+#include "third_party/blink/renderer/platform/weborigin/security_origin.h"
 #include "ui/accessibility/ax_role_properties.h"
 #include "ui/gfx/geometry/point_conversions.h"
 #include "ui/gfx/geometry/rect_conversions.h"
@@ -1047,18 +1049,37 @@
   content_node.content_attributes->iframe_data = std::move(iframe_data);
 
   auto* local_frame = DynamicTo<LocalFrame>(frame);
+  if (!local_frame) {
+    return;
+  }
+
+  if (options_->include_same_site_only && !frame.IsOutermostMainFrame()) {
+    const SecurityOrigin* frame_origin =
+        local_frame->GetSecurityContext()->GetSecurityOrigin();
+    const SecurityOrigin* main_frame_origin =
+        local_frame->Top()->GetSecurityContext()->GetSecurityOrigin();
+    CHECK(frame_origin);
+    CHECK(main_frame_origin);
+    if (!frame_origin->IsSameSiteWith(main_frame_origin)) {
+      content_node.content_attributes->iframe_data->content =
+          mojom::blink::AIPageContentIframeContent::NewRedactedFrameMetadata(
+              mojom::blink::RedactedFrameMetadata::New(
+                  mojom::blink::RedactedFrameMetadata::Reason::kCrossSite));
+      return;
+    }
+  }
 
   // Add interaction metadata before walking the tree to ensure we promote
   // interactive DOM nodes to ContentNodes.
-  if (local_frame && local_frame->GetDocument()) {
+  if (local_frame->GetDocument()) {
     auto frame_data = mojom::blink::AIPageContentFrameData::New();
     AddFrameData(*local_frame, *frame_data);
-    content_node.content_attributes->iframe_data->local_frame_data =
-        std::move(frame_data);
+    content_node.content_attributes->iframe_data->content =
+        mojom::blink::AIPageContentIframeContent::NewLocalFrameData(
+            std::move(frame_data));
   }
 
-  auto* child_layout_view =
-      local_frame ? local_frame->ContentLayoutObject() : nullptr;
+  auto* child_layout_view = local_frame->ContentLayoutObject();
   if (child_layout_view) {
     RecursionData child_recursion_data(*child_layout_view->Style());
     // The aria attribute values don't pierce frame boundaries.
@@ -1071,9 +1092,9 @@
         MaybeGenerateContentNode(*child_layout_view, child_recursion_data);
     CHECK(child_content_node);
 
-    // We could consider removing an iframe with no visible content. But this is
-    // likely not common and should be done in the browser so it's consistently
-    // done for local and remote frames.
+    // We could consider removing an iframe with no visible content. But this
+    // is likely not common and should be done in the browser so it's
+    // consistently done for local and remote frames.
     WalkChildren(*child_layout_view, *child_content_node, child_recursion_data);
     content_node.children_nodes.emplace_back(std::move(child_content_node));
   }
diff --git a/third_party/blink/renderer/modules/content_extraction/ai_page_content_agent_unittest.cc b/third_party/blink/renderer/modules/content_extraction/ai_page_content_agent_unittest.cc
index 4570e5f..be5beb7b 100644
--- a/third_party/blink/renderer/modules/content_extraction/ai_page_content_agent_unittest.cc
+++ b/third_party/blink/renderer/modules/content_extraction/ai_page_content_agent_unittest.cc
@@ -10,6 +10,7 @@
 #include "mojo/public/cpp/test_support/test_utils.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/blink/public/common/input/web_mouse_event.h"
+#include "third_party/blink/public/mojom/content_extraction/ai_page_content.mojom-data-view.h"
 #include "third_party/blink/renderer/core/accessibility/ax_context.h"
 #include "third_party/blink/renderer/core/accessibility/ax_object_cache.h"
 #include "third_party/blink/renderer/core/dom/document.h"
@@ -62,6 +63,10 @@
     ASSERT_TRUE(helper_.LocalMainFrame());
   }
 
+  void TearDown() override {
+    url_test_helpers::UnregisterAllURLsAndClearMemoryCache();
+  }
+
   void CheckListItemWithText(const mojom::blink::AIPageContentNode& node,
                              const String& expected_text) {
     const auto& attributes = *node.content_attributes;
@@ -641,6 +646,128 @@
   CheckTextNode(*iframe_root.children_nodes[0], "inside iframe");
 }
 
+TEST_F(AIPageContentAgentTest, CrossSiteIframeIncluded) {
+  KURL main_url = url_test_helpers::ToKURL("http://example.com/main.html");
+  KURL cross_origin_url =
+      url_test_helpers::ToKURL("http://www.example.com/frame.html");
+  KURL cross_site_url =
+      url_test_helpers::ToKURL("http://altostrat.com/frame_another.html");
+
+  // Mock the cross origin, same-site iframe's content.
+  url_test_helpers::RegisterMockedURLLoadFromBase(
+      WebString::FromUTF8("http://www.example.com/"), test::CoreTestDataPath(),
+      WebString::FromUTF8("frame.html"));
+
+  // Mock the cross-site iframe's content.
+  url_test_helpers::RegisterMockedURLLoadFromBase(
+      WebString::FromUTF8("http://altostrat.com/"), test::CoreTestDataPath(),
+      WebString::FromUTF8("frame_another.html"));
+
+  // Load the main page which contains the same-site iframe and the cross-origin
+  // iframe.
+  frame_test_helpers::LoadHTMLString(
+      helper_.LocalMainFrame(),
+      "<body>"
+      "<iframe src='http://www.example.com/frame.html'></iframe>"
+      "<iframe src='http://altostrat.com/frame_another.html'></iframe>"
+      "</body>",
+      main_url);
+
+  // Let the iframe load.
+  test::RunPendingTasks();
+
+  auto options = GetAIPageContentOptionsForTest();
+
+  options.include_same_site_only = false;
+  GetAIPageContent(options);
+
+  const auto& root = ContentRootNode();
+  ASSERT_EQ(root.children_nodes.size(), 2u);
+
+  // Both nodes should be present.
+  const auto& same_site_iframe_node = *root.children_nodes[0];
+  const auto& cross_site_iframe_node = *root.children_nodes[1];
+  CheckIframeNode(same_site_iframe_node);
+  CheckIframeNode(cross_site_iframe_node);
+
+  // The contents of both nodes should be present as well.
+  ASSERT_EQ(same_site_iframe_node.children_nodes.size(), 1u);
+  ASSERT_EQ(cross_site_iframe_node.children_nodes.size(), 1u);
+
+  const auto& same_site_iframe_root = *same_site_iframe_node.children_nodes[0];
+  const auto& cross_site_iframe_root =
+      *cross_site_iframe_node.children_nodes[0];
+
+  CheckRootNode(same_site_iframe_root);
+  CheckRootNode(cross_site_iframe_root);
+  ASSERT_EQ(same_site_iframe_root.children_nodes.size(), 1u);
+  ASSERT_EQ(cross_site_iframe_root.children_nodes.size(), 1u);
+
+  CheckTextNode(*same_site_iframe_root.children_nodes[0], "I am an iframe\n");
+  CheckTextNode(*cross_site_iframe_root.children_nodes[0],
+                "I am another iframe\n");
+}
+
+TEST_F(AIPageContentAgentTest, CrossSiteIframeExcluded) {
+  KURL main_url = url_test_helpers::ToKURL("http://example.com/main.html");
+  KURL cross_origin_url =
+      url_test_helpers::ToKURL("http://www.example.com/frame.html");
+  KURL cross_site_url =
+      url_test_helpers::ToKURL("http://altostrat.com/frame_another.html");
+
+  // Mock the cross origin, same-site iframe's content.
+  url_test_helpers::RegisterMockedURLLoadFromBase(
+      WebString::FromUTF8("http://www.example.com/"), test::CoreTestDataPath(),
+      WebString::FromUTF8("frame.html"));
+
+  // Mock the cross-site iframe's content.
+  url_test_helpers::RegisterMockedURLLoadFromBase(
+      WebString::FromUTF8("http://altostrat.com/"), test::CoreTestDataPath(),
+      WebString::FromUTF8("frame_another.html"));
+
+  // Load the main page which contains the same-site iframe and the cross-origin
+  // iframe.
+  frame_test_helpers::LoadHTMLString(
+      helper_.LocalMainFrame(),
+      "<body>"
+      "<iframe src='http://www.example.com/frame.html'></iframe>"
+      "<iframe src='http://altostrat.com/frame_another.html'></iframe>"
+      "</body>",
+      main_url);
+
+  // Let the iframe load.
+  test::RunPendingTasks();
+
+  auto options = GetAIPageContentOptionsForTest();
+
+  options.include_same_site_only = true;
+  GetAIPageContent(options);
+
+  const auto& root = ContentRootNode();
+  ASSERT_EQ(root.children_nodes.size(), 2u);
+
+  // Both nodes should be present.
+  const auto& same_site_iframe_node = *root.children_nodes[0];
+  const auto& cross_site_iframe_node = *root.children_nodes[1];
+  CheckIframeNode(same_site_iframe_node);
+  CheckIframeNode(cross_site_iframe_node);
+
+  // Only the contents of the same-site iframe should be present.
+  ASSERT_EQ(same_site_iframe_node.children_nodes.size(), 1u);
+  ASSERT_TRUE(cross_site_iframe_node.children_nodes.empty());
+  ASSERT_EQ(cross_site_iframe_node.content_attributes->iframe_data->content
+                ->get_redacted_frame_metadata()
+                ->reason,
+            blink::mojom::RedactedFrameMetadata_Reason::kCrossSite);
+
+  const auto& same_site_iframe_root = *same_site_iframe_node.children_nodes[0];
+
+  CheckRootNode(same_site_iframe_root);
+  ASSERT_EQ(same_site_iframe_root.children_nodes.size(), 1u);
+
+  CheckTextNode(*same_site_iframe_root.children_nodes[0], "I am an iframe\n");
+}
+
 TEST_F(AIPageContentAgentTest, NoLayoutElement) {
   frame_test_helpers::LoadHTMLString(
       helper_.LocalMainFrame(),
@@ -2334,8 +2461,9 @@
       Content()->frame_data->frame_interaction_info;
   ASSERT_FALSE(frame_interaction_info->selection);
 
+  EXPECT_TRUE(iframe.content_attributes->iframe_data->content);
   const auto& iframe_interaction_info =
-      iframe.content_attributes->iframe_data->local_frame_data
+      iframe.content_attributes->iframe_data->content->get_local_frame_data()
           ->frame_interaction_info;
   ASSERT_TRUE(iframe_interaction_info->selection);
   const auto& selection = *iframe_interaction_info->selection;
@@ -2512,13 +2640,17 @@
             mojom::blink::AIPageContentAttributeType::kIframe);
 
   const auto& iframe_data = *iframe.content_attributes->iframe_data;
-  EXPECT_EQ(iframe_data.local_frame_data->meta_data.size(), 2u);
 
-  EXPECT_EQ(iframe_data.local_frame_data->meta_data[0]->name, "author");
-  EXPECT_EQ(iframe_data.local_frame_data->meta_data[0]->content, "Gary");
+  EXPECT_TRUE(iframe.content_attributes->iframe_data->content);
+  EXPECT_EQ(iframe_data.content->get_local_frame_data()->meta_data.size(), 2u);
+  EXPECT_EQ(iframe_data.content->get_local_frame_data()->meta_data[0]->name,
+            "author");
+  EXPECT_EQ(iframe_data.content->get_local_frame_data()->meta_data[0]->content,
+            "Gary");
 
-  EXPECT_EQ(iframe_data.local_frame_data->meta_data[1]->name, "keywords");
-  EXPECT_EQ(iframe_data.local_frame_data->meta_data[1]->content,
+  EXPECT_EQ(iframe_data.content->get_local_frame_data()->meta_data[1]->name,
+            "keywords");
+  EXPECT_EQ(iframe_data.content->get_local_frame_data()->meta_data[1]->content,
             "HTML, CSS, JavaScript");
 }
 
@@ -2556,10 +2688,13 @@
             mojom::blink::AIPageContentAttributeType::kIframe);
 
   const auto& iframe_data = *iframe.content_attributes->iframe_data;
-  EXPECT_EQ(iframe_data.local_frame_data->meta_data.size(), 1u);
+  EXPECT_TRUE(iframe_data.content);
+  EXPECT_EQ(iframe_data.content->get_local_frame_data()->meta_data.size(), 1u);
 
-  EXPECT_EQ(iframe_data.local_frame_data->meta_data[0]->name, "author");
-  EXPECT_EQ(iframe_data.local_frame_data->meta_data[0]->content, "Gary");
+  EXPECT_EQ(iframe_data.content->get_local_frame_data()->meta_data[0]->name,
+            "author");
+  EXPECT_EQ(iframe_data.content->get_local_frame_data()->meta_data[0]->content,
+            "Gary");
 
   EXPECT_EQ(iframe.children_nodes.size(), 1u);
 
@@ -2572,10 +2707,15 @@
             mojom::blink::AIPageContentAttributeType::kIframe);
 
   const auto& subiframe_data = *subiframe.content_attributes->iframe_data;
-  EXPECT_EQ(subiframe_data.local_frame_data->meta_data.size(), 1u);
+  EXPECT_TRUE(subiframe_data.content);
+  EXPECT_EQ(subiframe_data.content->get_local_frame_data()->meta_data.size(),
+            1u);
 
-  EXPECT_EQ(subiframe_data.local_frame_data->meta_data[0]->name, "author");
-  EXPECT_EQ(subiframe_data.local_frame_data->meta_data[0]->content, "Jordan");
+  EXPECT_EQ(subiframe_data.content->get_local_frame_data()->meta_data[0]->name,
+            "author");
+  EXPECT_EQ(
+      subiframe_data.content->get_local_frame_data()->meta_data[0]->content,
+      "Jordan");
 }
 
 TEST_F(AIPageContentAgentTest, Title) {
@@ -2949,8 +3089,10 @@
   const auto& iframe = nodes[1];
   EXPECT_EQ(iframe->content_attributes->attribute_type,
             mojom::blink::AIPageContentAttributeType::kIframe);
-  EXPECT_TRUE(iframe->content_attributes->iframe_data->local_frame_data
-                  ->contains_paid_content);
+  EXPECT_TRUE(iframe->content_attributes->iframe_data->content);
+  EXPECT_TRUE(
+      iframe->content_attributes->iframe_data->content->get_local_frame_data()
+          ->contains_paid_content);
 
   auto& children = iframe->children_nodes[0]->children_nodes;
   EXPECT_FALSE(
@@ -3034,8 +3176,10 @@
   const auto& iframe1 = nodes[2];
   EXPECT_EQ(iframe1->content_attributes->attribute_type,
             mojom::blink::AIPageContentAttributeType::kIframe);
-  EXPECT_TRUE(iframe1->content_attributes->iframe_data->local_frame_data
-                  ->contains_paid_content);
+  EXPECT_TRUE(iframe1->content_attributes->iframe_data->content);
+  EXPECT_TRUE(
+      iframe1->content_attributes->iframe_data->content->get_local_frame_data()
+          ->contains_paid_content);
 
   const auto& children1 = iframe1->children_nodes[0]->children_nodes;
   EXPECT_FALSE(
@@ -3048,8 +3192,10 @@
   const auto& iframe2 = nodes[3];
   EXPECT_EQ(iframe2->content_attributes->attribute_type,
             mojom::blink::AIPageContentAttributeType::kIframe);
-  EXPECT_FALSE(iframe2->content_attributes->iframe_data->local_frame_data
-                   ->contains_paid_content);
+  EXPECT_TRUE(iframe2->content_attributes->iframe_data->content);
+  EXPECT_FALSE(
+      iframe2->content_attributes->iframe_data->content->get_local_frame_data()
+          ->contains_paid_content);
 
   const auto& children2 = iframe2->children_nodes[0]->children_nodes;
   EXPECT_FALSE(
@@ -3062,8 +3208,10 @@
   const auto& iframe3 = nodes[4];
   EXPECT_EQ(iframe3->content_attributes->attribute_type,
             mojom::blink::AIPageContentAttributeType::kIframe);
-  EXPECT_TRUE(iframe3->content_attributes->iframe_data->local_frame_data
-                  ->contains_paid_content);
+  EXPECT_TRUE(iframe3->content_attributes->iframe_data->content);
+  EXPECT_TRUE(
+      iframe3->content_attributes->iframe_data->content->get_local_frame_data()
+          ->contains_paid_content);
 
   const auto& children3 = iframe3->children_nodes[0]->children_nodes;
   EXPECT_FALSE(
diff --git a/third_party/blink/renderer/platform/bindings/frozen_array_base.cc b/third_party/blink/renderer/platform/bindings/frozen_array_base.cc
index 79b3ff52..e273ba7a 100644
--- a/third_party/blink/renderer/platform/bindings/frozen_array_base.cc
+++ b/third_party/blink/renderer/platform/bindings/frozen_array_base.cc
@@ -35,20 +35,6 @@
 const WrapperTypeInfo& FrozenArrayBase::wrapper_type_info_ =
     frozen_array_wrapper_type_info_;
 
-v8::Local<v8::Value> FrozenArrayBase::ToV8(ScriptState* script_state) const {
-  return const_cast<FrozenArrayBase*>(this)->ToV8(script_state);
-}
-
-v8::Local<v8::Value> FrozenArrayBase::ToV8(ScriptState* script_state) {
-  v8::Local<v8::Object> wrapper;
-  if (DOMDataStore::GetWrapper(script_state, this).ToLocal(&wrapper))
-      [[likely]] {
-    return wrapper;
-  }
-
-  return Wrap(script_state);
-}
-
 v8::Local<v8::Value> FrozenArrayBase::Wrap(ScriptState* script_state) {
   DCHECK(!DOMDataStore::ContainsWrapper(script_state->GetIsolate(), this));
 
diff --git a/third_party/blink/renderer/platform/bindings/frozen_array_base.h b/third_party/blink/renderer/platform/bindings/frozen_array_base.h
index 32e9dc3..9ee1a6b 100644
--- a/third_party/blink/renderer/platform/bindings/frozen_array_base.h
+++ b/third_party/blink/renderer/platform/bindings/frozen_array_base.h
@@ -26,9 +26,6 @@
  public:
   ~FrozenArrayBase() override = default;
 
-  v8::Local<v8::Value> ToV8(ScriptState* script_state) const;
-  v8::Local<v8::Value> ToV8(ScriptState* script_state);
-
   // ScriptWrappable overrides:
   v8::Local<v8::Value> Wrap(ScriptState* script_state) override;
   [[nodiscard]] v8::Local<v8::Object> AssociateWithWrapper(
diff --git a/third_party/blink/renderer/platform/runtime_enabled_features.json5 b/third_party/blink/renderer/platform/runtime_enabled_features.json5
index 9c22dfd8..1fbd28a4 100644
--- a/third_party/blink/renderer/platform/runtime_enabled_features.json5
+++ b/third_party/blink/renderer/platform/runtime_enabled_features.json5
@@ -3186,6 +3186,13 @@
       name: "ModifyParagraphCrossEditingoundary",
       status: "stable",
     },
+    // ModulePreloadReferrer enables modulepreload to send proper referrer
+    // headers as specified in the HTML spec. Behind a flag for safer rollout.
+    // https://crbug.com/441933555
+    {
+      name: "ModulePreloadReferrer",
+      status: "experimental",
+    },
     {
       name: "MojoJS",
       status: "test",
@@ -5022,7 +5029,7 @@
     // This feature enables drag and drop using touch input devices. Replaces
     // the old "--enable-touch-drag-drop" and "--disable-touch-drag-drop"
     // switches.
-    // Enabled by default in Android and ChromeOS.
+    // Enabled by default in Android, ChromeOS and Windows.
     {
       name: "TouchDragAndDrop",
       base_feature: "none",
@@ -5033,6 +5040,9 @@
     {
       name: "TouchDragOnShortPress",
       depends_on: ["TouchDragAndDrop"],
+      status: {
+        "Win": "stable",
+      },
     },
     // Many websites disable mouse support when touch APIs are available.  We'd
     // like to enable this always but can't until more websites fix this bug.
diff --git a/third_party/blink/renderer/platform/widget/input/main_thread_event_queue.cc b/third_party/blink/renderer/platform/widget/input/main_thread_event_queue.cc
index dba6c45..5d6b319 100644
--- a/third_party/blink/renderer/platform/widget/input/main_thread_event_queue.cc
+++ b/third_party/blink/renderer/platform/widget/input/main_thread_event_queue.cc
@@ -381,6 +381,24 @@
   return allowed;
 }
 
+void MainThreadEventQueue::OnGestureScrollUpdateAck(
+    mojom::blink::InputEventResultState ack_state) {
+  base::AutoLock lock(shared_state_lock_);
+  if (shared_state_.gsu_acked_as_consumed_) {
+    return;
+  }
+  if (ack_state != mojom::blink::InputEventResultState::kConsumed) {
+    return;
+  }
+  shared_state_.gsu_acked_as_consumed_ = true;
+}
+
+void MainThreadEventQueue::OnGestureScrollEndAck(
+    mojom::blink::InputEventResultState ack_state) {
+  base::AutoLock lock(shared_state_lock_);
+  shared_state_.gsu_acked_as_consumed_ = false;
+}
+
 void MainThreadEventQueue::HandleEvent(
     std::unique_ptr<WebCoalescedInputEvent> event,
     DispatchType original_dispatch_type,
@@ -670,7 +688,9 @@
 
       if (IsRafAlignedEvent(shared_state_.events_.front())) {
         // Throttle touchmoves that are async.
-        if (IsAsyncTouchMove(shared_state_.events_.front())) {
+        if (IsAsyncTouchMove(shared_state_.events_.front()) &&
+            ShouldThrottleAsyncTouchMoves(
+                shared_state_.gsu_acked_as_consumed_)) {
           if (shared_state_.events_.size() == 1 &&
               frame_time < shared_state_.last_async_touch_move_timestamp_ +
                                kAsyncTouchMoveInterval) {
@@ -957,4 +977,17 @@
   return compositor_thread_only_;
 }
 
+bool MainThreadEventQueue::ShouldThrottleAsyncTouchMoves(
+    bool gsu_acked_as_consumed) {
+  // TODO(441800312): Investigate updating touch moves throttling logic during
+  // scrolls.
+  if (base::FeatureList::IsEnabled(
+          blink::features::kAsyncTouchMovesImmediatelyAfterScroll)) {
+    // If a gsu is acked as consumed already, async touch moves should indeed be
+    // throttled.
+    return gsu_acked_as_consumed;
+  }
+  return true;
+}
+
 }  // namespace blink
diff --git a/third_party/blink/renderer/platform/widget/input/main_thread_event_queue.h b/third_party/blink/renderer/platform/widget/input/main_thread_event_queue.h
index 953bd7c..f7297bd 100644
--- a/third_party/blink/renderer/platform/widget/input/main_thread_event_queue.h
+++ b/third_party/blink/renderer/platform/widget/input/main_thread_event_queue.h
@@ -113,6 +113,9 @@
   // Type of dispatching of the event.
   enum class DispatchType { kBlocking, kNonBlocking };
 
+  void OnGestureScrollUpdateAck(mojom::blink::InputEventResultState ack_state);
+  void OnGestureScrollEndAck(mojom::blink::InputEventResultState ack_state);
+
   // Called once the compositor has handled |event| and indicated that it is
   // a non-blocking event to be queued to the main thread.
   void HandleEvent(std::unique_ptr<WebCoalescedInputEvent> event,
@@ -218,6 +221,7 @@
     bool sent_main_frame_request_ = false;
     // A PostTask to the main thread has been sent but not executed yet.
     bool sent_post_task_ = false;
+    bool gsu_acked_as_consumed_ = false;
     base::TimeTicks last_async_touch_move_timestamp_;
   };
 
@@ -235,6 +239,7 @@
   std::unique_ptr<InputEventPrediction> event_predictor_;
 
  private:
+  bool ShouldThrottleAsyncTouchMoves(bool gsu_acked_as_consumed);
   // Returns false if we are trying to send a gesture scroll event to the main
   // thread when we shouldn't be.  Used for DCHECK in HandleEvent.
   bool Allowed(const WebInputEvent& event, bool force_allow);
diff --git a/third_party/blink/renderer/platform/widget/input/widget_input_handler_manager.cc b/third_party/blink/renderer/platform/widget/input/widget_input_handler_manager.cc
index 3c80769..75ba894 100644
--- a/third_party/blink/renderer/platform/widget/input/widget_input_handler_manager.cc
+++ b/third_party/blink/renderer/platform/widget/input/widget_input_handler_manager.cc
@@ -1210,6 +1210,15 @@
 
   mojom::blink::InputEventResultState ack_state =
       InputEventDispositionToAck(event_disposition);
+  if (event->Event().GetType() ==
+      blink::WebInputEvent::Type::kGestureScrollUpdate) {
+    input_event_queue_->OnGestureScrollUpdateAck(ack_state);
+  }
+  if (event->Event().GetType() ==
+      blink::WebInputEvent::Type::kGestureScrollEnd) {
+    input_event_queue_->OnGestureScrollEndAck(ack_state);
+  }
+
   if (ack_state == mojom::blink::InputEventResultState::kConsumed) {
     widget_scheduler_->DidHandleInputEventOnCompositorThread(
         event->Event(), scheduler::WidgetScheduler::InputEventState::
diff --git a/third_party/blink/web_tests/TestExpectations b/third_party/blink/web_tests/TestExpectations
index 692e42bc..cf0f90e 100644
--- a/third_party/blink/web_tests/TestExpectations
+++ b/third_party/blink/web_tests/TestExpectations
@@ -9604,9 +9604,6 @@
 # Gardener 2025-08-05
 crbug.com/436615917 inspector-protocol/timeline/long-animation-frame-trace-visual-update.js [ Failure Pass ]
 
-# Gardener 2025-08-06
-crbug.com/436534724 external/wpt/resource-timing/tentative/initiator-url/document-initiated.html [ Pass Timeout ]
-
 # Gardener 2025-08-07
 crbug.com/435121429 external/wpt/web-animations/timing-model/animations/setting-the-start-time-of-an-animation.html [ Failure Pass ]
 
diff --git a/third_party/blink/web_tests/external/wpt/html/semantics/scripting-1/the-script-element/module/modulepreload-cross-origin-referrerpolicy.sub.html b/third_party/blink/web_tests/external/wpt/html/semantics/scripting-1/the-script-element/module/modulepreload-cross-origin-referrerpolicy.sub.html
new file mode 100644
index 0000000..2d914f3d
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/html/semantics/scripting-1/the-script-element/module/modulepreload-cross-origin-referrerpolicy.sub.html
@@ -0,0 +1,96 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>Modulepreload Cross-Origin Referrer Policy Tests</title>
+<meta name="author" title="Google" href="https://www.google.com/">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/links.html#link-type-modulepreload">
+<link rel="help" href="https://w3c.github.io/webappsec-referrer-policy/">
+<meta name="assert" content="link rel=modulepreload respects the referrerpolicy attribute for cross-origin requests">
+<!--
+  This test verifies that modulepreload correctly handles various referrer policies
+  for cross-origin requests. It tests all standard referrer policy values:
+  - no-referrer
+  - origin
+  - same-origin
+  - strict-origin
+  - strict-origin-when-cross-origin
+  - unsafe-url
+
+  It also tests that modulepreload respects the document's default referrer policy.
+
+  Each policy is tested by creating a modulepreload link with CORS enabled and the
+  specific policy, then verifying the referrer header that was sent when requesting
+  the resource from another origin.
+-->
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/security-features/resources/common.sub.js"></script>
+<script>
+// This test is more of a basic verification that the modulepreload element
+// correctly handles cross-origin requests with referrer policies, rather than
+// a comprehensive test of all referrer policy values. Those are tested more
+// thoroughly in the standard WPT referrer-policy tests.
+
+setup({
+  // Allow more time for cross-origin tests
+  explicit_timeout: true,
+  single_test: false
+});
+
+// Helper function to create a modulepreload element
+function createModulePreload(url, referrerPolicy = null) {
+  const link = document.createElement('link');
+  link.rel = 'modulepreload';
+  link.href = url;
+  link.crossOrigin = 'anonymous'; // Enable CORS
+
+  if (referrerPolicy !== null) {
+    link.referrerPolicy = referrerPolicy;
+  }
+
+  return link;
+}
+
+// Helper function to test a modulepreload element with a specific referrer policy
+function testModulePreloadWithPolicy(policy, testName) {
+  promise_test(async t => {
+    // Set a timeout for this test
+    t.step_timeout(() => {
+      assert_unreached("Test timed out");
+    }, 10000);
+
+    return new Promise((resolve, reject) => {
+      const link = createModulePreload(
+        `https://{{domains[www1]}}:{{ports[https][0]}}/common/security-features/subresource/script.py`,
+        policy
+      );
+
+      link.onload = () => {
+        // If we got here, the load succeeded, which is what we want to verify
+        assert_true(true, "Cross-origin modulepreload with " + policy + " policy loaded successfully");
+        resolve();
+      };
+
+      link.onerror = () => {
+        reject(new Error("Failed to load cross-origin modulepreload with " + policy + " policy"));
+      };
+
+      document.head.appendChild(link);
+    });
+  }, testName);
+}
+
+// Test basic cross-origin cases with different referrer policies
+testModulePreloadWithPolicy(null, "Cross-origin modulepreload with default referrer policy should load");
+testModulePreloadWithPolicy("no-referrer", "Cross-origin modulepreload with no-referrer policy should load");
+testModulePreloadWithPolicy("origin", "Cross-origin modulepreload with origin policy should load");
+testModulePreloadWithPolicy("same-origin", "Cross-origin modulepreload with same-origin policy should load");
+testModulePreloadWithPolicy("strict-origin", "Cross-origin modulepreload with strict-origin policy should load");
+testModulePreloadWithPolicy("strict-origin-when-cross-origin", "Cross-origin modulepreload with strict-origin-when-cross-origin policy should load");
+testModulePreloadWithPolicy("unsafe-url", "Cross-origin modulepreload with unsafe-url policy should load");
+</script>
+</head>
+<body>
+<div id="log"></div>
+</body>
+</html>
\ No newline at end of file
diff --git a/third_party/blink/web_tests/external/wpt/html/semantics/scripting-1/the-script-element/module/modulepreload-inline-referrerpolicy.html b/third_party/blink/web_tests/external/wpt/html/semantics/scripting-1/the-script-element/module/modulepreload-inline-referrerpolicy.html
new file mode 100644
index 0000000..6a743e5
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/html/semantics/scripting-1/the-script-element/module/modulepreload-inline-referrerpolicy.html
@@ -0,0 +1,88 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>Modulepreload Inline Referrer Policy Tests</title>
+<meta name="author" title="Google" href="https://www.google.com/">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/links.html#link-type-modulepreload">
+<link rel="help" href="https://w3c.github.io/webappsec-referrer-policy/">
+<meta name="assert" content="link rel=modulepreload respects the referrerpolicy attribute on inline elements">
+<!--
+  This test verifies that inline modulepreload elements (statically declared in HTML)
+  correctly handle referrer policies. It tests:
+
+  1. Default behavior (no referrerpolicy attribute) - should use origin or full URL depending on implementation
+  2. origin referrerpolicy - should use only origin
+  3. no-referrer referrerpolicy - should not send referrer
+  4. Document-level referrer policy (via meta tag) - should be respected
+
+  Unlike the other modulepreload referrer tests that create elements dynamically,
+  this test uses inline link elements in the HTML.
+-->
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+// Initialize the window.referrers object that will be used by echo-referrer.py
+window.referrers = {};
+
+// Create unique IDs for each test
+const noReferrerPolicyId = Date.now();
+const originPolicyId = Date.now() + 1;
+const noReferrerId = Date.now() + 2;
+const originId = Date.now() + 3;
+</script>
+
+<!-- Test with no referrerpolicy attribute (should use document default) -->
+<link rel="modulepreload" href="/preload/resources/echo-referrer.py?uid=NOPOLICY_ID">
+
+<!-- Test with origin referrerpolicy attribute -->
+<link rel="modulepreload" href="/preload/resources/echo-referrer.py?uid=ORIGIN_ID" referrerpolicy="origin">
+
+<!-- Test with no-referrer referrerpolicy attribute -->
+<link rel="modulepreload" href="/preload/resources/echo-referrer.py?uid=NOREFERRER_ID" referrerpolicy="no-referrer">
+
+<!-- For document policy test, add meta tag for origin referrer policy -->
+<meta name="referrer" content="origin">
+<link rel="modulepreload" href="/preload/resources/echo-referrer.py?uid=DOC_POLICY_ID">
+
+<script>
+// Replace placeholder IDs with actual IDs in the HTML
+document.documentElement.innerHTML = document.documentElement.innerHTML
+  .replace('NOPOLICY_ID', noReferrerPolicyId)
+  .replace('ORIGIN_ID', originPolicyId)
+  .replace('NOREFERRER_ID', noReferrerId)
+  .replace('DOC_POLICY_ID', originId);
+</script>
+</head>
+<body>
+<script>
+// Ensure modules are loaded by importing them
+promise_test(async t => {
+  await Promise.all([
+    import(`/preload/resources/echo-referrer.py?uid=${noReferrerPolicyId}`),
+    import(`/preload/resources/echo-referrer.py?uid=${originPolicyId}`),
+    import(`/preload/resources/echo-referrer.py?uid=${noReferrerId}`),
+    import(`/preload/resources/echo-referrer.py?uid=${originId}`)
+  ]);
+
+  // Test that module with no specified referrerpolicy
+  // Note: Some implementations may send only origin for inline modulepreload requests
+  const expectedDefault = location.origin + "/";
+  assert_equals(window.referrers[noReferrerPolicyId], expectedDefault,
+    "Modulepreload with no referrerpolicy should use expected referrer");
+
+  // Test that module with origin referrerpolicy sends only origin
+  assert_equals(window.referrers[originPolicyId], location.origin + "/",
+    "Modulepreload with origin referrerpolicy should send origin only");
+
+  // Test that module with no-referrer policy sends no referrer
+  assert_equals(window.referrers[noReferrerId], "",
+    "Modulepreload with no-referrer referrerpolicy should not send referrer");
+
+  // Test that module with no policy but document policy uses document policy
+  assert_equals(window.referrers[originId], location.origin + "/",
+    "Modulepreload with no referrerpolicy should use document's policy");
+
+}, "Inline modulepreload elements should respect the referrerpolicy attribute");
+</script>
+</body>
+</html>
\ No newline at end of file
diff --git a/third_party/blink/web_tests/external/wpt/html/semantics/scripting-1/the-script-element/module/modulepreload-referrer-check.html b/third_party/blink/web_tests/external/wpt/html/semantics/scripting-1/the-script-element/module/modulepreload-referrer-check.html
new file mode 100644
index 0000000..4d422e79
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/html/semantics/scripting-1/the-script-element/module/modulepreload-referrer-check.html
@@ -0,0 +1,49 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>Modulepreload Referrer Header Check</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+// Initialize the window.referrers object that will be used by echo-referrer.py
+window.referrers = {};
+</script>
+</head>
+<body>
+<iframe id="test-frame"></iframe>
+
+<script>
+// This test uses a simple approach to check if modulepreload sends a referrer header
+promise_test(async t => {
+  const importId = Date.now();
+  const preloadId = Date.now() + 1;
+
+  // Import will set window.referrers[importId] to the referrer value
+  await import(`/preload/resources/echo-referrer.py?uid=${importId}`);
+
+  const link = document.createElement('link');
+  link.rel = 'modulepreload';
+  link.href = `/preload/resources/echo-referrer.py?uid=${preloadId}`;
+
+  // Wait for the preload to complete using onload event
+  const preloadComplete = new Promise(resolve => {
+    link.onload = resolve;
+    link.onerror = () => {
+      assert_unreached("Modulepreload failed to load");
+    };
+  });
+
+  document.head.appendChild(link);
+  await preloadComplete;
+
+  // Second import ensures the module is loaded and referrer is recorded
+  await import(`/preload/resources/echo-referrer.py?uid=${preloadId}`);
+
+  // Check if referrers were recorded in the global window.referrers object
+  assert_equals(window.referrers[importId], location.href, "Dynamic import should have a referrer header");
+  assert_equals(window.referrers[preloadId], location.href, "Modulepreload should have a referrer header");
+
+}, "Modulepreload should send a referrer header");
+</script>
+</body>
+</html>
\ No newline at end of file
diff --git a/third_party/blink/web_tests/external/wpt/html/semantics/scripting-1/the-script-element/module/modulepreload-referrerpolicy.html b/third_party/blink/web_tests/external/wpt/html/semantics/scripting-1/the-script-element/module/modulepreload-referrerpolicy.html
new file mode 100644
index 0000000..061f076
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/html/semantics/scripting-1/the-script-element/module/modulepreload-referrerpolicy.html
@@ -0,0 +1,200 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>Modulepreload Referrer Policy Tests</title>
+<meta name="author" title="Google" href="https://www.google.com/">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/links.html#link-type-modulepreload">
+<link rel="help" href="https://w3c.github.io/webappsec-referrer-policy/">
+<meta name="assert" content="link rel=modulepreload respects the referrerpolicy attribute">
+<!--
+  This test verifies that modulepreload correctly handles various referrer policies
+  for same-origin requests. It tests all standard referrer policy values:
+  - no-referrer
+  - origin
+  - same-origin
+  - strict-origin
+  - strict-origin-when-cross-origin
+  - unsafe-url
+  Each policy is tested by creating a modulepreload link dynamically with the
+  specific policy and verifying the referrer header that was sent.
+-->
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+// Initialize the window.referrers object that will be used by echo-referrer.py
+window.referrers = {};
+</script>
+</head>
+<body>
+<script>
+// Helper function to create a unique ID for each test
+function generateUniqueId() {
+  return Date.now() + Math.floor(Math.random() * 1000);
+}
+
+// Helper function to create a modulepreload element with a given referrer policy
+function createModulePreload(url, referrerPolicy = null) {
+  const link = document.createElement('link');
+  link.rel = 'modulepreload';
+  link.href = url;
+
+  if (referrerPolicy !== null) {
+    link.referrerPolicy = referrerPolicy;
+  }
+
+  return link;
+}
+
+// Helper function to wait for preload completion
+function waitForPreload(link) {
+  return new Promise((resolve, reject) => {
+    link.onload = resolve;
+    link.onerror = () => reject(new Error("Modulepreload failed to load"));
+  });
+}
+
+// Test default referrer policy behavior
+promise_test(async t => {
+  const uid = generateUniqueId();
+  const url = `/preload/resources/echo-referrer.py?uid=${uid}`;
+
+  // First import to establish baseline
+  await import(url);
+
+  // Create modulepreload
+  const link = createModulePreload(url);
+  const preloadComplete = waitForPreload(link);
+
+  document.head.appendChild(link);
+  await preloadComplete;
+
+  // Import again to ensure the module is loaded
+  await import(url);
+
+  // Check if referrer was sent
+  assert_equals(window.referrers[uid], location.href, "Modulepreload should use default referrer policy");
+
+}, "Modulepreload should use default referrer policy");
+
+// Test explicit no-referrer policy
+promise_test(async t => {
+  const uid = generateUniqueId();
+  const url = `/preload/resources/echo-referrer.py?uid=${uid}`;
+
+  // Create modulepreload with no-referrer policy
+  const link = createModulePreload(url, 'no-referrer');
+  const preloadComplete = waitForPreload(link);
+
+  document.head.appendChild(link);
+  await preloadComplete;
+
+  // Import again to ensure the module is loaded
+  await import(url);
+
+  // Check if referrer was NOT sent
+  assert_equals(window.referrers[uid], "", "Modulepreload with no-referrer policy should not send referrer");
+
+}, "Modulepreload with no-referrer policy should not send referrer");
+
+// Test origin referrer policy
+promise_test(async t => {
+  const uid = generateUniqueId();
+  const url = `/preload/resources/echo-referrer.py?uid=${uid}`;
+
+  // Create modulepreload with origin policy
+  const link = createModulePreload(url, 'origin');
+  const preloadComplete = waitForPreload(link);
+
+  document.head.appendChild(link);
+  await preloadComplete;
+
+  // Import again to ensure the module is loaded
+  await import(url);
+
+  // Check if origin-only referrer was sent
+  assert_equals(window.referrers[uid], location.origin + "/", "Modulepreload with origin policy should send origin-only referrer");
+
+}, "Modulepreload with origin policy should send origin-only referrer");
+
+// Test same-origin referrer policy
+promise_test(async t => {
+  const uid = generateUniqueId();
+  const url = `/preload/resources/echo-referrer.py?uid=${uid}`;
+
+  // Create modulepreload with same-origin policy
+  const link = createModulePreload(url, 'same-origin');
+  const preloadComplete = waitForPreload(link);
+
+  document.head.appendChild(link);
+  await preloadComplete;
+
+  // Import again to ensure the module is loaded
+  await import(url);
+
+  // Check if full referrer was sent (for same-origin requests)
+  assert_equals(window.referrers[uid], location.href, "Modulepreload with same-origin policy should send full referrer for same-origin requests");
+
+}, "Modulepreload with same-origin policy should send full referrer for same-origin requests");
+
+// Test strict-origin referrer policy
+promise_test(async t => {
+  const uid = generateUniqueId();
+  const url = `/preload/resources/echo-referrer.py?uid=${uid}`;
+
+  // Create modulepreload with strict-origin policy
+  const link = createModulePreload(url, 'strict-origin');
+  const preloadComplete = waitForPreload(link);
+
+  document.head.appendChild(link);
+  await preloadComplete;
+
+  // Import again to ensure the module is loaded
+  await import(url);
+
+  // Check if origin-only referrer was sent
+  assert_equals(window.referrers[uid], location.origin + "/", "Modulepreload with strict-origin policy should send origin-only referrer");
+
+}, "Modulepreload with strict-origin policy should send origin-only referrer");
+
+// Test strict-origin-when-cross-origin referrer policy
+promise_test(async t => {
+  const uid = generateUniqueId();
+  const url = `/preload/resources/echo-referrer.py?uid=${uid}`;
+
+  // Create modulepreload with strict-origin-when-cross-origin policy
+  const link = createModulePreload(url, 'strict-origin-when-cross-origin');
+  const preloadComplete = waitForPreload(link);
+
+  document.head.appendChild(link);
+  await preloadComplete;
+
+  // Import again to ensure the module is loaded
+  await import(url);
+
+  // For same-origin requests, full URL should be sent
+  assert_equals(window.referrers[uid], location.href, "Modulepreload with strict-origin-when-cross-origin policy should send full referrer for same-origin requests");
+
+}, "Modulepreload with strict-origin-when-cross-origin policy should send full referrer for same-origin requests");
+
+// Test unsafe-url referrer policy
+promise_test(async t => {
+  const uid = generateUniqueId();
+  const url = `/preload/resources/echo-referrer.py?uid=${uid}`;
+
+  // Create modulepreload with unsafe-url policy
+  const link = createModulePreload(url, 'unsafe-url');
+  const preloadComplete = waitForPreload(link);
+
+  document.head.appendChild(link);
+  await preloadComplete;
+
+  // Import again to ensure the module is loaded
+  await import(url);
+
+  // Check if full referrer was sent
+  assert_equals(window.referrers[uid], location.href, "Modulepreload with unsafe-url policy should send full referrer");
+
+}, "Modulepreload with unsafe-url policy should send full referrer");
+</script>
+</body>
+</html>
\ No newline at end of file
diff --git a/third_party/blink/web_tests/external/wpt/resource-timing/resources/test-initiator.js b/third_party/blink/web_tests/external/wpt/resource-timing/resources/test-initiator.js
index c6ad50a..6e13a02f 100644
--- a/third_party/blink/web_tests/external/wpt/resource-timing/resources/test-initiator.js
+++ b/third_party/blink/web_tests/external/wpt/resource-timing/resources/test-initiator.js
@@ -1,25 +1,38 @@
-if (observe_entry === undefined) {
-  throw new Error("You must include resource-timing/resources/observe-entry.js "
-    + "before including this script.");
+
+const with_timeout_message = async (promise, message, timeout = 1000) => {
+  return Promise.race([
+    promise,
+    new Promise((resolve, reject) => {
+      step_timeout(() => {
+        reject(new Error(message));
+      }, timeout);
+    }),
+  ]);
 }
 
+const observe_entry_no_timeout = entry_name => {
+  const entry = new Promise(resolve => {
+    new PerformanceObserver((entry_list, observer) => {
+      for (const entry of entry_list.getEntries()) {
+        if (entry.name.endsWith(entry_name)) {
+          resolve(entry);
+          observer.disconnect();
+          return;
+        }
+      }
+    }).observe({"type": "resource", "buffered": true});
+  });
+  return entry;
+};
+
 // Asserts that, for the given name, there is/will-be a
 // PerformanceResourceTiming entry that has the given 'initiatorUrl'. The test
 // is labeled according to the given descriptor.
-const initiator_url_test = (resourceName, expectedUrl, descriptor) => {
+const initiator_url_test = (entry_name, expected_url, descriptor, timeout_msg) => {
   promise_test(async () => {
-    const entry = await observe_entry(resourceName);
-    assert_equals(entry.initiatorUrl, expectedUrl);
-  }, `The initiator Url for ${descriptor} must be '${expectedUrl}'`);
+    const promise = observe_entry_no_timeout(entry_name);
+    const entry = await with_timeout_message(promise, timeout_msg, /* timeout = */ 2000);
+    assert_equals(entry.initiatorUrl, expected_url);
+  }, `The initiator Url for ${descriptor} must be '${expected_url}'`);
 };
 
-const initiator_url_doc_write_test = (write_to_doc,
-  resourceName, expectedUrl, descriptor) => {
-  promise_test(async () => {
-    document.open();
-    document.write(write_to_doc);
-    document.close();
-    const entry = await observe_entry(resourceName);
-    assert_equals(entry.initiatorUrl, expectedUrl);
-  }, `The initiator Url for ${descriptor} must be '${expectedUrl}'`);
-};
diff --git a/third_party/blink/web_tests/external/wpt/resource-timing/tentative/initiator-url/document-initiated.html b/third_party/blink/web_tests/external/wpt/resource-timing/tentative/initiator-url/document-initiated.html
index 6b19227..09da305 100644
--- a/third_party/blink/web_tests/external/wpt/resource-timing/tentative/initiator-url/document-initiated.html
+++ b/third_party/blink/web_tests/external/wpt/resource-timing/tentative/initiator-url/document-initiated.html
@@ -44,7 +44,14 @@
   <script>
     display_paragraph();
 
-    // Load arbitrary stylesheet with inline script
+    // TODO(crbug.com/40919714): The two elements below are not initiated by html.
+    // They are initiated by script. They should be moved out of this test.
+    // As of 08/2025 the two below are passing for chromium but because of wrong
+    // reasons. They are not recognized to be resources initaited by scripts, so they
+    // are thought to be initiated from main html, for which the url happens to be
+    // correct for inline scripts.
+
+    // Load arbitrary stylesheet with inline script.
     const link = document.createElement("link");
     link.rel = "stylesheet";
     link.href = "../../resources/empty_style.css?inline-script";
@@ -69,27 +76,15 @@
       "Ahem.ttf?initiator-html",
       "blue.png?using-Img-tag",
         /*        "green.html",
-        TODO(guohuideng@microsoft.com): report initiator for iframe element.
+        TODO(crbug.com/40919714): report initiator for iframe element.
         */
       "empty_style.css?inline-script",
       "blue.png?inline-script",
     ];
     for (const resource of resources) {
-      initiator_url_test(resource, expectedInitiatorUrl, resource+" initiatorUrl from document-initiated");
+      initiator_url_test(resource, expectedInitiatorUrl, resource+
+        " initiatorUrl from document-initiated", resource+" timeout");
     }
 
-    write_to_doc = '<img id="img_written" src="/images/blue.png?inline-script-doc-write">';
-    resource = "blue.png?inline-script-doc-write";
-    initiator_url_doc_write_test(write_to_doc, resource, expectedInitiatorUrl,
-      resource+" initiatorUrl from document-initiated");
-
-    write_to_doc = '<link id="css_written" rel="stylesheet" href="../../resources/empty_style.css?inline-script-doc-write">';
-    resource = "empty_style.css?inline-script-doc-write";
-    initiator_url_doc_write_test(write_to_doc, resource, expectedInitiatorUrl, resource+" initiatorUrl from document-initiated");
-
-    write_to_doc = '<script id="script_written" src="../../resources/empty.js?doc-write"><\/script>';
-    resource = "empty.js?doc-write";
-    initiator_url_doc_write_test(write_to_doc, resource, expectedInitiatorUrl, resource+" initiatorUrl from document-initiated");
-
   </script>
 </body>
diff --git a/third_party/blink/web_tests/external/wpt/resource-timing/tentative/initiator-url/inline-document-write.html b/third_party/blink/web_tests/external/wpt/resource-timing/tentative/initiator-url/inline-document-write.html
new file mode 100644
index 0000000..ac8f204
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/resource-timing/tentative/initiator-url/inline-document-write.html
@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<head>
+  <script src="/resources/testharness.js"></script>
+  <script src="/resources/testharnessreport.js"></script>
+  <script src="/resource-timing/resources/observe-entry.js"></script>
+  <script src="../../resources/test-initiator.js"></script>
+  <script src="/common/get-host-info.sub.js"></script>
+
+</head>
+<body>
+  <script>
+    // As of 08/2025, Chromium hasn't properly implemented |initiator_Url| for document.write().
+    // These tests are passing because they are wrongly considered as html inline resources.
+    // document.write() is deprecated. The implementation of "initiator-url" for it is not
+    // planned. document.write() test cases should be removed.
+    document.write('<img id="img_written" src="/images/blue.png?inline-script-doc-write">');
+    document.write('<link id="css_written" rel="stylesheet" href="../resources/empty_style.css?inline-script-doc-write">');
+    document.write('<script id="script_written" src="../resources/empty.js?doc-write"><\/script>');
+
+    const host_info = get_host_info();
+    const expectedInitiatorUrl = host_info["ORIGIN"] +
+      "/resource-timing/tentative/initiator-url/inline-document-write.html";
+
+    const resources = [
+      "blue.png?inline-script-doc-write",
+      "empty_style.css?inline-script-doc-write",
+      "empty.js?doc-write",
+    ];
+
+    for (const resource of resources) {
+      initiator_url_test(resource, expectedInitiatorUrl, resource+
+        " initiatorUrl from document-initiated", resource+" timeout");
+    }
+  </script>
+</body>
diff --git a/third_party/blink/web_tests/webaudio/Analyser/realtimeanalyser-downmix.html b/third_party/blink/web_tests/webaudio/Analyser/realtimeanalyser-downmix.html
index 132046a..2e023a6 100644
--- a/third_party/blink/web_tests/webaudio/Analyser/realtimeanalyser-downmix.html
+++ b/third_party/blink/web_tests/webaudio/Analyser/realtimeanalyser-downmix.html
@@ -7,18 +7,15 @@
     <script src="../../resources/testharness.js"></script>
     <script src="../../resources/testharnessreport.js"></script>
     <script src="../resources/audit-util.js"></script>
-    <script src="../resources/audit.js"></script>
     <script src="../resources/fft.js"></script>
     <script src="../resources/realtimeanalyser-testing.js"></script>
   </head>
   <body>
-    <script id="layout-test-code">
-      let sampleRate = 44100;
-      let renderFrames = 2048;
+    <script>
+      const sampleRate = 44100;
+      const renderFrames = 2048;
 
-      let audit = Audit.createTaskRunner();
-
-      let testConfigs = [
+      const testConfigs = [
         {channelCount: 1, message: 'mono', floatRelError: 6.3283e-8},
         {channelCount: 2, message: 'stereo', floatRelError: 1.1681e-7},
         {channelCount: 4, message: 'quad', floatRelError: 4.9793e-7},
@@ -26,42 +23,35 @@
         {channelCount: 3, message: '3-channel', floatRelError: 6.3283e-8}
       ];
 
-      // Create tasks for each entry in testConfigs
-      for (k in testConfigs) {
-        audit.define(testConfigs[k].message, (function(config) {
-                       return function(task, should) {
-                         runTest(config, should).then(() => task.done());
-                       };
-                     })(testConfigs[k]));
+      // Create tests for each entry in testConfigs
+      for (const config of testConfigs) {
+        promise_test(async (t) => {
+          await runTest(config);
+        }, `Analyser downmix ${config.message} to mono`);
       }
 
-      audit.run();
-
       // Test downmixing of the AnalyserNode time data.  We use the downmixing
       // that automatically happens in the destination node to generate the
       // reference data which is compared to the data that the Analyser node has
       // captured.
-      function runTest(options, should) {
+      function runTest(options) {
         // Context MUST have exactly one channel so that we downmix the source
         // to mono to generate the reference.
-        let context = new OfflineAudioContext(1, renderFrames, sampleRate);
+        const context = new OfflineAudioContext(1, renderFrames, sampleRate);
 
-        let channels = options.channelCount || 1;
-        let source = context.createBufferSource();
+        const channels = options.channelCount || 1;
+        const source = context.createBufferSource();
 
         // The signals in each channel. Doesn't matter much what is in here, but
         // it's best if the values aren't linearly increasing so that the
         // average of the values isn't one of the values (in case the
         // implementation does something silly).  Only need to support up to 6
         // channels.
-        let bufferValues = [1, 2, 3, 4, 5, 6].map(function(x) {
-          return x * x
-        });
-        ;
+        const bufferValues = [1, 2, 3, 4, 5, 6].map((x) => x * x);
         source.buffer = createConstantBuffer(
             context, renderFrames, bufferValues.slice(0, channels));
 
-        let analyser = context.createAnalyser();
+        const analyser = context.createAnalyser();
         analyser.smoothingTimeConstant = 0;
         analyser.fftSize = 256;
 
@@ -71,34 +61,34 @@
         source.connect(analyser);
         source.connect(context.destination);
 
-        let timeData = new Float32Array(analyser.fftSize);
-        let freqData = new Float32Array(analyser.frequencyBinCount);
+        const timeData = new Float32Array(analyser.fftSize);
+        const freqData = new Float32Array(analyser.frequencyBinCount);
 
-        let suspendFrame = analyser.fftSize;
+        const suspendFrame = analyser.fftSize;
         context.suspend(suspendFrame / context.sampleRate)
-            .then(function() {
+            .then(() => {
               analyser.getFloatTimeDomainData(timeData);
               analyser.getFloatFrequencyData(freqData);
             })
             .then(context.resume.bind(context));
 
         source.start();
-        return context.startRendering().then(function(renderedBuffer) {
+        return context.startRendering().then((renderedBuffer) => {
           // Verify the time domain data is correct.
-          let prefix = 'Analyser downmix ' + options.message + ' to mono'
-          should(timeData, prefix + ' time data')
-              .beEqualToArray(renderedBuffer.getChannelData(0).subarray(
-                  0, analyser.fftSize));
+          const prefix = 'Analyser downmix ' + options.message + ' to mono';
+          assert_array_equals(
+              timeData,
+              renderedBuffer.getChannelData(0).subarray(0, analyser.fftSize),
+              prefix + ' time data');
 
-          let expectedTimeData =
+          const expectedTimeData =
               renderedBuffer.getChannelData(0).subarray(0, analyser.fftSize);
-          let fftOrder = Math.floor(Math.log2(analyser.fftSize));
-          let expectedFreqData =
+          const fftOrder = Math.floor(Math.log2(analyser.fftSize));
+          const expectedFreqData =
               computeFFTMagnitude(expectedTimeData, fftOrder).map(linearToDb);
 
-          compareFloatFreq(
-              prefix + ' freq data', freqData, expectedFreqData, should, {
-                precision: 6,
+          compareFloatFreq_W3CTH(
+              prefix + ' freq data', freqData, expectedFreqData, {
                 floatRelError: options.floatRelError,
               });
         });
diff --git a/third_party/blink/web_tests/webaudio/resources/realtimeanalyser-testing.js b/third_party/blink/web_tests/webaudio/resources/realtimeanalyser-testing.js
index 17441d81..ea291005 100644
--- a/third_party/blink/web_tests/webaudio/resources/realtimeanalyser-testing.js
+++ b/third_party/blink/web_tests/webaudio/resources/realtimeanalyser-testing.js
@@ -98,11 +98,30 @@
   return {success: success, expected: expectedFreq};
 }
 
+// Compare the float frequency data in dB, |actualFreq|, against the expected
+// value, |expectedFreq|. |options| is a dictionary with the property
+// |floatRelError| for setting the comparison threshold and |precision| for
+// setting the printed precision.  Setting |precision| to |undefined| means
+// printing all digits.  If |options.precision} doesn't exist, use a default
+// precision.
+function compareFloatFreq_W3CTH(message, actualFreq, expectedFreq, options) {
+  // Any dB values below -100 is pretty much in the noise due to round-off in
+  // the (single-precisiion) FFT, so just clip those values to -100.
+  const lowerLimit = -100;
+  clipMagnitude(lowerLimit, expectedFreq);
+  clipMagnitude(lowerLimit, actualFreq);
+  const threshold = {relativeThreshold: options.floatRelError || 0};
+
+  assert_array_equal_within_eps(actualFreq, expectedFreq, threshold, message);
+
+  return {success: true, expected: expectedFreq};
+}
+
 // Apply FFT smoothing, accumulating the result in |oldFreqData| with the new
 // data in |newFreqData|.  The smoothing time constant is |smoothingTime|
 function smoothFFT(oldFreqData, newFreqData, smoothingTime) {
   for (let k = 0; k < oldFreqData.length; ++k) {
-    let value =
+    const value =
         smoothingTime * oldFreqData[k] + (1 - smoothingTime) * newFreqData[k];
     oldFreqData[k] = value;
   }
diff --git a/third_party/crabbyavif/BUILD.gn b/third_party/crabbyavif/BUILD.gn
index c15d90ba7..d63ff65 100644
--- a/third_party/crabbyavif/BUILD.gn
+++ b/third_party/crabbyavif/BUILD.gn
@@ -162,6 +162,7 @@
     "src/src/codecs/mod.rs",
     "src/src/decoder/item.rs",
     "src/src/decoder/mod.rs",
+    "src/src/decoder/sampletransform.rs",
     "src/src/decoder/tile.rs",
     "src/src/decoder/track.rs",
     "src/src/gainmap.rs",
@@ -182,6 +183,7 @@
     "src/src/reformat/rgb_impl.rs",
     "src/src/reformat/scale.rs",
     "src/src/utils/clap.rs",
+    "src/src/utils/error.rs",
     "src/src/utils/mod.rs",
     "src/src/utils/pixels.rs",
     "src/src/utils/reader/mod.rs",
diff --git a/third_party/crabbyavif/README.chromium b/third_party/crabbyavif/README.chromium
index bcaa1922..585f770 100644
--- a/third_party/crabbyavif/README.chromium
+++ b/third_party/crabbyavif/README.chromium
@@ -2,7 +2,7 @@
 Short Name: crabbyavif
 URL: https://github.com/webmproject/CrabbyAvif
 Version: N/A
-Revision: 4c70b98d1ebc8a210f2919be7ccabbcf23061cb5
+Revision: f96d484aa93776f076a2949805fd29bc4888b1c4
 License: Apache-2.0
 License File: LICENSE
 Security Critical: yes
diff --git a/third_party/crabbyavif/src b/third_party/crabbyavif/src
index 4c70b98..f96d484 160000
--- a/third_party/crabbyavif/src
+++ b/third_party/crabbyavif/src
@@ -1 +1 @@
-Subproject commit 4c70b98d1ebc8a210f2919be7ccabbcf23061cb5
+Subproject commit f96d484aa93776f076a2949805fd29bc4888b1c4
diff --git a/third_party/depot_tools b/third_party/depot_tools
index 83ad192..0e9c00f 160000
--- a/third_party/depot_tools
+++ b/third_party/depot_tools
@@ -1 +1 @@
-Subproject commit 83ad19235f633ab5ac843b3e977d53f4b7b042a5
+Subproject commit 0e9c00fe9f9783104836bd68870b5253c8c76121
diff --git a/third_party/devtools-frontend/src b/third_party/devtools-frontend/src
index 5c0782b..6f55537 160000
--- a/third_party/devtools-frontend/src
+++ b/third_party/devtools-frontend/src
@@ -1 +1 @@
-Subproject commit 5c0782b1ec316a185616cd76223d66c8b2b4dbbb
+Subproject commit 6f55537aa2a782d9e8093896c857add3a88d01f3
diff --git a/third_party/federated_compute/BUILD.gn b/third_party/federated_compute/BUILD.gn
index e2ef7b0..5ef8094 100644
--- a/third_party/federated_compute/BUILD.gn
+++ b/third_party/federated_compute/BUILD.gn
@@ -2,10 +2,14 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
+import("//testing/test.gni")
 import("//third_party/protobuf/proto_library.gni")
 
 config("confidential_compute_include") {
-  include_dirs = [ "src" ]
+  include_dirs = [
+    "chromium",
+    "src",
+  ]
 }
 
 proto_library("confidential_compute_proto") {
@@ -34,10 +38,10 @@
   ]
 
   sources = [
+    "chromium/fcp/confidentialcompute/cose.cc",
     "src/fcp/base/base_name.cc",
     "src/fcp/base/digest.cc",
     "src/fcp/base/monitoring.cc",
-    "src/fcp/confidentialcompute/cose.cc",
     "src/fcp/confidentialcompute/crypto.cc",
   ]
 
@@ -52,7 +56,33 @@
   ]
 
   deps = [
+    "//base",
+    "//components/cbor",
     "//third_party/abseil-cpp:absl",
     "//third_party/boringssl",
   ]
 }
+
+if (!is_win && !is_android && !is_ios && !is_fuchsia) {
+  test("federated_compute_tests") {
+    testonly = true
+    public = [ "chromium/fcp/testing/testing.h" ]
+
+    sources = [
+      "chromium/fcp/confidentialcompute/cose_test.cc",
+      "chromium/fcp/testing/testing.cc",
+    ]
+
+    deps = [
+      ":confidential_compute",
+      "//base",
+      "//components/cbor",
+      "//testing/gmock",
+      "//testing/gtest",
+      "//testing/gtest:gtest_main",
+      "//third_party/abseil-cpp:absl",
+      "//third_party/protobuf:protobuf_full",
+      "//third_party/protobuf:struct_proto",
+    ]
+  }
+}
diff --git a/third_party/federated_compute/chromium/DEPS b/third_party/federated_compute/chromium/DEPS
new file mode 100644
index 0000000..2670c2b
--- /dev/null
+++ b/third_party/federated_compute/chromium/DEPS
@@ -0,0 +1,10 @@
+noparent = True
+
+include_rules = [
+  "+absl",
+  "+fcp",
+  "+gmock",
+  "+google",
+  "+gtest",
+  "+components/cbor",
+]
\ No newline at end of file
diff --git a/third_party/federated_compute/chromium/fcp/confidentialcompute/cose.cc b/third_party/federated_compute/chromium/fcp/confidentialcompute/cose.cc
new file mode 100644
index 0000000..35e28615
--- /dev/null
+++ b/third_party/federated_compute/chromium/fcp/confidentialcompute/cose.cc
@@ -0,0 +1,810 @@
+// Copyright 2024 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "fcp/confidentialcompute/cose.h"
+
+#include <cstdint>
+#include <memory>
+#include <optional>
+#include <string>
+#include <tuple>
+#include <utility>
+#include <vector>
+
+#include "absl/status/status.h"
+#include "absl/status/statusor.h"
+#include "absl/strings/str_cat.h"
+#include "absl/strings/string_view.h"
+#include "absl/time/time.h"
+#include "components/cbor/reader.h"
+#include "components/cbor/values.h"
+#include "components/cbor/writer.h"
+#include "fcp/base/monitoring.h"
+
+namespace fcp::confidential_compute {
+
+namespace {
+
+// CWT Claims; see https://www.iana.org/assignments/cwt/cwt.xhtml.
+enum CwtClaim {
+  kExp = 4,  // Expiration time.
+  kIat = 6,  // Issued at.
+
+  // Claims in the private space (-65537 and below).
+  // See ../protos/confidentialcompute/cbor_ids.md for claims originating from
+  // this project and
+  // https://github.com/project-oak/oak/blob/main/oak_dice/src/cert.rs for Oak
+  // claims.
+  kPublicKey = -65537,           // Claim containing serialized public key.
+  kConfigProperties = -65538,    // Claim containing configuration properties.
+  kAccessPolicySha256 = -65543,  // Claim containing access policy hash.
+  kOakPublicKey = -4670552,      // Oak claim containing serialized public key.
+};
+
+// COSE Header parameters; see https://www.iana.org/assignments/cose/cose.xhtml.
+enum CoseHeaderParameter {
+  kHdrAlg = 1,
+  kHdrKid = 4,
+
+  // Parameters in the private space (-65537 and below).
+  // See ../protos/confidentialcompute/cbor_ids.md.
+  kEncapsulatedKey = -65537,
+  kSrcState = -65538,
+  kDstState = -65539,
+};
+
+// COSE Key parameters; see https://www.iana.org/assignments/cose/cose.xhtml.
+enum CoseKeyParameter {
+  // Common parameters.
+  kKty = 1,
+  kKid = 2,
+  kAlg = 3,
+  kKeyOps = 4,
+
+  // OKP parameters.
+  kOkpCrv = -1,
+  kOkpX = -2,
+  kOkpD = -4,
+
+  // Symmetric parameters.
+  kSymmetricK = -1,
+};
+
+// COSE Key types; see https://www.iana.org/assignments/cose/cose.xhtml.
+enum CoseKeyType {
+  kOkp = 1,
+  kSymmetric = 4,
+};
+
+// Builds the protected header for a COSE structure, which is a map encoded as a
+// bstr.
+std::vector<uint8_t> BuildProtectedHeader(
+    std::optional<int64_t> algorithm,
+    const std::optional<std::optional<std::string>>& src_state,
+    const std::optional<std::string>& dst_state) {
+  cbor::Value::MapValue map;
+  if (algorithm) {
+    map.emplace(cbor::Value(CoseHeaderParameter::kHdrAlg),
+                cbor::Value(*algorithm));
+  }
+  if (src_state) {
+    map.emplace(cbor::Value(CoseHeaderParameter::kSrcState),
+                *src_state
+                    ? cbor::Value(**src_state, cbor::Value::Type::BYTE_STRING)
+                    : cbor::Value(cbor::Value::SimpleValue::NULL_VALUE));
+  }
+  if (dst_state) {
+    map.emplace(cbor::Value(CoseHeaderParameter::kDstState),
+                cbor::Value(*dst_state, cbor::Value::Type::BYTE_STRING));
+  }
+  std::optional<std::vector<uint8_t>> encoded_protected_header =
+      cbor::Writer::Write(cbor::Value(std::move(map)));
+  if (!encoded_protected_header) {
+    return {};
+  }
+  return *encoded_protected_header;
+}
+
+// Builds the payload for a CWT, which is a map of CWT claims encoded as a bstr.
+// See RFC 8392 section 7.1.
+absl::StatusOr<std::vector<uint8_t>> BuildCwtPayload(const OkpCwt& cwt) {
+  cbor::Value::MapValue map;
+  if (cwt.expiration_time) {
+    map.emplace(cbor::Value(CwtClaim::kExp),
+                cbor::Value(absl::ToUnixSeconds(*cwt.expiration_time)));
+  }
+  if (cwt.issued_at) {
+    map.emplace(cbor::Value(CwtClaim::kIat),
+                cbor::Value(absl::ToUnixSeconds(*cwt.issued_at)));
+  }
+  if (cwt.public_key) {
+    FCP_ASSIGN_OR_RETURN(std::string encoded_public_key,
+                         cwt.public_key->Encode());
+    map.emplace(
+        cbor::Value(CwtClaim::kPublicKey),
+        cbor::Value(encoded_public_key, cbor::Value::Type::BYTE_STRING));
+  }
+  if (!cwt.config_properties.empty()) {
+    map.emplace(
+        cbor::Value(CwtClaim::kConfigProperties),
+        cbor::Value(cwt.config_properties, cbor::Value::Type::BYTE_STRING));
+  }
+  if (!cwt.access_policy_sha256.empty()) {
+    map.emplace(
+        cbor::Value(CwtClaim::kAccessPolicySha256),
+        cbor::Value(cwt.access_policy_sha256, cbor::Value::Type::BYTE_STRING));
+  }
+
+  std::optional<std::vector<uint8_t>> encoded_cwt_payload =
+      cbor::Writer::Write(cbor::Value(std::move(map)));
+  if (!encoded_cwt_payload) {
+    return absl::InternalError("failed to build cwt payload");
+    ;
+  }
+  return *encoded_cwt_payload;
+}
+
+// Parses a serialized protected header from a COSE structure and sets the
+// corresponding output variables (if non-null).
+absl::Status ParseProtectedHeader(
+    const std::vector<uint8_t>& serialized_header,
+    std::optional<int64_t>* algorithm,
+    std::optional<std::optional<std::string>>* src_state,
+    std::optional<std::string>* dst_state) {
+  std::optional<cbor::Value> header = cbor::Reader::Read(serialized_header);
+  if (!header || !header->is_map()) {
+    return absl::InvalidArgumentError("protected header is invalid");
+  }
+
+  // Process the parameters map.
+  const cbor::Value::MapValue& map = header->GetMap();
+  for (const auto& [key, value] : map) {
+    if (!key.is_integer()) continue;
+    switch (key.GetInteger()) {
+      case CoseHeaderParameter::kHdrAlg:
+        if (algorithm) {
+          if (!value.is_integer()) {
+            return absl::InvalidArgumentError(absl::StrCat(
+                "unsupported algorithm parameter type ", value.type()));
+          }
+          *algorithm = value.GetInteger();
+        }
+        break;
+
+      case CoseHeaderParameter::kSrcState:
+        if (src_state) {
+          if (value.is_simple() &&
+              value.GetSimpleValue() == cbor::Value::SimpleValue::NULL_VALUE) {
+            *src_state = std::optional<std::string>(std::nullopt);
+          } else if (value.is_bytestring()) {
+            *src_state = value.GetBytestringAsString();
+          } else {
+            return absl::InvalidArgumentError(
+                absl::StrCat("unsupported src_state type ", value.type()));
+          }
+        }
+        break;
+
+      case CoseHeaderParameter::kDstState:
+        if (dst_state) {
+          if (!value.is_bytestring()) {
+            return absl::InvalidArgumentError(
+                absl::StrCat("unsupported dst_state type ", value.type()));
+          }
+          *dst_state = value.GetBytestringAsString();
+        }
+        break;
+
+      default:
+        break;
+    }
+  }
+  return absl::OkStatus();
+}
+
+// Parses a serialized CWT payload and updates the OkpCwt.
+absl::Status ParseCwtPayload(const std::vector<uint8_t>& serialized_payload,
+                             OkpCwt& cwt) {
+  std::optional<cbor::Value> payload = cbor::Reader::Read(serialized_payload);
+  if (!payload || !payload->is_map()) {
+    return absl::InvalidArgumentError("cwt payload is invalid");
+  }
+
+  // Process the claims map.
+  const cbor::Value::MapValue& map = payload->GetMap();
+  for (const auto& [key, value] : map) {
+    if (!key.is_integer()) continue;
+    switch (key.GetInteger()) {
+      case CwtClaim::kExp:
+        if (!value.is_integer()) {
+          return absl::InvalidArgumentError(
+              absl::StrCat("unsupported exp type ", value.type()));
+        }
+        cwt.expiration_time = absl::FromUnixSeconds(value.GetInteger());
+        break;
+
+      case CwtClaim::kIat:
+        if (!value.is_integer()) {
+          return absl::InvalidArgumentError(
+              absl::StrCat("unsupported iat type ", value.type()));
+        }
+        cwt.issued_at = absl::FromUnixSeconds(value.GetInteger());
+        break;
+
+      case CwtClaim::kOakPublicKey:
+      case CwtClaim::kPublicKey: {
+        if (!value.is_bytestring()) {
+          return absl::InvalidArgumentError(
+              absl::StrCat("unsupported public_key type ", value.type()));
+        }
+        FCP_ASSIGN_OR_RETURN(cwt.public_key,
+                             OkpKey::Decode(value.GetBytestringAsString()));
+        break;
+      }
+
+      case CwtClaim::kConfigProperties:
+        if (!value.is_bytestring()) {
+          return absl::InvalidArgumentError(
+              absl::StrCat("unsupported configuration type ", value.type()));
+        }
+        cwt.config_properties = value.GetBytestringAsString();
+        break;
+
+      case CwtClaim::kAccessPolicySha256:
+        if (!value.is_bytestring()) {
+          return absl::InvalidArgumentError(absl::StrCat(
+              "unsupported access_policy_sha256 type ", value.type()));
+        }
+        cwt.access_policy_sha256 = value.GetBytestringAsString();
+        break;
+
+      default:
+        break;
+    }
+  }
+  return absl::OkStatus();
+}
+
+// Builds a Sig_structure object for a COSE_Sign or COSE_Sign1 structure.
+// See RFC 9052 section 4.4 for the contents of the Sig_structure.
+std::string BuildSigStructure(
+    std::vector<uint8_t> body_protected,
+    std::optional<std::vector<uint8_t>> sign_protected, absl::string_view aad,
+    std::vector<uint8_t> payload) {
+  cbor::Value::ArrayValue sig_structure;
+  sig_structure.emplace_back(sign_protected ? "Signature" : "Signature1");
+  sig_structure.emplace_back(std::move(body_protected));
+  if (sign_protected) {
+    sig_structure.emplace_back(std::move(*sign_protected));
+  }
+  sig_structure.emplace_back(std::string(aad), cbor::Value::Type::BYTE_STRING);
+  sig_structure.emplace_back(std::move(payload));
+
+  std::optional<std::vector<uint8_t>> encoded_sig_structure =
+      cbor::Writer::Write(cbor::Value(std::move(sig_structure)));
+  if (!encoded_sig_structure) {
+    return {};
+  }
+  return std::string(encoded_sig_structure->begin(),
+                     encoded_sig_structure->end());
+}
+
+// Parses a serialized COSE_Sign or COSE_Sign1 structure and returns the
+// protected header, signer protected header (COSE_Sign only), payload, and
+// signature.
+absl::StatusOr<
+    std::tuple<std::vector<uint8_t>, std::optional<std::vector<uint8_t>>,
+               std::vector<uint8_t>, std::vector<uint8_t>>>
+ParseCoseSign(absl::string_view encoded) {
+  std::optional<cbor::Value> value =
+      cbor::Reader::Read(std::vector<uint8_t>(encoded.begin(), encoded.end()));
+  if (!value || !value->is_array()) {
+    return absl::InvalidArgumentError("COSE_Sign is invalid");
+  }
+
+  const cbor::Value::ArrayValue& array = value->GetArray();
+  if (array.size() != 4 || !array[0].is_bytestring() || !array[1].is_map() ||
+      !array[2].is_bytestring()) {
+    return absl::InvalidArgumentError("COSE_Sign is invalid");
+  }
+
+  // Extract the signature and signer protected header (COSE_Sign only).
+  std::optional<std::vector<uint8_t>> sign_protected;
+  std::optional<std::vector<uint8_t>> signature;
+  const cbor::Value& signature_component = array[3];
+  if (signature_component.is_bytestring()) {
+    // If the 4th element is a bstr, we're decoding a COSE_Sign1 structure.
+    signature = std::move(signature_component.GetBytestring());
+  } else if (signature_component.is_array() &&
+             signature_component.GetArray().size() > 0) {
+    // If the 4th element is an array, we're decoding a COSE_Sign structure.
+    // Use the signature and protected header from the first COSE_Signature,
+    // which is a (protected header, unprotected header, signature) tuple.
+    const cbor::Value::ArrayValue& sigs = signature_component.GetArray();
+    if (sigs[0].is_array() && sigs[0].GetArray().size() == 3 &&
+        sigs[0].GetArray()[0].is_bytestring() &&
+        sigs[0].GetArray()[2].is_bytestring()) {
+      sign_protected = std::move(sigs[0].GetArray()[0].GetBytestring());
+      signature = std::move(sigs[0].GetArray()[2].GetBytestring());
+    }
+  }
+
+  if (!signature) {
+    return absl::InvalidArgumentError("COSE_Sign is invalid");
+  }
+
+  return std::make_tuple(
+      std::move(array[0].GetBytestring()), std::move(sign_protected),
+      std::move(array[2].GetBytestring()), std::move(*signature));
+}
+
+// Builds the payload for a ReleaseToken, which is a COSE_Encrypt0 object
+// encoded as a bstr. See also RFC 9052 section 5.2.
+std::vector<uint8_t> BuildReleaseTokenPayload(const ReleaseToken& token) {
+  cbor::Value::ArrayValue array_value;
+  array_value.emplace_back(BuildProtectedHeader(
+      token.encryption_algorithm, token.src_state, token.dst_state));
+
+  cbor::Value::MapValue unprotected_header;
+  if (token.encryption_key_id) {
+    unprotected_header.emplace(
+        CoseHeaderParameter::kHdrKid,
+        cbor::Value(*token.encryption_key_id, cbor::Value::Type::BYTE_STRING));
+  }
+  if (token.encapped_key) {
+    unprotected_header.emplace(
+        CoseHeaderParameter::kEncapsulatedKey,
+        cbor::Value(*token.encapped_key, cbor::Value::Type::BYTE_STRING));
+  }
+  array_value.emplace_back(std::move(unprotected_header));
+  array_value.emplace_back(token.encrypted_payload,
+                           cbor::Value::Type::BYTE_STRING);
+  std::optional<std::vector<uint8_t>> encoded_array =
+      cbor::Writer::Write(cbor::Value(std::move(array_value)));
+  if (!encoded_array) {
+    return {};
+  }
+  return *encoded_array;
+}
+
+// Builds a Enc_structure object for a COSE_Encrypt0 structure.
+// See RFC 9052 section 5.3 for the contents of the Enc_structure.
+std::string BuildEncStructure(std::vector<uint8_t> protected_header,
+                              absl::string_view aad) {
+  cbor::Value::ArrayValue enc_structure;
+  enc_structure.emplace_back("Encrypt0");
+  enc_structure.emplace_back(std::move(protected_header));
+  enc_structure.emplace_back(aad, cbor::Value::Type::BYTE_STRING);
+  auto result = cbor::Writer::Write(cbor::Value(std::move(enc_structure)));
+  if (!result) {
+    return {};
+  }
+  return std::string(result->begin(), result->end());
+}
+
+// Parses a serialized ReleaseToken payload and updated the ReleaseToken.
+absl::StatusOr<
+    std::tuple<std::vector<uint8_t>, cbor::Value, std::vector<uint8_t>>>
+ParseCoseEncrypt0(const std::vector<uint8_t>& encoded) {
+  std::optional<cbor::Value> value =
+      cbor::Reader::Read(std::vector<uint8_t>(encoded.begin(), encoded.end()));
+  if (!value || !value->is_array()) {
+    return absl::InvalidArgumentError("COSE_Sign is invalid");
+  }
+
+  const cbor::Value::ArrayValue& array = value->GetArray();
+  if (array.size() != 3) {
+    return absl::InvalidArgumentError(
+        "COSE_Encrypt0 is invalid: not an array of size 3.");
+  }
+
+  if (!array[0].is_bytestring() || !array[1].is_map() ||
+      !array[2].is_bytestring()) {
+    return absl::InvalidArgumentError(
+        "COSE_Encrypt0 is invalid: wrong element types.");
+  }
+
+  return std::make_tuple(array[0].GetBytestring(), array[1].Clone(),
+                         array[2].GetBytestring());
+}
+
+}  // namespace
+
+absl::StatusOr<OkpKey> OkpKey::Decode(absl::string_view encoded) {
+  std::optional<cbor::Value> encoded_value =
+      cbor::Reader::Read(std::vector<uint8_t>(encoded.begin(), encoded.end()));
+  if (!encoded_value || !encoded_value->is_map()) {
+    return absl::InvalidArgumentError("okpkey is invalid");
+  }
+
+  std::optional<int64_t> kty;
+  OkpKey okp_key;
+  const cbor::Value::MapValue& map = encoded_value->GetMap();
+  for (const auto& [key, value] : map) {
+    if (!key.is_integer()) continue;
+    switch (key.GetInteger()) {
+      case CoseKeyParameter::kKty:
+        if (!value.is_integer()) {
+          return absl::InvalidArgumentError(
+              absl::StrCat("unsupported kty type ", value.type()));
+        }
+        kty = value.GetInteger();
+        break;
+
+      case CoseKeyParameter::kKid:
+        if (!value.is_bytestring()) {
+          return absl::InvalidArgumentError(
+              absl::StrCat("unsupported kid type ", value.type()));
+        }
+        okp_key.key_id = value.GetBytestringAsString();
+        break;
+
+      case CoseKeyParameter::kAlg:
+        if (!value.is_integer()) {
+          return absl::InvalidArgumentError(
+              absl::StrCat("unsupported alg type ", value.type()));
+        }
+        okp_key.algorithm = value.GetInteger();
+        break;
+
+      case CoseKeyParameter::kKeyOps:
+        if (!value.is_array()) {
+          return absl::InvalidArgumentError(
+              absl::StrCat("unsupported key_ops type ", value.type()));
+        }
+        for (const cbor::Value& op : value.GetArray()) {
+          if (!op.is_integer()) {
+            return absl::InvalidArgumentError(
+                absl::StrCat("unsupported key_ops entry type", op.type()));
+          }
+          okp_key.key_ops.push_back(op.GetInteger());
+        }
+        break;
+
+      case CoseKeyParameter::kOkpCrv:
+        if (!value.is_integer()) {
+          return absl::InvalidArgumentError(
+              absl::StrCat("unsupported curve type ", value.type()));
+        }
+        okp_key.curve = value.GetInteger();
+        break;
+
+      case CoseKeyParameter::kOkpX:
+        if (!value.is_bytestring()) {
+          return absl::InvalidArgumentError(
+              absl::StrCat("unsupported x type ", value.type()));
+        }
+        okp_key.x = value.GetBytestringAsString();
+        break;
+
+      case CoseKeyParameter::kOkpD:
+        if (!value.is_bytestring()) {
+          return absl::InvalidArgumentError(
+              absl::StrCat("unsupported d type ", value.type()));
+        }
+        okp_key.d = value.GetBytestringAsString();
+        break;
+
+      default:
+        break;
+    }
+  }
+
+  if (!kty.has_value() || *kty != CoseKeyType::kOkp) {
+    return absl::InvalidArgumentError("missing or wrong Cose_Key type");
+  }
+  return okp_key;
+}
+
+absl::StatusOr<std::string> OkpKey::Encode() const {
+  // Generate a map containing the parameters that are set.
+  cbor::Value::MapValue map;
+  map.emplace(cbor::Value(CoseKeyParameter::kKty),
+              cbor::Value(CoseKeyType::kOkp));
+  if (!key_id.empty()) {
+    map.emplace(cbor::Value(CoseKeyParameter::kKid),
+                cbor::Value(key_id, cbor::Value::Type::BYTE_STRING));
+  }
+  if (algorithm) {
+    map.emplace(cbor::Value(CoseKeyParameter::kAlg), cbor::Value(*algorithm));
+  }
+  if (!key_ops.empty()) {
+    cbor::Value::ArrayValue array;
+    for (int64_t key_op : key_ops) {
+      array.emplace_back(key_op);
+    }
+    map.emplace(cbor::Value(CoseKeyParameter::kKeyOps),
+                cbor::Value(std::move(array)));
+  }
+  if (curve) {
+    map.emplace(cbor::Value(CoseKeyParameter::kOkpCrv), cbor::Value(*curve));
+  }
+  if (!x.empty()) {
+    map.emplace(cbor::Value(CoseKeyParameter::kOkpX),
+                cbor::Value(x, cbor::Value::Type::BYTE_STRING));
+  }
+  if (!d.empty()) {
+    map.emplace(cbor::Value(CoseKeyParameter::kOkpD),
+                cbor::Value(d, cbor::Value::Type::BYTE_STRING));
+  }
+  std::optional<std::vector<uint8_t>> encoded_key =
+      cbor::Writer::Write(cbor::Value(std::move(map)));
+  if (!encoded_key) {
+    return absl::InternalError("failed to encode OkpKey");
+  }
+  return std::string(encoded_key->begin(), encoded_key->end());
+}
+
+absl::StatusOr<SymmetricKey> SymmetricKey::Decode(absl::string_view encoded) {
+  std::optional<cbor::Value> symmetric_key_value =
+      cbor::Reader::Read(std::vector<uint8_t>(encoded.begin(), encoded.end()));
+  if (!symmetric_key_value || !symmetric_key_value->is_map()) {
+    return absl::InvalidArgumentError("symmetric key is invalid");
+  }
+
+  std::optional<int64_t> kty;
+  SymmetricKey symmetric_key;
+  const cbor::Value::MapValue& map = symmetric_key_value->GetMap();
+  for (const auto& [key, value] : map) {
+    if (!key.is_integer()) continue;
+    switch (key.GetInteger()) {
+      case CoseKeyParameter::kKty:
+        if (!value.is_integer()) {
+          return absl::InvalidArgumentError(
+              absl::StrCat("unsupported kty type ", value.type()));
+        }
+        kty = value.GetInteger();
+        break;
+
+      case CoseKeyParameter::kAlg:
+        if (!value.is_integer()) {
+          return absl::InvalidArgumentError(
+              absl::StrCat("unsupported alg type ", value.type()));
+        }
+        symmetric_key.algorithm = value.GetInteger();
+        break;
+
+      case CoseKeyParameter::kKeyOps:
+        if (!value.is_array()) {
+          return absl::InvalidArgumentError(
+              absl::StrCat("unsupported key_ops type ", value.type()));
+        }
+        for (const cbor::Value& op : value.GetArray()) {
+          if (!op.is_integer()) {
+            return absl::InvalidArgumentError(
+                absl::StrCat("unsupported key_ops entry type", op.type()));
+          }
+          symmetric_key.key_ops.push_back(op.GetInteger());
+        }
+        break;
+
+      case CoseKeyParameter::kSymmetricK:
+        if (!value.is_bytestring()) {
+          return absl::InvalidArgumentError(
+              absl::StrCat("unsupported k type ", value.type()));
+        }
+        symmetric_key.k = value.GetBytestringAsString();
+        break;
+
+      default:
+        break;
+    }
+  }
+
+  if (!kty.has_value() || *kty != CoseKeyType::kSymmetric) {
+    return absl::InvalidArgumentError("missing or wrong Cose_Key type");
+  }
+  return symmetric_key;
+}
+
+absl::StatusOr<std::string> SymmetricKey::Encode() const {
+  cbor::Value::MapValue map;
+  map.emplace(cbor::Value(CoseKeyParameter::kKty),
+              cbor::Value(CoseKeyType::kSymmetric));
+  if (algorithm) {
+    map.emplace(cbor::Value(CoseKeyParameter::kAlg), cbor::Value(*algorithm));
+  }
+  if (!key_ops.empty()) {
+    cbor::Value::ArrayValue array;
+    for (int64_t key_op : key_ops) {
+      array.emplace_back(key_op);
+    }
+    map.emplace(cbor::Value(CoseKeyParameter::kKeyOps),
+                cbor::Value(std::move(array)));
+  }
+  if (!k.empty()) {
+    map.emplace(cbor::Value(CoseKeyParameter::kSymmetricK),
+                cbor::Value(k, cbor::Value::Type::BYTE_STRING));
+  }
+  std::optional<std::vector<uint8_t>> encoded_key =
+      cbor::Writer::Write(cbor::Value(std::move(map)));
+  if (!encoded_key) {
+    return absl::InternalError("failed to encode SymmetricKey");
+  }
+  return std::string(encoded_key->begin(), encoded_key->end());
+}
+
+absl::StatusOr<std::string> OkpCwt::BuildSigStructureForSigning(
+    absl::string_view aad) const {
+  std::vector<uint8_t> protected_header =
+      BuildProtectedHeader(algorithm, /*src_state=*/std::nullopt,
+                           /*dst_state=*/std::nullopt);
+  FCP_ASSIGN_OR_RETURN(std::vector<uint8_t> payload, BuildCwtPayload(*this));
+  return BuildSigStructure(std::move(protected_header), std::nullopt, aad,
+                           std::move(payload));
+}
+
+absl::StatusOr<std::string> OkpCwt::GetSigStructureForVerifying(
+    absl::string_view encoded, absl::string_view aad) {
+  std::vector<uint8_t> body_protected, payload;
+  std::optional<std::vector<uint8_t>> sign_protected;
+  FCP_ASSIGN_OR_RETURN(
+      std::tie(body_protected, sign_protected, payload, std::ignore),
+      ParseCoseSign(encoded));
+  return BuildSigStructure(std::move(body_protected), std::move(sign_protected),
+                           aad, std::move(payload));
+}
+
+absl::StatusOr<OkpCwt> OkpCwt::Decode(absl::string_view encoded) {
+  std::vector<uint8_t> body_protected, payload, signature;
+  std::optional<std::vector<uint8_t>> sign_protected;
+  FCP_ASSIGN_OR_RETURN(
+      std::tie(body_protected, sign_protected, payload, signature),
+      ParseCoseSign(encoded));
+  OkpCwt cwt;
+  // When decoding a COSE_Sign structure, information will be in the signer
+  // protected header instead of the body protected header.
+  FCP_RETURN_IF_ERROR(ParseProtectedHeader(
+      sign_protected ? *sign_protected : body_protected, &cwt.algorithm,
+      /*src_state=*/nullptr,
+      /*dst_state=*/nullptr));
+  FCP_RETURN_IF_ERROR(ParseCwtPayload(payload, cwt));
+  cwt.signature = std::string(signature.begin(), signature.end());
+  return cwt;
+}
+
+absl::StatusOr<std::string> OkpCwt::Encode() const {
+  // See RFC 9052 section 4.2 for the contents of the COSE_Sign1 structure.
+  cbor::Value::ArrayValue array;
+  array.emplace_back(BuildProtectedHeader(algorithm, /*src_state=*/std::nullopt,
+                                          /*dst_state=*/std::nullopt));
+  array.emplace_back(cbor::Value::MapValue());  // unprotected header
+  FCP_ASSIGN_OR_RETURN(std::vector<uint8_t> payload, BuildCwtPayload(*this));
+  array.emplace_back(std::move(payload));
+  array.emplace_back(signature, cbor::Value::Type::BYTE_STRING);
+  std::optional<std::vector<uint8_t>> encoded_array =
+      cbor::Writer::Write(cbor::Value(std::move(array)));
+  if (!encoded_array) {
+    return absl::InternalError("failed to encode OkpCwt");
+  }
+  return std::string(encoded_array->begin(), encoded_array->end());
+}
+
+absl::StatusOr<std::string> ReleaseToken::BuildEncStructureForEncrypting(
+    absl::string_view aad) const {
+  std::vector<uint8_t> protected_header =
+      BuildProtectedHeader(encryption_algorithm, src_state, dst_state);
+  return BuildEncStructure(std::move(protected_header), aad);
+}
+
+absl::StatusOr<std::string> ReleaseToken::GetEncStructureForDecrypting(
+    absl::string_view encoded, absl::string_view aad) {
+  std::vector<uint8_t> payload;
+  FCP_ASSIGN_OR_RETURN(std::tie(std::ignore, std::ignore, payload, std::ignore),
+                       ParseCoseSign(encoded));
+  std::vector<uint8_t> protected_header;
+  FCP_ASSIGN_OR_RETURN(std::tie(protected_header, std::ignore, std::ignore),
+                       ParseCoseEncrypt0(payload));
+  return BuildEncStructure(std::move(protected_header), aad);
+}
+
+absl::StatusOr<std::string> ReleaseToken::BuildSigStructureForSigning(
+    absl::string_view aad) const {
+  std::vector<uint8_t> protected_header =
+      BuildProtectedHeader(signing_algorithm, /*src_state=*/std::nullopt,
+                           /*dst_state=*/std::nullopt);
+  std::vector<uint8_t> payload = BuildReleaseTokenPayload(*this);
+  return BuildSigStructure(std::move(protected_header), std::nullopt, aad,
+                           std::move(payload));
+}
+
+absl::StatusOr<std::string> ReleaseToken::GetSigStructureForVerifying(
+    absl::string_view encoded, absl::string_view aad) {
+  // Like a CWT, a ReleaseToken is also a COSE_Sign1 object, so the
+  // Sig_structure is the same.
+  return OkpCwt::GetSigStructureForVerifying(encoded, aad);
+}
+
+absl::StatusOr<ReleaseToken> ReleaseToken::Decode(absl::string_view encoded) {
+  ReleaseToken token;
+
+  // Parse the outer COSE_Sign1 structure.
+  std::vector<uint8_t> protected_header_sign, signature;
+  std::optional<std::vector<uint8_t>> payload_sign;
+  FCP_ASSIGN_OR_RETURN(
+      std::tie(protected_header_sign, std::ignore, payload_sign, signature),
+      ParseCoseSign(encoded));
+
+  if (!payload_sign) {
+    return absl::InternalError(
+        "empty payload sign when decoding release token");
+  }
+
+  FCP_RETURN_IF_ERROR(
+      ParseProtectedHeader(protected_header_sign, &token.signing_algorithm,
+                           /*src_state=*/nullptr, /*dst_state=*/nullptr));
+  token.signature = std::string(signature.begin(), signature.end());
+
+  // Parse the inner COSE_Encrypt0 structure.
+  std::vector<uint8_t> protected_header_encrypt, encrypted_payload;
+  cbor::Value unprotected_header;
+  FCP_ASSIGN_OR_RETURN(
+      std::tie(protected_header_encrypt, unprotected_header, encrypted_payload),
+      ParseCoseEncrypt0(*payload_sign));
+
+  FCP_RETURN_IF_ERROR(ParseProtectedHeader(protected_header_encrypt,
+                                           &token.encryption_algorithm,
+                                           &token.src_state, &token.dst_state));
+  token.encrypted_payload =
+      std::string(encrypted_payload.begin(), encrypted_payload.end());
+
+  // Process the COSE_Encrypt0 unprotected header.
+  if (unprotected_header.is_map()) {
+    for (const auto& [key, value] : unprotected_header.GetMap()) {
+      if (!key.is_integer()) {
+        continue;  // Ignore other key types.
+      }
+      switch (key.GetInteger()) {
+        case CoseHeaderParameter::kHdrKid:
+          if (!value.is_bytestring()) {
+            return absl::InvalidArgumentError(
+                absl::StrCat("unsupported kid type ", value.type()));
+          }
+          token.encryption_key_id = value.GetBytestringAsString();
+          break;
+
+        case CoseHeaderParameter::kEncapsulatedKey:
+          if (!value.is_bytestring()) {
+            return absl::InvalidArgumentError(absl::StrCat(
+                "unsupported encapsulated key type ", value.type()));
+          }
+          token.encapped_key = value.GetBytestringAsString();
+          break;
+
+        default:
+          break;
+      }
+    }
+  }
+  return token;
+}
+
+absl::StatusOr<std::string> ReleaseToken::Encode() const {
+  // See RFC 9052 section 4.2 for the contents of the COSE_Sign1 structure.
+  cbor::Value::ArrayValue array;
+  array.emplace_back(BuildProtectedHeader(signing_algorithm,
+                                          /*src_state=*/std::nullopt,
+                                          /*dst_state=*/std::nullopt));
+  array.emplace_back(cbor::Value::MapValue());  // unprotected header
+  array.emplace_back(BuildReleaseTokenPayload(*this));
+  array.emplace_back(signature, cbor::Value::Type::BYTE_STRING);
+  std::optional<std::vector<uint8_t>> encoded_array =
+      cbor::Writer::Write(cbor::Value(std::move(array)));
+  if (!encoded_array) {
+    return absl::InternalError("failed to build release token");
+  }
+  return std::string(encoded_array->begin(), encoded_array->end());
+}
+
+}  // namespace fcp::confidential_compute
\ No newline at end of file
diff --git a/third_party/federated_compute/chromium/fcp/confidentialcompute/cose_test.cc b/third_party/federated_compute/chromium/fcp/confidentialcompute/cose_test.cc
new file mode 100644
index 0000000..c4a796b
--- /dev/null
+++ b/third_party/federated_compute/chromium/fcp/confidentialcompute/cose_test.cc
@@ -0,0 +1,724 @@
+// Copyright 2024 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "fcp/confidentialcompute/cose.h"
+
+#include <optional>
+#include <string>
+
+#include "absl/status/status.h"
+#include "absl/status/statusor.h"
+#include "absl/strings/escaping.h"
+#include "absl/strings/string_view.h"
+#include "absl/time/time.h"
+#include "fcp/testing/testing.h"
+#include "gmock/gmock.h"
+#include "google/protobuf/struct.pb.h"
+#include "gtest/gtest.h"
+
+namespace fcp::confidential_compute {
+namespace {
+
+using ::testing::ElementsAre;
+using ::testing::IsEmpty;
+
+MATCHER_P(IsOkAndHolds, matcher, "") {
+  return arg.ok() &&
+         testing::ExplainMatchResult(matcher, arg.value(), result_listener);
+}
+
+TEST(OkpKeyTest, EncodeEmpty) {
+  EXPECT_THAT(OkpKey().Encode(),
+              IsOkAndHolds("\xa1"      // map with 1 item:
+                           "\x01\x01"  // key type (1): OKP (1)
+                           ));
+}
+
+TEST(OkpKeyTest, EncodeFull) {
+  EXPECT_THAT((OkpKey{
+                   .key_id = "key-id",
+                   .algorithm = 7,
+                   .key_ops = {1, 2},
+                   .curve = 45,
+                   .x = "x-value",
+                   .d = "d-value",
+               })
+                  .Encode(),
+              IsOkAndHolds("\xa7"                // map with 7 items:
+                           "\x01\x01"            // key type (1): OKP (1)
+                           "\x02\x46key-id"      // key id (2): b"key-id"
+                           "\x03\x07"            // algorithm (3): 7
+                           "\x04\x82\x01\x02"    // key_ops (4): [1, 2]
+                           "\x20\x18\x2d"        // curve (-1): 45
+                           "\x21\x47x-value"     // x (-2): b"x-value"
+                           "\x23\x47\x64-value"  // d (-4): b"d-value"
+                           ));
+}
+
+TEST(OkpKeyTest, DecodeEmpty) {
+  absl::StatusOr<OkpKey> key = OkpKey::Decode("\xa1\x01\x01");
+  ASSERT_OK(key);
+  EXPECT_EQ(key->key_id, "");
+  EXPECT_EQ(key->algorithm, std::nullopt);
+  EXPECT_THAT(key->key_ops, IsEmpty());
+  EXPECT_EQ(key->curve, std::nullopt);
+  EXPECT_EQ(key->x, "");
+  EXPECT_EQ(key->d, "");
+}
+
+TEST(OkpKeyTest, DecodeFull) {
+  absl::StatusOr<OkpKey> key = OkpKey::Decode(
+      "\xa7\x01\x01\x02\x46key-id\x03\x07\x04\x82\x01\x02\x20\x18\x2d\x21\x47x-"
+      "value\x23\x47\x64-value");
+  ASSERT_OK(key);
+  EXPECT_EQ(key->key_id, "key-id");
+  EXPECT_EQ(key->algorithm, 7);
+  EXPECT_THAT(key->key_ops, ElementsAre(1, 2));
+  EXPECT_EQ(key->curve, 45);
+  EXPECT_EQ(key->x, "x-value");
+  EXPECT_EQ(key->d, "d-value");
+}
+
+TEST(OkpKeyTest, DecodeInvalid) {
+  EXPECT_THAT(OkpKey::Decode(""), IsCode(absl::StatusCode::kInvalidArgument));
+  EXPECT_THAT(OkpKey::Decode("\xa5"),  // map with 5 items
+              IsCode(absl::StatusCode::kInvalidArgument));
+  EXPECT_THAT(OkpKey::Decode("\xa0 extra"),  // map with 0 items + " extra"
+              IsCode(absl::StatusCode::kInvalidArgument));
+}
+
+TEST(SymmetricKeyTest, EncodeEmpty) {
+  EXPECT_THAT(SymmetricKey().Encode(),
+              IsOkAndHolds("\xa1"      // map with 1 item:
+                           "\x01\x04"  // key type (1): Symmetric (4)
+                           ));
+}
+
+TEST(SymmetricKeyTest, EncodeFull) {
+  EXPECT_THAT((SymmetricKey{
+                   .algorithm = 7,
+                   .key_ops = {1, 2},
+                   .k = "secret",
+               })
+                  .Encode(),
+              IsOkAndHolds("\xa4"              // map with 4 items:
+                           "\x01\x04"          // key type (1): Symmetric (4)
+                           "\x03\x07"          // algorithm (3): 7
+                           "\x04\x82\x01\x02"  // key_ops (4): [1, 2]
+                           "\x20\x46secret"    // k (-1): b"secret"
+                           ));
+}
+
+TEST(SymmetricKeyTest, DecodeEmpty) {
+  absl::StatusOr<SymmetricKey> key = SymmetricKey::Decode("\xa1\x01\x04");
+  ASSERT_OK(key);
+  EXPECT_EQ(key->algorithm, std::nullopt);
+  EXPECT_THAT(key->key_ops, IsEmpty());
+  EXPECT_EQ(key->k, "");
+}
+
+TEST(SymmetricKeyTest, DecodeFull) {
+  absl::StatusOr<SymmetricKey> key = SymmetricKey::Decode(
+      "\xa4\x01\x04\x03\x07\x04\x82\x01\x02\x20\x46secret");
+  ASSERT_OK(key);
+  EXPECT_EQ(key->algorithm, 7);
+  EXPECT_THAT(key->key_ops, ElementsAre(1, 2));
+  EXPECT_EQ(key->k, "secret");
+}
+
+TEST(SymmetricKeyTest, DecodeInvalid) {
+  EXPECT_THAT(SymmetricKey::Decode(""),
+              IsCode(absl::StatusCode::kInvalidArgument));
+  EXPECT_THAT(SymmetricKey::Decode("\xa3"),  // map with 5 items
+              IsCode(absl::StatusCode::kInvalidArgument));
+  EXPECT_THAT(SymmetricKey::Decode("\xa0 xtra"),  // map with 0 items + " xtra"
+              IsCode(absl::StatusCode::kInvalidArgument));
+}
+
+TEST(OkpCwtTest, BuildSigStructureForSigningEmpty) {
+  EXPECT_THAT(OkpCwt().BuildSigStructureForSigning(""),
+              IsOkAndHolds("\x84"            // array with 4 items:
+                           "\x6aSignature1"  // "Signature1"
+                           "\x41\xa0"        // protected headers
+                           "\x40"            // aad: b""
+                           "\x41\xa0"        // payload (empty claims map)
+                           ));
+}
+
+TEST(OkpCwtTest, BuildSigStructureForSigningFull) {
+  EXPECT_THAT(
+      (OkpCwt{
+           .algorithm = 7,
+           .issued_at = absl::FromUnixSeconds(1000),
+           .expiration_time = absl::FromUnixSeconds(2000),
+           .public_key = OkpKey(),
+           .signature = "signature",
+       })
+          .BuildSigStructureForSigning("aad"),
+      IsOkAndHolds(absl::string_view(
+          "\x84"            // array with 4 items:
+          "\x6aSignature1"  // "Signature1"
+          "\x43\xa1"        // protected headers: bstr map w/ 1 item
+          "\x01\x07"        // alg: 7
+          "\x43"            // associated data: 3-byte string
+          "aad"
+          "\x52\xa3"          // payload: bstr map w/ 3 items (claims)
+          "\x04\x19\x07\xd0"  // expiration time (4) = 2000
+          "\x06\x19\x03\xe8"  // issued at (6) = 1000
+          "\x3a\x00\x01\x00\x00\x43\xa1\x01\x01",  // public key (-65537)
+                                                   // = empty OkpKey
+          39)));
+}
+
+TEST(OkpCwtTest, GetSigStructureForVerifyingMatchesStructureForSigning) {
+  OkpCwt cwt{
+      .algorithm = 7,
+      .issued_at = absl::FromUnixSeconds(1000),
+      .expiration_time = absl::FromUnixSeconds(2000),
+      .public_key = OkpKey(),
+      .signature = "signature",
+  };
+  absl::StatusOr<std::string> expected = cwt.BuildSigStructureForSigning("aad");
+  ASSERT_OK(expected);
+  absl::StatusOr<std::string> encoded = cwt.Encode();
+  ASSERT_OK(encoded);
+
+  absl::StatusOr<std::string> sig_structure =
+      OkpCwt::GetSigStructureForVerifying(*encoded, "aad");
+  ASSERT_OK(sig_structure);
+  EXPECT_EQ(*sig_structure, *expected);
+}
+
+TEST(OkpCwtTest, GetSigStructureForVerifyingInvalid) {
+  EXPECT_THAT(OkpCwt::GetSigStructureForVerifying("", ""),
+              IsCode(absl::StatusCode::kInvalidArgument));
+  EXPECT_THAT(OkpCwt::GetSigStructureForVerifying("\xa3",  // map with 3 items
+                                                  ""),
+              IsCode(absl::StatusCode::kInvalidArgument));
+  EXPECT_THAT(OkpCwt::GetSigStructureForVerifying(
+                  "\xa0 extra",  // map with 0 items + " extra"
+                  ""),
+              IsCode(absl::StatusCode::kInvalidArgument));
+
+  // Even if the CBOR is valid, the top-level structure must be a 4-element
+  // array.
+  EXPECT_THAT(
+      OkpCwt::GetSigStructureForVerifying("\xa0", ""),  // map with 0 items
+      IsCode(absl::StatusCode::kInvalidArgument));
+  EXPECT_THAT(
+      OkpCwt::GetSigStructureForVerifying("\x80", ""),  // array with 0 items
+      IsCode(absl::StatusCode::kInvalidArgument));
+  EXPECT_THAT(OkpCwt::GetSigStructureForVerifying(
+                  "\x83\x41\xa0\xa0\x41\xa0",  // array with 3 items
+                  ""),
+              IsCode(absl::StatusCode::kInvalidArgument));
+  EXPECT_THAT(OkpCwt::GetSigStructureForVerifying(
+                  "\x85\x41\xa0\xa0\x41\xa0\x40\x40",  // array with 5 items
+                  ""),
+              IsCode(absl::StatusCode::kInvalidArgument));
+
+  // The map entry types must be bstr, *, bstr, *.
+  // "\x84\x41\xa0\xa0\x41\xa0\x40" is valid.
+  EXPECT_THAT(OkpCwt::GetSigStructureForVerifying(
+                  "\x84\xa0\xa0\x41\xa0\x40",  // 1st not bstr
+                  ""),
+              IsCode(absl::StatusCode::kInvalidArgument));
+  EXPECT_THAT(OkpCwt::GetSigStructureForVerifying(
+                  "\x84\x41\xa0\xa0\xa0\x40",  // 3rd not bstr
+                  ""),
+              IsCode(absl::StatusCode::kInvalidArgument));
+}
+
+TEST(OkpCwtTest, GetSigStructureForVerifyingReferenceExample) {
+  // From RFC 8392 Section A.3, without the leading COSE_Sign1 tag "d2":
+  std::string encoded = "";
+  EXPECT_TRUE(absl::HexStringToBytes(
+      "8443a10126a104524173796d6d657472696345434453413235365850a701756"
+      "36f61703a2f2f61732e6578616d706c652e636f6d02656572696b77037818636f"
+      "61703a2f2f6c696768742e6578616d706c652e636f6d041a5612aeb0051a5610d"
+      "9f0061a5610d9f007420b7158405427c1ff28d23fbad1f29c4c7c6a555e601d6f"
+      "a29f9179bc3d7438bacaca5acd08c8d4d4f96131680c429a01f85951ecee743a5"
+      "2b9b63632c57209120e1c9e30",
+      &encoded));
+  absl::StatusOr<std::string> sig_structure =
+      OkpCwt::GetSigStructureForVerifying(encoded, "aad");
+  ASSERT_OK(sig_structure);
+  EXPECT_EQ(
+      absl::BytesToHexString(*sig_structure),
+      "846a5369676e61747572653143a10126436161645850a70175636f61703a2f2f61732e65"
+      "78616d706c652e636f6d02656572696b77037818636f61703a2f2f6c696768742e657861"
+      "6d706c652e636f6d041a5612aeb0051a5610d9f0061a5610d9f007420b71");
+}
+
+TEST(OkpCwtTest, EncodeEmpty) {
+  EXPECT_THAT(
+      OkpCwt().Encode(),
+      IsOkAndHolds("\x84"      // array with 4 items:
+                   "\x41\xa0"  // bstr containing empty map (protected headers)
+                   "\xa0"      // empty map (unprotected headers)
+                   "\x41\xa0"  // bstr containing empty map (claims)
+                   "\x40"      // b"" (signature)
+                   ));
+}
+
+TEST(OkpCwtTest, EncodeFull) {
+  google::protobuf::Struct config_properties;
+  (*config_properties.mutable_fields())["x"].set_bool_value(true);
+
+  EXPECT_THAT(
+      (OkpCwt{
+           .algorithm = 7,
+           .issued_at = absl::FromUnixSeconds(1000),
+           .expiration_time = absl::FromUnixSeconds(2000),
+           .public_key = OkpKey(),
+           .config_properties = config_properties.SerializeAsString(),
+           .access_policy_sha256 = "hash",
+           .signature = "signature",
+       })
+          .Encode(),
+      IsOkAndHolds(absl::string_view(
+          "\x84"              // array with 4 items:
+          "\x43\xa1\x01\x07"  // bstr containing { alg: 7 } (protected headers)
+          "\xa0"              // empty map (unprotected headers)
+          "\x58\x2b\xa5"      // bstr containing a map with 5 items: (claims)
+          "\x04\x19\x07\xd0"  // expiration time (4) = 2000
+          "\x06\x19\x03\xe8"  // issued at (6) = 1000
+          "\x3a\x00\x01\x00\x00\x43\xa1\x01\x01"  // public key (-65537)
+                                                  // = empty OkpKey
+          "\x3a\x00\x01\x00\x01\x49"              // config (-65538) = {
+          "\x0a\x07"                              // fields (1) {
+          "\x0a\x01x"                             //   key (1): "x"
+          "\x12\x02"                              //   value (2): {
+          "\x20\x01"                              //     bool_value (4): true
+                                                  //   }
+                                                  // }
+          "\x3a\x00\x01\x00\x06\x44hash"  // access_policy_sha256 (-65543) =
+                                          // b"hash"
+          "\x49signature",
+          61)));
+}
+
+TEST(OkpCwtTest, DecodeEmpty) {
+  absl::StatusOr<OkpCwt> key = OkpCwt::Decode("\x84\x41\xa0\xa0\x41\xa0\x40");
+  ASSERT_OK(key);
+  EXPECT_EQ(key->issued_at, std::nullopt);
+  EXPECT_EQ(key->expiration_time, std::nullopt);
+  EXPECT_EQ(key->public_key, std::nullopt);
+  EXPECT_EQ(key->config_properties, "");
+  EXPECT_EQ(key->access_policy_sha256, "");
+  EXPECT_EQ(key->signature, "");
+}
+
+TEST(OkpCwtTest, DecodeFull) {
+  absl::StatusOr<OkpCwt> cwt = OkpCwt::Decode(absl::string_view(
+      "\x84\x43\xa1\x01\x07\xa0\x58\x2b\xa5\x04\x19\x07\xd0\x06\x19\x03\xe8\x3a"
+      "\x00\x01\x00\x00\x43\xa1\x01\x01\x3a\x00\x01\x00\x01\x49\x0a\x07\x0a\x01"
+      "x\x12\x02\x20\x01\x3a\x00\x01\x00\x06\x44hash\x49signature",
+      61));
+  ASSERT_OK(cwt);
+  EXPECT_EQ(cwt->issued_at, absl::FromUnixSeconds(1000));
+  EXPECT_EQ(cwt->expiration_time, absl::FromUnixSeconds(2000));
+  EXPECT_TRUE(cwt->public_key.has_value());
+  EXPECT_EQ(cwt->access_policy_sha256, "hash");
+  EXPECT_EQ(cwt->signature, "signature");
+
+  google::protobuf::Struct config_properties;
+  ASSERT_TRUE(config_properties.ParseFromString(cwt->config_properties));
+
+  // Because `google/protobuf/util/message_differencer.h` cannot be used with
+  // PROTOBUF_EXPORT in Chromium, we cannot use EqualsProto. As such, we
+  // manually match the expected fields.
+
+  // EXPECT_THAT(config_properties, EqualsProto(R"pb(
+  //               fields {
+  //                 key: "x"
+  //                 value { bool_value: true }
+  //               }
+  //             )pb"));
+
+  const auto& fields = config_properties.fields();
+  ASSERT_EQ(fields.size(), 1u);
+
+  const auto it = fields.find("x");
+  ASSERT_NE(it, fields.end());
+
+  const auto& value = it->second;
+  ASSERT_EQ(value.kind_case(), google::protobuf::Value::kBoolValue);
+  ASSERT_TRUE(value.bool_value());
+}
+
+TEST(OkpCwtTest, DecodeInvalid) {
+  EXPECT_THAT(OkpCwt::Decode(""), IsCode(absl::StatusCode::kInvalidArgument));
+  EXPECT_THAT(OkpCwt::Decode("\xa3"),  // map with 5 items
+              IsCode(absl::StatusCode::kInvalidArgument));
+  EXPECT_THAT(OkpCwt::Decode("\xa0 extra"),  // map with 0 items + " extra"
+              IsCode(absl::StatusCode::kInvalidArgument));
+
+  // Even if the CBOR is valid, the top-level structure must be a 4-element
+  // array.
+  EXPECT_THAT(OkpCwt::Decode("\xa0"),  // map with 0 items
+              IsCode(absl::StatusCode::kInvalidArgument));
+  EXPECT_THAT(OkpCwt::Decode("\x80"),  // array with 0 items
+              IsCode(absl::StatusCode::kInvalidArgument));
+  EXPECT_THAT(OkpCwt::Decode("\x83\x41\xa0\xa0\x41\xa0"),  // array with 3 items
+              IsCode(absl::StatusCode::kInvalidArgument));
+  EXPECT_THAT(
+      OkpCwt::Decode("\x85\x41\xa0\xa0\x41\xa0\x40\x40"),  // array with 5 items
+      IsCode(absl::StatusCode::kInvalidArgument));
+
+  // The map entry types must be bstr, map, bstr, bstr.
+  // "\x84\x41\xa0\xa0\x41\xa0\x40" is valid.
+  EXPECT_THAT(OkpCwt::Decode("\x84\xa0\xa0\x41\xa0\x40"),  // 1st not bstr
+              IsCode(absl::StatusCode::kInvalidArgument));
+  EXPECT_THAT(OkpCwt::Decode("\x84\x41\xa0\x40\x41\xa0\x40"),  // 2nd not map
+              IsCode(absl::StatusCode::kInvalidArgument));
+  EXPECT_THAT(OkpCwt::Decode("\x84\x41\xa0\xa0\xa0\x40"),  // 3rd not bstr
+              IsCode(absl::StatusCode::kInvalidArgument));
+  EXPECT_THAT(OkpCwt::Decode("\x84\x41\xa0\xa0\x41\xa0\xa0"),  // 4th not bstr
+              IsCode(absl::StatusCode::kInvalidArgument));
+}
+
+TEST(OkpCwtTest, DecodeCoseSign1ReferenceExample) {
+  // From RFC 8392 Section A.3, without the leading COSE_Sign1 tag "d2":
+  std::string encoded = "";
+  EXPECT_TRUE(absl::HexStringToBytes(
+      "8443a10126a104524173796d6d657472696345434453413235365850a701756"
+      "36f61703a2f2f61732e6578616d706c652e636f6d02656572696b77037818636f"
+      "61703a2f2f6c696768742e6578616d706c652e636f6d041a5612aeb0051a5610d"
+      "9f0061a5610d9f007420b7158405427c1ff28d23fbad1f29c4c7c6a555e601d6f"
+      "a29f9179bc3d7438bacaca5acd08c8d4d4f96131680c429a01f85951ecee743a5"
+      "2b9b63632c57209120e1c9e30",
+      &encoded));
+  absl::StatusOr<OkpCwt> cwt = OkpCwt::Decode(encoded);
+  ASSERT_OK(cwt);
+  EXPECT_EQ(cwt->issued_at, absl::FromUnixSeconds(1443944944));
+  EXPECT_EQ(cwt->expiration_time, absl::FromUnixSeconds(1444064944));
+  EXPECT_FALSE(cwt->public_key.has_value());
+  std::string expected_signature = "";
+  EXPECT_TRUE(absl::HexStringToBytes(
+      "5427c1ff28d23fbad1f29c4c7c6a555e601d6fa29f9179bc3d7438bacaca5acd08c8"
+      "d4d4f96131680c429a01f85951ecee743a52b9b63632c57209120e1c9e30",
+      &expected_signature));
+  EXPECT_EQ(cwt->signature, expected_signature);
+}
+
+TEST(OkpCwtTest, VerifyAndDecodeCoseSign) {
+  // The encoded CWT and signature structure were generated using the Rust coset
+  // crate.
+  std::string encoded = "";
+  EXPECT_TRUE(absl::HexStringToBytes(
+      "8440a05846a30419162e061904d23a000100005836a5010102466b65792d6964033a0001"
+      "000020042158204850e21e94eb470337fd46a401f4c8b46150195732fb47d53fa0533f59"
+      "4cb342828343a10126a058208dfb544c010408b5c24eeaf67e2ff89b98dcab365d50b244"
+      "7d569c1561540bc28344a101382ea058206b33008400add41a3b82ef4bf4bb85fcf8d2d5"
+      "7d2f453bcddb277cf3fa3880d6",
+      &encoded));
+
+  absl::StatusOr<std::string> sig_structure =
+      OkpCwt::GetSigStructureForVerifying(encoded, "");
+  ASSERT_OK(sig_structure);
+  EXPECT_EQ(
+      absl::BytesToHexString(*sig_structure),
+      "85695369676e61747572654043a10126405846a30419162e061904d23a000100005836a5"
+      "010102466b65792d6964033a0001000020042158204850e21e94eb470337fd46a401f4c8"
+      "b46150195732fb47d53fa0533f594cb342");
+
+  absl::StatusOr<OkpCwt> cwt = OkpCwt::Decode(encoded);
+  ASSERT_OK(cwt);
+  EXPECT_EQ(cwt->algorithm, -7);
+  EXPECT_EQ(cwt->issued_at, absl::FromUnixSeconds(1234));
+  EXPECT_EQ(cwt->expiration_time, absl::FromUnixSeconds(5678));
+  ASSERT_TRUE(cwt->public_key.has_value());
+  EXPECT_EQ(cwt->public_key->key_id, "key-id");
+  EXPECT_EQ(cwt->public_key->algorithm, -65537);
+  EXPECT_EQ(cwt->public_key->curve, 4);
+  EXPECT_NE(cwt->public_key->x, "");
+}
+TEST(ReleaseTokenTest, EncodeEmpty) {
+  EXPECT_THAT(
+      ReleaseToken().Encode(),
+      IsOkAndHolds(
+          "\x84"      // array with 4 items:
+          "\x41\xa0"  // bstr containing empty map (protected headers)
+          "\xa0"      // empty map (unprotected headers)
+          "\x45\x83"  // bstr containing an array with 3 items: (COSE_Encrypt0)
+          "\x41\xa0"  // bstr containing empty map (protected headers)
+          "\xa0"      // empty map (unprotected headers)
+          "\x40"      // b"" (encrypted payload)
+          "\x40"      // b"" (signature)
+          ));
+}
+
+TEST(ReleaseTokenTest, EncodeFull) {
+  EXPECT_THAT(
+      (ReleaseToken{
+           .signing_algorithm = 1,
+           .encryption_algorithm = 2,
+           .encryption_key_id = "key-id",
+           .src_state = "old-state",
+           .dst_state = "new-state",
+           .encrypted_payload = "payload",
+           .encapped_key = "key",
+           .signature = "signature",
+       })
+          .Encode(),
+      IsOkAndHolds(absl::string_view(
+          "\x84"              // array with 4 items:
+          "\x43\xa1\x01\x01"  // bstr containing { alg: 1 } (protected headers)
+          "\xa0"              // empty map (unprotected headers)
+          "\x58\x3e\x83"      // bstr containing an array with 3 items:
+                              // (COSE_Encrypt0)
+          "\x58\x21\xa3"      // bstr containing a map with 3 items:
+                              // (protected headers)
+          "\x01\x02"          // alg: 2
+          "\x3a\x00\x01\x00\x01\x49old-state"  // -65538: b"old-state"
+          "\x3a\x00\x01\x00\x02\x49new-state"  // -65539: b"new-state"
+          "\xa2"            // map with 2 items: (unprotected headers)
+          "\x04\x46key-id"  // key_id: b"key-id"
+          "\x3a\x00\x01\x00\x00\x43key"  // encapped_key: b"key"
+          "\x47payload"                  // b"encrypted-payload" (payload)
+          "\x49signature",               // b"signature" (signature)
+          80)));
+}
+
+TEST(ReleaseTokenTest, EncodeNullSrcState) {
+  EXPECT_THAT(
+      (ReleaseToken{
+           .src_state = std::optional<std::string>(std::nullopt),
+       })
+          .Encode(),
+      IsOkAndHolds(absl::string_view(
+          "\x84"      // array with 4 items:
+          "\x41\xa0"  // bstr containing empty map (protected headers)
+          "\xa0"      // empty map (unprotected headers)
+          "\x4b\x83"  // bstr containing an array with 3 items:
+                      // (COSE_Encrypt0)
+          "\x47\xa1\x3a\x00\x01\x00\x01\xf6"  // bstr containing { src_state:
+                                              // null } (protected headers)
+          "\xa0"                              // empty map (unprotected headers)
+          "\x40"                              // b"" (payload)
+          "\x40",                             // b"" (signature)
+          17)));
+}
+
+TEST(ReleaseTokenTest, DecodeEmpty) {
+  absl::StatusOr<ReleaseToken> release_token =
+      ReleaseToken::Decode("\x84\x41\xa0\xa0\x45\x83\x41\xa0\xa0\x40\x40");
+  ASSERT_OK(release_token);
+  EXPECT_EQ(release_token->signing_algorithm, std::nullopt);
+  EXPECT_EQ(release_token->encryption_algorithm, std::nullopt);
+  EXPECT_EQ(release_token->encryption_key_id, std::nullopt);
+  EXPECT_EQ(release_token->src_state, std::nullopt);
+  EXPECT_EQ(release_token->dst_state, std::nullopt);
+  EXPECT_EQ(release_token->encrypted_payload, "");
+  EXPECT_EQ(release_token->encapped_key, std::nullopt);
+  EXPECT_EQ(release_token->signature, "");
+}
+
+TEST(ReleaseTokenTest, DecodeFull) {
+  absl::StatusOr<ReleaseToken> release_token =
+      ReleaseToken::Decode(absl::string_view(
+          "\x84\x43\xa1\x01\x01\xa0\x58\x3e\x83\x58\x21\xa3\x01\x02\x3a\x00\x01"
+          "\x00\x01\x49old-state\x3a\x00\x01\x00\x02\x49new-state\xa2\x04\x46ke"
+          "y-id\x3a\x00\x01\x00\x00\x43key\x47payload\x49signature",
+          80));
+  ASSERT_OK(release_token);
+  EXPECT_EQ(release_token->signing_algorithm, 1);
+  EXPECT_EQ(release_token->encryption_algorithm, 2);
+  EXPECT_EQ(release_token->encryption_key_id, "key-id");
+  EXPECT_EQ(release_token->src_state, "old-state");
+  EXPECT_EQ(release_token->dst_state, "new-state");
+  EXPECT_EQ(release_token->encrypted_payload, "payload");
+  EXPECT_EQ(release_token->encapped_key, "key");
+  EXPECT_EQ(release_token->signature, "signature");
+}
+
+TEST(ReleaseTokenTest, DecodeNullSrcState) {
+  absl::StatusOr<ReleaseToken> release_token = ReleaseToken::Decode(
+      absl::string_view("\x84\x41\xa0\xa0\x4b\x83\x47\xa1\x3a\x00\x01\x00\x01"
+                        "\xf6\xa0\x40\x40",
+                        17));
+  ASSERT_OK(release_token);
+  ASSERT_TRUE(release_token->src_state.has_value());
+  EXPECT_EQ(*release_token->src_state, std::nullopt);
+}
+
+TEST(ReleaseTokenTest, DecodeInvalidCoseSign1) {
+  EXPECT_THAT(ReleaseToken::Decode(""),
+              IsCode(absl::StatusCode::kInvalidArgument));
+  EXPECT_THAT(ReleaseToken::Decode("\xa3"),  // map with 3 items
+              IsCode(absl::StatusCode::kInvalidArgument));
+  EXPECT_THAT(
+      ReleaseToken::Decode("\xa0 extra"),  // map with 0 items + " extra"
+      IsCode(absl::StatusCode::kInvalidArgument));
+
+  // Even if the CBOR is valid, the top-level structure must be a 4-element
+  // array.
+  EXPECT_THAT(ReleaseToken::Decode("\xa0"),  // map with 0 items
+              IsCode(absl::StatusCode::kInvalidArgument));
+  EXPECT_THAT(ReleaseToken::Decode("\x80"),  // array with 0 items
+              IsCode(absl::StatusCode::kInvalidArgument));
+  EXPECT_THAT(
+      ReleaseToken::Decode("\x83\x41\xa0\xa0\x41\xa0"),  // array with 3 items
+      IsCode(absl::StatusCode::kInvalidArgument));
+  EXPECT_THAT(ReleaseToken::Decode(  // array with 5 items
+                  "\x85\x41\xa0\xa0\x41\xa0\x40\x40"),
+              IsCode(absl::StatusCode::kInvalidArgument));
+
+  // The COSE_Sign1 array entry types must be bstr, map, bstr, bstr.
+  // "\x84\x41\xa0\xa0\x45\x83\x41\xa0\xa0\x40\x40" is valid.
+  EXPECT_THAT(ReleaseToken::Decode(  // 1st not bstr
+                  "\x84\xa0\xa0\x45\x83\x41\xa0\xa0\x40\x40"),
+              IsCode(absl::StatusCode::kInvalidArgument));
+  EXPECT_THAT(ReleaseToken::Decode(  // 2nd not map
+                  "\x84\x41\xa0\x40\x45\x83\x41\xa0\xa0\x40\x40"),
+              IsCode(absl::StatusCode::kInvalidArgument));
+  EXPECT_THAT(ReleaseToken::Decode("\x84\x41\xa0\xa0\xa0\x40"),  // 3rd not bstr
+              IsCode(absl::StatusCode::kInvalidArgument));
+  EXPECT_THAT(ReleaseToken::Decode(  // 4th not bstr
+                  "\x84\x41\xa0\xa0\x45\x83\x41\xa0\xa0\x40\xa0"),
+              IsCode(absl::StatusCode::kInvalidArgument));
+}
+
+TEST(ReleaseTokenTest, DecodeInvalidCoseEncrypt0) {
+  // The serialized COSE_Encrypt0 is wrapped in a COSE_Sign1 structure:
+  // "\x84\x41\xa0\xa0\x4N<COSE_Encrypt0>\x40" is valid.
+
+  EXPECT_THAT(ReleaseToken::Decode("\x84\x41\xa0\xa0\x40\x40"),  // empty string
+              IsCode(absl::StatusCode::kInvalidArgument));
+  EXPECT_THAT(
+      ReleaseToken::Decode("\x84\x41\xa0\xa0\x41\xa3\x40"),  // map with 3 items
+      IsCode(absl::StatusCode::kInvalidArgument));
+  EXPECT_THAT(ReleaseToken::Decode(  // map with 0 items + " extra"
+                  "\x84\x41\xa0\xa0\x47\xa0 extra\x40"),
+              IsCode(absl::StatusCode::kInvalidArgument));
+
+  // Even if the CBOR is valid, the top-level structure must be a 4-element
+  // array.
+  EXPECT_THAT(
+      ReleaseToken::Decode("\x84\x41\xa0\xa0\x41\xa0\x40"),  // map with 0 items
+      IsCode(absl::StatusCode::kInvalidArgument));
+  EXPECT_THAT(ReleaseToken::Decode(  // array with 0 items
+                  "\x84\x41\xa0\xa0\x41\x80\x40"),
+              IsCode(absl::StatusCode::kInvalidArgument));
+  EXPECT_THAT(ReleaseToken::Decode(  // array with 2 items
+                  "\x84\x41\xa0\xa0\x44\x82\x41\xa0\xa0\x40"),
+              IsCode(absl::StatusCode::kInvalidArgument));
+  EXPECT_THAT(ReleaseToken::Decode(  // array with 4 items
+                  "\x84\x41\xa0\xa0\x46\x84\x41\xa0\xa0\x40\x40\x40"),
+              IsCode(absl::StatusCode::kInvalidArgument));
+
+  // The COSE_Encrypt0 array entry types must be bstr, map, bstr.
+  // "\x84\x41\xa0\xa0\x45\x83\x41\xa0\xa0\x40\x40" is valid.
+  EXPECT_THAT(ReleaseToken::Decode(  // 1st not bstr
+                  "\x84\x41\xa0\xa0\x44\x83\xa0\xa0\x40\x40"),
+              IsCode(absl::StatusCode::kInvalidArgument));
+  EXPECT_THAT(ReleaseToken::Decode(  // 2nd not map
+                  "\x84\x41\xa0\xa0\x45\x83\x41\xa0\x40\x40\x40"),
+              IsCode(absl::StatusCode::kInvalidArgument));
+  EXPECT_THAT(ReleaseToken::Decode(  // 3rd not bstr
+                  "\x84\x41\xa0\xa0\x45\x83\x41\xa0\xa0\xa0\x40"),
+              IsCode(absl::StatusCode::kInvalidArgument));
+}
+
+TEST(ReleaseTokenTest, BuildEncStructureForEncrypting) {
+  EXPECT_THAT((ReleaseToken{
+                   .signing_algorithm = 1,
+                   .encryption_algorithm = 2,
+                   .encryption_key_id = "key-id",
+                   .src_state = "old-state",
+                   .dst_state = "new-state",
+                   .encrypted_payload = "payload",
+                   .encapped_key = "key",
+                   .signature = "signature",
+               })
+                  .BuildEncStructureForEncrypting("aad"),
+              IsOkAndHolds(absl::string_view(
+                  "\x83"             // array with 3 items:
+                  "\x68\x45ncrypt0"  // "Encrypt0"
+                  "\x58\x21\xa3"     // bstr containing a map with 3 items:
+                  "\x01\x02"         // alg: 2
+                  "\x3a\x00\x01\x00\x01\x49old-state"  // -65538: b"old-state"
+                  "\x3a\x00\x01\x00\x02\x49new-state"  // -65539: b"new-state"
+                  "\x43\x61\x61\x64",                  // b"aad"
+                  49)));
+}
+
+TEST(ReleaseTokenTest, GetEncStructureForDecrypting) {
+  EXPECT_THAT(ReleaseToken::GetEncStructureForDecrypting(
+                  absl::string_view(
+                      "\x84\x43\xa1\x01\x01\xa0\x58\x3e\x83\x58\x21\xa3\x01\x02"
+                      "\x3a\x00\x01\x00\x01\x49old-state\x3a\x00\x01\x00\x02"
+                      "\x49new-state\xa2\x04\x46key-id\x3a\x00\x01\x00\x00\x43"
+                      "key\x47payload\x49signature",
+                      80),
+                  "aad"),
+              IsOkAndHolds(absl::string_view(
+                  "\x83"             // array with 3 items:
+                  "\x68\x45ncrypt0"  // "Encrypt0"
+                  "\x58\x21\xa3"     // bstr containing a map with 3 items:
+                  "\x01\x02"         // alg: 2
+                  "\x3a\x00\x01\x00\x01\x49old-state"  // -65538: b"old-state"
+                  "\x3a\x00\x01\x00\x02\x49new-state"  // -65539: b"new-state"
+                  "\x43\x61\x61\x64",                  // b"aad"
+                  49)));
+}
+
+TEST(ReleaseTokenTest, BuildSigStructureForSigning) {
+  EXPECT_THAT((ReleaseToken{
+                   .signing_algorithm = 1,
+                   .encryption_algorithm = 2,
+                   .encryption_key_id = "key-id",
+                   .src_state = "old-state",
+                   .dst_state = "new-state",
+                   .encrypted_payload = "payload",
+                   .encapped_key = "key",
+                   .signature = "signature",
+               })
+                  .BuildSigStructureForSigning("aad"),
+              IsOkAndHolds(absl::string_view(
+                  "\x84"              // array with 4 items:
+                  "\x6aSignature1"    // "Signature1"
+                  "\x43\xa1\x01\x01"  // bstr containing { alg: 1 }
+                  "\x43\x61\x61\x64"  // b"aad"
+                  // bstr containing COSE_Encrypt0
+                  "\x58\x3e\x83\x58\x21\xa3\x01\x02\x3a\x00\x01\x00\x01\x49old-"
+                  "state\x3a\x00\x01\x00\x02\x49new-state\xa2\x04\x46key-"
+                  "id\x3a\x00\x01\x00\x00\x43key\x47payload",
+                  84)));
+}
+
+TEST(ReleaseTokenTest, GetSigStructureForVerifying) {
+  EXPECT_THAT(ReleaseToken::GetSigStructureForVerifying(
+                  absl::string_view(
+                      "\x84\x43\xa1\x01\x01\xa0\x58\x3e\x83\x58\x21\xa3\x01\x02"
+                      "\x3a\x00\x01\x00\x01\x49old-state\x3a\x00\x01\x00\x02"
+                      "\x49new-state\xa2\x04\x46key-id\x3a\x00\x01\x00\x00\x43"
+                      "key\x47payload\x49signature",
+                      80),
+                  "aad"),
+              IsOkAndHolds(absl::string_view(
+                  "\x84"              // array with 4 items:
+                  "\x6aSignature1"    // "Signature1"
+                  "\x43\xa1\x01\x01"  // bstr containing { alg: 1 }
+                  "\x43\x61\x61\x64"  // b"aad"
+                  // bstr containing COSE_Encrypt0
+                  "\x58\x3e\x83\x58\x21\xa3\x01\x02\x3a\x00\x01\x00\x01\x49old-"
+                  "state\x3a\x00\x01\x00\x02\x49new-state\xa2\x04\x46key-"
+                  "id\x3a\x00\x01\x00\x00\x43key\x47payload",
+                  84)));
+}
+
+}  // namespace
+}  // namespace fcp::confidential_compute
diff --git a/third_party/federated_compute/chromium/fcp/testing/testing.cc b/third_party/federated_compute/chromium/fcp/testing/testing.cc
new file mode 100644
index 0000000..86f13f0
--- /dev/null
+++ b/third_party/federated_compute/chromium/fcp/testing/testing.cc
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2017 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "fcp/testing/testing.h"
+
+#include "fcp/base/monitoring.h"
+
+namespace fcp {
+
+StatusMatcher IsCode(StatusCode code) { return StatusMatcher(code); }
+StatusMatcher IsOk() { return IsCode(OK); }
+
+}  // namespace fcp
diff --git a/third_party/federated_compute/chromium/fcp/testing/testing.h b/third_party/federated_compute/chromium/fcp/testing/testing.h
new file mode 100644
index 0000000..9f016a1
--- /dev/null
+++ b/third_party/federated_compute/chromium/fcp/testing/testing.h
@@ -0,0 +1,93 @@
+/*
+ * Copyright 2017 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef FCP_TESTING_TESTING_H_
+#define FCP_TESTING_TESTING_H_
+
+#include <iostream>
+#include <memory>
+#include <string>
+#include <type_traits>
+
+#include "absl/status/status.h"
+#include "absl/strings/string_view.h"
+#include "fcp/base/monitoring.h"
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+
+// This file defines platform dependent utilities for testing,
+// based on the public version of googletest.
+
+namespace fcp {
+
+// Convenience macros for `EXPECT_THAT(s, IsOk())`, where `s` is either
+// a `Status` or a `StatusOr<T>`.
+// Old versions of the protobuf library define EXPECT_OK as well, so we only
+// conditionally define our version.
+#if !defined(EXPECT_OK)
+#define EXPECT_OK(result) EXPECT_THAT(result, fcp::IsOk())
+#endif
+#define ASSERT_OK(result) ASSERT_THAT(result, fcp::IsOk())
+
+/**
+ * Polymorphic matchers for Status or StatusOr on status code.
+ */
+template <typename T>
+bool IsCode(StatusOr<T> const& x, StatusCode code) {
+  return x.status().code() == code;
+}
+inline bool IsCode(Status const& x, StatusCode code) {
+  return x.code() == code;
+}
+
+template <typename T>
+class StatusMatcherImpl : public ::testing::MatcherInterface<T> {
+ public:
+  explicit StatusMatcherImpl(StatusCode code) : code_(code) {}
+  void DescribeTo(::std::ostream* os) const override {
+    *os << "is " << absl::StatusCodeToString(code_);
+  }
+  void DescribeNegationTo(::std::ostream* os) const override {
+    *os << "is not " << absl::StatusCodeToString(code_);
+  }
+  bool MatchAndExplain(
+      T x, ::testing::MatchResultListener* listener) const override {
+    return IsCode(x, code_);
+  }
+
+ private:
+  StatusCode code_;
+};
+
+class StatusMatcher {
+ public:
+  explicit StatusMatcher(StatusCode code) : code_(code) {}
+
+  template <typename T>
+  operator testing::Matcher<T>() const {  // NOLINT
+    return ::testing::MakeMatcher(new StatusMatcherImpl<T>(code_));
+  }
+
+ private:
+  StatusCode code_;
+};
+
+StatusMatcher IsCode(StatusCode code);
+StatusMatcher IsOk();
+
+}  // namespace fcp
+
+#endif  // FCP_TESTING_TESTING_H_
diff --git a/third_party/perfetto b/third_party/perfetto
index d17b40b..49af9ec 160000
--- a/third_party/perfetto
+++ b/third_party/perfetto
@@ -1 +1 @@
-Subproject commit d17b40b3b5e36f3744f1d010fe3ba2d3c55559c0
+Subproject commit 49af9ecad055e597f1cc7d57677454b72606e96d
diff --git a/third_party/protobuf/BUILD.gn b/third_party/protobuf/BUILD.gn
index b2efa8253..891430b 100644
--- a/third_party/protobuf/BUILD.gn
+++ b/third_party/protobuf/BUILD.gn
@@ -34,6 +34,10 @@
   }
 }
 
+proto_library("struct_proto") {
+  sources = [ "src/google/protobuf/struct.proto" ]
+}
+
 if (is_component_build) {
   config("protobuf_use_dlls") {
     defines = [ "PROTOBUF_USE_DLLS" ]
@@ -161,6 +165,9 @@
       # Sync requires descriptors for testing.
       "//components/sync:sync_full_proto_unittests",
       "//components/sync/protocol:full_proto",
+
+      # third_party/federated_compute requires it for testing.
+      "//third_party/federated_compute:federated_compute_tests",
     ]
   }
 
diff --git a/third_party/skia b/third_party/skia
index 359f3d7..7c2f502 160000
--- a/third_party/skia
+++ b/third_party/skia
@@ -1 +1 @@
-Subproject commit 359f3d7cc7edfcb93e99ab5ed7e9f2f5fdd8ef85
+Subproject commit 7c2f502e3304f4c76413696a9035470c32ac6dde
diff --git a/third_party/sqlite/BUILD.gn b/third_party/sqlite/BUILD.gn
index 4826a3c..46b11e7 100644
--- a/third_party/sqlite/BUILD.gn
+++ b/third_party/sqlite/BUILD.gn
@@ -456,7 +456,10 @@
     "fuzz/sql_query_proto_to_string.cc",
     "fuzz/sql_query_proto_to_string.h",
   ]
-  deps = [ ":sqlite3_lpm_fuzzer_input" ]
+  deps = [
+    ":sqlite3_lpm_fuzzer_input",
+    "//base",
+  ]
 
   # TODO(crbug.com/40031409): Fix code that adds exit-time destructors and
   # enable the diagnostic by removing this line.
@@ -472,6 +475,7 @@
   ]
   deps = [
     ":sqlite3_lpm_fuzzer_core",
+    "//base",
     "//third_party/libprotobuf-mutator",
   ]
   additional_configs = [ ":sqlite_warnings" ]
@@ -491,6 +495,7 @@
   ]
   deps = [
     ":sqlite3_lpm_fuzzer_core",
+    "//base",
     "//third_party/libprotobuf-mutator",
   ]
   additional_configs = [
@@ -517,6 +522,7 @@
   ]
   deps = [
     ":sqlite3_lpm_fuzzer_core",
+    "//base",
     "//third_party/libprotobuf-mutator",
   ]
   libfuzzer_options = [ "max_len=111000" ]
@@ -530,6 +536,7 @@
   ]
   deps = [
     ":sqlite3_lpm_fuzzer_core",
+    "//base",
     "//third_party/libprotobuf-mutator",
   ]
   libfuzzer_options = [ "max_len=111000" ]
@@ -543,6 +550,7 @@
   ]
   deps = [
     ":sqlite3_lpm_fuzzer_core",
+    "//base",
     "//third_party/libprotobuf-mutator",
   ]
   libfuzzer_options = [
diff --git a/third_party/sqlite/fuzz/sql_query_proto_to_string.cc b/third_party/sqlite/fuzz/sql_query_proto_to_string.cc
index 0ec82934..f74b6ac9 100644
--- a/third_party/sqlite/fuzz/sql_query_proto_to_string.cc
+++ b/third_party/sqlite/fuzz/sql_query_proto_to_string.cc
@@ -12,6 +12,7 @@
 #include <string>
 #include <vector>
 
+#include "base/no_destructor.h"
 #include "third_party/sqlite/fuzz/icu_codes.pb.h"
 #include "third_party/sqlite/fuzz/sql_queries.pb.h"
 #include "third_party/sqlite/fuzz/sql_query_grammar.pb.h"
@@ -22,7 +23,7 @@
 #define CONV_FN(TYPE, VAR_NAME) std::string TYPE##ToString(const TYPE& VAR_NAME)
 
 #define RETURN_IF_DISABLED_QUERY(TYPE)     \
-  if (disabled_queries_.count(#TYPE) != 0) \
+  if (disabled_queries_->count(#TYPE) != 0) \
     return "";
 
 namespace sql_fuzzer {
@@ -54,7 +55,7 @@
 constexpr uint32_t kMaxViewNumber = 5;
 constexpr uint32_t kMaxTriggerNumber = 10;
 
-std::set<std::string> disabled_queries_;
+static base::NoDestructor<std::set<std::string>> disabled_queries_;
 }  // namespace
 
 CONV_FN(Expr, expr);
@@ -2726,7 +2727,7 @@
 }
 
 void SetDisabledQueries(std::set<std::string> disabled_queries) {
-  disabled_queries_ = disabled_queries;
+  *disabled_queries_ = disabled_queries;
 }
 
 }  // namespace sql_fuzzer
diff --git a/third_party/webrtc b/third_party/webrtc
index db0e64c..35d9a8f 160000
--- a/third_party/webrtc
+++ b/third_party/webrtc
@@ -1 +1 @@
-Subproject commit db0e64c5e80168839d74ae0cc6173ccd537ae211
+Subproject commit 35d9a8f86a90ddfebd9c8b5f6ddce14b68a1a879
diff --git a/tools/android/logcat_filtering/styles.css b/tools/android/logcat_filtering/styles.css
index 866a3ca3..a790d3a 100644
--- a/tools/android/logcat_filtering/styles.css
+++ b/tools/android/logcat_filtering/styles.css
@@ -258,6 +258,10 @@
   align-items: flex-start;
 }
 
+#checkbox-controls .checkbox-column:last-of-type {
+  margin-right: 40px;
+}
+
 #checkbox-controls label {
   display: flex;
   align-items: center;
diff --git a/tools/autotest.py b/tools/autotest.py
index 056fa7e..796629c8 100755
--- a/tools/autotest.py
+++ b/tools/autotest.py
@@ -126,6 +126,7 @@
     '//third_party/crc32c:crc32c_benchmark',
     '//third_party/crc32c:crc32c_tests',
     '//third_party/dawn/src/dawn/tests/benchmarks:dawn_benchmarks',
+    '//third_party/federated_compute:federated_compute_tests',
     '//third_party/highway:highway_tests',
     '//third_party/ipcz/src:ipcz_tests',
     '//third_party/libaom:av1_encoder_fuzz_test',
diff --git a/tools/clang/scripts/analyze_includes.py b/tools/clang/scripts/analyze_includes.py
index 8bdb8e56..d07ce5b5 100755
--- a/tools/clang/scripts/analyze_includes.py
+++ b/tools/clang/scripts/analyze_includes.py
@@ -17,7 +17,9 @@
 Windows due to crbug.com/1223741#c9)
 
 The script takes roughly half an hour on a fast machine for the chrome build
-target, which is considered fast enough for batch job purposes for now.
+target, which is considered fast enough for batch job purposes for now. It can
+be sped up significantly by using multiple processes with the --proccesses option,
+but it will also use significantly more memory as a result (OOM is a risk).
 
 If --json-out is not provided, the script exits after printing some statistics
 to stdout. This is significantly faster than generating the full JSON data. For
@@ -28,7 +30,10 @@
 """
 
 import argparse
+import concurrent.futures
+import functools
 import json
+import math
 import os
 import pathlib
 import re
@@ -36,6 +41,7 @@
 import unittest
 from collections import defaultdict
 from datetime import datetime, timezone
+from itertools import batched
 
 
 def parse_build(build_log, root_filter=None):
@@ -52,19 +58,24 @@
   # invocations depending on -D flags. For such cases, includes[file] will be
   # the union of those includes.
 
-  # Normalize paths.
-  normalized = {}
-
+  @functools.cache
   def norm(fn):
-    if fn not in normalized:
-      x = fn.replace('\\\\', '\\')
-      # Use Path.resolve() rather than path.realpath() to get the canonical
-      # upper/lower-case version of the path on Windows.
-      p = pathlib.Path(os.path.join(build_dir, x)).resolve()
-      x = os.path.relpath(p)
-      x = x.replace(os.path.sep, '/')
-      normalized[fn] = x
-    return normalized[fn]
+    x = fn.replace('\\\\', '\\')
+    # Use Path.resolve() rather than path.realpath() to get the canonical
+    # upper/lower-case version of the path on Windows.
+    p = pathlib.Path(os.path.join(build_dir, x)).resolve()
+    x = os.path.relpath(p)
+    return x.replace(os.path.sep, '/')
+
+  @functools.cache
+  def parse_include_line(line):
+    m = INCLUDE_RE.match(line)
+
+    if m:
+      depth = len(m.group(1))
+      filename = norm(m.group(2))
+
+      return filename, depth
 
   # ninja: Entering directory `out/foo'
   ENTER_DIR_RE = re.compile(r'ninja: Entering directory `(.*?)\'$')
@@ -81,16 +92,15 @@
   skipping_root = False
 
   for line in build_log:
-    m = INCLUDE_RE.match(line)
     # TODO(https://crbug.com/435303792): Ignore precompiled modules (.pcm) until
     # an appropriate way to calculate their size for include analysis is found.
-    if m and not m.group(2).endswith('.pcm'):
+    if (parsed := parse_include_line(line)) and not parsed[0].endswith('.pcm'):
       if skipping_root:
         continue
       prev_depth = len(file_stack) - 1
-      depth = len(m.group(1))
-      filename = norm(m.group(2))
-      includes.setdefault(filename, set())
+      filename, depth = parsed
+      if filename not in includes:
+        includes[filename] = set()
 
       if depth > prev_depth:
         if sys.platform != 'win32':
@@ -101,16 +111,14 @@
           print('missing include under', file_stack[0])
           continue
       else:
-        for _ in range(prev_depth - depth + 1):
-          file_stack.pop()
+        del file_stack[-(prev_depth - depth + 1):]
 
       includes[file_stack[-1]].add(filename)
       file_stack.append(filename)
       continue
 
-    m = COMPILE_RE.match(line)
     # Clang module compile uses -x after -c, so skip that from include analysis.
-    if m and m.group(2) != '-x':
+    if (m := COMPILE_RE.match(line)) and m.group(2) != '-x':
       skipping_root = False
       filename = norm(m.group(2))
       if root_filter and not root_filter.match(filename):
@@ -121,8 +129,7 @@
       includes.setdefault(filename, set())
       continue
 
-    m = ENTER_DIR_RE.match(line)
-    if m:
+    if (m := ENTER_DIR_RE.match(line)):
       build_dir = m.group(1)
       continue
 
@@ -340,6 +347,25 @@
   return {n: dom_set(n) for n in vertex}
 
 
+def compute_added_sizes(args):
+  """Helper to compute added sizes from the given root."""
+
+  roots, includes, sizes = args
+  added_sizes = {node: 0 for node in includes}
+
+  for root in roots:
+    doms = compute_doms(root, includes)
+
+    for node in doms:
+      if node not in sizes:
+        # Skip the (src,dst) pseudo nodes.
+        continue
+      for dom in doms[node]:
+        added_sizes[dom] += sizes[node]
+
+  return added_sizes
+
+
 class TestComputeDoms(unittest.TestCase):
   def test_basic(self):
     includes = {}
@@ -394,28 +420,35 @@
     self.assertEqual(doms['r'], set(['r']))
 
 
-def trans_size(root, includes, sizes):
-  """Compute the transitive size of a file, i.e. the size of the file itself and
-  all its transitive includes."""
-  return sum([sizes[n] for n in post_order_nodes(root, includes)])
-
-
 def log(*args, **kwargs):
   """Log output to stderr."""
   print(*args, file=sys.stderr, **kwargs)
 
 
-def analyze(target, revision, build_log_file, json_file, root_filter):
+def analyze(target, revision, build_log_file, json_file, root_filter, processes=1):
   log('Parsing build log...')
   (roots, includes) = parse_build(build_log_file, root_filter)
 
   log('Getting file sizes...')
   sizes = {name: os.path.getsize(name) for name in includes}
 
-  log('Computing transitive sizes...')
-  trans_sizes = {n: trans_size(n, includes, sizes) for n in includes}
+  log('Computing transitive sizes and prevalence...')
+  build_size = 0
+  trans_sizes = {name: 0 for name in includes}
+  prevalence = {name: 0 for name in includes}
 
-  build_size = sum([trans_sizes[n] for n in roots])
+  for n in includes:
+    for node in post_order_nodes(n, includes):
+      # Compute the transitive size of a file, i.e. the size of the
+      # file itself and all its transitive includes.
+      trans_sizes[n] += sizes[node]
+
+      if n in roots:
+        prevalence[node] += 1
+
+    # Total build size is the sum of the transitive size of all roots.
+    if n in roots:
+      build_size += trans_sizes[n]
 
   print('build_size', build_size)
 
@@ -423,12 +456,6 @@
     log('--json-out not set; exiting.')
     return 0
 
-  log('Counting prevalence...')
-  prevalence = {name: 0 for name in includes}
-  for r in roots:
-    for n in post_order_nodes(r, includes):
-      prevalence[n] += 1
-
   # Map from file to files that include it.
   log('Building reverse include map...')
   included_by = {k: set() for k in includes}
@@ -448,15 +475,24 @@
       augmented_includes[src].add((src, dst))
       augmented_includes[(src, dst)] = {dst}
 
-  added_sizes = {node: 0 for node in augmented_includes}
-  for r in roots:
-    doms = compute_doms(r, augmented_includes)
-    for node in doms:
-      if node not in sizes:
-        # Skip the (src,dst) pseudo nodes.
-        continue
-      for dom in doms[node]:
-        added_sizes[dom] += sizes[node]
+  if processes > 1:
+    added_sizes = {node: 0 for node in augmented_includes}
+
+    # Break up the list of roots into chunks based on the number of processes and pass them
+    # to each worker. Giving each worker a complete chunk of roots to work on rather than
+    # having workers pull as they go minimizes contention and gives better parallelization.
+    chunk_size = math.ceil(float(len(roots)) / processes)
+    chunked = list(batched(roots, chunk_size))
+
+    with concurrent.futures.ProcessPoolExecutor(max_workers=processes) as pool:
+      for computed_added_sizes in pool.map(
+        compute_added_sizes,
+        ((chunk, augmented_includes, sizes) for chunk in chunked),
+      ):
+        for dom, size in computed_added_sizes.items():
+          added_sizes[dom] += size
+  else:
+    added_sizes = compute_added_sizes((roots, augmented_includes, sizes))
 
   # Assign a number to each filename for tighter JSON representation.
   names = []
@@ -482,7 +518,7 @@
           'files': names,
           'roots': [nr(x) for x in sorted(roots)],
           'includes': [[nr(x) for x in sorted(includes[n])] for n in names],
-          'included_by': [[nr(x) for x in included_by[n]] for n in names],
+          'included_by': [[nr(x) for x in sorted(included_by[n])] for n in names],
           'sizes': [sizes[n] for n in names],
           'tsizes': [trans_sizes[n] for n in names],
           'asizes': [added_sizes[n] for n in names],
@@ -513,6 +549,12 @@
       help='Write full analysis data to a JSON file (- for stdout).')
   parser.add_argument('--root-filter',
                       help='Regex to filter which root files are analyzed.')
+  parser.add_argument('--processes',
+                      action="store",
+                      type=int,
+                      default=1,
+                      help="Use multiple processes to speed up the analysis - "
+                           "note that this scales memory usage significantly")
   args = parser.parse_args()
 
   if args.json_out and not (args.target and args.revision):
@@ -526,7 +568,7 @@
     return 1
 
   analyze(args.target, args.revision, args.build_log, args.json_out,
-          root_filter)
+          root_filter, processes=args.processes)
 
 
 if __name__ == '__main__':
diff --git a/tools/gritsettings/translation_expectations.pyl b/tools/gritsettings/translation_expectations.pyl
index 3dc6085..55b02ea 100644
--- a/tools/gritsettings/translation_expectations.pyl
+++ b/tools/gritsettings/translation_expectations.pyl
@@ -140,9 +140,9 @@
   },
   # Internal grds that contain parts not available publicly and thus should
   # not be checked by translation script.
+  # NOTE: This list does NOT itemize src-internal translations.
   "internal_grds": [
     # Test grd that contains a part file that doesn't exist.
     "tools/translation/testdata/internal.grd",
-    "third_party/search_engines_data/resources_internal/search_engine_descriptions_strings.grd",
   ],
 }
diff --git a/tools/metrics/histograms/metadata/attribution_reporting/histograms.xml b/tools/metrics/histograms/metadata/attribution_reporting/histograms.xml
index d9af2832..0957362 100644
--- a/tools/metrics/histograms/metadata/attribution_reporting/histograms.xml
+++ b/tools/metrics/histograms/metadata/attribution_reporting/histograms.xml
@@ -651,21 +651,6 @@
   </summary>
 </histogram>
 
-<histogram name="Conversions.FirstBatch.HttpResponseOrNetErrorCode"
-    enum="CombinedHttpResponseAndNetErrorCode" expires_after="2026-02-22">
-  <owner>apaseltiner@chromium.org</owner>
-  <owner>measurement-api-dev+metrics@google.com</owner>
-  <summary>
-    Error info for sending an attribution report, recorded for each report
-    within the first report delivery batch for a new user session i.e. upon
-    initialization of AttributionManager. The HTTP response code is recorded if
-    there is no net error code for the request, or the net error code indicates
-    there was a response code failure. Note: The first batch of reports may not
-    send exactly at manager startup due to purposeful delays added as to not
-    allow temporal joins between them.
-  </summary>
-</histogram>
-
 <histogram name="Conversions.GetAllDataKeysTime" units="ms"
     expires_after="2026-02-22">
   <owner>linnan@chromium.org</owner>
diff --git a/tools/metrics/histograms/metadata/autofill/enums.xml b/tools/metrics/histograms/metadata/autofill/enums.xml
index 3dad0eb..be723cf 100644
--- a/tools/metrics/histograms/metadata/autofill/enums.xml
+++ b/tools/metrics/histograms/metadata/autofill/enums.xml
@@ -4157,6 +4157,8 @@
 <enum name="SaveAndFillFormEvent">
   <int value="0" label="The Save and Fill suggestion was shown"/>
   <int value="1" label="The Save and Fill suggestion was accepted"/>
+  <int value="2" label="The form was filled after Save and Fill finished"/>
+  <int value="3" label="The form was submitted after Save and Fill finished"/>
 </enum>
 
 <!-- LINT.ThenChange(/components/autofill/core/browser/metrics/payments/save_and_fill_metrics.h:SaveAndFillFormEvent) -->
diff --git a/tools/metrics/histograms/metadata/autofill/histograms.xml b/tools/metrics/histograms/metadata/autofill/histograms.xml
index a7e23eb..c7030e8 100644
--- a/tools/metrics/histograms/metadata/autofill/histograms.xml
+++ b/tools/metrics/histograms/metadata/autofill/histograms.xml
@@ -4123,7 +4123,7 @@
 </histogram>
 
 <histogram name="Autofill.iOS.TriggeredFormExtractionFromDriver.{Range}"
-    units="count" expires_after="2025-10-12">
+    units="count" expires_after="2026-10-12">
   <owner>vincb@google.com</owner>
   <owner>tmartino@chromium.org</owner>
   <owner>bling-transactions@google.com</owner>
@@ -5977,6 +5977,26 @@
   </token>
 </histogram>
 
+<histogram name="Autofill.SaveAndFill.Funnel.{SaveType}.{Result}"
+    enum="SaveAndFillFormEvent" expires_after="2026-06-01">
+  <owner>siyua@chromium.org</owner>
+  <owner>averina@google.com</owner>
+  <owner>payments-autofill-team@google.com</owner>
+  <summary>
+    Records form events to calculate the Save and Fill conversion rate. Logged
+    when the form is filled and when form is submitted. It has two breakdowns to
+    indicate that the save is a {SaveType} and {Result}.
+  </summary>
+  <token key="SaveType">
+    <variant name="Local" summary="local save"/>
+    <variant name="Upload" summary="server save"/>
+  </token>
+  <token key="Result">
+    <variant name="Failure" summary="save failed"/>
+    <variant name="Success" summary="save succeeded"/>
+  </token>
+</histogram>
+
 <histogram
     name="Autofill.SaveAndFill.GetDetailsForCreateCard.Latency{RequestResult}"
     units="ms" expires_after="2026-06-01">
diff --git a/tools/metrics/histograms/metadata/cookie/histograms.xml b/tools/metrics/histograms/metadata/cookie/histograms.xml
index c821f71..3ecc6dd 100644
--- a/tools/metrics/histograms/metadata/cookie/histograms.xml
+++ b/tools/metrics/histograms/metadata/cookie/histograms.xml
@@ -1122,16 +1122,6 @@
   </summary>
 </histogram>
 
-<histogram name="Cookie.TimeDatabaseMigrationToV19" units="ms"
-    expires_after="2025-09-22">
-  <owner>arichiv@chromium.org</owner>
-  <owner>src/net/cookies/OWNERS</owner>
-  <summary>
-    The amount of time (ms) to migrate a v18 cookie database to v19. Migration
-    occurs upon first startup of a browser version with v18 database code.
-  </summary>
-</histogram>
-
 <histogram name="Cookie.TimeDatabaseMigrationToV20" units="ms"
     expires_after="2025-11-14">
   <owner>cfredric@chromium.org</owner>
diff --git a/tools/metrics/histograms/metadata/extensions/enums.xml b/tools/metrics/histograms/metadata/extensions/enums.xml
index fbd0e019..f1c0c9a 100644
--- a/tools/metrics/histograms/metadata/extensions/enums.xml
+++ b/tools/metrics/histograms/metadata/extensions/enums.xml
@@ -2820,8 +2820,8 @@
   <int value="1931" label="AUTOFILLPRIVATE_GETPAYOVERTIMEISSUERLIST"/>
   <int value="1932" label="AUTOFILLPRIVATE_SETAUTOFILLAIOPTINSTATUS"/>
   <int value="1933" label="AUTOFILLPRIVATE_GETAUTOFILLAIOPTINSTATUS"/>
-  <int value="1934" label="EXPERIMENTALACTOR_STARTTASK"/>
-  <int value="1935" label="EXPERIMENTALACTOR_EXECUTEACTION"/>
+  <int value="1934" label="DELETED_EXPERIMENTALACTOR_STARTTASK"/>
+  <int value="1935" label="DELETED_EXPERIMENTALACTOR_EXECUTEACTION"/>
   <int value="1936" label="EXPERIMENTALACTOR_STOPTASK"/>
   <int value="1937" label="BOOKMARKMANAGERPRIVATE_OPENINNEWTABGROUP"/>
   <int value="1938" label="CONTROLLEDFRAMEINTERNAL_CONTEXTMENUSUPDATE"/>
diff --git a/tools/metrics/histograms/metadata/ios/histograms.xml b/tools/metrics/histograms/metadata/ios/histograms.xml
index 7183270a..ce725bf8 100644
--- a/tools/metrics/histograms/metadata/ios/histograms.xml
+++ b/tools/metrics/histograms/metadata/ios/histograms.xml
@@ -2867,7 +2867,7 @@
 </histogram>
 
 <histogram name="IOS.MagicStack.Module.Click.AppBundlePromo" units="index"
-    expires_after="2026-01-18">
+    expires_after="2025-12-02">
   <owner>ericekey@google.com</owner>
   <owner>bling-pandamonium@google.com</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/others/histograms.xml b/tools/metrics/histograms/metadata/others/histograms.xml
index 89f051b..eb89f48 100644
--- a/tools/metrics/histograms/metadata/others/histograms.xml
+++ b/tools/metrics/histograms/metadata/others/histograms.xml
@@ -4009,7 +4009,7 @@
 </histogram>
 
 <histogram name="DirectSockets.TCPServerNetworkFailures" enum="NetErrorCodes"
-    expires_after="2025-09-13">
+    expires_after="2026-09-13">
   <owner>greengrape@google.com</owner>
   <owner>iwa-team@google.com</owner>
   <summary>
@@ -4018,21 +4018,21 @@
 </histogram>
 
 <histogram name="DirectSockets.TCPServerReadableStreamError"
-    enum="NetErrorCodes" expires_after="2025-09-13">
+    enum="NetErrorCodes" expires_after="2026-09-13">
   <owner>greengrape@google.com</owner>
   <owner>iwa-team@google.com</owner>
   <summary>How often tcp server socket readable stream is errored.</summary>
 </histogram>
 
 <histogram name="DirectSockets.TCPWritableStreamError" enum="NetErrorCodes"
-    expires_after="2025-09-13">
+    expires_after="2026-09-13">
   <owner>greengrape@google.com</owner>
   <owner>iwa-team@google.com</owner>
   <summary>How often tcp socket writable stream is errored.</summary>
 </histogram>
 
 <histogram name="DirectSockets.UDPNetworkFailures" enum="NetErrorCodes"
-    expires_after="2025-09-13">
+    expires_after="2026-09-13">
   <owner>greengrape@google.com</owner>
   <owner>iwa-team@google.com</owner>
   <summary>
@@ -4041,14 +4041,14 @@
 </histogram>
 
 <histogram name="DirectSockets.UDPReadableStreamError" enum="NetErrorCodes"
-    expires_after="2025-11-23">
+    expires_after="2026-11-23">
   <owner>greengrape@google.com</owner>
   <owner>iwa-team@google.com</owner>
   <summary>How often udp socket readable stream is errored.</summary>
 </histogram>
 
 <histogram name="DirectSockets.UDPWritableStreamError" enum="NetErrorCodes"
-    expires_after="2025-09-13">
+    expires_after="2026-09-13">
   <owner>greengrape@google.com</owner>
   <owner>iwa-team@google.com</owner>
   <summary>How often udp socket writable stream is errored.</summary>
diff --git a/tools/metrics/histograms/metadata/password/enums.xml b/tools/metrics/histograms/metadata/password/enums.xml
index d63e8b14..f3d1051d 100644
--- a/tools/metrics/histograms/metadata/password/enums.xml
+++ b/tools/metrics/histograms/metadata/password/enums.xml
@@ -608,27 +608,6 @@
              policy"/>
 </enum>
 
-<!-- LINT.IfChange(LoginDbDeprecationExportProgress) -->
-
-<enum name="LoginDbDeprecationExportProgress">
-  <int value="0" label="Scheduled"/>
-  <int value="1" label="Started"/>
-  <int value="2" label="Finished"/>
-</enum>
-
-<!-- LINT.ThenChange(/components/password_manager/core/browser/export/login_db_deprecation_runner.h:LoginDbDeprecationExportProgress) -->
-
-<!-- LINT.IfChange(LoginDbDeprecationExportResult) -->
-
-<enum name="LoginDbDeprecationExportResult">
-  <int value="0" label="Success"/>
-  <int value="1" label="No passwords"/>
-  <int value="2" label="Error fetching paswords"/>
-  <int value="3" label="File write error"/>
-</enum>
-
-<!-- LINT.ThenChange(/components/password_manager/core/browser/export/login_db_deprecation_password_exporter.h:LoginDbDeprecationExportResult) -->
-
 <enum name="LoginsChangedTrigger">
   <summary>
     Triggers that may indirectly cause PasswordStore observers notifications
@@ -1456,7 +1435,7 @@
   <int value="0" label="Internal backend missing"/>
   <int value="1" label="No GMS, no Play Store"/>
   <int value="2" label="Outdated GMS"/>
-  <int value="3" label="Auto-export pending"/>
+  <int value="3" label="(obsolete) Auto-export pending"/>
 </enum>
 
 <!-- LINT.ThenChange(/chrome/browser/password_manager/android/password_manager_android_util.h:PasswordManagerNotAvailableReason) -->
diff --git a/tools/metrics/histograms/metadata/password/histograms.xml b/tools/metrics/histograms/metadata/password/histograms.xml
index 7ee612a..02e8f79d2c 100644
--- a/tools/metrics/histograms/metadata/password/histograms.xml
+++ b/tools/metrics/histograms/metadata/password/histograms.xml
@@ -3297,17 +3297,6 @@
   </summary>
 </histogram>
 
-<histogram
-    name="PasswordManager.PasswordSharingRecipients.FetchAccessTokenResult"
-    enum="GoogleServiceAuthError" expires_after="2025-10-12">
-  <owner>rushans@google.com</owner>
-  <owner>mamir@chromium.org</owner>
-  <summary>
-    Records the result of access token fetch for the password sharing recipients
-    request. Recorded when a token fetch completes (including retries).
-  </summary>
-</histogram>
-
 <histogram name="PasswordManager.PasswordSharingRecipients.ResponseOrErrorCode"
     enum="CombinedHttpResponseAndNetErrorCode" expires_after="2026-02-22">
   <owner>rushans@google.com</owner>
@@ -4363,42 +4352,6 @@
   </summary>
 </histogram>
 
-<histogram name="PasswordManager.UPM.LoginDbDeprecationExport.Latency"
-    units="ms" expires_after="2026-02-22">
-  <owner>ioanap@chromium.org</owner>
-  <owner>vasilii@chromium.org</owner>
-  <summary>
-    Records the latency of the auto-export preceeding the login database
-    deprecation on Android. The export is performed on start-up, for clients who
-    didn't migrate to UPM. Recorded only when the export completes successfully.
-    It includes the time required to fetch passwords from the database.
-  </summary>
-</histogram>
-
-<histogram name="PasswordManager.UPM.LoginDbDeprecationExport.Progress"
-    enum="LoginDbDeprecationExportProgress" expires_after="2025-10-12">
-  <owner>ioanap@chromium.org</owner>
-  <owner>vasilii@chromium.org</owner>
-  <summary>
-    Records when the pre-deprecation export flow is scheduled, started and
-    finished, irrespective of success status. The success status breakdown is
-    recorded in &quot;PasswordManager.UPM.LoginDbDeprecationExport.Result&quot;.
-    The export might be started with a delay. Android only.
-  </summary>
-</histogram>
-
-<histogram name="PasswordManager.UPM.LoginDbDeprecationExport.Result"
-    enum="LoginDbDeprecationExportResult" expires_after="2026-02-22">
-  <owner>ioanap@chromium.org</owner>
-  <owner>vasilii@chromium.org</owner>
-  <summary>
-    Records the result of the auto-export preceeding the login database
-    deprecation on Android. The export is performed on start-up, for clients who
-    didn't migrate to UPM. Recorded when the export completes, or before it
-    starts if there has been an issue fetching passwords. Android only.
-  </summary>
-</histogram>
-
 <histogram name="PasswordManager.UPM.NoGmsNoPasswordsDialogShown"
     enum="BooleanShown" expires_after="2026-02-22">
   <owner>ioanap@chromium.org</owner>
diff --git a/tools/metrics/histograms/metadata/sql/histograms.xml b/tools/metrics/histograms/metadata/sql/histograms.xml
index af3cfebb..7e19265d 100644
--- a/tools/metrics/histograms/metadata/sql/histograms.xml
+++ b/tools/metrics/histograms/metadata/sql/histograms.xml
@@ -183,8 +183,8 @@
 </histogram>
 
 <histogram name="Sql.Recovery.Result" enum="SqlRecoveryResult"
-    expires_after="2025-09-15">
-  <owner>asully@chromium.org</owner>
+    expires_after="2026-02-01">
+  <owner>etienneb@chromium.org</owner>
   <owner>chrome-catan@google.com</owner>
   <summary>
     Outcome of attempting to recover a database with sql::Recovery.
@@ -192,8 +192,8 @@
 </histogram>
 
 <histogram name="Sql.Recovery.Result.{DatabaseTag}" enum="SqlRecoveryResult"
-    expires_after="2026-01-18">
-  <owner>asully@chromium.org</owner>
+    expires_after="2026-02-01">
+  <owner>etienneb@chromium.org</owner>
   <owner>chrome-catan@google.com</owner>
   <summary>
     Outcome of attempting to recover the {DatabaseTag} database with
@@ -204,7 +204,7 @@
 
 <histogram name="Sql.Recovery.ResultCode" enum="SqliteLoggedResultCode"
     expires_after="2026-02-01">
-  <owner>asully@chromium.org</owner>
+  <owner>etienneb@chromium.org</owner>
   <owner>chrome-catan@google.com</owner>
   <summary>
     SQLite result code from attempting to recover a database with sql::Recovery.
@@ -216,8 +216,8 @@
 </histogram>
 
 <histogram name="Sql.Recovery.ResultCode.{DatabaseTag}"
-    enum="SqliteLoggedResultCode" expires_after="2025-09-15">
-  <owner>asully@chromium.org</owner>
+    enum="SqliteLoggedResultCode" expires_after="2026-02-01">
+  <owner>etienneb@chromium.org</owner>
   <owner>chrome-catan@google.com</owner>
   <summary>
     SQLite result code from attempting to recover the {DatabaseTag} database
diff --git a/tools/metrics/histograms/metadata/webapps/histograms.xml b/tools/metrics/histograms/metadata/webapps/histograms.xml
index 6c7eb96a..6081162 100644
--- a/tools/metrics/histograms/metadata/webapps/histograms.xml
+++ b/tools/metrics/histograms/metadata/webapps/histograms.xml
@@ -1590,7 +1590,7 @@
 </histogram>
 
 <histogram name="WebApp.Isolated.OrphanedBundlesCleanupJobError"
-    enum="IsolatedWebAppCleanupOrphanedIWAsError" expires_after="2025-10-05">
+    enum="IsolatedWebAppCleanupOrphanedIWAsError" expires_after="2026-02-09">
   <owner>giovax@chromium.org</owner>
   <owner>pwa-commercial@google.com</owner>
   <owner>src/chrome/browser/web_applications/isolated_web_apps/OWNERS</owner>
@@ -1736,7 +1736,7 @@
 </histogram>
 
 <histogram name="WebApp.Isolated.SwbnFileUsabilityError"
-    enum="IsolatedWebAppSwbnFileUsabilityError" expires_after="2025-10-05">
+    enum="IsolatedWebAppSwbnFileUsabilityError" expires_after="2026-02-09">
   <owner>giovax@chromium.org</owner>
   <owner>pwa-commercial@google.com</owner>
   <owner>src/chrome/browser/web_applications/isolated_web_apps/OWNERS</owner>
diff --git a/tools/metrics/histograms/metadata/webauthn/histograms.xml b/tools/metrics/histograms/metadata/webauthn/histograms.xml
index 052dbf3..a31a10a 100644
--- a/tools/metrics/histograms/metadata/webauthn/histograms.xml
+++ b/tools/metrics/histograms/metadata/webauthn/histograms.xml
@@ -560,7 +560,7 @@
 </histogram>
 
 <histogram name="WebAuthentication.Windows.FindHelloDialogIterationCount"
-    units="iterations" expires_after="2025-09-07">
+    units="iterations" expires_after="2026-06-30">
   <owner>kenrb@chromium.org</owner>
   <owner>chrome-webauthn@google.com</owner>
   <summary>
@@ -571,7 +571,7 @@
 </histogram>
 
 <histogram name="WebAuthentication.Windows.ForegroundedWindowsHelloDialog"
-    enum="WindowsForegroundedHelloDialog" expires_after="2025-09-07">
+    enum="WindowsForegroundedHelloDialog" expires_after="2026-06-30">
   <owner>kenrb@chromium.org</owner>
   <owner>chrome-webauthn@google.com</owner>
   <summary>
diff --git a/tools/metrics/ukm/ukm.xml b/tools/metrics/ukm/ukm.xml
index d4feee69..548f45e3 100644
--- a/tools/metrics/ukm/ukm.xml
+++ b/tools/metrics/ukm/ukm.xml
@@ -3739,6 +3739,50 @@
   </metric>
 </event>
 
+<event name="AutofillAi.UserPromptMetrics">
+  <owner>jihadghanna@google.com</owner>
+  <owner>jkeitel@google.com</owner>
+  <owner>brunobraga@google.com</owner>
+  <summary>
+    Records when an AutofillAi prompt is shown to the user, such as saving or
+    updating entities.
+  </summary>
+  <metric name="EntityType">
+    <summary>
+      The type of entity for which the current metric record is being logged
+      (E.g. Passport, Driver's License, etc.).
+    </summary>
+  </metric>
+  <metric name="FormSessionIdentifier">
+    <summary>
+      A random variable assigned to a specific instance of a HTML form in a
+      specific renderer. This should be globally unique and suitable for
+      counting distinct forms. Two different users would have different values.
+      A single user loading the same form in two different tabs would have two
+      different values. This is used to link the fields to a form in
+      AutofillAi.KeyMetrics.
+    </summary>
+  </metric>
+  <metric name="PromptType">
+    <summary>
+      Describes the type of prompt shown to the user. This could be for example
+      a save or update entity prompt.
+    </summary>
+  </metric>
+  <metric name="Result">
+    <summary>
+      The decision made by the user after being shown a prompt. This could be
+      for example, accepted, declined or ignored.
+    </summary>
+  </metric>
+  <metric name="StorageType">
+    <summary>
+      Describes to which storage the data will be saved if the user accepts the
+      prompt, such locally or in the wallet server.
+    </summary>
+  </metric>
+</event>
+
 <event name="BackForwardCacheDisabledForRenderFrameHostReason">
   <owner>hajimehoshi@chromium.org</owner>
   <summary>
diff --git a/tools/perf/core/perfetto_binary_roller/binary_deps.json b/tools/perf/core/perfetto_binary_roller/binary_deps.json
index 8f1ccb0..f3f76b2 100644
--- a/tools/perf/core/perfetto_binary_roller/binary_deps.json
+++ b/tools/perf/core/perfetto_binary_roller/binary_deps.json
@@ -1,16 +1,16 @@
 {
     "trace_processor_shell": {
         "linux_arm64": {
-            "hash": "4a4432738c52fb7b071d0d3bd8478a3026ca3d55",
-            "full_remote_path": "perfetto-luci-artifacts/3b37d4c14b693cb89309578baa5ad77751307250/linux-arm64/trace_processor_shell"
+            "hash": "8df907de30d065b73667c8c4adfe6c75e9936e0d",
+            "full_remote_path": "perfetto-luci-artifacts/d17b40b3b5e36f3744f1d010fe3ba2d3c55559c0/linux-arm64/trace_processor_shell"
         },
         "win": {
-            "hash": "bb20d180f4376b4e101b81662a24b21879733b6e",
-            "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/win/43afaf571d990c0f3275c6800cf3ed42138bdc26/trace_processor_shell.exe"
+            "hash": "597b5d7d406a89b4237451ec94545ad23cc77980",
+            "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/win/d17b40b3b5e36f3744f1d010fe3ba2d3c55559c0/trace_processor_shell.exe"
         },
         "linux_arm": {
-            "hash": "8f61747472bfaf68561d72d515a1ca4643b8f166",
-            "full_remote_path": "perfetto-luci-artifacts/3b37d4c14b693cb89309578baa5ad77751307250/linux-arm/trace_processor_shell"
+            "hash": "d2819bf77c3920780f2b33cc43f328d24cc1e427",
+            "full_remote_path": "perfetto-luci-artifacts/d17b40b3b5e36f3744f1d010fe3ba2d3c55559c0/linux-arm/trace_processor_shell"
         },
         "mac": {
             "hash": "f5d83eca972747f7c3db9f35c07ed57902418e8c",
@@ -21,8 +21,8 @@
             "full_remote_path": "perfetto-luci-artifacts/d17b40b3b5e36f3744f1d010fe3ba2d3c55559c0/mac-arm64/trace_processor_shell"
         },
         "linux": {
-            "hash": "bcb0bf8bc8af7c097b0389189bbb3c6cd7d5fc3e",
-            "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/linux/9e99848ef108842c9c6f1fefe5bf8d4fd44c8496/trace_processor_shell"
+            "hash": "b1296477ed3d348b1e367a5569467c6815146fb2",
+            "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/linux/d17b40b3b5e36f3744f1d010fe3ba2d3c55559c0/trace_processor_shell"
         }
     },
     "power_profile.sql": {
diff --git a/ui/base/ui_base_features.cc b/ui/base/ui_base_features.cc
index de6fd4f5..f2d4b3a 100644
--- a/ui/base/ui_base_features.cc
+++ b/ui/base/ui_base_features.cc
@@ -265,7 +265,7 @@
 // This feature enables drag and drop using touch input devices.
 BASE_FEATURE(kTouchDragAndDrop,
              "TouchDragAndDrop",
-#if BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_ANDROID)
+#if BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_WIN)
              base::FEATURE_ENABLED_BY_DEFAULT
 #else
              base::FEATURE_DISABLED_BY_DEFAULT
diff --git a/ui/compositor/compositor.cc b/ui/compositor/compositor.cc
index 57cac20..d51815a 100644
--- a/ui/compositor/compositor.cc
+++ b/ui/compositor/compositor.cc
@@ -901,7 +901,7 @@
       "cc,benchmark", "FramePresented",
       frame_timing_details.presentation_feedback.timestamp, "environment",
       "browser");
-  observer_list_.Notify(&CompositorObserver::OnDidPresentCompositorFrame,
+  observer_list_.Notify(&CompositorObserver::OnDidPresentCompositorFrame, this,
                         frame_token,
                         frame_timing_details.presentation_feedback);
 }
diff --git a/ui/compositor/compositor_observer.h b/ui/compositor/compositor_observer.h
index a2f67f9d..82e37a1 100644
--- a/ui/compositor/compositor_observer.h
+++ b/ui/compositor/compositor_observer.h
@@ -71,6 +71,7 @@
 
   // Called when the presentation feedback was received from the viz.
   virtual void OnDidPresentCompositorFrame(
+      Compositor* compositor,
       uint32_t frame_token,
       const gfx::PresentationFeedback& feedback) {}
 
diff --git a/ui/compositor/layer_unittest.cc b/ui/compositor/layer_unittest.cc
index 15b5002f..0de15f8 100644
--- a/ui/compositor/layer_unittest.cc
+++ b/ui/compositor/layer_unittest.cc
@@ -431,6 +431,7 @@
   }
 
   void OnDidPresentCompositorFrame(
+      ui::Compositor* compositor,
       uint32_t frame_token,
       const gfx::PresentationFeedback& feedback) override {
     ended_ = true;
diff --git a/ui/compositor/test/draw_waiter_for_test.cc b/ui/compositor/test/draw_waiter_for_test.cc
index 5e4fe22a..8a9e36d 100644
--- a/ui/compositor/test/draw_waiter_for_test.cc
+++ b/ui/compositor/test/draw_waiter_for_test.cc
@@ -51,6 +51,7 @@
 }
 
 void DrawWaiterForTest::OnDidPresentCompositorFrame(
+    ui::Compositor* compositor,
     uint32_t frame_token,
     const gfx::PresentationFeedback& feedback) {
   if (wait_event_ == WAIT_FOR_COMPOSITING_ENDED) {
diff --git a/ui/compositor/test/draw_waiter_for_test.h b/ui/compositor/test/draw_waiter_for_test.h
index aa26d0a..f4206ff3 100644
--- a/ui/compositor/test/draw_waiter_for_test.h
+++ b/ui/compositor/test/draw_waiter_for_test.h
@@ -49,6 +49,7 @@
   void OnCompositingStarted(Compositor* compositor,
                             base::TimeTicks start_time) override;
   void OnDidPresentCompositorFrame(
+      Compositor* compositor,
       uint32_t frame_token,
       const gfx::PresentationFeedback& feedback) override;
 
diff --git a/ui/compositor/total_animation_throughput_reporter_unittest.cc b/ui/compositor/total_animation_throughput_reporter_unittest.cc
index 5e1e05ab..914c25bf 100644
--- a/ui/compositor/total_animation_throughput_reporter_unittest.cc
+++ b/ui/compositor/total_animation_throughput_reporter_unittest.cc
@@ -70,6 +70,7 @@
   }
 
   void OnDidPresentCompositorFrame(
+      ui::Compositor* compositor,
       uint32_t frame_token,
       const gfx::PresentationFeedback& feedback) override {
     if (waiting_for_did_present_compositor_frame_) {
diff --git a/ui/gfx/geometry/insets_f_unittest.cc b/ui/gfx/geometry/insets_f_unittest.cc
index 3443aab3..0ca32ad 100644
--- a/ui/gfx/geometry/insets_f_unittest.cc
+++ b/ui/gfx/geometry/insets_f_unittest.cc
@@ -98,6 +98,16 @@
   EXPECT_FALSE(insets.IsEmpty());
 }
 
+TEST(InsetsFTest, Transpose) {
+  InsetsF insets = InsetsF::TLBR(1.25, 2.5, 3.75, 4.875);
+  insets.Transpose();
+  EXPECT_EQ(insets, InsetsF::TLBR(2.5, 1.25, 4.875, 3.75));
+
+  insets = InsetsF();
+  insets.Transpose();
+  EXPECT_EQ(insets, InsetsF());
+}
+
 TEST(InsetsFTest, Operators) {
   InsetsF insets =
       InsetsF().set_left(2.5f).set_right(4.1f).set_top(1.f).set_bottom(3.3f);
diff --git a/ui/gfx/geometry/insets_outsets_base.h b/ui/gfx/geometry/insets_outsets_base.h
index 4ca1286..85752c1 100644
--- a/ui/gfx/geometry/insets_outsets_base.h
+++ b/ui/gfx/geometry/insets_outsets_base.h
@@ -6,6 +6,7 @@
 #define UI_GFX_GEOMETRY_INSETS_OUTSETS_BASE_H_
 
 #include <string>
+#include <utility>
 
 #include "base/component_export.h"
 #include "base/numerics/clamped_math.h"
@@ -47,6 +48,13 @@
   // Returns true if the insets/outsets are empty.
   bool IsEmpty() const { return width() == 0 && height() == 0; }
 
+  // Flips x- and y-axes.
+  void Transpose() {
+    using std::swap;
+    swap(top_, left_);
+    swap(bottom_, right_);
+  }
+
   // These setters can be used together with the default constructor and the
   // single-parameter constructor to construct Insets instances, for example:
   //                                                  // T, L, B, R
diff --git a/ui/gfx/geometry/insets_outsets_f_base.h b/ui/gfx/geometry/insets_outsets_f_base.h
index 40fee5e5..0c2d0e5 100644
--- a/ui/gfx/geometry/insets_outsets_f_base.h
+++ b/ui/gfx/geometry/insets_outsets_f_base.h
@@ -6,6 +6,7 @@
 #define UI_GFX_GEOMETRY_INSETS_OUTSETS_F_BASE_H_
 
 #include <string>
+#include <utility>
 
 #include "base/component_export.h"
 
@@ -35,6 +36,13 @@
   // Returns true if the insets/outsets are empty.
   bool IsEmpty() const { return width() == 0.f && height() == 0.f; }
 
+  // Flips x- and y-axes.
+  void Transpose() {
+    using std::swap;
+    swap(top_, left_);
+    swap(bottom_, right_);
+  }
+
   // These setters can be used together with the default constructor and the
   // single-parameter constructor to construct InsetsF instances, for example:
   //                                                    // T, L, B, R
diff --git a/ui/gfx/geometry/insets_unittest.cc b/ui/gfx/geometry/insets_unittest.cc
index 0972ae5..08b44ba7 100644
--- a/ui/gfx/geometry/insets_unittest.cc
+++ b/ui/gfx/geometry/insets_unittest.cc
@@ -121,6 +121,16 @@
   EXPECT_FALSE(insets.IsEmpty());
 }
 
+TEST(InsetsTest, Transpose) {
+  Insets insets = Insets::TLBR(1, 2, 3, 4);
+  insets.Transpose();
+  EXPECT_EQ(insets, Insets::TLBR(2, 1, 4, 3));
+
+  insets = Insets();
+  insets.Transpose();
+  EXPECT_EQ(insets, Insets());
+}
+
 TEST(InsetsTest, Operators) {
   Insets insets = Insets().set_left_right(2, 4).set_top_bottom(1, 3);
   insets += Insets().set_left_right(6, 8).set_top_bottom(5, 7);
diff --git a/ui/gfx/mojom/delegated_ink_point_renderer.mojom b/ui/gfx/mojom/delegated_ink_point_renderer.mojom
index 7434bef..1b06a7d 100644
--- a/ui/gfx/mojom/delegated_ink_point_renderer.mojom
+++ b/ui/gfx/mojom/delegated_ink_point_renderer.mojom
@@ -13,6 +13,7 @@
 // ink trail. When the browser detects the end of the trail, it will call
 // ResetPrediction() so that viz does not predict any points further than what
 // the user is expecting.
+[DirectReceiver]
 interface DelegatedInkPointRenderer {
   // Used to send the DelegatedInkPoint that was created in the browser process
   // to viz in order to be drawn as part of the delegated ink trail.
@@ -22,4 +23,4 @@
   // by previously received points. Used by the browser process when a delegated
   // ink trail should end.
   ResetPrediction();
-};
\ No newline at end of file
+};
diff --git a/ui/ozone/platform/wayland/host/wayland_tablet_tool.cc b/ui/ozone/platform/wayland/host/wayland_tablet_tool.cc
index 9caab86..472f8807 100644
--- a/ui/ozone/platform/wayland/host/wayland_tablet_tool.cc
+++ b/ui/ozone/platform/wayland/host/wayland_tablet_tool.cc
@@ -64,9 +64,7 @@
 
 }  // namespace
 
-WaylandTabletTool::FrameData::FrameData() {
-  pointer_details.pointer_type = EventPointerType::kPen;
-}
+WaylandTabletTool::FrameData::FrameData() = default;
 
 WaylandTabletTool::FrameData::~FrameData() = default;
 
@@ -144,6 +142,7 @@
 
 void WaylandTabletTool::ResetFrameData() {
   frame_data_ = FrameData();
+  frame_data_.pointer_details.pointer_type = pointer_type_;
 }
 
 // static
@@ -151,7 +150,8 @@
                              zwp_tablet_tool_v2* tool,
                              uint32_t tool_type) {
   auto* self = static_cast<WaylandTabletTool*>(data);
-  self->frame_data_.pointer_details.pointer_type = ToolToPointerType(tool_type);
+  self->pointer_type_ = ToolToPointerType(tool_type);
+  self->frame_data_.pointer_details.pointer_type = self->pointer_type_;
 }
 
 // static
@@ -338,6 +338,7 @@
                               zwp_tablet_tool_v2* tool,
                               uint32_t time) {
   auto* self = static_cast<WaylandTabletTool*>(data);
+  self->frame_data_.pointer_details.pointer_type = self->pointer_type_;
   self->frame_data_.timestamp = wl::EventMillisecondsToTimeTicks(time);
   self->DispatchBufferedEvents();
   self->ResetFrameData();
diff --git a/ui/ozone/platform/wayland/host/wayland_tablet_tool.h b/ui/ozone/platform/wayland/host/wayland_tablet_tool.h
index 7d47e081..21be13175 100644
--- a/ui/ozone/platform/wayland/host/wayland_tablet_tool.h
+++ b/ui/ozone/platform/wayland/host/wayland_tablet_tool.h
@@ -116,6 +116,7 @@
   };
 
   FrameData frame_data_;
+  EventPointerType pointer_type_ = EventPointerType::kPen;
 };
 
 class WaylandTabletTool::Delegate {
diff --git a/ui/views/controls/tree/tree_view.cc b/ui/views/controls/tree/tree_view.cc
index 47d0717..8f91aad 100644
--- a/ui/views/controls/tree/tree_view.cc
+++ b/ui/views/controls/tree/tree_view.cc
@@ -879,14 +879,6 @@
     active_node_ = node;
   }
 
-  if (selection_changed) {
-    SchedulePaintForNode(selected_node_);
-    SetAccessibleSelectionForNode(selected_node_, false);
-    selected_node_ = node;
-    SetAccessibleSelectionForNode(selected_node_, true);
-    SchedulePaintForNode(selected_node_);
-  }
-
   if (active_changed && node) {
     // GetForegroundBoundsForNode() returns RTL-flipped coordinates for paint.
     // Un-flip before passing to ScrollRectToVisible(), which uses layout
@@ -910,11 +902,14 @@
   }
 
   if (selection_changed) {
+    SchedulePaintForNode(selected_node_);
+    SetAccessibleSelectionForNode(selected_node_, false);
+    selected_node_ = node;
+    SetAccessibleSelectionForNode(selected_node_, true);
+    SchedulePaintForNode(selected_node_);
     AXVirtualView* ax_selected_view =
         node ? node->accessibility_view() : nullptr;
-    if (ax_selected_view) {
-      ax_selected_view->NotifyEvent(ax::mojom::Event::kSelection, true);
-    } else {
+    if (!ax_selected_view) {
       NotifyAccessibilityEventDeprecated(ax::mojom::Event::kSelection, true);
     }
   }
diff --git a/ui/views/controls/tree/tree_view_unittest.cc b/ui/views/controls/tree/tree_view_unittest.cc
index a82628af..b4f1e072 100644
--- a/ui/views/controls/tree/tree_view_unittest.cc
+++ b/ui/views/controls/tree/tree_view_unittest.cc
@@ -1287,6 +1287,27 @@
       GetAccessibilityViewByName("a"), ax::mojom::Event::kFocus)));
   EXPECT_TRUE(FiredAccessibilityEvent(std::make_pair(
       GetAccessibilityViewByName("a"), ax::mojom::Event::kSelection)));
+
+  // On node selection, ensure the focus event is fired before the selection
+  // event.
+  ClearAccessibilityEvents();
+  tree()->SetModel(&model_);
+  tree()->SetSelectedNode(GetNodeByTitle("b"));
+  std::vector<ax::mojom::Event> events;
+  for (const auto& event : accessibility_events()) {
+    if (event.first == GetAccessibilityViewByName("b")) {
+      events.push_back(event.second);
+    }
+  }
+
+  auto focus_it =
+      std::find(events.begin(), events.end(), ax::mojom::Event::kFocus);
+  auto selection_it =
+      std::find(events.begin(), events.end(), ax::mojom::Event::kSelection);
+  ASSERT_NE(focus_it, events.end());
+  ASSERT_NE(selection_it, events.end());
+  EXPECT_LT(std::distance(events.begin(), focus_it),
+            std::distance(events.begin(), selection_it));
 }
 
 }  // namespace views